Skip to content

Commit

Permalink
temp: work in progress: builder pattern for validation
Browse files Browse the repository at this point in the history
  • Loading branch information
marcofavorito committed Jul 2, 2023
1 parent 6ba0257 commit 486fe21
Show file tree
Hide file tree
Showing 17 changed files with 796 additions and 407 deletions.
2 changes: 1 addition & 1 deletion pddl/_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pddl.action import Action
from pddl.custom_types import name as name_type
from pddl.custom_types import namelike, to_names, to_types # noqa: F401
from pddl.definitions.base import TypesDef
from pddl.definitions.types_def import TypesDef
from pddl.exceptions import PDDLValidationError
from pddl.helpers.base import check, ensure_set
from pddl.logic import Predicate
Expand Down
2 changes: 1 addition & 1 deletion pddl/validation/__init__.py → pddl/builders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
# https://opensource.org/licenses/MIT.
#

"""This package includes validation functions of PDDL domains/problems."""
"""This package includes builder classes for PDDL domains and problems."""
171 changes: 171 additions & 0 deletions pddl/builders/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#
# Copyright 2021-2023 WhiteMech
#
# ------------------------------
#
# This file is part of pddl.
#
# Use of this source code is governed by an MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.
#

"""This module includes the base classes for the PDDL builders."""

from abc import ABC, abstractmethod
from typing import AbstractSet, Generic, Type, TypeVar, Set, Optional, Dict, Callable

from pddl.builders.types_def import TypesDef, MutableTypesDef
from pddl.core import Domain, Problem
from pddl.custom_types import namelike
from pddl.exceptions import PDDLValidationError
from pddl.helpers.base import assert_
from pddl.logic.terms import Term
from pddl.requirements import Requirements

T = TypeVar("T", Domain, Problem)


class BaseBuilder(ABC, Generic[T]):
"""A base class for the PDDL builders."""

@abstractmethod
def build(self) -> T:
"""Build the PDDL object."""


class _NoDuplicateList(list):
"""A list that does not allow duplicates."""

def __init__(
self, item_name: str, exception_cls: Type[Exception] = PDDLValidationError
) -> None:
"""Initialize the list."""
super().__init__()
self.__item_name = item_name
self.__exception_cls = exception_cls
# this is for O(1) lookup
self.__elements = set()

def append(self, item) -> None:
"""Append an item to the list."""
if item in self.__elements:
raise PDDLValidationError(f"duplicate {self.__item_name}: '{item}'")
super().append(item)
self.__elements.add(item)

def extend(self, iterable) -> None:
"""Extend the list with an iterable."""
for item in iterable:
self.append(item)

def __contains__(self, item):
"""Check if the list contains an item."""
return item in self.__elements

def get_set(self) -> AbstractSet:
"""Get the set of elements."""
return self.__elements


class _Context:
"""A context for the PDDL builders."""

def __init__(self) -> None:
"""Initialize the context."""
self.__requirements: _NoDuplicateList = _NoDuplicateList("requirement")
self.__types_def: MutableTypesDef = MutableTypesDef()

self.__used_names: Dict[namelike, object] = {}

@property
def requirements(self) -> AbstractSet[Requirements]:
"""Get the requirements."""
return self.__requirements.get_set()

@property
def has_typing(self) -> bool:
"""Check if the typing requirement is specified."""
return Requirements.TYPING in self.requirements

@property
def types_def(self) -> MutableTypesDef:
"""Get the types definition."""
return self.__types_def

def add_requirement(self, requirement: Requirements) -> None:
"""Add a requirement to the domain."""
self.__requirements.append(requirement)

def add_type(
self, child_type: namelike, parent_type: Optional[namelike] = None
) -> None:
"""Add a type to the domain."""
self.check_name_not_already_used(child_type, "type")
self.check_name_not_already_used(parent_type, "type") if parent_type is not None else None
self.check_typing_requirement_for_types(child_type, parent_type)

self.__types_def.add_type(child_type, parent_type)

self.add_used_name(child_type, "type")
self.add_used_name(parent_type, "type") if parent_type is not None else None

def add_used_name(self, name: namelike, obj: object) -> None:
"""Add a name to the used names."""
self.__used_names[name] = obj

def get_used_name(self, name: namelike) -> Optional[object]:
"""Add a name to the used names."""
return self.__used_names.get(name)

def check_typing_requirement_for_types(
self, child_type: namelike, parent_type: Optional[namelike] = None
) -> None:
"""Check that the typing requirement is specified."""
if not self.has_typing:
raise PDDLValidationError(
f"typing requirement is not specified, but the following types were used: {child_type}"
+ (f" -> {parent_type}" if parent_type else "")
)

def check_name_not_already_used(self, new_name: namelike, new_object: object) -> None:
"""Check that the name is not already used."""
if new_name in self.__used_names:
raise PDDLValidationError(
f"name '{new_name}' of object '{new_object}' is already used for '{self.__used_names[new_name]}'"
)

def check_types_are_available(self, term: Term) -> None:
"""Check that the types of a term are available in the domain."""
if not self.types_def.are_types_available(term.type_tags):
raise PDDLValidationError(
f"types {sorted(term.type_tags)} of term '{term}' are not in available types {self.types_def.sorted_all_types}"
)


class _Definition:
"""Abstract class for a PDDL definition."""

def __init__(
self, context: _Context
) -> None:
"""Initialize the PDDL definition."""
assert_(type(self) is not _Definition)
self.__context = context

@property
def _context(self) -> _Context:
"""Get the context."""
return self.__context

@property
def has_typing(self) -> bool:
"""Check if the typing requirement is specified."""
return self.__context.has_typing

def _check_typing_requirement_for_term(self, term: Term) -> None:
"""Check that the typing requirement is specified for a term."""
if not self.has_typing and len(term.type_tags) > 0:
raise PDDLValidationError(
f"typing requirement is not specified, but the following types for term '{term}' were used: {term.type_tags}"
)
40 changes: 40 additions & 0 deletions pddl/builders/constants_def.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# Copyright 2021-2023 WhiteMech
#
# ------------------------------
#
# This file is part of pddl.
#
# Use of this source code is governed by an MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.
#

"""This module implements the ConstantsDef class to handle the constants of a PDDL domain."""
from typing import AbstractSet, Sequence, cast

from pddl.builders.base import _Definition, _Context
from pddl.builders.terms_list import TermsValidator
from pddl.logic import Constant


class ConstantsDef(_Definition):
"""A set of constants of a PDDL domain."""

def __init__(self, context: _Context) -> None:
"""Initialize the PDDL constants section validator."""
super().__init__(context)
self._terms_validator = TermsValidator(
no_duplicates=True, must_be_instances_of=Constant
)

def add_constant(self, constant: Constant) -> None:
"""Add a constant."""
self._check_typing_requirement_for_term(constant)
self._context.check_types_are_available(constant)
self._terms_validator.add_term(constant)

@property
def constants(self) -> AbstractSet[Constant]:
"""Get the constants."""
return frozenset(cast(Sequence[Constant], self._terms_validator.terms))
67 changes: 67 additions & 0 deletions pddl/builders/derived_predicates_def.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#
# Copyright 2021-2023 WhiteMech
#
# ------------------------------
#
# This file is part of pddl.
#
# Use of this source code is governed by an MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.
#

"""This module implements the DerivedPredicatesDef class to handle derivate predicate definitions of a PDDL domain."""
from typing import AbstractSet, Set

from pddl.builders.base import _Definition, _Context
from pddl.logic.predicates import DerivedPredicate


class DerivedPredicatesDef(_Definition):
"""A set of derived predicates of a PDDL domain."""

def __init__(
self,
context: _Context,
) -> None:
"""Initialize the PDDL constants section validator."""
super().__init__(context)

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

@property
def derived_predicates(self) -> AbstractSet[DerivedPredicate]:
"""Get the predicates."""
return self._derived_predicates

def _check_consistency(self) -> None:
"""Check consistency of the derived predicates definition."""

seen_predicates_by_name: Dict[name_type, Predicate] = {
p.name: p for p in self._predicates_def.predicates
}
for dp in self._derived_predicates:
self._check_derived_predicate(dp, seen_predicates_by_name)

def _check_derived_predicate(
self, dp: DerivedPredicate, seen: Dict[name_type, Predicate]
) -> None:
if dp.predicate.name in seen:
other_p = seen[dp.predicate.name]
raise PDDLValidationError(
f"the name of derived predicate {dp} has been already used by {other_p}"
)
seen[dp.predicate.name] = dp.predicate
TermsChecker(self._requirements, self._types, check_repetitions=False).check(
dp.predicate.terms
)
variables = {t for t in dp.predicate.terms}
FormulaChecker(
self._requirements,
self._types,
self._constants_def.constants,
variables,
self._predicates_def.predicates,
self._derived_predicates,
).check_formula(dp.condition)
73 changes: 73 additions & 0 deletions pddl/builders/domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import Optional, AbstractSet

from pddl.builders.base import BaseBuilder, _Context
from pddl.builders.constants_def import ConstantsDef
from pddl.builders.derived_predicates_def import DerivedPredicatesDef
from pddl.builders.predicates_def import PredicatesDef
from pddl.custom_types import namelike
from pddl.logic import Constant, Predicate
from pddl.requirements import Requirements


class DomainBuilder(BaseBuilder):
"""A builder for PDDL domains."""

def __init__(self, name: str):
"""Initialize the domain builder."""
self.__name = name
self.__context = _Context()
self.__constants_def: ConstantsDef = ConstantsDef(self.__context)
self.__predicates_def: PredicatesDef = PredicatesDef(self.__context)
self.__derived_predicates_def: DerivedPredicatesDef = DerivedPredicatesDef(self.__context)

@property
def requirements(self) -> AbstractSet[Requirements]:
"""Get the requirements."""
return self.__context.requirements

@property
def has_typing(self) -> bool:
"""Check if the typing requirement is specified."""
return self.__context.has_typing

def add_requirement(self, requirement: Requirements) -> "DomainBuilder":
"""Add a requirement to the domain."""
self.__context.add_requirement(requirement)
return self

def add_type(
self, child_type: namelike, parent_type: Optional[namelike] = None
) -> "DomainBuilder":
"""Add a type to the domain."""
self.__context.add_type(child_type, parent_type)
return self

def add_constant(self, constant: Constant) -> "DomainBuilder":
"""Add a constant to the domain."""
self.__constants_def.add_constant(constant)
return self

def add_predicate_def(self, predicate_def: Predicate) -> "DomainBuilder":
"""
Add a predicate definition to the domain.
The predicate definition must be a predicate with only variables.
"""
self.__predicates_def.add_predicate_def(predicate_def)
return self

def build(self) -> "T":
pass

# def build(self) -> Domain:
# """Build the domain."""
# return Domain(
# name=self.__name,
# requirements=self.__requirements,
# types=self.types,
# constants=self.__constants,
# predicates=self.predicates,
# functions=self.functions,
# actions=self.actions,
# axioms=self.axioms,
# )
Loading

0 comments on commit 486fe21

Please sign in to comment.