Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial pass on quantified conditions. #50

Merged
merged 8 commits into from
Nov 21, 2022
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
56 changes: 28 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ You can use the `pddl` package in two ways: as a library, and as a CLI tool.

### As a library

This is an example of how you can build a PDDL domain or problem
This is an example of how you can build a PDDL domain or problem
programmatically:
```python
from pddl.logic import Predicate, constants, variables
Expand Down Expand Up @@ -160,29 +160,29 @@ Supported commands are:

## Features

Supported [PDDL 3.1](https://helios.hud.ac.uk/scommv/IPC-14/repository/kovacs-pddl-3.1-2011.pdf)
requirements:

- [x] `:strips`
- [x] `:typing`
- [x] `:negative-preconditions`
- [x] `:disjunctive-preconditions`
- [x] `:equality`
- [ ] `:existential-preconditions`
- [ ] `:universal-preconditions`
- [ ] `:quantified-preconditions`
- [x] `:conditional-effects`
- [ ] `:fluents`
- [ ] `:numeric-fluents`
- [x] `:non-deterministic` (see [6th IPC: Uncertainty Part](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.163.7140&rep=rep1&type=pdf))
- [ ] `:adl`
- [ ] `:durative-actions`
- [ ] `:duration-inequalities`
- [x] `:derived-predicates`
- [ ] `:timed-initial-literals`
- [ ] `:preferences`
- [ ] `:constraints`
- [ ] `:action-costs`
Supported [PDDL 3.1](https://helios.hud.ac.uk/scommv/IPC-14/repository/kovacs-pddl-3.1-2011.pdf)
requirements:

- [x] `:strips`
- [x] `:typing`
- [x] `:negative-preconditions`
- [x] `:disjunctive-preconditions`
- [x] `:equality`
- [x] `:existential-preconditions`
- [x] `:universal-preconditions`
- [x] `:quantified-preconditions`
- [x] `:conditional-effects`
- [ ] `:fluents`
- [ ] `:numeric-fluents`
- [x] `:non-deterministic` (see [6th IPC: Uncertainty Part](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.163.7140&rep=rep1&type=pdf))
- [x] `:adl`
- [ ] `:durative-actions`
- [ ] `:duration-inequalities`
- [x] `:derived-predicates`
- [ ] `:timed-initial-literals`
- [ ] `:preferences`
- [ ] `:constraints`
- [ ] `:action-costs`

## Development

Expand Down Expand Up @@ -220,8 +220,8 @@ Copyright (c) 2021-2022 WhiteMech

## Acknowledgements

The `pddl` project is partially supported by the ERC Advanced Grant WhiteMech
(No. 834228), the EU ICT-48 2020 project TAILOR (No. 952215),
the PRIN project RIPER (No. 20203FFYLK), and the JPMorgan AI Faculty
Research Award "Resilience-based Generalized Planning and Strategic
The `pddl` project is partially supported by the ERC Advanced Grant WhiteMech
(No. 834228), the EU ICT-48 2020 project TAILOR (No. 952215),
the PRIN project RIPER (No. 20203FFYLK), and the JPMorgan AI Faculty
Research Award "Resilience-based Generalized Planning and Strategic
Reasoning".
3 changes: 3 additions & 0 deletions pddl/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ class Requirements(Enum):
TYPING = RS.TYPING.strip()
NEG_PRECONDITION = RS.NEG_PRECONDITION.strip()
DIS_PRECONDITION = RS.DIS_PRECONDITION.strip()
UNIVERSAL_PRECONDITION = RS.UNIVERSAL_PRECONDITION.strip()
EXISTENTIAL_PRECONDITION = RS.EXISTENTIAL_PRECONDITION.strip()
QUANTIFIED_PRECONDITION = RS.QUANTIFIED_PRECONDITION.strip()
EQUALITY = RS.EQUALITY.strip()
CONDITIONAL_EFFECTS = RS.CONDITIONAL_EFFECTS.strip()
ADL = RS.ADL.strip()
Expand Down
76 changes: 75 additions & 1 deletion pddl/logic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@
#

"""Base classes for PDDL logic formulas."""
from typing import Optional, Sequence
import functools
from typing import AbstractSet, Collection, Optional, Sequence

from pddl.helpers.base import ensure_set
from pddl.helpers.cache_hash import cache_hash
from pddl.logic.terms import Variable
from pddl.parser.symbols import Symbols


@cache_hash
Expand Down Expand Up @@ -210,6 +214,76 @@ class Not(UnaryOp):
SYMBOL = "not"


@cache_hash
@functools.total_ordering
class QuantifiedCondition(Formula):
"""Superclass for quantified conditions."""

SYMBOL: str

def __init__(
self, cond: "Formula", variables: Optional[Collection[Variable]] = None
) -> None:
"""Initialize the quantified condition."""
self._cond = cond
self._variables = ensure_set(variables)

@property
def condition(self) -> "Formula":
"""Get the condition."""
return self._cond

@property
def variables(self) -> AbstractSet[Variable]:
"""Get the variables."""
return self._variables

def __str__(self) -> str:
"""Get the string representation."""

def build_tags(tags):
if len(tags) == 0:
return ""
return f" - {' '.join(tags)}"

var_block = " ".join([f"{v}{build_tags(v.type_tags)}" for v in self.variables])
return f"({self.SYMBOL} ({var_block}) {self.condition})"

def __repr__(self) -> str:
"""Get an unambiguous string representation."""
return f"{type(self).__name__}({self.variables}, {self.condition})"

def __eq__(self, other) -> bool:
"""Compare with another object."""
return (
isinstance(other, type(self))
and self.variables == other.variables
and self.condition == other.condition
)

def __hash__(self) -> int:
"""Compute the hash of the object."""
return hash((type(self), self.variables, self.condition))

def __lt__(self, other):
"""Compare with another object."""
if isinstance(other, QuantifiedCondition):
return (self.variables, self.condition) < (other.variables, other.condition)
return super().__lt__(other)


class ForallCondition(QuantifiedCondition):
"""Forall Condition."""

SYMBOL = Symbols.FORALL.value


class ExistsCondition(QuantifiedCondition):
"""Exists Condition."""

SYMBOL = Symbols.EXISTS.value


def ensure_formula(f: Optional[Formula], is_none_true: bool) -> Formula:
"""
Ensure the argument is a formula.
Expand Down
7 changes: 7 additions & 0 deletions pddl/parser/common.lark
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ NAME: /[a-zA-Z][a-zA-Z0-9-_]*/
| NON_DETERMINISTIC
| NEG_PRECONDITION
| DIS_PRECONDITION
| EXISTENTIAL_PRECONDITIONS
| UNIVERSAL_PRECONDITIONS
| QUANTIFIED_PRECONDITIONS
| ADL
| DERIVED_PREDICATES
| CONDITIONAL_EFFECTS
Expand All @@ -24,6 +27,7 @@ PRECONDITION: ":precondition"
EFFECT: ":effect"
DERIVED: ":derived"
FORALL: "forall"
EXISTS: "exists"
WHEN: "when"
OBJECT: "object"
AND: "and"
Expand All @@ -44,6 +48,9 @@ NEG_PRECONDITION: ":negative-preconditions"
DIS_PRECONDITION: ":disjunctive-preconditions"
DERIVED_PREDICATES: ":derived-predicates"
CONDITIONAL_EFFECTS: ":conditional-effects"
EXISTENTIAL_PRECONDITIONS: ":existential-preconditions"
UNIVERSAL_PRECONDITIONS: ":universal-preconditions"
QUANTIFIED_PRECONDITIONS: ":quantified-preconditions"

// others
LPAR : "("
Expand Down
3 changes: 3 additions & 0 deletions pddl/parser/domain.lark
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ gd: atomic_formula_term
| LPAR NOT gd RPAR
| LPAR AND gd* RPAR
| LPAR IMPLY gd gd RPAR
| LPAR EXISTS LPAR typed_list_variable RPAR gd RPAR
| LPAR FORALL LPAR typed_list_variable RPAR gd RPAR

// effects
emptyor_effect: LPAR RPAR
Expand Down Expand Up @@ -76,6 +78,7 @@ type_def: LPAR EITHER primitive_type+ RPAR
%import .common.EFFECT -> EFFECT
%import .common.DERIVED -> DERIVED
%import .common.FORALL -> FORALL
%import .common.EXISTS -> EXISTS
%import .common.WHEN -> WHEN
%import .common.OBJECT -> OBJECT
%import .common.AND -> AND
Expand Down
104 changes: 79 additions & 25 deletions pddl/parser/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@
from pddl.core import Action, Domain, Requirements
from pddl.exceptions import PDDLMissingRequirementError
from pddl.helpers.base import assert_, safe_get, safe_index
from pddl.logic.base import And, FalseFormula, Imply, Not, OneOf, Or
from pddl.logic.base import (
And,
ExistsCondition,
FalseFormula,
ForallCondition,
Imply,
Not,
OneOf,
Or,
)
from pddl.logic.effects import AndEffect, Forall, When
from pddl.logic.predicates import DerivedPredicate, EqualTo, Predicate
from pddl.logic.terms import Constant, Variable
Expand Down Expand Up @@ -126,37 +135,76 @@ def emptyor_pregd(self, args):
assert_(len(args) == 1)
return args[0]

def gd_not(self, args):
"""Process the 'gd' not rule."""
if not bool(
{Requirements.NEG_PRECONDITION, Requirements.ADL}
& self._extended_requirements
):
# raise PDDLMissingRequirementError(Requirements.NEG_PRECONDITION)
# TODO temporary change; remove
pass
return Not(args[2])

def gd_and(self, args):
"""Process the 'gd_and' rule."""
operands = args[2:-1]
return And(*operands)

def gd_or(self, args):
"""Process the 'gd' or rule."""
if not bool(
{Requirements.DIS_PRECONDITION, Requirements.ADL}
& self._extended_requirements
):
raise PDDLMissingRequirementError(Requirements.DIS_PRECONDITION)
operands = args[2:-1]
return Or(*operands)

def gd_imply(self, args):
"""Process the 'gd' imply rule."""
if not bool(
{Requirements.DIS_PRECONDITION, Requirements.ADL}
& self._extended_requirements
):
raise PDDLMissingRequirementError(Requirements.DIS_PRECONDITION)
return Imply(args[2], args[3])

def gd_quantifiers(self, args):
"""Process the 'gd' quantifiers rule."""
req, cond_class = {
Symbols.FORALL.value: (
Requirements.UNIVERSAL_PRECONDITION,
ForallCondition,
),
Symbols.EXISTS.value: (
Requirements.EXISTENTIAL_PRECONDITION,
ExistsCondition,
),
}[args[1]]
if not bool(
{req, Requirements.QUANTIFIED_PRECONDITION, Requirements.ADL}
& self._extended_requirements
):
raise PDDLMissingRequirementError(req)
variables = [Variable(name, tags) for name, tags in args[3].items()]
condition = args[5]
return cond_class(cond=condition, variables=variables)

def gd(self, args):
"""Process the 'gd' rule."""
if len(args) == 1:
return args[0]
elif args[1] == Symbols.NOT.value:
if not bool(
{Requirements.NEG_PRECONDITION, Requirements.ADL}
& self._extended_requirements
):
# raise PDDLMissingRequirementError(Requirements.NEG_PRECONDITION)
# TODO temporary change; remove
pass
return Not(args[2])
return self.gd_not(args)
elif args[1] == Symbols.AND.value:
operands = args[2:-1]
return And(*operands)
return self.gd_and(args)
elif args[1] == Symbols.OR.value:
if not bool(
{Requirements.DIS_PRECONDITION, Requirements.ADL}
& self._extended_requirements
):
raise PDDLMissingRequirementError(Requirements.DIS_PRECONDITION)
operands = args[2:-1]
return Or(*operands)
return self.gd_or(args)
elif args[1] == Symbols.IMPLY.value:
if not bool(
{Requirements.DIS_PRECONDITION, Requirements.ADL}
& self._extended_requirements
):
raise PDDLMissingRequirementError(Requirements.DIS_PRECONDITION)
return Imply(args[2], args[3])
return self.gd_imply(args)
elif args[1] in [Symbols.FORALL.value, Symbols.EXISTS.value]:
return self.gd_quantifiers(args)

def emptyor_effect(self, args):
"""Process the 'emptyor_effect' rule."""
Expand All @@ -178,7 +226,7 @@ def c_effect(self, args):
if len(args) == 1:
return args[0]
if args[1] == Symbols.FORALL.value:
return Forall(effects=args[-2], variables=args[3])
return Forall(effect=args[-2], variables=args[3])
if args[1] == Symbols.WHEN.value:
return When(args[2], args[3])
if args[1] == Symbols.ONEOF.value:
Expand Down Expand Up @@ -206,6 +254,12 @@ def atomic_formula_term(self, args):
"""Process the 'atomic_formula_term' rule."""

def constant_or_variable(t):
# Case where the term is a free variable (bug) or comes from a parent quantifier
if (
not isinstance(t, Constant)
and t not in self._current_parameters_by_name
):
return Variable(str(t), {})
return t if isinstance(t, Constant) else self._current_parameters_by_name[t]

if args[1] == Symbols.EQUAL.value:
Expand Down
3 changes: 3 additions & 0 deletions pddl/parser/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class RequirementSymbols(Enum):
TYPING = ":typing"
NEG_PRECONDITION = ":negative-preconditions"
DIS_PRECONDITION = ":disjunctive-preconditions"
UNIVERSAL_PRECONDITION = ":universal-preconditions"
EXISTENTIAL_PRECONDITION = ":existential-preconditions"
QUANTIFIED_PRECONDITION = ":quantified-preconditions"
EQUALITY = ":equality"
CONDITIONAL_EFFECTS = ":conditional-effects"
ADL = ":adl"
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"tireworld",
"tireworld-truck",
"triangle-tireworld",
# "zenotravel",
"zenotravel",
]

DOMAIN_FILES = [
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/pddl_files/zenotravel/domain.pddl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(define (domain zenotravel)
(:requirements :typing :non-deterministic)
(:requirements :typing :universal-preconditions :non-deterministic)
(:types aircraft person city flevel)
(:predicates (atperson ?p - person ?c - city)
(at-aircraft ?a - aircraft ?c - city)
Expand Down
Loading