Skip to content

Commit

Permalink
Merge pull request #79 from APLA-Toolbox/hsp-heuristic
Browse files Browse the repository at this point in the history
[Heuristics] Update architecture / Implement HSP
  • Loading branch information
guilyx authored Jan 1, 2021
2 parents b536d40 + 82ffb89 commit 42f18d0
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 114 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ You should have a `pddl-examples` folder containing PDDL instances.

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

apl.initial_state
<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}())>
Expand Down Expand Up @@ -102,17 +102,17 @@ from jupyddl import DataAnalyst
da = DataAnalyst()
da.plot_astar() # plots complexity statistics for all the problem.pddl/domain.pddl couples in the pddl-examples/ folder

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

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

da.plot_dfs() # same as astar

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

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

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

da.comparative_data_plot(collect_new_data=False) # uses data.json to plot the data
```
Expand Down
1 change: 0 additions & 1 deletion jupyddl/a_star.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .heuristics import zero_heuristic
from .node import Node
import logging
import math
Expand Down
26 changes: 19 additions & 7 deletions jupyddl/automated_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .dfs import DepthFirstSearch
from .dijkstra import DijkstraBestFirstSearch
from .a_star import AStarBestFirstSearch
from .heuristics import goal_count_heuristic, zero_heuristic
from .heuristics import BasicHeuristic, DeleteRelaxationHeuristic
import coloredlogs
import logging
import julia
Expand All @@ -18,13 +18,18 @@ class AutomatedPlanner:
def __init__(self, domain_path, problem_path, log_level="DEBUG"):
# Planning Tool
self.pddl = PDDL
self.domain_path = domain_path
self.problem_path = problem_path
self.domain = self.pddl.load_domain(domain_path)
self.problem = self.pddl.load_problem(problem_path)
self.initial_state = self.pddl.initialize(self.problem)
self.goals = self.__flatten_goal()
self.available_heuristics = dict()
self.available_heuristics["goal_count"] = goal_count_heuristic
self.available_heuristics["zero"] = zero_heuristic
self.available_heuristics = [
"basic/zero",
"basic/goal_count",
"delete_relaxation/h_add",
"delete_relaxation/h_max",
]

# Logger
self.__init_logger(log_level)
Expand Down Expand Up @@ -53,7 +58,7 @@ def __init_logger(self, log_level):
)

def display_available_heuristics(self):
print(list(self.available_heuristics.keys()))
print(self.available_heuristics)

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

return path, total_time, opened_nodes

def astar_best_first_search(self, heuristic=goal_count_heuristic):
astar = AStarBestFirstSearch(self, heuristic)
def astar_best_first_search(self, heuristic_key="basic/goal_count"):
if "basic" in heuristic_key:
heuristic = BasicHeuristic(self, heuristic_key)
elif "delete_relaxation" in heuristic_key:
heuristic = DeleteRelaxationHeuristic(self, heuristic_key)
else:
logging.fatal("Not yet implemented")
exit()
astar = AStarBestFirstSearch(self, heuristic.compute)
last_node, total_time, opened_nodes = astar.search()
path = self.__retrace_path(last_node)

Expand Down
41 changes: 24 additions & 17 deletions jupyddl/data_analyst.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
class DataAnalyst:
def __init__(self):
logging.info("Instantiating data analyst...")
self.available_heuristics = ["goal_count", "zero"]
self.available_heuristics = [
"basic/goal_count",
"basic/zero",
"delete_relaxation/h_add",
"delete_relaxation/h_max",
]

def __get_all_pddl_from_data(self, max_pddl_instances=-1):
tested_files = []
Expand All @@ -42,7 +47,7 @@ def __get_all_pddl_from_data(self, max_pddl_instances=-1):
return domains_problems
return domains_problems
return [
("pddl-examples/flip/problem.pddl", "pddl-examples/flip/domain.pddl"),
("pddl-examples/dinner/problem.pddl", "pddl-examples/dinner/domain.pddl"),
("pddl-examples/dinner/problem.pddl", "pddl-examples/dinner/domain.pddl"),
]

Expand Down Expand Up @@ -75,7 +80,7 @@ def __gather_data_astar(
self,
domain_path="",
problem_path="",
heuristic_key="goal_count",
heuristic_key="basic/goal_count",
max_pddl_instances=-1,
):
has_multiple_files_tested = True
Expand All @@ -84,13 +89,17 @@ def __gather_data_astar(
for problem, domain in self.__get_all_pddl_from_data(
max_pddl_instances=max_pddl_instances
):
logging.debug("Loading new PDDL instance planned with A*...")
logging.debug(
"Loading new PDDL instance planned with A* [ "
+ heuristic_key
+ " ]"
)
logging.debug("Domain: " + domain)
logging.debug("Problem: " + problem)
apla = AutomatedPlanner(domain, problem)
if heuristic_key in apla.available_heuristics:
path, total_time, opened_nodes = apla.astar_best_first_search(
heuristic=apla.available_heuristics[heuristic_key]
heuristic_key=heuristic_key
)
else:
logging.critical(
Expand All @@ -112,7 +121,7 @@ def __gather_data_astar(
apla = AutomatedPlanner(domain_path, problem_path)
if heuristic_key in apla.available_heuristics:
path, total_time, opened_nodes = apla.astar_best_first_search(
heuristic=apla.available_heuristics[heuristic_key]
heuristic_key=heuristic_key
)
else:
logging.critical(
Expand All @@ -124,7 +133,11 @@ def __gather_data_astar(
return [0], [0], has_multiple_files_tested

def plot_astar(
self, heuristic_key="goal_count", domain="", problem="", max_pddl_instances=-1
self,
heuristic_key="basic/goal_count",
domain="",
problem="",
max_pddl_instances=-1,
):
if bool(not problem) != bool(not domain):
logging.warning(
Expand Down Expand Up @@ -288,7 +301,7 @@ def plot_dijkstra(self, problem="", domain="", max_pddl_instances=-1):

def __gather_data(
self,
heuristic_key="goal_count",
heuristic_key="basic/goal_count",
astar=True,
bfs=True,
dfs=True,
Expand Down Expand Up @@ -354,10 +367,7 @@ def comparative_astar_heuristic_plot(
times_y.append(data[node_opened])

ax.plot(
nodes_sorted,
times_y,
"-o",
label=h,
nodes_sorted, times_y, "-o", label=h,
)

plt.title("A* heuristics complexity comparison")
Expand All @@ -374,7 +384,7 @@ def comparative_data_plot(
dijkstra=True,
domain="",
problem="",
heuristic_key="goal_count",
heuristic_key="basic/goal_count",
collect_new_data=True,
max_pddl_instances=-1,
):
Expand Down Expand Up @@ -429,10 +439,7 @@ def comparative_data_plot(
for node_opened in nodes_sorted:
times_y.append(data[node_opened])
ax.plot(
nodes_sorted,
times_y,
"-o",
label=planner,
nodes_sorted, times_y, "-o", label=planner,
)
plt.title("Planners complexity comparison")
plt.legend(loc="upper left")
Expand Down
5 changes: 4 additions & 1 deletion jupyddl/dijkstra.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from .heuristics import zero_heuristic
from .node import Node
import logging
import math
from datetime import datetime as timestamp
from time import time as now


def zero_heuristic():
return 0


class DijkstraBestFirstSearch:
def __init__(self, automated_planner):
self.automated_planner = automated_planner
Expand Down
131 changes: 123 additions & 8 deletions jupyddl/heuristics.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,125 @@
def zero_heuristic(state, automated_planner):
return 0
import logging


def goal_count_heuristic(state, automated_planner):
count = 0
for goal in automated_planner.goals:
if not automated_planner.state_has_term(state, goal):
count += 1
return count
class BasicHeuristic:
def __init__(self, automated_planner, heuristic_key):
self.automated_planner = automated_planner
self.heuristic_keys = {
"basic/zero": self.__zero_heuristic,
"basic/goal_count": self.__goal_count_heuristic,
}
if heuristic_key not in list(self.heuristic_keys.keys()):
logging.warning(
"Heuristic key isn't registered, forcing it to [basic/goal_count]"
)
heuristic_key = "basic/goal_count"

self.current_h = heuristic_key

def compute(self, state):
return self.heuristic_keys[self.current_h](state)

def __zero_heuristic(self, state):
return 0

def __goal_count_heuristic(self, state):
count = 0
for goal in self.automated_planner.goals:
if not self.automated_planner.state_has_term(state, goal):
count += 1
return count


class DeleteRelaxationHeuristic:
def __init__(self, automated_planner, heuristic_key):
class DRHCache:
def __init__(self, domain=None, axioms=None, preconds=None, additions=None):
self.domain = domain
self.axioms = axioms
self.preconds = preconds
self.additions = additions

self.automated_planner = automated_planner
self.cache = DRHCache()
self.heuristic_keys = {
"delete_relaxation/h_add": self.__h_add,
"delete_relaxation/h_max": self.__h_max,
}
if heuristic_key not in list(self.heuristic_keys.keys()):
logging.warning(
"Heuristic key isn't registered, forcing it to [delete_relaxation/h_add]"
)
heuristic_key = "delete_relaxation/h_add"

self.current_h = heuristic_key
self.has_been_precomputed = False
self.__pre_compute()
# return self.heuristic_keys[self.current_h](state)

def compute(self, state):
if not self.has_been_precomputed:
self.__pre_compute()
domain = self.cache.domain
goals = self.automated_planner.goals
types = state.types
facts = state.facts
fact_costs = self.automated_planner.pddl.init_facts_costs(facts)
while not (
len(fact_costs) == self.automated_planner.pddl.length(facts)
and self.__facts_eq(fact_costs, facts)
):
facts, state = self.automated_planner.pddl.get_facts_and_state(
fact_costs, types
)
if self.automated_planner.satisfies(goals, state):
costs = []
fact_costs_str = dict([(str(k), val) for k, val in fact_costs.items()])
for g in goals:
if str(g) in fact_costs_str:
costs.append(fact_costs_str[str(g)])
costs.insert(0, 0)
return self.heuristic_keys[self.current_h](costs)

for ax in self.cache.axioms:
fact_costs = self.automated_planner.pddl.compute_costs_one_step_derivation(
facts, fact_costs, ax, self.current_h
)

actions = self.automated_planner.available_actions(state)
if not actions:
break
for act in actions:
fact_costs = self.automated_planner.pddl.compute_cost_action_effect(
fact_costs, act, domain, self.cache.additions, self.current_h
)
return float("inf")

def __pre_compute(self):
if self.has_been_precomputed:
return
domain = self.automated_planner.domain
domain, axioms = self.automated_planner.pddl.compute_hsp_axioms(domain)
# preconditions = dict()
additions = dict()
self.automated_planner.pddl.cache_global_preconditions(domain)
for name, definition in domain.actions.items():
additions[name] = self.automated_planner.pddl.effect_diff(
definition.effect
).add
self.cache.additions = additions
self.cache.preconds = self.automated_planner.pddl.g_preconditions
self.cache.domain = domain
self.cache.axioms = axioms
self.has_been_precomputed = True

def __h_add(self, costs):
return sum(costs)

def __h_max(self, costs):
return max(costs)

def __facts_eq(self, facts_dict, facts_set):
for f in facts_set:
if not (f in facts_dict.keys()):
return False
return True
4 changes: 2 additions & 2 deletions jupyddl/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(
self.g_cost = temp_cost
if heuristic_based:
if heuristic:
self.h_cost = heuristic(state, automated_planner)
self.h_cost = heuristic(state)
else:
automated_planner.logger.warning(
"Heuristic function wasn't found, forcing it to return zero [Best practice: use the zero_heuristic function]"
Expand All @@ -38,7 +38,7 @@ def __init__(
self.g_cost = g_cost
if heuristic_based:
if heuristic:
self.h_cost = heuristic(state, automated_planner)
self.h_cost = heuristic(state)
else:
automated_planner.logger.warning(
"Heuristic function wasn't found, forcing it to return zero [Best practice: use the zero_heuristic function]"
Expand Down
Loading

0 comments on commit 42f18d0

Please sign in to comment.