Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,13 @@ There are wonderful material at the the University of Edinburgh:

* Concurrent temporal planning (required action overlap, e.g. match/cellar).
* Heuristic improvements for the reference planners.
* ADL (conditional effects, quantifiers) and a full and/or/not precondition tree.
* ADL on the goal side (`or`/`forall`/`exists` inside `(:goal ...)`) and
`(either ...)` union types.

Done recently: case-insensitive keywords, `:requirements` capture/enforcement,
Done recently: ADL support (#10) — conditional/universal effects, quantified
and full-boolean preconditions and `=` equality, evaluated end-to-end by the
reference planners — plus case-insensitive keywords, `:requirements`
capture/enforcement,
a planner interface with BFS/A*/GBFS/UCS reference planners, numeric fluents
(`:functions`, numeric preconditions/effects), action costs (`total-cost` +
cost-aware search), durative-action recovery into the object model, a
Expand Down
37 changes: 32 additions & 5 deletions docs/object-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,34 @@ tuple. **`Atom` has no value equality** — `initialstate()`/`goals()` hold
tuples. Normalize with `pddlpy.planning.atom_tuple` (or compare `repr`s).

### `Operator`
An instantaneous action, grounded or not. Fields: `operator_name`,
`variable_list`, `precondition_pos`/`precondition_neg`,
An instantaneous action, grounded or not. The flat STRIPS-style fields —
`operator_name`, `variable_list`, `precondition_pos`/`precondition_neg`,
`precondition_connective` (`'and'`/`'or'`, #13), `effect_pos`/`effect_neg`,
and the numeric `precondition_num`/`effect_num` (#11).
and the numeric `precondition_num`/`effect_num` (#11) — summarise the
unconditional conjunctive part of an action and remain the fast path for plain
STRIPS domains.

**ADL (#10).** For full boolean / quantified / equality preconditions and
conditional / universal effects, an `Operator` also carries structured trees:

- `precondition_tree` (lifted) and `precondition` (grounded) — a `Condition`
tree of `Lit` / `Not` / `And` / `Or` / `Equality` / `NumericCond` /
`Exists` / `Forall`. `condition.holds(atoms, fluents)` evaluates the whole
tree, so `or`, `imply`, `=` and quantifiers are **evaluated**, not merely
preserved. `State.applicable` uses the grounded tree when present.
- `effect_tree` (lifted) and `conditional_effects` (grounded) — an `Effect`
tree (`AddDel` / `NumEff` / `EffAnd` / `When` / `Universal`). Grounding
compiles it into the unconditional `effect_pos`/`effect_neg`/`effect_num`
plus a list of `CondEffect` guards that `State.apply` fires only when their
condition holds in the pre-state.

Quantifiers are grounded by **expansion over the world objects**:
`(forall (?x - t) φ)` becomes the `And` of `φ` instantiated for every object of
type `t` (vacuously *true* when there are none), and `(exists …)` the
corresponding `Or` (vacuously *false*). The `simple_conjunction` flag records
whether the precondition is a plain conjunction of literals/numerics; anything
richer makes the grounder fall back from static pruning to the full cartesian
product so no applicable binding is dropped.

### Numeric fluents (#11)
`Expr` tree — `Num`, `Fluent`, `BinOp`, `Neg` — each with
Expand Down Expand Up @@ -164,8 +188,11 @@ a follow-up to this planner.
- **Union types:** the `:types` hierarchy is supported (#22) — a parameter
typed with a supertype binds objects of any transitive subtype — but
`(either ...)` union types are not handled.
- **Disjunctive/ADL preconditions:** the `or` connective is preserved but not
evaluated; the reference planners reject such domains via capability checks.
- **ADL (#10):** conditional/universal effects, quantified and full-boolean
preconditions and `=` equality are parsed, grounded and **solved** by the
reference planners. One gap remains: ADL structure on the **goal** side of a
problem (`or`/`forall`/`exists` in `(:goal …)`) is still flattened to a
conjunction of atoms.
- **Durative actions:** solved sequentially by the `temporal` planner (#84);
the reference STRIPS planners remain non-temporal. **Required concurrency**
(mandatory action overlap) and PDDL+ processes/events are out of scope.
1 change: 1 addition & 0 deletions pddl.g4
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ goalDesc
| '(' 'imply' goalDesc goalDesc ')'
| '(' 'exists' '(' typedVariableList ')' goalDesc ')'
| '(' 'forall' '(' typedVariableList ')' goalDesc ')'
| '(' '=' term term ')'
| fComp
;

Expand Down
10 changes: 6 additions & 4 deletions pddlpy/binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ class StaticPrunedBinder(VariableBinder):

def bind(self, dp, operator) -> Iterator[Dict[str, str]]:
# Pruning assumes the positive preconditions must all hold (a
# conjunction). For a disjunctive precondition (#13) the flattened atoms
# are alternatives, not conjuncts, so static joining would be unsound —
# fall back to the full cartesian product.
if operator.precondition_connective != 'and':
# conjunction). If the precondition is anything richer than a plain
# conjunction of literals/numerics — a disjunction (#13), a quantifier,
# an equality, or a nested negation (#10) — the flat positive atoms are
# not guaranteed conjuncts, so static joining would be unsound: fall
# back to the full cartesian product.
if not getattr(operator, 'simple_conjunction', True):
yield from CartesianBinder().bind(dp, operator)
return

Expand Down
11 changes: 8 additions & 3 deletions pddlpy/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@ def _condition_atoms(dp: DomainProblem) -> Iterator[Tuple[str, Any]]:
for atom in dp.goals():
yield ":goal", atom
for name, op in dp.domain.operators.items():
for group in (op.precondition_pos, op.precondition_neg,
op.effect_pos, op.effect_neg):
for atom in group:
# Walk the ADL precondition/effect trees so atoms nested inside
# or/forall/exists/when/= are checked too (#10), not just the flat
# unconditional summary sets.
if op.precondition_tree is not None:
for atom in op.precondition_tree.atoms():
yield "action %r" % name, atom
if op.effect_tree is not None:
for atom in op.effect_tree.atoms():
yield "action %r" % name, atom
for name, dop in dp.domain.durative_operators.items():
for timed in (dop.condition_pos, dop.condition_neg):
Expand Down
Loading