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

Fix/issues #35

Merged
merged 11 commits into from
Nov 9, 2022
14 changes: 10 additions & 4 deletions pddl/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@

from pddl.custom_types import name as name_type
from pddl.custom_types import namelike, to_names
from pddl.helpers.base import assert_, ensure, ensure_sequence, ensure_set
from pddl.helpers.base import (
assert_,
ensure,
ensure_sequence,
ensure_set,
_typed_parameters,
)
from pddl.logic.base import FalseFormula, Formula, TrueFormula, is_literal
from pddl.logic.predicates import DerivedPredicate, Predicate
from pddl.logic.terms import Constant, Variable
Expand Down Expand Up @@ -145,9 +151,9 @@ def __init__(
:param init: the initial condition.
:param goal: the goal condition.
"""
self._name: str = name_type(name)
self._name = name_type(name)
self._domain: Optional[Domain] = domain
self._domain_name = domain_name
self._domain_name = name_type(domain_name)
self._requirements: AbstractSet[Requirements] = ensure_set(requirements)
self._objects: AbstractSet[Constant] = ensure_set(objects)
self._init: AbstractSet[Formula] = ensure_set(init)
Expand Down Expand Up @@ -280,7 +286,7 @@ def effect(self) -> Optional[Formula]:
def __str__(self):
"""Get the string."""
operator_str = "(:action {0}\n".format(self.name)
operator_str += f" :parameters ({' '.join(map(str, self.parameters))})\n"
operator_str += f" :parameters ({_typed_parameters(self.parameters)})\n"
if self.precondition is not None:
operator_str += f" :precondition {str(self.precondition)}\n"
if self.effect is not None:
Expand Down
40 changes: 30 additions & 10 deletions pddl/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ def _sort_and_print_collection(
return ""


def _print_predicates_with_types(predicates: Collection):
result = ""
for p in predicates:
if p.arity == 0:
result += f"({p.name})"
else:
result += f"({p.name}"
for t in p.terms:
result += (
f" ?{t.name} - {' '.join(t.type_tags)}"
if t.type_tags
else f"?{t.name}"
)
result += ") "
result += " "
return result.strip()


def _print_objects_with_types(objects: Collection):
result = ""
for o in objects:
result += f"{o.name} - {' '.join(o.type_tags)}" if o.type_tags else f"{o.name}"
result += " "
return result.strip()


def domain_to_string(domain: Domain) -> str:
"""Print a PDDL domain object."""
result = f"(define (domain {domain.name})"
Expand All @@ -50,18 +76,12 @@ def domain_to_string(domain: Domain) -> str:
body += _sort_and_print_collection("(:requirements ", domain.requirements, ")\n")
body += _sort_and_print_collection("(:types ", domain.types, ")\n")
body += _sort_and_print_collection("(:constants ", domain.constants, ")\n")
body += _sort_and_print_collection("(:predicates ", domain.predicates, ")\n")
body += f"(:predicates {_print_predicates_with_types(domain.predicates)})\n"
body += _sort_and_print_collection(
"",
domain.derived_predicates,
"",
to_string=lambda obj: str(obj) + "\n",
"", domain.derived_predicates, "", to_string=lambda obj: str(obj) + "\n",
)
body += _sort_and_print_collection(
"",
domain.actions,
"",
to_string=lambda obj: str(obj) + "\n",
"", domain.actions, "", to_string=lambda obj: str(obj) + "\n",
)
result = result + "\n" + indent(body, indentation) + "\n)"
result = _remove_empty_lines(result)
Expand All @@ -74,7 +94,7 @@ def problem_to_string(problem: Problem) -> str:
body = f"(:domain {problem.domain_name})\n"
indentation = " " * 4
body += _sort_and_print_collection("(:requirements ", problem.requirements, ")\n")
body += _sort_and_print_collection("(:objects ", problem.objects, ")\n")
body += f"(:objects {_print_objects_with_types(problem.objects)})\n"
body += _sort_and_print_collection("(:init ", problem.init, ")\n")
body += f"{'(:goal ' + str(problem.goal) + ')'}\n" if problem.goal != TRUE else ""
result = result + "\n" + indent(body, indentation) + "\n)"
Expand Down
15 changes: 13 additions & 2 deletions pddl/helpers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def ensure_set(arg: Optional[Collection], immutable: bool = True) -> AbstractSet
:return: the same set, or an empty set if the arg was None.
"""
op = frozenset if immutable else set
return op(arg) if arg is not None and not isinstance(arg, op) else op()
return op(arg) if arg is not None else op()


def ensure_sequence(arg: Optional[Sequence], immutable: bool = True) -> Sequence:
Expand All @@ -67,7 +67,7 @@ def ensure_sequence(arg: Optional[Sequence], immutable: bool = True) -> Sequence
:return: the same list, or an empty list if the arg was None.
"""
op: Type = tuple if immutable else list
return op(arg) if arg is not None and not isinstance(arg, op) else op()
return op(arg) if arg is not None else op()


def safe_index(seq: Sequence, *args, **kwargs):
Expand All @@ -94,6 +94,17 @@ def find(seq: Sequence, condition: Callable[[Any], bool]) -> int:
return next((i for i, x in enumerate(seq) if condition(x)), -1)


def _typed_parameters(parameters) -> str:
"""Return a list of parameters along with types if available."""
result = ""
for p in parameters:
if p.type_tags:
result += f"?{p.name} - {' '.join(map(str, p.type_tags))} "
else:
result += str(p) + " "
return result


class RegexConstrainedString(str):
"""
A string that is constrained by a regex.
Expand Down
2 changes: 1 addition & 1 deletion pddl/parser/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def cond_effect(self, args):
"""Process the 'cond_effect' rule."""
if len(args) >= 3 and args[1] == Symbols.AND.value:
p_effects = args[2:-1]
return AndEffect(p_effects)
return And(*p_effects)
assert len(args) == 1
return args[0]

Expand Down
7 changes: 6 additions & 1 deletion pddl/parser/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,12 @@ def atomic_formula_name(self, args):
return EqualTo(obj1, obj2)
else:
name = args[1]
terms = [self._objects_by_name.get(str(name)) for name in args[2:-1]]
terms = [
Constant(str(_term_name))
if self._objects_by_name.get(str(_term_name)) is None
else self._objects_by_name.get(str(_term_name))
for _term_name in args[2:-1]
]
return Predicate(name, *terms)


Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
FIXTURES_PDDL_FILES = FIXTURES_DIR / "pddl_files"
BLOCKSWORLD_FILES = FIXTURES_PDDL_FILES / "blocksworld-ipc08"
TRIANGLE_FILES = FIXTURES_PDDL_FILES / "triangle-tireworld"
BLOCKSWORLD_FOND_FILES = FIXTURES_PDDL_FILES / "blocksworld_fond"

# TODO once missing features are supported, uncomment this
# DOMAIN_FILES = [
Expand All @@ -52,13 +53,15 @@
"acrobatics",
"beam-walk",
"blocksworld-ipc08",
"blocksworld_fond",
"doors",
# "earth_observation",
"elevators",
# "faults-ipc08",
# "first-responders-ipc08",
"islands",
"miner",
"rovers_fond",
"spiky-tireworld",
"tireworld",
"tireworld-truck",
Expand Down Expand Up @@ -101,5 +104,9 @@ def markdown_parser():
triangle_tireworld_domain,
triangle_tireworld_problem_01,
)
from tests.fixtures.code_objects.blocksworld_fond import ( # noqa: E402, F401
blocksworld_fond_domain,
blocksworld_fond_01,
)

#################################################
120 changes: 120 additions & 0 deletions tests/fixtures/code_objects/blocksworld_fond.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
#
# Copyright 2021 WhiteMech
#
# ------------------------------
#
# This file is part of pddl.
#
# pddl is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pddl is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pddl. If not, see <https://www.gnu.org/licenses/>.
#

"""This test module contains the fixtures for 'blocksworld-ipc08' domain and problem."""
import pytest

from pddl.core import Action, Domain, Problem, Requirements
from pddl.logic import Constant
from pddl.logic.base import And, OneOf
from pddl.logic.effects import When, AndEffect
from pddl.logic.helpers import constants, variables
from pddl.logic.predicates import EqualTo, Predicate


@pytest.fixture(scope="session")
def blocksworld_fond_domain():
"""The 'blocksworld' FOND domain."""
# terms
x, y, z, b= variables("x y z b")

# constants:
table = Constant("Table")

# predicates
on = Predicate("on", x, y)
clear = Predicate("clear", x)
block = Predicate("block", b)
predicates = {on, clear, block}

# actions
# put-on
put_on_name = "puton"
put_on_parameters = [x, y, z]
put_on_precondition = on(x, z) & clear(x) & clear(y) & ~EqualTo(y, z) & ~EqualTo(x, z) & ~EqualTo(x, y) & ~EqualTo(
x, table)
put_on_effect = OneOf(
AndEffect(on(x, y), ~on(x, z),
When(~EqualTo(z, table), clear(z)), When(~EqualTo(y, table), ~clear(y))),
AndEffect(on(x, table),
When(~EqualTo(z, table), ~on(x, z) & clear(z)), When(~EqualTo(y, table), ~clear(y))),
)
put_on = Action(put_on_name, put_on_parameters, put_on_precondition, put_on_effect)

name = "blocks-world-domain"
requirements = {
Requirements.STRIPS,
Requirements.EQUALITY,
Requirements.NON_DETERMINISTIC,
Requirements.CONDITIONAL_EFFECTS
}
actions = {
put_on
}
domain = Domain(
name=name,
requirements=requirements,
constants={table},
predicates=predicates,
actions=actions,
)
return domain


@pytest.fixture(scope="session")
def blocksworld_fond_01():
"""Blocksworld FOND problem 01."""
# objects
objects = [A, B, C] = constants("A B C")

Table = Constant("Table")

# predicates
block = Predicate("block", A)
on = Predicate("on", C, A)
clear = Predicate("clear", B)

init = {
block(A),
block(B),
block(C),
block(Table),
on(C, A),
on(A, Table),
on(B, Table),
clear(C),
clear(B),
clear(Table)
}

goal = (And())

problem_name = "sussman-anomaly"

problem = Problem(
problem_name,
domain_name="blocks-world-domain",
objects=objects,
init=init,
goal=goal,
)
return problem
21 changes: 11 additions & 10 deletions tests/fixtures/code_objects/blocksworld_ipc08.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from pddl.core import Action, Domain, Problem, Requirements
from pddl.logic.base import And, OneOf
from pddl.logic.effects import AndEffect
from pddl.logic.helpers import constants, variables
from pddl.logic.predicates import EqualTo, Predicate

Expand Down Expand Up @@ -55,8 +56,8 @@ def blocksworld_domain():
pick_up_parameters = [b1, b2]
pick_up_precondition = ~EqualTo(b1, b2) & emptyhand & clear(b1) & on(b1, b2)
pick_up_effect = OneOf(
holding(b1) & clear(b2) & ~emptyhand & ~clear(b1) & ~on(b1, b2),
clear(b2) & on_table(b1) & ~on(b1, b2),
AndEffect(holding(b1), clear(b2), ~emptyhand, ~clear(b1), ~on(b1, b2)),
AndEffect(clear(b2), on_table(b1), ~on(b1, b2)),
)
pick_up = Action(
pick_up_name, pick_up_parameters, pick_up_precondition, pick_up_effect
Expand All @@ -66,7 +67,7 @@ def blocksworld_domain():
pick_up_from_table_name = "pick-up-from-table"
pick_up_from_table_parameters = [b]
pick_up_from_table_precondition = emptyhand & clear(b) & on_table(b)
pick_up_from_table_effect = OneOf(And(), holding(b) & ~emptyhand & ~on_table(b))
pick_up_from_table_effect = OneOf(AndEffect(), AndEffect(holding(b), ~emptyhand, ~on_table(b)))
pick_up_from_table = Action(
pick_up_from_table_name,
pick_up_from_table_parameters,
Expand All @@ -79,8 +80,8 @@ def blocksworld_domain():
put_on_block_parameters = [b1, b2]
put_on_block_precondition = holding(b1) & clear(b2)
put_on_block_effect = OneOf(
on(b1, b2) & emptyhand & clear(b1) & ~holding(b1) & ~clear(b2),
on_table(b1) & emptyhand & clear(b1) & ~holding(b1),
AndEffect(on(b1, b2), emptyhand, clear(b1), ~holding(b1), ~clear(b2)),
AndEffect(on_table(b1), emptyhand, clear(b1), ~holding(b1)),
)
put_on_block = Action(
put_on_block_name,
Expand All @@ -93,15 +94,15 @@ def blocksworld_domain():
put_down_name = "put-down"
put_down_parameters = [b]
put_down_precondition = holding(b)
put_down_effect = on_table(b) & emptyhand & clear(b) & ~holding(b)
put_down_effect = AndEffect(on_table(b), emptyhand, clear(b), ~holding(b))
put_down = Action(
put_down_name, put_down_parameters, put_down_precondition, put_down_effect
)
# pick-tower
pick_tower_name = "pick-tower"
pick_tower_parameters = [b1, b2, b3]
pick_tower_precondition = emptyhand & on(b1, b2) & on(b2, b3)
pick_tower_effect = OneOf(And(), holding(b2) & clear(b3) & ~emptyhand & ~on(b2, b3))
pick_tower_effect = OneOf(AndEffect(), AndEffect(holding(b2), clear(b3), ~emptyhand, ~on(b2, b3)))
pick_tower = Action(
pick_tower_name,
pick_tower_parameters,
Expand All @@ -114,8 +115,8 @@ def blocksworld_domain():
put_tower_on_block_parameters = [b1, b2, b3]
put_tower_on_block_precondition = holding(b2) & on(b1, b2) & clear(b3)
put_tower_on_block_effect = OneOf(
on(b2, b3) & emptyhand & ~holding(b2) & ~clear(b3),
on_table(b2) & emptyhand & ~holding(b2),
AndEffect(on(b2, b3), emptyhand, ~holding(b2), ~clear(b3)),
AndEffect(on_table(b2), emptyhand, ~holding(b2)),
)
put_tower_on_block = Action(
put_tower_on_block_name,
Expand All @@ -128,7 +129,7 @@ def blocksworld_domain():
put_tower_down_name = "put-tower-down"
put_tower_down_parameters = [b1, b2]
put_tower_down_precondition = holding(b2) & on(b1, b2)
put_tower_down_effect = on_table(b2) & emptyhand & ~holding(b2)
put_tower_down_effect = AndEffect(on_table(b2), emptyhand, ~holding(b2))
put_tower_down = Action(
put_tower_down_name,
put_tower_down_parameters,
Expand Down
Loading