Skip to content

Commit 6f29a46

Browse files
committed
add critical path to data analysis
1 parent bdb9ccb commit 6f29a46

6 files changed

Lines changed: 130 additions & 12 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ A Python wrapper using JuliaPy for the PDDL.jl parser package and implementing i
2121
- A*
2222
- Goal Count Heuristic
2323
- Delete Relaxation Heuristics (Hmax, Hadd)
24+
- Critical Path Heuristic
2425

2526
# Dependencies
2627

jupyddl/automated_planner.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .a_star import AStarBestFirstSearch
55
from .greedy_best_first import GreedyBestFirstSearch
66
from .metrics import Metrics
7-
from .heuristics import BasicHeuristic, DeleteRelaxationHeuristic
7+
from .heuristics import BasicHeuristic, DeleteRelaxationHeuristic, CriticalPathHeuristic
88
import coloredlogs
99
import logging
1010
import julia
@@ -27,10 +27,9 @@ def __init__(self, domain_path, problem_path, log_level="DEBUG"):
2727
self.initial_state = self.pddl.initialize(self.problem)
2828
self.goals = self.__flatten_goal()
2929
self.available_heuristics = [
30-
"basic/zero",
31-
"basic/goal_count",
32-
"delete_relaxation/h_add",
33-
"delete_relaxation/h_max",
30+
"basic/zero", "basic/goal_count",
31+
"delete_relaxation/h_add", "delete_relaxation/h_max",
32+
"critical_path/1", "critical_path/2", "critical_path/3",
3433
]
3534

3635
# Logger
@@ -158,6 +157,8 @@ def astar_best_first_search(
158157
heuristic = BasicHeuristic(self, heuristic_key)
159158
elif "delete_relaxation" in heuristic_key:
160159
heuristic = DeleteRelaxationHeuristic(self, heuristic_key)
160+
elif "critical_path" in heuristic_key:
161+
heuristic = CriticalPathHeuristic(self, int(heuristic_key[-1]))
161162
else:
162163
logging.fatal("Not yet implemented")
163164
return [], Metrics()
@@ -178,6 +179,8 @@ def greedy_best_first_search(
178179
heuristic = BasicHeuristic(self, "basic/goal_count")
179180
elif "delete_relaxation" in heuristic_key:
180181
heuristic = DeleteRelaxationHeuristic(self, heuristic_key)
182+
elif "critical_path" in heuristic_key:
183+
heuristic = CriticalPathHeuristic(self, int(heuristic_key[-1]))
181184
else:
182185
logging.fatal("Not yet implemented")
183186
return [], Metrics()

jupyddl/data_analyst.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,8 @@ def plot_metrics(self):
642642
metrics_dict["A* [Goal_Count]"] = []
643643
metrics_dict["A* [H_Add]"] = []
644644
metrics_dict["A* [H_Max]"] = []
645+
metrics_dict["A* [Critical_Path (H2)]"] = []
646+
metrics_dict["A* [Critical_Path (H3)]"] = []
645647
logging.debug("Computation of all metrics for all domains registered...")
646648
for problem, domain in self.__get_all_pddl_from_data():
647649
logging.debug("Loading new PDDL instance planned with Dijkstra...")
@@ -660,20 +662,28 @@ def plot_metrics(self):
660662
_, metrics_dfs = apla.depth_first_search(
661663
node_bound=metrics_bfs.n_opened * 2
662664
)
665+
_, metrics_cp2 = apla.astar_best_first_search(
666+
heuristic_key="critical_path/2"
667+
)
668+
_, metrics_cp3 = apla.astar_best_first_search(
669+
heuristic_key="critical_path/3"
670+
)
663671
metrics_dict["A* [Zero]"].append(metrics_dij)
664672
metrics_dict["DFS"].append(metrics_dfs)
665673
metrics_dict["BFS"].append(metrics_bfs)
666674
metrics_dict["A* [Goal_Count]"].append(metrics_agc)
667675
metrics_dict["A* [H_Add]"].append(metrics_ahadd)
668676
metrics_dict["A* [H_Max]"].append(metrics_ahmax)
677+
metrics_dict["A* [Critical_Path (H2)]"].append(metrics_cp2)
678+
metrics_dict["A* [Critical_Path (H3)]"].append(metrics_cp3)
669679

670680
plot_dict = dict()
671681

672682
for key, val in metrics_dict.items():
673683
plot_dict[key] = dict()
674684
plot_dict[key]["Search Runtime (s)"] = [m.runtime for m in val]
675-
plot_dict[key]["Heuristics Average Runtime (s)"] = [
676-
m.get_average_heuristic_runtime() for m in val
685+
plot_dict[key]["Total Heuristics Runtime (s)"] = [
686+
sum(m.heuristic_runtimes) for m in val
677687
]
678688
plot_dict[key]["Number of Expanded Nodes"] = [m.n_expended for m in val]
679689
plot_dict[key]["Number of Opened Nodes"] = [m.n_opened for m in val]

jupyddl/heuristics.py

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ def __goal_count_heuristic(self, state):
2929
count += 1
3030
return count
3131

32-
3332
class DeleteRelaxationHeuristic:
3433
def __init__(self, automated_planner, heuristic_key):
3534
class DRHCache:
@@ -127,3 +126,109 @@ def __facts_eq(self, facts_dict, facts_set):
127126
if not (str(f) in fact_costs_str.keys()):
128127
return False
129128
return True
129+
130+
class CriticalPathHeuristic:
131+
def __init__(self, automated_planner, critical_path_level=1):
132+
class CPCache:
133+
def __init__(self, domain=None, axioms=None, preconds=None, additions=None):
134+
self.domain = domain
135+
self.axioms = axioms
136+
self.preconds = preconds
137+
self.additions = additions
138+
139+
self.automated_planner = automated_planner
140+
self.cache = CPCache()
141+
if critical_path_level > 3:
142+
logging.warning("Critical Path level is only implemented until 3, forcing it to 3.")
143+
self.critical_path_level = 3
144+
if critical_path_level < 1:
145+
logging.warning("Critical Path level has to be at least 1, forcing it to 1.")
146+
self.critical_path_level = 1
147+
else:
148+
self.critical_path_level = critical_path_level
149+
self.has_been_precomputed = False
150+
self.__pre_compute()
151+
# return self.heuristic_keys[self.current_h](state)
152+
153+
def compute(self, state):
154+
if not self.has_been_precomputed:
155+
self.__pre_compute()
156+
domain = self.cache.domain
157+
goals = self.automated_planner.goals
158+
types = state.types
159+
facts = state.facts
160+
fact_costs = self.automated_planner.pddl.init_facts_costs(facts)
161+
while True:
162+
facts, state = self.automated_planner.pddl.get_facts_and_state(
163+
fact_costs, types
164+
)
165+
if self.automated_planner.satisfies(goals, state):
166+
costs = []
167+
fact_costs_str = dict([(str(k), val) for k, val in fact_costs.items()])
168+
if self.critical_path_level == 1:
169+
for g in goals:
170+
if str(g) in fact_costs_str:
171+
costs.append(fact_costs_str[str(g)])
172+
if self.critical_path_level == 2:
173+
pairs_of_goals = [(g1, g2) for g1 in goals for g2 in goals if g1 != g2]
174+
for gs in pairs_of_goals:
175+
if str(gs[0]) in fact_costs_str and str(gs[1]) in fact_costs_str:
176+
costs.append(fact_costs_str[str(gs[0])] + fact_costs_str[str(gs[1])])
177+
if self.critical_path_level == 3:
178+
triplets_of_goals = [(g1, g2, g3) for g1 in goals for g2 in goals for g3 in goals if g1 != g2 and g1 != g3 and g2 != g3]
179+
for gs in triplets_of_goals:
180+
if str(gs[0]) in fact_costs_str and str(gs[1]) in fact_costs_str and str(gs[2]) in fact_costs_str:
181+
costs.append(fact_costs_str[str(gs[0])] + fact_costs_str[str(gs[1])] + fact_costs_str[str(gs[2])])
182+
costs.insert(0, 0)
183+
return max(costs)
184+
185+
for ax in self.cache.axioms:
186+
fact_costs = (
187+
self.automated_planner.pddl.compute_costs_one_step_derivation(
188+
facts, fact_costs, ax, "max"
189+
)
190+
)
191+
192+
actions = self.automated_planner.available_actions(state)
193+
for act in actions:
194+
fact_costs = self.automated_planner.pddl.compute_cost_action_effect(
195+
fact_costs, act, domain, self.cache.additions, "max"
196+
)
197+
198+
if len(fact_costs) == self.automated_planner.pddl.length(
199+
facts
200+
) and self.__facts_eq(fact_costs, facts):
201+
break
202+
203+
return float("inf")
204+
205+
def __pre_compute(self):
206+
if self.has_been_precomputed:
207+
return
208+
domain = self.automated_planner.domain
209+
domain, axioms = self.automated_planner.pddl.compute_hsp_axioms(domain)
210+
# preconditions = dict()
211+
additions = dict()
212+
self.automated_planner.pddl.cache_global_preconditions(domain)
213+
for name, definition in domain.actions.items():
214+
additions[name] = self.automated_planner.pddl.effect_diff(
215+
definition.effect
216+
).add
217+
self.cache.additions = additions
218+
self.cache.preconds = self.automated_planner.pddl.g_preconditions
219+
self.cache.domain = domain
220+
self.cache.axioms = axioms
221+
self.has_been_precomputed = True
222+
223+
def __h_add(self, costs):
224+
return sum(costs)
225+
226+
def __h_max(self, costs):
227+
return max(costs)
228+
229+
def __facts_eq(self, facts_dict, facts_set):
230+
fact_costs_str = dict([(str(k), val) for k, val in facts_dict.items()])
231+
for f in facts_set:
232+
if not (str(f) in fact_costs_str.keys()):
233+
return False
234+
return True

jupyddl/metrics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ def get_average_heuristic_runtime(self):
1717

1818
def __str__(self):
1919
if self.heuristic_runtimes:
20-
av = self.get_average_heuristic_runtime()
21-
w = av / sum(self.heuristic_runtimes) * 100
20+
av = sum(self.heuristic_runtimes)
21+
w = sum(self.heuristic_runtimes) / self.runtime * 100
2222
else:
2323
av = 0
2424
w = 0
25-
return "Expanded %d state(s).\nOpened %d state(s).\nReopened %d state(s).\nEvaluated %d state(s).\nGenerated %d state(s).\nDead ends: %d state(s).\nRuntime: %.2fs.\nAverage heuristic runtime: %.2fs\nComputational weight of heuristic in the search: %.2f%%" % (
25+
return "Expanded %d state(s).\nOpened %d state(s).\nReopened %d state(s).\nEvaluated %d state(s).\nGenerated %d state(s).\nDead ends: %d state(s).\nRuntime: %.2fs.\nTotal heuristic runtime: %.2fs\nComputational weight of heuristic in the search: %.2f%%" % (
2626
self.n_expended,
2727
self.n_opened,
2828
self.n_reopened,

tests/test_automated_planner.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import sys
44
from os import path
5-
65
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
76
from jupyddl.automated_planner import AutomatedPlanner
87

0 commit comments

Comments
 (0)