Skip to content

Commit

Permalink
fix formatting of typed lists
Browse files Browse the repository at this point in the history
- types are now printed correctly in the :constants section
- in the :objects section, the objects are grouped by type (as in constants)
- minor fixes in how :init and :goal are printed (they are always printed even if empty).

The code changes added here are to be considered temporary, since a refactoring of the formatting module is required. Nevertheless, the printing of the typing information of constants will remain.
  • Loading branch information
marcofavorito committed Jun 13, 2023
1 parent f5858b3 commit edcc295
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 28 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ that gives:
(define (domain my_domain)
(:requirements :strips :typing)
(:types type_1)
(:constants a b c)
(:constants a b c - type_1)
(:predicates (p1 ?x - type_1 ?y - type_1 ?z - type_1) (p2 ?x - type_1 ?y - type_1))
(:action action-1
:parameters (?x - type_1 ?y - type_1 ?z - type_1)
Expand Down Expand Up @@ -148,7 +148,7 @@ Output:
(define (problem problem-1)
(:domain my_domain)
(:requirements :strips :typing)
(:objects a - type_1 b - type_1 c - type_1)
(:objects a b c - type_1)
(:init (not (p2 b c)) (p1 a b c))
(:goal (p2 b c))
)
Expand Down
99 changes: 73 additions & 26 deletions pddl/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@

"""Formatting utilities for PDDL domains and problems."""
from textwrap import indent
from typing import Callable, Collection, Dict, Optional
from typing import Callable, Collection, Dict, List, Optional

from pddl.core import Domain, Problem
from pddl.custom_types import name
from pddl.logic.base import TRUE
from pddl.logic.terms import Constant


def _remove_empty_lines(s: str) -> str:
Expand All @@ -25,12 +26,41 @@ def _remove_empty_lines(s: str) -> str:


def _sort_and_print_collection(
prefix, collection: Collection, postfix, to_string: Callable = str
prefix,
collection: Collection,
postfix,
to_string: Callable = str,
is_mandatory: bool = False,
):
if len(collection) > 0:
return prefix + " ".join(sorted(map(to_string, collection))) + postfix
else:
return ""
elif is_mandatory:
return prefix + postfix
return ""


def _print_types_with_parents(
prefix: str,
types_dict: Dict[name, Optional[name]],
postfix: str,
to_string: Callable = str,
):
"""Print the type dictionary of a PDDL domain."""
name_by_type: Dict[Optional[name], List[name]] = {}
for type_name, parent_type in types_dict.items():
name_by_type.setdefault(parent_type, []).append(type_name)
return _print_typed_lists(prefix, name_by_type, postfix, to_string)


def _print_constants(
prefix, constants: Collection[Constant], postfix, to_string: Callable = str
):
"""Print constants in a PDDL domain."""
term_by_type_tags: Dict[Optional[name], List[name]] = {}
for c in constants:
term_by_type_tags.setdefault(c.type_tag, []).append(c.name)

return _print_typed_lists(prefix, term_by_type_tags, postfix, to_string)


def _print_predicates_with_types(predicates: Collection):
Expand All @@ -54,26 +84,37 @@ def _print_predicates_with_types(predicates: Collection):
return result.strip()


def _print_types_with_parents(types: Dict[name, Optional[name]]):
"""
Print types with parent types..
def _print_typed_lists(
prefix,
names_by_type: Dict[Optional[name], List[name]],
postfix,
to_string: Callable = str,
):
"""Print typed lists."""
result = prefix + " "

:param types: the type definition in dict format.
:return: the domain types definition in string format.
"""
result = ""
for t in sorted(types.keys()):
result += f"{t} - {types[t]}" if types[t] else f"{t}"
result += " "
return result.strip()
# names with no type will be printed at the end
names_with_none_types = names_by_type.pop(None, [])

# print typed constants, first sorted by type, then by constant name
for type_tag, typed_names in sorted(
names_by_type.items(), key=lambda type_and_name: type_and_name[0] # type: ignore
):
result += (
" ".join(sorted(to_string(n) for n in typed_names)) + " - " + type_tag + " " # type: ignore
)

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

# print constants with no type
result += " ".join(sorted(to_string(n) for n in names_with_none_types))

if result == prefix + " ":
result = result[:-1]
result += postfix

return result


def domain_to_string(domain: Domain) -> str:
Expand All @@ -82,8 +123,8 @@ def domain_to_string(domain: Domain) -> str:
body = ""
indentation = " " * 4
body += _sort_and_print_collection("(:requirements ", domain.requirements, ")\n")
body += f"(:types {_print_types_with_parents(domain.types)})\n"
body += _sort_and_print_collection("(:constants ", domain.constants, ")\n")
body += _print_types_with_parents("(:types", domain.types, ")\n")
body += _print_constants("(:constants", domain.constants, ")\n")
body += f"(:predicates {_print_predicates_with_types(domain.predicates)})\n"
body += _sort_and_print_collection(
"",
Expand All @@ -108,9 +149,15 @@ 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 += 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 ""
body += _print_constants("(:objects", problem.objects, ")\n")
body += _sort_and_print_collection(
"(:init ", problem.init, ")\n", is_mandatory=True
)
body += (
f"{'(:goal ' + str(problem.goal) + ')'}\n"
if problem.goal != TRUE
else "(:goal (and))\n"
)
result = result + "\n" + indent(body, indentation) + "\n)"
result = _remove_empty_lines(result)
return result
63 changes: 63 additions & 0 deletions tests/test_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@

"""This module contains tests verifying that the formatter outputs are syntactically valid and semantically faithful."""
from pathlib import Path
from textwrap import dedent

import pytest

from pddl.core import Domain, Problem
from pddl.formatter import domain_to_string, problem_to_string
from pddl.logic import constants
from pddl.requirements import Requirements
from tests.conftest import DOMAIN_FILES, PROBLEM_FILES


Expand All @@ -36,3 +40,62 @@ def test_problem_formatter(problem_parser, pddl_file):
actual_problem_str = problem_to_string(expected_problem_obj)
actual_problem_obj = problem_parser(actual_problem_str)
assert actual_problem_obj == expected_problem_obj


def test_typed_constants_formatting_in_domain() -> None:
"""Test that types and typed constants are formatted correctly."""
t1, t2, t3 = "type_1", "type_2", "type_3"

a1, b1, c1 = constants("a b c", type_=t1)
d2, e2, f2 = constants("d e f", type_=t2)
g3, h3, i3 = constants("g h i", type_=t3)
j, k, lc = constants("j k l", type_=None)

# define the domain object.
domain = Domain(
"my_domain",
requirements=[Requirements.TYPING],
types={t1: None, t2: t1, t3: t1},
constants=[a1, b1, c1, d2, e2, f2, g3, h3, i3, j, k, lc],
)

domain_str = domain_to_string(domain)

assert domain_str == dedent(
"""\
(define (domain my_domain)
(:requirements :typing)
(:types type_2 type_3 - type_1 type_1)
(:constants a b c - type_1 d e f - type_2 g h i - type_3 j k l)
(:predicates )
)"""
)


def test_typed_objects_formatting_in_problem() -> None:
"""Test that typed objects are formatted correctly."""
t1, t2, t3 = "type_1", "type_2", "type_3"

a1, b1, c1 = constants("a b c", type_=t1)
d2, e2, f2 = constants("d e f", type_=t2)
g3, h3, i3 = constants("g h i", type_=t3)
j, k, lc = constants("j k l", type_=None)

problem = Problem(
"problem-1",
domain_name="my_domain",
requirements=[Requirements.TYPING],
objects=[a1, b1, c1, d2, e2, f2, g3, h3, i3, j, k, lc],
)
problem_str = problem_to_string(problem)

assert problem_str == dedent(
"""\
(define (problem problem-1)
(:domain my_domain)
(:requirements :typing)
(:objects a b c - type_1 d e f - type_2 g h i - type_3 j k l)
(:init )
(:goal (and))
)"""
)

0 comments on commit edcc295

Please sign in to comment.