def bind_action_plan(opt_plan, mapping): fn = lambda o: mapping.get(o, o) new_action_plan = [ transform_action_args(action, fn) for action in opt_plan.action_plan ] new_preimage_facts = frozenset( replace_expression(fact, fn) for fact in opt_plan.preimage_facts) return OptPlan(new_action_plan, new_preimage_facts)
def opt_from_graph(names, orders, infos={}): param_from_order = {order: PARAM_TEMPLATE.format(*order) for order in orders} fact_from_order = {order: (PREDICATE, param_from_order[order]) for order in orders} object_from_param = {param: parse_value(param) for param in param_from_order.values()} incoming_from_edges, outgoing_from_edges = neighbors_from_orders(orders) stream_plan = [] for i, n in enumerate(names): #info = infos.get(n, StreamInfo(p_success=1, overhead=0, verbose=True)) inputs = [param_from_order[n2, n] for n2 in incoming_from_edges[n]] outputs = [param_from_order[n, n2] for n2 in outgoing_from_edges[n]] #gen = get_gen(outputs=outputs, p_success=info.p_success) #gen = get_gen(infos[i], outputs=outputs) stream = Stream( name=n, #gen_fn=DEBUG, #gen_fn=from_gen(gen), gen_fn=from_gen_fn(get_gen_fn(outputs=outputs, **infos[i])), inputs=inputs, domain=[fact_from_order[n2, n] for n2 in incoming_from_edges[n]], fluents=[], outputs=outputs, certified=[fact_from_order[n, n2] for n2 in outgoing_from_edges[n]], #info=info, info=StreamInfo(), ) # TODO: dump names print() print(stream) input_objects = safe_apply_mapping(stream.inputs, object_from_param) instance = stream.get_instance(input_objects) print(instance) output_objects = safe_apply_mapping(stream.outputs, object_from_param) result = instance.get_result(output_objects) print(result) stream_plan.append(result) opt_plan = OptPlan(action_plan=[], preimage_facts=[]) opt_solution = OptSolution(stream_plan, opt_plan, cost=1) return opt_solution
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 constraint_satisfaction(stream_pddl, stream_map, init, terms, stream_info={}, costs=True, max_cost=INF, success_cost=INF, max_time=INF, unit_efforts=False, max_effort=INF, max_skeletons=INF, search_sample_ratio=1, verbose=True, **search_args): # Approaches # 1) Existential quantification of bindings in goal conditions # 2) Backtrack useful streams and then schedule. Create arbitrary outputs for not mentioned. # 3) Construct all useful streams and then associate outputs with bindings # Useful stream must satisfy at least one fact. How should these assignments be propagated though? # Make an action that maps each stream result to unbound values? # TODO: include functions again for cost-sensitive satisfaction # TODO: convert init into streams to bind certain facts # TODO: investigate constraint satisfaction techniques for binding instead # TODO: could also instantiate all possible free parameters even if not useful # TODO: effort that is a function of the number of output parameters (degrees of freedom) # TODO: use a CSP solver instead of a planner internally # TODO: max_iterations? if not terms: return {}, 0, init constraints, negated, functions = partition_facts( set(map(obj_from_existential_expression, terms))) if not costs: functions = [] evaluations = evaluations_from_init(init) goal_facts = set( filter(lambda f: evaluation_from_fact(f) not in evaluations, constraints)) free_parameters = sorted(get_parameters(goal_facts)) print('Parameters:', free_parameters) externals = parse_stream_pddl(stream_pddl, stream_map, stream_info, unit_efforts=unit_efforts) stream_results = extract_streams(evaluations, externals, goal_facts) function_plan = plan_functions(negated + functions, externals) plan_skeleton = [Assignment(free_parameters)] cost = get_optimistic_cost(function_plan) if max_cost < cost: return None, INF, init # TODO: detect connected components # TODO: eagerly evaluate fully bound constraints # TODO: consider other results if this fails domain = create_domain(goal_facts) init_evaluations = evaluations.copy() store = SolutionStore(evaluations, max_time=max_time, success_cost=success_cost, verbose=verbose) queue = SkeletonQueue(store, domain, disable=False) num_iterations = search_time = sample_time = 0 planner = 'ff-astar' # TODO: toggle within reschedule_stream_plan #last_clusters = set() #last_success = True while not store.is_terminated(): num_iterations += 1 start_time = time.time() print( '\nIteration: {} | Skeletons: {} | Skeleton Queue: {} | Evaluations: {} | ' 'Cost: {:.3f} | Search Time: {:.3f} | Sample Time: {:.3f} | Total Time: {:.3f}' .format(num_iterations, len(queue.skeletons), len(queue), len(evaluations), store.best_cost, search_time, sample_time, store.elapsed_time())) external_plan = None if len(queue.skeletons) < max_skeletons: domain.axioms[:] = create_disabled_axioms(queue, use_parameters=False) #dominated = are_domainated(last_clusters, clusters) #last_clusters = clusters #if last_success or not dominated: # Could also keep a history of results stream_plan = reschedule_stream_plan(init_evaluations, goal_facts, domain, stream_results, unique_binding=True, unsatisfiable=True, max_effort=max_effort, planner=planner, **search_args) if stream_plan is not None: external_plan = reorder_stream_plan( store, combine_optimizers(init_evaluations, stream_plan + list(function_plan))) print('Stream plan ({}, {:.3f}): {}'.format( get_length(external_plan), compute_plan_effort(external_plan), external_plan)) last_success = (external_plan is not None) search_time += elapsed_time(start_time) # Once a constraint added for a skeleton, it should only be relaxed start_time = time.time() if last_success: # Only works if create_disable_axioms never changes allocated_sample_time = (search_sample_ratio * search_time) - sample_time else: allocated_sample_time = INF queue.process( external_plan, OptPlan(plan_skeleton, []), cost=cost, # TODO: fill in preimage facts complexity_limit=INF, max_time=allocated_sample_time) sample_time += elapsed_time(start_time) if not last_success and not queue: break # TODO: exhaustively compute all plan skeletons and add to queue within the focused algorithm write_stream_statistics(externals, verbose) action_plan, cost, facts = revert_solution(store.best_plan, store.best_cost, evaluations) bindings = bindings_from_plan(plan_skeleton, action_plan) return bindings, cost, facts