Source code for up_SMT_engine.actions.BaseAction

from z3 import And, Implies, Bool
from up_SMT_engine.helper_functions.FNODEHelperFunctions import (
    convert_FNODE_to_Z3,
    search_fluents_list,
    create_fluent_condition_tuple,
    convert_effect_to_Z3,
)


[docs]class BaseAction: """Simple action class, used to handle all methods for sequential plans""" def __init__(self, grounded_action): self.base_name = grounded_action.name self.preconditions = grounded_action.preconditions self.effects = grounded_action.effects
[docs] def get_name(self): """Return action basename Returns: String: action basename """ return self.base_name
[docs] def check_name_match(self, other_action_name): """Check if the action basename matches this action's basename Args: other_action_name (String): other action's Basename Returns: Bool: True if matching, False otherwise """ return self.base_name == other_action_name
def __eq__(self, other): """Define two action objects as equal if they share the same grounded name, and are BaseActions or inherit from BaseAction. If two distinct actions happen to share the same grounded name an error has occurred Args: other_action (BaseAction or BaseAction subclass): Other action to compare against Returns: Bool: True if matching, False otherwise """ try: if isinstance(other, BaseAction) or issubclass(other, BaseAction): return self.base_name == other.get_name() return False except: # Something went wrong, most likely an incompatible type return False def __hash__(self): return hash(repr(self))
[docs] def get_action_at_t(self, timestep): """Method used to create an action at timestep t Args: timestep (int): Current timestep Returns: z3 Bool: z3 variable representing whether an action is executed at timstep t """ stated_action_name = self.base_name + "_@t" + str(timestep) return Bool(stated_action_name)
def __get_effects_at_t(self, timestep): """Each effect has three FNODE structures, the condition (optional, makes effect conditional), fluent (the stated variable to be changed) and the value (the new fluent value). These are separately converted into Z3, and returned as Implies(condition, (fluent == value)) These statements are And'd together, as the set of effects of the action. n.b. The fluents in the 'fluent' part of the effect are in the next state (time + 1), because they are the result of the current action, while all other fluents are in the current state Args: timestep (int): Current timestep Returns: List(z3 expression): A list of z3 expressions representing the effects of this action at timestep t """ effects_list = [] for effect in self.effects: effects_list.append(convert_effect_to_Z3(effect, timestep)) return effects_list def __get_causal_axioms_at_t(self, timestep): """The causal axioms assert the effects, given the action occurs Args: timestep (int): Current timestep Returns: z3 expression: A z3 expression representing the causal axioms of this action at timestep t """ effects_list = self.__get_effects_at_t(timestep) axiom = Implies(self.get_action_at_t(timestep), And(effects_list)) return axiom
[docs] def get_causal_axioms_up_to_t(self, timestep): """Generate the list of causal axioms for a problem of length up to timestep t Args: timestep (int): Final timestep Returns: List(z3 expression): A list of z3 expressions representing the causal axioms of this action up to timestep t """ if timestep > 0: causal_axioms = [] # We don't consider actions occurring in the final state (at timestep 'timestep') because their effects cannot influence # the final state for t in range(0, timestep): causal_axioms.append(self.__get_causal_axioms_at_t(t)) return causal_axioms return []
# Returns causal axioms at t without adding to memory. Used for incremental solving.
[docs] def get_causal_axioms_at_t(self, timestep): """Get causal axioms for the final timestep Args: timestep (int): Final timestep Returns: z3 expression: A z3 expression representing the causal axioms of this action at timestep t """ if timestep > 0: # Use timestep - 1 because we don't consider actions in final timestep return self.__get_causal_axioms_at_t(timestep - 1) return []
def __get_precondition_constraints_at_t(self, timestep): """Generate the precondition constraints for this action at timestep t Precondition constraint is: action occuring at timestep t implies preconditions are true at timestep t Args: timestep (int): Current timestep Returns: z3 expression: A z3 expression representing the precondition constraints for this action at timestep t """ # First parses FNODEs for each precondition, converts into a list of Z3 statements, then uses And to join them precondition_constraint_list = [] for precondition in self.preconditions: precondition_constraint_list.append( convert_FNODE_to_Z3(precondition, timestep) ) preconditions = And(precondition_constraint_list) # Uses (action@t implies preconditions@t) to bind actions to preconditions return Implies(self.get_action_at_t(timestep), preconditions)
[docs] def get_precondition_constraints_up_to_t(self, timestep): """Generate the list of precondition constraints for a problem of length up to timestep t Args: timestep (int): Final timestep Returns: List(z3 expression): A list of z3 expressions representing the precondition constraints of this action up to timestep t """ precondition_constraints = [] # We don't consider actions occurring in the final state (at timestep 'timestep') because their effects cannot influence # the final state for t in range(0, timestep): precondition_constraints.append(self.__get_precondition_constraints_at_t(t)) return precondition_constraints
# Returns precondition constraints at t without adding to memory. Used for incremental solving.
[docs] def get_precondition_constraints_at_t(self, timestep): """Get precondition constraints for the final timestep Args: timestep (int): Final timestep Returns: z3 expression: A z3 expression representing the precondition constraints of this action at timestep t """ if timestep > 0: # We don't consider actions occurring in the final state (at timestep 'timestep') because their effects cannot influence # the final state return self.__get_precondition_constraints_at_t(timestep - 1) return True
[docs] def get_effected_fluent_basename_condition_tuples(self): """Method used to get a list of all effected fluents, and the conditions for the effect This is part of the process for sharing the information 'which actions affect which fluents' with fluent objects Returns: Tuple(String, FNODE): A tuple of a fluent basename, and a FNODE expressing the condition for that fluent being affected """ tuple_list = [] for effect in self.effects: tuple_list.append(create_fluent_condition_tuple(effect)) return tuple_list
[docs] def deliver_effect_tuples_to_Fluents(self, fluents_list): """Method used to deliver effect-fluent tupes for frame-axioms to the appropriate Fluent object. Should only be called once Args: fluents_list (List(BaseFluent or BaseFluent subclass)): List of fluents Raises: Exception: Raise an exception if an unknown fluent is affected by an action """ tuple_list = self.get_effected_fluent_basename_condition_tuples() for effect_tuple in tuple_list: matched_fluent = search_fluents_list(fluents_list, effect_tuple[0]) if not (matched_fluent is None): matched_fluent.add_action_condition(self, effect_tuple[1]) else: print(effect_tuple[0]) raise Exception( "Fluent name not recognised, has the fluent list been initialised?" )