Extension Interfaces
PDDL.jl provides a set of extension interfaces for adding new global predicates, functions, and types. Extensions can be added on a per-predicate or per-function basis, or by registering theories that provide a class of related functionality.
Built-in Types, Predicates and Functions
PDDL.jl stores mappings from (global) names to their implementations by making use of value-based dispatch. These mappings are stored by defining methods for the following functions:
PDDL.datatype_def
— Functiondatatype_def(name)
Mapping from PDDL data types to Julia types and default values.
PDDL.predicate_def
— Functionpredicate_def(name)
Mapping from PDDL built-in predicates to Julia functions.
PDDL.function_def
— Functionfunction_def(name)
Mapping from PDDL built-in functions to Julia functions.
PDDL.modifier_def
— Functionmodifier_def(name)
Mapping from PDDL modifiers (i.e. in-place assignments) to PDDL functions.
These mappings can also be accessed in dictionary form with the following utility functions:
PDDL.global_datatypes
— Methodglobal_datatypes()
Return dictionary mapping global datatype names to implementations.
PDDL.global_predicates
— Methodglobal_predicates()
Return dictionary mapping global predicate names to implementations.
PDDL.global_functions
— Methodglobal_functions()
Return dictionary mapping global function names to implementations.
PDDL.global_modifiers
— Methodglobal_modifiers()
Return dictionary mapping global modifier names to implementations.
Datatypes
By default, PDDL.jl supports fluents with Boolean and numeric values. These correspond to the PDDL datatypes named boolean
, integer
, number
and numeric
, and are implemented in Julia with the Bool
, Int
and Float64
types:
PDDL.datatype_def(::Val{:boolean}) = (type=Bool, default=false)
PDDL.datatype_def(::Val{:integer}) = (type=Int, default=0)
PDDL.datatype_def(::Val{:number}) = (type=Float64, default=1.0)
PDDL.datatype_def(::Val{:numeric}) = (type=Float64, default=1.0)
When declaring a function in a PDDL domain, it is possible to denote its (output) type as one of the aforementioned types. For example, the distance
between two cities might be declared to have a number
type:
(distance ?c1 - city ?c2 - city) - number
Predicates and Functions
PDDL.jl also supports built-in predicates and functions for comparisons and arithmetic operations. Since these functions can be used in any PDDL domain, they are called global functions. Global predicates and functions are implemented by mapping them to Julia functions:
# Built-in predicates
PDDL.predicate_def(::Val{:(==)}) = PDDL.equiv
PDDL.predicate_def(::Val{:<=}) = <=
PDDL.predicate_def(::Val{:>=}) = >=
PDDL.predicate_def(::Val{:<}) = <
PDDL.predicate_def(::Val{:>}) = >
# Built-in functions
PDDL.function_def(::Val{:+}) = +
PDDL.function_def(::Val{:-}) = -
PDDL.function_def(::Val{:*}) = *
PDDL.function_def(::Val{:/}) = /
Modifiers
Finally, PDDL.jl supports modifier expressions such as (increase fluent val)
, which modifies the current value of fluent
by val
, setting the new value of fluent
to the modified value
. Like global functions, modifiers are implemented by mapping their names to corresponding Julia functions:
PDDL.modifier_def(::Val{:increase}) = :+
PDDL.modifier_def(::Val{:decrease}) = :-
PDDL.modifier_def(::Val{Symbol("scale-up")}) = :*
PDDL.modifier_def(::Val{Symbol("scale-down")}) = :/
Adding Types, Predicates and Functions
To add a new global datatype, predicate, function, or modifier to PDDL, it is enough to define a new method of PDDL.datatype_def
, PDDL.predicate_def
, PDDL.function_def
, or PDDL.modifier_def
respectively. Alternatively, one can use the @register
macro to register new implementations at compile-time:
PDDL.@register
— Macro@register([:datatype|:predicate|:function|:modifier|:converter], name, x)
Register x
as a global datatype, predicate, function, modifier, or term converter under the specified name
.
In scripting contexts, run-time registration and de-registration can be achieved using PDDL.register!
and PDDL.deregister!
:
PDDL.register!
— Functionregister!([:datatype|:predicate|:function|:modifier|:converter], name, x)
Register x
as a global datatype, predicate, function or modifier, or term converter under the specified name
.
Because register!
defines new methods, it should only be called at the top-level in order to avoid world-age errors.
Because register!
evaluates code in the PDDL
module, it will lead to precompilation errors when used in another module. For this reason, the @register
macro is preferred, and this function should only be used in scripting contexts.
PDDL.deregister!
— Functionderegister!([:datatype|:predicate|:function|:modifier|:converter], name)
Deregister the datatype, predicate, function or modifier specified by name
.
Because deregister!
deletes existing methods, it should only be called at the top-level in order to avoid world-age errors. It should primarily be used in scripting contexts, and not by other packages or modules.
Defining and Registering Theories
Similar to Satisfiability Modulo Theories (SMT) solvers, PDDL.jl provides support for planning modulo theories. By registering a new theory, developers can extend the semantics of PDDL to handle new mathematical objects such as sets, arrays, and tuples.
A new theory can be implemented by writing a (sub)module annotated with the @pddltheory
macro:
PDDL.@pddltheory
— Macro@pddltheory module M ... end
Declares a module M
as a PDDL theory. This defines the M.@register
, M.register!
, M.deregister!
and M.attach!
functions automatically.
For example, a theory for how to handle sets of PDDL objects can be written as follows (adapting the example by Gregory et al (2012)):
@pddltheory module Sets
using PDDL
using PDDL: SetAbs
construct_set(xs::Symbol...) = Set{Symbol}(xs)
empty_set() = Set{Symbol}()
cardinality(s::Set) = length(s)
member(s::Set, x) = in(x, s)
subset(x::Set, y::Set) = issubset(x, y)
union(x::Set, y::Set) = Base.union(x, y)
intersect(x::Set, y::Set) = Base.intersect(x, y)
difference(x::Set, y::Set) = setdiff(x, y)
add_element(s::Set, x) = push!(copy(s), x)
rem_element(s::Set, x) = pop!(copy(s), x)
set_to_term(s::Set) = isempty(s) ? Const(Symbol("(empty-set)")) :
Compound(Symbol("construct-set"), PDDL.val_to_term.(collect(s)))
const DATATYPES = Dict(
"set" => (type=Set{Symbol}, default=Set{Symbol}())
)
const ABSTRACTIONS = Dict(
"set" => SetAbs{Set{Symbol}}
)
const CONVERTERS = Dict(
"set" => set_to_term
)
const PREDICATES = Dict(
"member" => member,
"subset" => subset
)
const FUNCTIONS = Dict(
"construct-set" => construct_set,
"empty-set" => empty_set,
"cardinality" => cardinality,
"union" => union,
"intersect" => intersect,
"difference" => difference,
"add-element" => add_element,
"rem-element" => rem_element
)
end
This theory introduces a new PDDL type called set
, implemented as the Julia datatype Set{Symbol}
. Sets can be modified with functions such as union
or add-element
, and can also serve as arguments to predicates like subset
. The default abstraction for a set is specified to be a SetAbs
, which means that the abstract interpreter will use a set of sets to represent the abstract value of a set-valued variable.
After defining a new theory, we can register it by calling the @register
macro for that module, and make use of the new functionality in PDDL domains and problems:
Sets.@register()
domain = pddl"""
(define (domain storytellers)
(:requirements :typing :fluents)
(:types storyteller audience story)
(:functions (known ?t - storyteller) - set
(heard ?a - audience) - set
(story-set) - set
)
(:action entertain
:parameters (?t - storyteller ?a - audience)
:precondition (true)
:effect ((assign (heard ?a) (union (heard ?a) (known ?t))))
)
)
"""
problem = pddl"""
(define (problem storytellers-problem)
(:domain storytellers)
(:objects
jacob wilhelm - storyteller
hanau steinau - audience
snowwhite rumpelstiltskin - story
)
(:init
(= (story-set) (construct-set snowwhite rumpelstiltskin))
(= (known jacob) (construct-set snowwhite))
(= (known wilhelm) (construct-set rumpelstiltskin))
(= (heard hanau) (empty-set))
(= (heard steinau) (empty-set))
)
(:goal (and
; both audiences must hear all stories
(= (heard hanau) (story-set))
(= (heard steinau) (story-set))
))
)
"""
state = initstate(domain, problem)
As is the case for registering new predicates and functions, the @register
macro is preferred whenever packages that depend on PDDL.jl need to be precompiled. However, it is also possible register and deregister theories at runtime with register!
and deregister!
:
Sets.register!()
Sets.deregister!()
Predefined Theories
Alongside the Sets
example shown above, a theory for handling Arrays
is predefined as part of PDDL.jl:
PDDL.Sets
— ModulePDDL.Sets
Extends PDDL with set-valued fluents. Set members must be PDDL objects. Register by calling PDDL.Sets.@register()
. Attach to a specific domain
by calling PDDL.Sets.attach!(domain)
.
Datatypes
set
: A set of PDDL objects.
Predicates
(member ?x - object ?s - set)
: Is?x
a member of?s
?(subset ?s1 ?s2 - set)
: Is?s1
a subset of?s2
?
Functions
(construct-set ?x ?y ... - object)
: Constructs a set from?x
,?y
, etc.(empty-set)
: Constructs an empty set.(cardinality ?s - set)
: The number of elements in?s
.(union ?s1 ?s2 - set)
: The union of?s1
and?s2
.(intersect ?s1 ?s2 - set)
: The intersection of?s1
and?s2
.(difference ?s1 ?s2 - set)
: The set difference of?s1
and?s2
.(add-element ?s - set? ?x - object)
: Add?x
to?s
.(rem-element ?s - set? ?x - object)
: Remove?x
from?s
.
PDDL.Arrays
— ModulePDDL.Arrays
Extends PDDL with array-valued fluents. Register by calling PDDL.Arrays.@register()
. Attach to a specific domain
by calling PDDL.Arrays.attach!(domain)
.
Datatypes
Generic Arrays
array
: A multidimensional array with untyped elements.vector
: A 1D array with untyped elements.matrix
: A 2D array with untyped elements.
Bit Arrays
bit-array
: A multidimensional array withboolean
elements.bit-vector
: A 1D array withboolean
elements.bit-matrix
: A 2D array withboolean
elements.
Integer Arrays
int-array
: A multidimensional array withinteger
elements.int-vector
: A 1D array withinteger
elements.int-matrix
: A 2D array withinteger
elements.
Numeric Arrays
num-array
: A multidimensional array withnumber
elements.num-vector
: A 1D array withnumber
elements.num-matrix
: A 2D array withnumber
elements.
Array Indices
array-index
: A multidimensional array index, represented as aTuple
.vector-index
: A 1D array index, represented as anInt
.matrix-index
: A 2D array index, represented as a 2-tuple.
Predicates
(has-index ?a ?i)
: Checks if?i
is a valid index for?a
.(has-row ?m ?i)
: Checks if?i
is a valid row index for matrix?m
.(has-col ?m ?i)
: Checks if?i
is a valid column index for matrix?m
.
Functions
Array Constructors
(new-array ?v ?n1 ?n2 ...)
: Construct a new array filled with?v
.(new-matrix ?v ?h ?w)
: Construct a new?h
x?w
matrix filled with?v
.(new-vector ?v ?n)
: Construct a new vector of length?n
filled with?v
.(vec ?x1 ?x2 ... ?xn)
: Construct a vector from?x1
,?x2
, etc.(mat ?v1 ?v2 ... ?vn)
: Construct a matrix from column vectors?v1
,?v2
, etc.
Similar constructors are available for bit arrays, integer arrays, and numeric arrays.
Index Constructors
(index ?i1 ?i2 ...)
: Construct an array index from?i1
,?i2
, etc.(vec-index ?i)
: Construct a 1D array index from?i
.(mat-index ?i1 ?i2)
: Construct a 2D array index from?i1
,?i2
.
Accessors
(get-index ?a ?i ?j ...)
: Get element(?i, ?j, ...)
of?a
.(set-index ?a ?v ?i ?j ...)
: Set element(?i, ?j, ...)
of?a
to?v
.
Array Dimensions
(length ?a)
: Get the number of elements in an array?a
.(height ?a)
: Get the height of a matrix?m
.(width ?a)
: Get the width of a matrix?m
.(ndims ?a)
: Get the number of dimensions of an array?a
.
Array Manipulation
(push ?v ?x)
: Push?x
onto the end of?v
.(pop ?v)
: Pop the last element of?v
.(transpose ?m)
: Transpose a matrix?m
.
Index Manipulation
(get-row ?idx)
: Get the row number of amatrix-index
.(get-col ?idx)
: Get the column number of amatrix-index
.(set-row ?idx ?row)
: Set the row number of amatrix-index
to?row
.(set-col ?idx ?col)
: Set the column number of amatrix-index
to?col
.(increase-row ?idx ?d)
: Increase the row number of amatrix-index
by?d
.(increase-col ?idx ?d)
: Increase the column number of amatrix-index
by?d
.(decrease-row ?idx ?d)
: Decrease the row number of amatrix-index
by?d
.(decrease-col ?idx ?d)
: Decrease the column number of amatrix-index
by?d
.
These theories are not registered by default, and should be registered with the @register
macro before use.