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 0ca7150
Show file tree
Hide file tree
Showing 13 changed files with 513 additions and 342 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
13 changes: 13 additions & 0 deletions pddl/builders/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#
# 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 package includes builder classes for PDDL domains and problems."""
162 changes: 162 additions & 0 deletions pddl/builders/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#
# 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_already_used(child_type, "type")
self._check_name_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 _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_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]}'"
)


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 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}"
)

def _check_types_are_available(self, term: Term) -> None:
"""Check that the types of a term are available in the domain."""
if not self.__context.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.__context.types_def.sorted_all_types}"
)
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._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))
76 changes: 76 additions & 0 deletions pddl/builders/domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from typing import Callable, Collection, List, Optional, Dict, AbstractSet

from pddl.builders.base import BaseBuilder, _NoDuplicateList, _Context
from pddl.builders.constants_def import ConstantsDef
from pddl.builders.predicates_def import PredicatesDef
from pddl.builders.types_def import MutableTypesDef
from pddl.custom_types import namelike
from pddl.exceptions import PDDLValidationError
from pddl.logic import Constant, Predicate
from pddl.logic.terms import Term
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)

@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._check_predicate_def(constant)
self._check_types_are_available(constant)
self.__predicates_def.add_predicate(constant)
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,
# )
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
"""This module implements the ConstantsDef class to handle the constants of a PDDL domain."""
from typing import AbstractSet, Collection, Dict, Optional

from pddl.builders.base import _Context
from pddl.custom_types import name as name_type
from pddl.definitions.base import TypesDef, _Definition
from pddl.definitions.types_def import TypesDef, _Definition
from pddl.exceptions import PDDLValidationError
from pddl.helpers.base import ensure_set
from pddl.logic import Variable
Expand All @@ -28,14 +29,10 @@ class PredicatesDef(_Definition):

def __init__(
self,
requirements: AbstractSet[Requirements],
types: TypesDef,
predicates: Optional[Collection[Predicate]],
context: _Context,
) -> None:
"""Initialize the PDDL constants section validator."""
super().__init__(requirements, types)

self._predicates: AbstractSet[Predicate] = ensure_set(predicates)
super().__init__(context)

self._check_consistency()

Expand All @@ -56,6 +53,6 @@ def _check_consistency(self) -> None:
seen_predicates_by_name[p.name] = p

# check that the terms are consistent wrt types, and that are all variables
TermsValidator(
self._requirements, self._types, must_be_instances_of=Variable
).check_terms(p.terms)
# TermsValidator(
# self._requirements, self._types, must_be_instances_of=Variable
# ).check_terms(p.terms)
Loading

0 comments on commit 0ca7150

Please sign in to comment.