def parse_expression(exp): if isinstance(exp, list): operator_or_functionsymbol = exp[0] if operator_or_functionsymbol in ("+", "/", "*", "-"): # NFD # TODO: print warning if only :action-cost is used in PDDL requirements, but not :numeric-fluents args = [parse_expression(arg) for arg in exp[1:]] operator = operator_or_functionsymbol else: # TODO: if only :action-costs are used, operator_or_functionsymbol MUST be "total-cost" # TODO: Support terms. right now a numeric variable is expected here return pddl.PrimitiveNumericExpression(operator_or_functionsymbol, exp[1:]) if operator == "+": return pddl.Sum(args) elif operator == "/": assert len(args) == 2 return pddl.Quotient(args) elif operator == "*": return pddl.Product(args) else: if len(args) == 1: return pddl.AdditiveInverse(args) else: assert len(args) == 2 return pddl.Difference(args) elif isFloat(exp): # TODO: another place where a warning might be useful if only :action-costs but # not :numeric-fluents are used. Negative numbers are only supported with :numeric-fluents return pddl.NumericConstant(float(exp)) else: return pddl.PrimitiveNumericExpression(exp, [])
def parse_expression(exp): if isinstance(exp, list): functionsymbol = exp[0] return pddl.PrimitiveNumericExpression(functionsymbol, exp[1:]) elif exp.replace(".", "").isdigit(): return pddl.NumericConstant(float(exp)) elif exp[0] == "-": raise ValueError("Negative numbers are not supported") else: return pddl.PrimitiveNumericExpression(exp, [])
def make_cost(cost): if cost is None: return cost fluent = pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]) try: expression = pddl.NumericConstant(cost) except TypeError: expression = pddl.PrimitiveNumericExpression( symbol=get_prefix(cost), args=list(map(pddl_from_object, get_args(cost)))) return pddl.Increase(fluent=fluent, expression=expression)
def get_stream_action(result, name, unit_cost, effect_scale=1): #from pddl_parser.parsing_functions import parse_action import pddl parameters = [] preconditions = [ fd_from_fact(fact) for fact in result.instance.get_domain() ] precondition = pddl.Conjunction(preconditions) effects = [ pddl.Effect(parameters=[], condition=pddl.Truth(), literal=fd_from_fact(fact)) for fact in result.get_certified() ] effort = 1 if unit_cost else result.instance.get_effort() if effort == INF: return None fluent = pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]) expression = pddl.NumericConstant(int_ceil(effect_scale * effort)) # Integer cost = pddl.Increase(fluent=fluent, expression=expression) # Can also be None return pddl.Action(name=name, parameters=parameters, num_external_parameters=len(parameters), precondition=precondition, effects=effects, cost=cost)
def get_fluent_functions(model): fluent_pnes = set() for atom in model: if isinstance(atom.predicate, pddl.PrimitiveNumericExpression): fluent_pnes.add( pddl.PrimitiveNumericExpression(atom.predicate.symbol, atom.args)) return fluent_pnes
def get_fluent_functions(model): fluent_pnes = set() for atom in model: if isinstance(atom.predicate, pddl.PrimitiveNumericExpression): # print("instantiate.get_fluent_functions adds atom" ,atom) # assert(atom.predicate.ntype != 'U') , "Atom is falsch %s" % atom fluent_pnes.add( pddl.PrimitiveNumericExpression(atom.predicate.symbol, atom.args, atom.predicate.ntype)) return fluent_pnes
def compile_to_exogenous_actions(evaluations, domain, streams): import pddl # TODO: automatically derive fluents # TODO: version of this that operates on fluents of length one? # TODO: better instantiation when have full parameters # TODO: conversion from stream cost to real cost units? # TODO: any predicates derived would need to be replaced as well fluent_predicates = get_fluents(domain) domain_predicates = {get_prefix(a) for s in streams for a in s.domain} if not (domain_predicates & fluent_predicates): return certified_predicates = {get_prefix(a) for s in streams for a in s.certified} future_map = {p: 'f-{}'.format(p) for p in certified_predicates} augment_evaluations(evaluations, future_map) rename_future = lambda a: rename_atom(a, future_map) for stream in list(streams): if not isinstance(stream, Stream): raise NotImplementedError(stream) # TODO: could also just have conditions asserting that one of the fluent conditions fails streams.append(create_static_stream(stream, evaluations, fluent_predicates, rename_future)) stream_atom = streams[-1].certified[0] parameters = [pddl.TypedObject(p, 'object') for p in get_args(stream_atom)] # TODO: add to predicates as well? domain.predicate_dict[get_prefix(stream_atom)] = pddl.Predicate(get_prefix(stream_atom), parameters) precondition = pddl.Conjunction(tuple(map(fd_from_fact, (stream_atom,) + tuple(stream.domain)))) effects = [pddl.Effect(parameters=[], condition=pddl.Truth(), literal=fd_from_fact(fact)) for fact in stream.certified] effort = 1 # TODO: use stream info #effort = 1 if unit_cost else result.instance.get_effort() #if effort == INF: # continue fluent = pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]) expression = pddl.NumericConstant(int_ceil(effort)) # Integer cost = pddl.Increase(fluent=fluent, expression=expression) # Can also be None domain.actions.append(pddl.Action(name='call-{}'.format(stream.name), parameters=parameters, num_external_parameters=len(parameters), precondition=precondition, effects=effects, cost=cost)) stream.certified = tuple(set(stream.certified) | set(map(rename_future, stream.certified)))
def simplify_actions(opt_evaluations, action_plan, task, actions, unit_costs): # TODO: add ordering constraints to simplify the optimization import pddl import instantiate fluent_facts = MockSet() init_facts = set() type_to_objects = instantiate.get_objects_by_type(task.objects, task.types) results_from_head = get_results_from_head(opt_evaluations) action_from_name = {} function_plan = set() for i, (name, args) in enumerate(action_plan): action = find_unique(lambda a: a.name == name, actions) assert (len(action.parameters) == len(args)) # parameters = action.parameters[:action.num_external_parameters] var_mapping = {p.name: a for p, a in zip(action.parameters, args)} new_name = '{}-{}'.format(name, i) new_parameters = action.parameters[len(args):] new_preconditions = [] action.precondition.instantiate(var_mapping, init_facts, fluent_facts, new_preconditions) new_effects = [] for eff in action.effects: eff.instantiate(var_mapping, init_facts, fluent_facts, type_to_objects, new_effects) new_effects = [pddl.Effect([], pddl.Conjunction(conditions), effect) for conditions, effect in new_effects] cost = pddl.Increase(fluent=pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]), expression=pddl.NumericConstant(1)) # cost = None task.actions.append(pddl.Action(new_name, new_parameters, len(new_parameters), pddl.Conjunction(new_preconditions), new_effects, cost)) action_from_name[new_name] = (name, map(obj_from_pddl, args)) if not unit_costs: function_result = extract_function_results(results_from_head, action, args) if function_result is not None: function_plan.add(function_result) return action_from_name, list(function_plan)
def instantiate(task, model): relaxed_reachable = False fluent_facts = get_fluent_facts(task, model) fluent_functions = get_fluent_functions(model) ## HACK: This is a not very clean way of initializing the previously ## added functions that store the duration of an action to a haphazardly value for atom in model: if isinstance(atom.predicate,str) and atom.predicate.startswith("defined!duration_"): pne = pddl.PrimitiveNumericExpression(atom.predicate.replace("defined!","",1),atom.args) value = pddl.NumericConstant(1.0) init_assign = pddl.Assign(pne, value) task.init.append(init_assign) init_facts = set(task.init) # TODO adapt init_function_vals = init_function_values(init_facts) # print "** fluent functions" # for function in fluent_functions: # function.dump() # print "** fluent facts" # for fact in fluent_facts: # print fact # print "** init facts" # for fact in init_facts: # print fact type_to_objects = get_objects_by_type(task.objects,task.types) instantiated_actions = [] instantiated_durative_actions = [] instantiated_axioms = [] instantiated_numeric_axioms = set() new_constant_numeric_axioms = set() reachable_action_parameters = defaultdict(list) for atom in model: if isinstance(atom.predicate, pddl.Action): action = atom.predicate parameters = action.parameters if isinstance(action.condition, pddl.ExistentialCondition): parameters = list(parameters) parameters += action.condition.parameters variable_mapping = dict([(pddl.Variable(par.name), arg) for par, arg in zip(parameters, atom.args)]) inst_action = action.instantiate(variable_mapping, init_facts, fluent_facts, init_function_vals, fluent_functions, task, new_constant_numeric_axioms, type_to_objects) if inst_action: instantiated_actions.append(inst_action) elif isinstance(atom.predicate, pddl.DurativeAction): action = atom.predicate parameters = action.parameters reachable_action_parameters[action.name].append(parameters) for condition in action.condition: if isinstance(condition,pddl.ExistentialCondition): parameters = list(parameters) parameters += condition.parameters variable_mapping = dict([(pddl.Variable(par.name), arg) for par, arg in zip(parameters, atom.args)]) inst_action = action.instantiate(variable_mapping, init_facts, fluent_facts, init_function_vals, fluent_functions, task, new_constant_numeric_axioms, type_to_objects) if inst_action: instantiated_durative_actions.append(inst_action) elif isinstance(atom.predicate, pddl.Axiom): axiom = atom.predicate parameters = axiom.parameters if isinstance(axiom.condition, pddl.ExistentialCondition): parameters = list(parameters) parameters += axiom.condition.parameters variable_mapping = dict([(pddl.Variable(par.name), arg) for par, arg in zip(parameters, atom.args)]) inst_axiom = axiom.instantiate(variable_mapping, init_facts, fluent_facts, fluent_functions, init_function_vals, task, new_constant_numeric_axioms) if inst_axiom: instantiated_axioms.append(inst_axiom) elif isinstance(atom.predicate, pddl.NumericAxiom): axiom = atom.predicate variable_mapping = dict([(pddl.Variable(par.name), arg) for par, arg in zip(axiom.parameters, atom.args)]) new_constant_numeric_axioms = set() inst_axiom = axiom.instantiate(variable_mapping, fluent_functions, init_function_vals, task, new_constant_numeric_axioms) instantiated_numeric_axioms.add(inst_axiom) elif atom.predicate == "@goal-reachable": relaxed_reachable = True instantiated_numeric_axioms |= new_constant_numeric_axioms return (relaxed_reachable, fluent_facts, fluent_functions, instantiated_actions, instantiated_durative_actions, instantiated_axioms, instantiated_numeric_axioms, reachable_action_parameters)
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 parse_task_pddl(task_pddl, type_dict, predicate_dict): iterator = iter(task_pddl) define_tag = next(iterator) assert define_tag == "define" problem_line = next(iterator) assert problem_line[0] == "problem" and len(problem_line) == 2 yield problem_line[1] domain_line = next(iterator) assert domain_line[0] == ":domain" and len(domain_line) == 2 yield domain_line[1] requirements_opt = next(iterator) if requirements_opt[0] == ":requirements": requirements = requirements_opt[1:] objects_opt = next(iterator) else: requirements = [] objects_opt = requirements_opt yield pddl.Requirements(requirements) if objects_opt[0] == ":objects": yield parse_typed_list(objects_opt[1:]) init = next(iterator) else: yield [] init = objects_opt assert init[0] == ":init" initial = [] num_initial = [] initial_true = set() initial_false = set() initial_assignments = dict() for fact in init[1:]: # if DEBUG: print("Analyzing fact %s"%fact) if fact[0] == "=": # if DEBUG: print("Fact is '=' assignment") try: assignment = parse_assignment(fact) except ValueError as e: raise SystemExit("Error in initial state specification\n" + "Reason: %s." % e) if not isinstance(assignment.expression, pddl.NumericConstant): raise SystemExit("Illegal assignment in initial state " + "specification:\n%s" % assignment) if assignment.fluent in initial_assignments: prev = initial_assignments[assignment.fluent] if assignment.expression == prev.expression: print("Warning: %s is specified twice" % assignment, "in initial state specification") else: raise SystemExit("Error in initial state specification\n" + "Reason: conflicting assignment for " + "%s." % assignment.fluent) else: initial_assignments[assignment.fluent] = assignment num_initial.append(assignment) elif fact[0] == "not": # if DEBUG: print("Fact is negation") atom = pddl.Atom(fact[1][0], fact[1][1:]) check_atom_consistency(atom, initial_false, initial_true, False) initial_false.add(atom) else: # if DEBUG: print("Fact is positive atom") atom = pddl.Atom(fact[0], fact[1:]) check_atom_consistency(atom, initial_true, initial_false) initial_true.add(atom) # if DEBUG: print("initial_true is %s"%sorted(initial_true)) initial.extend(initial_true) if DEBUG: initial.sort( key=lambda x: x.__repr__()) # makes the result deterministic # print("initial is s%s"%initial) yield initial # yielding num_initial delayed (metric section may add artificial fluent 'total-cost' # goal goal = next(iterator) assert goal[0] == ":goal" and len(goal) == 2 #metric metric_symbol = '<' # minimize metric_expression = None # unit cost for entry in iterator: if entry[0] == ":metric": if entry[1] == "minimize": metric_symbol = '<' else: assert entry[ 1] == "maximize", "Unknown metric, 'minimize' or 'maximize' expected." metric_symbol = '>' # try: metric_expression = parse_expression(entry[2]) # except: # raise SystemExit("Cannot parse metric expression in %s" % entry[2]) if metric_expression is None: metric_expression = pddl.PrimitiveNumericExpression( 'total-cost', [], 'I') num_initial.append(parse_assignment(['=', 'total-cost', 0])) # print("parsing_functions yields num_initial:", num_initial) yield num_initial yield parse_condition(goal[1], type_dict, predicate_dict) yield (metric_symbol, metric_expression) for entry in iterator: assert False, entry
def instantiate(task, model): relaxed_reachable = False fluent_facts = get_fluent_facts(task, model) fluent_functions = get_fluent_functions(model) ## HACK: This is a not very clean way of initializing the previously ## added functions that store the duration of an action to a haphazardly value for atom in model: if isinstance(atom.predicate, str) and atom.predicate.startswith("defined!duration_"): pne = pddl.PrimitiveNumericExpression( atom.predicate.replace("defined!", "", 1), atom.args) value = pddl.NumericConstant(1.0) init_assign = pddl.Assign(pne, value) task.init.append(init_assign) init_facts = set(task.init) # TODO adapt init_function_vals = init_function_values(init_facts) # Determine initial facts, that are not fluents => constant facts, that a module might need init_constant_fluents = set(init_function_vals) init_constant_fluents.difference_update( fluent_functions ) # all fluents that are in init, but are NOT a fluent -> constant # Now get the assigned values from the init_facts for the constant fluents init_constant_numeric_facts = set( ) # This will hold Assigns that assign the fluents for i in init_constant_fluents: for j in init_facts: if isinstance(j, pddl.Assign): if isinstance(j.fluent, pddl.PrimitiveNumericExpression): if j.fluent is i: # Assign in init_fact assign this (i) fluent init_constant_numeric_facts.add(j) # Now get predicates that are in init, but are not fluent_facts init_constant_predicate_facts = set() for i in init_facts: if isinstance(i, pddl.Atom): # do NOT consider PNEs, etc. if i not in fluent_facts: # only consider non-fluents if i.predicate is not "=": # hack to remove the intermediate '=' fluents init_constant_predicate_facts.add(i) # print "** fluent functions" # for function in fluent_functions: # function.dump() # print "** fluent facts" # for fact in fluent_facts: # print fact # print "** init facts" # for fact in init_facts: # print fact type_to_objects = get_objects_by_type(task.objects, task.types) instantiated_actions = [] instantiated_durative_actions = [] instantiated_axioms = [] instantiated_numeric_axioms = set() new_constant_numeric_axioms = set() reachable_action_parameters = defaultdict(list) instantiated_modules = set() for atom in model: if isinstance(atom.predicate, pddl.Action): action = atom.predicate parameters = action.parameters if isinstance(action.condition, pddl.ExistentialCondition): parameters = list(parameters) parameters += action.condition.parameters variable_mapping = dict([ (pddl.Variable(par.name), arg) for par, arg in zip(parameters, atom.args) ]) inst_action = action.instantiate(variable_mapping, init_facts, fluent_facts, init_function_vals, fluent_functions, task, new_constant_numeric_axioms, instantiated_modules, type_to_objects) if inst_action: instantiated_actions.append(inst_action) elif isinstance(atom.predicate, pddl.DurativeAction): action = atom.predicate parameters = action.parameters reachable_action_parameters[action.name].append(parameters) for condition in action.condition: if isinstance(condition, pddl.ExistentialCondition): parameters = list(parameters) parameters += condition.parameters variable_mapping = dict([ (pddl.Variable(par.name), arg) for par, arg in zip(parameters, atom.args) ]) inst_action = action.instantiate(variable_mapping, init_facts, fluent_facts, init_function_vals, fluent_functions, task, new_constant_numeric_axioms, instantiated_modules, type_to_objects) if inst_action: instantiated_durative_actions.append(inst_action) elif isinstance(atom.predicate, pddl.Axiom): axiom = atom.predicate parameters = axiom.parameters if isinstance(axiom.condition, pddl.ExistentialCondition): parameters = list(parameters) parameters += axiom.condition.parameters variable_mapping = dict([ (pddl.Variable(par.name), arg) for par, arg in zip(parameters, atom.args) ]) inst_axiom = axiom.instantiate(variable_mapping, init_facts, fluent_facts, fluent_functions, init_function_vals, task, new_constant_numeric_axioms, instantiated_modules) if inst_axiom: instantiated_axioms.append(inst_axiom) elif isinstance(atom.predicate, pddl.NumericAxiom): axiom = atom.predicate variable_mapping = dict([ (pddl.Variable(par.name), arg) for par, arg in zip(axiom.parameters, atom.args) ]) new_constant_numeric_axioms = set() inst_axiom = axiom.instantiate(variable_mapping, fluent_functions, init_function_vals, task, new_constant_numeric_axioms) instantiated_numeric_axioms.add(inst_axiom) elif atom.predicate == "@goal-reachable": relaxed_reachable = True instantiated_numeric_axioms |= new_constant_numeric_axioms return (relaxed_reachable, fluent_facts, fluent_functions, instantiated_actions, instantiated_durative_actions, instantiated_axioms, instantiated_numeric_axioms, instantiated_modules, init_constant_predicate_facts, init_constant_numeric_facts, reachable_action_parameters)
def make_cost(cost): fluent = pddl.PrimitiveNumericExpression(symbol=TOTAL_COST, args=[]) expression = pddl.NumericConstant(cost) return pddl.Increase(fluent=fluent, expression=expression)