Skip to content

Commit ad50fa5

Browse files
authored
Merge pull request #16 from APLA-Toolbox/add-dijktra
Improve planners architecture
2 parents 466c7cc + e134bfc commit ad50fa5

14 files changed

Lines changed: 281 additions & 119 deletions

File tree

.github/workflows/pyapp.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ jobs:
1717
- uses: julia-actions/setup-julia@v1
1818
with:
1919
version: '1.4.1'
20-
- name: Install PDDL.jl
20+
- name: Install Julia dependencies
2121
run: |
2222
julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(path="https://github.com/APLA-Toolbox/PDDL.jl"))'
23-
- name: Install dependencies
23+
julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(path="https://github.com/JuliaPy/PyCall.jl"))'
24+
- name: Install Python dependencies
2425
run: |
2526
python -m pip install --upgrade pip
2627
python -m pip install julia
28+
python -m pip install pycall
2729
- name: Lint with flake8
2830
run: |
2931
python -m pip install flake8
@@ -38,5 +40,4 @@ jobs:
3840
- name: Upload coverage to Codecov
3941
uses: codecov/codecov-action@v1
4042
with:
41-
file: ./coverage.xml
4243
name: codecov-umbrella

codecov.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
codecov:
2+
require_ci_to_pass: yes
3+
4+
coverage:
5+
precision: 2
6+
round: down
7+
range: "70...100"
8+
9+
parsers:
10+
gcov:
11+
branch_detection:
12+
conditional: yes
13+
loop: yes
14+
method: no
15+
macro: no
16+
17+
comment:
18+
layout: "reach,diff,flags,files,footer"
19+
behavior: default
20+
require_changes: no

main.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import src.pddl as plarser
1+
import src.automated_planner as plarser
22
import argparse
33

44

@@ -11,9 +11,10 @@ def main():
1111
args_parser.add_argument("problem", type=str, help="PDDL problem file")
1212
args_parser.add_argument("-v", "--verbose", help="Increases the output's verbosity")
1313
args = args_parser.parse_args()
14-
apla_tbx = plarser.AutomatedPlanning(args.domain, args.problem)
15-
path = apla_tbx.get_actions_from_path(apla_tbx.breadth_first_search())
16-
print(path)
14+
apla_tbx = plarser.AutomatedPlanner(args.domain, args.problem)
15+
path, time = apla_tbx.dijktra_best_first_search(time_it=True)
16+
print(apla_tbx.get_actions_from_path(path))
17+
print("Computation time: %.2f" % time)
1718

1819

1920
if __name__ == "__main__":

src/astar.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class AStarBestFirstSearch():
2+
def __init__(self, automated_planner):
3+
self.automated_planner = automated_planner
4+
5+
def search(self):
6+
print("-/!\- No path found -/!\-")
7+
return []

src/automated_planner.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from .modules import loading_bar_handler
2+
from .bfs import BreadthFirstSearch
3+
from .dijkstra import DijkstraBestFirstSearch
4+
5+
UI = False
6+
7+
if UI:
8+
loading_bar_handler(False)
9+
import julia
10+
11+
_ = julia.Julia(compiled_modules=False)
12+
13+
if UI:
14+
loading_bar_handler(True)
15+
16+
from julia import PDDL
17+
from time import time as now
18+
19+
class AutomatedPlanner:
20+
def __init__(self, domain_path, problem_path):
21+
self.pddl = PDDL
22+
self.domain = self.pddl.load_domain(domain_path)
23+
self.problem = self.pddl.load_problem(problem_path)
24+
self.initial_state = self.pddl.initialize(self.problem)
25+
26+
def __execute_action(self, action, state):
27+
return self.pddl.execute(action, state)
28+
29+
def transition(self, state, action):
30+
return self.pddl.transition(self.domain, state, action, check=False)
31+
32+
def available_actions(self, state):
33+
return self.pddl.available(state, self.domain)
34+
35+
def satisfies(self, asserted_state, state):
36+
return self.pddl.satisfy(asserted_state, state, self.domain)[0]
37+
38+
def __retrace_path(self, node):
39+
if not node:
40+
return []
41+
path = []
42+
while node.parent:
43+
path.append(node)
44+
node = node.parent
45+
path.reverse()
46+
return path
47+
48+
def get_actions_from_path(self, path):
49+
if not path:
50+
print("Path is empty, can't operate...")
51+
return []
52+
actions = []
53+
for node in path:
54+
actions.append((node.parent_action, node.g_cost))
55+
56+
cost = self.pddl.get_value(path[-1].state, "total-cost")
57+
if not cost:
58+
return actions
59+
else:
60+
return (actions, cost)
61+
62+
def get_state_def_from_path(self, path):
63+
if not path:
64+
print("Path is empty, can't operate...")
65+
return []
66+
trimmed_path = []
67+
for node in path:
68+
trimmed_path.append(node.state)
69+
return trimmed_path
70+
71+
def breadth_first_search(self, time_it=False):
72+
if time_it:
73+
start_time = now()
74+
bfs = BreadthFirstSearch(self)
75+
last_node = bfs.search()
76+
if time_it:
77+
total_time = now() - start_time
78+
path = self.__retrace_path(last_node)
79+
if time_it:
80+
return path, total_time
81+
else:
82+
return path, None
83+
84+
def dijktra_best_first_search(self, time_it=False):
85+
if time_it:
86+
start_time = now()
87+
dijkstra = DijkstraBestFirstSearch(self)
88+
last_node = dijkstra.search()
89+
if time_it:
90+
total_time = now() - start_time
91+
path = self.__retrace_path(last_node)
92+
if time_it:
93+
return path, total_time
94+
else:
95+
return path, None
96+
97+
98+
if __name__ == "__main__":
99+
ap = AutomatedPlanner("..data/domain.pddl", "..data/problem.pddl")

src/bfs.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from .node import Node
2+
3+
class BreadthFirstSearch():
4+
def __init__(self, automated_planner):
5+
self.visited = []
6+
self.automated_planner = automated_planner
7+
self.init = Node(self.automated_planner.initial_state, automated_planner)
8+
self.queue = [self.init]
9+
10+
def search(self):
11+
while self.queue:
12+
current_node = self.queue.pop(0)
13+
if current_node not in self.visited:
14+
self.visited.append(current_node)
15+
16+
if self.automated_planner.satisfies(self.automated_planner.problem.goal, current_node.state):
17+
return current_node
18+
19+
actions = self.automated_planner.available_actions(current_node.state)
20+
for act in actions:
21+
child = Node(state=self.automated_planner.transition(current_node.state, act), automated_planner=self.automated_planner, parent_action=act, parent=current_node)
22+
if child in self.visited:
23+
continue
24+
self.queue.append(child)
25+
print("!!! No path found !!!")
26+
return None

src/dfs.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class DepthFirstSearch():
2+
def __init__(self, automated_planner):
3+
self.automated_planner = automated_planner
4+
5+
def search(self):
6+
print("-/!\- No path found -/!\-")
7+
return []

src/dijkstra.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from .node import Node
2+
import math
3+
from .heuristics import zero_heuristic
4+
5+
class DijkstraBestFirstSearch():
6+
def __init__(self, automated_planner):
7+
self.automated_planner = automated_planner
8+
self.init = Node(self.automated_planner.initial_state, automated_planner, is_closed=False, is_open=True)
9+
self.open_nodes_n = 1
10+
self.nodes = dict()
11+
self.nodes[self.__hash(self.init)] = self.init
12+
13+
def __hash(self, node):
14+
sep = ", Dict{Symbol,Any}"
15+
string = str(node.state)
16+
return string.split(sep, 1)[0] + ")"
17+
18+
def search(self):
19+
while self.open_nodes_n > 0:
20+
current_key = min([n for n in self.nodes if self.nodes[n].is_open], key=(lambda k: self.nodes[k].f_cost))
21+
current_node = self.nodes[current_key]
22+
23+
if self.automated_planner.satisfies(self.automated_planner.problem.goal, current_node.state):
24+
return current_node
25+
26+
current_node.is_closed = True
27+
current_node.is_open = False
28+
self.open_nodes_n -= 1
29+
30+
actions = self.automated_planner.available_actions(current_node.state)
31+
for act in actions:
32+
child = Node(state=self.automated_planner.transition(current_node.state, act), automated_planner=self.automated_planner, parent_action=act, parent=current_node, heuristic=zero_heuristic, is_closed=False, is_open=True)
33+
child_hash = self.__hash(child)
34+
if child_hash in self.nodes:
35+
if self.nodes[child_hash].is_closed:
36+
continue
37+
if not self.nodes[child_hash].is_open:
38+
self.nodes[child_hash] = child
39+
self.open_nodes_n += 1
40+
else:
41+
if child.g_cost < self.nodes[child_hash].g_cost:
42+
self.nodes[child_hash] = child
43+
self.open_nodes_n += 1
44+
45+
else:
46+
self.nodes[child_hash] = child
47+
self.open_nodes_n += 1
48+
print("!!! No path found !!!")
49+
return None

src/heuristics.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def zero_heuristic(state, pddl):
2+
return 0

src/node.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class Node():
2+
def __init__(self, state, automated_planner, is_closed=None, is_open=None, parent_action=None, parent=None, g_cost=0, heuristic=None):
3+
self.state = state
4+
self.parent_action = parent_action
5+
self.parent = parent
6+
temp_cost = automated_planner.pddl.get_value(state, "total-cost")
7+
if temp_cost:
8+
self.g_cost = temp_cost
9+
if heuristic:
10+
self.h_cost = heuristic(state, automated_planner)
11+
else:
12+
self.h_cost = 0
13+
self.f_cost = self.g_cost + self.h_cost
14+
else:
15+
self.g_cost = 1
16+
if heuristic:
17+
self.h_cost = heuristic(state, automated_planner)
18+
else:
19+
self.h_cost = 0
20+
self.f_cost = self.g_cost + self.h_cost
21+
22+
23+
self.is_closed = is_closed
24+
self.is_open = is_open
25+
26+
def __lt__(self, other):
27+
return self.f_cost <= other.f_cost

0 commit comments

Comments
 (0)