def __init__(self, optimizer, external_plan): optimizer.streams.append(self) self.optimizer = optimizer self.stream_plan, self.function_plan = partition_external_plan( external_plan) inputs, domain, outputs, certified, functions, self.macro_from_micro, \ self.input_objects, self.output_objects, self.fluent_facts = get_cluster_values(external_plan) hint = {} for result, mapping in safe_zip(self.stream_plan, self.macro_from_micro): if isinstance(result, StreamResult): for param, obj in safe_zip(result.external.outputs, result.output_objects): if isinstance(obj, Object): hint[mapping[param]] = obj.value self.objectives = certified + functions gen_fn = get_list_gen_fn(optimizer.procedure, inputs, outputs, self.objectives, hint=hint) #assert len(self.get_cluster_plans()) == 1 super(OptimizerStream, self).__init__(optimizer.name, gen_fn, inputs, domain, outputs, certified, optimizer.info)
def solve_pddlstream_satisfaction(stream_pddl, stream_map, init, constraints, incremental=False, **kwargs): # TODO: prune set of streams based on constraints domain, goal = planning_from_satisfaction(init, constraints) constant_map = {} problem = PDDLProblem(domain, constant_map, stream_pddl, stream_map, init, goal) if incremental: plan, cost, facts = solve_incremental(problem, **kwargs) else: plan, cost, facts = solve_focused(problem, **kwargs) if plan is None: return None, cost, facts assert len(plan) == len(domain.actions) bindings = {} for action, (name, args) in safe_zip(domain.actions, plan): assert action.name == name for param, arg in safe_zip(action.parameters, args): name = param.name assert bindings.get(name, arg) is arg bindings[name] = arg return bindings, cost, facts
def create_hint(self): hint = {} for result, mapping in safe_zip(self.stream_plan, self.macro_from_micro): if isinstance(result, StreamResult): for param, obj in safe_zip(result.external.outputs, result.output_objects): if isinstance(obj, Object): hint[mapping[param]] = obj.value return hint
def test_mapping(atoms1, atoms2): mapping = {} for a1, a2 in safe_zip(atoms1, atoms2): assert a1.function == a2.function for arg1, arg2 in safe_zip(a1.args, a2.args): if mapping.get(arg1, arg2) == arg2: mapping[arg1] = arg2 else: return None return mapping
def bindings_from_plan(problem, plan): if plan is None: return None domain = problem[0] bindings = {} for action, (name, args) in safe_zip(domain.actions, plan): assert action.name == name for param, arg in safe_zip(action.parameters, args): name = param.name assert bindings.get(name, arg) is arg bindings[name] = arg return bindings
def recover_negative_axioms(real_task, opt_task, axiom_plans, action_plan, negative_from_name): action_plan = reinstantiate_action_instances(opt_task, action_plan) axiom_plans = list(map(reinstantiate_axiom_instances, axiom_plans)) axioms_from_name = get_derived_predicates(opt_task.axioms) # TODO: could instead just accumulate difference between real and opt opt_task.init = set(opt_task.init) real_states = [set(real_task.init)] preimage_plan = [] for axiom_plan, action_instance in safe_zip(axiom_plans, action_plan): for literal in action_instance.precondition: # TODO: check conditional effects if literal.predicate in negative_from_name: raise NotImplementedError( 'Negated predicates not currently supported within actions: {}' .format(literal.predicate)) simplify_conditional_effects(real_states[-1], opt_task.init, action_instance, axioms_from_name) preimage = list(plan_preimage(axiom_plan + [action_instance], [])) assert conditions_hold( opt_task.init, (l for l in preimage if l.predicate not in axioms_from_name)) new_axiom_plan = extract_axiom_plan(opt_task, preimage, negative_from_name, static_state=real_states[-1]) assert new_axiom_plan is not None preimage_plan.extend(new_axiom_plan + axiom_plan + [action_instance]) if action_instance.name != GOAL_NAME: apply_action(opt_task.init, action_instance) real_states.append(set(real_states[-1])) apply_action(real_states[-1], action_instance) return real_states, preimage_plan
def recover_negative_axioms(real_task, opt_task, axiom_plans, action_plan, negative_from_name): action_plan = reinstantiate_action_instances(opt_task, action_plan) simplify_conditional_effects(opt_task, action_plan, negative_from_name) axiom_plans = list(map(reinstantiate_axiom_instances, axiom_plans)) axioms_from_name = get_derived_predicates(opt_task.axioms) # TODO: could instead just accumulate difference between real and opt opt_task.init = set(opt_task.init) real_states = [set(real_task.init)] preimage_plan = [] for axiom_plan, action_instance in safe_zip(axiom_plans, action_plan): preimage = list(plan_preimage(axiom_plan + [action_instance], [])) assert conditions_hold( opt_task.init, (l for l in preimage if (l.predicate not in axioms_from_name and ( l.predicate not in negative_from_name)))) # TODO: only add derived facts and negative facts to fluent state to make normalizing easier new_axiom_plan = extract_axiom_plan(opt_task, preimage, negative_from_name, static_state=opt_task.init) #static_state=real_states[-1]) assert new_axiom_plan is not None preimage_plan.extend(new_axiom_plan + axiom_plan + [action_instance]) if action_instance.name != GOAL_NAME: apply_action(opt_task.init, action_instance) real_states.append(set(real_states[-1])) apply_action(real_states[-1], action_instance) return real_states, preimage_plan
def simple_from_durative_action(durative_actions, fluents): import pddl simple_actions = {} for action in durative_actions: parameters = convert_parameters(action.parameters) conditions = list(map(convert_condition, action.condition)) start_effects, end_effects = action.effects over_effects = [] effects = list( map(convert_effects, [start_effects, over_effects, end_effects])) static_condition = pddl.Conjunction( list({ literal for condition in conditions for literal in get_conjunctive_parts(condition.simplified()) if literal.predicate not in fluents })) actions = [] for i, (condition, effect) in enumerate(safe_zip(conditions, effects)): actions.append( pddl.Action( SIMPLE_TEMPLATE.format(action.name, i), parameters, len(parameters), pddl.Conjunction([static_condition, condition]).simplified(), effect, None)) #actions[-1].dump() simple_actions[action] = actions return simple_actions
def recover_negative_axioms(real_task, opt_task, axiom_plans, action_plan, negative_from_name): action_plan = reinstantiate_action_instances( opt_task, action_plan, negative_from_name=negative_from_name) # https://github.com/caelan/pddlstream/commit/18b303e19bbab9f8e0016fbb2656f461067e1e94#diff-55454a85485551f9139e20a446b56a83L53 #simplify_conditional_effects(opt_task, action_plan, negative_from_name) axiom_plans = list(map(reinstantiate_axiom_instances, axiom_plans)) axioms_from_name = get_derived_predicates(opt_task.axioms) # TODO: could instead just accumulate difference between real and opt opt_task.init = set(opt_task.init) real_states = [set(real_task.init)] preimage_plan = [] for axiom_plan, action_instance in safe_zip(axiom_plans, action_plan): preimage = [ l for l in plan_preimage(axiom_plan + [action_instance]) if (l.predicate in axioms_from_name) ] #assert conditions_hold(opt_task.init, conditions) # TODO: only add derived facts and negative facts to fluent state to make normalizing easier negative_axiom_plan = extract_axiom_plan(opt_task, preimage, negative_from_name, static_state=opt_task.init) #static_state=real_states[-1]) assert negative_axiom_plan is not None preimage_plan.extend(negative_axiom_plan + axiom_plan + [action_instance]) if action_instance.name != GOAL_NAME: apply_action(opt_task.init, action_instance) real_states.append(set(real_states[-1])) apply_action(real_states[-1], action_instance) return real_states, preimage_plan
def simple_from_durative_action(durative_actions, fluents): from pddlstream.algorithms.algorithm import get_predicates import pddl simple_actions = {} for action in durative_actions: parameters = convert_parameters(action.parameters) conditions = list(map(convert_condition, action.condition)) start_effects, end_effects = action.effects over_effects = [] effects = list( map(convert_effects, [start_effects, over_effects, end_effects])) static_condition = pddl.Conjunction( list({ part for condition in conditions for part in get_conjunctive_parts(condition.simplified()) if not isinstance(part, pddl.Truth) and not (get_predicates(part) & fluents) })) # TODO: deal with case where there are fluents actions = [] for i, (condition, effect) in enumerate(safe_zip(conditions, effects)): # TODO: extract the durations by pretending they are action costs actions.append( pddl.Action( SIMPLE_TEMPLATE.format(action.name, i), parameters, len(parameters), pddl.Conjunction([static_condition, condition]).simplified(), effect, None)) #actions[-1].dump() simple_actions[action] = actions return simple_actions
def is_useful_atom(atom, conditions_from_predicate): # TODO: this is currently a bottleneck. Instantiate for all actions along the plan first? (apply before checking) if not isinstance(atom, pddl.Atom): return False for atom2 in conditions_from_predicate[atom.predicate]: if all(is_parameter(a2) or (a1 == a2) for a1, a2 in safe_zip(atom.args, atom2.args)): return True return False
def bindings_from_plan(plan_skeleton, action_plan): if action_plan is None: return None bindings = {} for (args1,), (args2,) in safe_zip(plan_skeleton, action_plan): parameter_names = [o.value for o in args1] bindings.update(get_mapping(parameter_names, args2)) return bindings
def update_bindings(bindings, opt_result, result): if not isinstance(result, StreamResult): return bindings new_bindings = bindings.copy() for opt, obj in safe_zip(opt_result.output_objects, result.output_objects): assert (opt not in new_bindings) # TODO: return failure if conflicting bindings new_bindings[opt] = obj return new_bindings
def convert_fluent_streams(stream_plan, real_states, action_plan, step_from_fact, node_from_atom): #return stream_plan import pddl assert len(real_states) == len(action_plan) + 1 steps_from_stream = get_steps_from_stream(stream_plan, step_from_fact, node_from_atom) # TODO: ensure that derived facts aren't in fluents? # TODO: handle case where costs depend on the outputs _, outgoing_edges = neighbors_from_orders( get_partial_orders(stream_plan, init_facts=map( fact_from_fd, filter(lambda f: isinstance(f, pddl.Atom), real_states[0])))) static_plan = [] fluent_plan = [] for result in stream_plan: external = result.external if isinstance(result, FunctionResult) or (result.opt_index != 0) or ( not external.is_fluent): static_plan.append(result) continue if outgoing_edges[result]: # No way of taking into account the binding of fluent inputs when preventing cycles raise NotImplementedError( 'Fluent stream is required for another stream: {}'.format( result)) #if (len(steps_from_stream[result]) != 1) and result.output_objects: # raise NotImplementedError('Fluent stream required in multiple states: {}'.format(result)) for state_index in steps_from_stream[result]: new_output_objects = [ #OptimisticObject.from_opt(out.value, object()) OptimisticObject.from_opt( out.value, UniqueOptValue(result.instance, object(), name)) for name, out in safe_zip(result.external.outputs, result.output_objects) ] if new_output_objects and (state_index <= len(action_plan) - 1): # TODO: check that the objects aren't used in any effects instance = copy.copy(action_plan[state_index]) action_plan[state_index] = instance output_mapping = get_mapping( list(map(pddl_from_object, result.output_objects)), list(map(pddl_from_object, new_output_objects))) instance.var_mapping = { p: output_mapping.get(v, v) for p, v in instance.var_mapping.items() } new_instance = get_fluent_instance(external, result.instance.input_objects, real_states[state_index]) # TODO: handle optimistic here new_result = new_instance.get_result(new_output_objects, opt_index=result.opt_index) fluent_plan.append(new_result) return static_plan + fluent_plan
def bindings_from_plan(plan_skeleton, action_plan): if action_plan is None: return None bindings = {} for (name1, args1), (name2, args2) in safe_zip(plan_skeleton, action_plan): assert name1 == name2 parameter_names = [o.value for o in args1] bindings.update(get_mapping(parameter_names, args2)) return bindings
def wrap_optimistic(self, output_values, call_index): output_objects = [] for name, value in safe_zip(self.external.outputs, output_values): unique = UniqueOptValue(instance=self, sequence_index=call_index, output=name) # object() param = unique if ( self.opt_index == 0) else value # TODO: make a proper abstraction generator output_objects.append(OptimisticObject.from_opt(value, param)) return tuple(output_objects)
def update_instances(self): updated = False for index, (opt_result, attempt) in enumerate(safe_zip(self.remaining_results, self.stream_attempts)): if self.enumerated: return updated if opt_result.instance.num_calls != attempt: updated = True for new_result in opt_result.instance.get_results(start=attempt): self._instantiate(index, new_result) self.stream_attempts[index] = opt_result.instance.num_calls self.enumerated |= opt_result.instance.enumerated return updated
def recover_optimistic_outputs(stream_plan): if not is_plan(stream_plan): return stream_plan new_mapping = {} new_stream_plan = [] for result in stream_plan: new_result = result.remap_inputs(new_mapping) new_stream_plan.append(new_result) if isinstance(new_result, StreamResult): opt_result = new_result.instance.opt_results[ 0] # TODO: empty if disabled new_mapping.update( safe_zip(new_result.output_objects, opt_result.output_objects)) return new_stream_plan
def compute_pruning_orders(results, stats_fn=Performance.get_statistics, tiebreaker_fn=lambda v: None): # TODO: reason about pairs that don't have a (transitive) ordering # TODO: partial orders make this heuristic not optimal # TODO: use result.external.name to cluster? dominates = lambda v1, v2: all(s1 <= s2 for s1, s2 in safe_zip(stats_fn(v1), stats_fn(v2))) \ and tiebreaker_fn(v1) <= tiebreaker_fn(v2) effort_orders = set() for v1, v2 in combinations(results, r=2): # randomize if dominates(v1, v2): effort_orders.add((v1, v2)) # Includes equality elif dominates(v2, v1): effort_orders.add((v2, v1)) return effort_orders
def _add_combinations_relation(self, stream, atoms): if not all(atoms): return # TODO: might be a bug here? domain = list(map(head_from_fact, stream.domain)) # TODO: compute this first? relations = [ Relation(filter(is_parameter, domain[index].args), [ tuple(a for a, b in safe_zip(atom.args, domain[index].args) if is_parameter(b)) for atom in atoms[index] ]) for index in compute_order(domain, atoms) ] solution = solve_satisfaction(relations) for element in solution.body: mapping = solution.get_mapping(element) input_objects = safe_apply_mapping(stream.inputs, mapping) self.push_instance(stream.get_instance(input_objects))
def optimistic_stream_evaluation(evaluations, stream_plan, use_bindings=True): # TODO: can also use the instantiator and operate directly on the outputs # TODO: could bind by just using new_evaluations evaluations = set(evaluations) # Converts to a set for subset testing opt_evaluations = set(evaluations) new_results = [] bindings = {} # TODO: report the depth considered for opt_result in stream_plan: # TODO: just refine the first step of the plan for new_instance in optimistic_stream_instantiation( opt_result.instance, (bindings if use_bindings else {}), opt_evaluations): for new_result in new_instance.next_optimistic(): opt_evaluations.update(map(evaluation_from_fact, new_result.get_certified())) new_results.append(new_result) if isinstance(new_result, StreamResult): # Could not add if same value for opt, obj in safe_zip(opt_result.output_objects, new_result.output_objects): bindings.setdefault(opt, []).append(obj) return new_results, bindings
def get_action_instances(task, action_plan): type_to_objects = instantiate.get_objects_by_type(task.objects, task.types) function_assignments = get_function_assignments(task) predicate_to_atoms = instantiate.get_atoms_by_predicate(task.init) fluent_facts = MockSet() init_facts = set() action_instances = [] for name, objects in action_plan: # TODO: what if more than one action of the same name due to normalization? # Normalized actions have same effects, so I just have to pick one # TODO: conditional effects and internal parameters action = find_unique(lambda a: a.name == name, task.actions) args = list(map(pddl_from_object, objects)) variable_mapping = {p.name: a for p, a in safe_zip(action.parameters, args)} instance = action.instantiate(variable_mapping, init_facts, fluent_facts, type_to_objects, task.use_min_cost_metric, function_assignments, predicate_to_atoms) assert (instance is not None) action_instances.append(instance) return action_instances
def process_stream_plan_branch(store, domain, disabled, stream_plan, action_plan, cost): if not is_plan(stream_plan): return stream_plan = [result for result in stream_plan if result.optimistic] if not stream_plan: store.add_plan(action_plan, cost) return free_objects = get_free_objects(stream_plan) bindings = defaultdict(set) for opt_result in stream_plan: opt_inputs = [inp for inp in opt_result.instance.input_objects if inp in free_objects] inp_bindings = [bindings[inp] for inp in opt_inputs] for combo in product(*inp_bindings): bound_result = opt_result.remap_inputs(get_mapping(opt_inputs, combo)) bound_instance = bound_result.instance if bound_instance.enumerated or not is_instance_ready(store.evaluations, bound_instance): continue # Disabled new_results = process_instance(store, domain, bound_instance) if not bound_instance.enumerated: disabled.add(bound_instance) if isinstance(opt_result, StreamResult): for new_result in new_results: for out, obj in safe_zip(opt_result.output_objects, new_result.output_objects): bindings[out].add(obj)
def solve_pyplanners(instantiated, planner=None, max_planner_time=DEFAULT_MAX_TIME, max_cost=INF): if instantiated is None: return None, INF # https://github.mit.edu/caelan/stripstream/blob/c8c6cd1d6bd5e2e8e31cd5603e28a8e0d7bb2cdc/stripstream/algorithms/search/pyplanners.py pyplanners_path = get_pyplanners_path() if pyplanners_path is None: raise RuntimeError( 'Must clone https://github.com/caelan/pyplanners ' 'and set the environment variable {} to its path'.format( PYPLANNERS_VAR)) if pyplanners_path not in sys.path: sys.path.append(pyplanners_path) # TODO: could operate on translated SAS instead from strips.states import State, PartialState from strips.operators import Action, Axiom from strips.utils import solve_strips, default_derived_plan import pddl # TODO: PLUSONE costs pyplanner = dict(DEFAULT_PYPLANNER) if isinstance(planner, dict): pyplanner.update(planner) fd_action_from_py_action = {} py_actions = [] for action in instantiated.actions: #action.dump() py_action = Action({'fd_action': action}) py_action.conditions = set(action.precondition) py_action.effects = set() for condition, effect in action.del_effects: assert not condition py_action.effects.add(effect.negate()) for condition, effect in action.add_effects: assert not condition py_action.effects.add(effect) py_action.cost = action.cost py_action.test, fd_action_from_py_action[ py_action] = get_attachment_test(action) py_actions.append(py_action) py_axioms = [] for axiom in instantiated.axioms: #axiom.dump() py_axiom = Axiom({'fd_axiom_id': id(axiom)}) # Not hashable for some reason py_axiom.conditions = set(axiom.condition) py_axiom.effects = {axiom.effect} py_axioms.append(py_axiom) goal = PartialState(instantiated.goal_list) fluents = {f.positive() for f in goal.conditions} for py_operator in py_actions + py_axioms: fluents.update(f.positive() for f in py_operator.conditions) initial = State(atom for atom in instantiated.task.init if isinstance(atom, pddl.Atom) and (atom in fluents)) plan, state_space = solve_strips(initial, goal, py_actions, py_axioms, max_time=max_planner_time, max_cost=max_cost, **pyplanner) if plan is None: return None, INF #fd_plan = [action.fd_action for action in plan.operators] states = plan.get_states() # get_states | get_derived_states fd_plan = [ fd_action_from_py_action[action][state] for state, action in safe_zip(states[:-1], plan.operators) ] actions = [pddl_from_instance(action) for action in fd_plan] #print(actions) cost = plan.cost / get_cost_scale() return actions, cost
def get_pairs(sequence): sequence = list(sequence) return safe_zip(sequence[:-1], sequence[1:])
def is_instance(atom, schema): return (atom.function == schema.function) and \ all(is_parameter(b) or (a == b) for a, b in safe_zip(atom.args, schema.args))
def add_plan_constraints(constraints, domain, evaluations, goal_exp, internal=False): if (constraints is None) or (constraints.skeletons is None): return goal_exp import pddl # TODO: can search over skeletons first and then fall back # TODO: unify this with the constraint ordering # TODO: can constrain to use a plan prefix prefix = '_' if internal else '' assigned_predicate = ASSIGNED_PREDICATE.format(prefix) group_predicate = GROUP_PREDICATE.format(prefix) order_predicate = ORDER_PREDICATE.format(prefix) for group in constraints.groups: for value in constraints.groups[group]: # TODO: could make all constants groups (like an equality group) fact = (group_predicate, to_obj(group), to_obj(value)) add_fact(evaluations, fact, result=INTERNAL_EVALUATION) new_actions = [] new_goals = [] for num, skeleton in enumerate(constraints.skeletons): # TODO: change the prefix for these order_facts = [(order_predicate, to_obj('n{}'.format(num)), to_obj('t{}'.format(step))) for step in range(len(skeleton) + 1)] add_fact(evaluations, order_facts[0], result=INTERNAL_EVALUATION) new_goals.append(order_facts[-1]) bound_parameters = set() for step, (name, args) in enumerate(skeleton): # TODO: could also just remove the free parameter from the action new_action = deepcopy( find_unique(lambda a: a.name == name, domain.actions)) constant_pairs = [(a, p.name) for a, p in safe_zip(args, new_action.parameters) if not is_parameter(a) and a != WILD] skeleton_parameters = list(filter(is_parameter, args)) existing_parameters = [ p for p in skeleton_parameters if p in bound_parameters ] local_from_global = { a: p.name for a, p in safe_zip(args, new_action.parameters) if is_parameter(a) } group_preconditions = [ (group_predicate if is_hashable(a) and (a in constraints.groups) else EQ, to_obj(a), p) for a, p in constant_pairs ] new_preconditions = make_assignment_facts(assigned_predicate, local_from_global, existing_parameters) + \ group_preconditions + [order_facts[step]] new_action.precondition = pddl.Conjunction([ new_action.precondition, make_preconditions(new_preconditions) ]).simplified() new_effects = make_assignment_facts(assigned_predicate, local_from_global, skeleton_parameters) \ + [Not(order_facts[step]), order_facts[step + 1]] new_action.effects.extend(make_effects(new_effects)) # TODO: should also negate the effects of all other sequences here new_actions.append(new_action) bound_parameters.update(skeleton_parameters) #new_action.dump() add_predicate(domain, make_predicate(order_predicate, ['?num', '?step'])) if constraints.exact: domain.actions[:] = [] domain.actions.extend(new_actions) new_goal_exp = And(goal_exp, Or(*new_goals)) return new_goal_exp
def add_plan_constraints(constraints, domain, evaluations, goal_exp, internal=False): if (constraints is None) or (constraints.skeletons is None): return goal_exp import pddl # TODO: unify this with the constraint ordering # TODO: can constrain to use a plan prefix prefix = get_internal_prefix(internal) assigned_predicate = ASSIGNED_PREDICATE.format(prefix) bound_predicate = BOUND_PREDICATE.format(prefix) group_predicate = GROUP_PREDICATE.format(prefix) order_predicate = ORDER_PREDICATE.format(prefix) new_facts = [] for group in constraints.groups: for value in constraints.groups[group]: # TODO: could make all constants groups (like an equality group) fact = (group_predicate, to_obj(group), to_obj(value)) new_facts.append(fact) new_actions = [] new_goals = [] for num, skeleton in enumerate(constraints.skeletons): actions, orders = skeleton incoming_orders, _ = neighbors_from_orders(orders) order_facts = [(order_predicate, to_obj('n{}'.format(num)), to_obj('t{}'.format(step))) for step in range(len(actions))] for step, (name, args) in enumerate(actions): # TODO: could also just remove the free parameter from the action new_action = deepcopy( find_unique(lambda a: a.name == name, domain.actions)) local_from_global = { a: p.name for a, p in safe_zip(args, new_action.parameters) if is_parameter(a) } ancestors, descendants = get_ancestors(step, orders), get_descendants( step, orders) parallel = set(range( len(actions))) - ancestors - descendants - {step} parameters = set(filter(is_parameter, args)) ancestor_parameters = parameters & set( filter(is_parameter, (p for idx in ancestors for p in actions[idx][1]))) #descendant_parameters = parameters & set(filter(is_parameter, (p for idx in descendants for p in actions[idx][1]))) parallel_parameters = parameters & set( filter(is_parameter, (p for idx in parallel for p in actions[idx][1]))) #bound_preconditions = [Imply(bound, assigned) for bound, assigned in safe_zip(bound_facts, assigned_facts)] bound_condition = pddl.Conjunction([ pddl.Disjunction( map(fd_from_fact, [ Not((bound_predicate, to_constant(p))), (assigned_predicate, to_constant(p), local_from_global[p]) ])) for p in parallel_parameters ]) existing_preconditions = [(assigned_predicate, to_constant(p), local_from_global[p]) for p in ancestor_parameters] constant_pairs = [(a, p.name) for a, p in safe_zip(args, new_action.parameters) if is_constant(a)] group_preconditions = [ (group_predicate if is_hashable(a) and (a in constraints.groups) else EQ, to_obj(a), p) for a, p in constant_pairs ] order_preconditions = [ order_facts[idx] for idx in incoming_orders[step] ] new_preconditions = existing_preconditions + group_preconditions + order_preconditions + [ Not(order_facts[step]) ] new_action.precondition = pddl.Conjunction([ new_action.precondition, bound_condition, make_preconditions(new_preconditions) ]).simplified() new_parameters = parameters - ancestors bound_facts = [(bound_predicate, to_constant(p)) for p in new_parameters] assigned_facts = [(assigned_predicate, to_constant(p), local_from_global[p]) for p in new_parameters] new_effects = bound_facts + assigned_facts + [order_facts[step]] new_action.effects.extend(make_effects(new_effects)) # TODO: should also negate the effects of all other sequences here new_actions.append(new_action) #new_action.dump() new_goals.append( And(*[order_facts[idx] for idx in incoming_orders[GOAL_INDEX]])) add_predicate(domain, make_predicate(order_predicate, ['?num', '?step'])) if constraints.exact: domain.actions[:] = [] domain.actions.extend(new_actions) new_goal_exp = And(goal_exp, Or(*new_goals)) for fact in new_facts: add_fact(evaluations, fact, result=INTERNAL_EVALUATION) return new_goal_exp