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 verify_expression(aexp): # print("Verifying ",str(aexp)) if isinstance(aexp, pddl.PrimitiveNumericExpression): # print("- it is a PNE with fluent",aexp.symbol) if not aexp.symbol in function_names: msg = ( "WARNING: Function symbol '%s' appears in a numeric expression but is not defined in domain file.\n" "Adding new symbol '%s' with arity %d.") % ( aexp.symbol, aexp.symbol, len(aexp.args)) print(msg, file=sys.stderr) if len(aexp.args) == 0: newfunction = pddl.Function(aexp.symbol, [], "number") task.functions.append(newfunction) initassignment = pddl.Assign(aexp, pddl.NumericConstant(0.0)) task.num_init.append(initassignment) else: # if this occurs in an actual domain, maybe we could also determine the type of the parameters of higher-arity functions raise Error( "Don't know the parameters of function %s with arity %d" % (aexp.symbol, len(aexp.args))) assert False # elif isinstance(aexp, pddl.NumericConstant): # print("- it is a constant: ",aexp.value) elif isinstance(aexp, pddl.AdditiveInverse): # print("- it is an additive inverse: ",aexp) assert len(parts) == 1 verify_expression(aexp.parts[0]) elif isinstance(aexp, pddl.ArithmeticExpression): # print("- it is an arithmetic expression: ",aexp) for part in aexp.parts: verify_expression(part)
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 is_constant(axiom): if DEBUG: print("Testing if %s is constant" % axiom) if isinstance(axiom, pddl.PrimitiveNumericExpression): if axiom in axiom_by_pne: axiom = axiom_by_pne[axiom] else: return (False, None) if (axiom.op == None and isinstance(axiom.parts[0], pddl.NumericConstant)): return (True, axiom.parts[0].value) else: all_constants = True values = [] assert axiom.parts for part in axiom.parts: if DEBUG: print("recursively checking part %s" % part) const, val = is_constant(part) if not const: if DEBUG: print("not constant -> aborting loop") all_constants = False break if DEBUG: print("appending constant %s" % val) values.append(val) if all_constants: assert len(values), "Values is empty" if len(values) == 1: if axiom.op == "-": new_value = -values[0] axiom.op = None else: assert axiom.op == None new_value = values[0] axiom.parts = [pddl.NumericConstant(new_value)] axiom.ntype = 'C' axiom.effect.ntype = 'C' return (True, new_value) else: calculation = axiom.op.join(map(str, values)) new_val = eval(calculation) axiom.parts = [pddl.NumericConstant(new_val)] axiom.ntype = 'C' axiom.effect.ntype = 'C' axiom.op = None return (True, new_val) else: return (False, None)
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 is_constant(axiom): if isinstance(axiom, pddl.PrimitiveNumericExpression): if axiom in axiom_by_pne: axiom = axiom_by_pne[axiom] else: return (False, None) if (axiom.op == None and isinstance(axiom.parts[0], pddl.NumericConstant)): return (True, axiom.parts[0].value) else: all_constants = True values = [] for part in axiom.parts: const, val = is_constant(part) if not const: all_constants = False break values.append(val) if all_constants: if len(values) == 1: if axiom.op == "-": new_value = -values[0] axiom.op = None else: assert axiom.op == None new_value = values[0] axiom.parts = [pddl.NumericConstant(new_value)] return (True, new_value) else: calculation = axiom.op.join(map(str, values)) new_val = eval(calculation) axiom.parts = [pddl.NumericConstant(new_val)] axiom.op = None return (True, new_val) else: return (False, None)
def parse_action(alist, type_dict, predicate_dict): iterator = iter(alist) action_tag = next(iterator) assert action_tag == ":action" or action_tag == ':action' name = next(iterator) parameters_tag_opt = next(iterator) if parameters_tag_opt == ":parameters": parameters = parse_typed_list(next(iterator), only_variables=True) precondition_tag_opt = next(iterator) else: parameters = [] precondition_tag_opt = parameters_tag_opt if precondition_tag_opt == ":precondition": precondition_list = next(iterator) if not precondition_list: # Note that :precondition () is allowed in PDDL. precondition = pddl.Conjunction([]) else: precondition = parse_condition(precondition_list, type_dict, predicate_dict) precondition = precondition.simplified() effect_tag = next(iterator) else: precondition = pddl.Conjunction([]) effect_tag = precondition_tag_opt assert effect_tag == ":effect" effect_list = next(iterator) eff = [] if effect_list: try: cost = parse_effects(effect_list, eff, type_dict, predicate_dict) except ValueError as e: raise SystemExit("Error in Action %s\nReason: %s." % (name, e)) next(iterator) == ":duration" duration = next(iterator) if len(duration[2]) == 1: duration = pddl.NumericConstant(int(duration[2])) else: duration = pddl.f_expression.PrimitiveNumericExpression( duration[2][0], duration[2][1:]) for rest in iterator: assert False, rest if eff: return pddl.Action(name, parameters, len(parameters), precondition, eff, cost, duration) else: return None
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 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)