Interface Functions
PDDL.jl defines a set of interface functions that serve as basic operations in a wide variety of symbolic planning algorithms and applications. These functions are intended to be low-level enough such that planning algorithms can be expressed primarily in terms of the operations they represent, but high-level enough so as to abstract away from implementational details. A schematic overview of most of these interface functions is shown below.
Evaluating Formulae and Expressions
The key distinguishing feature of symbolic planning is the ability to describe and determine whether certain facts about the world hold true (e.g. is the robot holding a block?), or evaluate numeric properties (e.g. the distance between two cities), with queries expressed in terms of first-order logic. As such, PDDL.jl provides the following functions which satisfy or evaluate first-order expressions in the context of a State
:
Formula Satisfaction
Given a term representing a well-formed logical formula, or a collection of terms
(treated as conjunctions of such formulae), the satisfy
function returns whether they are satisfiable within a domain and state:
PDDL.satisfy
— Functionsatisfy(domain::Domain, state::State, term::Term)
satisfy(domain::Domain, state::State, terms::AbstractVector{<:Term})
Returns whether the queried term
or terms
can be satisfied in the given domain
and state
.
When a term has free variables, satisfy
returns true as long as one satisfying assignment exists. A related function, satisfiers
, returns a list of all satisfying assignments to such variables (a.k.a. substitutions), including the empty list when a variable-free formula is satisfied. If no satisfying assignments exist, nothing
is returned:
PDDL.satisfiers
— Functionsatisfiers(domain::Domain, state::State, term::Term)
satisfiers(domain::Domain, state::State, terms::AbstractVector{<:Term})
Returns a list of satisfying substitutions of the queried term
or terms
within the given domain
and state
.
Term Evaluation
Given a term representing a ground expression (i.e. one with no free variables), the evaluate
function returns the value of that expression in the context of a domain and state:
PDDL.evaluate
— Functionevaluate(domain::Domain, state::State, term::Term)
Evaluates a grounded term
in the given domain
and state
. If term
refers to a numeric fluent, the value of the fluent is returned. For logical predicates, evaluate
is equivalent to satisfy
.
For example, if term
refers to a fluent, the value of the fluent is returned. Compound numeric expressions (e.g., the sum of two fluents) can also be evaluated.
State Initialization and Transition
A PDDL domain specifies the transition dynamics of a first order symbolic model of the world, while a PDDL problem specifies the initial state and object set over which these dynamics are grounded. PDDL.jl thus provides functions for constructing an initial state for a domain and problem, and for simulating the transition dynamics:
State Initialization
Given a domain and problem, the initstate
function returns the initial state, the type of which is concrete subtype of State
:
PDDL.initstate
— Functioninitstate(domain::Domain, problem::Problem)
initstate(domain::Domain, objtypes[, fluents])
Construct the initial state for a given planning domain
and problem
, or from a domain
, a map of objects to their types (objtypes
), and an optional list of fluents
.
Fluents can either be provided as a list of Term
s representing the initial fluents in a PDDL problem, or as a map from fluent names to fluent values.
The type of the returned state may vary depending on the type of the domain or problem provided. For example, providing a compiled domain as an argument leads initstate
to return a compiled state representation.
State Transition
Given a domain, state and action, the transition
function returns a successor state, including the effects of events and processes (as supported by PDDL+) and random sampling (in the case of probabilistic PDDL). To support future multi-agent extensions of PDDL.jl, transition
may also accept a set of actions
to be executed in parallel:
PDDL.transition
— Functiontransition(domain::Domain, state::State, action::Term)
transition(domain::Domain, state::State, actions)
Returns the successor to state
in the given domain
after applying a single action
or a set of actions
in parallel.
PDDL.transition!
— Functiontransition!(domain::Domain, state::State, action::Term)
transition!(domain::Domain, state::State, actions)
Variant of transition
that modifies state
in place.
Forward Action Semantics
A widely-used strategy in symbolic planning is forward state space search, guided by a planning heuristic. These algorithms are built upon two basic operations to search forward in state space: querying the actions that are available in any given state, and executing an action to generate a successor state. These operations can be performed using the following functions:
Action Availability
Given a domain, state, action schema and action arguments, the available
function returns whether the corresponding action is available in the specified state – i.e. its precondition is fulfilled. An action may alternatively be provided as a Term
(e.g. pddl"(stack a b)"
):
PDDL.available
— Methodavailable(domain::Domain, state::State, action::Action, args)
available(domain::Domain, state::State, action::Term)
Check if an action
parameterized by args
can be executed in the given state
and domain
. Action parameters can also be specified as the arguments of a compound Term
.
When available
is called without specifying an action, it returns an iterator over all actions available in the specified state, effectively encapsulating the logic for node expansion in a search algorithm:
PDDL.available
— Methodavailable(domain::Domain, state::State)
Return an iterator over available actions in a given state
and domain
.
Action Execution
Given a domain, state, action schema and action arguments, the execute
function returns the result of applying the specified action to the state. An action may also be provided as a Term
:
PDDL.execute
— Functionexecute(domain::Domain, state::State, action::Action, args)
execute(domain::Domain, state::State, action::Term)
Execute an action
parameterized by args
in the given state
, returning the resulting state. Action parameters can also be specified as the arguments of a compound Term
.
PDDL.execute!
— Functionexecute!(domain::Domain, state::State, action::Action, args)
execute!(domain::Domain, state::State, action::Term)
Variant of execute
that modifies state
in-place.
Inverse Semantics
Regression-based planners (e.g. the classical STRIPS algorithm) make use of the fact that is possible to plan by working backwards from a goal, repeatedly selecting actions that are relevant to achieving a goal state or specification. This motivates the following interface methods for (i) constructing abstract states from goal specifications and (ii) exposing the inverse semantics of actions:
Goal State Construction
In symbolic planning, a logical goal formula $g$ effectively specifies the set of all concrete goal states where $g$ holds true. We can represent this set of concrete states as an abstract state $\bar s$. In the special case where the goal $g$ contains no disjunctions or functions, $\bar s$ can also be understood as a partial state that specifies the values of all predicates in $g$, and leaves all other predicates unspecified.
To support regression search in this abstract space, PDDL.jl provides the goalstate
method for constructing an abstract state from the goal specification of a problem:
PDDL.goalstate
— Functiongoalstate(domain::Domain, problem::Problem)
goalstate(domain::Domain, objtypes, terms)
Construct a (partial) goal state from a domain
and problem
, or from a domain
, a map of objects to their types (objtypes
), and goal terms
.
As with initstate
, the data type of the returned state $\bar s$ may depend on the type of domain or problem provided.
Action Relevance
Given a domain, state, action schema and action arguments, the relevant
function returns whether the action is relevant to achieving the specified state – i.e., it achieves at least one predicate or numeric constraint in the state, and destroys none through deletion or modification. In the case where the action's effect reduces to a list of predicates to be added and a list to be deleted, this simplifies to checking that at least one added predicate is true in the state, and that none are deleted. An action may also be provided as a Term
:
PDDL.relevant
— Methodrelevant(domain::Domain, state::State, action::Action, args)
relevant(domain::Domain, state::State, action::Term)
Check if an action
parameterized by args
is relevant (can lead to) a state
in the given domain
. Action parameters can also be specified as the arguments of a compound Term
.
When relevant
is called without specifying an action, it returns an iterator over all actions relevant to the specified state, encapsulating the logic for node expansion in a regression search algorithm:
PDDL.relevant
— Methodrelevant(domain::Domain, state::State)
Return an iterator over relevant actions in a given state
and domain
.
Action Regression
Given a domain, state, action schema and action arguments, the regress
function executes the action in reverse, returning a (potentially abstract) state that represents the pre-image of the action with respect to the input state. An action may also be provided as a Term
:
PDDL.regress
— Functionregress(domain::Domain, state::State, action::Action, args)
regress(domain::Domain, state::State, action::Term)
Compute the pre-image of an action
parameterized by args
with respect to a state
. Action parameters can also be specified as the arguments of a compound Term
.
PDDL.regress!
— Functionregress!(domain::Domain, state::State, action::Action, args)
regress!(domain::Domain, state::State, action::Term)
Variant of regress
that modifies state
in-place.