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

Fixes and improvements in the parsing of typed lists #82

Merged
merged 51 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e3a249e
fix: remove constants.py module and use pddl/parser/symbols.py
marcofavorito Jun 6, 2023
658dc62
fix: add missing __invert__ method for True/FalseFormula classes
marcofavorito Jun 7, 2023
d7d0534
test: fix blocksworld_fond/p01.pddl goal
marcofavorito Jun 7, 2023
d5b509c
chore: add 'check' function to generalize assert_ for any exception type
marcofavorito Jun 7, 2023
3c53e3f
feat: add TypesIndex class
marcofavorito Jun 7, 2023
3cf8cb4
feat: changing domain parser behaviour on typed_list_name (backward c…
marcofavorito Jun 7, 2023
cf25df5
test: add problem parsing in test_formatter.py
marcofavorito Jun 7, 2023
ccb5097
test: fix expected error messages when duplicated names/types occur
marcofavorito Jun 7, 2023
7a7836c
fix: minor fix to object-supertypes-error
marcofavorito Jun 7, 2023
f1e3316
fix: use 'name' type to validate tokens of typed list
marcofavorito Jun 7, 2023
48ffa56
chore: sort Symbols in alphanumerical order
marcofavorito Jun 7, 2023
976f481
fix: add keyword validation for typed list names
marcofavorito Jun 7, 2023
889b3fc
fix: renaming variables to avoid shadowing from outer scope
marcofavorito Jun 7, 2023
73e4fac
feat: use TypesIndex to parse and validate typed_list_variables
marcofavorito Jun 7, 2023
d2dded9
test: split domain parser tests from problem parser tests
marcofavorito Jun 7, 2023
67d3b1d
docs: fix types argument passed to Domain in README
marcofavorito Jun 7, 2023
124a60a
fix: problem optionally accepts requirements
marcofavorito Jun 7, 2023
03abf7d
lint: fix vulture's whitelist
marcofavorito Jun 7, 2023
04fe2f8
feat: add internal class to manage and handle types
marcofavorito Jun 7, 2023
1a35939
refactor: more Requirements to its own module pddl.requirements
marcofavorito Jun 7, 2023
c3ab1aa
chore: move _Types in pddl._validation
marcofavorito Jun 7, 2023
35f333b
chore: return 'name' instead of str in domain/problem names property …
marcofavorito Jun 7, 2023
529d35d
chore: used 'validate' instead of 'assert_'
marcofavorito Jun 7, 2023
0d264a8
chore: add Problem.check method stub
marcofavorito Jun 7, 2023
03e632b
chore: change the way requirements are set in a Problem object
marcofavorito Jun 7, 2023
f232127
fix: update domain setter of Problem
marcofavorito Jun 7, 2023
1586db1
feat: add TypeChecker utility class and use it for checking (both dom…
marcofavorito Jun 7, 2023
e879efb
fix: improve Problem initialize regarding requirements and domain_nam…
marcofavorito Jun 7, 2023
771c6fc
test: refactor parametrized tests
marcofavorito Jun 7, 2023
2d35e8c
build: bump minimum Python interpreter supported to 3.8
marcofavorito Jun 7, 2023
190a7c5
fix: minor fixes to requirements handling in core module
marcofavorito Jun 7, 2023
4376e54
feat: add validation of domain/problem predicates
marcofavorito Jun 7, 2023
a511022
feat: add type validation of domain actions
marcofavorito Jun 7, 2023
3d02538
refactor: move Action class in its own module core.action
marcofavorito Jun 7, 2023
617cfd7
test: fix validation exception message
marcofavorito Jun 7, 2023
bdddfbf
feat: add keyword check
marcofavorito Jun 7, 2023
7b3e7ad
test: add tests on wrong variable typed lists
marcofavorito Jun 7, 2023
a12e34b
fix: move keyword validation inside class contructors
marcofavorito Jun 8, 2023
f0e79af
fix: make find_cycle to handle arbitrary graphs
marcofavorito Jun 9, 2023
8c41830
chore: rename TypesIndex to TypedListParser
marcofavorito Jun 9, 2023
eb2429d
feat: allow repetition in variable list
marcofavorito Jun 9, 2023
9a16858
test: improve 'test_variables_repetition_allowed_if_same_type'
marcofavorito Jun 9, 2023
a6367ba
Update .github/workflows/docs.yml
marcofavorito Jun 9, 2023
2c67fde
feat: add validation of no-duplicates in type-tags of a Term
marcofavorito Jun 9, 2023
b8e0a2b
feat: make Term non-instantiatable
marcofavorito Jun 9, 2023
3661f64
feat: check terms consistency wrt type tag
marcofavorito Jun 9, 2023
924c88c
feat: add term type checks in EqualTo class
marcofavorito Jun 9, 2023
f7996d1
test: add more tests for problem parsing
marcofavorito Jun 9, 2023
f5858b3
ci: change GH action version from master to main
marcofavorito Jun 9, 2023
edcc295
fix formatting of typed lists
marcofavorito Jun 13, 2023
97b379f
fix: update error message in case a name inherits from multiple types
marcofavorito Jun 13, 2023
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
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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, earlier we did not consider types with no parents, and we printed type_1 in the first position. However, this is not correct wrt PDDL syntax; typed names with no type should be at the end of the list, o/w there is no way the parsing rules can spot them:
image

(:constants a b c - type_1 d e f - type_2 g h i - type_3 j k l)
Copy link
Member Author

@marcofavorito marcofavorito Jun 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same applies here: first group by type, print groups by type name in alphanumerical order, and then print constants sorted within the group; however, constants with no type must be at the end.

(: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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same approach used for constants applies here.

(:init )
(:goal (and))
Comment on lines +98 to +99
Copy link
Member Author

@marcofavorito marcofavorito Jun 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should decide what to do with empty init and goal sections. The PDDL grammar specifies these sections are mandatory. We will tackle this in a next PR, taking into account formatter refactoring #72

)"""
)
Loading