Skip to content

Commit

Permalink
fix: use simplified mapping
Browse files Browse the repository at this point in the history
After this commit, there is no need to provide an explicit mapping, since it is assumed that the propositions *are* the ground fluents.

E.g. if previously the formula was specified as follows:

formula = "on_b_a & O(ontable_c)"

Now we require that the propositions can be interpreted as PDDL predicates, i.e.:

formula = '"on b a" & O("ontable c")'

Pylogics does not allow spaces in the proposition name; therefore, the double quotes are always required.

Only exception is when the predicate is unary:

formula = 'Y(O(made-p4)))'

(as in openstacks).
  • Loading branch information
marcofavorito committed Jul 9, 2023
1 parent d539ad9 commit bac485a
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 591 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ from pddl.parser.problem import ProblemParser
from pylogics.parsers import parse_pltl
from plan4past.compiler import Compiler

formula = "on_b_a & O(ontable_c)"
formula = '"on b a" & O("ontable c")'
domain_parser = DomainParser()
problem_parser = ProblemParser()

Expand Down
22 changes: 4 additions & 18 deletions plan4past/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from pylogics.syntax.base import Formula

from plan4past.compiler import Compiler
from plan4past.utils.mapping_parser import mapping_parser

DEFAULT_NEW_DOMAIN_FILENAME: str = "new-domain.pddl"
DEFAULT_NEW_PROBLEM_FILENAME: str = "new-problem.pddl"
Expand Down Expand Up @@ -62,13 +61,6 @@
help="The path to the PPLTL goal formula.",
type=click.Path(exists=True, readable=True),
)
@click.option(
"-m",
"--mapping",
help="The mapping file.",
type=click.Path(exists=True, readable=True),
default=None,
)
@click.option(
"-od",
"--out-domain",
Expand All @@ -83,20 +75,14 @@
help="Path to PDDL file to store the new problem.",
type=click.Path(dir_okay=False),
)
def cli(domain, problem, goal_inline, goal_file, mapping, out_domain, out_problem):
def cli(domain, problem, goal_inline, goal_file, out_domain, out_problem):
"""Plan4Past: Planning for Pure-Past Temporally Extended Goals."""
goal = _get_goal(goal_inline, goal_file)

in_domain, in_problem, formula = _parse_instance(domain, problem, goal)

var_map = (
mapping_parser(Path(mapping).read_text(encoding="utf-8"), formula)
if mapping
else None
)

compiled_domain, compiled_problem = _compile_instance(
in_domain, in_problem, formula, var_map
in_domain, in_problem, formula
)

try:
Expand Down Expand Up @@ -137,9 +123,9 @@ def _parse_instance(in_domain, in_problem, goal) -> Tuple[Domain, Problem, Formu
return domain, problem, formula


def _compile_instance(domain, problem, formula, mapping) -> Tuple[Domain, Problem]:
def _compile_instance(domain, problem, formula) -> Tuple[Domain, Problem]:
"""Compile the PDDL domain and problem files and the PPLTL goal formula."""
compiler = Compiler(domain, problem, formula, mapping)
compiler = Compiler(domain, problem, formula)
compiler.compile()
compiled_domain, compiled_problem = compiler.result

Expand Down
38 changes: 2 additions & 36 deletions plan4past/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
#

"""Compiler from PDDL Domain and PPLTL into a new PDDL domain."""
from typing import AbstractSet, Dict, Optional, Set, Tuple
from typing import AbstractSet, Optional, Set, Tuple

from pddl.core import Action, Domain, Problem, Requirements
from pddl.logic import Constant
from pddl.logic.base import And, Not
from pddl.logic.effects import AndEffect, When
from pddl.logic.predicates import DerivedPredicate, Predicate
Expand All @@ -35,7 +34,6 @@
from plan4past.helpers.utils import (
add_val_prefix,
check_,
default_mapping,
remove_before_prefix,
replace_symbols,
)
Expand All @@ -54,24 +52,17 @@ def __init__(
domain: Domain,
problem: Problem,
formula: Formula,
from_atoms_to_fluent: Optional[Dict[PLTLAtomic, Predicate]] = None,
) -> None:
"""
Initialize the compiler.
:param domain: the domain
:param problem: the problem
:param formula: the formula
:param from_atoms_to_fluent: optional mapping from atoms to fluent
"""
self.domain = domain
self.problem = problem
self.formula = rewrite(formula)
if from_atoms_to_fluent:
self.from_atoms_to_fluent = from_atoms_to_fluent
self.validate_mapping(domain, formula, from_atoms_to_fluent)
else:
self.from_atoms_to_fluent = default_mapping(self.formula)

check_(self.formula.logic == Logic.PLTL, "only PPLTL is supported!")

Expand All @@ -81,29 +72,6 @@ def __init__(

self._derived_predicates: Set[DerivedPredicate] = set()

@classmethod
def validate_mapping(
cls,
_domain: Domain,
_formula: Formula,
from_atoms_to_fluent: Dict[PLTLAtomic, Predicate],
):
"""
Check that the mapping is valid wrt the problem instance.
In particular:
- check that all the formula atoms are covered (TODO)
- check that all the atoms are legal wrt the formula
- check that the fluents are legal wrt the domain
:param _domain:
:param _formula:
:param from_atoms_to_fluent:
:return:
"""
for _atom, fluent in from_atoms_to_fluent.items():
check_(all(isinstance(t, Constant) for t in fluent.terms))

@property
def result(self) -> Tuple[Domain, Problem]:
"""Get the result."""
Expand All @@ -121,9 +89,7 @@ def compile(self):
def _compile_domain(self):
"""Compute the new domain."""
new_predicates = predicates(self.formula).union(val_predicates(self.formula))
new_derived_predicates = derived_predicates(
self.formula, self.from_atoms_to_fluent
)
new_derived_predicates = derived_predicates(self.formula)
new_whens = _compute_whens(self.formula)
domain_actions = _update_domain_actions_det(self.domain.actions, new_whens)

Expand Down
52 changes: 35 additions & 17 deletions plan4past/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
#

"""Miscellanea utilities."""
from typing import Dict
import re

from pddl.logic import Predicate, constants
from pylogics.syntax.base import Formula
from pylogics.syntax.pltl import Atomic as PLTLAtomic

from plan4past.utils.atoms_visitor import find_atoms
_PDDL_NAME_REGEX = "[A-Za-z][-_A-Za-z0-9]*"
_GROUND_FLUENT_REGEX = re.compile(
rf"(\"({_PDDL_NAME_REGEX})( {_PDDL_NAME_REGEX})*\")|({_PDDL_NAME_REGEX})"
)


def add_val_prefix(name: str):
Expand Down Expand Up @@ -65,19 +66,6 @@ def replace_symbols(name: str):
)


def default_mapping(formula: Formula) -> Dict[PLTLAtomic, Predicate]:
"""Compute mapping from atoms to fluents using underscores."""
symbols = find_atoms(formula)
from_atoms_to_fluents = {}
for symbol in symbols:
name, *cons = symbol.name.split("_")
if cons:
from_atoms_to_fluents[symbol] = Predicate(name, *constants(" ".join(cons)))
else:
from_atoms_to_fluents[symbol] = Predicate(name)
return from_atoms_to_fluents


def check_(condition: bool, message: str = "") -> None:
"""
User-defined assert.
Expand All @@ -88,3 +76,33 @@ def check_(condition: bool, message: str = "") -> None:
"""
if not condition:
raise AssertionError(message)


def parse_ground_fluent(symbol: str) -> Predicate:
"""
Parse a ground fluent.
:param symbol: the ground fluent
:return: the predicate
"""
match = _GROUND_FLUENT_REGEX.fullmatch(symbol)
if match is None:
raise ValueError(f"invalid PDDL symbol in formula: {symbol}")

if '"' in symbol:
tokens = symbol[1:-1].split(" ", 1)
predicate_name, cons = (
(tokens[0], tokens[1]) if len(tokens) > 1 else (tokens[0], "")
)
return Predicate(predicate_name, *constants(cons))
return Predicate(symbol)


def validate(symbol: str) -> None:
"""
Validate a symbol.
:param symbol: the symbol
"""
# check if the symbol is a valid PDDL ground fluent
parse_ground_fluent(symbol)
62 changes: 21 additions & 41 deletions plan4past/utils/derived_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

"""Derived Predicates visitor."""
from functools import singledispatch
from typing import Dict, Set
from typing import Set

from pddl.logic.base import And, Not, Or
from pddl.logic.predicates import DerivedPredicate, Predicate
Expand All @@ -39,49 +39,39 @@
)
from pylogics.utils.to_string import to_string

from plan4past.helpers.utils import add_val_prefix, replace_symbols
from plan4past.helpers.utils import add_val_prefix, parse_ground_fluent, replace_symbols


@singledispatch
def derived_predicates(
formula: object, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates(formula: object) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a formula."""
raise NotImplementedError(f"handler not implemented for object {type(formula)}")


@derived_predicates.register
def derived_predicates_true(
_formula: PropositionalTrue, _atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_true(_formula: PropositionalTrue) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a true formula."""
val = Predicate(add_val_prefix("true"))
return {DerivedPredicate(val, And())}


@derived_predicates.register
def derived_predicates_false(
_formula: PropositionalFalse, _atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_false(_formula: PropositionalFalse) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a false formula."""
val = Predicate(add_val_prefix("false"))
return {DerivedPredicate(val, Or())}


@derived_predicates.register
def derived_predicates_atomic(
formula: PLTLAtomic, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_atomic(formula: PLTLAtomic) -> Set[DerivedPredicate]:
"""Compute the derived predicate for an atomic formula."""
val = Predicate(add_val_prefix(formula.name))
condition = atoms_to_fluents[formula]
val = Predicate(add_val_prefix(replace_symbols(formula.name)))
condition = parse_ground_fluent(formula.name)
return {DerivedPredicate(val, condition)}


@derived_predicates.register
def derived_predicates_and(
formula: PLTLAnd, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_and(formula: PLTLAnd) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a PPLTL And formula."""
formula_name = to_string(formula)
val = Predicate(add_val_prefix(replace_symbols(formula_name)))
Expand All @@ -90,14 +80,12 @@ def derived_predicates_and(
for op in formula.operands
]
condition = And(*val_ops)
der_pred_ops = [derived_predicates(op, atoms_to_fluents) for op in formula.operands]
der_pred_ops = [derived_predicates(op) for op in formula.operands]
return {DerivedPredicate(val, condition)}.union(*der_pred_ops)


@derived_predicates.register
def derived_predicates_or(
formula: PLTLOr, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_or(formula: PLTLOr) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a PPLTL Or formula."""
formula_name = to_string(formula)
val = Predicate(add_val_prefix(replace_symbols(formula_name)))
Expand All @@ -106,45 +94,39 @@ def derived_predicates_or(
for op in formula.operands
]
condition = Or(*val_ops)
der_pred_ops = [derived_predicates(op, atoms_to_fluents) for op in formula.operands]
der_pred_ops = [derived_predicates(op) for op in formula.operands]
return {DerivedPredicate(val, condition)}.union(*der_pred_ops)


@derived_predicates.register
def derived_predicates_not(
formula: PLTLNot, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_not(formula: PLTLNot) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a PPLTL Not formula."""
formula_name = to_string(formula)
val = Predicate(add_val_prefix(replace_symbols(formula_name)))
condition = Not(
Predicate(add_val_prefix(replace_symbols(to_string(formula.argument))))
)
der_pred_arg = derived_predicates(formula.argument, atoms_to_fluents)
der_pred_arg = derived_predicates(formula.argument)
return {DerivedPredicate(val, condition)}.union(der_pred_arg)


@derived_predicates.register
def derived_predicates_before(
formula: Before, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_before(formula: Before) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a Before formula."""
formula_name = to_string(formula)
val = Predicate(add_val_prefix(replace_symbols(formula_name)))
condition = Predicate(replace_symbols(to_string(formula)))
der_pred_arg = derived_predicates(formula.argument, atoms_to_fluents)
der_pred_arg = derived_predicates(formula.argument)
return {DerivedPredicate(val, condition)}.union(der_pred_arg)


@derived_predicates.register
def derived_predicates_since(
formula: Since, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_since(formula: Since) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a Since formula."""
if len(formula.operands) != 2:
head = formula.operands[0]
tail = Since(*formula.operands[1:])
return derived_predicates(Since(head, tail), atoms_to_fluents)
return derived_predicates(Since(head, tail))
formula_name = to_string(formula)
val = Predicate(add_val_prefix(replace_symbols(formula_name)))
op_or_1 = Predicate(add_val_prefix(replace_symbols(to_string(formula.operands[1]))))
Expand All @@ -153,20 +135,18 @@ def derived_predicates_since(
Predicate(f"Y-{replace_symbols(to_string(formula))}"),
)
condition = Or(op_or_1, op_or_2)
der_pred_ops = [derived_predicates(op, atoms_to_fluents) for op in formula.operands]
der_pred_ops = [derived_predicates(op) for op in formula.operands]
return {DerivedPredicate(val, condition)}.union(*der_pred_ops)


@derived_predicates.register
def derived_predicates_once(
formula: Once, atoms_to_fluents: Dict[PLTLAtomic, Predicate]
) -> Set[DerivedPredicate]:
def derived_predicates_once(formula: Once) -> Set[DerivedPredicate]:
"""Compute the derived predicate for a Once formula."""
formula_name = to_string(formula)
val = Predicate(add_val_prefix(replace_symbols(formula_name)))
condition = Or(
Predicate(add_val_prefix(replace_symbols(to_string(formula.argument)))),
Predicate(f"Y-{replace_symbols(to_string(formula))}"),
)
der_pred_arg = derived_predicates(formula.argument, atoms_to_fluents)
der_pred_arg = derived_predicates(formula.argument)
return {DerivedPredicate(val, condition)}.union(der_pred_arg)
Loading

0 comments on commit bac485a

Please sign in to comment.