Source code for unified_planning.model.problem_kind

# Copyright 2021 AIPlan4EU project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from functools import partialmethod, total_ordering
from typing import Dict, List, Set
import unified_planning as up


# TODO: This features map needs to be extended with all the problem characterizations.
FEATURES = {
    "PROBLEM_CLASS": ["ACTION_BASED", "HIERARCHICAL", "CONTINGENT"],
    "PROBLEM_TYPE": ["SIMPLE_NUMERIC_PLANNING", "GENERAL_NUMERIC_PLANNING"],
    "TIME": [
        "CONTINUOUS_TIME",
        "DISCRETE_TIME",
        "INTERMEDIATE_CONDITIONS_AND_EFFECTS",
        "TIMED_EFFECT",
        "TIMED_GOALS",
        "DURATION_INEQUALITIES",
    ],
    "EXPRESSION_DURATION": ["STATIC_FLUENTS_IN_DURATION", "FLUENTS_IN_DURATION"],
    "NUMBERS": ["CONTINUOUS_NUMBERS", "DISCRETE_NUMBERS"],
    "CONDITIONS_KIND": [
        "NEGATIVE_CONDITIONS",
        "DISJUNCTIVE_CONDITIONS",
        "EQUALITY",
        "EXISTENTIAL_CONDITIONS",
        "UNIVERSAL_CONDITIONS",
    ],
    "EFFECTS_KIND": ["CONDITIONAL_EFFECTS", "INCREASE_EFFECTS", "DECREASE_EFFECTS"],
    "TYPING": ["FLAT_TYPING", "HIERARCHICAL_TYPING"],
    "FLUENTS_TYPE": ["NUMERIC_FLUENTS", "OBJECT_FLUENTS"],
    "QUALITY_METRICS": [
        "ACTIONS_COST",
        "FINAL_VALUE",
        "MAKESPAN",
        "PLAN_LENGTH",
        "OVERSUBSCRIPTION",
    ],
    "SIMULATED_ENTITIES": ["SIMULATED_EFFECTS"],
}


class ProblemKindMeta(type):
    """Meta class used to interpret the nodehandler decorator."""

    def __new__(cls, name, bases, dct):
        def _set(self, feature, possible_features):
            assert feature in possible_features
            self._features.add(feature)

        def _unset(self, feature, possible_features):
            assert feature in possible_features
            self._features.discard(feature)

        def _has(self, feature):
            return feature in self._features

        obj = type.__new__(cls, name, bases, dct)
        for m, l in FEATURES.items():
            setattr(obj, "set_" + m.lower(), partialmethod(_set, possible_features=l))
            setattr(
                obj, "unset_" + m.lower(), partialmethod(_unset, possible_features=l)
            )
            for f in l:
                setattr(obj, "has_" + f.lower(), partialmethod(_has, feature=f))
        return obj


[docs]@total_ordering class ProblemKind(up.AnyBaseClass, metaclass=ProblemKindMeta): """ This class represents the main interesting feature that a :class:`planning Problem <unified_planning.model.AbstractProblem>` can have in order to understand if an :class:`~unified_planning.engines.Engine` is capable of solving the `Problem` or not; some features might also help the `Engine`, allowing some assumptions to be made. The `ProblemKind` of a `Problem` is calculated by it's :func:`kind <unified_planning.model.Problem.kind>` property. """ def __init__(self, features: Set[str] = set()): self._features: Set[str] = set(features) def __repr__(self) -> str: features_gen = (f"'{feature}'" for feature in self._features) return f'ProblemKind([{", ".join(features_gen)}])' def __str__(self) -> str: features_mapped: Dict[str, List[str]] = {} for k, fl in FEATURES.items(): for feature in self._features: if feature in fl: feature_list = features_mapped.get(k, None) if feature_list is None: features_mapped[k] = [feature] else: feature_list.append(feature) result_str: List[str] = [f"{k}: {fl}" for k, fl in features_mapped.items()] return "\n".join(result_str) def __eq__(self, oth: object) -> bool: if isinstance(oth, ProblemKind): return self._features == oth._features else: return False def __hash__(self) -> int: res = 0 for f in self._features: res += hash(f) return res def __le__(self, oth: object): if not isinstance(oth, ProblemKind): raise return self._features.issubset(oth._features) @property def features(self) -> Set[str]: """Returns the features contained by this `ProblemKind`.""" return self._features
[docs] def union(self, oth: "ProblemKind") -> "ProblemKind": """ Returns a new `ProblemKind` that is the union of this `ProblemKind` and the `ProblemKind` given as parameter. :param oth: the `ProblemKind` that must be united to this `ProblemKind`. :return: a new `ProblemKind` that is the union of this `ProblemKind` and `oth` """ return ProblemKind(self.features.union(oth.features))
[docs] def intersection(self, oth: "ProblemKind") -> "ProblemKind": """ Returns a new `ProblemKind` that is the intersection of this `ProblemKind` and the `ProblemKind` given as parameter. :param oth: the `ProblemKind` that must be intersected with this `ProblemKind`. :return: a new `ProblemKind` that is the intersection between this `ProblemKind` and `oth` """ return ProblemKind(self.features.intersection(oth.features))
basic_classical_kind = ProblemKind() basic_classical_kind.set_problem_class("ACTION_BASED") basic_classical_kind.set_typing("FLAT_TYPING") hierarchical_kind = ProblemKind() hierarchical_kind.set_typing("HIERARCHICAL_TYPING") classical_kind = ProblemKind() classical_kind.set_problem_class("ACTION_BASED") classical_kind.set_typing("FLAT_TYPING") classical_kind.set_conditions_kind("NEGATIVE_CONDITIONS") classical_kind.set_conditions_kind("DISJUNCTIVE_CONDITIONS") classical_kind.set_conditions_kind("EQUALITY") full_classical_kind = ProblemKind() full_classical_kind.set_problem_class("ACTION_BASED") full_classical_kind.set_typing("FLAT_TYPING") full_classical_kind.set_conditions_kind("NEGATIVE_CONDITIONS") full_classical_kind.set_conditions_kind("DISJUNCTIVE_CONDITIONS") full_classical_kind.set_conditions_kind("EQUALITY") full_classical_kind.set_conditions_kind("EXISTENTIAL_CONDITIONS") full_classical_kind.set_conditions_kind("UNIVERSAL_CONDITIONS") full_classical_kind.set_effects_kind("CONDITIONAL_EFFECTS") object_fluent_kind = ProblemKind() object_fluent_kind.set_fluents_type("OBJECT_FLUENTS") basic_numeric_kind = ProblemKind() basic_numeric_kind.set_problem_class("ACTION_BASED") basic_numeric_kind.set_typing("FLAT_TYPING") basic_numeric_kind.set_numbers("DISCRETE_NUMBERS") basic_numeric_kind.set_numbers("CONTINUOUS_NUMBERS") basic_numeric_kind.set_fluents_type("NUMERIC_FLUENTS") full_numeric_kind = ProblemKind() full_numeric_kind.set_problem_class("ACTION_BASED") full_numeric_kind.set_typing("FLAT_TYPING") full_numeric_kind.set_numbers("DISCRETE_NUMBERS") full_numeric_kind.set_numbers("CONTINUOUS_NUMBERS") full_numeric_kind.set_fluents_type("NUMERIC_FLUENTS") full_numeric_kind.set_effects_kind("INCREASE_EFFECTS") full_numeric_kind.set_effects_kind("DECREASE_EFFECTS") basic_temporal_kind = ProblemKind() basic_temporal_kind.set_problem_class("ACTION_BASED") basic_temporal_kind.set_typing("FLAT_TYPING") basic_temporal_kind.set_time("CONTINUOUS_TIME") temporal_kind = ProblemKind() temporal_kind.set_problem_class("ACTION_BASED") temporal_kind.set_typing("FLAT_TYPING") temporal_kind.set_time("CONTINUOUS_TIME") temporal_kind.set_time("INTERMEDIATE_CONDITIONS_AND_EFFECTS") temporal_kind.set_time("TIMED_EFFECT") temporal_kind.set_time("TIMED_GOALS") temporal_kind.set_time("DURATION_INEQUALITIES") temporal_kind.set_expression_duration("STATIC_FLUENTS_IN_DURATION") quality_metrics_kind = ProblemKind() quality_metrics_kind.set_quality_metrics("PLAN_LENGTH") quality_metrics_kind.set_quality_metrics("ACTIONS_COST") quality_metrics_kind.set_quality_metrics("FINAL_VALUE") oversubscription_kind = ProblemKind() oversubscription_kind.set_quality_metrics("OVERSUBSCRIPTION")