def _compute_links(self, state): """Helper method for mutex. Sets up state/action dependencies. """ # Map from literal to all actions that it's a precond of. current_links = defaultdict(list) # Map from literal to all actions that it's an effect of. next_links = defaultdict(list) for lit in state.literals: pred = lit.predicate persist_pred = structs.Predicate("PERSIST" + pred.name, pred.arity, pred.var_types, pred.is_negative, pred.is_anti, pred.negated_as_failure) persist_lit = structs.Literal(persist_pred, lit.variables) current_links[lit].append(persist_lit) next_links[lit].append(persist_lit) for action in self._action_space.all_ground_literals(state): for op in self._learned_operators: assignments = self._preconds_satisfied(state, action, op.preconds.literals) if assignments is None: continue ground_preconds = [ structs.ground_literal(l, assignments) for l in op.preconds.literals ] for precond in ground_preconds: current_links[precond].append(action) ground_effects = [ structs.ground_literal(l, assignments) for l in op.effects.literals ] for effect in ground_effects: next_links[effect].append(action) return current_links, next_links
def _get_predicted_next_state_ops(self, state, action, mode="max"): """WARNING: Only use this method when self._learned_operators is GROUND TRUTH OPS!!! """ for op in self._learned_operators: assignments = self._preconds_satisfied(state, action, op.preconds.literals) if assignments is not None: ground_effects = [] for l in op.effects.literals: if isinstance(l, structs.ProbabilisticEffect): if mode == "max": # assume max-probability event chosen_effect = l.max() elif mode == "sample": # sample an event chosen_effect = l.sample() else: raise Exception("Invalid mode: {}".format(mode)) if chosen_effect == "NOCHANGE": continue if isinstance(chosen_effect, structs.LiteralConjunction): for lit in chosen_effect.literals: ground_effects.append( structs.ground_literal(lit, assignments)) else: ground_effects.append( structs.ground_literal(chosen_effect, assignments)) else: ground_effects.append( structs.ground_literal(l, assignments)) return self._execute_effects(state, ground_effects) return state # no change
def _apply_effects(state, lifted_effects, assignments): """ Update a state given lifted operator effects and assignments of variables to objects. Parameters ---------- state : #TODO figure out type The state on which the effects are applied. lifted_effects : { Literal } assignments : { TypedEntity : TypedEntity } Maps variables to objects. """ new_state = {lit for lit in state} for lifted_effect in lifted_effects: effect = ground_literal(lifted_effect, assignments) # Negative effect if effect.is_anti: literal = effect.inverted_anti if literal in new_state: new_state.remove(literal) for lifted_effect in lifted_effects: effect = ground_literal(lifted_effect, assignments) if not effect.is_anti: new_state.add(effect) return new_state
def _update_objects_from_state(self, state): # Check whether the objects have changed # If so, we need to recompute things if state.objects == self._objects: return # Parent class update super()._update_objects_from_state(state) # Recompute all ground operators # Associate each ground action literal with ground preconditions self._ground_action_to_pos_preconds = {} self._ground_action_to_neg_preconds = {} for ground_action in self._all_ground_literals: operator = self._action_predicate_to_operators[ ground_action.predicate] lifted_preconds = operator.preconds.literals subs = dict(zip(operator.params, ground_action.variables)) subs.update(zip(self.domain.constants, self.domain.constants)) preconds = [ground_literal(lit, subs) for lit in lifted_preconds] pos_preconds, neg_preconds = set(), set() for p in preconds: if p.is_negative: neg_preconds.add(p.positive) else: pos_preconds.add(p) self._ground_action_to_pos_preconds[ground_action] = pos_preconds self._ground_action_to_neg_preconds[ground_action] = neg_preconds
def _compute_new_state_from_lifted_effects(lifted_effects, assignments, new_literals): for lifted_effect in lifted_effects: if lifted_effect == NoChange(): continue effect = ground_literal(lifted_effect, assignments) # Negative effect if effect.is_anti: literal = effect.inverted_anti if literal in new_literals: new_literals.remove(literal) for lifted_effect in lifted_effects: if lifted_effect == NoChange(): continue effect = ground_literal(lifted_effect, assignments) if not effect.is_anti: new_literals.add(effect) return new_literals
def ground_literal_multi(lit, multi_sigma): """ """ out = [] vals_for_vars = [multi_sigma[v] for v in lit.variables] for choice in itertools.product(*vals_for_vars): subs = dict(zip(lit.variables, choice)) ground_lit = ground_literal(lit, subs) out.append(ground_lit) return out
def _get_predicted_next_state_ops(self, state, action): """ """ for op in self._learned_operators: assignments = self._preconds_satisfied(state, action, op.preconds.literals) if assignments is not None: ground_effects = [structs.ground_literal(l, assignments) \ for l in op.effects.literals] return self._execute_effects(state, ground_effects) return state # no change
def _get_ground_effects(self, state, action): for op in self._learned_operators: assignments = self._preconds_satisfied(state, action, op.preconds.literals) if assignments is not None: ground_effects = [ structs.ground_literal(l, assignments) for l in op.effects.literals ] return ground_effects return None
def _apply_effects(state, lifted_effects, assignments): """ Update a state given lifted operator effects and assignments of variables to objects. Parameters ---------- state : State The state on which the effects are applied. lifted_effects : { Literal } assignments : { TypedEntity : TypedEntity } Maps variables to objects. """ new_literals = set(state.literals) determinized_lifted_effects = [] # Handle probabilistic effects. for lifted_effect in lifted_effects: if isinstance(lifted_effect, ProbabilisticEffect): chosen_effect = lifted_effect.sample() if chosen_effect == "NOCHANGE": continue if isinstance(chosen_effect, LiteralConjunction): for lit in chosen_effect.literals: determinized_lifted_effects.append(lit) else: determinized_lifted_effects.append(chosen_effect) else: determinized_lifted_effects.append(lifted_effect) for lifted_effect in determinized_lifted_effects: effect = ground_literal(lifted_effect, assignments) # Negative effect if effect.is_anti: literal = effect.inverted_anti if literal in new_literals: new_literals.remove(literal) for lifted_effect in determinized_lifted_effects: effect = ground_literal(lifted_effect, assignments) if not effect.is_anti: new_literals.add(effect) return state.with_literals(new_literals)
def find_unique_matching_effect_index(self, transition): """Find the unique effect index that matches the transition. Note that the noise outcome always holds, but only return it if no other effects hold. Used for quickly learning effect probabilities. """ state, action, effects = transition cache_key = hash((frozenset(state), action, frozenset(effects))) if cache_key not in self._effect_cache: sigma = self.find_substitutions(state, action) try: assert sigma is not None, "Rule assumed to cover transition" except AssertionError: import ipdb ipdb.set_trace() selected_outcome_idx = None noise_outcome_idx = None for i, outcome in enumerate(self.effects): if NOISE_OUTCOME in outcome: assert noise_outcome_idx is None noise_outcome_idx = i else: ground_outcome = { ground_literal(lit, sigma) for lit in outcome } match = False # Check if the ground outcome is equivalent to the effects # before Anti's have been applied if sorted(ground_outcome) == sorted(effects): match = True # Check if the ground outcome is equivalent to the effects # after Anti's have been applied else: for lit in set(ground_outcome): if lit.is_anti and lit.inverted_anti in ground_outcome: ground_outcome.remove(lit) ground_outcome.remove(lit.inverted_anti) if sorted(ground_outcome) == sorted(effects): match = True if match: if selected_outcome_idx is not None: raise MultipleOutcomesPossible() selected_outcome_idx = i if selected_outcome_idx is not None: result = selected_outcome_idx else: assert noise_outcome_idx is not None result = noise_outcome_idx self._effect_cache[cache_key] = result return self._effect_cache[cache_key]
def _get_approx_reachable_set(self): obs = self._get_observation() old_ops = self.domain.operators self.domain.operators = self._delete_relaxed_ops prev_len = 0 while prev_len != len(obs): # do the fixed-point iteration prev_len = len(obs) for action in self.action_space.all_ground_literals(): selected_operator, assignment = self._select_operator(action) if assignment is not None: for lifted_effect in selected_operator.effects.literals: effect = ground_literal(lifted_effect, assignment) assert not effect.is_anti # should be relaxed obs.add(effect) self.domain.operators = old_ops return obs
def get_ground_conds(conds, objs, type_to_parent_types=None, allow_duplicates=True): if type_to_parent_types: type_to_parent_types = dict(type_to_parent_types) vrs = sorted({v for lit in conds for v in lit.variables}) var_types = [v.var_type for v in vrs] for choice in get_object_combinations( objs, len(var_types), var_types=var_types, allow_duplicates=allow_duplicates, type_to_parent_types=type_to_parent_types): assignment = dict(zip(vrs, choice)) ground_conds = {ground_literal(lit, assignment) for lit in conds} yield (assignment, ground_conds)
def parse_plan_step(plan_step, operators, action_predicates, objects, operators_as_actions=False): plan_step_split = plan_step.split() if operators_as_actions: action_predicate = [a for a in action_predicates \ if a.name.lower() == plan_step_split[0].lower()][0] object_names = plan_step_split[1:] args = [] for name in object_names: matches = [o for o in objects if o.name == name] assert len(matches) == 1 args.append(matches[0]) return action_predicate(*args) # Get the operator from its name operator = None for op in operators: if op.name.lower() == plan_step_split[0]: operator = op break assert operator is not None, "Unknown operator '{}'".format( plan_step_split[0]) assert len(plan_step_split) == len(operator.params) + 1 object_names = plan_step_split[1:] args = [] for name in object_names: matches = [o for o in objects if o.name == name] assert len(matches) == 1 args.append(matches[0]) assignments = dict(zip(operator.params, args)) for cond in operator.preconds.literals: if cond.predicate in action_predicates: ground_action = ground_literal(cond, assignments) return ground_action import ipdb ipdb.set_trace() raise Exception("Unrecognized plan step: `{}`".format(str(plan_step)))
def get_transition_likelihood(transition, rule, p_min=P_MIN, ndr_settings=None): """Calculate the likelihood of a transition for a rule that covers it """ try: effect_idx = rule.find_unique_matching_effect_index(transition) prob, outcome = rule.effect_probs[effect_idx], rule.effects[effect_idx] # Non-noise outcome if NOISE_OUTCOME not in outcome: transition_likelihood = prob # Noise outcome else: transition_likelihood = p_min * prob # if transition_likelihood == 0.: # import ipdb; ipdb.set_trace() except MultipleOutcomesPossible: state, action, effects = transition sigma = rule.find_substitutions(state, action) assert sigma is not None, "Rule assumed to cover transition" transition_likelihood = 0. for prob, outcome in zip(rule.effect_probs, rule.effects): if NOISE_OUTCOME in outcome: # c.f. equation 3 in paper transition_likelihood += p_min * prob else: ground_outcome = {ground_literal(lit, sigma) for lit in outcome} # Check if the ground outcome is equivalent to the effects # before Anti's have been applied if sorted(ground_outcome) == sorted(effects): transition_likelihood += prob # Check if the ground outcome is equivalent to the effects # after Anti's have been applied else: for lit in set(ground_outcome): if lit.is_anti and lit.inverted_anti in ground_outcome: ground_outcome.remove(lit) ground_outcome.remove(lit.inverted_anti) if sorted(ground_outcome) == sorted(effects): transition_likelihood += prob return transition_likelihood
def parse_plan_step(plan_step, operators, action_predicates): plan_step_split = plan_step.split() # Get the operator from its name operator = None for op in operators: if op.name.lower() == plan_step_split[0]: operator = op break assert operator is not None, "Unknown operator '{}'".format( plan_step_split[0]) assert len(plan_step_split) == len(operator.params) + 1 args = plan_step_split[1:] assignments = dict(zip(operator.params, args)) for cond in operator.preconds.literals: if cond.predicate in action_predicates: ground_action = ground_literal(cond, assignments) return ground_action import ipdb ipdb.set_trace() raise Exception("Unrecognized plan step: `{}`".format(str(plan_step)))
def _execute_effects(self, lifted_effects, assignments): """ Update the state given lifted operator effects and assignments of variables to objects. Parameters ---------- lifted_effects : { Literal } assignments : { TypedEntity : TypedEntity } Maps variables to objects. """ new_state = {lit for lit in self._state} for lifted_effect in lifted_effects: effect = ground_literal(lifted_effect, assignments) # Negative effect if effect.is_anti: literal = effect.inverted_anti if literal in new_state: new_state.remove(literal) else: new_state.add(effect) self._state = new_state
def _predict(self, state, action, ind): lifted_effects = self._effects[ind] sigma = self.find_substitutions(state, action) return {ground_literal(e, sigma) for e in lifted_effects}