Skip to content

Commit 1f40c6a

Browse files
authored
Merge pull request #83 from APLA-Toolbox/safe-hsp-heuristics
Safer HSP Heuristics
2 parents 5aee9f4 + 8e2d2eb commit 1f40c6a

11 files changed

Lines changed: 200 additions & 57 deletions

jupyddl/automated_planner.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,14 @@ def transition(self, state, action):
6464
return self.pddl.transition(self.domain, state, action, check=False)
6565

6666
def available_actions(self, state):
67-
return self.pddl.available(state, self.domain)
67+
try:
68+
return self.pddl.available(state, self.domain)
69+
except (RuntimeError, TypeError, NameError):
70+
self.logger.warning(
71+
"Runtime, Type or Name error occured when fetching available action from state"
72+
+ str(state)
73+
)
74+
return []
6875

6976
def satisfies(self, asserted_state, state):
7077
return self.pddl.satisfy(asserted_state, state, self.domain)[0]
@@ -137,7 +144,7 @@ def astar_best_first_search(self, heuristic_key="basic/goal_count"):
137144
heuristic = DeleteRelaxationHeuristic(self, heuristic_key)
138145
else:
139146
logging.fatal("Not yet implemented")
140-
exit()
147+
return [], 0, 0
141148
astar = AStarBestFirstSearch(self, heuristic.compute)
142149
last_node, total_time, opened_nodes = astar.search()
143150
path = self.__retrace_path(last_node)

jupyddl/data_analyst.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,7 @@ def comparative_astar_heuristic_plot(
367367
times_y.append(data[node_opened])
368368

369369
ax.plot(
370-
nodes_sorted,
371-
times_y,
372-
"-o",
373-
label=h,
370+
nodes_sorted, times_y, "-o", label=h,
374371
)
375372

376373
plt.title("A* heuristics complexity comparison")
@@ -442,10 +439,7 @@ def comparative_data_plot(
442439
for node_opened in nodes_sorted:
443440
times_y.append(data[node_opened])
444441
ax.plot(
445-
nodes_sorted,
446-
times_y,
447-
"-o",
448-
label=planner,
442+
nodes_sorted, times_y, "-o", label=planner,
449443
)
450444
plt.title("Planners complexity comparison")
451445
plt.legend(loc="upper left")

jupyddl/heuristics.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ def compute(self, state):
6464
types = state.types
6565
facts = state.facts
6666
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-
):
67+
while True:
7168
facts, state = self.automated_planner.pddl.get_facts_and_state(
7269
fact_costs, types
7370
)
@@ -81,19 +78,21 @@ def compute(self, state):
8178
return self.heuristic_keys[self.current_h](costs)
8279

8380
for ax in self.cache.axioms:
84-
fact_costs = (
85-
self.automated_planner.pddl.compute_costs_one_step_derivation(
86-
facts, fact_costs, ax, self.current_h
87-
)
81+
fact_costs = self.automated_planner.pddl.compute_costs_one_step_derivation(
82+
facts, fact_costs, ax, self.current_h
8883
)
8984

9085
actions = self.automated_planner.available_actions(state)
91-
if not actions:
92-
break
9386
for act in actions:
9487
fact_costs = self.automated_planner.pddl.compute_cost_action_effect(
9588
fact_costs, act, domain, self.cache.additions, self.current_h
9689
)
90+
91+
if len(fact_costs) == self.automated_planner.pddl.length(
92+
facts
93+
) and self.__facts_eq(fact_costs, facts):
94+
break
95+
9796
return float("inf")
9897

9998
def __pre_compute(self):
@@ -121,7 +120,8 @@ def __h_max(self, costs):
121120
return max(costs)
122121

123122
def __facts_eq(self, facts_dict, facts_set):
123+
fact_costs_str = dict([(str(k), val) for k, val in facts_dict.items()])
124124
for f in facts_set:
125-
if not (f in facts_dict.keys()):
125+
if not (str(f) in fact_costs_str.keys()):
126126
return False
127127
return True

tests/test_automated_planner.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,40 @@ def test_state_assertion():
4444
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
4545
)
4646
assert not apla.satisfies(apla.problem.goal, apla.initial_state)
47+
48+
def test_bfs():
49+
apla = AutomatedPlanner(
50+
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
51+
)
52+
path, _, _ = apla.breadth_first_search()
53+
plan = apla.get_actions_from_path(path)
54+
plan_state = apla.get_state_def_from_path(path)
55+
assert plan and plan_state
56+
57+
def test_dfs():
58+
apla = AutomatedPlanner(
59+
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
60+
)
61+
path, _, _ = apla.depth_first_search()
62+
plan = apla.get_actions_from_path(path)
63+
plan_state = apla.get_state_def_from_path(path)
64+
assert plan and plan_state
65+
66+
def test_dij():
67+
apla = AutomatedPlanner(
68+
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
69+
)
70+
path, _, _ = apla.dijktra_best_first_search()
71+
plan = apla.get_actions_from_path(path)
72+
plan_state = apla.get_state_def_from_path(path)
73+
assert plan and plan_state
74+
75+
76+
def test_astar():
77+
apla = AutomatedPlanner(
78+
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
79+
)
80+
path, _, _ = apla.astar_best_first_search()
81+
plan = apla.get_actions_from_path(path)
82+
plan_state = apla.get_state_def_from_path(path)
83+
assert plan and plan_state
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
77
from jupyddl.automated_planner import AutomatedPlanner
88
from jupyddl.a_star import AStarBestFirstSearch
9-
from jupyddl.heuristics import DeleteRelaxationHeuristic, BasicHeuristic
9+
from jupyddl.heuristics import BasicHeuristic
1010

1111

1212
def test_astar_basic():
@@ -18,15 +18,6 @@ def test_astar_basic():
1818
assert astar.init.h_cost == heuristic.compute(apla.initial_state)
1919

2020

21-
def test_astar_delete_relaxation():
22-
apla = AutomatedPlanner(
23-
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
24-
)
25-
heuristic = DeleteRelaxationHeuristic(apla, "delete_relaxation/h_max")
26-
astar = AStarBestFirstSearch(apla, heuristic.compute)
27-
assert astar.init.h_cost == heuristic.compute(apla.initial_state)
28-
29-
3021
def test_astar_goal():
3122
apla = AutomatedPlanner(
3223
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
@@ -43,3 +34,19 @@ def test_astar_path_length():
4334
)
4435
path, _, _ = apla.astar_best_first_search()
4536
assert len(path) > 0
37+
38+
39+
def test_astar_path_no_path():
40+
apla = AutomatedPlanner(
41+
"pddl-examples/vehicle/domain.pddl", "pddl-examples/vehicle/problem.pddl"
42+
)
43+
path, _, _ = apla.astar_best_first_search()
44+
assert len(path) == 0
45+
46+
47+
def test_astar_path_no_heuristic():
48+
apla = AutomatedPlanner(
49+
"pddl-examples/flip/domain.pddl", "pddl-examples/flip/problem.pddl"
50+
)
51+
p, t, c = apla.astar_best_first_search(heuristic_key="idontexist")
52+
assert not p and not t and not c
Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from jupyddl.automated_planner import AutomatedPlanner
2-
from jupyddl.dijkstra import DijkstraBestFirstSearch
2+
from jupyddl.dijkstra import DijkstraBestFirstSearch, zero_heuristic
33
from jupyddl.a_star import AStarBestFirstSearch
44
from jupyddl.bfs import BreadthFirstSearch
55
from jupyddl.heuristics import BasicHeuristic, DeleteRelaxationHeuristic
@@ -37,6 +37,33 @@ def test_search_dijkstra():
3737
assert res[-1] > 0 # Assert that it visited some nodes
3838

3939

40+
def test_search_dijkstra_no_path():
41+
apla = AutomatedPlanner(
42+
"pddl-examples/vehicle/domain.pddl", "pddl-examples/vehicle/problem.pddl"
43+
)
44+
dijk = DijkstraBestFirstSearch(apla)
45+
res = dijk.search() # Goal, computation_time, opened_nodes(in this order)
46+
assert not res[0]
47+
48+
49+
def test_search_dfs_no_path():
50+
apla = AutomatedPlanner(
51+
"pddl-examples/vehicle/domain.pddl", "pddl-examples/vehicle/problem.pddl"
52+
)
53+
dfs = DepthFirstSearch(apla)
54+
res = dfs.search() # Goal, computation_time, opened_nodes(in this order)
55+
assert not res[0]
56+
57+
58+
def test_search_bfs_no_path():
59+
apla = AutomatedPlanner(
60+
"pddl-examples/vehicle/domain.pddl", "pddl-examples/vehicle/problem.pddl"
61+
)
62+
bfs = BreadthFirstSearch(apla)
63+
res = bfs.search() # Goal, computation_time, opened_nodes(in this order)
64+
assert not res[0]
65+
66+
4067
def test_search_astar_basic():
4168
apla = AutomatedPlanner(
4269
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
@@ -48,12 +75,16 @@ def test_search_astar_basic():
4875
assert res[-1] > 0 # Assert that it visited at least one node
4976

5077

51-
def test_search_astar_delete_relaxation():
78+
def test_search_astar_basic_no_path():
5279
apla = AutomatedPlanner(
53-
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
80+
"pddl-examples/vehicle/domain.pddl", "pddl-examples/vehicle/problem.pddl"
5481
)
55-
heuristic = DeleteRelaxationHeuristic(apla, "delete_relaxation/h_max")
82+
heuristic = BasicHeuristic(apla, "basic/goal_count")
5683
astar = AStarBestFirstSearch(apla, heuristic.compute)
5784
res = astar.search() # Goal, computation_time, opened_nodes(in this order)
58-
assert res[1] != 0 # Assert that it took time to compute
59-
assert res[-1] > 0 # Assert that it visited at least one node
85+
assert not res[0]
86+
87+
88+
def test_zero_heuristic():
89+
assert zero_heuristic() == 0
90+

tests/test_heuristics.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def test_zero_heuristic():
1818
apla = AutomatedPlanner(
1919
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
2020
)
21+
apla.display_available_heuristics()
2122
heuristic = hs.BasicHeuristic(apla, "basic/zero")
2223
h = heuristic.compute(apla.initial_state)
2324
assert h == 0
@@ -27,6 +28,7 @@ def test_goal_count_heuristic():
2728
apla = AutomatedPlanner(
2829
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
2930
)
31+
apla.display_available_heuristics()
3032
heuristic = hs.BasicHeuristic(apla, "basic/goal_count")
3133
h = heuristic.compute(apla.initial_state)
3234
assert h != 0
@@ -36,6 +38,7 @@ def test_delete_relaxation_add_heuristic():
3638
apla = AutomatedPlanner(
3739
"pddl-examples/tsp/domain.pddl", "pddl-examples/tsp/problem.pddl"
3840
)
41+
apla.display_available_heuristics()
3942
heuristic = hs.DeleteRelaxationHeuristic(apla, "delete_relaxation/h_max")
4043
h = heuristic.compute(apla.initial_state)
4144
assert h != 0

tests/test_hsp_astar.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import sys
4+
from os import path
5+
6+
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
7+
from jupyddl.automated_planner import AutomatedPlanner
8+
from jupyddl.a_star import AStarBestFirstSearch
9+
from jupyddl.heuristics import DeleteRelaxationHeuristic
10+
11+
12+
def test_astar_hmax():
13+
apla = AutomatedPlanner(
14+
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
15+
)
16+
heuristic = DeleteRelaxationHeuristic(apla, "delete_relaxation/h_max")
17+
astar = AStarBestFirstSearch(apla, heuristic.compute)
18+
assert astar.init.h_cost == heuristic.compute(apla.initial_state)
19+
20+
21+
def test_astar_hadd():
22+
apla = AutomatedPlanner(
23+
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
24+
)
25+
heuristic = DeleteRelaxationHeuristic(apla, "delete_relaxation/h_max")
26+
astar = AStarBestFirstSearch(apla, heuristic.compute)
27+
assert astar.init.h_cost == heuristic.compute(apla.initial_state)
28+
29+
30+
def test_astar_hmax_sensible_domain():
31+
apla = AutomatedPlanner(
32+
"pddl-examples/grid/domain.pddl", "pddl-examples/grid/problem.pddl"
33+
)
34+
heuristic = DeleteRelaxationHeuristic(apla, "delete_relaxation/h_max")
35+
astar = AStarBestFirstSearch(apla, heuristic.compute)
36+
assert astar.init.h_cost == heuristic.compute(apla.initial_state)
37+
38+
39+
def test_astar_hadd_sensible_domain():
40+
apla = AutomatedPlanner(
41+
"pddl-examples/grid/domain.pddl", "pddl-examples/grid/problem.pddl"
42+
)
43+
heuristic = DeleteRelaxationHeuristic(apla, "delete_relaxation/h_max")
44+
astar = AStarBestFirstSearch(apla, heuristic.compute)
45+
assert astar.init.h_cost == heuristic.compute(apla.initial_state)

tests/test_node.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import sys
4+
from os import path
5+
6+
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
7+
from jupyddl.automated_planner import AutomatedPlanner
8+
from jupyddl.node import Node
9+
10+
11+
def test_node_equality_cost():
12+
apla = AutomatedPlanner(
13+
"pddl-examples/tsp/domain.pddl", "pddl-examples/tsp/problem.pddl"
14+
)
15+
actions = apla.available_actions(apla.initial_state)
16+
next_state = apla.transition(apla.initial_state, actions[0])
17+
next_node = Node(next_state, apla, heuristic_based=True)
18+
next_node_v2 = Node(next_state, apla)
19+
20+
assertion = next_node_v2 < next_node
21+
assertion2 = next_node < next_node_v2
22+
23+
assert assertion and assertion2
24+
25+
26+
def test_node_equality_no_cost():
27+
apla = AutomatedPlanner(
28+
"pddl-examples/dinner/domain.pddl", "pddl-examples/dinner/problem.pddl"
29+
)
30+
actions = apla.available_actions(apla.initial_state)
31+
next_state = apla.transition(apla.initial_state, actions[0])
32+
next_node = Node(next_state, apla, heuristic_based=True)
33+
next_node_v2 = Node(next_state, apla)
34+
35+
assertion = next_node_v2 < next_node
36+
assertion2 = next_node < next_node_v2
37+
38+
assert assertion and assertion2

0 commit comments

Comments
 (0)