Source code for up_SMT_engine.actions.ThereExistsAction
from up_SMT_engine.helper_functions.ParallelPlanningHelperFunctions import (
search_FNODE_for_Fluents,
)
from z3 import And, Not
from up_SMT_engine.helper_functions.FNODEHelperFunctions import (
convert_FNODE_to_Z3,
search_fluents_list,
create_stated_action_instance,
)
from up_SMT_engine.actions.BaseAction import BaseAction
[docs]class ThereExistsAction(BaseAction):
"""Extension of BaseAction to support ThereExists parallelism, allowing more parallelism than ForAll"""
def __init__(self, grounded_action):
super().__init__(grounded_action)
# For a coarse grained version of parallelism, where shared fluents imply affectation
# An action has variables in the preconditions, effect preconditions, and effected variables
# Affectation occurs if another action affects any of these
# This set is the set of all fluents related to this action
self.all_fluents = set()
# ThereExists parallelism implementation uses an arbitrary ordering over actions, then requires that earlier
# actions do not affect later actions in the same parallel step.
# For this we need a set of affecting fluents, which we can compare against a later action's all_fluents set
self.affecting_fluents = set()
def __get_matching_fluent_set(self, fluents_list, name_set):
"""Method used to find the set of fluents matching a set of basenames
Args:
fluents_list (List(BaseFluent or BaseFluent subclass)): List of fluents
name_set (Set(String)): Set of basenames
Returns:
Set(BaseFluent or BaseFluent subclass): Set of fluents matching the basenames
"""
fluent_set = set()
for basename in name_set:
matched_fluent = search_fluents_list(fluents_list, basename)
if matched_fluent is not None:
fluent_set.add(matched_fluent)
return fluent_set
def __get_precondition_fluent_names(self):
"""Method used to find all fluents mentioned in preconditions. This is a coarse grained approach because
each fluent's semantics are discarded
Used with __get_matching_fluent_set to get matching fluent objects
Returns:
Set(String): Set of fluent basenames
"""
# It's useful to use sets because we don't care about repeated occurences of the same fluent
# We use syntactic based parallelism, so any semantic information is redundant anyway
fluent_basename_set = set()
for precondition in self.preconditions:
search_FNODE_for_Fluents(precondition, fluent_basename_set)
return fluent_basename_set
def __get_effect_precondition_fluent_names(self):
"""Method used to find all fluents mentioned in effect preconditions. This is a coarse grained approach because
each fluent's semantics are discarded.
Effects without preconditions are skipped.
Used with __get_matching_fluent_set to get matching fluent objects
Returns:
Set(String): Set of fluent basenames
"""
fluent_basename_set = set()
for effect in self.effects:
if effect.is_conditional():
effect_precondition = effect.condition
search_FNODE_for_Fluents(effect_precondition, fluent_basename_set)
return fluent_basename_set
def __get_effect_fluent_names(self):
"""Method used to find all fluents affected by an effect, including conditional effects
This is also a coarse grained approach.
Used with __get_matching_fluent_set to get matching fluent objects
Returns:
Set(String): Set of fluent basenames
"""
fluent_basename_set = set()
for effect in self.effects:
search_FNODE_for_Fluents(effect.fluent, fluent_basename_set)
return fluent_basename_set
[docs] def populate_affecting_fluents_set(self, fluents_list):
"""Method used to populate the affecting_fluents set for an action
Args:
fluents_list (List(BaseFluent or BaseFluent subclass)): List of all fluents
"""
# Add fluents mentioned in effects:
effect_fluent_names = self.__get_effect_fluent_names()
# Create set of all fluents mentioned
fluents = self.__get_matching_fluent_set(fluents_list, effect_fluent_names)
self.affecting_fluents = self.all_fluents.union(fluents)
[docs] def populate_all_fluents_set(self, fluents_list):
"""Method used to populate the all_fluents set for an action
Args:
fluents_list (List(BaseFluent or BaseFluent subclass)): List of all fluents
"""
fluent_names = set()
# Add fluents mentioned in preconditions:
precondition_fluent_names = self.__get_precondition_fluent_names()
# Add fluents mentioned in effect preconditions:
effect_precondition_fluent_names = self.__get_effect_precondition_fluent_names()
# Effects set is already populated, skip searching for fluents again and just copy the set.
self.all_fluents = self.all_fluents.union(self.affecting_fluents)
# Create set of all fluent names mentioned
fluent_names = fluent_names.union(precondition_fluent_names)
fluent_names = fluent_names.union(effect_precondition_fluent_names)
# Create set of all fluents mentioned
fluents = self.__get_matching_fluent_set(fluents_list, fluent_names)
self.all_fluents = self.all_fluents.union(fluents)
def __get_ThereExists_constraints_at_time_t(self, timestep, ordered_actions):
"""Method used to generate all parallelism constraints for this action for a ThereExists encoding
Affecting actions are found by finding affecting actions of fluents in all_fluents set
Args:
timestep (int): Current timestep
ordered_actions (List(BaseAction or BaseAction subclass)): Ordered list of all actions
Returns:
List(z3 expression): List of constraints expressing the ThereExists parallelism constraints over simultaneous actions
"""
constraints = []
later_action_bool = self.get_action_at_t(timestep)
for fluent in self.all_fluents:
for action_tuple in fluent.get_action_conditions():
# index == i means it's the current action, which should be ignored
# index > i means we ignore this, because later actions are allowed to affect current ones
# effects are kept consistent because if a later effect alters an earlier effect, then
# the earlier effect alters the later one, and so they cannot execute together
if ordered_actions.index(action_tuple[0]) < ordered_actions.index(self):
earlier_action_bool = create_stated_action_instance(
action_tuple[0].get_name(), timestep
)
condition_as_Z3 = convert_FNODE_to_Z3(
action_tuple[1], timestep, None
)
constraints.append(
Not(
And(later_action_bool, earlier_action_bool, condition_as_Z3)
)
)
return constraints
[docs] def get_ThereExists_constraints_up_to_t(self, timestep, ordered_actions):
"""Method used to get all ThereExists parallelism constraints over simultaneous actions up to timestep t
Args:
timestep (int): Final timestep
ordered_actions (List(BaseAction or BaseAction subclass)): Ordered list of all actions
Returns:
List(z3 expression): List of constraints expressing the ThereExists parallelism constraints over simultaneous actions
"""
ThereExists_constraints = []
# We don't consider actions occuring at the final timestep
for t in range(0, timestep):
ThereExists_constraints.append(
self.__get_ThereExists_constraints_at_time_t(t, ordered_actions)
)
return ThereExists_constraints
[docs] def get_ThereExists_constraints_at_t(self, timestep, ordered_actions):
"""Method used to get all ThereExists parallelism constraints over simultaneous actions at timestep t
Args:
timestep (int): Current timestep
ordered_actions (List(BaseAction or BaseAction subclass)): Ordered list of all actions
Returns:
List(z3 expression): List of constraints expressing the ThereExists parallelism constraints over simultaneous actions
"""
# We add new actions at timestep = timestep - 1
return self.__get_ThereExists_constraints_at_time_t(
timestep - 1, ordered_actions
)