def recursive_solve_stream_plan(evaluations, streams, functions, stream_results, solve_stream_plan, depth): combined_plan, cost = solve_stream_plan(stream_results) stream_plan, action_plan = separate_plan(combined_plan, action_info=None, terminate=False, stream_only=False) #print('Depth: {}\n' # 'Stream plan: {}\n' # 'Action plan: {}'.format(depth, stream_plan, action_plan)) if stream_plan is None: return stream_plan, cost, depth plan_index = get_stream_plan_index(stream_plan) if plan_index == 0: return combined_plan, cost, depth stream_results, bindings = optimistic_process_stream_plan( evaluations, stream_plan) double_bindings = { v: k for k, values in bindings.items() if 2 <= len(values) for v in values } stream_results += optimistic_process_streams( evaluations_from_stream_plan(evaluations, stream_results), streams, double_bindings=double_bindings) stream_results += optimistic_process_streams( evaluations_from_stream_plan(evaluations, stream_results), functions) return recursive_solve_stream_plan(evaluations, streams, functions, stream_results, solve_stream_plan, depth + 1)
def relaxed_stream_plan(evaluations, goal_expression, domain, stream_results, negative, unit_costs, **kwargs): # TODO: alternatively could translate with stream actions on real opt_state and just discard them opt_evaluations = evaluations_from_stream_plan(evaluations, stream_results) problem = get_problem(opt_evaluations, goal_expression, domain, unit_costs) task = task_from_domain_problem(domain, problem) action_plan, action_cost = solve_from_task(task, **kwargs) if action_plan is None: return None, action_cost # TODO: just use solve finite? stream_plan = recover_stream_plan(evaluations, goal_expression, domain, stream_results, action_plan, negative, unit_costs) action_plan = obj_from_pddl_plan(action_plan) combined_plan = stream_plan + action_plan return combined_plan, action_cost
def get_combined_orders(evaluations, stream_plan, action_plan, domain): if action_plan is None: return None # TODO: could just do this within relaxed # TODO: do I want to strip the fluents and just do the partial ordering? stream_instances = get_stream_instances(stream_plan) negative_results = filter( lambda r: isinstance(r, PredicateResult) and (r.value == False), stream_plan) negative_init = set( get_init((evaluation_from_fact(f) for r in negative_results for f in r.get_certified()), negated=True)) #negated_from_name = {r.instance.external.name for r in negative_results} opt_evaluations = evaluations_from_stream_plan(evaluations, stream_plan) goal_expression = ('and', ) task = task_from_domain_problem( domain, get_problem(opt_evaluations, goal_expression, domain, unit_costs=True)) action_instances = get_action_instances(task, action_plan) replace_derived(task, negative_init, action_instances) #combined_instances = stream_instances + action_instances orders = set() for i, a1 in enumerate(action_plan): for a2 in action_plan[i + 1:]: orders.add((a1, a2)) # TODO: just store first achiever here for i, instance1 in enumerate(stream_instances): for j in range(i + 1, len(stream_instances)): effects = {e for _, e in instance1.add_effects} if effects & set(stream_instances[j].precondition): orders.add((stream_plan[i], stream_plan[j])) for i, instance1 in enumerate(stream_instances): for j, instance2 in enumerate(action_instances): effects = {e for _, e in instance1.add_effects} | \ {e.negate() for _, e in instance1.del_effects} if effects & set(instance2.precondition): orders.add((stream_plan[i], action_plan[j])) return orders
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
def locally_optimize(evaluations, store, goal_expression, domain, functions, negative, dynamic_streams): action_plan = store.best_plan if action_plan is None: return None print('\nPostprocessing') # TODO: postprocess current skeleton as well task = task_from_domain_problem( domain, get_problem(evaluations, goal_expression, domain, unit_costs=False)) plan_instances = get_action_instances( task, action_plan) + [get_goal_instance(task.goal)] replace_derived(task, set(), plan_instances) preimage = filter(lambda a: not a.negated, plan_preimage(plan_instances, [])) opt_stream_plan = [] opt_from_obj = {} for stream_result in extract_order(evaluations, preimage): input_objects = tuple( opt_from_obj.get(o, o) for o in stream_result.instance.input_objects) instance = stream_result.instance.external.get_instance(input_objects) #assert(not instance.disabled) instance.disabled = False opt_results = instance.next_optimistic() if not opt_results: continue opt_result = opt_results[0] opt_stream_plan.append(opt_result) for obj, opt in zip(stream_result.output_objects, opt_result.output_objects): opt_from_obj[obj] = opt opt_stream_plan += optimistic_process_streams( evaluations_from_stream_plan(evaluations, opt_stream_plan), functions) opt_action_plan = [(name, tuple(opt_from_obj.get(o, o) for o in args)) for name, args in action_plan] pddl_plan = [(name, map(pddl_from_object, args)) for name, args in opt_action_plan] stream_plan = recover_stream_plan(evaluations, goal_expression, domain, opt_stream_plan, pddl_plan, negative, unit_costs=False) stream_plan = reorder_stream_plan(stream_plan) stream_plan = get_synthetic_stream_plan(stream_plan, dynamic_streams) print('Stream plan: {}\n' 'Action plan: {}'.format(stream_plan, opt_action_plan)) opt_cost = 0 # TODO: compute this cost for real store.start_time = time.time() store.max_cost = store.best_cost #sampling_time = 10 sampling_time = 0 queue = [] heappush(queue, (SkeletonKey(0, len(stream_plan)), Skeleton(instantiate_first({}, stream_plan), 0, {}, stream_plan, opt_action_plan, opt_cost))) greedily_process_queue(queue, evaluations, store, sampling_time)
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 solve_focused(problem, stream_info={}, action_info={}, dynamic_streams=[], max_time=INF, max_cost=INF, unit_costs=False, commit=True, effort_weight=None, eager_layers=1, visualize=False, verbose=True, postprocess=False, **search_kwargs): """ Solves a PDDLStream problem by first hypothesizing stream outputs and then determining whether they exist :param problem: a PDDLStream problem :param action_info: a dictionary from stream name to ActionInfo for planning and execution :param stream_info: a dictionary from stream name to StreamInfo altering how individual streams are handled :param max_time: the maximum amount of time to apply streams :param max_cost: a strict upper bound on plan cost :param commit: if True, it commits to instantiating a particular partial plan-skeleton. :param effort_weight: a multiplier for stream effort compared to action costs :param eager_layers: the number of eager stream application layers per iteration :param visualize: if True, it draws the constraint network and stream plan as a graphviz file :param verbose: if True, this prints the result of each stream application :param search_kwargs: keyword args for the search subroutine :return: a tuple (plan, cost, evaluations) where plan is a sequence of actions (or None), cost is the cost of the plan, and evaluations is init but expanded using stream applications """ # TODO: return to just using the highest level samplers at the start start_time = time.time() num_iterations = 0 best_plan = None best_cost = INF evaluations, goal_expression, domain, stream_name, externals = parse_problem( problem) action_info = get_action_info(action_info) update_stream_info(externals, stream_info) load_stream_statistics(stream_name, externals) eager_externals = filter(lambda e: e.info.eager, externals) disabled = [] if visualize: clear_visualizations() #functions = filter(lambda s: isinstance(s, Function), externals) functions = filter(lambda s: type(s) is Function, externals) negative = filter(lambda s: type(s) is Predicate and s.is_negative(), externals) streams = filter(lambda s: s not in (functions + negative), externals) stream_results = [] depth = 1 sampling_queue = [] while elapsed_time(start_time) < max_time: search_time = time.time( ) # TODO: allocate more sampling effort to maintain the balance # TODO: total search time vs most recent search time? if stream_results is None: stream_plan, action_plan, cost = None, None, INF else: num_iterations += 1 print( '\nIteration: {} | Depth: {} | Evaluations: {} | Cost: {} | Time: {:.3f}' .format(num_iterations, depth, len(evaluations), best_cost, elapsed_time(start_time))) # TODO: constrain to use previous plan to some degree eagerly_evaluate(evaluations, eager_externals, eager_layers, max_time - elapsed_time(start_time), verbose) stream_results += optimistic_process_streams( evaluations_from_stream_plan(evaluations, stream_results), functions) # TODO: warning check if using simultaneous_stream_plan or relaxed_stream_plan with non-eager functions solve_stream_plan = relaxed_stream_plan if effort_weight is None else simultaneous_stream_plan #solve_stream_plan = sequential_stream_plan if effort_weight is None else simultaneous_stream_plan combined_plan, cost = solve_stream_plan(evaluations, goal_expression, domain, stream_results, negative, max_cost=best_cost, unit_costs=unit_costs, **search_kwargs) combined_plan = reorder_combined_plan(evaluations, combined_plan, action_info, domain) print('Combined plan: {}'.format(combined_plan)) stream_plan, action_plan = separate_plan(combined_plan, action_info) stream_plan = reorder_stream_plan( stream_plan) # TODO: is this strictly redundant? stream_plan = get_synthetic_stream_plan(stream_plan, dynamic_streams) print('Stream plan: {}\n' 'Action plan: {}'.format(stream_plan, action_plan)) if stream_plan is None: if disabled or (depth != 0): if depth == 0: reset_disabled(disabled) stream_results = optimistic_process_streams( evaluations, streams) depth = 0 # Recurse on problems else: break elif len(stream_plan) == 0: if cost < best_cost: best_plan = action_plan best_cost = cost if best_cost < max_cost: break stream_results = None else: """ sampling_key = SkeletonKey(0, len(stream_plan)) sampling_problem = Skeleton({}, stream_plan, action_plan, cost) heappush(sampling_queue, (sampling_key, sampling_problem)) greedily_process_queue(sampling_queue, evaluations, disabled, max_cost, True, 0, verbose) depth += 1 stream_results = None """ if visualize: create_visualizations(evaluations, stream_plan, num_iterations) option = True if option: # TODO: can instantiate all but subtract stream_results # TODO: can even pass a subset of the fluent state # TODO: can just compute the stream plan preimage # TODO: replan constraining the initial state and plan skeleton # TODO: reuse subproblems # TODO: always start from the initial state (i.e. don't update) old_evaluations = set(evaluations) stream_results, _ = process_stream_plan( evaluations, stream_plan, disabled, verbose) new_evaluations = set(evaluations) - old_evaluations if stream_results is not None: new_instances = [r.instance for r in stream_results] stream_results = optimistic_process_streams( new_evaluations, streams, new_instances) if not commit: stream_results = None depth += 1 reset_disabled(disabled) if postprocess and (not unit_costs) and (best_plan is not None): best_plan = locally_optimize(evaluations, best_plan, goal_expression, domain, functions, negative, dynamic_streams, verbose) write_stream_statistics(stream_name, externals) return revert_solution(best_plan, best_cost, evaluations)