Concepts and Data Types
Symbolic planning is a general term for approaches to automated planning that describe the environment and its dynamics in terms of high-level symbols. PDDL is one way of representing such symbolic knowledge, but there are many related formalisms which the shared concepts of fluents, states, actions, domains, and problems. Here we provide general definitions of these concepts, and also describe the system of data types in PDDL.jl that mirror these concepts. A graphical overview is shown below.
Fluents and Terms
Fluents define (relational) state variables which may (or may not) change over time. A fluent of arity $n$ is a predicate (Boolean-valued) or function (non-Boolean) with $n$ object arguments, which describes some property or relation over those objects. A ground fluent is a fluent defined over particular set of objects (i.e. none of its arguments are free variables). Arguments may optionally be type-restricted.
The fluent (on ?x ?y)
is named on
, has arity 2, and describes whether some object denoted by the variable ?x
is stacked on top of ?y
. The ground fluent (on a b)
denotes that object a
is stacked on top of object b
when true.
The Term
data type is used to represent fluents, but also object constants, variables, logical formulae, effect formulae, and ground actions. Every Term
has a name
property, as well as an args
property, representing the (potentially empty) list of sub-terms it has as arguments. Term
s are inherited from the Julog.jl package for Prolog-style reasoning about first-order logic.
Julog.Term
— TypeTerm
Represents a term or expression in first-order logic, including variables, constants, or compound terms.
There are three subtypes of Term
s:
Const
terms, which are used to represent object constants, and have no arguments.Var
terms are used to represent variables in the context of first-order expressions.Compound
terms are terms with arguments. They can be used to represent fluents, action preconditions or effects, logical expressions, or ground actions.
To construct a Term
using PDDL syntax, the @pddl
macro or pddl"..."
string macro can be used:
julia> pddl"(on a b)" |> dump
Compound
name: Symbol on
args: Array{Term}((2,))
1: Const
name: Symbol a
2: Const
name: Symbol b
Fluent Signatures
In the context of a planning Domain
, (lifted) fluents often have specific type signatures. For example, fluent arguments may be restricted to objects of particular types, and their values may be :boolean
or :numeric
. This type information is stored in the PDDL.Signature
data type:
PDDL.Signature
— TypeSignature
Type signature for a PDDL fluent (i.e. predicate or function).
Fields
name
: Name of fluent.type
: Output datatype of fluent, represented as aSymbol
.args
: Argument variables.argtypes
: Argument datatypes, represented asSymbol
s.
PDDL.arity
— Functionarity(signature)
Returns the arity (i.e. number of arguments) of a fluent signature.
States
In symbolic planning, states are symbolic descriptions of the environment and its objects at a particular point in time. Formally, given a finite set of fluents $\mathcal{F}$, a state $s$ is composed of a set of (optionally typed) objects $\mathcal{O}$, and valuations of ground fluents $\mathcal{F}(\mathcal{O})$ defined over all objects in $\mathcal{O}$ of the appropriate types. Each ground fluent thus refers to a state variable. For a ground fluent $f \in \mathcal{F}(\mathcal{O})$, we will use the notation $s[f] = v$ to denote that $f$ has value $v$ in state $s$.
Given the fluents (on ?x ?y)
and (on-table ?x)
that describe a state $s$ with objects a
and b
, there are six ground fluents whose values are defined in the state: (on a a)
, (on a b)
, (on b a)
, (on b b)
, (on-table a)
and (on-table b)
. The expression $s[$(on a b)
$] =$ true
means that object a
is on top of b
in state $s$.
In PDDL.jl, states are represented by the State
abstract type:
PDDL.State
— TypeState
Abstract supertype for symbolic states. A State
is a symbolic description of the environment and its objects at a particular point in time. It consists of a set of objects, and a set of ground fluents (predicates or functions) with values defined over those objects.
The following accessor methods are defined for a State
:
PDDL.get_objects
— Methodget_objects(state::State, [type::Symbol])
get_objects(domain::Domain, state::State, type::Symbol)
Returns an iterator over objects in the state
. If a type
is specified, the iterator will contain objects only of that type (but not its subtypes). If a domain
is provided, then the iterator will contain all objects of that type or any of its subtypes.
PDDL.get_objtypes
— Methodget_objtypes(state)
Returns a map (dictionary, named tuple, etc.) from state objects to their types.
PDDL.get_objtype
— Methodget_objtype(state, object)
Returns the type of an object
in a state
.
PDDL.get_facts
— Methodget_facts(state)
Returns an iterator over true Boolean predicates in a state
.
PDDL.get_fluent
— Methodget_fluent(state, term)
Gets the value of a (non-derived) fluent.Equivalent to using the index notation state[term]
.
PDDL.set_fluent!
— Methodset_fluent!(state, val, term)
Sets the value of a (non-derived) fluent. Equivalent to using the index notation state[term] = val
.
PDDL.get_fluents
— Methodget_fluents(state)
Returns a map from fluent names to values (false predicates may be omitted). Base.pairs
is an alias.
Actions
As described in the Getting Started, symbolic planning formalisms distinguish between action schemas (also known as operators), which specify the general semantics of an action, and ground actions, which represent instantiations of an action schema for specific objects.
An action schema comprises:
- A name that identifies the action.
- A list of (optionally typed) parameters or arguments that an action operates over.
- A precondition formula, defined over the parameters, that has to hold true for the action to be executable.
- An effect formula, defined over the parameters, specifying how the action modifies the state once it is executed.
An example action schema definition in PDDL is shown below:
(:action stack
:parameters (?x ?y - block)
:precondition (and (holding ?x) (clear ?y) (not (= ?x ?y)))
:effect (and (not (holding ?x)) (not (clear ?y)) (clear ?x) (handempty) (on ?x ?y)))
This schema defines the semantics of an action named stack
and has two parameters of type block
. Its precondition states that block ?x
has to be held, block ?y
has to be clear (no other block is on top of it), and that?x
is not the same as ?y
. Its effect states that in the next state, ?x
will no longer be held, and that it will be instead be placed on top of block ?y
.
In PDDL.jl, action schemas are represented by the Action
abstract type:
PDDL.Action
— TypeAction
Abstract supertype for action schemas, which specify the general semantics of an action parameterized by a set of arguments.
The following accessor methods are defined for an Action
:
PDDL.get_argvars
— Methodget_argvars(action)
Returns the argument variables for an action schema.
PDDL.get_argtypes
— Methodget_argtypes(action)
Returns the argument types for an action schema.
PDDL.get_precond
— Methodget_precond(action::Action)
get_precond(action::Action, args)
get_precond(domain::Domain, action::Term)
Returns the precondition of an action schema as a Term
, optionally parameterized by args
. Alternatively, an action and its arguments can be specified as a Term
.
PDDL.get_effect
— Methodget_effect(action::Action)
get_effect(action::Action, args)
get_effect(domain::Domain, action::Term)
Returns the effect of an action schema as a Term
, optionally parameterized by args
. Alternatively, an action and its arguments can be specified as a Term
.
In contrast to action schemas, ground actions are represented with the Term
data type. This is because the name
property of a Term
is sufficient to identify an action schema in the context of a planning domain, and the args
property can be used to represent action parameters.
There also exists a special no-op action schema, denoted PDDL.NoOp()
in Julia code. The corresponding ground action can be expressed as PDDL.no_op
or pddl"(--)"
.
PDDL.NoOp
— TypeNoOp()
Constructs a no-op action which has no preconditions or effects.
PDDL.no_op
— ConstantPDDL.no_op
An alias for pddl"(–)", the action Term
for a NoOp
action which has no preconditions or effects.
State Differences
For some use cases, such as action grounding or interpreted execution, it can be helpful to more explicitly represent the effects of an action as a difference between State
s. PDDL.jl uses the PDDL.Diff
abstract data type to represent such differences, including PDDL.GenericDiff
s and PDDL.ConditionalDiff
s.
PDDL.Diff
— TypeDiff
Abstract type representing differences between states.
PDDL.GenericDiff
— TypeGenericDiff(add, del, ops)
Generic state difference represented as additions, deletions, and assignments.
Fields
add
: List of addedTerm
sdel
: List of deletedTerm
sops
: Dictionary mapping fluents to their assigned expressions.
PDDL.ConditionalDiff
— TypeConditionalDiff(conds, diffs)
Conditional state difference, represented as paired conditions and sub-diffs.
Fields
conds
: List of list of conditionTerm
s for each sub-diff.diffs
: List of sub-diffs.
Multiple PDDL.Diff
s can be combined using the PDDL.combine!
and PDDL.combine
functions:
PDDL.combine!
— Functioncombine!(diff::Diff, ds::Diff...)
Combine state differences, modifying the first Diff
in place.
PDDL.combine
— Functioncombine(diff::Diff, ds::Diff...)
Combine state differences, returning a fresh Diff
.
Domains
A planning domain is a (first-order) symbolic model of the environment, specifying the predicates and functions that can be used to describe the environment, and the actions that can be taken in the environment, including their preconditions and effects. Some domains may also specify the types of objects that exist, or include domain axioms that specify which predicates can be derived from others.
In PDDL.jl, domains are represented by the Domain
abstract type:
PDDL.Domain
— TypeDomain
Abstract supertype for planning domains, which specify a symbolic model of the environment and its transition dynamics.
The following accessor methods are defined for a Domain
:
PDDL.get_name
— Methodget_name(domain)
Returns the name of a domain.
PDDL.get_typetree
— Methodget_typetree(domain)
Returns a map from domain types to subtypes.
PDDL.get_types
— Methodget_types(domain)
Returns an iterator over types in the domain.
PDDL.get_subtypes
— Methodget_subtypes(domain, type)
Returns an iterator over (immediate) subtypes of type
in the domain.
PDDL.get_predicates
— Methodget_predicates(domain)
Returns a map from predicate names to predicate Signature
s.
PDDL.get_predicate
— Methodget_predicate(domain, name)
Returns the signature associated with a predicate name
.
PDDL.get_functions
— Methodget_functions(domain)
Returns a map from function names to function Signature
s.
PDDL.get_function
— Methodget_function(domain, name)
Returns the signature associated with a function name
.
PDDL.get_fluents
— Methodget_fluents(domain)
Returns a map from domain fluent names to fluent Signature
s.
PDDL.get_fluent
— Methodget_fluent(domain, name)
Returns the signature associated with a fluent name
.
PDDL.get_axioms
— Methodget_axioms(domain)
Returns a map from names of derived predicates to their corresponding axioms.
PDDL.get_axiom
— Methodget_axiom(domain, name)
Returns the axiom assocated with a derived predicate name
.
PDDL.get_actions
— Methodget_actions(domain)
Returns a map from action names to action schemata.
PDDL.get_action
— Methodget_action(domain, name)
Returns the action schema specified by name
.
Problems
A planning problem for a particular domain specifies both the initial state of the environment, and the task specification to be achieved. Typically, the task specification is a goal to be achieved, specified as a logical formula to be satisfied. However, planning problems can also include other specifications, such as a cost metric to minimize, and temporal constraints on the plan or state trajectory.
In PDDL.jl, problems are represented by the Problem
abstract type:
PDDL.Problem
— TypeProblem
Abstract supertype for planning problems, which specify the initial state of a planning domain and the task specification to be achieved.
The following accessor methods are defined for a Problem
:
PDDL.get_name
— Methodget_name(problem)
Returns the name of a problem.
PDDL.get_domain_name
— Methodget_domain_name(problem)
Returns the name of a problem's domain.
PDDL.get_objects
— Methodget_objects(problem)
Returns an iterator over objects in a problem
.
PDDL.get_objtypes
— Methodget_objtypes(problem)
Returns a map from problem objects to their types.
PDDL.get_init_terms
— Methodget_init_terms(problem)
Returns a list of terms that determine the initial state.
PDDL.get_goal
— Methodget_goal(problem)
Returns the goal specification of a problem.
PDDL.get_metric
— Methodget_metric(problem)
Returns the metric specification of a problem.
PDDL.get_constraints
— Methodget_constraints(problem)
Returns the constraint specification of a problem.