Problem Representation
The main functionality offered by the library concerns the specification of a planning problem. In particular, the UP library supports the following classes of planning problems: Classical, Numeric, Temporal, Scheduling, Multi-Agent, Hierarchical, Task and Motion Planning (TAMP) and Conformant. For each of these, we provide a short description, a syntax overview and a link to a detailed discussion.
One of the key element of the problem specifications is the ProblemKind class (automatically computed by all the planning problems classes via the kind property), which is a collection of flags, documented in the table below, that identifies the modeling features used in any problem specification, so that the library can determine the applicability of each engine for a certain query.
Problem Kinds
Features |
ProblemKind flag |
Description |
---|---|---|
PROBLEM_CLASS |
ACTION_BASED |
The problem is an action-based classical, numeric or temporal planning problem. |
HIERARCHICAL |
The problem is an action-based problem with hierarchical features. |
|
CONTINGENT |
The problem is an action-based problem with non-deterministic initial state and observation (called sensing) actions. |
|
ACTION_BASED_MULTI_AGENT |
The problem is an action-based problem where action and fluents can be divided in more than one agent. |
|
SCHEDULING |
The problem is a scheduling problem where a known set of activities need to be scheduled in time. |
|
TAMP |
The problem is a Task and Motion Planning problem. |
|
PROBLEM_TYPE |
SIMPLE_NUMERIC_PLANNING |
The numeric part of the problem exhibits only linear numeric conditions of the form f(X) {>=,>,=} 0 where f(X) is a linear expression constructed over numeric variables X. Moreover, effects are restricted to increase and decrease operations by a constant. For instance x+=k with k constant is allowed, while x+=y with y variable is not. |
GENERAL_NUMERIC_PLANNING |
The problem uses numeric planning using unrestricted arithmetic. |
|
TIME |
CONTINUOUS_TIME |
The temporal planning problem is defined over a continuous time model. |
DISCRETE_TIME |
The temporal planning problem is defined over a discrete time model. |
|
INTERMEDIATE_CONDITIONS_AND_EFFECTS |
The temporal planning problem uses either conditions or effects happening during an action (not just at the beginning or end of the interval). |
|
EXTERNAL_CONDITIONS_AND_EFFECTS |
The temporal planning problem uses either conditions or effects happening outside the interval of an action (e.g. 10 seconds after the end of the action). |
|
TIMED_EFFECTS |
The temporal planning problem has effects scheduled at absolute times (e.g., Timed Initial Literals in PDDL). |
|
TIMED_GOALS |
The temporal planning problem uses goals required to be true at times different from the end of the plan. |
|
DURATION_INEQUALITIES |
The temporal planning problem has at least one action with non-constant duration (and instead uses a lower bound different than upper bound). |
|
SELF_OVERLAPPING |
The temporal planning problem allows actions self overlapping. |
|
EXPRESSION_DURATION |
STATIC_FLUENTS_IN_DURATION |
The duration of at least one action uses static fluents (that may never change). |
FLUENTS_IN_DURATION |
The duration of at least one action is specified using non-static fluents (that might change over the course of a plan). |
|
INT_TYPE_DURATIONS |
The duration of at least one action is of int type; added in ProblemKind’s version 2. |
|
REAL_TYPE_DURATIONS |
The duration of at least one action is of real type; added in ProblemKind’s version 2. |
|
NUMBERS |
CONTINUOUS_NUMBERS |
The problem uses numbers ranging over continuous domains (e.g. reals); deprecated in ProblemKind’s version 2. |
DISCRETE_NUMBERS |
The problem uses numbers ranging over discrete domains (e.g. integers); deprecated in ProblemKind’s version 2. |
|
BOUNDED_TYPES |
The problem uses bounded-domain numbers. |
|
CONDITIONS_KIND |
NEGATIVE_CONDITIONS |
The problem has at least one condition using the negation Boolean operator. |
DISJUNCTIVE_CONDITIONS |
The problem has at least one condition using the Boolean “or” operator. |
|
EQUALITIES |
The problem has at least one condition using the equality predicate (usually over two finite-domain variables or object fluents). |
|
EXISTENTIAL_CONDITIONS |
The problem has at least a condition using the “exists” quantifier over problem objects. |
|
UNIVERSAL_CONDITIONS |
The problem has at least a condition using the “forall” quantifier over problem objects. |
|
EFFECTS_KIND |
CONDITIONAL_EFFECTS |
At least one effect has a condition. |
FORALL_EFFECTS |
At least one effect uses the “forall” quantifier over problem objects. |
|
INCREASE_EFFECTS |
At least one effect uses the numeric increment operator. |
|
DECREASE_EFFECTS |
At least one effect uses the numeric decrement operator. |
|
STATIC_FLUENTS_IN_BOOLEAN_ASSIGNMENTS |
At least one effect uses a static fluent in the expression of a boolean assignment. |
|
STATIC_FLUENTS_IN_NUMERIC_ASSIGNMENTS |
At least one effect uses a static fluent in the expression of a numeric assignment. |
|
STATIC_FLUENTS_IN_OBJECT_ASSIGNMENTS |
At least one effect uses a static fluent in the expression of a object assignment. |
|
FLUENTS_IN_BOOLEAN_ASSIGNMENTS |
At least one effect uses a fluent in the expression of a boolean assignment. |
|
FLUENTS_IN_NUMERIC_ASSIGNMENTS |
At least one effect uses a fluent in the expression of a numeric assignment. |
|
FLUENTS_IN_OBJECT_ASSIGNMENTS |
At least one effect uses a fluent in the expression of a object assignment. |
|
TYPING |
FLAT_TYPING |
The problem uses user-defined types, but no type inherits from another. |
HIERARCHICAL_TYPING |
At least one user-defined type in the problem inherits from another type. |
|
PARAMETERS |
BOOL_FLUENT_PARAMETERS |
At least one fluent has a parameter of boolean type. |
BOUNDED_INT_FLUENT_PARAMETERS |
At least one fluent has a parameter of bounded integer type. Note that unbounded ints are not allowed in fluent parameters). |
|
BOOL_ACTION_PARAMETERS |
At least one action has a parameter of boolean type. |
|
BOUNDED_INT_ACTION_PARAMETERS |
At least one action has a parameter of bounded integer type. |
|
UNBOUNDED_INT_ACTION_PARAMETERS |
At least one action has a parameter of unbounded integer type. |
|
REAL_ACTION_PARAMETERS |
At least one action has a parameter of real type. |
|
FLUENTS_TYPE |
NUMERIC_FLUENTS |
The problem has at least one fluent of numeric type; deprecated in ProblemKind’s version 2. |
INT_FLUENTS |
The problem has at least one fluent of integer type; added in ProblemKind’s version 2. |
|
REAL_FLUENTS |
The problem has at least one fluent of real type; added in ProblemKind’s version 2. |
|
OBJECT_FLUENTS |
The problem has at least one finite-domain fluent (fluent of user-defined type). |
|
QUALITY_METRICS |
ACTIONS_COST |
The problem has a quality metric associating a cost to each action and requiring to minimize the total cost of actions used in a plan. |
FINAL_VALUE |
The problem has a quality metric requiring to optimize the value of a numeric expression in the final state of a plan. |
|
MAKESPAN |
The problem has a quality metric requiring to minimize the time at which the last action in the plan is terminated. |
|
PLAN_LENGTH |
The problem has a quality metric requiring to minimize the number of actions used in a plan. |
|
OVERSUBSCRIPTION |
The problem has a quality metric associating a positive value to some optional goal and the objective is to find the plan of maximal value. |
|
TEMPORAL_OVERSUBSCRIPTION |
The problem has a quality metric associating a positive value to some optional timed goal and the objective is to find the plan of maximal value. |
|
ACTIONS_COST_KIND |
STATIC_FLUENTS_IN_ACTIONS_COST |
There is at least a static fluent in the Action’s cost (that may never change). |
FLUENTS_IN_ACTIONS_COST |
There is at least a non-static fluent in the Action’s cost (that might change over the course of a plan). |
|
INT_NUMBERS_IN_ACTIONS_COST |
There is at least one Action’s cost in the ACTIONS_COST that is of int type; added in ProblemKind’s version 2. |
|
REAL_NUMBERS_IN_ACTIONS_COST |
There is at least one Action’s cost in the ACTIONS_COST that is of real type; added in ProblemKind’s version 2. |
|
OVERSUBSCRIPTION_KIND |
INT_NUMBERS_IN_OVERSUBSCRIPTION |
There is at least one gain in the Oversubscription (or Temporal Oversubscription) metric that is of int type; added in ProblemKind’s version 2. |
REAL_NUMBERS_IN_OVERSUBSCRIPTION |
There is at least one gain in the Oversubscription (or Temporal Oversubscription) metric that is of real type; added in ProblemKind’s version 2. |
|
SIMULATED_ENTITIES |
SIMULATED_EFFECTS |
The problem uses at least one simulated effect. |
CONSTRAINTS_KIND |
TRAJECTORY_CONSTRAINTS |
The problem uses at least one LTL trajectory constraint. |
STATE_INVARIANTS |
The problem uses at least one state invariants. |
|
HIERARCHICAL |
METHOD_PRECONDITIONS |
At least one method of the problem contains preconditions (i.e. statement that must hold at the start of the method. |
TASK_NETWORK_CONSTRAINTS |
At least one task network (initial task network or method) contains a constraint: statement over static functions that is required to hold for the task network to appear in the solution. |
|
INITIAL_TASK_NETWORK_VARIABLES |
The initial task network contains at least one existentially qualified variable. |
|
TASK_ORDER_TOTAL |
In all task networks, all temporal constraints are simple precedence constraints and induce a total order over all subtasks. |
|
TASK_ORDER_PARTIAL |
In all task networks, all temporal constraints are simple precedence constraints. At least one task network is not totally ordered. |
|
TASK_ORDER_TEMPORAL |
Task networks may be subject to arbitrary temporal constraints (e.g. simple temporal constraints or disjunctive temporal constraints). |
|
MULTI_AGENT |
AGENT_SPECIFIC_PRIVATE_GOAL |
At least one agent has at least one private specific goal. Private-specific goals are; individual agent goals (not coalition goals) unknown to other agents. |
AGENT_SPECIFIC_PUBLIC_GOAL |
At least one agent has at least one public-specific goal. Public-specific goals are; individual agent goals (not coalition goals) known to other agents. |
The API provides classes and functions to populate a Problem object with the fluents, actions, initial states and goal specifications constituting the planning problem specification. The functionalities for creating model objects and to manipulate them are collected in the unified_planning.model package of the library. In all the examples below all the shortcuts must be imported, with the command:
from unified_planning.shortcuts import *
Classical and Numeric Planning
Classical and Numeric planning are the most common problems and the building blocks of most other planning problems. At their root, they allow the definition of instantaneous that provide:
preconditions that allow to check whether the action is applicable in a given state, and
effects that allow the computation of the resulting state after the action is executed.
The classical and numeric planning only differ in that classical planning does not allow to referring to numeric variables in the state. The following example shows a simple robotic planning problem modeling a robot moving between locations while consuming battery. The example shows the basic functionalities and objects needed to declare the problem specification. [Classical detailed presentation 🔗] [Numeric detailed presentation 🔗]
# Import all the shortcuts, an handy way of using the unified_planning framework
from unified_planning.shortcuts import *
# Declaring types
Location = UserType("Location")
# Creating problem ‘variables’
robot_at = Fluent("robot_at", BoolType(), location=Location)
battery_charge = Fluent("battery_charge", RealType(0, 100))
# Creating actions
move = InstantaneousAction("move", l_from=Location, l_to=Location)
l_from = move.parameter("l_from")
l_to = move.parameter("l_to")
move.add_precondition(GE(battery_charge, 10))
move.add_precondition(robot_at(l_from))
move.add_precondition(Not(robot_at(l_to)))
move.add_effect(robot_at(l_from), False)
move.add_effect(robot_at(l_to), True)
move.add_effect(battery_charge, Minus(battery_charge, 10))
# Declaring objects
l1 = Object("l1", Location)
l2 = Object("l2", Location)
# Populating the problem with initial state and goals
problem = Problem("robot")
problem.add_fluent(robot_at)
problem.add_fluent(battery_charge)
problem.add_action(move)
problem.add_object(l1)
problem.add_object(l2)
problem.set_initial_value(robot_at(l1), True)
problem.set_initial_value(robot_at(l2), False)
problem.set_initial_value(battery_charge, 100)
problem.add_goal(robot_at(l2))
In the current version, the Unified-Planning library allows the specification of classical, numerical and temporal planning problems. In order to support the latitude expressiveness levels we have operators for arithmetic such as plus minus times and division and specific temporal operators to attach conditions and effects to specific timings within the duration of an action. The library documentation provides examples and describes the use of these functionalities.
Temporal Planning
Temporal planning is the problem of finding a plan for a planning problem involving durative actions and/or temporal constraints. This means that it is possible to model actions having:
duration constrained by a (possibly state-based) lower and and upper bound
conditions or effects expressed at specific instant of the action, in particular at times start+delay or end+delay, where start refers to the starting time of the action, end to the ending time and delay is a real constant (positive or negative).
durative conditions to be maintained within sub-intervals, delimited by the same time-point used for instantaneous conditions.
problem = Problem("MatchCellar")
Match = UserType("Match")
Fuse = UserType("Fuse")
light = problem.add_fluent("light", BoolType(), default_initial_value=False)
# since BoolType is the default, it can be avoided
handfree = problem.add_fluent("handfree", default_initial_value=True)
match_used = problem.add_fluent("match_used", default_initial_value=False, m=Match)
fuse_mended = problem.add_fluent("fuse_mended", default_initial_value=False, f=Fuse)
light_match = DurativeAction("light_match", m=Match)
m = light_match.parameter("m")
light_match.set_fixed_duration(6)
light_match.add_condition(StartTiming(), Not(match_used(m)))
light_match.add_effect(StartTiming(), match_used(m), True)
light_match.add_effect(StartTiming(), light, True)
light_match.add_effect(EndTiming(), light, False)
problem.add_action(light_match)
mend_fuse = DurativeAction("mend_fuse", f=Fuse)
f = mend_fuse.parameter("f")
mend_fuse.set_fixed_duration(5)
mend_fuse.add_condition(StartTiming(), handfree)
mend_fuse.add_condition(ClosedTimeInterval(StartTiming(), EndTiming()), light)
mend_fuse.add_effect(StartTiming(), handfree, False)
mend_fuse.add_effect(EndTiming(), fuse_mended(f), True)
mend_fuse.add_effect(EndTiming(), handfree, True)
problem.add_action(mend_fuse)
obj_number = 3
match_objects = [Object(f"m{i}", Match) for i in range(1, obj_number + 1)]
problem.add_objects(match_objects)
fuse_objects = [Object(f"f{i}", Fuse) for i in range(1, obj_number + 1)]
problem.add_objects(fuse_objects)
for fuse_obj in fuse_objects:
problem.add_goal(fuse_mended(fuse_obj))
Hierarchical Planning
A hierarchical planning problem is a problem formulation which extends the Problem object described so far adding support for tasks, methods and decompositions. The general idea is that the planning problem is augmented with high-level tasks that represent abstract operations (e.g. processing an order, going to some distant location) that may require a combination of actions to be achieved. Each high-level task is associated with one or several methods that describe how the task can be decomposed into a set of lower-level tasks and actions.The presence of several methods for a single task represent alternative possibilities of achieving the same operation, among which the planner shall decide. The most important difference between hierarchical and non-hierarchical planning is that in hierarchical planning all actions of the plan must derive from a set of (partially ordered) high-level objective tasks, called the initial task network. [Detailed presentation 🔗]
from unified_planning.model.htn import HierarchicalProblem, Method
htn = HierarchicalProblem()
Location = UserType("Location")
l1 = htn.add_object("l1", Location)
l2 = htn.add_object("l2", Location)
l3 = htn.add_object("l3", Location)
l4 = htn.add_object("l4", Location)
l5 = htn.add_object("l5", Location)
loc = htn.add_fluent("loc", Location)
connected = Fluent("connected", l1=Location, l2=Location)
htn.add_fluent(connected, default_initial_value=False)
for li, lj in [(l1, l2), (l2, l3), (l3, l4), (l4, l3), (l3, l2), (l2, l1)]:
htn.set_initial_value(connected(li, lj), True)
# define low level action, as in non-hierarchical planning
move = InstantaneousAction("move", l_from=Location, l_to=Location)
move.add_precondition(Equals(loc, move.l_from))
move.add_precondition(connected(move.l_from, move.l_to))
move.add_effect(loc, move.l_to)
htn.add_action(move)
# define high-level task: going to some location
go = htn.add_task("go", target=Location)
# first method: nothing to do if already at target
go_noop = Method("go-noop", target=Location)
go_noop.set_task(go)
target = go_noop.parameter("target")
go_noop.add_precondition(Equals(loc, target))
htn.add_method(go_noop)
# second method (tail recursive): first make an allowed move an action
# and recursively decompose go(target) again
go_recursive = Method("go-recursive", source=Location, inter=Location, target=Location)
go_recursive.set_task(go, go_recursive.parameter("target"))
go_recursive.add_precondition(Equals(loc, go_recursive.source))
go_recursive.add_precondition(connected(go_recursive.source, go_recursive.inter))
t1 = go_recursive.add_subtask(
move, go_recursive.source, go_recursive.inter, ident="move"
)
t2 = go_recursive.add_subtask(go, go_recursive.target, ident="go-rec")
go_recursive.set_ordered(t1, t2)
htn.add_method(go_recursive)
# set objectives tasks in initial task network
go1 = htn.task_network.add_subtask(go, l4, ident="go_l4")
final_loc = htn.task_network.add_variable("final_loc", Location)
go2 = htn.task_network.add_subtask(go, final_loc, ident="go_final")
htn.task_network.add_constraint(Or(Equals(final_loc, l1), Equals(final_loc, l2)))
htn.task_network.set_strictly_before(go1, go2)
htn.set_initial_value(loc, l1)
Multi-Agent Planning
Multi-agent Planning lifts planning to a context where many agents operate in a common environment, each with its own view of the domain. The objective is to produce a set of plans, one for each agent, which allows the agents to achieve their goals. The problem comes in several variants, depending on various features. Specifically, multi-agent planning can be competitive, meaning that the agents compete against each other in order to achieve their goal, or cooperative, which refers to the case where the agents collaborate towards a common goal. Another distinction is based on whether planning is performed in a centralized or distributed manner. In the former case, the planning responsibility is assigned to a single entity, which produces a plan consisting of actions, each to be delegated to some agent, while in the latter, the responsibility is distributed to the participating agents, each of which plans at a local level, by possibly exchanging information with the others; in this variant, each agent comes up with a local plan, and the execution of all plans must be appropriately coordinated at runtime. Finally, the setting may or may not require privacy-preservation, which refers to the requirement that every agent might decide not to disclose some information (consequently affecting the space of admissible solutions). [Detailed presentation 🔗]
from unified_planning.model.multi_agent import MultiAgentProblem, Agent
problem = MultiAgentProblem("robot_migration")
Location = UserType("Location")
is_connected = Fluent("is_connected", BoolType(), l1=Location, l2=Location)
problem.ma_environment.add_fluent(is_connected, default_initial_value=False)
locations_number = 4
locations = [Object(f"l{i}", Location) for i in range(1, locations_number + 1)]
problem.add_objects(locations)
robot_position = Fluent("pos", Location)
move = InstantaneousAction("move", l_from=Location, l_to=Location)
l_from = move.parameter("l_from")
l_to = move.parameter("l_to")
move.add_precondition(Equals(robot_position, l_from))
move.add_precondition(is_connected(l_from, l_to))
move.add_effect(robot_position, l_to)
robots_number = 3
robots = []
for i in range(1, robots_number + 1):
robot = Agent(f"robot{i}", problem)
robots.append(robot)
robot.add_fluent(robot_position)
robot.add_action(move)
problem.add_agent(robot)
last_location = locations[0]
for robot in robots:
problem.set_initial_value(Dot(robot, robot_position), last_location)
for location in locations[1:]:
problem.set_initial_value(is_connected(last_location, location), True)
last_location = location
for robot in robots:
problem.add_goal(Equals(Dot(robot, robot_position), locations[-1]))
Contingent Planning
A contingent planning problem represents an action-based problem in which the exact initial state is not entirely known and some of the actions produce “observations” upon execution. More specifically, some actions can be SensingActions, which indicate which fluents they observe and after the successful execution of such actions, the observed fluents become known to the executor. The inherent non-determinism in the initial state can therefore be “shrinked” by performing suitable SensingActions and a plan is then a strategy that prescribes what to execute based on the past observations.
from unified_planning.model.contingent_problem import ContingentProblem
from unified_planning.model.action import SensingAction
problem = ContingentProblem("lost_packages")
Package = UserType("Package")
Location = UserType("Location")
loader_plate = problem.add_object("loader_plate", Location)
loader_at = problem.add_fluent("loader_at", Location)
is_package_at = problem.add_fluent("is_package_at", location=Location, package=Package)
loader_free = problem.add_fluent("loader_free", default_initial_value=True)
# senses if the package is at the location
sense_package = SensingAction("sense_package", location=Location, package=Package)
sense_package.add_observed_fluent(
is_package_at(sense_package.location, sense_package.package)
)
sense_package.add_precondition(loader_at.Equals(sense_package.location))
move_loader = InstantaneousAction("move_loader", l_from=Location, l_to=Location)
move_loader.add_precondition(loader_at.Equals(move_loader.l_from))
move_loader.add_effect(loader_at, move_loader.l_to)
load = InstantaneousAction("load", package=Package, location=Location)
load.add_precondition(is_package_at(load.location, load.package))
load.add_precondition(loader_at.Equals(load.location))
load.add_precondition(loader_free)
load.add_effect(is_package_at(load.location, load.package), False)
load.add_effect(is_package_at(loader_plate, load.package), True)
load.add_effect(loader_free, False)
unload = InstantaneousAction("unload", package=Package, location=Location)
unload.add_precondition(is_package_at(loader_plate, unload.package))
unload.add_precondition(loader_at.Equals(unload.location))
unload.add_effect(loader_free, True)
unload.add_effect(is_package_at(loader_plate, unload.package), False)
unload.add_effect(is_package_at(unload.location, unload.package), True)
problem.add_actions([sense_package, move_loader, load, unload])
locations_number = 5
locations = [Object(f"l{i}", Location) for i in range(1, locations_number + 1)]
problem.add_objects(locations)
problem.set_initial_value(loader_at, locations[1])
packages_number = 10
packages = [Object(f"p{i}", Package) for i in range(1, packages_number + 1)]
problem.add_objects(packages)
for p in packages:
# The package is in only one of the locations
problem.add_oneof_initial_constraint([is_package_at(l, p) for l in locations])
problem.set_initial_value(is_package_at(loader_plate, p), False)
problem.add_goal(is_package_at(locations[-1], p))
Scheduling
Scheduling is a restricted form of temporal planning where the set of actions (usually called activities) are known in advance and the problem consists in deciding the timing and parameters of the activities. Generally, scheduled problems involve resources and constraints that define the feasible space of solutions. Since scheduling problems are very common and scheduling as a computer science problem belongs to a simpler complexity class (NP) with respect to temporal planning (PSPACE, under suitable assumptions) we created a dedicated representation for scheduling problems. We can represent a generic scheduling problem using the SchedulingProblem class as shown in the example below. [Detailed presentation 🔗]
from unified_planning.model.scheduling import SchedulingProblem
problem = SchedulingProblem("factory")
workers = problem.add_resource("workers", capacity=4)
machine1 = problem.add_resource("machine1", capacity=1)
machine2 = problem.add_resource("machine2", capacity=1)
a1 = problem.add_activity("a1", duration=3)
a1.uses(machine1)
a1.uses(workers, amount=2)
a1.add_deadline(14)
a2 = problem.add_activity("a2", duration=6)
a2.uses(workers)
a2.uses(machine2)
problem.add_constraint(LT(a2.end, a1.start))
# One worker is unavailable over [16, 25)
problem.add_decrease_effect(16, workers, 1)
problem.add_increase_effect(25, workers, 1)
Combined Task and Motion Planning
A combined Task and Motion Planning (TAMP) problem allows adding constraints that require that a valid path exists in a map between a series of waypoints given a model of the motion that an object is capable of. This is often important for cases where we need to make sure a motion required by an action is feasible in the real world.
Concretely, the problem is extended by adding an occupancy map with associated configuration types. These types can then be used to add configurations (position and orientation) to objects to form waypoints in the corresponding map. Movable objects contain a footprint and a motion model with its required parameters. In the example below, the Reeds Shepp car is used as a motion model. An instantaneous motion action allows the addition of motion constraints which state that in order to apply the action, a valid path must exist for the movable object between a series of waypoints. [Detailed presentation 🔗]
t_robot = MovableType("robot")
occ_map = OccupancyMap(
os.path.join("../notebooks", "maps", "office-map-1.yaml"), (0, 0)
)
t_robot_config = ConfigurationType("robot_config", occ_map, 3)
t_parcel = UserType("parcel")
robot_at = Fluent("robot_at", BoolType(), robot=t_robot, configuration=t_robot_config)
parcel_at = Fluent(
"parcel_at", BoolType(), parcel=t_parcel, configuration=t_robot_config
)
carries = Fluent("carries", BoolType(), robot=t_robot, parcel=t_parcel)
park1 = ConfigurationObject("parking-1", t_robot_config, (46.0, 26.0, 3 * math.pi / 2))
park2 = ConfigurationObject("parking-2", t_robot_config, (40.0, 26.0, 3 * math.pi / 2))
office1 = ConfigurationObject("office-1", t_robot_config, (4.0, 4.0, 3 * math.pi / 2))
office2 = ConfigurationObject("office-2", t_robot_config, (14.0, 4.0, math.pi / 2))
office3 = ConfigurationObject("office-3", t_robot_config, (24.0, 4.0, 3 * math.pi / 2))
office4 = ConfigurationObject("office-4", t_robot_config, (32.0, 4.0, 3 * math.pi / 2))
office5 = ConfigurationObject("office-5", t_robot_config, (4.0, 24.0, 3 * math.pi / 2))
office6 = ConfigurationObject("office-6", t_robot_config, (14.0, 24.0, math.pi / 2))
office7 = ConfigurationObject("office-7", t_robot_config, (24.0, 24.0, math.pi / 2))
office8 = ConfigurationObject("office-8", t_robot_config, (32.0, 24.0, math.pi / 2))
r1 = MovableObject(
"robot-1",
t_robot,
footprint=[(-1.0, 0.5), (1.0, 0.5), (1.0, -0.5), (-1.0, -0.5)],
motion_model=MotionModels.REEDSSHEPP,
parameters={"turning_radius": 2.0},
)
r2 = MovableObject(
"robot-2",
t_robot,
footprint=[(-1.0, 0.5), (1.0, 0.5), (1.0, -0.5), (-1.0, -0.5)],
motion_model=MotionModels.REEDSSHEPP,
parameters={"turning_radius": 2.0},
)
nothing = Object("nothing", t_parcel)
p1 = Object("parcel-1", t_parcel)
p2 = Object("parcel-2", t_parcel)
move = InstantaneousMotionAction(
"move", robot=t_robot, c_from=t_robot_config, c_to=t_robot_config
)
robot = move.parameter("robot")
c_from = move.parameter("c_from")
c_to = move.parameter("c_to")
move.add_precondition(robot_at(robot, c_from))
move.add_effect(robot_at(robot, c_from), False)
move.add_effect(robot_at(robot, c_to), True)
move.add_motion_constraint(Waypoints(robot, c_from, [c_to]))
pick = InstantaneousMotionAction(
"pick", robot=t_robot, loc=t_robot_config, parcel=t_parcel
)
pick_robot = pick.parameter("robot")
pick_loc = pick.parameter("loc")
pick_parcel = pick.parameter("parcel")
pick.add_precondition(robot_at(pick_robot, pick_loc))
pick.add_precondition(parcel_at(pick_parcel, pick_loc))
pick.add_precondition(carries(pick_robot, nothing))
pick.add_precondition(Not(carries(pick_robot, pick_parcel)))
pick.add_effect(carries(pick_robot, pick_parcel), True)
pick.add_effect(parcel_at(pick_parcel, pick_loc), False)
pick.add_effect(carries(pick_robot, nothing), False)
place = InstantaneousMotionAction(
"place", robot=t_robot, loc=t_robot_config, parcel=t_parcel
)
place_robot = place.parameter("robot")
place_loc = place.parameter("loc")
place_parcel = place.parameter("parcel")
place.add_precondition(robot_at(place_robot, place_loc))
place.add_precondition(carries(place_robot, place_parcel))
place.add_precondition(Not(parcel_at(place_parcel, place_loc)))
place.add_precondition(Not(carries(place_robot, nothing)))
place.add_effect(carries(place_robot, place_parcel), False)
place.add_effect(carries(place_robot, nothing), True)
place.add_effect(parcel_at(place_parcel, place_loc), True)
problem = Problem("tamp")
problem.add_fluent(robot_at, default_initial_value=False)
problem.add_fluent(parcel_at, default_initial_value=False)
problem.add_fluent(carries, default_initial_value=False)
problem.add_action(move)
problem.add_action(pick)
problem.add_action(place)
problem.add_objects([park1, park2])
problem.add_objects([office1, office2, office3, office4])
problem.add_objects([office5, office6, office7, office8])
problem.add_objects([r1, r2])
problem.add_object(nothing)
problem.add_objects([p1, p2])
problem.set_initial_value(carries(r1, nothing), True)
problem.set_initial_value(carries(r2, nothing), True)
problem.set_initial_value(parcel_at(p1, office1), True)
problem.set_initial_value(parcel_at(p2, office6), True)
problem.set_initial_value(robot_at(r1, park1), True)
problem.add_goal(robot_at(r1, park1))
problem.add_goal(parcel_at(p1, office2))
problem.add_goal(parcel_at(p2, office3))