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_defFunction
datatype_def(name)

Mapping from PDDL data types to Julia types and default values.

source
PDDL.modifier_defFunction
modifier_def(name)

Mapping from PDDL modifiers (i.e. in-place assignments) to PDDL functions.

source

These mappings can also be accessed in dictionary form with the following utility functions:

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.@registerMacro
@register([:datatype|:predicate|:function|:modifier|:converter], name, x)

Register x as a global datatype, predicate, function, modifier, or term converter under the specified name.

source

In scripting contexts, run-time registration and de-registration can be achieved using PDDL.register! and PDDL.deregister!:

PDDL.register!Function
register!([:datatype|:predicate|:function|:modifier|:converter], name, x)

Register x as a global datatype, predicate, function or modifier, or term converter under the specified name.

Top-Level Only

Because register! defines new methods, it should only be called at the top-level in order to avoid world-age errors.

Precompilation Not Supported

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.

source
PDDL.deregister!Function
deregister!([:datatype|:predicate|:function|:modifier|:converter], name)

Deregister the datatype, predicate, function or modifier specified by name.

Top-Level Only

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.

source

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.@pddltheoryMacro
@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.

source

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.SetsModule
PDDL.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.
source
PDDL.ArraysModule
PDDL.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 with boolean elements.
  • bit-vector: A 1D array with boolean elements.
  • bit-matrix: A 2D array with boolean elements.

Integer Arrays

  • int-array: A multidimensional array with integer elements.
  • int-vector: A 1D array with integer elements.
  • int-matrix: A 2D array with integer elements.

Numeric Arrays

  • num-array: A multidimensional array with number elements.
  • num-vector: A 1D array with number elements.
  • num-matrix: A 2D array with number elements.

Array Indices

  • array-index: A multidimensional array index, represented as a Tuple.
  • vector-index: A 1D array index, represented as an Int.
  • 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 a matrix-index.
  • (get-col ?idx): Get the column number of a matrix-index.
  • (set-row ?idx ?row): Set the row number of a matrix-index to ?row.
  • (set-col ?idx ?col): Set the column number of a matrix-index to ?col.
  • (increase-row ?idx ?d): Increase the row number of a matrix-index by ?d.
  • (increase-col ?idx ?d): Increase the column number of a matrix-index by ?d.
  • (decrease-row ?idx ?d): Decrease the row number of a matrix-index by ?d.
  • (decrease-col ?idx ?d): Decrease the column number of a matrix-index by ?d.
source

These theories are not registered by default, and should be registered with the @register macro before use.