def add_atom(self, atom): if not is_atom(atom): return False head = atom.head if head in self.atoms: return False self.atoms.add(head) # TODO: doing this in a way that will eventually allow constants for i, stream in enumerate(self.streams): for j, domain_atom in enumerate(stream.domain): if get_prefix(head) != get_prefix(domain_atom): continue if len(head.args) != len(get_args(domain_atom)): raise ValueError(head, domain_atom) if any( isinstance(b, Object) and (a != b) for (a, b) in zip(head.args, get_args(domain_atom))): continue self.atoms_from_domain[(i, j)].append(head) values = [ self.atoms_from_domain[(i, k)] if j != k else [head] for k in range(len(stream.domain)) ] domain = list(map(head_from_fact, stream.domain)) #domain = stream.domain for combo in product(*values): mapping = get_mapping(domain, combo) if mapping is None: continue input_objects = tuple(mapping[p] for p in stream.inputs) self._add_instance(stream, input_objects) return True
def get_mapping(atoms1, atoms2, initial={}): assert len(atoms1) == len(atoms2) mapping = initial.copy() for a1, a2 in zip(atoms1, atoms2): assert (get_prefix(a1) == get_prefix(a2)) #for arg1, arg2 in zip(get_args(a1), a2.args): # TODO: this is because eval vs predicate for arg1, arg2 in zip(a1.args, a2.args): if mapping.get(arg1, arg2) == arg2: mapping[arg1] = arg2 else: return None return mapping
def compile_to_exogenous_axioms(evaluations, domain, streams): import pddl 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) derived_map = {p: 'd-{}'.format(p) for p in certified_predicates} rename_derived = lambda a: rename_atom(a, derived_map) for action in domain.actions: action.precondition = replace_predicates(derived_map, action.precondition) for effect in action.effects: assert(isinstance(effect, pddl.Effect)) effect.condition = replace_predicates(derived_map, effect.condition) for axiom in domain.axioms: axiom.condition = replace_predicates(derived_map, axiom.condition) #fluent_predicates.update(certified_predicates) for stream in list(streams): if not isinstance(stream, Stream): raise NotImplementedError(stream) streams.append(create_static_stream(stream, evaluations, fluent_predicates, rename_future)) stream_atom = streams[-1].certified[0] domain.predicate_dict[get_prefix(stream_atom)] = pddl.Predicate(get_prefix(stream_atom), get_args(stream_atom)) precondition = pddl.Conjunction(tuple(map(fd_from_fact, (stream_atom,) + tuple(map(rename_derived, stream.domain))))) for fact in stream.certified: derived_fact = fd_from_fact(rename_derived(fact)) external_params = derived_fact.args internal_params = tuple(p for p in (stream.inputs + stream.outputs) if p not in derived_fact.args) parameters = tuple(pddl.TypedObject(p, 'object') for p in (external_params + internal_params)) #precondition = pddl.Conjunction(tuple(map(fd_from_fact, [stream_atom] + # list(map(rename_derived, stream.domain))))) #precondition = pddl.Disjunction([fd_from_fact(fact), precondition]) # TODO: quantifier domain.axioms.extend([ pddl.Axiom(name=derived_fact.predicate, parameters=parameters, num_external_parameters=len(external_params), condition=precondition), pddl.Axiom(name=derived_fact.predicate, parameters=parameters[:len(external_params)], num_external_parameters=len(external_params), condition=fd_from_fact(fact))]) stream.certified = tuple(set(stream.certified) | set(map(rename_future, stream.certified)))
def fd_from_fact(fact): # TODO: convert to evaluation? prefix = get_prefix(fact) if prefix == NOT: return fd_from_fact(fact[1]).negate() if prefix == EQ: _, head, value = fact predicate = get_prefix(head) args = map(pddl_from_object, get_args(head)) fluent = pddl.f_expression.PrimitiveNumericExpression(symbol=predicate, args=args) expression = pddl.f_expression.NumericConstant(value) return pddl.f_expression.Assign(fluent, expression) args = map(pddl_from_object, get_args(fact)) return pddl.Atom(prefix, args)
def create_static_stream(stream, evaluations, fluent_predicates, get_future): def static_fn(*input_values): instance = stream.get_instance(tuple(map(Object.from_value, input_values))) if all(evaluation_from_fact(f) in evaluations for f in instance.get_domain()): return None return tuple(FutureValue(stream.name, input_values, o) for o in stream.outputs) #opt_evaluations = None def static_opt_gen_fn(*input_values): instance = stream.get_instance(tuple(map(Object.from_value, input_values))) if all(evaluation_from_fact(f) in evaluations for f in instance.get_domain()): return for output_values in stream.opt_gen_fn(*input_values): yield output_values # TODO: need to replace regular opt_gen_fn to update opt_evaluations # if I want to prevent switch from normal to static in opt # Focused algorithm naturally biases against using future because of axiom layers stream_name = 'future-{}'.format(stream.name) gen_fn = from_fn(static_fn) static_domain = list(filter(lambda a: get_prefix(a) not in fluent_predicates, stream.domain)) new_domain = list(map(get_future, static_domain)) stream_atom = ('{}-result'.format(stream.name),) + tuple(stream.inputs + stream.outputs) new_certified = [stream_atom] + list(map(get_future, stream.certified)) static_stream = Stream(stream_name, gen_fn, stream.inputs, new_domain, stream.outputs, new_certified, stream.info) static_stream.opt_gen_fn = static_opt_gen_fn return static_stream
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 get_stream_instances(stream_plan): import pddl # TODO: something that inverts the negative items stream_instances = [] # TODO: could even apply these to the state directly for result in stream_plan: name = result.instance.external.name precondition = list(map(fd_from_fact, result.instance.get_domain())) effects = [([], fd_from_fact(fact)) for fact in result.get_certified() if not get_prefix(fact) == EQ] cost = None # TODO: effort? instance = pddl.PropositionalAction(name, precondition, effects, cost) stream_instances.append(instance) return stream_instances
def __init__(self, head, fn, domain, info): if info is None: info = FunctionInfo(p_success=self._default_p_success, overhead=self._default_overhead) super(Function, self).__init__(get_prefix(head), info, get_args(head), domain) self.head = head opt_fn = lambda *args: self._codomain() if fn == DEBUG: fn = opt_fn self.fn = fn self.opt_fn = opt_fn if ( self.info.opt_fn is None) else self.info.opt_fn
def parse_common(lisp_list, stream_map, stream_info): assert (2 <= len(lisp_list) <= 3) head = tuple(lisp_list[1]) assert (is_head(head)) name = get_prefix(head) if stream_map == DEBUG: fn = DEBUG else: if name not in stream_map: raise ValueError('Undefined external function: {}'.format(name)) fn = stream_map[name] domain = [] if len(lisp_list) == 3: domain = list_from_conjunction(lisp_list[2]) info = stream_info.get(name, None) return head, fn, domain, info
def next_results(self, verbose=False): start_time = time.time() assert not self.enumerated self.enumerated = True input_values = self.get_input_values() try: value = self.external.fn(*input_values) except TypeError: raise TypeError('Function [{}] expects {} inputs'.format( self.external.name, len(input_values))) self.value = self.external._codomain(value) # TODO: cast the inputs and test whether still equal? #if not (type(self.value) is self.external._codomain): #if not isinstance(self.value, self.external._codomain): if self.value != value: raise ValueError( 'Function [{}] produced a nonintegral value [{}]. ' 'FastDownward only supports integral costs. ' 'To "use" real costs, scale each cost by a large factor, ' 'capturing the most significant bits.'.format( self.external.name, self.value)) if self.value < 0: raise ValueError( 'Function [{}] produced a negative value [{}]'.format( self.external.name, self.value)) if verbose: print('{}{}={}'.format(get_prefix(self.external.head), str_from_tuple(self.get_input_values()), self.value)) results = [self.external._Result(self, self.value)] if isinstance(self, PredicateInstance) and ( self.value != self.external.opt_fn(*input_values)): self.update_statistics(start_time, []) # TODO: do this more automatically else: self.update_statistics(start_time, results) return results
def visualize_constraints(constraints, filename='constraint_network.pdf', use_functions=True): from pygraphviz import AGraph graph = AGraph(strict=True, directed=False) graph.node_attr['style'] = 'filled' graph.node_attr['shape'] = 'circle' graph.node_attr['fontcolor'] = 'black' graph.node_attr['colorscheme'] = 'SVG' graph.edge_attr['colorscheme'] = 'SVG' functions = set() heads = set() for fact in constraints: if get_prefix(fact) == EQ: functions.add(fact[1]) else: heads.add(fact) heads.update(functions) objects = {a for head in heads for a in get_args(head)} optimistic_objects = filter(lambda o: isinstance(o, OptimisticObject), objects) for opt_obj in optimistic_objects: graph.add_node(str(opt_obj), shape='circle', color=PARAMETER_COLOR) for head in heads: if not use_functions and (head in functions): continue # TODO: prune values w/o free parameters? name = str_from_tuple(head) color = COST_COLOR if head in functions else CONSTRAINT_COLOR graph.add_node(name, shape='box', color=color) for arg in get_args(head): if arg in optimistic_objects: graph.add_edge(name, str(arg)) graph.draw(filename, prog='dot') return graph
def add_fact(fact): head, color = (fact[1], COST_COLOR) if get_prefix(fact) == EQ else (fact, CONSTRAINT_COLOR) s_fact = str_from_tuple(head) graph.add_node(s_fact, shape='box', color=color) return s_fact
def rename_atom(atom, mapping): name = get_prefix(atom) if name not in mapping: return atom return (mapping[name],) + get_args(atom)