From f148ef861b0a6fe3a7b29a362e69eae23281ae0f Mon Sep 17 00:00:00 2001 From: Luca Framba Date: Tue, 23 Apr 2024 11:19:02 +0200 Subject: [PATCH 1/5] Feat(PDDLReader): Now the PDDLReader parses durative actions that have actions costs if they are specified at end or at start. --- unified_planning/io/pddl_reader.py | 59 ++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/unified_planning/io/pddl_reader.py b/unified_planning/io/pddl_reader.py index 425672a2c..d8590887a 100644 --- a/unified_planning/io/pddl_reader.py +++ b/unified_planning/io/pddl_reader.py @@ -974,13 +974,16 @@ def _durative_action_has_cost(self, dur_act: up.model.DurativeAction): for c in cl: if self._totalcost in self._fve.get(c): return False + cost_found = False for _, el in dur_act.effects.items(): for e in el: - if ( - self._totalcost in self._fve.get(e.fluent) - or self._totalcost in self._fve.get(e.value) - or self._totalcost in self._fve.get(e.condition) - ): + if self._totalcost in self._fve.get(e.fluent): + if cost_found: + return False + cost_found = True + if self._totalcost in self._fve.get( + e.value + ) or self._totalcost in self._fve.get(e.condition): return False return True @@ -1644,20 +1647,44 @@ def declare_type( problem._fluents.remove(self._totalcost.fluent()) if self._totalcost in problem._initial_value: problem._initial_value.pop(self._totalcost) - use_plan_length = all(False for _ in problem.durative_actions) - for a in problem.instantaneous_actions: - cost = None - for e in a.effects: - if e.fluent == self._totalcost: - cost = e - break - if cost is not None: - costs[a] = cost.value - a._effects.remove(cost) - if cost.value != 1: + # use_plan_length = all(False for _ in problem.durative_actions) + start_timing, end_timing = ( + up.model.StartTiming(), + up.model.EndTiming(), + ) + for a in problem.actions: + if isinstance(a, up.model.InstantaneousAction): + cost = None + for e in a.effects: + if e.fluent == self._totalcost: + cost = e + break + if cost is not None: + costs[a] = cost.value + a._effects.remove(cost) + if cost.value != 1: + use_plan_length = False + else: use_plan_length = False else: + assert isinstance(a, up.model.DurativeAction) use_plan_length = False + cost, effects_list = None, None + for t, el in a.effects.items(): + if t in (start_timing, end_timing): + for e in el: + if e.fluent == self._totalcost: + if cost is not None: + raise UPUnsupportedProblemTypeError( + f"Action {a.name} has more than one effect modifying it's cost" + ) + cost, effects_list = e, el + break + if cost is not None: + costs[a] = cost.value + effects_list.remove(cost) + else: + use_plan_length = False if use_plan_length: problem.add_quality_metric( up.model.metrics.MinimizeSequentialPlanLength() From 143e1fe0856df79e0d8c511160573e62815856cd Mon Sep 17 00:00:00 2001 From: Luca Framba Date: Tue, 23 Apr 2024 11:23:07 +0200 Subject: [PATCH 2/5] Added typing --- unified_planning/io/pddl_reader.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/unified_planning/io/pddl_reader.py b/unified_planning/io/pddl_reader.py index d8590887a..d4e40de44 100644 --- a/unified_planning/io/pddl_reader.py +++ b/unified_planning/io/pddl_reader.py @@ -17,7 +17,7 @@ from collections import OrderedDict from fractions import Fraction import re -from typing import Dict, Union, Callable, List, cast, Tuple +from typing import Dict, Mapping, Union, Callable, List, cast, Tuple import typing import unified_planning as up import unified_planning.model.htn as htn @@ -1643,7 +1643,7 @@ def declare_type( and optimization == "minimize" and metric_exp == self._totalcost ): - costs = {} + costs: Dict[up.model.Action, up.model.Expression] = {} problem._fluents.remove(self._totalcost.fluent()) if self._totalcost in problem._initial_value: problem._initial_value.pop(self._totalcost) @@ -1670,8 +1670,8 @@ def declare_type( assert isinstance(a, up.model.DurativeAction) use_plan_length = False cost, effects_list = None, None - for t, el in a.effects.items(): - if t in (start_timing, end_timing): + for timing, el in a.effects.items(): + if timing in (start_timing, end_timing): for e in el: if e.fluent == self._totalcost: if cost is not None: @@ -1681,6 +1681,7 @@ def declare_type( cost, effects_list = e, el break if cost is not None: + assert effects_list is not None costs[a] = cost.value effects_list.remove(cost) else: From 8af7ca710fb685b9f93421aa008c4d3dd61be78b Mon Sep 17 00:00:00 2001 From: Luca Framba Date: Tue, 23 Apr 2024 11:39:35 +0200 Subject: [PATCH 3/5] Cleanup --- unified_planning/io/pddl_reader.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/unified_planning/io/pddl_reader.py b/unified_planning/io/pddl_reader.py index d4e40de44..cdbe065b3 100644 --- a/unified_planning/io/pddl_reader.py +++ b/unified_planning/io/pddl_reader.py @@ -17,7 +17,7 @@ from collections import OrderedDict from fractions import Fraction import re -from typing import Dict, Mapping, Union, Callable, List, cast, Tuple +from typing import Dict, Union, Callable, List, cast, Tuple import typing import unified_planning as up import unified_planning.model.htn as htn @@ -1647,7 +1647,6 @@ def declare_type( problem._fluents.remove(self._totalcost.fluent()) if self._totalcost in problem._initial_value: problem._initial_value.pop(self._totalcost) - # use_plan_length = all(False for _ in problem.durative_actions) start_timing, end_timing = ( up.model.StartTiming(), up.model.EndTiming(), From 83cdf10e835919c0e87f27798dfa6299122bc4a4 Mon Sep 17 00:00:00 2001 From: Luca Framba Date: Tue, 23 Apr 2024 11:58:34 +0200 Subject: [PATCH 4/5] Fixed wrong examples in UP --- unified_planning/test/examples/minimals.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unified_planning/test/examples/minimals.py b/unified_planning/test/examples/minimals.py index ec0722c82..404a7d846 100644 --- a/unified_planning/test/examples/minimals.py +++ b/unified_planning/test/examples/minimals.py @@ -87,10 +87,10 @@ def get_example_problems(): problem.add_fluent(y) problem.add_action(da) problem.set_initial_value(x, False) - problem.add_timed_effect(StartTiming(5), x, False) + problem.add_timed_effect(GlobalStartTiming(5), x, False) problem.set_initial_value(y, False) - problem.add_timed_effect(StartTiming(2), y, True) - problem.add_timed_effect(StartTiming(8), y, False) + problem.add_timed_effect(GlobalStartTiming(2), y, True) + problem.add_timed_effect(GlobalStartTiming(8), y, False) problem.add_goal(x) t_plan = up.plans.TimeTriggeredPlan([(Fraction(6), da(), Fraction(1))]) invalid_t_plans: List[up.plans.Plan] = [ @@ -486,7 +486,7 @@ def get_example_problems(): problem.add_action(task) problem.set_initial_value(value, 1) problem.add_goal(Equals(value, 2)) - problem.add_timed_effect(StartTiming(1), value, 1) + problem.add_timed_effect(GlobalStartTiming(1), value, 1) t_plan = up.plans.TimeTriggeredPlan( [(Fraction(2), up.plans.ActionInstance(task), None)] ) From c0fb5f688c454889814187757641744dde24e5bc Mon Sep 17 00:00:00 2001 From: Luca Framba Date: Wed, 24 Apr 2024 15:30:07 +0200 Subject: [PATCH 5/5] test: added a test on durative actions with action cost --- .../test/pddl/parking_action_cost/domain.pddl | 85 +++++++++++++++++++ .../pddl/parking_action_cost/problem.pddl | 23 +++++ unified_planning/test/test_pddl_io.py | 27 ++++++ 3 files changed, 135 insertions(+) create mode 100644 unified_planning/test/pddl/parking_action_cost/domain.pddl create mode 100644 unified_planning/test/pddl/parking_action_cost/problem.pddl diff --git a/unified_planning/test/pddl/parking_action_cost/domain.pddl b/unified_planning/test/pddl/parking_action_cost/domain.pddl new file mode 100644 index 000000000..7670b3c4a --- /dev/null +++ b/unified_planning/test/pddl/parking_action_cost/domain.pddl @@ -0,0 +1,85 @@ +(define (domain parking) + (:requirements :strips :typing :durative-actions) + (:types car curb) + (:predicates + (at-curb ?car - car) + (at-curb-num ?car - car ?curb - curb) + (behind-car ?car ?front-car - car) + (car-clear ?car - car) + (curb-clear ?curb - curb) + ) + + (:functions (total-cost)) + (:durative-action move-curb-to-curb + :parameters (?car - car ?curbsrc ?curbdest - curb) + :duration (= ?duration 1) + :condition (and + (at start (car-clear ?car)) + (at start (curb-clear ?curbdest)) + (at start (at-curb-num ?car ?curbsrc)) + ) + :effect (and + (at start (not (curb-clear ?curbdest))) + (at end (curb-clear ?curbsrc)) + (at end (at-curb-num ?car ?curbdest)) + (at start (not (at-curb-num ?car ?curbsrc))) + (at end (increase (total-cost) 1)) + ) + ) + + (:durative-action move-curb-to-car + :parameters (?car - car ?curbsrc - curb ?cardest - car) + :duration (= ?duration 2) + :condition (and + (at start (car-clear ?car)) + (at start (car-clear ?cardest)) + (at start (at-curb-num ?car ?curbsrc)) + (at start (at-curb ?cardest)) + ) + :effect (and + (at start (not (car-clear ?cardest))) + (at end (curb-clear ?curbsrc)) + (at end (behind-car ?car ?cardest)) + (at start (not (at-curb-num ?car ?curbsrc))) + (at start (not (at-curb ?car))) + (at end (increase (total-cost) 2)) + ) + ) + + (:durative-action move-car-to-curb + :parameters (?car - car ?carsrc - car ?curbdest - curb) + :duration (= ?duration 2) + :condition (and + (at start (car-clear ?car)) + (at start (curb-clear ?curbdest)) + (at start (behind-car ?car ?carsrc)) + ) + :effect (and + (at start (not (curb-clear ?curbdest))) + (at end (car-clear ?carsrc)) + (at end (at-curb-num ?car ?curbdest)) + (at start (not (behind-car ?car ?carsrc))) + (at end (at-curb ?car)) + (at end (increase (total-cost) 2)) + ) + ) + + (:durative-action move-car-to-car + :parameters (?car - car ?carsrc - car ?cardest - car) + :duration (= ?duration 3) + :condition (and + (at start (car-clear ?car)) + (at start (car-clear ?cardest)) + (at start (behind-car ?car ?carsrc)) + (at start (at-curb ?cardest)) + ) + :effect (and + (at start (not (car-clear ?cardest))) + (at end (car-clear ?carsrc)) + (at end (behind-car ?car ?cardest)) + (at start (not (behind-car ?car ?carsrc))) + (at end (increase (total-cost) 3)) + ) + ) +) + diff --git a/unified_planning/test/pddl/parking_action_cost/problem.pddl b/unified_planning/test/pddl/parking_action_cost/problem.pddl new file mode 100644 index 000000000..e87fe524d --- /dev/null +++ b/unified_planning/test/pddl/parking_action_cost/problem.pddl @@ -0,0 +1,23 @@ +(define (problem parking) + (:domain parking) + (:objects + car_00 car_01 - car + curb_00 curb_01 curb_02 curb_03 - curb + ) + (:init + (at-curb-num car_00 curb_00) + (at-curb-num car_01 curb_01) + (curb-clear curb_02) + (curb-clear curb_03) + (car-clear car_00) + (car-clear car_01) + (= (total-cost) 0) + ) + (:goal + (and + (at-curb-num car_00 curb_02) + (at-curb-num car_01 curb_03) + ) + ) +(:metric minimize (total-cost)) +) diff --git a/unified_planning/test/test_pddl_io.py b/unified_planning/test/test_pddl_io.py index 765dc7afb..5ac9b1e16 100644 --- a/unified_planning/test/test_pddl_io.py +++ b/unified_planning/test/test_pddl_io.py @@ -449,6 +449,33 @@ def test_matchcellar_reader(self): problem_2 = reader.parse_problem_string(domain_str, problem_str) self.assertEqual(problem, problem_2) + def test_parking_reader(self): + reader = PDDLReader() + + domain_filename = os.path.join( + PDDL_DOMAINS_PATH, "parking_action_cost", "domain.pddl" + ) + problem_filename = os.path.join( + PDDL_DOMAINS_PATH, "parking_action_cost", "problem.pddl" + ) + problem = reader.parse_problem(domain_filename, problem_filename) + + self.assertIsNotNone(problem) + self.assertEqual(len(problem.fluents), 5) + self.assertEqual(len(problem.actions), 4) + self.assertEqual(len(list(problem.objects(problem.user_type("car")))), 2) + self.assertEqual(len(list(problem.objects(problem.user_type("curb")))), 4) + self.assertEqual(len(problem.quality_metrics), 1) + self.assertTrue(problem.quality_metrics[0].is_minimize_action_costs()) + + with open(domain_filename, "r", encoding="utf-8") as file: + domain_str = file.read() + with open(problem_filename, "r", encoding="utf-8") as file: + problem_str = file.read() + + problem_2 = reader.parse_problem_string(domain_str, problem_str) + self.assertEqual(problem, problem_2) + def _test_htn_transport_reader(self, problem): assert isinstance(problem, up.model.htn.HierarchicalProblem) self.assertEqual(5, len(problem.fluents))