Skip to content

Commit f771ba1

Browse files
committed
wip - proved out some simple cases
1 parent 656c717 commit f771ba1

File tree

4 files changed

+204
-30
lines changed

4 files changed

+204
-30
lines changed

PathPlanning/TimeBasedPathPlanning/ConflictBasedSearch.py

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,24 @@ def plan(grid: Grid, start_and_goals: list[StartAndGoal], single_agent_planner_c
3333

3434
initial_solution: dict[AgentId, NodePath] = {}
3535

36-
# Reserve initial positions
36+
# Generate initial solution (no reservations for robots)
3737
for agent_idx, start_and_goal in enumerate(start_and_goals):
38-
# grid.reserve_position(start_and_goal.start, start_and_goal.index, Interval(0, 10))
3938
path = single_agent_planner_class.plan(grid, start_and_goal.start, start_and_goal.goal, agent_idx, verbose)
4039
initial_solution[AgentId(agent_idx)] = path
4140

41+
# for (agent_idx, path) in initial_solution.items():
42+
# print(f"\nAgent {agent_idx} path:\n {path}")
43+
4244
constraint_tree = ConstraintTree(initial_solution)
4345

46+
attempted_constraint_combos = set()
47+
4448
while constraint_tree.nodes_to_expand:
4549
constraint_tree_node = constraint_tree.get_next_node_to_expand()
4650
ancestor_constraints = constraint_tree.get_ancestor_constraints(constraint_tree_node.parent_idx)
47-
print(f"Expanded node: {constraint_tree_node.constraint} with parent: {constraint_tree_node.parent_idx}")
48-
print(f"\tAncestor constraints: {ancestor_constraints}")
51+
52+
# print(f"Expanded node: {constraint_tree_node.constraint} with parent: {constraint_tree_node.parent_idx}")
53+
# print(f"\tAncestor constraints: {ancestor_constraints}")
4954

5055
if verbose:
5156
print(f"Expanding node with constraint {constraint_tree_node.constraint} and parent {constraint_tree_node.parent_idx} ")
@@ -66,48 +71,96 @@ def plan(grid: Grid, start_and_goals: list[StartAndGoal], single_agent_planner_c
6671
if verbose:
6772
print(f"\nOuter loop step for agent {constrained_agent}")
6873

69-
applied_constraint = AppliedConstraint(constraint_tree_node.constraint.constraint, constrained_agent)
70-
all_constraints = deepcopy(ancestor_constraints)
74+
applied_constraint = AppliedConstraint(constrained_agent.constraint, constrained_agent.agent)
75+
76+
# TODO: check type of other constraints somewhere
77+
all_constraints = deepcopy(ancestor_constraints) # TODO - no deepcopy pls
7178
all_constraints.append(applied_constraint)
7279

80+
# Skip if we have already tried this set of constraints
81+
constraint_hash = hash(frozenset(all_constraints))
82+
if constraint_hash in attempted_constraint_combos:
83+
break
84+
else:
85+
attempted_constraint_combos.add(constraint_hash)
86+
7387
if verbose:
7488
print(f"\tall constraints: {all_constraints}")
7589

7690
grid.clear_constraint_points()
7791
grid.apply_constraint_points(all_constraints)
7892

93+
failed_to_plan = False
7994
for agent_idx, start_and_goal in enumerate(start_and_goals):
80-
path = single_agent_planner_class.plan(grid, start_and_goal.start, start_and_goal.goal, agent_idx, verbose)
95+
# print("planning for:", agent_idx)
96+
try:
97+
path = single_agent_planner_class.plan(grid, start_and_goal.start, start_and_goal.goal, agent_idx, verbose)
98+
except Exception as e:
99+
print(f"Failed to plan with constraints: {all_constraints}")
100+
failed_to_plan = True
101+
81102
if path is None:
82103
raise RuntimeError(f"Failed to find path for {start_and_goal}")
83104
paths[AgentId(start_and_goal.index)] = path
84105

106+
if failed_to_plan:
107+
print(f"Failed to plan with constraints: {all_constraints}")
108+
continue
109+
110+
# for (agent_idx, path) in paths.items():
111+
# print(f"\nAgent {agent_idx} path:\n {path}")
112+
85113
applied_constraint_parent = deepcopy(constraint_tree_node) #TODO: not sure if deepcopy is actually needed
86114
applied_constraint_parent.constraint = applied_constraint
87115
parent_idx = constraint_tree.add_expanded_node(applied_constraint_parent)
88116

89117
new_constraint_tree_node = ConstraintTreeNode(paths, parent_idx)
90118
if new_constraint_tree_node.constraint is None:
91119
# This means we found a solution!
120+
print("Found a path with constraints:")
121+
for constraint in all_constraints:
122+
print(f"\t{constraint}")
92123
return (start_and_goals, [paths[AgentId(i)] for i in range(len(start_and_goals))])
93124

94-
if verbose:
95-
print(f"Adding new constraint tree node with constraint: {new_constraint_tree_node.constraint}")
125+
# if verbose:
126+
print(f"Adding new constraint tree node with constraint: {new_constraint_tree_node.constraint}")
96127
constraint_tree.add_node_to_tree(new_constraint_tree_node)
97128

129+
raise RuntimeError("No solution found")
130+
# TODO
131+
# * get SIPP working w CBS
132+
# * add checks for duplicate expansions
133+
# * fan out across multiple threads
134+
# * sipp intervals seem much larger than needed?
135+
98136
verbose = False
99137
show_animation = True
100138
def main():
101139
grid_side_length = 21
102140

103-
# start_and_goals = [StartAndGoal(i, Position(1, i), Position(19, 19-i)) for i in range(1, 16)]
104-
start_and_goals = [StartAndGoal(i, Position(1, 8+i), Position(19, 19-i)) for i in range(4)]
141+
# TODO: bug somewhere where it expects agent ids to match indices
142+
# start_and_goals = [StartAndGoal(i, Position(1, i), Position(19, 19-i)) for i in range(1, 12)]
143+
start_and_goals = [StartAndGoal(i, Position(1, 8+i), Position(19, 19-i)) for i in range(6)]
144+
# start_and_goals = [StartAndGoal(i, Position(1, 2*i), Position(19, 19-i)) for i in range(4)]
145+
146+
# hallway cross
147+
# start_and_goals = [StartAndGoal(0, Position(6, 10), Position(13, 10)),
148+
# StartAndGoal(1, Position(13, 10), Position(7, 10)),
149+
# StartAndGoal(2, Position(11, 10), Position(6, 10))]
150+
151+
# temporary obstacle
152+
# start_and_goals = [StartAndGoal(0, Position(15, 14), Position(15, 16))]
153+
154+
# start_and_goals = [StartAndGoal(1, Position(6, 10), Position(8, 10)),
155+
# StartAndGoal(2, Position(13, 10), Position(11, 10))]
105156
obstacle_avoid_points = [pos for item in start_and_goals for pos in (item.start, item.goal)]
106157

107158
grid = Grid(
108159
np.array([grid_side_length, grid_side_length]),
109160
num_obstacles=250,
110161
obstacle_avoid_points=obstacle_avoid_points,
162+
# obstacle_arrangement=ObstacleArrangement.TEMPORARY_OBSTACLE,
163+
# obstacle_arrangement=ObstacleArrangement.HALLWAY,
111164
obstacle_arrangement=ObstacleArrangement.NARROW_CORRIDOR,
112165
# obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1,
113166
# obstacle_arrangement=ObstacleArrangement.RANDOM,

PathPlanning/TimeBasedPathPlanning/ConstraintTree.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ class Constraint:
1212
time: int
1313

1414
@dataclass
15-
class ForkingConstraint:
15+
class ConstrainedAgent:
16+
agent: AgentId
1617
constraint: Constraint
17-
constrained_agents: tuple[AgentId, AgentId]
18+
19+
@dataclass
20+
class ForkingConstraint:
21+
constrained_agents: tuple[ConstrainedAgent, ConstrainedAgent]
1822

1923
@dataclass(frozen=True)
2024
class AppliedConstraint:
@@ -39,30 +43,53 @@ def __lt__(self, other) -> bool:
3943
return self.cost < other.cost
4044

4145
def get_constraint_point(self, verbose = False) -> Optional[ForkingConstraint]:
42-
if verbose:
43-
print(f"\tpath for {agent_id}: {path}\n")
4446

4547
final_t = max(path.goal_reached_time() for path in self.paths.values())
4648
positions_at_time: dict[PositionAtTime, AgentId] = {}
4749
for t in range(final_t + 1):
4850
# TODO: need to be REALLY careful that these agent ids are consitent
4951
for agent_id, path in self.paths.items():
52+
# Check for edge conflicts
53+
last_position = None
54+
if t > 0:
55+
last_position = path.get_position(t - 1)
56+
5057
position = path.get_position(t)
5158
if position is None:
5259
continue
53-
# print(f"reserving pos/t for {agent_id}: {position} @ {t}")
60+
# print(f"\treserving pos/t for {agent_id}: {position} @ {t}")
5461
position_at_time = PositionAtTime(position, t)
5562
if position_at_time in positions_at_time:
5663
conflicting_agent_id = positions_at_time[position_at_time]
5764

5865
if verbose:
66+
# if True:
5967
print(f"found constraint: {position_at_time} for agents {agent_id} & {conflicting_agent_id}")
60-
return ForkingConstraint(
61-
constraint=Constraint(position=position, time=t),
62-
constrained_agents=(AgentId(agent_id), AgentId(conflicting_agent_id))
63-
)
68+
constraint = Constraint(position=position, time=t)
69+
return ForkingConstraint((
70+
ConstrainedAgent(agent_id, constraint), ConstrainedAgent(conflicting_agent_id, constraint)
71+
))
6472
else:
6573
positions_at_time[position_at_time] = AgentId(agent_id)
74+
75+
if last_position:
76+
new_position_at_last_time = PositionAtTime(position, t-1)
77+
old_position_at_new_time = PositionAtTime(last_position, t)
78+
if new_position_at_last_time in positions_at_time and old_position_at_new_time in positions_at_time:
79+
conflicting_agent_id1 = positions_at_time[new_position_at_last_time]
80+
conflicting_agent_id2 = positions_at_time[old_position_at_new_time]
81+
82+
if conflicting_agent_id1 == conflicting_agent_id2 and conflicting_agent_id1 != agent_id:
83+
print(f"Found edge constraint between with agent {conflicting_agent_id1} for {agent_id}")
84+
print(f"\tpositions old: {old_position_at_new_time}, new: {position_at_time}")
85+
new_constraint = ForkingConstraint((
86+
ConstrainedAgent(agent_id, position_at_time),
87+
ConstrainedAgent(conflicting_agent_id1, Constraint(position=last_position, time=t))
88+
))
89+
print(f"new constraint: {new_constraint}")
90+
return new_constraint
91+
else:
92+
positions_at_time[new_position_at_last_time] = AgentId(agent_id)
6693
return None
6794

6895

PathPlanning/TimeBasedPathPlanning/GridWithDynamicObstacles.py

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@ class ObstacleArrangement(Enum):
2222
ARRANGEMENT1 = 1
2323
# Static obstacle arrangement
2424
NARROW_CORRIDOR = 2
25+
# A hallway surrounded by obstacles with a 2-cell opening in the middle
26+
HALLWAY = 3
27+
# A temporary obstacle that vanishes after some time
28+
TEMPORARY_OBSTACLE = 4
2529

2630
"""
2731
Generates a 2d numpy array with lists for elements.
2832
"""
2933
def empty_2d_array_of_lists(x: int, y: int) -> np.ndarray:
3034
arr = np.empty((x, y), dtype=object)
3135
# assign each element individually - np.full creates references to the same list
32-
arr[:] = [[[] for _ in range(y)] for _ in range(x)]
36+
# arr[:] = [[[] for _ in range(y)] for _ in range(x)]
37+
for x in range(arr.shape[0]):
38+
for y in range(arr.shape[1]):
39+
arr[x, y] = []
3340
return arr
3441

3542
def empty_3d_array_of_sets(x: int, y: int, z: int) -> np.ndarray:
@@ -79,6 +86,10 @@ def __init__(
7986
self.obstacle_paths = self.obstacle_arrangement_1(num_obstacles)
8087
elif obstacle_arrangement == ObstacleArrangement.NARROW_CORRIDOR:
8188
self.obstacle_paths = self.generate_narrow_corridor_obstacles(num_obstacles)
89+
elif obstacle_arrangement == ObstacleArrangement.HALLWAY:
90+
self.obstacle_paths = self.generate_hallway_obstacles()
91+
elif obstacle_arrangement == ObstacleArrangement.TEMPORARY_OBSTACLE:
92+
self.obstacle_paths = self.generate_temporary_obstacle()
8293

8394
for i, path in enumerate(self.obstacle_paths):
8495
# TODO: i think this is a bug. obstacle indices will overlap with robot indices
@@ -89,6 +100,19 @@ def __init__(
89100
self.reservation_matrix[path[t - 1].x, path[t - 1].y, t] = obs_idx
90101
self.reservation_matrix[position.x, position.y, t] = obs_idx
91102

103+
def reset(self):
104+
self.reservation_matrix = np.zeros((self.grid_size[0], self.grid_size[1], self.time_limit))
105+
106+
# TODO: copy pasta from above
107+
for i, path in enumerate(self.obstacle_paths):
108+
# TODO: i think this is a bug. obstacle indices will overlap with robot indices
109+
obs_idx = i + 1 # avoid using 0 - that indicates free space in the grid
110+
for t, position in enumerate(path):
111+
# Reserve old & new position at this time step
112+
if t > 0:
113+
self.reservation_matrix[path[t - 1].x, path[t - 1].y, t] = obs_idx
114+
self.reservation_matrix[position.x, position.y, t] = obs_idx
115+
92116
"""
93117
Generate dynamic obstacles that move around the grid. Initial positions and movements are random
94118
"""
@@ -198,6 +222,55 @@ def generate_narrow_corridor_obstacles(self, obs_count: int) -> list[list[Positi
198222

199223
return obstacle_paths
200224

225+
226+
def generate_hallway_obstacles(self) -> list[list[Position]]:
227+
"""
228+
Generate obstacles that form a hallway with a 2-cell opening in the middle.
229+
Creates only a 1-cell border around the edges and hallway walls.
230+
231+
Pattern created:
232+
**********
233+
* *
234+
*** ***
235+
**********
236+
237+
Args:
238+
hallway_length: Length of the hallway (number of rows for the corridor)
239+
240+
Returns:
241+
List of obstacle paths, where each path represents one obstacle over time
242+
"""
243+
obstacle_paths = []
244+
245+
for y in range(8, 12):
246+
for x in range(5, 15):
247+
is_obstacle = False
248+
249+
# Border walls
250+
if x == 5 or x == 14 or y == 8 or y == 11:
251+
is_obstacle = True
252+
if y == 9 and x not in [9, 10]:
253+
is_obstacle = True
254+
255+
# If this position should be an obstacle, create a path for it
256+
if is_obstacle:
257+
obstacle_path = []
258+
for t in range(self.time_limit):
259+
obstacle_path.append(Position(x, y))
260+
obstacle_paths.append(obstacle_path)
261+
262+
return obstacle_paths
263+
264+
def generate_temporary_obstacle(self, hallway_length: int = 3) -> list[list[Position]]:
265+
"""
266+
Generates a temporary obstacle at (10, 10) that disappears at t=30
267+
"""
268+
obstacle_path = []
269+
for t in range(max(self.time_limit, 40)):
270+
obstacle_path.append(Position(15, 15))
271+
272+
return [obstacle_path]
273+
201274
def apply_constraint_points(self, constraints: list[AppliedConstraint], verbose = False):
202275
for constraint in constraints:
203276
if verbose:
@@ -299,7 +372,7 @@ def get_safe_intervals_at_cell(self, cell: Position, agent_idx: int) -> list[Int
299372
vals = self.reservation_matrix[cell.x, cell.y, :]
300373

301374
had_constraints = False
302-
# ct: AppliedConstraint
375+
303376
for constraint_set in self.constraint_points[cell.x, cell.y]:
304377
for constraint in constraint_set:
305378
if constraint.constrained_agent != agent_idx:
@@ -308,10 +381,12 @@ def get_safe_intervals_at_cell(self, cell: Position, agent_idx: int) -> list[Int
308381
continue
309382
had_constraints = True
310383
vals[constraint.constraint.time] = 99999 # TODO: no magic numbers
311-
384+
# print(f"\tapplying constraint at cell {cell}: {constraint}")
312385
# TODO: hack
313-
# if constraint.constraint.time + 1 < self.time_limit:
314-
# vals[constraint.constraint.time + 1] = 99999 # TODO: no magic numbers
386+
# if constraint.constraint.time + 3 < self.time_limit:
387+
# vals[constraint.constraint.time + 1] = 99999 # TODO: no magic numbers
388+
# vals[constraint.constraint.time + 2] = 99999 # TODO: no magic numbers
389+
# vals[constraint.constraint.time + 3] = 99999 # TODO: no magic numbers
315390

316391
# Find where the array is zero
317392
zero_mask = (vals == 0)
@@ -340,8 +415,9 @@ def get_safe_intervals_at_cell(self, cell: Position, agent_idx: int) -> list[Int
340415
intervals = [interval for interval in intervals if interval.start_time != interval.end_time]
341416

342417
# if had_constraints:
343-
# print("\t\tconstraints: ", self.constraint_points[cell.x, cell.y])
344-
# print("\t\tintervals: ", intervals)
418+
# print(f"agent {agent_idx}")
419+
# print("\t\tconstraints: ", self.constraint_points[cell.x, cell.y])
420+
# print("\t\tintervals: ", intervals)
345421
return intervals
346422

347423
"""
@@ -352,6 +428,7 @@ def reserve_path(self, node_path: NodePath, agent_index: int):
352428
if agent_index == 0:
353429
raise Exception("Agent index cannot be 0")
354430

431+
# TODO: this is wrong for SIPP
355432
for i, node in enumerate(node_path.path):
356433
reservation_finish_time = node.time + 1
357434
if i < len(node_path.path) - 1:

0 commit comments

Comments
 (0)