Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Heuristics] Update architecture / Implement HSP #79

Merged
merged 10 commits into from
Jan 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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