Source code for up_SMT_engine.helper_functions.SmtModelHelperFunctions
# Regex used for finding action base names
import re
# unified_planning Used for converting the SAT model into a plan
import unified_planning as up
from z3 import BoolRef, FuncDeclRef
[docs]def get_z3_FuncDecl_name(obj):
"""Function used to get the string name of a variable in the Model result
Args:
obj: object to check
Returns:
String: The object's name
"""
if type(obj) == FuncDeclRef:
return obj.name()
else:
return None
[docs]def split_stated_action_name(action_name):
"""Splits action name from 'action_@tx' to ['action', 'x'], giving the action basename, and time of execution
Args:
action_name (String): An action basename with time of execution
Returns:
String: The action basename
"""
return re.split("_@t", action_name)
[docs]def get_action_by_name(name, grounded_actions):
"""Function used to find corresponding action to string action name in grounded_actions, if one exists
Args:
name (String): Action name
grounded_actions (List of InstantaneousAction objects): List of grounded actions
Returns:
InstantaneousAction: The grounded InstantaneousAction corresponding to the name
"""
for action in grounded_actions:
if name == action.name:
return action
return None
[docs]def get_goal_actions_list(solution, grounded_actions, plan_len, parallelism):
"""Using the full z3 solution model compare each True boolean variable against the name of a grounded_action
If the names match then the variable shows that action is executed at time x, given the variable name is 'action_@tx'
Iterate over each variable in the model, setting the corresponding ground action to index x in the plan array
Return the plan array
'solution' parameter should be a Solver.model() object
Args:
solution (z3.Model): The model satisfying the problem as SMT
grounded_actions (List of InstantaneousAction objects): List of grounded actions
plan_len (int): Plan length
parallelism (String): The type of parallelism chosen
Returns:
List or List of sets: If sequential this returns a list of actions in the order the appear in the plan. Otherwise this returns a list of sets, each set corresponding to the parallel actions taken at that timestep
"""
# Get TRUE booleans from solution, these will include the actions we want
true_values = []
for val in solution:
if type(solution[val]) == BoolRef and solution[val]:
true_values.append(val)
# if the plan is parallel we need a list of sets of actions, otherwise just a list
if parallelism == "sequential":
# Plan list, consisting of actions from grounded_actions where index corresponds to execution order
plan = [None] * plan_len
else:
plan = [set() for _ in range(plan_len)]
for val in true_values:
val_name = get_z3_FuncDecl_name(val)
split_name = split_stated_action_name(val_name)
ground_name = split_name[0]
state = split_name[1]
matched_action = get_action_by_name(ground_name, grounded_actions)
if matched_action is not None:
if parallelism == "sequential":
plan[int(state)] = up.plans.ActionInstance(matched_action)
else:
plan[int(state)].add(up.plans.ActionInstance(matched_action))
return plan
[docs]def find_action_in_ordered_list(action_instance, ordered_list):
"""Custom index finding function, for finding the action of an action_instance in an ordered list
Uses the action's name to find the action
Args:
action_instance (ActionInstance): a unified-planning Action instance
ordered_list (List): ordered list of actions
Returns:
int: action index
"""
name = action_instance.action.name
for action in ordered_list:
if action.check_name_match(name):
return ordered_list.index(action)
print("Error. Action not found.")
return -1
[docs]def convert_action_sequence_to_plan(
actions, parallelism, ForAll_get_sets, ordered_list
):
"""Function used to convert a list or list of sets to a corresponding unified-planning Plan object.
Args:
actions (list or list of sets): list or list of sets of InstantaneousActions
parallelism (String): Choice of parallelism
ForAll_get_sets (Boolean): True if the user wants to maintain the sets of parallel actions
ordered_list (list): Ordered list of actions
Returns:
PartialOrderPlan or SequentialPlan: If ForAll_get_sets is true we return a PartialOrderPlan. Otherwise we return a SequentialPlan
"""
if parallelism == "sequential":
# The candidate plan, initially empty
plan = up.plans.SequentialPlan([])
for action in actions:
plan.actions.append(action)
return plan
elif parallelism == "ForAll" and ForAll_get_sets:
# Represent parallel plans as a partial order plan, this plan is initialised after full construction
# of the plan as a dictionary
plan_dict = {}
plan_len = len(actions)
for i in range(0, plan_len):
action_set = actions[i]
for action in action_set:
affected_list = []
if i + 1 < plan_len:
other_action_sets = actions[i + 1]
for other_actions in other_action_sets:
affected_list.append(other_actions)
plan_dict[action] = affected_list
return up.plans.PartialOrderPlan(plan_dict)
elif (
parallelism == "ForAll"
or parallelism == "ThereExists"
or parallelism == "relaxed_relaxed_ThereExists"
):
# Convert parallel action sets into sequential plan, using total ordering assigned
# We need to iterate over each set, turning them into ordered sub-plans
# For each set. Iterate while action has members. Iterate over each member, remembering
plan = up.plans.SequentialPlan([])
for action_set in actions:
# Iterate over every item in action set
for _ in range(0, len(action_set)):
earliest_action = None
earliest_action_index = -1
for action in action_set:
if earliest_action is None:
earliest_action = action
earliest_action_index = find_action_in_ordered_list(
earliest_action, ordered_list
)
else:
current_action_index = find_action_in_ordered_list(
action, ordered_list
)
if earliest_action_index > current_action_index:
earliest_action = action
earliest_action_index = find_action_in_ordered_list(
earliest_action, ordered_list
)
plan.actions.append(earliest_action)
action_set.remove(earliest_action)
return plan
else:
print("Error. Parallelism type not currently handled: ")
print(parallelism)