Skip to content

Commit 7bde6c5

Browse files
committed
Creates code for launching navigation algorithms with visualization
- Creates utils for running decentralized discrete navigation - Creates utils for solution visualization - Adds A* algorithm implementation - Adds basic navigation politics (random, random + obstacle collision avoidance, A*-based) - Adds jupyter notebook with random problem example
1 parent 033d520 commit 7bde6c5

12 files changed

Lines changed: 1291 additions & 1 deletion

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.idea/*
2+
exp/__pycache__/*
3+
exp/.ipynb_checkpoints/*
4+
dec_tswap/__pycache__/*
5+
exp/animated_trajectories.apng
6+
exp/animated_trajectories.gif
7+
exp/animated_trajectories.png
8+
exp/example-Copy1.ipynb

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
# Decentralized TSWAP
1+
# Decentralized TSWAP

dec_tswap/__init__.py

Whitespace-only changes.

dec_tswap/agent.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type, Union
2+
from manavlib.gen.params import DiscreteAgentParams, BaseAlgParams
3+
import numpy.typing as npt
4+
from enum import Enum
5+
import numpy as np
6+
import random
7+
8+
from dec_tswap.map import Map
9+
from dec_tswap.astar_algorithm import astar_search, manhattan_distance, make_path
10+
11+
12+
class DecTSWAPParams(BaseAlgParams):
13+
def __init__(self) -> None:
14+
super().__init__()
15+
pass
16+
17+
18+
class Message:
19+
def __init__(self):
20+
self.pos: npt.NDArray | None = None
21+
self.next_pos: npt.NDArray | None = None
22+
self.priority: int | None = None
23+
24+
25+
class Action(Enum):
26+
WAIT = (0, 0)
27+
UP = (-1, 0)
28+
DOWN = (1, 0)
29+
LEFT = (0, -1)
30+
RIGHT = (0, 1)
31+
32+
33+
class Agent:
34+
def __init__(self,
35+
a_id: int,
36+
ag_params: DiscreteAgentParams,
37+
alg_params: BaseAlgParams,
38+
grid_map: npt.NDArray,
39+
goals: npt.NDArray):
40+
self.a_id = a_id
41+
self.ag_params = ag_params
42+
self.alg_params = alg_params
43+
self.grid_map = grid_map
44+
self.goals = goals
45+
46+
def update_neighbors_info(self, neighbors_info: List[Message]) -> None:
47+
raise NotImplementedError
48+
49+
def compute_action(self) -> Action:
50+
raise NotImplementedError
51+
52+
def update_state_info(self, new_pos: npt.NDArray) -> None:
53+
raise NotImplementedError
54+
55+
def send_message(self) -> Message:
56+
raise NotImplementedError
57+
58+
59+
class RandomAgent(Agent):
60+
def __init__(self,
61+
a_id: int,
62+
ag_params: DiscreteAgentParams,
63+
alg_params: BaseAlgParams,
64+
grid_map: npt.NDArray,
65+
goals: npt.NDArray):
66+
super().__init__(a_id, ag_params, alg_params, grid_map, goals)
67+
pass
68+
69+
def update_neighbors_info(self, neighbors_info: List[Message]) -> None:
70+
pass
71+
72+
def compute_action(self) -> npt.NDArray:
73+
return np.array(random.choice(list(Action)).value)
74+
75+
def update_state_info(self, new_pos: npt.NDArray) -> None:
76+
pass
77+
78+
def send_message(self) -> Message:
79+
return Message()
80+
81+
82+
class SmartRandomAgent(Agent):
83+
def __init__(self,
84+
a_id: int,
85+
ag_params: DiscreteAgentParams,
86+
alg_params: BaseAlgParams,
87+
grid_map: npt.NDArray,
88+
goals: npt.NDArray):
89+
super().__init__(a_id, ag_params, alg_params, grid_map, goals)
90+
self.pos = None
91+
92+
def update_neighbors_info(self, neighbors_info: List[Message]) -> None:
93+
pass
94+
95+
def compute_action(self) -> npt.NDArray:
96+
actions = list(Action)
97+
actions.remove(Action.WAIT)
98+
while len(actions):
99+
action = random.choice(actions)
100+
actions.remove(action)
101+
action = np.array(action.value)
102+
predicted_pos = self.pos + action
103+
h, w = self.grid_map.shape
104+
i, j = predicted_pos
105+
if not ((0 <= i < h) and (0 <= j < w)):
106+
continue
107+
if self.grid_map[i, j]:
108+
continue
109+
110+
return action
111+
112+
return np.array(Action.WAIT.value)
113+
114+
def update_state_info(self, new_pos: npt.NDArray) -> None:
115+
self.pos = new_pos
116+
117+
def send_message(self) -> Message:
118+
return Message()
119+
120+
121+
class AStarAgent(Agent):
122+
def __init__(self,
123+
a_id: int,
124+
ag_params: DiscreteAgentParams,
125+
alg_params: BaseAlgParams,
126+
grid_map: npt.NDArray,
127+
goals: npt.NDArray):
128+
super().__init__(a_id, ag_params, alg_params, grid_map, goals)
129+
self.pos = None
130+
self.neighbors_info = None
131+
self.path = []
132+
self.goal_chosen = False
133+
self.goal = None
134+
self.search_map = Map(self.grid_map)
135+
self.path_exist = False
136+
137+
def update_neighbors_info(self, neighbors_info: List[Message]) -> None:
138+
self.neighbors_info = neighbors_info
139+
140+
def compute_action(self) -> npt.NDArray:
141+
142+
if not self.goal_chosen:
143+
self.choose_goal()
144+
start_i, start_j = self.pos
145+
goal_i, goal_j = self.goal
146+
path_found, last_node, length = astar_search(self.search_map, start_i, start_j, goal_i, goal_j,
147+
manhattan_distance)
148+
self.path = make_path(last_node)[:-1]
149+
150+
if not self.path_exist or len(self.path) == 0:
151+
return np.array(Action.WAIT.value)
152+
next_pos = np.array(self.path.pop())
153+
action = (next_pos - self.pos)
154+
return action
155+
156+
def update_state_info(self, new_pos: npt.NDArray) -> None:
157+
self.pos = new_pos
158+
159+
def send_message(self) -> Message:
160+
message = Message()
161+
message.pos = self.pos
162+
return message
163+
164+
def choose_goal(self) -> None:
165+
if self.goal_chosen:
166+
return
167+
168+
start_i, start_j = self.pos
169+
min_len = np.inf
170+
171+
for goal_i, goal_j in self.goals:
172+
path_found, last_node, length = astar_search(self.search_map, start_i, start_j, goal_i, goal_j,
173+
manhattan_distance)
174+
if not path_found:
175+
continue
176+
self.path_exist = True
177+
if length < min_len:
178+
min_len = length
179+
self.goal = np.array((goal_i, goal_j))

dec_tswap/astar_algorithm.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
from dec_tswap.map import Map, compute_cost
2+
from dec_tswap.search_tree import SearchTree
3+
from dec_tswap.node import Node
4+
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type, Union
5+
import numpy as np
6+
7+
8+
def manhattan_distance(i1: int, j1: int, i2: int, j2: int) -> int:
9+
"""
10+
Computes the Manhattan distance between two cells on a grid.
11+
12+
Parameters
13+
----------
14+
i1, j1 : int
15+
(i, j) coordinates of the first cell on the grid.
16+
i2, j2 : int
17+
(i, j) coordinates of the second cell on the grid.
18+
19+
Returns
20+
-------
21+
int
22+
Manhattan distance between the two cells.
23+
"""
24+
return abs(i1 - i2) + abs(j1 - j2)
25+
26+
27+
def astar_search(
28+
task_map: Map,
29+
start_i: int,
30+
start_j: int,
31+
goal_i: int,
32+
goal_j: int,
33+
heuristic_func: Callable
34+
) -> Tuple[bool, Optional[Node], Optional[int]]:
35+
"""
36+
Implements the A* search algorithm.
37+
38+
Parameters
39+
----------
40+
task_map : Map
41+
The grid or map being searched.
42+
start_i, start_j : int, int
43+
Starting coordinates.
44+
goal_i, goal_j : int, int
45+
Goal coordinates.
46+
heuristic_func : Callable
47+
Heuristic function for estimating the distance from a node to the goal.
48+
49+
Returns
50+
-------
51+
Tuple[bool, Optional[Node], int, int, Optional[Iterable[Node]], Optional[Iterable[Node]]]
52+
Tuple containing:
53+
- A boolean indicating if a path was found.
54+
- The last node in the found path or None.
55+
- Path length
56+
"""
57+
ast = SearchTree()
58+
steps = 0
59+
start_node = Node(start_i, start_j, g=0, h=heuristic_func(start_i, start_j, goal_i, goal_j))
60+
ast.add_to_open(start_node)
61+
62+
while not ast.open_is_empty():
63+
current = ast.get_best_node_from_open()
64+
65+
if current is None:
66+
break
67+
68+
ast.add_to_closed(current)
69+
70+
if (current.i, current.j) == (goal_i, goal_j):
71+
return True, current, current.g
72+
73+
for i, j in task_map.get_neighbors(current.i, current.j):
74+
new_node = Node(i, j)
75+
if not ast.was_expanded(new_node):
76+
new_node.g = current.g + compute_cost(current.i, current.j, i, j)
77+
new_node.h = heuristic_func(i, j, goal_i, goal_j)
78+
new_node.f = new_node.g + new_node.h
79+
new_node.parent = current
80+
ast.add_to_open(new_node)
81+
82+
steps += 1
83+
84+
return False, None, None
85+
86+
87+
def make_path(goal: Node) -> List[Node]:
88+
"""
89+
Creates a path by tracing parent pointers from the goal node to the start node.
90+
It also returns the path's length.
91+
92+
Parameters
93+
----------
94+
goal : Node
95+
Pointer to the goal node in the search tree.
96+
97+
Returns
98+
-------
99+
Tuple[List[Node], float]
100+
Path and its length.
101+
"""
102+
current = goal
103+
path = []
104+
while current.parent:
105+
path.append((current.i, current.j))
106+
current = current.parent
107+
path.append(current)
108+
return path

0 commit comments

Comments
 (0)