Source code for up_SMT_engine.fluents.BaseFluent

from z3 import And, Or, Implies, Real, Int, Bool
from up_SMT_engine.helper_functions.FNODEHelperFunctions import (
    convert_FNODE_to_Z3,
    create_stated_action_instance,
)


[docs]class BaseFluent: """Custom fluent object used to generate Frame Axiom constraints, value bound constraints and to handle variables and chained variables for Fluents. Able to handle all methods for sequential, and therefore most parallel plans """ def __init__(self, name, API_fluent): self.base_name = name API_type = API_fluent.type if API_type.is_real_type(): self.lower_bound = API_type.lower_bound self.upper_bound = API_type.upper_bound self.fluent_type = "Real" elif API_type.is_int_type(): self.lower_bound = API_type.lower_bound self.upper_bound = API_type.upper_bound self.fluent_type = "Int" else: self.lower_bound = None self.upper_bound = None self.fluent_type = "Bool" # Used for creating frame axiom constraints self.action_condition_pairs = set()
[docs] def check_name_match(self, name): """Return true/false if name matches this fluent's base name Args: name (String): name to match Returns: Bool: True if match, False otherwise """ return self.base_name == name
# Override equality method for use in sets def __eq__(self, other): """Return true/false if this fluent is the same fluent as the other fluent Args: other (BaseFluent): Other BaseFluent to compare with Returns: Bool: True if match, False otherwise """ try: if isinstance(other, BaseFluent) or issubclass(other, BaseFluent): return self.check_name_match(other.base_name) return False except: return False def __hash__(self): return hash(repr(self)) def __get_predicate_at_t(self, timestep): """Method used to generate this fluent's Bool representing its state at t = 'timestep' Args: timestep (int): Timestep value for the variable Returns: z3.Real or z3.Int or z3.Bool: Variable expression of value of Fluent at timestep t """ predicate_name = self.base_name + "_@t" + str(timestep) if self.fluent_type == "Real": predicate = Real(predicate_name) elif self.fluent_type == "Int": predicate = Int(predicate_name) else: predicate = Bool(predicate_name) return predicate
[docs] def get_fluents_up_to_t(self, timestep): """Method for finding the list of state predicates corresponding to this fluent's variables in each state from t = 0, to t = timestep Args: timestep (int): Last timestep Returns: List: List of variables expressing this fluent's values from timestep 0 to timestep """ fluent_array = [] for t in range(0, timestep + 1): fluent_array.append(self.__get_predicate_at_t(t)) return fluent_array
[docs] def add_action_condition(self, action, condition): """Used by actions to register as effecting this fluent Args: action (BaseAction or BaseAction subclass): Action which may affect this fluent when executed condition (FNODE): FNODE expression for action's effect condition affecting this fluent """ action_condition_tuple = (action, condition) self.action_condition_pairs.add(action_condition_tuple)
[docs] def get_action_conditions(self): """Return set of actions that may affect this fluent, with their conditions Returns: List: List of (BaseAction or BaseAction subclass, FNODE) pairs. The Action is an action which affects this fluent as part of its effects. The FNODE is the condition of the effect, if it has one """ return self.action_condition_pairs
def __get_bound_constraints_at_t(self, timestep, is_lower): """Method used to generate bound constraints for either upper, or lower bound at timestep t Args: timestep (int): Timestep value bound (Numeric type): upper or lower bound value is_lower (bool): True if is lower, False if is upper bound Returns: _type_: _description_ """ pred_at_t = self.__get_predicate_at_t(timestep) bound = self.lower_bound if (is_lower) else self.upper_bound if is_lower: return pred_at_t >= bound else: return pred_at_t <= bound
[docs] def get_bound_constraints_up_to_t(self, timestep): """Method used to generate all bound constraints up to timestep t, can be called if no constraints are needed Args: timestep (int): Last timestep value Returns: List: List of bound constraints up to t """ if self.lower_bound or self.upper_bound is not None: bound_constraints = [] if self.lower_bound is not None: for t in range(0, timestep + 1): bound_constraints.append(self.__get_bound_constraints_at_t(t, True)) if self.upper_bound is not None: for t in range(0, timestep + 1): bound_constraints.append( self.__get_bound_constraints_at_t(t, False) ) return bound_constraints return None
[docs] def get_bound_constraints_at_t(self, timestep): """Returns bound constraints at t. Used for incremental solving. Args: timestep (int): current timestep Returns: z3 expression: z3 expression for bound constraints at timestep t """ if self.lower_bound or self.upper_bound is not None: bound_constraints = [] if self.lower_bound is not None: bound_constraints.append( self.__get_bound_constraints_at_t(timestep, True) ) if self.upper_bound is not None: bound_constraints.append( self.__get_bound_constraints_at_t(timestep, False) ) return And(bound_constraints) return True
def __generate_frame_axiom_constraints_at_t(self, timestep): """Method used internally to generate the frame axiom constraints for this fluent at time timestep Args: timestep (int): current timestep Returns: z3 expression: z3 expression for Frame axiom constraints at timestep t """ current_state = self.__get_predicate_at_t(timestep) future_state = self.__get_predicate_at_t(timestep + 1) # No actions effect this fluent, so assert no changes of value between states if len(self.action_condition_pairs) == 0: # return Implies((current_state != future_state), False) # Same meaning, but shorter return current_state == future_state constraints = [] # Actions exist that effect this fluent, assert a change of value implies the action (and effect condition) for action_tuple in self.action_condition_pairs: action_bool = create_stated_action_instance( action_tuple[0].get_name(), timestep ) constraint_as_Z3 = convert_FNODE_to_Z3(action_tuple[1], timestep) constraints.append(And(constraint_as_Z3, action_bool)) # Essentially: change in value implies at least one (action, precondition) tuple is true return Implies((current_state != future_state), Or(constraints))
[docs] def generate_frame_axiom_constraints_up_to_t(self, timestep): """Generate frame axiom constraints for each timestep point up to t Args: timestep (int): final timestep Returns: List(z3 expression): List of z3 expressions for frame axiom constraints up to timestep t """ constraints_list = [] # Only need to handle frame axiom constraints for states 1 ... (n - 1), where n is final state # Because we don't care what happens in state n + 1 for t in range(0, timestep): axiom = self.__generate_frame_axiom_constraints_at_t(t) constraints_list.append(axiom) return constraints_list
[docs] def generate_frame_axiom_constraints_at_t(self, timestep): """Returns frame axioms at t. Used for incremental solving. Args: timestep (int): current timestep Returns: z3 expression: z3 expressions for frame axiom constraints at timestep t """ # No frame axiom constraints necessary when there is only one timestep if timestep == 0: return True # Only need to handle frame axiom constraints for states 1 ... (n - 1), where n is final state # Because we don't care what happens in state n + 1 return self.__generate_frame_axiom_constraints_at_t(timestep - 1)