@inproceedings{silver2005cooperative,
title={Cooperative pathfinding},
author={Silver, David},
booktitle={Proceedings of the aaai conference on artificial intelligence and interactive digital entertainment},
volume={1},
number={1},
pages={117--122},
year={2005}
}
To start experimenting the code environment, please look at Getting Started file.
The paper studies Cooperative Pathfinding, a multi-agent path planning setting where all agents have full knowledge of each otherβs planned routes. The target domain is real-time, congested environments (strategic games), where classical replanning-based approaches fail due to bottlenecks, deadlocks, and oscillations.
The work introduces and evaluates three decoupled but cooperative algorithms:
- Cooperative A* (CA*): It searches space-time for a non-colliding route.
- Hierarchical Cooperative A* (HCA*): It uses an abstract heuristic to boost performance.
- Windowed Hierarchical Cooperative A* (WHCA*): It limits the space-time search depth to a dynamic window, spreading computation over the duration of the route.
These are compared against the industry-standard Local Repair A* (LRA*).
LRA* works in the following manner:
- Each agent plans independently with A*, ignoring others except for imminent collisions.
- On collision, the agent replans.
- Every time an agent is forced to replan, its agitation level increases.
- Random noise then added to the A* heuristic, proportional to agitation.
- The hope is that randomness helps agents wiggle out of deadlocks or bottlenecks.
LRA* weakness:
- Deadlocks in narrow passages.
- Excessive replanning.
- Cyclic behavior.
- Poor scalability with agent count.
It plans each agent's path in space-time while treating other agents' planned future positions as hard constraints via a reservation table, ensuring collision-free execution, but at high computational cost with strong dependence on agent ordering.
The reservation table is a shared structure that records "which spatial locations are occupied at which times".
Formally:
- Each entry is a tuple
(x, y, t) - If a cell is reserved at time
t, no other agent may occupy it att. - Waiting in place is allowed, but only if the space-time cell is free.
The table is typically sparse and implemented as a hash map.
Agents are planned sequentially using a fixed priority ordering:
Earlier agents constrain later ones.
For agent
- State space:
(x, y, t) - Actions:
- Move
N,E,S,W - Wait (stay in place)
- Move
- Transition cost: usually 1 per step
- Heuristic: typically Manhattan distance (ignoring time and agents)
During search:
- Any successor
(x', y', t+1)that is reserved is forbidden. - Collisions are avoided by construction.
Once a path is found:
- All
(x, y, t)states along the path are inserted into the reservation table. - These become hard constraints for all later agents.
continue until all agents are planned or a failure occurs.
If CA* finds a path for all agents, then:
- β οΈ No two agents collide (vertex conflict)
- β οΈ Agents follow consistent, non-oscillatory paths
- β οΈ Each agent has a complete, time-indexed plan
However, CA* is not complete in general.
1) Sensitivity to Agent Ordering: Because CA* is greedy and sequential
- A poor priority order can make the problem unsolvable
- Early agents may block critical corridors indefinitely
This is a classic weakness of prioritized planning.
2) Computational Cost: The search space is 3D (x, y, t)
- Branching factor increases (including
waitactions) - Time horizon can grow large
- Full paths must be planned before execution
This makes CA* slow for real-time systems.
3) Goal Blocking: Once an agent reaches its goal
- It remains there forever in the reservation table.
- This can block other agents in narrow environments.
CA* has no mechanism for ongoing cooperation after arrival.
procedure CA_STAR(map, agents_in_priority_order):
R β empty_reservation_table()
for agent a in agents_in_priority_order do
path_a β SPACE_TIME_ASTAR(map, a.start, a.goal, R, heuristic=MANHATTAN)
if path_a = FAIL then
return FAIL
RESERVE_PATH(R, path_a) // mark (x,y,t) along the path
a.path β path_a
end for
return {a.path for all agents a}
procedure SPACE_TIME_ASTAR(map, s, g, R, heuristic):
// state: (x, y, t)
open β priority_queue()
g_cost[(s.x, s.y, 0)] β 0
push open with node (s.x, s.y, 0) with f = g_cost + heuristic((x,y), g)
while open not empty do
n β pop_min_f(open)
if (n.x, n.y) = g then
return RECONSTRUCT_PATH(n)
for each action in {N,E,S,W,WAIT} do
n2 β successor_state(n, action) // (x',y',t+1)
if n2 hits_obstacle(map) then continue
if RESERVED(R, n2.x, n2.y, n2.t) then continue // vertex conflict
if EDGE_CONFLICT(R, n, n2) then continue // swap conflict (recommended)
tentative β g_cost[n] + 1
if n2 not seen or tentative < g_cost[n2] then
parent[n2] β n
g_cost[n2] β tentative
f β tentative + heuristic((n2.x,n2.y), g)
push_or_decrease_key(open, n2, f)
end for
end while
return FAIL
CA* is the conceptual foundation for:
- Hierarchical Cooperative A* (HCA*)
- Windowed HCA* (WHCA*)
- Modern reservation-table MAPF methods
- Rolling-horizon and execution-aware planners
Later methods keep CA*βs explicit spaceβtime constraints, but:
- Limit planning horizon
- Add replanning
- Allow agents to move after reaching goals
- Reduce sensitivity to priority order
Hierarchical Cooperative A* (HCA*) is an extension of Cooperative A* (CA*) that reduces computational cost by using a hierarchical, abstraction-based heuristic, while keeping the same cooperative planning framework.
CA* plans in full spaceβtime (x, y, t) and is expensive because:
- The search space is large (extra time dimension)
- The heuristic (e.g., Manhattan distance) ignores agent interactions poorly
- Many unnecessary nodes are expanded
HCA* addresses this by giving CA* a much stronger heuristic.
HCA* uses a hierarchical abstraction of the environment to compute exact distances in a simplified domain, and uses those distances as an admissible heuristic during cooperative spaceβtime search.
Crucially:
- The abstraction removes time
- The abstraction removes other agents
- Only static obstacles remain
Used for actual planning: (x, y, t)
procedure HCA_STAR(map, agents_in_priority_order):
R β empty_reservation_table()
for agent a in agents_in_priority_order do
// per-agent abstract heuristic cache
rra β INIT_RRA_STAR(map_2D_without_agents, a.goal, a.start)
path_a β SPACE_TIME_ASTAR(map, a.start, a.goal, R,
heuristic = (pos)->ABSTRACT_DIST(rra, pos))
if path_a = FAIL then
return FAIL
RESERVE_PATH(R, path_a)
a.path β path_a
end for
return {a.path for all agents a}
procedure INIT_RRA_STAR(map2D, goal, origin):
// Reverse A* from goal; can be resumed to compute dist(nodeβgoal)
rra.open β PQ(); rra.closed β set()
set g(goal)=0
push rra.open goal with f = g(goal) + MANHATTAN(goal, origin)
rra.goal β goal; rra.origin β origin; rra.map2D β map2D
return rra
procedure ABSTRACT_DIST(rra, node_xy):
if node_xy in rra.closed then
return g(node_xy) // already optimal distance to goal
RESUME_RRA_UNTIL(rra, node_xy)
if node_xy in rra.closed then return g(node_xy)
return +β
procedure RESUME_RRA_UNTIL(rra, target_xy):
while rra.open not empty do
p β pop_min_f(rra.open)
add p to rra.closed
if p = target_xy then return
for each neighbor q of p in 4-neighborhood do
if q blocked in rra.map2D then continue
tentative β g(p) + 1
if q not in rra.open and q not in rra.closed then
set g(q)=tentative
f(q)=g(q)+MANHATTAN(q, rra.origin)
push rra.open q
else if q in rra.open and tentative < g(q) then
update g(q), decrease_key in rra.open
end for
end while
procedure WHCA_STAR(map, agents, window_size w, replan_every k):
// Typical choice in the paper: replan when halfway through window β k = w/2
R β empty_reservation_table()
// Each agent keeps its own RRA* cache across windows (reuse)
for each agent a do
a.rra β INIT_RRA_STAR(map_2D_without_agents, a.goal, a.start)
a.plan β empty
end for
t_global β 0
while not all agents finished (or for each simulation tick) do
// Option A: plan for all agents each cycle; Option B: stagger planning
for each agent a in some scheduling order do
// compute partial plan of length w in space-time (relative time 0..w)
partial β WINDOWED_SPACE_TIME_ASTAR(map, a.pos, a.goal, R, w,
heuristic=(pos)->ABSTRACT_DIST(a.rra, pos))
if partial = FAIL then
return FAIL
// Reserve only within the window horizon
RESERVE_PATH_WINDOW(R, a.id, partial, t_global, w)
a.plan β partial
end for
// Execute k steps, then slide window
for step = 1..k do
for each agent a do
a.pos β EXECUTE_NEXT_ACTION(a.plan)
end for
t_global β t_global + 1
ADVANCE_RESERVATIONS_TIME(R) // optional housekeeping
end for
end while
return SUCCESS
procedure WINDOWED_SPACE_TIME_ASTAR(map, s_xy, g_xy, R, w, heuristic):
// state: (x, y, Ο) where Ο is time offset inside window: 0..w
// Terminal rule at depth w: connect to goal with cost=abstract_dist
open β PQ()
start_state β (s_xy.x, s_xy.y, 0)
g_cost[start_state] β 0
push open start_state with f = 0 + heuristic(s_xy)
while open not empty do
n β pop_min_f(open)
if n.Ο = w then
// reached window boundary: terminate and score using abstract distance
return RECONSTRUCT_PARTIAL_PATH(n)
for each action in {N,E,S,W,WAIT} do
n2 β successor(n, action) // (x',y',Ο+1)
if n2 hits_obstacle(map) then continue
if RESERVED(R, n2.x, n2.y, absolute_time = current_global_time + n2.Ο) then continue
if EDGE_CONFLICT(R, n, n2, absolute_time) then continue
step_cost β 1
tentative β g_cost[n] + step_cost
// f uses heuristic normally, but implicitly WHCA* treats depth=w as terminal
f β tentative + heuristic((n2.x, n2.y))
if n2 not seen or tentative < g_cost[n2] then
parent[n2] β n
g_cost[n2] β tentative
push_or_decrease_key(open, n2, f)
end for
end while
return FAIL