|
| 1 | +# Extension Interfaces |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## Built-in Types, Predicates and Functions |
| 6 | + |
| 7 | +PDDL.jl stores mappings from (global) names to their implementations by making use of [value-based dispatch](https://github.com/ztangent/ValSplit.jl). These mappings are stored by defining methods for the following functions: |
| 8 | + |
| 9 | +```@docs |
| 10 | +PDDL.datatype_def |
| 11 | +PDDL.predicate_def |
| 12 | +PDDL.function_def |
| 13 | +PDDL.modifier_def |
| 14 | +``` |
| 15 | + |
| 16 | +These mappings can also be accessed in dictionary form with the following utility functions: |
| 17 | + |
| 18 | +```@docs |
| 19 | +PDDL.global_datatypes() |
| 20 | +PDDL.global_predicates() |
| 21 | +PDDL.global_functions() |
| 22 | +PDDL.global_modifiers() |
| 23 | +``` |
| 24 | + |
| 25 | +### Datatypes |
| 26 | + |
| 27 | +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: |
| 28 | + |
| 29 | +```julia |
| 30 | +PDDL.datatype_def(::Val{:boolean}) = (type=Bool, default=false) |
| 31 | +PDDL.datatype_def(::Val{:integer}) = (type=Int, default=0) |
| 32 | +PDDL.datatype_def(::Val{:number}) = (type=Float64, default=1.0) |
| 33 | +PDDL.datatype_def(::Val{:numeric}) = (type=Float64, default=1.0) |
| 34 | +``` |
| 35 | + |
| 36 | +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: |
| 37 | + |
| 38 | +```pddl |
| 39 | +(distance ?c1 - city ?c2 - city) - number |
| 40 | +``` |
| 41 | + |
| 42 | +### Predicates and Functions |
| 43 | + |
| 44 | +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: |
| 45 | + |
| 46 | +```julia |
| 47 | +# Built-in predicates |
| 48 | +PDDL.predicate_def(::Val{:(==)}) = PDDL.equiv |
| 49 | +PDDL.predicate_def(::Val{:<=}) = <= |
| 50 | +PDDL.predicate_def(::Val{:>=}) = >= |
| 51 | +PDDL.predicate_def(::Val{:<}) = < |
| 52 | +PDDL.predicate_def(::Val{:>}) = > |
| 53 | + |
| 54 | +# Built-in functions |
| 55 | +PDDL.function_def(::Val{:+}) = + |
| 56 | +PDDL.function_def(::Val{:-}) = - |
| 57 | +PDDL.function_def(::Val{:*}) = * |
| 58 | +PDDL.function_def(::Val{:/}) = / |
| 59 | +``` |
| 60 | + |
| 61 | +### Modifiers |
| 62 | + |
| 63 | +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: |
| 64 | + |
| 65 | +```julia |
| 66 | +PDDL.modifier_def(::Val{:increase}) = :+ |
| 67 | +PDDL.modifier_def(::Val{:decrease}) = :- |
| 68 | +PDDL.modifier_def(::Val{Symbol("scale-up")}) = :* |
| 69 | +PDDL.modifier_def(::Val{Symbol("scale-down")}) = :/ |
| 70 | +``` |
| 71 | + |
| 72 | +## Adding Types, Predicates and Functions |
| 73 | + |
| 74 | +To add a new global datatype, predicate, function, or modifier to PDDL, it is enough to define a new method of [`PDDL.datatype_def`](@ref), [`PDDL.predicate_def`](@ref), [`PDDL.function_def`](@ref), or [`PDDL.modifier_def`](@ref) respectively. Alternatively, one can use the [`@register`](@ref) macro to register new implementations at compile-time: |
| 75 | + |
| 76 | +```@docs |
| 77 | +PDDL.@register |
| 78 | +``` |
| 79 | + |
| 80 | +In scripting contexts, run-time registration and de-registration can be achieved using [`PDDL.register!`](@ref) and [`PDDL.deregister!`](@ref): |
| 81 | + |
| 82 | +```@docs |
| 83 | +PDDL.register! |
| 84 | +PDDL.deregister! |
| 85 | +``` |
| 86 | + |
| 87 | +## Defining and Registering Theories |
| 88 | + |
| 89 | +Similar to [Satisfiability Modulo Theories (SMT) solvers](https://en.wikipedia.org/wiki/Satisfiability_modulo_theories), PDDL.jl provides support for [*planning* modulo theories](https://dl.acm.org/doi/10.5555/3038546.3038555). By registering a new theory, developers can extend the semantics of PDDL to handle new mathematical objects such as sets, arrays, and tuples. |
| 90 | + |
| 91 | +A new theory can be implemented by writing a (sub)module annotated with the [`@pddltheory`](@ref) macro: |
| 92 | + |
| 93 | +```@docs |
| 94 | +@pddltheory |
| 95 | +``` |
| 96 | + |
| 97 | +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)](https://dl.acm.org/doi/10.5555/3038546.3038555)): |
| 98 | + |
| 99 | +```julia |
| 100 | +@pddltheory module Sets |
| 101 | + |
| 102 | +using PDDL |
| 103 | +using PDDL: SetAbs |
| 104 | + |
| 105 | +construct_set(xs::Symbol...) = Set{Symbol}(xs) |
| 106 | +empty_set() = Set{Symbol}() |
| 107 | +cardinality(s::Set) = length(s) |
| 108 | +member(s::Set, x) = in(x, s) |
| 109 | +subset(x::Set, y::Set) = issubset(x, y) |
| 110 | +union(x::Set, y::Set) = Base.union(x, y) |
| 111 | +intersect(x::Set, y::Set) = Base.intersect(x, y) |
| 112 | +difference(x::Set, y::Set) = setdiff(x, y) |
| 113 | +add_element(s::Set, x) = push!(copy(s), x) |
| 114 | +rem_element(s::Set, x) = pop!(copy(s), x) |
| 115 | + |
| 116 | +set_to_term(s::Set) = isempty(s) ? Const(Symbol("(empty-set)")) : |
| 117 | + Compound(Symbol("construct-set"), PDDL.val_to_term.(collect(s))) |
| 118 | + |
| 119 | +const DATATYPES = Dict( |
| 120 | + "set" => (type=Set{Symbol}, default=Set{Symbol}()) |
| 121 | +) |
| 122 | + |
| 123 | +const ABSTRACTIONS = Dict( |
| 124 | + "set" => SetAbs{Set{Symbol}} |
| 125 | +) |
| 126 | + |
| 127 | +const CONVERTERS = Dict( |
| 128 | + "set" => set_to_term |
| 129 | +) |
| 130 | + |
| 131 | +const PREDICATES = Dict( |
| 132 | + "member" => member, |
| 133 | + "subset" => subset |
| 134 | +) |
| 135 | + |
| 136 | +const FUNCTIONS = Dict( |
| 137 | + "construct-set" => construct_set, |
| 138 | + "empty-set" => empty_set, |
| 139 | + "cardinality" => cardinality, |
| 140 | + "union" => union, |
| 141 | + "intersect" => intersect, |
| 142 | + "difference" => difference, |
| 143 | + "add-element" => add_element, |
| 144 | + "rem-element" => rem_element |
| 145 | +) |
| 146 | + |
| 147 | +end |
| 148 | +``` |
| 149 | + |
| 150 | +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`](@ref), which means that the abstract interpreter will use a set of sets to represent the abstract value of a set-valued variable. |
| 151 | + |
| 152 | +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: |
| 153 | + |
| 154 | +```julia |
| 155 | +Sets.@register() |
| 156 | + |
| 157 | +domain = pddl""" |
| 158 | +(define (domain storytellers) |
| 159 | + (:requirements :typing :fluents) |
| 160 | + (:types storyteller audience story) |
| 161 | + (:functions (known ?t - storyteller) - set |
| 162 | + (heard ?a - audience) - set |
| 163 | + (story-set) - set |
| 164 | + ) |
| 165 | + (:action entertain |
| 166 | + :parameters (?t - storyteller ?a - audience) |
| 167 | + :precondition (true) |
| 168 | + :effect ((assign (heard ?a) (union (heard ?a) (known ?t)))) |
| 169 | + ) |
| 170 | +) |
| 171 | +""" |
| 172 | + |
| 173 | +problem = pddl""" |
| 174 | +(define (problem storytellers-problem) |
| 175 | + (:domain storytellers) |
| 176 | + (:objects |
| 177 | + jacob wilhelm - storyteller |
| 178 | + hanau steinau - audience |
| 179 | + snowwhite rumpelstiltskin - story |
| 180 | + ) |
| 181 | + (:init |
| 182 | + (= (story-set) (construct-set snowwhite rumpelstiltskin)) |
| 183 | + (= (known jacob) (construct-set snowwhite)) |
| 184 | + (= (known wilhelm) (construct-set rumpelstiltskin)) |
| 185 | + (= (heard hanau) (empty-set)) |
| 186 | + (= (heard steinau) (empty-set)) |
| 187 | + ) |
| 188 | + (:goal (and |
| 189 | + ; both audiences must hear all stories |
| 190 | + (= (heard hanau) (story-set)) |
| 191 | + (= (heard steinau) (story-set)) |
| 192 | + )) |
| 193 | +) |
| 194 | +""" |
| 195 | + |
| 196 | +state = initstate(domain, problem) |
| 197 | +``` |
| 198 | + |
| 199 | +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!`: |
| 200 | + |
| 201 | +```julia |
| 202 | +Sets.register!() |
| 203 | +Sets.deregister!() |
| 204 | +``` |
| 205 | + |
| 206 | +## Predefined Theories |
| 207 | + |
| 208 | +Alongside the `Sets` example shown above, a theory for handling `Arrays` is predefined as part of PDDL.jl: |
| 209 | + |
| 210 | +```julia |
| 211 | +PDDL.Sets |
| 212 | +PDDL.Arrays |
| 213 | +``` |
| 214 | + |
| 215 | +These theories are not registered by default, and should be registered with the `@register` macro before use. |
0 commit comments