Skip to content

Commit 42f18d0

Browse files
authored
Merge pull request #79 from APLA-Toolbox/hsp-heuristic
[Heuristics] Update architecture / Implement HSP
2 parents b536d40 + 82ffb89 commit 42f18d0

16 files changed

Lines changed: 273 additions & 114 deletions

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ You should have a `pddl-examples` folder containing PDDL instances.
6969

7070
```python
7171
from jupyddl import AutomatedPlanner # takes some time because it has to instantiate the Julia interface
72-
apl = AutomatedPlanner("pddl-examples/flip/domain.pddl", "pddl-examples/flip/problem.pddl)
72+
apl = AutomatedPlanner("pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl)
7373

7474
apl.initial_state
7575
<PyCall.jlwrap PDDL.State(Set(Julog.Term[row(r1), column(c3), row(r3), row(r2), column(c2), column(c1)]), Set(Julog.Term[white(r2, c1), white(r1, c2), white(r3, c2), white(r2, c3)]), Dict{Symbol,Any}())>
@@ -102,17 +102,17 @@ from jupyddl import DataAnalyst
102102
da = DataAnalyst()
103103
da.plot_astar() # plots complexity statistics for all the problem.pddl/domain.pddl couples in the pddl-examples/ folder
104104

105-
da.plot_astar(problem="pddl-examples/flip/problem.pddl", domain="pddl-examples/flip/domain.pddl") # scatter complexity statistics for the provided pddl
105+
da.plot_astar(problem="pddl-examples/dinner/problem.pddl", domain="pddl-examples/dinner/domain.pddl") # scatter complexity statistics for the provided pddl
106106

107-
da.plot_astar(heuristic_key="zero") # use h=0 instead of goal_count for your computation
107+
da.plot_astar(heuristic_key="basic/zero") # use h=0 instead of goal_count for your computation
108108

109109
da.plot_dfs() # same as astar
110110

111111
da.comparative_data_plot() # Run all planners on the pddl-examples folder and plots them on the same figure, data is stored in a data.json file
112112

113113
da.comparative_data_plot(astar=False) # Exclude astar from the comparative plot
114114

115-
da.comparative_data_plot(heuristic_key="zero") # use zero heuristic for h based planners
115+
da.comparative_data_plot(heuristic_key="basic/zero") # use zero heuristic for h based planners
116116

117117
da.comparative_data_plot(collect_new_data=False) # uses data.json to plot the data
118118
```

jupyddl/a_star.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from .heuristics import zero_heuristic
21
from .node import Node
32
import logging
43
import math

jupyddl/automated_planner.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .dfs import DepthFirstSearch
33
from .dijkstra import DijkstraBestFirstSearch
44
from .a_star import AStarBestFirstSearch
5-
from .heuristics import goal_count_heuristic, zero_heuristic
5+
from .heuristics import BasicHeuristic, DeleteRelaxationHeuristic
66
import coloredlogs
77
import logging
88
import julia
@@ -18,13 +18,18 @@ class AutomatedPlanner:
1818
def __init__(self, domain_path, problem_path, log_level="DEBUG"):
1919
# Planning Tool
2020
self.pddl = PDDL
21+
self.domain_path = domain_path
22+
self.problem_path = problem_path
2123
self.domain = self.pddl.load_domain(domain_path)
2224
self.problem = self.pddl.load_problem(problem_path)
2325
self.initial_state = self.pddl.initialize(self.problem)
2426
self.goals = self.__flatten_goal()
25-
self.available_heuristics = dict()
26-
self.available_heuristics["goal_count"] = goal_count_heuristic
27-
self.available_heuristics["zero"] = zero_heuristic
27+
self.available_heuristics = [
28+
"basic/zero",
29+
"basic/goal_count",
30+
"delete_relaxation/h_add",
31+
"delete_relaxation/h_max",
32+
]
2833

2934
# Logger
3035
self.__init_logger(log_level)
@@ -53,7 +58,7 @@ def __init_logger(self, log_level):
5358
)
5459

5560
def display_available_heuristics(self):
56-
print(list(self.available_heuristics.keys()))
61+
print(self.available_heuristics)
5762

5863
def transition(self, state, action):
5964
return self.pddl.transition(self.domain, state, action, check=False)
@@ -125,8 +130,15 @@ def dijktra_best_first_search(self):
125130

126131
return path, total_time, opened_nodes
127132

128-
def astar_best_first_search(self, heuristic=goal_count_heuristic):
129-
astar = AStarBestFirstSearch(self, heuristic)
133+
def astar_best_first_search(self, heuristic_key="basic/goal_count"):
134+
if "basic" in heuristic_key:
135+
heuristic = BasicHeuristic(self, heuristic_key)
136+
elif "delete_relaxation" in heuristic_key:
137+
heuristic = DeleteRelaxationHeuristic(self, heuristic_key)
138+
else:
139+
logging.fatal("Not yet implemented")
140+
exit()
141+
astar = AStarBestFirstSearch(self, heuristic.compute)
130142
last_node, total_time, opened_nodes = astar.search()
131143
path = self.__retrace_path(last_node)
132144

jupyddl/data_analyst.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919
class DataAnalyst:
2020
def __init__(self):
2121
logging.info("Instantiating data analyst...")
22-
self.available_heuristics = ["goal_count", "zero"]
22+
self.available_heuristics = [
23+
"basic/goal_count",
24+
"basic/zero",
25+
"delete_relaxation/h_add",
26+
"delete_relaxation/h_max",
27+
]
2328

2429
def __get_all_pddl_from_data(self, max_pddl_instances=-1):
2530
tested_files = []
@@ -42,7 +47,7 @@ def __get_all_pddl_from_data(self, max_pddl_instances=-1):
4247
return domains_problems
4348
return domains_problems
4449
return [
45-
("pddl-examples/flip/problem.pddl", "pddl-examples/flip/domain.pddl"),
50+
("pddl-examples/dinner/problem.pddl", "pddl-examples/dinner/domain.pddl"),
4651
("pddl-examples/dinner/problem.pddl", "pddl-examples/dinner/domain.pddl"),
4752
]
4853

@@ -75,7 +80,7 @@ def __gather_data_astar(
7580
self,
7681
domain_path="",
7782
problem_path="",
78-
heuristic_key="goal_count",
83+
heuristic_key="basic/goal_count",
7984
max_pddl_instances=-1,
8085
):
8186
has_multiple_files_tested = True
@@ -84,13 +89,17 @@ def __gather_data_astar(
8489
for problem, domain in self.__get_all_pddl_from_data(
8590
max_pddl_instances=max_pddl_instances
8691
):
87-
logging.debug("Loading new PDDL instance planned with A*...")
92+
logging.debug(
93+
"Loading new PDDL instance planned with A* [ "
94+
+ heuristic_key
95+
+ " ]"
96+
)
8897
logging.debug("Domain: " + domain)
8998
logging.debug("Problem: " + problem)
9099
apla = AutomatedPlanner(domain, problem)
91100
if heuristic_key in apla.available_heuristics:
92101
path, total_time, opened_nodes = apla.astar_best_first_search(
93-
heuristic=apla.available_heuristics[heuristic_key]
102+
heuristic_key=heuristic_key
94103
)
95104
else:
96105
logging.critical(
@@ -112,7 +121,7 @@ def __gather_data_astar(
112121
apla = AutomatedPlanner(domain_path, problem_path)
113122
if heuristic_key in apla.available_heuristics:
114123
path, total_time, opened_nodes = apla.astar_best_first_search(
115-
heuristic=apla.available_heuristics[heuristic_key]
124+
heuristic_key=heuristic_key
116125
)
117126
else:
118127
logging.critical(
@@ -124,7 +133,11 @@ def __gather_data_astar(
124133
return [0], [0], has_multiple_files_tested
125134

126135
def plot_astar(
127-
self, heuristic_key="goal_count", domain="", problem="", max_pddl_instances=-1
136+
self,
137+
heuristic_key="basic/goal_count",
138+
domain="",
139+
problem="",
140+
max_pddl_instances=-1,
128141
):
129142
if bool(not problem) != bool(not domain):
130143
logging.warning(
@@ -288,7 +301,7 @@ def plot_dijkstra(self, problem="", domain="", max_pddl_instances=-1):
288301

289302
def __gather_data(
290303
self,
291-
heuristic_key="goal_count",
304+
heuristic_key="basic/goal_count",
292305
astar=True,
293306
bfs=True,
294307
dfs=True,
@@ -354,10 +367,7 @@ def comparative_astar_heuristic_plot(
354367
times_y.append(data[node_opened])
355368

356369
ax.plot(
357-
nodes_sorted,
358-
times_y,
359-
"-o",
360-
label=h,
370+
nodes_sorted, times_y, "-o", label=h,
361371
)
362372

363373
plt.title("A* heuristics complexity comparison")
@@ -374,7 +384,7 @@ def comparative_data_plot(
374384
dijkstra=True,
375385
domain="",
376386
problem="",
377-
heuristic_key="goal_count",
387+
heuristic_key="basic/goal_count",
378388
collect_new_data=True,
379389
max_pddl_instances=-1,
380390
):
@@ -429,10 +439,7 @@ def comparative_data_plot(
429439
for node_opened in nodes_sorted:
430440
times_y.append(data[node_opened])
431441
ax.plot(
432-
nodes_sorted,
433-
times_y,
434-
"-o",
435-
label=planner,
442+
nodes_sorted, times_y, "-o", label=planner,
436443
)
437444
plt.title("Planners complexity comparison")
438445
plt.legend(loc="upper left")

jupyddl/dijkstra.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
from .heuristics import zero_heuristic
21
from .node import Node
32
import logging
43
import math
54
from datetime import datetime as timestamp
65
from time import time as now
76

87

8+
def zero_heuristic():
9+
return 0
10+
11+
912
class DijkstraBestFirstSearch:
1013
def __init__(self, automated_planner):
1114
self.automated_planner = automated_planner

jupyddl/heuristics.py

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,125 @@
1-
def zero_heuristic(state, automated_planner):
2-
return 0
1+
import logging
32

43

5-
def goal_count_heuristic(state, automated_planner):
6-
count = 0
7-
for goal in automated_planner.goals:
8-
if not automated_planner.state_has_term(state, goal):
9-
count += 1
10-
return count
4+
class BasicHeuristic:
5+
def __init__(self, automated_planner, heuristic_key):
6+
self.automated_planner = automated_planner
7+
self.heuristic_keys = {
8+
"basic/zero": self.__zero_heuristic,
9+
"basic/goal_count": self.__goal_count_heuristic,
10+
}
11+
if heuristic_key not in list(self.heuristic_keys.keys()):
12+
logging.warning(
13+
"Heuristic key isn't registered, forcing it to [basic/goal_count]"
14+
)
15+
heuristic_key = "basic/goal_count"
16+
17+
self.current_h = heuristic_key
18+
19+
def compute(self, state):
20+
return self.heuristic_keys[self.current_h](state)
21+
22+
def __zero_heuristic(self, state):
23+
return 0
24+
25+
def __goal_count_heuristic(self, state):
26+
count = 0
27+
for goal in self.automated_planner.goals:
28+
if not self.automated_planner.state_has_term(state, goal):
29+
count += 1
30+
return count
31+
32+
33+
class DeleteRelaxationHeuristic:
34+
def __init__(self, automated_planner, heuristic_key):
35+
class DRHCache:
36+
def __init__(self, domain=None, axioms=None, preconds=None, additions=None):
37+
self.domain = domain
38+
self.axioms = axioms
39+
self.preconds = preconds
40+
self.additions = additions
41+
42+
self.automated_planner = automated_planner
43+
self.cache = DRHCache()
44+
self.heuristic_keys = {
45+
"delete_relaxation/h_add": self.__h_add,
46+
"delete_relaxation/h_max": self.__h_max,
47+
}
48+
if heuristic_key not in list(self.heuristic_keys.keys()):
49+
logging.warning(
50+
"Heuristic key isn't registered, forcing it to [delete_relaxation/h_add]"
51+
)
52+
heuristic_key = "delete_relaxation/h_add"
53+
54+
self.current_h = heuristic_key
55+
self.has_been_precomputed = False
56+
self.__pre_compute()
57+
# return self.heuristic_keys[self.current_h](state)
58+
59+
def compute(self, state):
60+
if not self.has_been_precomputed:
61+
self.__pre_compute()
62+
domain = self.cache.domain
63+
goals = self.automated_planner.goals
64+
types = state.types
65+
facts = state.facts
66+
fact_costs = self.automated_planner.pddl.init_facts_costs(facts)
67+
while not (
68+
len(fact_costs) == self.automated_planner.pddl.length(facts)
69+
and self.__facts_eq(fact_costs, facts)
70+
):
71+
facts, state = self.automated_planner.pddl.get_facts_and_state(
72+
fact_costs, types
73+
)
74+
if self.automated_planner.satisfies(goals, state):
75+
costs = []
76+
fact_costs_str = dict([(str(k), val) for k, val in fact_costs.items()])
77+
for g in goals:
78+
if str(g) in fact_costs_str:
79+
costs.append(fact_costs_str[str(g)])
80+
costs.insert(0, 0)
81+
return self.heuristic_keys[self.current_h](costs)
82+
83+
for ax in self.cache.axioms:
84+
fact_costs = self.automated_planner.pddl.compute_costs_one_step_derivation(
85+
facts, fact_costs, ax, self.current_h
86+
)
87+
88+
actions = self.automated_planner.available_actions(state)
89+
if not actions:
90+
break
91+
for act in actions:
92+
fact_costs = self.automated_planner.pddl.compute_cost_action_effect(
93+
fact_costs, act, domain, self.cache.additions, self.current_h
94+
)
95+
return float("inf")
96+
97+
def __pre_compute(self):
98+
if self.has_been_precomputed:
99+
return
100+
domain = self.automated_planner.domain
101+
domain, axioms = self.automated_planner.pddl.compute_hsp_axioms(domain)
102+
# preconditions = dict()
103+
additions = dict()
104+
self.automated_planner.pddl.cache_global_preconditions(domain)
105+
for name, definition in domain.actions.items():
106+
additions[name] = self.automated_planner.pddl.effect_diff(
107+
definition.effect
108+
).add
109+
self.cache.additions = additions
110+
self.cache.preconds = self.automated_planner.pddl.g_preconditions
111+
self.cache.domain = domain
112+
self.cache.axioms = axioms
113+
self.has_been_precomputed = True
114+
115+
def __h_add(self, costs):
116+
return sum(costs)
117+
118+
def __h_max(self, costs):
119+
return max(costs)
120+
121+
def __facts_eq(self, facts_dict, facts_set):
122+
for f in facts_set:
123+
if not (f in facts_dict.keys()):
124+
return False
125+
return True

jupyddl/node.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def __init__(
2222
self.g_cost = temp_cost
2323
if heuristic_based:
2424
if heuristic:
25-
self.h_cost = heuristic(state, automated_planner)
25+
self.h_cost = heuristic(state)
2626
else:
2727
automated_planner.logger.warning(
2828
"Heuristic function wasn't found, forcing it to return zero [Best practice: use the zero_heuristic function]"
@@ -38,7 +38,7 @@ def __init__(
3838
self.g_cost = g_cost
3939
if heuristic_based:
4040
if heuristic:
41-
self.h_cost = heuristic(state, automated_planner)
41+
self.h_cost = heuristic(state)
4242
else:
4343
automated_planner.logger.warning(
4444
"Heuristic function wasn't found, forcing it to return zero [Best practice: use the zero_heuristic function]"

0 commit comments

Comments
 (0)