def translate_strips_operators(actions, strips_to_sas, ranges, mutex_dict, mutex_ranges, implied_facts): # FOND Step 1: Group actions by name. Different actions with the same name # are in fact just different non-deterministic versions of the # same action with equal parameters and conditions. actions_by_name = partition(actions, (lambda a: a.name)) result = [] for action_list in actions_by_name: sas_ops = translate_strips_operator(action_list, strips_to_sas, ranges, mutex_dict, mutex_ranges, implied_facts) if len(sas_ops) > 0: # FOND Step 2: Translate groups of actions with the same name # and precondition together. operatorname = sas_ops[0].name assert all(op.name == operatorname for op in sas_ops) operatorcost = sas_ops[0].cost assert all(op.cost == operatorcost for op in sas_ops) observation = sas_ops[0].observation assert all(op.observation == observation for op in sas_ops) #preconditions = sas_ops[0].get_preconditions() ops_by_name = partition(sas_ops, (lambda a: repr(a.get_preconditions()))) for op_list in ops_by_name: prevail = set() pre_post = [] for op in op_list: prevail |= set(op.prevail) pre_post.append(sorted(op.pre_post)) result.append(sas_tasks.SASOperator(operatorname, prevail, pre_post, operatorcost, observation)) return result
def translate_operator(self, op): """Compute a new operator from op where the var/value renaming has been applied. Return None if op should be pruned (because it is always inapplicable or has no effect.)""" # We do not call this apply_to_operator, breaking the analogy # with the other methods, because it creates a new operator # rather than transforming in-place. The reason for this is # that it would be quite difficult to generate the operator # in-place. # This method is trickier than it may at first appear. For # example, pre_post values should be fully sorted (see # documentation in the sas_tasks module), and pruning effect # conditions from a conditional effects can break this sort # order. Recreating the operator from scratch solves this # because the pre_post entries are sorted by # SASOperator.__init__. # Also, when we detect a pre_post pair where the effect part # can never trigger, the precondition part is still important, # but may be demoted to a prevail condition. Whether or not # this happens depends on the presence of other pre_post # entries for the same variable. We solve this by computing # the sorting into prevail vs. preconditions from scratch, too. applicability_conditions = op.get_applicability_conditions() try: self.convert_pairs(applicability_conditions) except Impossible: # The operator is never applicable. return None conditions_dict = dict(applicability_conditions) new_prevail_vars = set(conditions_dict) new_pre_post = [] new_assign_effect = [] for entry in op.pre_post: new_entry = self.translate_pre_post(entry, conditions_dict) if new_entry is not None: new_pre_post.append(new_entry) # Mark the variable in the entry as not prevailed. new_var = new_entry[0] new_prevail_vars.discard(new_var) for entry in op.assign_effects: new_entry = self.translate_assign_effect(entry) if new_entry is not None: new_assign_effect.append(new_entry) if not new_pre_post and not new_assign_effect: # The operator has no effect. return None new_prevail = sorted((var, value) for (var, value) in conditions_dict.items() if var in new_prevail_vars) return sas_tasks.SASOperator(name=op.name, prevail=new_prevail, pre_post=new_pre_post, assign_effect=new_assign_effect, cost=op.cost)
def build_sas_operator(name, condition, effects_by_variable, cost, ranges, implied_facts): if False: #options.add_implied_preconditions: implied_precondition = set() for fact in condition.items(): implied_precondition.update(implied_facts[fact]) prevail_and_pre = dict(condition) pre_post = [] for var, effects_on_var in effects_by_variable.items(): orig_pre = condition.get(var, -1) added_effect = False for post, eff_conditions in effects_on_var.items(): pre = orig_pre # if the effect does not change the variable value, we ignore it if pre == post: continue eff_condition_lists = [ sorted(eff_cond.items()) for eff_cond in eff_conditions ] if ranges[var] == 2: # Apply simplifications for binary variables. if prune_stupid_effect_conditions(var, post, eff_condition_lists, effects_on_var): global simplified_effect_condition_counter simplified_effect_condition_counter += 1 # if (options.add_implied_preconditions and pre == -1 and # (var, 1 - post) in implied_precondition): if False: global added_implied_precondition_counter added_implied_precondition_counter += 1 pre = 1 - post for eff_condition in eff_condition_lists: # we do not need to represent a precondition as effect condition # and we do not want to keep an effect whose condition contradicts # a pre- or prevail condition filtered_eff_condition = [] eff_condition_contradicts_precondition = False for variable, value in eff_condition: if variable in prevail_and_pre: if prevail_and_pre[variable] != value: eff_condition_contradicts_precondition = True break else: filtered_eff_condition.append((variable, value)) if eff_condition_contradicts_precondition: continue pre_post.append((var, pre, post, filtered_eff_condition)) added_effect = True if added_effect: # the condition on var is not a prevail condition but a # precondition, so we remove it from the prevail condition condition.pop(var, -1) if not pre_post: # operator is noop return None prevail = list(condition.items()) return sas_tasks.SASOperator(name, prevail, pre_post, cost)
def build_sas_operator(name, condition, effects_by_variable, cost, ranges, implied_facts): if ADD_IMPLIED_PRECONDITIONS: implied_precondition = set() for fact in condition.items(): implied_precondition.update(implied_facts[fact]) pre_post = [] for var in effects_by_variable: orig_pre = condition.get(var, -1) none_of_those = ranges[var] - 1 has_delete_effect = none_of_those in effects_by_variable[var] for post, eff_conditions in effects_by_variable[var].items(): pre = orig_pre # if the effect does not change the variable value, we ignore it # unless there is a delete effect present, in which case the add # effect can overwrite the delete effect and it is not safe to # ignore it. if pre == post and not has_delete_effect: continue # otherwise the condition on var is not a prevail condition but a # precondition, so we remove it from the prevail condition condition.pop(var, -1) eff_condition_lists = [ sorted(eff_cond.items()) for eff_cond in eff_conditions ] if ranges[var] == 2: # Apply simplifications for binary variables. if prune_stupid_effect_conditions(var, post, eff_condition_lists): global simplified_effect_condition_counter simplified_effect_condition_counter += 1 if (ADD_IMPLIED_PRECONDITIONS and pre == -1 and (var, 1 - post) in implied_precondition): global added_implied_precondition_counter added_implied_precondition_counter += 1 pre = 1 - post for eff_condition in eff_condition_lists: # we do not need to represent a precondition as effect condition if (var, pre) in eff_condition: eff_condition.remove((var, pre)) eff_condition.sort() pre_post.append((var, pre, post, eff_condition)) if not pre_post: # operator is noop return None prevail = list(condition.items()) # We sort effects on a variable in decreasing order of their post # condition so that none-of-those effects (corresponding to delete # effects) are executed first and possibly overwritten by other # effects (add-after-delete semantics). pre_post = sorted(pre_post, key=lambda (var, pre, val, cond): (var, -val, cond)) return sas_tasks.SASOperator(name, prevail, pre_post, cost)
def translate_strips_operator(operator, dictionary, mod_effects_dict, ranges, comp_axioms): # NOTE: This function does not really deal with the intricacies of properly # encoding delete effects for grouped propositions in the presence of # conditional effects. It should work ok but will bail out in more # complicated cases even though a conflict does not necessarily exist. assert False, "Actions not supported, use durative actions" condition = translate_strips_conditions(operator.condition, dictionary, ranges, comp_axioms) if condition is None: return None effect, possible_add_conflict, mod_eff = translate_add_effects( operator.add_effects, dictionary, mod_effects_dict, ranges, comp_axioms, False) translate_del_effects(operator.del_effects, dictionary, ranges, effect, condition, comp_axioms, False, None) if possible_add_conflict: print operator.name assert not possible_add_conflict, "Conflicting add effects?" assign_effect, possible_assign_conflict = \ translate_assignment_effects(operator.assign_effects, dictionary, ranges, comp_axioms, False) if possible_assign_conflict: print operator.name assert not possible_assign_conflict, "Conflicting assign effects?" pre_post = [] for var in effect: for (post, eff_condition_lists) in effect[var].iteritems(): pre = condition.get(var, -1) if pre != -1: del condition[var] for eff_condition in eff_condition_lists: pre_post.append((var, pre, post, eff_condition)) prevail = condition.items() assign_effects = [] for var in assign_effect: for ((op, valvar), eff_condition_lists) in assign_effect[var].iteritems(): for eff_condition in eff_condition_lists: sas_effect = sas_tasks.SASAssignmentEffect( var, op, valvar, eff_condition) assign_effects.append(sas_effect) return sas_tasks.SASOperator(operator.name, prevail, pre_post, assign_effects)
def build_sas_operator(name, condition, effects_by_variable, cost, ranges, implied_facts, observation): if ADD_IMPLIED_PRECONDITIONS: implied_precondition = set() for fact in condition.items(): implied_precondition.update(implied_facts[fact]) pre_post = [] for var in effects_by_variable: orig_pre = condition.get(var, -1) for post, eff_conditions in effects_by_variable[var].items(): pre = orig_pre # if the effect does not change the variable value, we ignore it if pre == post: continue # otherwise the condition on var is not a prevail condition but a # precondition, so we remove it from the prevail condition condition.pop(var, -1) eff_condition_lists = [ sorted(eff_cond.items()) for eff_cond in eff_conditions ] if ranges[var] == 2: # Apply simplifications for binary variables. if prune_stupid_effect_conditions(var, post, eff_condition_lists): global simplified_effect_condition_counter simplified_effect_condition_counter += 1 if (ADD_IMPLIED_PRECONDITIONS and pre == -1 and (var, 1 - post) in implied_precondition): global added_implied_precondition_counter added_implied_precondition_counter += 1 pre = 1 - post for eff_condition in eff_condition_lists: # we do not need to represent a precondition as effect condition if (var, pre) in eff_condition: eff_condition.remove((var, pre)) pre_post.append((var, pre, post, eff_condition)) # FOND: Noops as nondeterminstic effects are allowed! #if not pre_post: # operator is noop #return None prevail = list(condition.items()) return sas_tasks.SASOperator(name, prevail, pre_post, cost, observation)
def translate_strips_operator(operator, dictionary, ranges): # NOTE: This function does not really deal with the intricacies of properly # encoding delete effects for grouped propositions in the presence of # conditional effects. It should work ok but will bail out in more # complicated cases even though a conflict does not necessarily exist. possible_add_conflict = False condition = translate_strips_conditions(operator.precondition, dictionary, ranges) if condition is None: return None effect = {} for conditions, fact in operator.add_effects: eff_condition = translate_strips_conditions(conditions, dictionary, ranges) if eff_condition is None: # Impossible condition for this effect. continue eff_condition = eff_condition.items() for var, val in dictionary[fact]: effect_pair = effect.get(var) if not effect_pair: effect[var] = (val, [eff_condition]) else: other_val, eff_conditions = effect_pair # Don't flag conflict just yet... the operator might be invalid # because of conflicting add/delete effects (see pipesworld). if other_val != val: possible_add_conflict = True eff_conditions.append(eff_condition) for conditions, fact in operator.del_effects: eff_condition_dict = translate_strips_conditions( conditions, dictionary, ranges) if eff_condition_dict is None: continue eff_condition = eff_condition_dict.items() for var, val in dictionary[fact]: none_of_those = ranges[var] - 1 other_val, eff_conditions = effect.setdefault( var, (none_of_those, [])) if other_val != none_of_those: # Look for matching add effect; ignore this del effect if found. assert eff_condition in eff_conditions or [] in eff_conditions, \ "Add effect with uncertain del effect partner?" if other_val == val: if ALLOW_CONFLICTING_EFFECTS: # Conflicting ADD and DEL effect. This is *only* allowed if # this is also a precondition, in which case there is *no* # effect (the ADD takes precedence). We delete the add effect here. if condition.get(var) != val: # HACK HACK HACK! # There used to be an assertion here that actually # forbid this, but this was wrong in Pipesworld-notankage # (e.g. task 01). The thing is, it *is* possible for # an operator with proven (with the given invariants) # inconsistent preconditions to actually end up here if # the inconsistency of the preconditions is not obvious at # the SAS+ encoding level. # # Example: Pipes-Notank-01, operator # (pop-unitarypipe s12 b4 a1 a2 b4 lco lco). # This has precondition last(b4, s12) and on(b4, a2) which # is inconsistent because b4 can only be in one place. # However, the chosen encoding encodes *what is last in s12* # separately, and so the precondition translates to # "last(s12) = b4 and on(b4) = a2", which does not look # inconsistent at first glance. # # Something reasonable to do here would be to make a # decent check that the precondition is indeed inconsistent # (using *all* mutexes), but that seems tough with this # convoluted code, so we just warn and reject the operator. print "Warning: %s rejected. Cross your fingers." % ( operator.name) return None assert False assert eff_conditions == [[]] del effect[var] else: assert not eff_condition and not eff_conditions[ 0], "Uncertain conflict" return None # Definite conflict otherwise. else: if condition.get(var) != val and eff_condition_dict.get( var) != val: if var in condition: ## HACK HACK HACK! There is a precondition on the variable for ## this delete effect on another value, so there is no need to ## represent the delete effect. Right? Right??? continue # Need a guard for this delete effect. assert var not in condition and var not in eff_condition, "Oops?" eff_condition.append((var, val)) eff_conditions.append(eff_condition) if possible_add_conflict: print operator.name assert not possible_add_conflict, "Conflicting add effects?" # assert eff_condition != other_condition, "Duplicate effect" # assert eff_condition and other_condition, "Dominated conditional effect" pre_post = [] for var, (post, eff_condition_lists) in effect.iteritems(): pre = condition.get(var, -1) if pre != -1: del condition[var] for eff_condition in eff_condition_lists: pre_post.append((var, pre, post, eff_condition)) prevail = condition.items() return sas_tasks.SASOperator(operator.name, prevail, pre_post, operator.cost)
def translate_strips_operator_aux(operator, dictionary, ranges, mutex_dict, mutex_ranges, implied_facts, condition): # NOTE: This function does not really deal with the intricacies of properly # encoding delete effects for grouped propositions in the presence of # conditional effects. It should work ok but will bail out in more # complicated cases even though a conflict does not necessarily exist. possible_add_conflict = False effect = {} for conditions, fact in operator.add_effects: eff_condition_list = translate_strips_conditions( conditions, dictionary, ranges, mutex_dict, mutex_ranges) if eff_condition_list is None: # Impossible condition for this effect. continue eff_condition = [ list(eff_cond.items()) for eff_cond in eff_condition_list ] for var, val in dictionary[fact]: if condition.get(var) == val: # Effect implied by precondition. global removed_implied_effect_counter removed_implied_effect_counter += 1 # print "Skipping effect of %s..." % operator.name continue effect_pair = effect.get(var) if not effect_pair: effect[var] = (val, eff_condition) else: other_val, eff_conditions = effect_pair # Don't flag conflict just yet... the operator might be invalid # because of conflicting add/delete effects (see pipesworld). if other_val != val: possible_add_conflict = True eff_conditions.extend(eff_condition) for conditions, fact in operator.del_effects: eff_condition_list = translate_strips_conditions( conditions, dictionary, ranges, mutex_dict, mutex_ranges) if eff_condition_list is None: continue eff_condition = [ list(eff_cond.items()) for eff_cond in eff_condition_list ] for var, val in dictionary[fact]: none_of_those = ranges[var] - 1 other_val, eff_conditions = effect.setdefault( var, (none_of_those, [])) if other_val != none_of_those: # Look for matching add effect; ignore this del effect if found. for cond in eff_condition: if cond not in eff_conditions and [] not in eff_conditions: print("Condition:") print(cond) print("Operator:") operator.dump() assert False, "Add effect with uncertain del effect partner?" if other_val == val: if ALLOW_CONFLICTING_EFFECTS: # Conflicting ADD and DEL effect. This is *only* allowed if # this is also a precondition, in which case there is *no* # effect (the ADD takes precedence). We delete the add effect here. if condition.get(var) != val: # HACK HACK HACK! # There used to be an assertion here that actually # forbid this, but this was wrong in Pipesworld-notankage # (e.g. task 01). The thing is, it *is* possible for # an operator with proven (with the given invariants) # inconsistent preconditions to actually end up here if # the inconsistency of the preconditions is not obvious at # the SAS+ encoding level. # # Example: Pipes-Notank-01, operator # (pop-unitarypipe s12 b4 a1 a2 b4 lco lco). # This has precondition last(b4, s12) and on(b4, a2) which # is inconsistent because b4 can only be in one place. # However, the chosen encoding encodes *what is last in s12* # separately, and so the precondition translates to # "last(s12) = b4 and on(b4) = a2", which does not look # inconsistent at first glance. # # Something reasonable to do here would be to make a # decent check that the precondition is indeed inconsistent # (using *all* mutexes), but that seems tough with this # convoluted code, so we just warn and reject the operator. print("Warning: %s rejected. Cross your fingers." % (operator.name)) if DEBUG: operator.dump() return None assert False assert eff_conditions == [[]] del effect[var] else: assert not eff_condition[0] and not eff_conditions[ 0], "Uncertain conflict" return None # Definite conflict otherwise. else: # no add effect on this variable if condition.get(var) != val: if var in condition: ## HACK HACK HACK! There is a precondition on the variable for ## this delete effect on another value, so there is no need to ## represent the delete effect. Right? Right??? del effect[var] continue for index, cond in enumerate(eff_condition_list): if cond.get(var) != val: # Need a guard for this delete effect. assert (var not in condition and var not in eff_condition[index]), "Oops?" eff_condition[index].append((var, val)) eff_conditions.extend(eff_condition) if possible_add_conflict: operator.dump() assert not possible_add_conflict, "Conflicting add effects?" # assert eff_condition != other_condition, "Duplicate effect" # assert eff_condition and other_condition, "Dominated conditional effect" if ADD_IMPLIED_PRECONDITIONS: implied_precondition = set() for fact in condition.items(): implied_precondition.update(implied_facts[fact]) pre_post = [] for var, (post, eff_condition_lists) in effect.items(): pre = condition.pop(var, -1) if ranges[var] == 2: # Apply simplifications for binary variables. if prune_stupid_effect_conditions(var, post, eff_condition_lists): global simplified_effect_condition_counter simplified_effect_condition_counter += 1 if (ADD_IMPLIED_PRECONDITIONS and pre == -1 and (var, 1 - post) in implied_precondition): global added_implied_precondition_counter added_implied_precondition_counter += 1 pre = 1 - post # print "Added precondition (%d = %d) to %s" % ( # var, pre, operator.name) for eff_condition in eff_condition_lists: pre_post.append((var, pre, post, eff_condition)) prevail = list(condition.items()) return sas_tasks.SASOperator(operator.name, prevail, pre_post, operator.cost)
def op_from_comm(d): op = sas_tasks.SASOperator(d[0], d[1], d[2], d[3]) op.owner = d[4] op.global_id = d[5] return op
def build_sas_operator(name, condition, effects_by_variable, cost, ranges, implied_facts): if options.add_implied_preconditions: implied_precondition = set() for fact in condition.items(): implied_precondition.update(implied_facts[fact]) prevail_and_pre = dict(condition) pre_post = [] for var, effects_on_var in effects_by_variable.items(): orig_pre = condition.get(var, -1) none_of_those = ranges[var] - 1 has_delete_effect = none_of_those in effects_by_variable[var] added_effect = False for post, eff_conditions in effects_on_var.items(): pre = orig_pre # If the effect does not change the variable value, we ignore it if # we are enforcing definite effects or there is no delete effect # present. Under add-after-delete semantics for conditional effects # add effects can overwrite delete effects and it is not safe to # ignore add effects. if pre == post and (options.enforce_definite_effects or not has_delete_effect): continue eff_condition_lists = [ sorted(eff_cond.items()) for eff_cond in eff_conditions ] if ranges[var] == 2: # Apply simplifications for binary variables. if prune_stupid_effect_conditions(var, post, eff_condition_lists, effects_on_var): global simplified_effect_condition_counter simplified_effect_condition_counter += 1 if (options.add_implied_preconditions and pre == -1 and (var, 1 - post) in implied_precondition): global added_implied_precondition_counter added_implied_precondition_counter += 1 pre = 1 - post for eff_condition in eff_condition_lists: # we do not need to represent a precondition as effect condition # and we do not want to keep an effect whose condition contradicts # a pre- or prevail condition filtered_eff_condition = [] eff_condition_contradicts_precondition = False for variable, value in eff_condition: if variable in prevail_and_pre: if prevail_and_pre[variable] != value: eff_condition_contradicts_precondition = True break else: filtered_eff_condition.append((variable, value)) if eff_condition_contradicts_precondition: continue filtered_eff_condition.sort() # TODO: Is it already sorted? pre_post.append((var, pre, post, filtered_eff_condition)) added_effect = True if added_effect: # the condition on var is not a prevail condition but a # precondition, so we remove it from the prevail condition condition.pop(var, -1) if not pre_post: # operator is noop return None prevail = list(condition.items()) return sas_tasks.SASOperator(name, prevail, pre_post, cost)
def build_sas_operator(name, condition, effects_by_variable, ass_effects_by_variable, deprecated_cost, ranges, implied_facts, relevant_numeric_variables): # print("build_sas_operator with relevant vars:", relevant_numeric_variables) # if DEBUG: print("Building SAS Operator %s with %d logic and %d numeric effects" % (name, len(effects_by_variable),len(ass_effects_by_variable))) if options.add_implied_preconditions: implied_precondition = set() for fact in condition.items(): implied_precondition.update(implied_facts[fact]) prevail_and_pre = dict(condition) pre_post = [] num_pre_post = [] for var in effects_by_variable: orig_pre = condition.get(var, -1) added_effect = False for post, eff_conditions in effects_by_variable[var].items(): pre = orig_pre # if the effect does not change the variable value, we ignore it if pre == post: continue eff_condition_lists = [ sorted(eff_cond.items()) for eff_cond in eff_conditions ] if ranges[var] == 2: # Apply simplifications for binary variables. if prune_stupid_effect_conditions(var, post, eff_condition_lists): global simplified_effect_condition_counter simplified_effect_condition_counter += 1 if (options.add_implied_preconditions and pre == -1 and (var, 1 - post) in implied_precondition): global added_implied_precondition_counter added_implied_precondition_counter += 1 pre = 1 - post for eff_condition in eff_condition_lists: # we do not need to represent a precondition as effect condition # and we do not want to keep an effect whose condition contradicts # a pre- or prevail condition filtered_eff_condition = [] eff_condition_contradicts_precondition = False for variable, value in eff_condition: if variable in prevail_and_pre: if prevail_and_pre[variable] != value: eff_condition_contradicts_precondition = True break else: filtered_eff_condition.append((variable, value)) if eff_condition_contradicts_precondition: continue pre_post.append((var, pre, post, filtered_eff_condition)) added_effect = True if added_effect: # the condition on var is not a prevail condition but a # precondition, so we remove it from the prevail condition condition.pop(var, -1) for numvar in ass_effects_by_variable: # if DEBUG: print("Numeric effect on numvar = %s" % numvar) # orig_pre = condition.get(numvar, -1) # assert orig_pre == -1 # numeric variables cannot occur in preconditions (instead propositional variables are derived by axioms) # numeric variables and logic variables are not stored in the same data structures, but they *do* can have the same index within their # respective arrays for (ass_op, post_var ), eff_conditions in ass_effects_by_variable[numvar].items(): # if DEBUG: print("assignment operator : >%s<" % ass_op) # if DEBUG: print("post condition variable : %d" % post_var) # otherwise the condition on numvar is not a prevail condition but a # precondition, so we remove it from the prevail condition eff_condition_lists = [ sorted(eff_cond.items()) for eff_cond in eff_conditions ] for eff_condition in eff_condition_lists: # we do not need to represent a precondition as effect condition num_pre_post.append((numvar, ass_op, post_var, eff_condition)) # if DEBUG: print("pre_post = %s " % pre_post) # if DEBUG: print("num_pre_post = %s " % num_pre_post) if not pre_post: # build only operators with at least one regular effect (not instrumentation) irrelevant = True for effect in num_pre_post: # print ("numeric effect ", effect) if effect[0] in relevant_numeric_variables: # print("RELEVANT! because ", effect[0], " is in ", relevant_numeric_variables) irrelevant = False break if irrelevant: # if DEBUG: print("operator is noop - return None") return None prevail = list(condition.items()) return sas_tasks.SASOperator(name, prevail, pre_post, num_pre_post, deprecated_cost)