diff --git a/setup.py b/setup.py index ff2d5e7e4..df79f0f1c 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ "lpg": ["up-lpg==0.0.7"], "fmap": ["up-fmap==0.0.7"], "aries": ["up-aries>=0.0.8"], - "symk": ["up-symk>=0.0.3"], + "symk": ["up-symk>=1.0.0"], "engines": [ "tarski[arithmetic]", "up-pyperplan==1.0.0.1.dev1", @@ -40,7 +40,7 @@ "up-lpg==0.0.7", "up-fmap==0.0.7", "up-aries>=0.0.8", - "up-symk>=0.0.3", + "up-symk>=1.0.0", ], "plot": [ "plotly", diff --git a/unified_planning/io/pddl_reader.py b/unified_planning/io/pddl_reader.py index 17fab80a9..3dc99a9b2 100644 --- a/unified_planning/io/pddl_reader.py +++ b/unified_planning/io/pddl_reader.py @@ -101,7 +101,7 @@ def __init__(self): + ":requirements" + OneOrMore( one_of( - ":strips :typing :negative-preconditions :disjunctive-preconditions :equality :existential-preconditions :universal-preconditions :quantified-preconditions :conditional-effects :fluents :numeric-fluents :adl :durative-actions :duration-inequalities :timed-initial-literals :action-costs :hierarchy :method-preconditions :constraints :contingent :preferences" + ":strips :typing :negative-preconditions :disjunctive-preconditions :equality :existential-preconditions :universal-preconditions :quantified-preconditions :conditional-effects :fluents :numeric-fluents :adl :durative-actions :duration-inequalities :timed-initial-literals :action-costs :hierarchy :method-preconditions :constraints :contingent :preferences :derived-predicates" ) ) + Suppress(")") @@ -168,6 +168,14 @@ def __init__(self): + Suppress(")") ) + axiom_def = Group( + Suppress("(") + + ":derived" + - set_results_name(predicate, "head") + - set_results_name(nested_expr(), "body") + + Suppress(")") + ) + parameters = set_results_name( ZeroOrMore( Group( @@ -176,6 +184,7 @@ def __init__(self): ), "params", ) + action_def = Group( Suppress("(") + ":action" @@ -255,6 +264,7 @@ def __init__(self): + Optional(constants_def) + Optional(predicates_def) + Optional(functions_def) + + set_results_name(Group(ZeroOrMore(axiom_def)), "axioms") + set_results_name(Group(ZeroOrMore(task_def)), "tasks") + set_results_name(Group(ZeroOrMore(method_def)), "methods") + set_results_name( @@ -387,7 +397,9 @@ def __init__(self, environment: typing.Optional[Environment] = None): def _parse_exp( self, problem: up.model.Problem, - act: typing.Optional[Union[up.model.Action, htn.Method, htn.TaskNetwork]], + act: typing.Optional[ + Union[up.model.Action, up.model.Axiom, htn.Method, htn.TaskNetwork] + ], types_map: TypesMap, var: Dict[str, up.model.Variable], exp: CustomParseResults, @@ -1042,8 +1054,7 @@ def declare_type( has_actions_cost = False - for p in domain_res.get("predicates", []): - n = p[0] + def get_fluent_params(p: ParseResults) -> OrderedDict(): params = OrderedDict() for g in p[1]: try: @@ -1061,6 +1072,11 @@ def declare_type( ) for param_name in g.value[0]: params[param_name] = param_type + return params + + for p in domain_res.get("predicates", []): + n = p[0] + params = get_fluent_params(p) f = up.model.Fluent(n, self._tm.BoolType(), params, self._env) problem.add_fluent(f) @@ -1142,6 +1158,57 @@ def declare_type( task = htn.Task(name, task_params) problem.add_task(task) + for axiom_entry in domain_res.get("axioms", []): + # Each axiom should have only one predicate in the head + assert ( + len(axiom_entry["head"]) == 1 + ), "Only one predicate in head of axiom allowed" + + # Extract the fluent name from the axiom's head + fluent_name = axiom_entry["head"][0][0] + + # Set the fluent's type to DerivedBoolType + problem.fluent(fluent_name)._typename = self._tm.DerivedBoolType() + + # Extract and organize the axiom's parameters + axiom_params = OrderedDict() + for param_entry in axiom_entry["head"][0][1]: + param_name, param_type = param_entry[1][0][0], param_entry[1][1] + axiom_params[param_name] = types_map[ + param_type if len(param_entry[1]) > 1 else Object + ] + + # Create an Axiom object with the parameters + axiom = unified_planning.model.Axiom("", axiom_params) + + # Retrieve the current fluent + this_fluent = problem.fluent(fluent_name) + + # Create an effect using the axiom's parameters and set it as the axiom's head + effect = this_fluent(*axiom.parameters) + axiom.set_head(effect) + + # Ensure there's only one condition in the body of the axiom + assert ( + len(axiom_entry["body"]) == 1 + ), "Only one condition in body of axiom allowed" + + # Parse the condition and add it to the axiom's body + body_condition = self._parse_exp( + problem, + axiom, + types_map, + {}, + CustomParseResults(axiom_entry["body"][0]), + domain_str, + ) + + # Add the parsed body condition to the axiom + axiom.add_body_condition(body_condition) + + # Add the axiom to the problem + problem.add_axiom(axiom) + for a in domain_res.get("actions", []): n = a["name"] a_params = OrderedDict() diff --git a/unified_planning/io/pddl_writer.py b/unified_planning/io/pddl_writer.py index ddce37993..2c89f62a3 100644 --- a/unified_planning/io/pddl_writer.py +++ b/unified_planning/io/pddl_writer.py @@ -405,6 +405,8 @@ def _write_domain(self, out: IO[str]): or self.problem_kind.has_fluents_in_actions_cost() ): out.write(" :numeric-fluents") + if self.problem_kind.has_derived_fluents(): + out.write(" :derived-predicates") if self.problem_kind.has_conditional_effects(): out.write(" :conditional-effects") if self.problem_kind.has_existential_conditions(): @@ -477,7 +479,7 @@ def _write_domain(self, out: IO[str]): predicates = [] functions = [] for f in self.problem.fluents: - if f.type.is_bool_type(): + if f.type.is_bool_type() or f.type.is_derived_bool_type(): params = [] i = 0 for param in f.signature: @@ -533,6 +535,29 @@ def _write_domain(self, out: IO[str]): "Only one metric is supported!" ) + for a in self.problem.axioms: + if any(p.simplify().is_false() for p in a.preconditions): + continue + out.write(f" (:derived ({self._get_mangled_name(a.head._fluent.fluent())}") + for ap in a.parameters: + if ap.type.is_user_type(): + out.write( + f" {self._get_mangled_name(ap)} - {self._get_mangled_name(ap.type)}" + ) + else: + raise UPTypeError("PDDL supports only user type parameters") + out.write(")\n") + if len(a.preconditions) > 0: + precond_str: List[str] = [] + for p in (c.simplify() for c in a.preconditions): + if not p.is_true(): + if p.is_and(): + precond_str.extend(map(converter.convert, p.args)) + else: + precond_str.append(converter.convert(p)) + out.write(f' (and {" ".join(precond_str)})\n') + out.write(" )\n") + em = self.problem.environment.expression_manager for a in self.problem.actions: if isinstance(a, up.model.InstantaneousAction): diff --git a/unified_planning/model/__init__.py b/unified_planning/model/__init__.py index c4ba1dec6..89641b8cc 100644 --- a/unified_planning/model/__init__.py +++ b/unified_planning/model/__init__.py @@ -20,6 +20,7 @@ DurativeAction, SensingAction, ) +from unified_planning.model.axiom import Axiom from unified_planning.model.effect import Effect, SimulatedEffect, EffectKind from unified_planning.model.expression import ( BoolExpression, @@ -133,4 +134,5 @@ "Oversubscription", "TemporalOversubscription", "DeltaSimpleTemporalNetwork", + "Axiom", ] diff --git a/unified_planning/model/action.py b/unified_planning/model/action.py index 2f22177c8..267993fef 100644 --- a/unified_planning/model/action.py +++ b/unified_planning/model/action.py @@ -302,7 +302,12 @@ def add_precondition( (precondition_exp,) = self._environment.expression_manager.auto_promote( precondition ) - assert self._environment.type_checker.get_type(precondition_exp).is_bool_type() + assert ( + self._environment.type_checker.get_type(precondition_exp).is_bool_type() + or self._environment.type_checker.get_type( + precondition_exp + ).is_derived_bool_type() + ) if precondition_exp == self._environment.expression_manager.TRUE(): return free_vars = self._environment.free_vars_oracle.get_free_variables( @@ -341,7 +346,12 @@ def add_effect( raise UPUsageError( "fluent field of add_effect must be a Fluent or a FluentExp or a Dot." ) - if not self._environment.type_checker.get_type(condition_exp).is_bool_type(): + if not ( + self._environment.type_checker.get_type(condition_exp).is_bool_type() + or self._environment.type_checker.get_type( + condition_exp + ).is_derived_bool_type() + ): raise UPTypeError("Effect condition is not a Boolean condition!") if not fluent_exp.type.is_compatible(value_exp.type): # Value is not assignable to fluent (its type is not a subset of the fluent's type). diff --git a/unified_planning/model/axiom.py b/unified_planning/model/axiom.py new file mode 100644 index 000000000..8aa7d19d0 --- /dev/null +++ b/unified_planning/model/axiom.py @@ -0,0 +1,112 @@ +# Copyright 2021-2023 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. +# +""" +This module defines the base class `Axiom'. +An `Axiom' has a `head' which is a single `Parameter' and a `body' which is a single `Condition'. +""" + +from unified_planning.model.action import * +from unified_planning.environment import get_environment, Environment + + +class Axiom(InstantaneousAction): + def __init__( + self, + _name: str, + _parameters: Optional["OrderedDict[str, up.model.types.Type]"] = None, + _env: Optional[Environment] = None, + **kwargs: "up.model.types.Type", + ): + InstantaneousAction.__init__(self, _name, _parameters, _env, **kwargs) + + def set_head(self, fluent: Union["up.model.fnode.FNode", "up.model.fluent.Fluent"]): + if not fluent.type.is_derived_bool_type(): + raise UPTypeError("The head of an axiom must be of type DerivedBoolType!") + + self.add_effect(fluent) + + def add_body_condition( + self, + precondition: Union[ + "up.model.fnode.FNode", + "up.model.fluent.Fluent", + "up.model.parameter.Parameter", + bool, + ], + ): + super().add_precondition(precondition) + + def add_effect( + self, + fluent: Union["up.model.fnode.FNode", "up.model.fluent.Fluent"], + value: "up.model.expression.Expression" = True, + condition: "up.model.expression.BoolExpression" = True, + forall: Iterable["up.model.variable.Variable"] = tuple(), + ): + if value != True: + raise UPUsageError("value can only be true for an axiom") + if condition != True: + raise UPUsageError("the effect of an axiom can not include a condition") + if len(forall) > 0: + raise UPUsageError("the effect of an axiom can not a forall") + + ( + fluent_exp, + value_exp, + condition_exp, + ) = self._environment.expression_manager.auto_promote(fluent, value, condition) + + if not fluent_exp.is_fluent_exp(): + raise UPUsageError("head/effect of an axiom must be a fluent expression") + + fluent_exp_params = [p.parameter() for p in fluent_exp.args] + if self.parameters != fluent_exp_params: + raise UPUsageError( + f"parameters of axiom and this fluent expression do not match: {self.parameters} vs. {fluent_exp_params}" + ) + + self.clear_effects() + self._add_effect_instance( + up.model.effect.Effect(fluent_exp, value_exp, condition_exp, forall=forall) + ) + + @property + def head(self) -> List["up.model.fluent.Fluent"]: + """Returns the `head` of the `Axiom`.""" + return self._effects[0] + + @property + def body(self) -> List["up.model.fnode.FNode"]: + """Returns the `list` of the `Axiom` `body`.""" + return self._preconditions + + def __repr__(self) -> str: + s = [] + s.append(f"axiom {self.name}") + first = True + for p in self.parameters: + if first: + s.append("(") + first = False + else: + s.append(", ") + s.append(str(p)) + if not first: + s.append(")") + s.append("{\n") + s.append(f" head = {self.head}\n") + s.append(f" body = {self.body}\n") + s.append(" }") + return "".join(s) diff --git a/unified_planning/model/mixins/__init__.py b/unified_planning/model/mixins/__init__.py index 3cd6ed8e7..41cb91cb2 100644 --- a/unified_planning/model/mixins/__init__.py +++ b/unified_planning/model/mixins/__init__.py @@ -14,6 +14,7 @@ # from unified_planning.model.mixins.actions_set import ActionsSetMixin +from unified_planning.model.mixins.axioms_set import AxiomsSetMixin from unified_planning.model.mixins.time_model import TimeModelMixin from unified_planning.model.mixins.fluents_set import FluentsSetMixin from unified_planning.model.mixins.objects_set import ObjectsSetMixin @@ -24,6 +25,7 @@ __all__ = [ "ActionsSetMixin", + "AxiomsSetMixin", "TimeModelMixin", "FluentsSetMixin", "ObjectsSetMixin", diff --git a/unified_planning/model/mixins/axioms_set.py b/unified_planning/model/mixins/axioms_set.py new file mode 100644 index 000000000..943693830 --- /dev/null +++ b/unified_planning/model/mixins/axioms_set.py @@ -0,0 +1,61 @@ +# Copyright 2021-2023 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. +# + +import unified_planning as up +from typing import List, Iterable + + +class AxiomsSetMixin: + """ + This class is a mixin that contains a `set` of `axiom` with some related methods. + """ + + def __init__(self, environment): + self._env = environment + self._axioms: List["up.model.axiom.Axiom"] = [] + + @property + def environment(self) -> "up.environment.Environment": + """Returns the `Problem` environment.""" + return self._env + + @property + def axioms(self) -> List["up.model.axiom.Axiom"]: + """Returns the list of the `Axioms` in the `Problem`.""" + return self._axioms + + def clear_axioms(self): + """Removes all the `Problem` `Axioms`.""" + self._axioms = [] + + def add_axiom(self, axiom: "up.model.axiom.Axiom"): + """ + Adds the given `axiom` to the `problem`. + + :param axiom: The `axiom` that must be added to the `problem`. + """ + assert ( + axiom.environment == self._env + ), "Axiom does not have the same environment of the problem" + self._axioms.append(axiom) + + def add_axioms(self, axioms: Iterable["up.model.axiom.Axiom"]): + """ + Adds the given `axioms` to the `problem`. + + :param axioms: The `axioms` that must be added to the `problem`. + """ + for axiom in axioms: + self.add_axiom(axiom) diff --git a/unified_planning/model/mixins/initial_state.py b/unified_planning/model/mixins/initial_state.py index 4f21c9321..7f39a41f2 100644 --- a/unified_planning/model/mixins/initial_state.py +++ b/unified_planning/model/mixins/initial_state.py @@ -59,6 +59,8 @@ def set_initial_value( """ fluent_exp, value_exp = self._env.expression_manager.auto_promote(fluent, value) assert fluent_exp.is_fluent_exp(), "fluent field must be a fluent" + if fluent.type.is_derived_bool_type(): + raise UPTypeError("You cannot set the initial value of a derived fluent!") if not fluent_exp.type.is_compatible(value_exp.type): raise UPTypeError("Initial value assignment has not compatible types!") self._initial_value[fluent_exp] = value_exp @@ -82,6 +84,8 @@ def initial_value( return self._initial_value[fluent_exp] elif fluent_exp.fluent() in self._fluent_set.fluents_defaults: return self._fluent_set.fluents_defaults[fluent_exp.fluent()] + elif fluent_exp.fluent().type.is_derived_bool_type(): + return self._env.expression_manager.FALSE() else: raise UPProblemDefinitionError( f"Initial value not set for fluent: {fluent}" @@ -98,7 +102,8 @@ def initial_values(self) -> Dict["up.model.fnode.FNode", "up.model.fnode.FNode"] res = self._initial_value for f in self._fluent_set.fluents: for f_exp in get_all_fluent_exp(self._object_set, f): - res[f_exp] = self.initial_value(f_exp) + if not f.type.is_derived_bool_type(): + res[f_exp] = self.initial_value(f_exp) return res @property diff --git a/unified_planning/model/problem.py b/unified_planning/model/problem.py index dfe81d85d..2074a50ff 100644 --- a/unified_planning/model/problem.py +++ b/unified_planning/model/problem.py @@ -22,6 +22,7 @@ from unified_planning.model.abstract_problem import AbstractProblem from unified_planning.model.mixins import ( ActionsSetMixin, + AxiomsSetMixin, TimeModelMixin, FluentsSetMixin, ObjectsSetMixin, @@ -50,6 +51,7 @@ class Problem( # type: ignore[misc] UserTypesSetMixin, TimeModelMixin, FluentsSetMixin, + AxiomsSetMixin, ActionsSetMixin, ObjectsSetMixin, InitialStateMixin, @@ -76,6 +78,7 @@ def __init__( FluentsSetMixin.__init__( self, self.environment, self._add_user_type, self.has_name, initial_defaults ) + AxiomsSetMixin.__init__(self, self.environment) ActionsSetMixin.__init__( self, self.environment, self._add_user_type, self.has_name ) @@ -113,6 +116,10 @@ def __repr__(self) -> str: s.append("fluents = [\n") s.extend(map(custom_str, self.fluents)) s.append("]\n\n") + if len(self.axioms) > 0: + s.append("axioms = [\n") + s.extend(map(custom_str, self.axioms)) + s.append("]\n\n") s.append("actions = [\n") s.extend(map(custom_str, self.actions)) s.append("]\n\n") @@ -177,6 +184,8 @@ def __eq__(self, oth: object) -> bool: if set(self._goals) != set(oth._goals): return False + if set(self._axioms) != set(oth._axioms): + return False if set(self._actions) != set(oth._actions): return False if set(self._trajectory_constraints) != set(oth._trajectory_constraints): @@ -209,6 +218,8 @@ def __hash__(self) -> int: res += InitialStateMixin.__hash__(self) res += MetricsMixin.__hash__(self) + for a in self._axioms: + res += hash(a) for a in self._actions: res += hash(a) for c in self._trajectory_constraints: @@ -233,6 +244,7 @@ def clone(self): InitialStateMixin._clone_to(self, new_p) TimeModelMixin._clone_to(self, new_p) + new_p._axioms = [a.clone() for a in self._axioms] new_p._actions = [a.clone() for a in self._actions] new_p._timed_effects = { t: [e.clone() for e in el] for t, el in self._timed_effects.items() @@ -583,7 +595,10 @@ def add_goal( isinstance(goal, bool) or goal.environment == self._env ), "goal does not have the same environment of the problem" (goal_exp,) = self._env.expression_manager.auto_promote(goal) - assert self._env.type_checker.get_type(goal_exp).is_bool_type() + assert ( + self._env.type_checker.get_type(goal_exp).is_bool_type() + or self._env.type_checker.get_type(goal_exp).is_derived_bool_type() + ) if goal_exp != self._env.expression_manager.TRUE(): self._goals.append(goal_exp) @@ -665,6 +680,8 @@ def _kind_factory(self) -> "_KindFactory": for action in self._actions: factory.update_problem_kind_action(action) + if len(self.axioms) > 0: + factory.kind.set_fluents_type("DERIVED_FLUENTS") if len(self._timed_effects) > 0: factory.kind.set_time("CONTINUOUS_TIME") factory.kind.set_time("TIMED_EFFECTS") diff --git a/unified_planning/model/problem_kind.py b/unified_planning/model/problem_kind.py index 3220c8d97..d40b263fe 100644 --- a/unified_planning/model/problem_kind.py +++ b/unified_planning/model/problem_kind.py @@ -69,7 +69,7 @@ "UNBOUNDED_INT_ACTION_PARAMETERS", "REAL_ACTION_PARAMETERS", ], - "FLUENTS_TYPE": ["NUMERIC_FLUENTS", "OBJECT_FLUENTS"], + "FLUENTS_TYPE": ["DERIVED_FLUENTS", "NUMERIC_FLUENTS", "OBJECT_FLUENTS"], "QUALITY_METRICS": [ "ACTIONS_COST", "FINAL_VALUE", @@ -223,6 +223,7 @@ def intersection(self, oth: "ProblemKind") -> "ProblemKind": full_classical_kind.set_conditions_kind("EXISTENTIAL_CONDITIONS") full_classical_kind.set_conditions_kind("UNIVERSAL_CONDITIONS") full_classical_kind.set_effects_kind("CONDITIONAL_EFFECTS") +full_classical_kind.set_fluents_type("DERIVED_FLUENTS") object_fluent_kind = ProblemKind() object_fluent_kind.set_fluents_type("OBJECT_FLUENTS") diff --git a/unified_planning/model/type_manager.py b/unified_planning/model/type_manager.py index c2ce26ba0..b7b1f9be9 100644 --- a/unified_planning/model/type_manager.py +++ b/unified_planning/model/type_manager.py @@ -22,6 +22,7 @@ _RealType, _UserType, BOOL, + DERIVED_BOOL, TIME, ) from unified_planning.model.tamp.types import ( @@ -39,6 +40,7 @@ class TypeManager: def __init__(self): self._bool = BOOL + self._derived_bool = DERIVED_BOOL self._ints: Dict[Tuple[Optional[int], Optional[int]], Type] = {} self._reals: Dict[Tuple[Optional[Fraction], Optional[Fraction]], Type] = {} self._user_types: Dict[Tuple[str, Optional[Type]], Type] = {} @@ -54,6 +56,8 @@ def has_type(self, type: Type) -> bool: """ if type.is_bool_type(): return type == self._bool + elif type.is_derived_bool_type(): + return type == self._derived_bool elif type.is_int_type(): assert isinstance(type, _IntType) return self._ints.get((type.lower_bound, type.upper_bound), None) == type @@ -83,6 +87,10 @@ def BoolType(self) -> Type: """Returns this `Environment's` boolean `Type`.""" return self._bool + def DerivedBoolType(self) -> Type: + """Returns this `Environment's` derived boolean `Type`.""" + return self._derived_bool + def IntType( self, lower_bound: Optional[int] = None, upper_bound: Optional[int] = None ) -> Type: @@ -150,7 +158,8 @@ def UserType(self, name: str, father: Optional[Type] = None) -> Type: for ancestor in father.ancestors ): raise UPTypeError( - f"The name: {name} is already used. A UserType and one of his ancestors can not share the name." + f"The name: {name} is already used. A UserType and one of his" + " ancestors can not share the name." ) ut = _UserType(name, father) self._user_types[(name, father)] = ut @@ -175,7 +184,8 @@ def MovableType(self, name: str, father: Optional[Type] = None) -> Type: for ancestor in father.ancestors ): raise UPTypeError( - f"The name: {name} is already used. A MovableType and one of his ancestors can not share the name." + f"The name: {name} is already used. A MovableType and one of" + " his ancestors can not share the name." ) mt = _MovableType(name, father) self._movable_types[(name, father)] = mt diff --git a/unified_planning/model/types.py b/unified_planning/model/types.py index 251378b4e..e3c398db2 100644 --- a/unified_planning/model/types.py +++ b/unified_planning/model/types.py @@ -28,6 +28,10 @@ def is_bool_type(self) -> bool: """Returns `True` iff is `boolean Type`.""" return False + def is_derived_bool_type(self) -> bool: + """Returns `True` iff is `derived bool Type`.""" + return False + def is_user_type(self) -> bool: """Returns `True` iff is a `user Type`.""" return False @@ -75,6 +79,17 @@ def is_bool_type(self) -> bool: return True +class _DerivedBoolType(Type): + """Represents the derived bool type.""" + + def __repr__(self) -> str: + return "derived bool" + + def is_derived_bool_type(self) -> bool: + """Returns true iff is derived bool type.""" + return True + + class _TimeType(Type): """Represent the type for an absolute Time""" @@ -226,6 +241,7 @@ def is_real_type(self) -> bool: BOOL = _BoolType() +DERIVED_BOOL = _DerivedBoolType() TIME = _TimeType() @@ -243,6 +259,8 @@ def domain_size( """ if typename.is_bool_type(): return 2 + elif typename.is_derived_bool_type(): + return 2 elif typename.is_user_type(): return len(list(objects_set.objects(typename))) elif typename.is_int_type(): diff --git a/unified_planning/model/walkers/type_checker.py b/unified_planning/model/walkers/type_checker.py index 2380a233a..d7b3a1e46 100644 --- a/unified_planning/model/walkers/type_checker.py +++ b/unified_planning/model/walkers/type_checker.py @@ -18,7 +18,7 @@ import unified_planning.model.types import unified_planning.environment import unified_planning.model.walkers as walkers -from unified_planning.model.types import BOOL, TIME, _UserType +from unified_planning.model.types import BOOL, DERIVED_BOOL, TIME, _UserType from unified_planning.model.fnode import FNode from unified_planning.model.operators import OperatorKind from unified_planning.exceptions import UPTypeError @@ -62,7 +62,7 @@ def walk_bool_to_bool( ) -> Optional["unified_planning.model.types.Type"]: assert expression is not None for x in args: - if x is None or x != BOOL: + if x is None or x not in (BOOL, DERIVED_BOOL): return None return BOOL diff --git a/unified_planning/shortcuts.py b/unified_planning/shortcuts.py index 67b11c39f..94ffc6170 100644 --- a/unified_planning/shortcuts.py +++ b/unified_planning/shortcuts.py @@ -454,6 +454,11 @@ def BoolType() -> unified_planning.model.types.Type: return get_environment().type_manager.BoolType() +def DerivedBoolType() -> unified_planning.model.types.Type: + """Returns the global environment's boolean type.""" + return get_environment().type_manager.DerivedBoolType() + + def IntType( lower_bound: Optional[int] = None, upper_bound: Optional[int] = None ) -> unified_planning.model.types.Type: