def add_effect(tmp_effect, result): """tmp_effect has the following structure: [ConjunctiveEffect] [UniversalEffect] [ConditionalEffect] SimpleEffect.""" if isinstance(tmp_effect, pddl.ConjunctiveEffect): for effect in tmp_effect.effects: add_effect(effect, result) return else: parameters = [] condition = pddl.Truth() if isinstance(tmp_effect, pddl.UniversalEffect): parameters = tmp_effect.parameters if isinstance(tmp_effect.effect, pddl.ConditionalEffect): condition = tmp_effect.effect.condition assert isinstance(tmp_effect.effect.effect, pddl.SimpleEffect) effect = tmp_effect.effect.effect.effect else: assert isinstance(tmp_effect.effect, pddl.SimpleEffect) effect = tmp_effect.effect.effect elif isinstance(tmp_effect, pddl.ConditionalEffect): condition = tmp_effect.condition assert isinstance(tmp_effect.effect, pddl.SimpleEffect) effect = tmp_effect.effect.effect else: assert isinstance(tmp_effect, pddl.SimpleEffect) effect = tmp_effect.effect assert isinstance(effect, pddl.Literal) # Check for contradictory effects condition = condition.simplified() new_effect = pddl.Effect(parameters, condition, effect) contradiction = pddl.Effect(parameters, condition, effect.negate()) ### REMOVED CONTRADICTION CHECK result.append(new_effect)
def instantiate_unsatisfiable(state, action, var_mapping, negative_from_name={}): precondition = [] for effect in action.effects: if effect.literal.predicate == UNSATISFIABLE: # Condition must be false for plan to succeed conditions = set(get_conjunctive_parts(effect.condition)) negative = { literal for literal in conditions if literal.predicate in negative_from_name } if not negative: continue assert len(negative) == 1 # TODO: handle the case where negative is not used (not (CFree ..)) normal_conjunction = pddl.Conjunction(conditions - negative) # TODO: assumes that can instantiate with just predicate_to_atoms normal_effect = pddl.Effect(effect.parameters, normal_conjunction, effect.literal) # TODO: avoid recomputing these objects_by_type = instantiate.get_objects_by_type([], []) predicate_to_atoms = instantiate.get_atoms_by_predicate(state) result = [] normal_effect.instantiate(var_mapping, state, {effect.literal}, objects_by_type, predicate_to_atoms, result) for _, _, _, mapping in result: for literal in negative: new_literal = literal.rename_variables(mapping).negate() assert (not new_literal.free_variables()) precondition.append(new_literal) return precondition
def get_stream_action(result, name, unit_cost, effect_scale=1): #from pddl_parser.parsing_functions import parse_action import pddl parameters = [] preconditions = [ fd_from_fact(fact) for fact in result.instance.get_domain() ] precondition = pddl.Conjunction(preconditions) effects = [ pddl.Effect(parameters=[], condition=pddl.Truth(), literal=fd_from_fact(fact)) for fact in result.get_certified() ] effort = 1 if unit_cost else result.instance.get_effort() if effort == INF: return None fluent = pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]) expression = pddl.NumericConstant(int_ceil(effect_scale * effort)) # Integer cost = pddl.Increase(fluent=fluent, expression=expression) # Can also be None return pddl.Action(name=name, parameters=parameters, num_external_parameters=len(parameters), precondition=precondition, effects=effects, cost=cost)
def optimizer_conditional_effects(domain, externals): import pddl #from pddlstream.algorithms.scheduling.negative import get_negative_predicates # TODO: extend this to predicates if UNIVERSAL_TO_CONDITIONAL: negative_streams = list(filter(lambda e: e.is_negated, externals)) else: negative_streams = list( filter(lambda e: isinstance(e, ConstraintStream) and e.is_negated, externals)) negative_from_predicate = get_predicate_map(negative_streams) if not negative_from_predicate: return for action in domain.actions: universal_to_conditional(action) new_effects = [] for effect in action.effects: if effect.literal.predicate != UNSATISFIABLE: new_effects.append(effect) continue new_parts, stream_facts = process_conditional_effect( effect, negative_from_predicate) if not stream_facts: new_effects.append(effect) for stream_fact in stream_facts: new_effects.append( pddl.Effect(effect.parameters, pddl.Conjunction(new_parts), stream_fact)) action.effects = new_effects
def add_effect(tmp_effect, result): """tmp_effect has the following structure: [ConjunctiveEffect] [UniversalEffect] [ConditionalEffect] SimpleEffect.""" if isinstance(tmp_effect, pddl.ConjunctiveEffect): for effect in tmp_effect.effects: add_effect(effect, result) return else: parameters = [] condition = pddl.Truth() if isinstance(tmp_effect, pddl.UniversalEffect): parameters = tmp_effect.parameters if isinstance(tmp_effect.effect, pddl.ConditionalEffect): condition = tmp_effect.effect.condition assert isinstance(tmp_effect.effect.effect, pddl.SimpleEffect) effect = tmp_effect.effect.effect.effect else: assert isinstance(tmp_effect.effect, pddl.SimpleEffect) effect = tmp_effect.effect.effect elif isinstance(tmp_effect, pddl.ConditionalEffect): condition = tmp_effect.condition assert isinstance(tmp_effect.effect, pddl.SimpleEffect) effect = tmp_effect.effect.effect else: assert isinstance(tmp_effect, pddl.SimpleEffect) effect = tmp_effect.effect if effect is None: # Nothing to add return assert isinstance(effect, pddl.Literal) # Check for contradictory effects condition = condition.simplified() new_effect = pddl.Effect(parameters, condition, effect) contradiction = pddl.Effect(parameters, condition, effect.negate()) if not contradiction in result: result.append(new_effect) else: # We use add-after-delete semantics, keep positive effect if isinstance(contradiction.literal, pddl.NegatedAtom): result.remove(contradiction) result.append(new_effect)
def get_necessary_axioms(conditions, axioms, negative_from_name): if not conditions or not axioms: return {} axioms_from_name = get_derived_predicates(axioms) atom_queue = [] processed_atoms = set() def add_literals(literals): for lit in literals: atom = lit.positive() if atom not in processed_atoms: atom_queue.append( atom) # Previously was lit.positive() for some reason? processed_atoms.add(atom) import pddl add_literals(conditions) axiom_from_action = {} partial_instantiations = set() while atom_queue: literal = atom_queue.pop() for axiom in axioms_from_name[literal.predicate]: derived_parameters = axiom.parameters[:axiom. num_external_parameters] var_mapping = { p.name: a for p, a in zip(derived_parameters, literal.args) if not is_parameter(a) } key = (axiom, frozenset(var_mapping.items())) if key in partial_instantiations: continue partial_instantiations.add(key) parts = [ l.rename_variables(var_mapping) for l in get_literals(axiom.condition) if l.predicate not in negative_from_name ] # Assumes a conjunction? # new_condition = axiom.condition.uniquify_variables(None, var_mapping) effect_args = [ var_mapping.get(a.name, a.name) for a in derived_parameters ] effect = pddl.Effect([], pddl.Truth(), pddl.conditions.Atom(axiom.name, effect_args)) free_parameters = [ p for p in axiom.parameters if p.name not in var_mapping ] new_action = pddl.Action(axiom.name, free_parameters, len(free_parameters), pddl.Conjunction(parts), [effect], None) # Creating actions so I can partially instantiate (impossible with axioms) axiom_from_action[new_action] = (axiom, var_mapping) add_literals(parts) return axiom_from_action
def get_necessary_axioms(instance, axioms, negative_from_name): import pddl axioms_from_name = get_derived_predicates(axioms) atom_queue = [] processed_atoms = set() def add_literals(literals): for literal in literals: atom = literal.positive() if atom not in processed_atoms: atom_queue.append(literal.positive()) processed_atoms.add(atom) add_literals(instance.precondition) for (cond, _) in (instance.add_effects + instance.del_effects): add_literals(cond) axiom_from_action = {} partial_instantiations = set() while atom_queue: literal = atom_queue.pop() for axiom in axioms_from_name[literal.predicate]: derived_parameters = axiom.parameters[:axiom. num_external_parameters] var_mapping = { p.name: a for p, a in zip(derived_parameters, literal.args) if a[0] != '?' } key = (axiom, frozenset(var_mapping.items())) if key in partial_instantiations: continue partial_instantiations.add(key) parts = [] for literal in get_literals(axiom.condition): if literal.predicate in negative_from_name: continue parts.append(literal.rename_variables(var_mapping)) # new_condition = axiom.condition.uniquify_variables(None, var_mapping) effect_args = [ var_mapping.get(a.name, a.name) for a in derived_parameters ] effect = pddl.Effect([], pddl.Truth(), pddl.conditions.Atom(axiom.name, effect_args)) free_parameters = [ p for p in axiom.parameters if p.name not in var_mapping ] new_action = pddl.Action(axiom.name, free_parameters, 0, pddl.Conjunction(parts), [effect], None) # Creating actions so I can partially instantiate (impossible with axioms) axiom_from_action[new_action] = (axiom, var_mapping) add_literals(parts) return axiom_from_action
def universal_to_conditional(action): import pddl new_parts = [] unsatisfiable = fd_from_fact((UNSATISFIABLE,)) for quant in get_conjunctive_parts(action.precondition): if isinstance(quant, pddl.UniversalCondition): condition = quant.parts[0] # TODO: normalize first? if isinstance(condition, pddl.Disjunction) or isinstance(condition, pddl.Literal): action.effects.append(pddl.Effect(quant.parameters, condition.negate(), unsatisfiable)) continue new_parts.append(quant) action.precondition = pddl.Conjunction(new_parts)
def optimizer_conditional_effects(domain, externals): import pddl #from pddlstream.algorithms.scheduling.negative import get_negative_predicates # TODO: extend this to predicates if UNIVERSAL_TO_CONDITIONAL: negative_streams = list(filter(lambda e: e.is_negated(), externals)) else: negative_streams = list( filter( lambda e: isinstance(e, ConstraintStream) and e.is_negated(), externals)) negative_from_predicate = get_predicate_map(negative_streams) if not negative_from_predicate: return for action in domain.actions: universal_to_conditional(action) new_effects = [] for effect in action.effects: if effect.literal.predicate != UNSATISFIABLE: new_effects.append(effect) continue new_parts = [] stream_facts = [] for disjunctive in get_conjunctive_parts(effect.condition): for literal in get_disjunctive_parts(disjunctive): # TODO: assert only one disjunctive part if isinstance(literal, pddl.Literal) and ( literal.predicate in negative_from_predicate): stream = negative_from_predicate[literal.predicate] if not isinstance(stream, ConstraintStream): new_parts.append(literal) continue certified = find_unique( lambda f: get_prefix(f) == literal.predicate, stream.certified) mapping = get_mapping(get_args(certified), literal.args) stream_facts.append( fd_from_fact( substitute_expression(stream.stream_fact, mapping))) # TODO: add the negated literal as precondition here? else: new_parts.append(literal) if not stream_facts: new_effects.append(effect) for stream_fact in stream_facts: new_effects.append( pddl.Effect(effect.parameters, pddl.Conjunction(new_parts), stream_fact)) action.effects = new_effects
def visit_effect_stmt(self, node): """ Visits a PDDL effect statement.""" formula = node.formula effect = pddl.Effect() # For now we only allow 'and' in the effect. if formula.key == 'and': for c in formula.children: # Call helper. self.add_effect(effect, c) else: # Call helper. self.add_effect(effect, formula) # Store effect in node. self.set_in(node, effect)
def remove_duration_variable(task): def recurse(condition, act, time, duration, pnes): if isinstance(condition, pddl.FunctionComparison): parts = [ exp.remove_duration_variable(act, time, duration, pnes) for exp in condition.parts ] return condition.__class__(condition.comparator, parts) # return pddl.FunctionComparison(condition.comparator,parts) else: new_parts = [ recurse(part, act, time, duration, pnes) for part in condition.parts ] return condition.change_parts(new_parts) for act in task.durative_actions: assert len(act.duration[1]) == 0, "at end durations are not supported" assert len(act.duration[0]) == 1 and act.duration[0][0][0] == "=" duration = act.duration[0][0][1] duration_functions = [] # remove from action conditions condition = [] for time, cond in enumerate(act.condition): condition.append( recurse(cond, act, time, duration, duration_functions)) act.condition = condition for time in range(2): for eff in act.effects[time]: # remove from effect condition condition = [] for eff_time, cond in enumerate(eff.condition): condition.append( recurse(cond, act, eff_time, duration, duration_functions)) eff.condition = condition # remove from effect if isinstance(eff.peffect, pddl.FunctionAssignment): assign = eff.peffect assign.expression = assign.expression.remove_duration_variable( act, time, duration, duration_functions) for pne in duration_functions: assign = pddl.Assign(pne, duration) condition = [pddl.Truth(), pddl.Truth(), pddl.Truth()] effect = pddl.Effect([], condition, assign) act.effects[0].append(effect) task.function_symbols[pne.symbol] = "number"
def convert_effects(effects): import pddl new_effects = make_effects([('_noop',)]) # To ensure the action has at least one effect for effect in effects: class_name = effect.__class__.__name__ if class_name == 'Effect': peffect_name = effect.peffect.__class__.__name__ if peffect_name in ('Increase', 'Decrease'): # TODO: currently ignoring numeric conditions continue new_effects.append(pddl.Effect(convert_parameters(effect.parameters), pddl.Conjunction(list(map(convert_condition, effect.condition))).simplified(), convert_condition(effect.peffect))) else: raise NotImplementedError(class_name) return new_effects
def compile_to_exogenous_actions(evaluations, domain, streams): import pddl # TODO: automatically derive fluents # TODO: version of this that operates on fluents of length one? # TODO: better instantiation when have full parameters # TODO: conversion from stream cost to real cost units? # TODO: any predicates derived would need to be replaced as well fluent_predicates = get_fluents(domain) domain_predicates = {get_prefix(a) for s in streams for a in s.domain} if not (domain_predicates & fluent_predicates): return certified_predicates = {get_prefix(a) for s in streams for a in s.certified} future_map = {p: 'f-{}'.format(p) for p in certified_predicates} augment_evaluations(evaluations, future_map) rename_future = lambda a: rename_atom(a, future_map) for stream in list(streams): if not isinstance(stream, Stream): raise NotImplementedError(stream) # TODO: could also just have conditions asserting that one of the fluent conditions fails streams.append(create_static_stream(stream, evaluations, fluent_predicates, rename_future)) stream_atom = streams[-1].certified[0] parameters = [pddl.TypedObject(p, 'object') for p in get_args(stream_atom)] # TODO: add to predicates as well? domain.predicate_dict[get_prefix(stream_atom)] = pddl.Predicate(get_prefix(stream_atom), parameters) precondition = pddl.Conjunction(tuple(map(fd_from_fact, (stream_atom,) + tuple(stream.domain)))) effects = [pddl.Effect(parameters=[], condition=pddl.Truth(), literal=fd_from_fact(fact)) for fact in stream.certified] effort = 1 # TODO: use stream info #effort = 1 if unit_cost else result.instance.get_effort() #if effort == INF: # continue fluent = pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]) expression = pddl.NumericConstant(int_ceil(effort)) # Integer cost = pddl.Increase(fluent=fluent, expression=expression) # Can also be None domain.actions.append(pddl.Action(name='call-{}'.format(stream.name), parameters=parameters, num_external_parameters=len(parameters), precondition=precondition, effects=effects, cost=cost)) stream.certified = tuple(set(stream.certified) | set(map(rename_future, stream.certified)))
def simplify_actions(opt_evaluations, action_plan, task, actions, unit_costs): # TODO: add ordering constraints to simplify the optimization import pddl import instantiate fluent_facts = MockSet() init_facts = set() type_to_objects = instantiate.get_objects_by_type(task.objects, task.types) results_from_head = get_results_from_head(opt_evaluations) action_from_name = {} function_plan = set() for i, (name, args) in enumerate(action_plan): action = find_unique(lambda a: a.name == name, actions) assert (len(action.parameters) == len(args)) # parameters = action.parameters[:action.num_external_parameters] var_mapping = {p.name: a for p, a in zip(action.parameters, args)} new_name = '{}-{}'.format(name, i) new_parameters = action.parameters[len(args):] new_preconditions = [] action.precondition.instantiate(var_mapping, init_facts, fluent_facts, new_preconditions) new_effects = [] for eff in action.effects: eff.instantiate(var_mapping, init_facts, fluent_facts, type_to_objects, new_effects) new_effects = [pddl.Effect([], pddl.Conjunction(conditions), effect) for conditions, effect in new_effects] cost = pddl.Increase(fluent=pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]), expression=pddl.NumericConstant(1)) # cost = None task.actions.append(pddl.Action(new_name, new_parameters, len(new_parameters), pddl.Conjunction(new_preconditions), new_effects, cost)) action_from_name[new_name] = (name, map(obj_from_pddl, args)) if not unit_costs: function_result = extract_function_results(results_from_head, action, args) if function_result is not None: function_plan.add(function_result) return action_from_name, list(function_plan)
def make_effects(effects): return [ pddl.Effect(parameters=[], condition=pddl.Truth(), literal=fd_from_fact(fact)) for fact in effects ]
def reinstantiate_action(state, instance, negative_from_name={}): # Recomputes the instances with without any pruned preconditions # TODO: making the assumption that no negative derived predicates action = instance.action var_mapping = instance.var_mapping init_facts = set() fluent_facts = MockSet() precondition = [] try: action.precondition.instantiate(var_mapping, init_facts, fluent_facts, precondition) except pddl.conditions.Impossible: return None effects = [] effect_from_literal = { literal: (cond, effect, effect_mapping) for cond, literal, effect, effect_mapping in instance.effect_mappings } for effect in action.effects: if effect.literal.predicate == UNSATISFIABLE: # Condition must be false for plan to succeed conditions = set(get_conjunctive_parts(effect.condition)) negative = { literal for literal in conditions if literal.predicate in negative_from_name } if not negative: continue assert len(negative) == 1 # TODO: handle the case where negative is not used (not (CFree ..)) normal_conjunction = pddl.Conjunction(conditions - negative) # TODO: assumes that can instantiate with just predicate_to_atoms normal_effect = pddl.Effect(effect.parameters, normal_conjunction, effect.literal) # TODO: avoid recomputing these objects_by_type = instantiate.get_objects_by_type([], []) predicate_to_atoms = instantiate.get_atoms_by_predicate(state) result = [] normal_effect.instantiate(var_mapping, state, {effect.literal}, objects_by_type, predicate_to_atoms, result) for _, _, _, mapping in result: for literal in negative: new_literal = literal.rename_variables(mapping).negate() assert (not new_literal.free_variables()) precondition.append(new_literal) for literal in instance.applied_effects: cond, effect, effect_mapping = effect_from_literal[literal] if effect is None: # Stream effect #effects.append((cond, literal, cond, effect)) continue else: effect._instantiate(effect_mapping, init_facts, fluent_facts, effects) new_effects = [] for cond, effect, e, m in effects: precondition.extend(cond) new_effects.append(([], effect, e, m)) return pddl.PropositionalAction(instance.name, precondition, new_effects, instance.cost, action, var_mapping)
def add_effect(tmp_effect, result): # adds effect from tmp_effect to result """tmp_effect has the following structure: [ConjunctiveEffect] [UniversalEffect] [ConditionalEffect] SimpleEffect.""" if isinstance(tmp_effect, pddl.ConjunctiveEffect): for effect in tmp_effect.effects: add_effect(effect, result) return else: parameters = [] condition = pddl.Truth() if isinstance(tmp_effect, pddl.UniversalEffect): # print ("universal effect: ", tmp_effect) parameters = tmp_effect.parameters if isinstance(tmp_effect.effect, pddl.ConditionalEffect): condition = tmp_effect.effect.condition assert isinstance(tmp_effect.effect.effect, pddl.SimpleEffect)\ or isinstance(tmp_effect.effect.effect, pddl.NumericEffect) effect = tmp_effect.effect.effect.effect else: assert isinstance(tmp_effect.effect, pddl.SimpleEffect)\ or isinstance(tmp_effect.effect, pddl.NumericEffect), "Effect is not primitive but %s" % tmp_effect.effect.effect.__class__ effect = tmp_effect.effect.effect elif isinstance(tmp_effect, pddl.ConditionalEffect): # print ("conditional effect: ", tmp_effect) condition = tmp_effect.condition assert isinstance(tmp_effect.effect, pddl.SimpleEffect) effect = tmp_effect.effect.effect elif isinstance(tmp_effect, pddl.SimpleEffect): # print ("simple effect: ", tmp_effect) effect = tmp_effect.effect assert isinstance(effect, pddl.Literal) else: # print ("numeric effect: ", tmp_effect) effect = tmp_effect.effect assert isinstance(effect, pddl.FunctionAssignment) # Check for contradictory effects condition = condition.simplified() new_effect = pddl.Effect(parameters, condition, effect) if isinstance(effect, pddl.FunctionAssignment): # The check for multiple effects on the same variable loops through all effects over and over again. # If this raises performance issues it has to be reconsidered # print("Check for same effect on numeric variable %s" % effect.fluent) conflict = False for other_effect in result: if isinstance(other_effect, pddl.FunctionAssignment): # print("comparing to %s -> Conflict? %s" % (other_effect.fluent, (effect.fluent == other_effect.fluent))) if (effect.fluent == other_effect.fluent): conflict = True break if conflict: print( "Warning, multiple effects on numeric variable %s, ignoring %s" % (other_effect.fluent, effect.fluent)) else: result.append(new_effect) else: assert isinstance(effect, pddl.Literal) contradiction = pddl.Effect(parameters, condition, effect.negate()) if not contradiction in result: result.append(new_effect) else: # We use add-after-delete semantics, keep positive effect if isinstance(contradiction.peffect, pddl.NegatedAtom): result.remove(contradiction) result.append(new_effect)
def sequential_stream_plan(evaluations, goal_expression, domain, stream_results, negated, unit_costs=True, **kwargs): if negated: raise NotImplementedError() # TODO: compute preimage and make that the goal instead opt_evaluations = evaluations_from_stream_plan(evaluations, stream_results) opt_task = task_from_domain_problem(domain, get_problem(opt_evaluations, goal_expression, domain, unit_costs)) action_plan, action_cost = solve_from_task(opt_task, **kwargs) if action_plan is None: return None, action_cost import instantiate fluent_facts = MockSet() init_facts = set() task = task_from_domain_problem(domain, get_problem(evaluations, goal_expression, domain, unit_costs)) type_to_objects = instantiate.get_objects_by_type(task.objects, task.types) task.actions, stream_result_from_name = get_stream_actions(stream_results) results_from_head = get_results_from_head(opt_evaluations) # TODO: add ordering constraints to simplify the optimization import pddl action_from_name = {} function_plan = set() for i, (name, args) in enumerate(action_plan): action = find_unique(lambda a: a.name == name, domain.actions) assert(len(action.parameters) == len(args)) #parameters = action.parameters[:action.num_external_parameters] var_mapping = {p.name: a for p, a in zip(action.parameters, args)} new_name = '{}-{}'.format(name, i) new_parameters = action.parameters[len(args):] new_preconditions = [] action.precondition.instantiate(var_mapping, init_facts, fluent_facts, new_preconditions) new_effects = [] for eff in action.effects: eff.instantiate(var_mapping, init_facts, fluent_facts, type_to_objects, new_effects) new_effects = [pddl.Effect([], pddl.Conjunction(conditions), effect) for conditions, effect in new_effects] cost = pddl.Increase(fluent=pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]), expression=pddl.NumericConstant(1)) #cost = None task.actions.append(pddl.Action(new_name, new_parameters, 0, pddl.Conjunction(new_preconditions), new_effects, cost)) action_from_name[new_name] = (name, map(obj_from_pddl, args)) if not unit_costs: function_plan.update(extract_function_results(results_from_head, action, args)) planner = kwargs.get('planner', 'ff-astar') combined_plan, _ = solve_from_task(task, planner=planner, **kwargs) if combined_plan is None: return None, obj_from_pddl_plan(action_plan), INF stream_plan = [] action_plan = [] for name, args in combined_plan: if name in stream_result_from_name: stream_plan.append(stream_result_from_name[name]) else: action_plan.append(action_from_name[name]) stream_plan += list(function_plan) combined_plan = stream_plan + action_plan return combined_plan, action_cost