Skip to content

Commit 5aabddd

Browse files
committed
add astar and astar tests, add color to logging
1 parent c956c0c commit 5aabddd

9 files changed

Lines changed: 185 additions & 117 deletions

File tree

main.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import src.automated_planner as parser
1+
from src.automated_planner import AutomatedPlanner
22
import argparse
3-
import logging
43
import os
54

65

@@ -13,23 +12,15 @@ def main():
1312
args_parser.add_argument("problem", type=str, help="PDDL problem file")
1413
args_parser.add_argument("-v", "--verbose", help="Increases the output's verbosity")
1514
args = args_parser.parse_args()
16-
logging.basicConfig(
17-
filename="logs/main.log",
18-
format="%(levelname)s:%(message)s",
19-
filemode="w",
20-
level=logging.INFO,
21-
) # Creates the log file
22-
apla_tbx = parser.AutomatedPlanner(args.domain, args.problem)
23-
logging.info("Starting the tool")
24-
path, time = apla_tbx.depth_first_search(time_it=True)
25-
logging.info(apla_tbx.get_actions_from_path(path))
26-
logging.info("Computation time: %.2f seconds" % time)
27-
logging.info("Tool finished")
28-
# Output the log (to show something in the output screen)
29-
logfile = open("logs/main.log", "r")
30-
print(logfile.read())
31-
logfile.close()
15+
apla_tbx = AutomatedPlanner(args.domain, args.problem)
16+
apla_tbx.logger.info("Starting the planning script")
17+
apla_tbx.logger.debug("Available heuristics: " + str(apla_tbx.available_heuristics.keys()))
3218

19+
path, computation_time = apla_tbx.dijktra_best_first_search(time_it=True)
20+
apla_tbx.logger.debug(apla_tbx.get_actions_from_path(path))
21+
22+
apla_tbx.logger.debug("Computation time: %.2f seconds" % computation_time)
23+
apla_tbx.logger.info("Terminate with grace...")
3324

3425
if __name__ == "__main__":
3526
main()

src/a_star.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from .heuristics import zero_heuristic
2+
from .node import Node
3+
import logging
4+
import math
5+
from time import time as now
6+
from datetime import datetime as timestamp
7+
8+
9+
class AStarBestFirstSearch:
10+
def __init__(self, automated_planner, heuristic_function):
11+
self.automated_planner = automated_planner
12+
self.init = Node(
13+
self.automated_planner.initial_state,
14+
automated_planner,
15+
is_closed=False,
16+
is_open=True,
17+
heuristic=heuristic_function,
18+
)
19+
self.heuristic_function = heuristic_function
20+
self.open_nodes_n = 1
21+
self.nodes = dict()
22+
self.nodes[self.__hash(self.init)] = self.init
23+
24+
def __hash(self, node):
25+
sep = ", Dict{Symbol,Any}"
26+
string = str(node.state)
27+
return string.split(sep, 1)[0] + ")"
28+
29+
def search(self):
30+
time_start = now()
31+
self.automated_planner.logger.debug("Search started at: " + str(timestamp.now()))
32+
while self.open_nodes_n > 0:
33+
current_key = min(
34+
[n for n in self.nodes if self.nodes[n].is_open],
35+
key=(lambda k: self.nodes[k].f_cost),
36+
)
37+
current_node = self.nodes[current_key]
38+
39+
if self.automated_planner.satisfies(
40+
self.automated_planner.problem.goal, current_node.state
41+
):
42+
computation_time = now() - time_start
43+
self.automated_planner.logger.debug("Search finished at: " + str(timestamp.now()))
44+
return current_node, computation_time
45+
46+
current_node.is_closed = True
47+
current_node.is_open = False
48+
self.open_nodes_n -= 1
49+
50+
actions = self.automated_planner.available_actions(current_node.state)
51+
for act in actions:
52+
child = Node(
53+
state=self.automated_planner.transition(current_node.state, act),
54+
automated_planner=self.automated_planner,
55+
parent_action=act,
56+
parent=current_node,
57+
heuristic=self.heuristic_function,
58+
is_closed=False,
59+
is_open=True,
60+
)
61+
child_hash = self.__hash(child)
62+
if child_hash in self.nodes:
63+
if self.nodes[child_hash].is_closed:
64+
continue
65+
if not self.nodes[child_hash].is_open:
66+
self.nodes[child_hash] = child
67+
self.open_nodes_n += 1
68+
else:
69+
if child.g_cost < self.nodes[child_hash].g_cost:
70+
self.nodes[child_hash] = child
71+
self.open_nodes_n += 1
72+
73+
else:
74+
self.nodes[child_hash] = child
75+
self.open_nodes_n += 1
76+
computation_time = now() - time_start
77+
self.automated_planner.logger.warning("!!! No path found !!!")
78+
return None, computation_time

src/astar.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/automated_planner.py

Lines changed: 33 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,54 @@
1-
from .modules import loading_bar_handler
21
from .bfs import BreadthFirstSearch
32
from .dfs import DepthFirstSearch
43
from .dijkstra import DijkstraBestFirstSearch
5-
import logging
6-
7-
UI = False
8-
9-
if UI:
10-
loading_bar_handler(False)
4+
from .a_star import AStarBestFirstSearch
5+
from .heuristics import goal_count_heuristic, zero_heuristic
6+
import coloredlogs, logging
117
import julia
12-
13-
_ = julia.Julia(compiled_modules=False)
14-
15-
if UI:
16-
loading_bar_handler(True)
17-
8+
_ = julia.Julia(compiled_modules=False, debug=False)
189
from julia import PDDL
1910
from time import time as now
20-
21-
11+
logging.getLogger("julia").setLevel(logging.WARNING)
2212
class AutomatedPlanner:
23-
def __init__(self, domain_path, problem_path):
13+
def __init__(self, domain_path, problem_path, log_level="DEBUG"):
14+
# Planning Tool
2415
self.pddl = PDDL
2516
self.domain = self.pddl.load_domain(domain_path)
2617
self.problem = self.pddl.load_problem(problem_path)
2718
self.initial_state = self.pddl.initialize(self.problem)
2819
self.goals = self.__flatten_goal()
29-
30-
"""
31-
Transition from one state to the next using an action
32-
"""
20+
self.available_heuristics = dict()
21+
self.available_heuristics["goal_count"] = goal_count_heuristic
22+
self.available_heuristics["zero"] = zero_heuristic
23+
24+
# Logging
25+
logging.basicConfig(
26+
filename="logs/main.log",
27+
format="%(levelname)s:%(message)s",
28+
filemode="w",
29+
level=log_level,
30+
) # Creates the log file
31+
self.logger = logging.getLogger("automated_planning")
32+
coloredlogs.install(level=log_level)
3333

3434
def transition(self, state, action):
3535
return self.pddl.transition(self.domain, state, action, check=False)
3636

37-
"""
38-
Returns all available actions from the given state
39-
"""
40-
4137
def available_actions(self, state):
4238
return self.pddl.available(state, self.domain)
4339

44-
"""
45-
Check if a vector of terms is satisfied by the given state
46-
"""
47-
4840
def satisfies(self, asserted_state, state):
4941
return self.pddl.satisfy(asserted_state, state, self.domain)[0]
5042

51-
"""
52-
Check if the term is satisfied by the state
53-
To do: compare if it's faster to compute the check on a vector of terms in julia or python
54-
"""
55-
5643
def state_has_term(self, state, term):
5744
if self.pddl.has_term_in_state(self.domain, state, term):
5845
return True
5946
else:
6047
return False
6148

62-
"""
63-
Flatten the goal to a vector of terms
64-
To do: check if we can iterate over the jl vector
65-
"""
66-
6749
def __flatten_goal(self):
6850
return self.pddl.flatten_goal(self.problem)
6951

70-
"""
71-
Retrieves the linked list path
72-
"""
73-
7452
def __retrace_path(self, node):
7553
if not node:
7654
return []
@@ -81,13 +59,9 @@ def __retrace_path(self, node):
8159
path.reverse()
8260
return path
8361

84-
"""
85-
Returns all the actions operated to reach the goal
86-
"""
87-
8862
def get_actions_from_path(self, path):
8963
if not path:
90-
logging.warning("Path is empty, can't operate...")
64+
self.logger.warning("Path is empty, can't operate...")
9165
return []
9266
actions = []
9367
for node in path:
@@ -99,64 +73,45 @@ def get_actions_from_path(self, path):
9973
else:
10074
return (actions, cost)
10175

102-
"""
103-
Returns all the states that should be opened from start to goal
104-
"""
105-
10676
def get_state_def_from_path(self, path):
10777
if not path:
108-
logging.warning("Path is empty, can't operate...")
78+
self.logger.warning("Path is empty, can't operate...")
10979
return []
11080
trimmed_path = []
11181
for node in path:
11282
trimmed_path.append(node.state)
11383
return trimmed_path
11484

115-
"""
116-
Runs the BFS algorithm on the loaded domain/problem
117-
"""
118-
11985
def breadth_first_search(self, time_it=False):
120-
if time_it:
121-
start_time = now()
12286
bfs = BreadthFirstSearch(self)
123-
last_node = bfs.search()
124-
if time_it:
125-
total_time = now() - start_time
87+
last_node, total_time = bfs.search()
12688
path = self.__retrace_path(last_node)
12789
if time_it:
12890
return path, total_time
12991
else:
13092
return path, None
13193

132-
"""
133-
Runs the DFS algorithm on the domain/problem
134-
"""
135-
13694
def depth_first_search(self, time_it=False):
137-
if time_it:
138-
start_time = now()
13995
dfs = DepthFirstSearch(self)
140-
last_node = dfs.search()
141-
if time_it:
142-
total_time = now() - start_time
96+
last_node, total_time = dfs.search()
14397
path = self.__retrace_path(last_node)
14498
if time_it:
14599
return path, total_time
146100
else:
147101
return path, None
148102

149-
"""
150-
Runs the Dijkstra algorithm on the domain/problem
151-
"""
152-
153103
def dijktra_best_first_search(self, time_it=False):
154-
if time_it:
155-
start_time = now()
156104
dijkstra = DijkstraBestFirstSearch(self)
157-
last_node = dijkstra.search()
105+
last_node, total_time = dijkstra.search()
106+
path = self.__retrace_path(last_node)
158107
if time_it:
159-
total_time = now() - start_time
108+
return path, total_time
109+
else:
110+
return path, None
111+
112+
def astar_best_first_search(self, time_it=False, heuristic=goal_count_heuristic):
113+
astar = AStarBestFirstSearch(self, heuristic)
114+
last_node, total_time = astar.search()
160115
path = self.__retrace_path(last_node)
161116
if time_it:
162117
return path, total_time

src/bfs.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .node import Node
2-
import logging
2+
from time import time as now
3+
from datetime import datetime as timestamp
34

45

56
class BreadthFirstSearch:
@@ -10,6 +11,8 @@ def __init__(self, automated_planner):
1011
self.queue = [self.init]
1112

1213
def search(self):
14+
time_start = now()
15+
self.automated_planner.logger.debug("Search started at: " + str(timestamp.now()))
1316
while self.queue:
1417
current_node = self.queue.pop(0)
1518
if current_node not in self.visited:
@@ -18,7 +21,9 @@ def search(self):
1821
if self.automated_planner.satisfies(
1922
self.automated_planner.problem.goal, current_node.state
2023
):
21-
return current_node
24+
computation_time = now() - time_start
25+
self.automated_planner.logger.debug("Search finished at: " + str(timestamp.now()))
26+
return current_node, computation_time
2227

2328
actions = self.automated_planner.available_actions(current_node.state)
2429
for act in actions:
@@ -33,5 +38,6 @@ def search(self):
3338
if child in self.visited:
3439
continue
3540
self.queue.append(child)
36-
logging.warning("!!! No path found !!!")
37-
return None
41+
computation_time = now() - time_start
42+
self.automated_planner.logger.warning("!!! No path found !!!")
43+
return None, computation_time

src/dfs.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .node import Node
2-
import logging
3-
2+
from datetime import datetime as timestamp
3+
from time import time as now
44

55
class DepthFirstSearch:
66
def __init__(self, automated_planner):
@@ -10,6 +10,8 @@ def __init__(self, automated_planner):
1010
self.stack = [self.init]
1111

1212
def search(self):
13+
time_start = now()
14+
self.automated_planner.logger.debug("Search started at: " + str(timestamp.now()))
1315
while self.stack:
1416
current_node = self.stack.pop(0)
1517
if current_node not in self.visited:
@@ -18,7 +20,9 @@ def search(self):
1820
if self.automated_planner.satisfies(
1921
self.automated_planner.problem.goal, current_node.state
2022
):
21-
return current_node
23+
computation_time = now() - time_start
24+
self.automated_planner.logger.debug("Search finished at: " + str(timestamp.now()))
25+
return current_node, computation_time
2226

2327
actions = self.automated_planner.available_actions(current_node.state)
2428
for act in actions:
@@ -33,5 +37,6 @@ def search(self):
3337
if child in self.visited:
3438
continue
3539
self.stack.append(child)
36-
logging.warning("!!! No path found !!!")
37-
return None
40+
computation_time = now() - time_start
41+
self.automated_planner.logger.warning("!!! No path found !!!")
42+
return None, computation_time

0 commit comments

Comments
 (0)