1010 ObstacleArrangement ,
1111 Position ,
1212)
13+ from typing import Optional
1314from copy import deepcopy
1415from PathPlanning .TimeBasedPathPlanning .BaseClasses import MultiAgentPlanner , StartAndGoal
1516from PathPlanning .TimeBasedPathPlanning .Node import NodePath
@@ -33,7 +34,7 @@ def plan(grid: Grid, start_and_goals: list[StartAndGoal], single_agent_planner_c
3334
3435 initial_solution : dict [AgentId , NodePath ] = {}
3536
36- # Generate initial solution (no reservations for robots )
37+ # Generate initial solution (no constraints )
3738 for start_and_goal in start_and_goals :
3839 path = single_agent_planner_class .plan (grid , start_and_goal .start , start_and_goal .goal , start_and_goal .index , verbose )
3940 initial_solution [AgentId (start_and_goal .index )] = path
@@ -44,126 +45,135 @@ def plan(grid: Grid, start_and_goals: list[StartAndGoal], single_agent_planner_c
4445 print (f"\n Agent { agent_idx } path:\n { path } " )
4546
4647 constraint_tree = ConstraintTree (initial_solution )
47-
4848 attempted_constraint_combos = set ()
4949
5050 while constraint_tree .nodes_to_expand :
5151 constraint_tree_node = constraint_tree .get_next_node_to_expand ()
5252 ancestor_constraints = constraint_tree .get_ancestor_constraints (constraint_tree_node .parent_idx )
5353
54- # print(f"Expanded node: {constraint_tree_node.constraint} with parent: {constraint_tree_node.parent_idx}")
55- # print(f"\tAncestor constraints: {ancestor_constraints}")
56-
5754 if verbose :
5855 print (f"Expanding node with constraint { constraint_tree_node .constraint } and parent { constraint_tree_node .parent_idx } " )
56+ print (f"\t COST: { constraint_tree_node .cost } " )
5957
6058 if constraint_tree_node is None :
6159 raise RuntimeError ("No more nodes to expand in the constraint tree." )
6260 if not constraint_tree_node .constraint :
6361 # This means we found a solution!
62+ print (f"Found a path with constraints after { constraint_tree .expanded_node_count ()} expansions:" )
63+ print (f"Final cost: { constraint_tree_node .cost } " )
6464 return (start_and_goals , [constraint_tree_node .paths [start_and_goal .index ] for start_and_goal in start_and_goals ])
6565
6666 if not isinstance (constraint_tree_node .constraint , ForkingConstraint ):
6767 raise ValueError (f"Expected a ForkingConstraint, but got: { constraint_tree_node .constraint } " )
6868
69- # TODO: contents of this loop should probably be in a helper?
7069 for constrained_agent in constraint_tree_node .constraint .constrained_agents :
71- if verbose :
72- print (f"\n Outer loop step for agent { constrained_agent } " )
73-
7470 applied_constraint = AppliedConstraint (constrained_agent .constraint , constrained_agent .agent )
7571
76- # TODO: check type of other constraints somewhere
7772 all_constraints = deepcopy (ancestor_constraints ) # TODO - no deepcopy pls
7873 all_constraints .append (applied_constraint )
7974
80- num_expansions = constraint_tree .expanded_node_count ()
81- if num_expansions % 50 == 0 :
82- print (f"Expanded { num_expansions } nodes so far..." )
83- print (f"\t len of constraints { len (all_constraints )} " )
84-
85- # Skip if we have already tried this set of constraints
86- constraint_hash = hash (frozenset (all_constraints ))
87- if constraint_hash in attempted_constraint_combos :
88- if verbose :
89- print (f"\t Skipping already attempted constraint combination: { all_constraints } " )
75+ new_path = ConflictBasedSearch .plan_for_agent (constrained_agent , all_constraints , constraint_tree , attempted_constraint_combos , grid , single_agent_planner_class , start_and_goals )
76+ if not new_path :
9077 continue
91- else :
92- attempted_constraint_combos .add (constraint_hash )
9378
94- if verbose :
95- print (f"\t all constraints: { all_constraints } " )
96-
97- grid .clear_constraint_points ()
98- grid .apply_constraint_points (all_constraints )
99-
100- # Just plan for agent with new constraint
101- start_and_goal = ConflictBasedSearch .find_by_index (start_and_goals , constrained_agent .agent )
102- try :
103- if verbose :
104- print ("\t planning for: {}" , start_and_goal )
105- new_path = single_agent_planner_class .plan (grid , start_and_goal .start , start_and_goal .goal , start_and_goal .index , verbose )
106- except Exception as e :
107- continue
108-
109- applied_constraint_parent = deepcopy (constraint_tree_node ) #TODO: not sure if deepcopy is actually needed
110- paths : dict [AgentId , NodePath ] = deepcopy (constraint_tree_node .paths ) # TODO: not sure if deepcopy is actually needed
79+ # Deepcopy to update with applied constraint and new paths
80+ applied_constraint_parent = deepcopy (constraint_tree_node )
81+ # TODO: could have a map under the hood to make these copies cheaper
82+ paths : dict [AgentId , NodePath ] = deepcopy (constraint_tree_node .paths )
11183 paths [constrained_agent .agent ] = new_path
11284
113- # for (agent_idx, path) in paths.items():
114- # print(f"\nAgent {agent_idx} path:\n {path}")
85+ if verbose :
86+ for (agent_idx , path ) in paths .items ():
87+ print (f"\n Agent { agent_idx } path:\n { path } " )
11588
11689 applied_constraint_parent .constraint = applied_constraint
90+ # applied_constraint_parent.paths = paths
11791 parent_idx = constraint_tree .add_expanded_node (applied_constraint_parent )
11892
11993 new_constraint_tree_node = ConstraintTreeNode (paths , parent_idx , all_constraints )
12094 if new_constraint_tree_node .constraint is None :
12195 # This means we found a solution!
122- print (f"Found a path with constraints after { num_expansions } expansions:" )
96+ print (f"Found a path with constraints after { constraint_tree . expanded_node_count () } expansions:" )
12397 for constraint in all_constraints :
12498 print (f"\t { constraint } " )
125- # return (start_and_goals, [paths[AgentId(i)] for i in range(len(start_and_goals))] )
99+ print ( f"Final cost: { constraint_tree_node . cost } " )
126100 return (start_and_goals , paths .values ())
127101
128- # if verbose:
129- # print(f"Adding new constraint tree node with constraint: {new_constraint_tree_node.constraint}")
102+ if verbose :
103+ print (f"Adding new constraint tree node with constraint: { new_constraint_tree_node .constraint } " )
130104 constraint_tree .add_node_to_tree (new_constraint_tree_node )
131105
132106 raise RuntimeError ("No solution found" )
133107
134- # TODO: bad function name
135- def find_by_index (start_and_goal_list : list [StartAndGoal ], target_index : AgentId ) -> AgentId :
108+ def get_agents_start_and_goal (start_and_goal_list : list [StartAndGoal ], target_index : AgentId ) -> StartAndGoal :
136109 for item in start_and_goal_list :
137110 if item .index == target_index :
138111 return item
139112 raise RuntimeError (f"Could not find agent with index { target_index } in { start_and_goal_list } " )
140113
114+
115+ def plan_for_agent (constrained_agent : ConstraintTreeNode ,
116+ all_constraints : list [AppliedConstraint ],
117+ constraint_tree : ConstraintTree ,
118+ attempted_constraint_combos : set ,
119+ grid : Grid ,
120+ single_agent_planner_class : SingleAgentPlanner ,
121+ start_and_goals : list [StartAndGoal ]) -> Optional [tuple [list [StartAndGoal ], list [NodePath ]]]:
122+
123+ num_expansions = constraint_tree .expanded_node_count ()
124+ if num_expansions % 50 == 0 :
125+ print (f"Expanded { num_expansions } nodes so far..." )
126+ print (f"\t Number of constraints on expanded node: { len (all_constraints )} " )
127+
128+ # Skip if we have already tried this set of constraints
129+ constraint_hash = hash (frozenset (all_constraints ))
130+ if constraint_hash in attempted_constraint_combos :
131+ if verbose :
132+ print (f"\t Skipping already attempted constraint combination: { all_constraints } " )
133+ return None
134+ else :
135+ attempted_constraint_combos .add (constraint_hash )
136+
137+ if verbose :
138+ print (f"\t all constraints: { all_constraints } " )
139+
140+ grid .clear_constraint_points ()
141+ grid .apply_constraint_points (all_constraints )
142+
143+ # Just plan for agent with new constraint
144+ start_and_goal = ConflictBasedSearch .get_agents_start_and_goal (start_and_goals , constrained_agent .agent )
145+ try :
146+ if verbose :
147+ print ("\t planning for: {}" , start_and_goal )
148+ new_path = single_agent_planner_class .plan (grid , start_and_goal .start , start_and_goal .goal , start_and_goal .index , verbose )
149+ return new_path
150+ except Exception as e :
151+ print (f"Error: { e } " )
152+ return None
153+
141154# TODO
142- # * still discrepancies between sipp and A*
143155# * fan out across multiple threads
144156# * somehow test/check that high level tree is doing what you want
145- # * SIPP stinks at 3 robots in the hallway case
146157verbose = False
147158show_animation = True
148159np .random .seed (42 ) # For reproducibility
149160def main ():
150161 grid_side_length = 21
151162
152- # TODO: bug somewhere where it expects agent ids to match indices
153163 # start_and_goals = [StartAndGoal(i, Position(1, i), Position(19, 19-i)) for i in range(1, 12)]
154- # start_and_goals = [StartAndGoal(i, Position(1, 8+i), Position(19, 19-i)) for i in range(6 )]
164+ start_and_goals = [StartAndGoal (i , Position (1 , 8 + i ), Position (19 , 19 - i )) for i in range (5 )]
155165 # start_and_goals = [StartAndGoal(i, Position(1, 2*i), Position(19, 19-i)) for i in range(4)]
156166
157167 # generate random start and goals
158- start_and_goals : list [StartAndGoal ] = []
159- for i in range (40 ):
160- start = Position (np .random .randint (0 , grid_side_length ), np .random .randint (0 , grid_side_length ))
161- goal = Position (np .random .randint (0 , grid_side_length ), np .random .randint (0 , grid_side_length ))
162- while any ([start_and_goal .start == start for start_and_goal in start_and_goals ]):
163- start = Position (np .random .randint (0 , grid_side_length ), np .random .randint (0 , grid_side_length ))
164- goal = Position (np .random .randint (0 , grid_side_length ), np .random .randint (0 , grid_side_length ))
168+ # start_and_goals: list[StartAndGoal] = []
169+ # for i in range(40):
170+ # start = Position(np.random.randint(0, grid_side_length), np.random.randint(0, grid_side_length))
171+ # goal = Position(np.random.randint(0, grid_side_length), np.random.randint(0, grid_side_length))
172+ # while any([start_and_goal.start == start for start_and_goal in start_and_goals]):
173+ # start = Position(np.random.randint(0, grid_side_length), np.random.randint(0, grid_side_length))
174+ # goal = Position(np.random.randint(0, grid_side_length), np.random.randint(0, grid_side_length))
165175
166- start_and_goals .append (StartAndGoal (i , start , goal ))
176+ # start_and_goals.append(StartAndGoal(i, start, goal))
167177
168178 # hallway cross
169179 # start_and_goals = [StartAndGoal(0, Position(6, 10), Position(13, 10)),
@@ -183,8 +193,8 @@ def main():
183193 obstacle_avoid_points = obstacle_avoid_points ,
184194 # obstacle_arrangement=ObstacleArrangement.TEMPORARY_OBSTACLE,
185195 # obstacle_arrangement=ObstacleArrangement.HALLWAY,
186- # obstacle_arrangement=ObstacleArrangement.NARROW_CORRIDOR,
187- obstacle_arrangement = ObstacleArrangement .NONE ,
196+ obstacle_arrangement = ObstacleArrangement .NARROW_CORRIDOR ,
197+ # obstacle_arrangement=ObstacleArrangement.NONE,
188198 # obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1,
189199 # obstacle_arrangement=ObstacleArrangement.RANDOM,
190200 )
0 commit comments