def reinstantiate_action_instances(task, old_instances): import pddl import instantiate # Recomputes the instances with without any pruned preconditions fluents = get_fluents(task) function_assignments = { fact.fluent: fact.expression for fact in task.init if isinstance(fact, pddl.f_expression.FunctionAssignment) } type_to_objects = instantiate.get_objects_by_type(task.objects, task.types) init_facts = set() fluent_facts = MockSet() new_instances = [] state = set(task.init) for old_instance in old_instances: # TODO: better way of instantiating conditional effects (when not fluent) #new_instance = reinstantiate_action(old_instance) predicate_to_atoms = instantiate.get_atoms_by_predicate({ a for a in state if isinstance(a, pddl.Atom) and (a.predicate in fluents) }) action = old_instance.action var_mapping = old_instance.var_mapping new_instance = action.instantiate(var_mapping, init_facts, fluent_facts, type_to_objects, task.use_min_cost_metric, function_assignments, predicate_to_atoms) assert (new_instance is not None) new_instances.append(new_instance) apply_action(state, new_instance) new_instances.append(get_goal_instance(task.goal)) # TODO: move this? return new_instances
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 replace_derived(task, negative_init, action_instances): import pddl_to_prolog import build_model import axiom_rules import pddl original_actions = task.actions original_init = task.init task.actions = [] function_assignments = {f.fluent: f.expression for f in task.init if isinstance(f, pddl.f_expression.FunctionAssignment)} task.init = (set(task.init) | {a.negate() for a in negative_init}) - set(function_assignments) for instance in action_instances: #axiom_plan = extract_axiom_plan(task, instance, negative_from_name={}) # TODO: refactor this # TODO: just instantiate task? with Verbose(False): model = build_model.compute_model(pddl_to_prolog.translate(task)) # Changes based on init # fluent_facts = instantiate.get_fluent_facts(task, model) fluent_facts = MockSet() instantiated_axioms = instantiate_axioms(model, task.init, fluent_facts) goal_list = [] # TODO: include the goal? with Verbose(False): # TODO: helpful_axioms prunes axioms that are already true (e.g. not Unsafe) helpful_axioms, axiom_init, _ = axiom_rules.handle_axioms([instance], instantiated_axioms, goal_list) axiom_from_atom = get_achieving_axioms(task.init | negative_init | set(axiom_init), helpful_axioms) # negated_from_name=negated_from_name) axiom_plan = [] extract_axioms(axiom_from_atom, instance.precondition, axiom_plan) substitute_derived(axiom_plan, instance) assert(is_applicable(task.init, instance)) apply_action(task.init, instance) task.actions = original_actions task.init = original_init
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 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_axioms_plans(instantiated, action_instances): #axioms, axiom_init, _ = axiom_rules.handle_axioms( # instantiated.actions, instantiated.axioms, instantiated.goal_list) new_action_instances = [ copy.deepcopy(instance) for instance in action_instances ] axioms, axiom_init = instantiated.axioms, [ ] # TODO: bug when needing to reachieve negated axioms_from_effect = defaultdict(list) for axiom in axioms: axioms_from_effect[axiom.effect].append(axiom) axioms_from_name = get_derived_predicates(instantiated.task.axioms) state = set(instantiated.task.init) | set(axiom_init) axiom_plans = [] for action in new_action_instances + [ get_goal_instance(instantiated.task.goal) ]: all_conditions = list(get_precondition(action)) + list( flatten(cond for cond, _ in action.add_effects + action.del_effects)) axioms = backtrack_axioms(all_conditions, axioms_from_effect, set()) axiom_from_atom, _ = get_achieving_axioms(state, axioms) action.applied_effects = [] for effects in [action.add_effects, action.del_effects]: negate = (effects is action.del_effects) for i, (conditions, effect) in reversed(list(enumerate(effects))): if all( literal_holds(state, literal) or ( literal in axiom_from_atom) for literal in conditions): action.precondition.extend(conditions) effects[i] = ([], effect) action.applied_effects.append( effect.negate() if negate else effect) else: effects.pop(i) # RuntimeError: Preimage fact ('new-axiom@0',) is not achievable! #precondition = action.precondition # TODO: strange bug if this applies precondition = [ literal for literal in action.precondition if literal.predicate in axioms_from_name ] axiom_plans.append([]) success = extract_axioms(state, axiom_from_atom, precondition, axiom_plans[-1]) if not success: print(all_conditions) print(action) print(axioms) raise RuntimeError('Could not extract axioms') apply_action(state, action) return new_action_instances, axiom_plans
def reinstantiate_action_instances(task, old_instances, **kwargs): # Recomputes the instances with without any pruned preconditions state = set(task.init) new_instances = [] for old_instance in old_instances: # TODO: better way of instantiating conditional effects (when not fluent) new_instance = reinstantiate_action(state, old_instance, **kwargs) assert (new_instance is not None) new_instances.append(new_instance) apply_action(state, new_instance) new_instances.append(get_goal_instance(task.goal)) # TODO: move this? return new_instances
def recover_axioms_plans2(instantiated, action_instances): #import axiom_rules #with Verbose(False): # normalized_axioms, axiom_init, axiom_layer_dict = axiom_rules.handle_axioms( # [], instantiated.axioms, instantiated.goal_list) #state = set(instantiated.task.init + axiom_init) normalized_axioms = instantiated.axioms # TODO: ignoring negated because cannot reinstantiate correctly state = set(instantiated.task.init) fluents = get_fluents(state, action_instances) unprocessed_from_atom = defaultdict(list) fluents_from_axiom = {} remaining_from_axiom = {} for axiom in normalized_axioms: fluent_conditions = [] for literal in axiom.condition: if literal.positive() in fluents: fluent_conditions.append(literal) elif not literal_holds(state, literal): fluent_conditions = None break if fluent_conditions is None: continue for literal in fluent_conditions: unprocessed_from_atom[literal].append(axiom) fluents_from_axiom[id(axiom)] = len(fluent_conditions) remaining_from_axiom[id(axiom)] = fluents_from_axiom[id(axiom)] static_axioms = [ axiom for axiom, num in fluents_from_axiom.items() if num == 0 ] axiom_plans = [] for action in action_instances + [ get_goal_instance(instantiated.task.goal) ]: axiom_from_atom = mark_iteration(state, unprocessed_from_atom, fluents_from_axiom, remaining_from_axiom, static_axioms) preimage = [] for literal in action.precondition: if not literal_holds(state, literal): preimage.append(literal) assert literal in axiom_from_atom for cond, eff in (action.add_effects + action.del_effects): # TODO: add conditional effects that must hold here assert not cond axiom_plans.append([]) assert extract_axioms(axiom_from_atom, preimage, axiom_plans[-1]) apply_action(state, action) return axiom_plans
def recover_axioms_plans(instantiated, action_instances): task = instantiated.task derived_predicates = get_derived_predicates(task.axioms) state = set(task.init) axiom_plans = [] for action_instance in action_instances + [get_goal_instance(task.goal)]: # TODO: apply all axiom_instances unaffected by negative conditions preimage = list(plan_preimage([action_instance], [])) axiom_instances = filter( lambda ax: all(l.predicate in derived_predicates or literal_holds( state, l) for l in ax.condition), instantiated.axioms) # Only instantiate if preimage has goal axiom_plan = extraction_helper(state, axiom_instances, preimage) assert axiom_plan is not None axiom_plans.append(axiom_plan) apply_action(state, action_instance) return axiom_plans
def simplify_conditional_effects(opt_task, action_instances, negative_from_name={}): # TODO: extract out the minimum set of conditional effects that are actually required # TODO: handle more general case where can choose to achieve particular conditional effects # will likely require planning with streams axioms_from_name = get_derived_predicates(opt_task.axioms) state = set(opt_task.init) for action_instance in action_instances: for effects in [ action_instance.add_effects, action_instance.del_effects ]: for i, (conditions, effect) in reversed(list(enumerate(effects))): if any(c.predicate in axioms_from_name for c in conditions): raise NotImplementedError( 'Conditional effects cannot currently involve derived predicates' ) neg_conditions = [ literal for literal in conditions if literal.predicate in negative_from_name ] pos_conditions = [ literal for literal in conditions if literal not in neg_conditions ] #if conditions_hold(real_state, opt_conditions): if conditions_hold(state, pos_conditions): # Holds in optimistic state # Assuming that must achieve all possible conditional effects if neg_conditions: # TODO: use unsatisfiable # Assuming that negative conditions should not be achieved if len(neg_conditions) != 1: raise NotImplementedError() action_instance.precondition.extend( l.negate() for l in neg_conditions) effects.pop(i) else: action_instance.precondition.extend(pos_conditions) effects[i] = ([], effect) #elif not conditions_hold(opt_state, opt_conditions): else: # Does not hold in optimistic state effects.pop(i) apply_action(state, action_instance)
def is_solution(domain, evaluations, action_plan, goal_expression): task = task_from_domain_problem( domain, get_problem(evaluations, goal_expression, domain, unit_costs=True)) action_instances = get_action_instances( task, action_plan) + [get_goal_instance(task.goal)] #original_init = task.init task.init = set(task.init) for instance in action_instances: axiom_plan = extract_axiom_plan(task, instance, negative_from_name={}, static_state=task.init) if axiom_plan is None: return False #substitute_derived(axiom_plan, instance) #if not is_applicable(task.init, instance): # return False apply_action(task.init, instance) return True
def apply_actions(domain, state, plan, unit_costs=False): # Goal serialization just assumes the tail of the plan includes an abstract action to achieve each condition static_state, _ = partition_facts(domain, state) print('Static:', static_state) # TODO: might need properties that involve an object that aren't useful yet evaluations = evaluations_from_init(state) #goal_exp = obj_from_value_expression(goal) goal_exp = None problem = get_problem(evaluations, goal_exp, domain, unit_costs) task = task_from_domain_problem(domain, problem) task.init = set(task.init) for instance in get_action_instances( task, transform_plan_args(plan, Object.from_value)): apply_action(task.init, instance) fluents = get_fluents(domain) fluent_state = [ value_from_evaluation(evaluation_from_fd(atom)) for atom in task.init if isinstance(atom, pddl.Atom) and atom.predicate in fluents ] print('Fluent:', fluent_state) state = static_state + fluent_state return state
def recover_axioms_plans(instantiated, action_instances): #axioms, axiom_init, _ = axiom_rules.handle_axioms( # instantiated.actions, instantiated.axioms, instantiated.goal_list) axioms, axiom_init = instantiated.axioms, [ ] # TODO: bug when needing to reachieve negated axioms_from_effect = defaultdict(list) for axiom in axioms: axioms_from_effect[axiom.effect].append(axiom) state = set(instantiated.task.init) | set(axiom_init) axiom_plans = [] for action in action_instances + [ get_goal_instance(instantiated.task.goal) ]: all_conditions = list(get_precondition(action)) + list( flatten(cond for cond, _ in action.add_effects + action.del_effects)) axioms = backtrack_axioms(all_conditions, axioms_from_effect, set()) axiom_from_atom, _ = get_achieving_axioms(state, axioms) action.applied_effects = [] for effects in [action.add_effects, action.del_effects]: negate = effects is action.del_effects for i, (conditions, effect) in reversed(list(enumerate(effects))): if all( literal_holds(state, literal) or ( literal in axiom_from_atom) for literal in conditions): action.precondition.extend(conditions) effects[i] = ([], effect) action.applied_effects.append( effect.negate() if negate else effect) else: effects.pop(i) axiom_plans.append([]) assert extract_axioms(state, axiom_from_atom, action.precondition, axiom_plans[-1]) apply_action(state, action) return axiom_plans
def recover_stream_plan(evaluations, goal_expression, domain, stream_results, action_plan, negative, unit_costs, optimize=True): import pddl_to_prolog import build_model import pddl import axiom_rules import instantiate # Universally quantified conditions are converted into negative axioms # Existentially quantified conditions are made additional preconditions # Universally quantified effects are instantiated by doing the cartesian produce of types (slow) # Added effects cancel out removed effects 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)) real_task = task_from_domain_problem( domain, get_problem(evaluations, goal_expression, domain, unit_costs)) function_assignments = { fact.fluent: fact.expression for fact in opt_task.init # init_facts if isinstance(fact, pddl.f_expression.FunctionAssignment) } type_to_objects = instantiate.get_objects_by_type(opt_task.objects, opt_task.types) results_from_head = get_results_from_head(opt_evaluations) action_instances = [] for name, args in action_plan: # TODO: negative atoms in actions candidates = [] for action in opt_task.actions: if action.name != name: continue if len(action.parameters) != len(args): raise NotImplementedError( 'Existential quantifiers are not currently ' 'supported in preconditions: {}'.format(name)) variable_mapping = { p.name: a for p, a in zip(action.parameters, args) } instance = action.instantiate(variable_mapping, set(), MockSet(), type_to_objects, opt_task.use_min_cost_metric, function_assignments) assert (instance is not None) candidates.append(((action, args), instance)) if not candidates: raise RuntimeError( 'Could not find an applicable action {}'.format(name)) action_instances.append(candidates) action_instances.append([(None, get_goal_instance(opt_task.goal))]) axioms_from_name = get_derived_predicates(opt_task.axioms) negative_from_name = {n.name: n for n in negative} opt_task.actions = [] opt_state = set(opt_task.init) real_state = set(real_task.init) preimage_plan = [] function_plan = set() for layer in action_instances: for pair, instance in layer: nonderived_preconditions = [ l for l in instance.precondition if l.predicate not in axioms_from_name ] #nonderived_preconditions = instance.precondition if not conditions_hold(opt_state, nonderived_preconditions): continue opt_task.init = opt_state original_axioms = opt_task.axioms axiom_from_action = get_necessary_axioms(instance, original_axioms, negative_from_name) opt_task.axioms = [] opt_task.actions = axiom_from_action.keys() # TODO: maybe it would just be better to drop the negative throughout this process until this end with Verbose(False): model = build_model.compute_model( pddl_to_prolog.translate( opt_task)) # Changes based on init opt_task.axioms = original_axioms opt_facts = instantiate.get_fluent_facts( opt_task, model) | (opt_state - real_state) mock_fluent = MockSet(lambda item: ( item.predicate in negative_from_name) or (item in opt_facts)) instantiated_axioms = instantiate_necessary_axioms( model, real_state, mock_fluent, axiom_from_action) with Verbose(False): helpful_axioms, axiom_init, _ = axiom_rules.handle_axioms( [instance], instantiated_axioms, []) axiom_from_atom = get_achieving_axioms(opt_state, helpful_axioms, axiom_init, negative_from_name) axiom_plan = [] # Could always add all conditions extract_axioms(axiom_from_atom, instance.precondition, axiom_plan) # TODO: test if no derived solution # TODO: compute required stream facts in a forward way and allow opt facts that are already known required for effects in [instance.add_effects, instance.del_effects]: for i, (conditions, effect) in enumerate(effects[::-1]): if any(c.predicate in axioms_from_name for c in conditions): raise NotImplementedError( 'Conditional effects cannot currently involve derived predicates' ) if conditions_hold(real_state, conditions): # Holds in real state effects[i] = ([], effect) elif not conditions_hold(opt_state, conditions): # Does not hold in optimistic state effects.pop(i) else: # TODO: handle more general case where can choose to achieve particular conditional effects raise NotImplementedError( 'Conditional effects cannot currently involve certified predicates' ) #if any(conditions for conditions, _ in instance.add_effects + instance.del_effects): # raise NotImplementedError('Conditional effects are not currently supported: {}'.format(instance.name)) # TODO: add axiom init to reset state? apply_action(opt_state, instance) apply_action(real_state, instance) preimage_plan.extend(axiom_plan + [instance]) if not unit_costs and (pair is not None): function_plan.update( extract_function_results(results_from_head, *pair)) break else: raise RuntimeError('No action instances are applicable') preimage = plan_preimage(preimage_plan, set()) preimage -= set(real_task.init) negative_preimage = set( filter(lambda a: a.predicate in negative_from_name, preimage)) preimage -= negative_preimage # visualize_constraints(map(fact_from_fd, preimage)) # TODO: prune with rules # TODO: linearization that takes into account satisfied goals at each level # TODO: can optimize for all streams & axioms all at once for literal in negative_preimage: negative = negative_from_name[literal.predicate] instance = negative.get_instance(map(obj_from_pddl, literal.args)) value = not literal.negated if instance.enumerated: assert (instance.value == value) else: function_plan.add( PredicateResult(instance, value, opt_index=instance.opt_index)) node_from_atom = get_achieving_streams(evaluations, stream_results) preimage_facts = list( map(fact_from_fd, filter(lambda l: not l.negated, preimage))) stream_plan = [] extract_stream_plan(node_from_atom, preimage_facts, stream_plan) if not optimize: # TODO: detect this based on unique or not return stream_plan + list(function_plan) # TODO: search in space of partially ordered plans # TODO: local optimization - remove one and see if feasible reschedule_problem = get_problem(evaluations, And(*preimage_facts), domain, unit_costs=True) reschedule_task = task_from_domain_problem(domain, reschedule_problem) reschedule_task.actions, stream_result_from_name = get_stream_actions( stream_results) new_plan, _ = solve_from_task(reschedule_task, planner='max-astar', debug=False) # TODO: investigate admissible heuristics if new_plan is None: return stream_plan + list(function_plan) new_stream_plan = [stream_result_from_name[name] for name, _ in new_plan] return new_stream_plan + list(function_plan)
def recover_stream_plan(evaluations, current_plan, opt_evaluations, goal_expression, domain, node_from_atom, action_plan, axiom_plans, negative, replan_step): # Universally quantified conditions are converted into negative axioms # Existentially quantified conditions are made additional preconditions # Universally quantified effects are instantiated by doing the cartesian produce of types (slow) # Added effects cancel out removed effects # TODO: node_from_atom is a subset of opt_evaluations (only missing functions) real_task = task_from_domain_problem( domain, get_problem(evaluations, goal_expression, domain)) opt_task = task_from_domain_problem( domain, get_problem(opt_evaluations, goal_expression, domain)) negative_from_name = { external.blocked_predicate: external for external in negative if external.is_negated() } real_states, full_plan = recover_negative_axioms(real_task, opt_task, axiom_plans, action_plan, negative_from_name) function_plan = compute_function_plan(opt_evaluations, action_plan) full_preimage = plan_preimage(full_plan, []) # Does not contain the stream preimage! negative_preimage = set( filter(lambda a: a.predicate in negative_from_name, full_preimage)) negative_plan = convert_negative(negative_preimage, negative_from_name, full_preimage, real_states) function_plan.update(negative_plan) # TODO: OrderedDict for these plans # TODO: this assumes that actions do not negate preimage goals positive_preimage = { l for l in (set(full_preimage) - real_states[0] - negative_preimage) if not l.negated } steps_from_fact = { fact_from_fd(l): full_preimage[l] for l in positive_preimage } last_from_fact = { fact: min(steps) for fact, steps in steps_from_fact.items() if get_prefix(fact) != EQ } #stream_plan = reschedule_stream_plan(evaluations, target_facts, domain, stream_results) # visualize_constraints(map(fact_from_fd, target_facts)) for result, step in function_plan.items(): for fact in result.get_domain(): last_from_fact[fact] = min(step, last_from_fact.get(fact, INF)) # TODO: get_steps_from_stream stream_plan = [] last_from_stream = dict(function_plan) for result in current_plan: # + negative_plan? # TODO: actually compute when these are needed + dependencies last_from_stream[result] = 0 if isinstance(result.external, Function) or (result.external in negative): if len(action_plan) != replan_step: raise NotImplementedError( ) # TODO: deferring negated optimizers # Prevents these results from being pruned function_plan[result] = replan_step else: stream_plan.append(result) curr_evaluations = evaluations_from_stream_plan(evaluations, stream_plan, max_effort=None) extraction_facts = set(last_from_fact) - set( map(fact_from_evaluation, curr_evaluations)) extract_stream_plan(node_from_atom, extraction_facts, stream_plan) # Recomputing due to postprocess_stream_plan stream_plan = postprocess_stream_plan(evaluations, domain, stream_plan, last_from_fact) node_from_atom = get_achieving_streams(evaluations, stream_plan, max_effort=None) fact_sequence = [set(result.get_domain()) for result in stream_plan] + [extraction_facts] for facts in reversed(fact_sequence): # Bellman ford for fact in facts: # could flatten instead result = node_from_atom[fact].result if result is None: continue step = last_from_fact[fact] if result.is_deferrable() else 0 last_from_stream[result] = min(step, last_from_stream.get(result, INF)) for domain_fact in result.instance.get_domain(): last_from_fact[domain_fact] = min( last_from_stream[result], last_from_fact.get(domain_fact, INF)) stream_plan.extend(function_plan) # Useful to recover the correct DAG partial_orders = set() for child in stream_plan: # TODO: account for fluent objects for fact in child.get_domain(): parent = node_from_atom[fact].result if parent is not None: partial_orders.add((parent, child)) #stream_plan = topological_sort(stream_plan, partial_orders) bound_objects = set() for result in stream_plan: if (last_from_stream[result] == 0) or not result.is_deferrable(bound_objects=bound_objects): for ancestor in get_ancestors(result, partial_orders) | {result}: # TODO: this might change descendants of ancestor. Perform in a while loop. last_from_stream[ancestor] = 0 if isinstance(ancestor, StreamResult): bound_objects.update(out for out in ancestor.output_objects if out.is_unique()) #local_plan = [] # TODO: not sure what this was for #for fact, step in sorted(last_from_fact.items(), key=lambda pair: pair[1]): # Earliest to latest # print(step, fact) # extract_stream_plan(node_from_atom, [fact], local_plan, last_from_fact, last_from_stream) # Each stream has an earliest evaluation time # When computing the latest, use 0 if something isn't deferred # Evaluate each stream as soon as possible # Option to defer streams after a point in time? # TODO: action costs for streams that encode uncertainty state = set(real_task.init) remaining_results = list(stream_plan) first_from_stream = {} #assert 1 <= replan_step # Plan could be empty for step, instance in enumerate(action_plan): for result in list(remaining_results): # TODO: could do this more efficiently if need be domain = result.get_domain() + get_fluent_domain(result) if conditions_hold(state, map(fd_from_fact, domain)): remaining_results.remove(result) certified = { fact for fact in result.get_certified() if get_prefix(fact) != EQ } state.update(map(fd_from_fact, certified)) if step != 0: first_from_stream[result] = step # TODO: assumes no fluent axiom domain conditions apply_action(state, instance) #assert not remaining_results # Not true if retrace if first_from_stream: replan_step = min(replan_step, *first_from_stream.values()) eager_plan = [] results_from_step = defaultdict(list) for result in stream_plan: earliest_step = first_from_stream.get(result, 0) latest_step = last_from_stream.get(result, 0) assert earliest_step <= latest_step defer = replan_step <= latest_step if not defer: eager_plan.append(result) # We only perform a deferred evaluation if it has all deferred dependencies # TODO: make a flag that also allows dependencies to be deferred future = (earliest_step != 0) or defer if future: future_step = latest_step if defer else earliest_step results_from_step[future_step].append(result) # TODO: some sort of obj side-effect bug that requires obj_from_pddl to be applied last (likely due to fluent streams) eager_plan = convert_fluent_streams(eager_plan, real_states, action_plan, steps_from_fact, node_from_atom) combined_plan = [] for step, action in enumerate(action_plan): combined_plan.extend(result.get_action() for result in results_from_step[step]) combined_plan.append( transform_action_args(pddl_from_instance(action), obj_from_pddl)) # TODO: the returned facts have the same side-effect bug as above # TODO: annotate when each preimage fact is used preimage_facts = { fact_from_fd(l) for l in full_preimage if (l.predicate != EQ) and not l.negated } for negative_result in negative_plan: # TODO: function_plan preimage_facts.update(negative_result.get_certified()) for result in eager_plan: preimage_facts.update(result.get_domain()) # Might not be able to regenerate facts involving the outputs of streams preimage_facts.update( result.get_certified()) # Some facts might not be in the preimage # TODO: record streams and axioms return eager_plan, OptPlan(combined_plan, preimage_facts)
def recover_stream_plan(evaluations, goal_expression, domain, stream_results, action_plan, negative, unit_costs): import pddl import instantiate # Universally quantified conditions are converted into negative axioms # Existentially quantified conditions are made additional preconditions # Universally quantified effects are instantiated by doing the cartesian produce of types (slow) # Added effects cancel out removed effects real_task = task_from_domain_problem(domain, get_problem(evaluations, goal_expression, domain, unit_costs)) node_from_atom = get_achieving_streams(evaluations, stream_results) opt_evaluations = apply_streams(evaluations, stream_results) opt_task = task_from_domain_problem(domain, get_problem(opt_evaluations, goal_expression, domain, unit_costs)) function_assignments = {fact.fluent: fact.expression for fact in opt_task.init # init_facts if isinstance(fact, pddl.f_expression.FunctionAssignment)} type_to_objects = instantiate.get_objects_by_type(opt_task.objects, opt_task.types) results_from_head = get_results_from_head(opt_evaluations) action_instances = instantiate_actions(opt_task, type_to_objects, function_assignments, action_plan) negative_from_name = get_negative_predicates(negative) axioms_from_name = get_derived_predicates(opt_task.axioms) opt_task.init = set(opt_task.init) real_states = [set(real_task.init)] # TODO: had old way of doing this (~July 2018) preimage_plan = [] function_plan = set() for layer in action_instances: for pair, action_instance in layer: axiom_plan = extract_axiom_plan(opt_task, action_instance, negative_from_name, static_state=real_states[-1]) if axiom_plan is None: continue simplify_conditional_effects(real_states[-1], opt_task.init, action_instance, axioms_from_name) preimage_plan.extend(axiom_plan + [action_instance]) apply_action(opt_task.init, action_instance) real_states.append(set(real_states[-1])) apply_action(real_states[-1], action_instance) if not unit_costs and (pair is not None): function_result = extract_function_results(results_from_head, *pair) if function_result is not None: function_plan.add(function_result) break else: raise RuntimeError('No action instances are applicable') # TODO: could instead just accumulate difference between real and opt full_preimage = plan_preimage(preimage_plan, []) stream_preimage = set(full_preimage) - real_states[0] negative_preimage = set(filter(lambda a: a.predicate in negative_from_name, stream_preimage)) positive_preimage = stream_preimage - negative_preimage function_plan.update(convert_negative(negative_preimage, negative_from_name, full_preimage, real_states)) step_from_fact = {fact_from_fd(l): full_preimage[l] for l in positive_preimage if not l.negated} target_facts = list(step_from_fact.keys()) #stream_plan = reschedule_stream_plan(evaluations, target_facts, domain, stream_results) stream_plan = [] extract_stream_plan(node_from_atom, target_facts, stream_plan) stream_plan = prune_stream_plan(evaluations, stream_plan, target_facts) stream_plan = convert_fluent_streams(stream_plan, real_states, step_from_fact, node_from_atom) # visualize_constraints(map(fact_from_fd, stream_preimage)) if DO_RESCHEDULE: # TODO: detect this based on unique or not # TODO: maybe test if partial order between two ways of achieving facts, if not prune new_stream_plan = reschedule_stream_plan(evaluations, target_facts, domain, stream_plan) if new_stream_plan is not None: stream_plan = new_stream_plan return stream_plan + list(function_plan)