In a simple single-goal pathfinding problem, the search state is just the agent’s position — each cell is either visited or not, and the algorithm never needs to return to a cell it has already seen. Robotaxi Zoox cannot use that shortcut. Because the taxi must collect every passenger before reaching the destination, revisiting a cell is sometimes necessary: the taxi might need to backtrack through an already-visited corridor after picking up a passenger. A position-only state would incorrectly block those revisits. The solution is a composite state that encodes not just where the taxi is, but also which passengers it has already collected — making two visits to the same cell genuinely different states if the set of collected passengers has changed between them.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Juan-Carlos-Cruz/robotaxi-zoox/llms.txt
Use this file to discover all available pages before exploring further.
The Composite State
Every point in the search space is fully described by two pieces of information, both stored as attributes on aNode object:
| Attribute | Type | Meaning |
|---|---|---|
posicion | tuple[int, int] | Current (row, col) of the taxi |
pasajeros_recogidos | frozenset[tuple[int, int]] | Positions of all passengers collected so far |
pasajeros_recogidos:
Node also carries g (accumulated cost from the start), h (heuristic estimate), f = g + h (total priority for informed search), profundidad (depth from the root), and a padre pointer for path reconstruction.
Why a frozenset?
The collected-passenger component of the state is stored as afrozenset rather than a list or a regular set for two reasons:
- Immutability — a
frozensetcannot be changed after creation. WhenGrid.get_vecinosbuilds a new successor node, it creates a brand-newfrozensetby converting a temporary mutable set. The parent node’s state is never touched. - Hashability — because it is immutable, a
frozensetcan participate in a hash computation. TheNode.__hash__method combines the position tuple and thefrozensetinto a single hash value, which lets nodes be stored directly in Pythonsetanddictstructures for O(1) visited-state lookup:
State Transitions
Grid.get_vecinos(nodo) is the successor function. Given a node, it attempts all four directional moves and, for each passable target cell, constructs a new Node with an updated state:
- The new
posicionis the(row, col)of the target cell. - The new
gisnodo.g + costo_movimiento(target)— cost 7 forFLUJO_ALTO, cost 1 for everything else. - If the target cell contains a passenger (
Grid.PASAJERO), that cell’s coordinates are added topasajeros_recogidosbefore the newfrozensetis frozen. The pickup is automatic — the taxi does not need a separate action.
Avoiding Cycles While Allowing Revisits
The search algorithms maintain avisitados set of already-expanded composite states. Before expanding a node, the algorithm checks:
pasajeros_recogidos, visiting a cell a second time after collecting a new passenger produces a different composite state — (pos, {passenger_A}) is not the same as (pos, frozenset()) — and is therefore not filtered out. This is what allows the taxi to legally backtrack through previously visited corridors after a pickup.
Goal Test
A node is the goal when both components of its state simultaneously satisfy the terminal conditions:pasajeros_totales is a frozenset of all passenger positions read from the map — constructed once before the search starts as frozenset(grid.pasajeros). The equality check nodo.pasajeros_recogidos == pasajeros_totales is an O(n) frozenset comparison that confirms every passenger has been collected.
The goal test is evaluated after the node is dequeued, not when it is generated. For BFS this makes no practical difference, but for UCS and A* it ensures the returned cost is the true optimal cost, not an underestimate from the generation step.