From f506207add232f838c6dab19690996c854fd883e Mon Sep 17 00:00:00 2001 From: Samuel Gobbi Date: Mon, 14 Oct 2024 11:34:18 +0200 Subject: [PATCH] fix(interpreted functions): fix for booleans use iff instead of equals --- .github/workflows/test.yml | 24 ++--- .../interpreted_functions_remover.py | 99 ++++++++++++++++--- .../engines/interpreted_functions_planner.py | 20 ++-- unified_planning/engines/plan_validator.py | 41 +++++++- .../examples/complex_interpreted_functions.py | 13 +-- 5 files changed, 156 insertions(+), 41 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 416c0167e..d4156766e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -302,18 +302,18 @@ jobs: cd ENHSP-Public; git checkout enhsp20-0.14.0; bash ./compile; cd .. bash -c "mkdir .planners; mv ENHSP-Public/enhsp-dist .planners/enhsp-20; rm -rf ENHSP-Public" - #- name: Checkout up-enhsp - # uses: actions/checkout@v2 - # with: - # repository: aiplan4eu/up-enhsp - # path: up-enhsp - # ref: ${{env.up_enhsp_commit}} - # - #- name: Install up-enhsp - # run: python3 -m pip install up-enhsp/ - # - #- name: Run tests - # run: bash run_tests.sh + - name: Checkout up-enhsp + uses: actions/checkout@v2 + with: + repository: aiplan4eu/up-enhsp + path: up-enhsp + ref: ${{env.up_enhsp_commit}} + + - name: Install up-enhsp + run: python3 -m pip install up-enhsp/ + + - name: Run tests + run: bash run_tests.sh run-tests-macos: runs-on: macos-13 diff --git a/unified_planning/engines/compilers/interpreted_functions_remover.py b/unified_planning/engines/compilers/interpreted_functions_remover.py index 822245797..3614169bd 100644 --- a/unified_planning/engines/compilers/interpreted_functions_remover.py +++ b/unified_planning/engines/compilers/interpreted_functions_remover.py @@ -21,6 +21,7 @@ import unified_planning as up import unified_planning.engines as engines from unified_planning.model.interpreted_function import InterpretedFunction +from unified_planning.model.timing import StartTiming from unified_planning.model.walkers.substituter import Substituter from unified_planning.model.walkers.operators_extractor import OperatorsExtractor from unified_planning.model.operators import OperatorKind @@ -293,19 +294,48 @@ def _compile( ] ) if new_condition is None: - new_condition = new_action.environment.expression_manager.Equals( - aif._content.args[argumentcounter], - kf._content.args[argumentcounter], - ) - - else: - new_condition = new_action.environment.expression_manager.And( - new_condition, - new_action.environment.expression_manager.Equals( + if aif._content.args[ + argumentcounter + ].type.is_bool_type(): + # is this ok? + new_condition = new_action.environment.expression_manager.Iff( + aif._content.args[argumentcounter], + kf._content.args[argumentcounter], + ) + else: + new_condition = new_action.environment.expression_manager.Equals( aif._content.args[argumentcounter], kf._content.args[argumentcounter], - ), - ) + ) + + else: + if aif._content.args[ + argumentcounter + ].type.is_bool_type(): + # is this ok? + new_condition = new_action.environment.expression_manager.And( + new_condition, + new_action.environment.expression_manager.Iff( + aif._content.args[ + argumentcounter + ], + kf._content.args[ + argumentcounter + ], + ), + ) + else: + new_condition = new_action.environment.expression_manager.And( + new_condition, + new_action.environment.expression_manager.Equals( + aif._content.args[ + argumentcounter + ], + kf._content.args[ + argumentcounter + ], + ), + ) argumentcounter = argumentcounter + 1 new_action.add_precondition(new_condition) @@ -335,16 +365,37 @@ def _compile( no_IF_action = a.clone() minduration: FNode | int = 1 maxduration: FNode | int = 1000000 + IF_in_durations = list() + if not ( OperatorKind.INTERPRETED_FUNCTION_EXP in self.operators_extractor.get(a.duration.lower) ): minduration = a.duration.lower + else: + IFs = self.interpreted_functions_extractor.get(a.duration.lower) + if len(IFs) != 0: # get all the IFs in the precondition + for f in IFs: + if f not in IF_in_durations: + # and append them in the key list if not already there + IF_in_durations.append(f) + + IFs = None if not ( OperatorKind.INTERPRETED_FUNCTION_EXP in self.operators_extractor.get(a.duration.upper) ): maxduration = a.duration.upper + else: + IFs = self.interpreted_functions_extractor.get(a.duration.upper) + if len(IFs) != 0: # get all the IFs in the precondition + for f in IFs: + if f not in IF_in_durations: + # and append them in the key list if not already there + IF_in_durations.append(f) + print("compiler with knowledge:") + # print (IF_in_durations) + # print (self._interpreted_functions_values) no_IF_action.set_closed_duration_interval(minduration, maxduration) # print("after duration part of the remover") @@ -365,6 +416,32 @@ def _compile( in precondition_operators ): no_IF_action.add_condition(ii, fc) + kifid = [] + for kif in self._interpreted_functions_values: + for ifid in IF_in_durations: + if kif._content.payload.__eq__(ifid._content.payload): + if kif not in kifid: + kifid.append(ifid) + print("found a match:") + print(kif) + print(ifid) + condition_to_avoid = None + i = 0 + while i < len(kif.args): + print(ifid.args[i]) + print("should not be") + print(kif.args[i]) + # have to do the combination thing + temp_cond = no_IF_action.environment.expression_manager.Not( + no_IF_action.environment.expression_manager.Equals( + ifid.args[i], kif.args[i] + ) + ) + no_IF_action.add_condition(StartTiming(), temp_cond) + + i = i + 1 + + # no_IF_action.add_condition(StartTiming(), condition_to_avoid) no_IF_action.name = get_fresh_name(new_problem, a.name) new_to_old[no_IF_action] = a new_problem.add_action(no_IF_action) diff --git a/unified_planning/engines/interpreted_functions_planner.py b/unified_planning/engines/interpreted_functions_planner.py index b2e4acc47..4de88b9a1 100644 --- a/unified_planning/engines/interpreted_functions_planner.py +++ b/unified_planning/engines/interpreted_functions_planner.py @@ -209,16 +209,16 @@ def _attempt_to_solve( else: # temporary skip for ttp - if isinstance(mappedbackplan, TimeTriggeredPlan): - print("temporary skip for ttp") - if self._debug_print_final_problem: - print(new_problem) - return PlanGenerationResult( - validation_result.status, - mappedbackplan, - self.name, - log_messages=res.log_messages, - ) + # if isinstance(mappedbackplan, TimeTriggeredPlan): + # print("temporary skip for ttp") + # if self._debug_print_final_problem: + # print(new_problem) + # return PlanGenerationResult( + # validation_result.status, + # mappedbackplan, + # self.name, + # log_messages=res.log_messages, + # ) refined_problem = _refine(self, problem, validation_result) if refined_problem is None: diff --git a/unified_planning/engines/plan_validator.py b/unified_planning/engines/plan_validator.py index cfd11a0df..4bc3920ed 100644 --- a/unified_planning/engines/plan_validator.py +++ b/unified_planning/engines/plan_validator.py @@ -16,7 +16,7 @@ from dataclasses import dataclass from fractions import Fraction import heapq -from typing import Any, Dict, Generator, List, Optional, Set, Tuple, cast +from typing import Any, Dict, Generator, List, Optional, OrderedDict, Set, Tuple, cast import warnings import unified_planning as up import unified_planning.environment @@ -166,6 +166,7 @@ def _validate( msg = f"{str(i)}-th action instance {str(ai)} creates Conflicting Effects: {str(e)}" if msg is not None: logs = [LogMessage(LogLevel.INFO, msg)] + # print (cif) return ValidationResult( status=ValidationResultStatus.INVALID, engine_name=self.name, @@ -423,7 +424,10 @@ def _validate( cif = None hasif = False - if problem.kind.has_interpreted_functions_in_conditions(): + if ( + problem.kind.has_interpreted_functions_in_conditions() + or problem.kind.has_interpreted_functions_in_durations() + ): hasif = True start_actions: List[Tuple[Fraction, ActionInstance, Optional[Fraction]]] = list( plan.timed_actions @@ -628,6 +632,39 @@ def _validate( print( "this thing does not have a simulator like the sequential ones ;-;" ) + print(opt_ai.action) + ife: up.model.walkers.InterpretedFunctionsExtractor = ( + up.model.walkers.InterpretedFunctionsExtractor() + ) + ifl = ife.get(opt_ai.action.duration.lower) + ifu = ife.get(opt_ai.action.duration.upper) + ifc = [] + for ii, cl in opt_ai.action.conditions.items(): + + for c in cl: + ifc.append(ife.get(c)) + + # print (ifl) + # print (ifu) + # print (ifc) + # print(trace) + cif = OrderedDict() + for lll in ifl: + fp = lll._content.payload + fa = lll._content.args # aka args + fc = lll._content.payload.function + # print (fc) + notOkParams = list() + for fan in fa: + notOkParams.append(state.get_value(fan)) + + # print (fp) + # print (fa) + print(fp(*notOkParams)) + print(fc(*notOkParams)) + cif[fp(*notOkParams)] = fc( + *notOkParams + ) # does not use memoization return ValidationResult( status=ValidationResultStatus.INVALID, engine_name=self.name, diff --git a/unified_planning/test/examples/complex_interpreted_functions.py b/unified_planning/test/examples/complex_interpreted_functions.py index d15c138a4..2f5475e87 100644 --- a/unified_planning/test/examples/complex_interpreted_functions.py +++ b/unified_planning/test/examples/complex_interpreted_functions.py @@ -49,7 +49,7 @@ def simple_always_false(ind): return False s_simple_always_false = OrderedDict() - s_simple_always_false["ind"] = IntType() + s_simple_always_false["ind"] = BoolType() IF_simple_always_false = InterpretedFunction( "simple_always_false", BoolType(), s_simple_always_false, simple_always_false ) @@ -58,7 +58,7 @@ def simple_always_false(ind): ione = Fluent("ione", IntType(0, 20)) itwo = Fluent("itwo", IntType(0, 20)) ithree = Fluent("ithree", IntType(0, 20)) - ifour = Fluent("ifour", IntType(0, 20)) + ifour = Fluent("ifour", BoolType()) a = InstantaneousAction("a") a.add_precondition(And(IF_integers_to_bool(ione, itwo), LT(ione, 15))) a.add_precondition(LT(g, 10)) @@ -66,7 +66,8 @@ def simple_always_false(ind): b = InstantaneousAction("b") b.add_precondition( And( - And(IF_integers_to_bool(itwo, itwo), GT(simple_int_to_int(ithree), 5)), True + And(IF_integers_to_bool(itwo, itwo), GT(IF_simple_int_to_int(ithree), 5)), + True, ) ) b.add_effect(g, Plus(g, 5)) @@ -75,10 +76,10 @@ def simple_always_false(ind): d = InstantaneousAction("d") d.add_effect(ione, Minus(ione, 1)) e = InstantaneousAction("e") - e.add_precondition(simple_always_false(ifour)) + e.add_precondition(IF_simple_always_false(ifour)) e.add_effect(g, 5) f = InstantaneousAction("f") - f.add_precondition(And(GT(ione, simple_int_to_int(ithree)))) + f.add_precondition(And(GT(ione, IF_simple_int_to_int(ithree)))) f.add_effect(itwo, 5) problem = Problem("IF_in_conditions_complex_1") problem.add_fluent(g) @@ -96,7 +97,7 @@ def simple_always_false(ind): problem.set_initial_value(ione, 11) problem.set_initial_value(itwo, 1) problem.set_initial_value(ithree, 15) - problem.set_initial_value(ifour, 15) + problem.set_initial_value(ifour, False) problem.add_goal(GE(g, 5)) ifproblem = TestCase(