Skip to content

Commit

Permalink
feat: TermsValidator can check absence of duplicates, used for consta…
Browse files Browse the repository at this point in the history
…nts def
  • Loading branch information
marcofavorito committed Jul 1, 2023
1 parent 8743f7b commit 6ba0257
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 11 deletions.
4 changes: 3 additions & 1 deletion pddl/definitions/constants_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def __init__(
constants: Optional[Collection[Constant]],
) -> None:
"""Initialize the PDDL constants section validator."""
TermsValidator(requirements, types).check_terms(constants if constants else [])
TermsValidator(requirements, types, no_duplicates=True).check_terms(
constants if constants else []
)

super().__init__(requirements, types)
self._constants = ensure_set(constants)
Expand Down
38 changes: 31 additions & 7 deletions pddl/validation/terms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@

"""Module for validator of terms."""
from functools import partial
from typing import AbstractSet, Collection, Dict, Generator, Optional, Set, Type, Union
from typing import (
AbstractSet,
Collection,
Dict,
Generator,
Mapping,
Optional,
Type,
Union,
)

from pddl.custom_types import name as name_type
from pddl.definitions.base import TypesDef
Expand All @@ -35,12 +44,14 @@ def __init__(
requirements: AbstractSet[Requirements],
types: TypesDef,
must_be_instances_of: Optional[Union[Type[Constant], Type[Variable]]] = None,
no_duplicates: bool = False,
):
"""Initialize the validator."""
super().__init__(requirements, types)

# if none, then we don't care if constant or variable
self._allowed_superclass = must_be_instances_of
self._no_duplicates = no_duplicates

def check_terms_consistency(self, terms: Collection[Term]):
"""
Expand All @@ -58,28 +69,41 @@ def _check_terms_consistency_iterator(
Iterate over terms and check that terms with the same name must have the same type tags.
In particular:
- if no_duplicates=Term there cannot be terms with the same name (variable or constant);
- terms with the same name must be of the same term type (variable or constant);
- terms with the same name must have the same type tags.
"""
seen: Dict[name_type, Set[name_type]] = {}
seen: Dict[name_type, Term] = {}
for term in terms:
self._check_already_seen_term(term, seen)
self._check_same_term_has_same_type_tags(term, seen)
self._check_term_type(term, term_type=self._allowed_superclass)
yield term
seen[term.name] = term

def _check_already_seen_term(self, term: Term, seen: Mapping[name_type, Term]):
"""Check whether a term has been already seen earlier in the terms list."""
if self._no_duplicates and term.name in seen:
same_name_but_different_type = type(term) is not type( # noqa: E721
seen[term.name]
)
check(
same_name_but_different_type,
f"Term '{term}' occurred twice in the same list of terms",
exception_cls=PDDLValidationError,
)

@classmethod
def _check_same_term_has_same_type_tags(
cls, term: Term, seen: Dict[name_type, Set[name_type]]
cls, term: Term, seen: Dict[name_type, Term]
) -> None:
"""
Check if the term has already been seen and, if so, that it has the same type tags.
This is an auxiliary method to simplify the implementation of '_check_terms_consistency_iterator'.
"""
if term.name not in seen:
seen[term.name] = set(term.type_tags)
else:
expected_type_tags = seen[term.name]
if term.name in seen:
expected_type_tags = seen[term.name].type_tags
actual_type_tags = set(term.type_tags)
check(
expected_type_tags == actual_type_tags,
Expand Down
6 changes: 3 additions & 3 deletions tests/test_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ def test_constants_type_not_available() -> None:
Domain("test", requirements={Requirements.TYPING}, constants={a}, types=type_set) # type: ignore


def test_constants_duplicates_with_different_types() -> None:
"""Test that when two constants have same name but different types we raise error."""
def test_constants_duplicates() -> None:
"""Test that when two constants have same name we raise error."""
a1 = Constant("a", type_tag="t1")
a2 = Constant("a", type_tag="t2")

type_set = {"t1": None, "t2": None}

with pytest.raises(
PDDLValidationError,
match=r"Term a occurred twice with different type tags: previous type tags \['t1'\], new type tags \['t2'\]",
match=r"Term 'a' occurred twice in the same list of terms",
):
Domain("test", requirements={Requirements.TYPING}, constants=[a1, a2], types=type_set) # type: ignore

Expand Down

0 comments on commit 6ba0257

Please sign in to comment.