Skip to content
dgDavid Gray
All work

LaCam for Multi-Agent Path Finding

A from-scratch LaCam solver for CS440 — agents searching a space of joint configurations, each expanded by a constraint tree that rules out collisions and swaps.

Type
School
Year
2026
Built with
Python · Search · Multi-agent systems

Every frame above is real: a trace captured by instrumenting my actual solver as it runs on a small two-agent instance with a single-cell doorway — every push, backtrack, branch, accept and reject shown is a genuine event from the code, not a re-enactment.

For the last assignment in CS440 (Artificial Intelligence), we had to solve “hard” multi-agent path finding (MAPF) instances: get every agent from its start cell to its goal cell on a shared grid, with no two agents ever occupying the same cell at the same time and no two agents swapping places along an edge. Optimality wasn’t graded — completeness and speed were. That combination is exactly what LaCam (Okumura, 2023) is built for, so I implemented it.

Why not just run A* per agent?

Because the moment you plan agents independently, their paths collide, and patching collisions one at a time (à la CBS) means re-planning individual agents over and over as new constraints pile up. LaCam sidesteps that by searching a completely different space: instead of nodes being single-agent positions, nodes are joint configurations — one position per agent, all at once. An edge in this space is a fully collision-free simultaneous move for every agent. Find a path from the start configuration to the goal configuration in that space, and you’ve solved the whole team in one search.

The catch is that the branching factor of “every agent moves at once” is enormous — up to 5 choices per agent (4 directions + wait), so 5^n for n agents. LaCam’s second idea is what makes that tractable: at each configuration node, don’t enumerate all 5^n successors. Grow a small constraint tree instead — a breadth-first tree where each node forces one additional agent to a specific next cell, and a greedy assignment (nearest neighbor to that agent’s goal, checked against everyone already assigned) fills in the rest. Pop one constraint tree node, try to build a full successor; if it collides, that branch dies and the next constraint tree node gets a turn. The two searches interleave: depth-first over configurations, breadth-first over constraints at each one.

Three spaces, one algorithm

That gives you three different spaces to keep straight, which is the part that’s genuinely hard to hold in your head from the pseudocode alone:

  • The problem space — the actual grid: walls, start cells, goal cells.
  • The search space — the DFS over joint configurations. Each node here is a full grid state for every agent simultaneously.
  • The constraint space — the BFS tree grown at whichever configuration node is currently on top of the DFS stack, used only to generate that node’s next valid configuration.

Watch the loop at the top of the page with that in mind: the constraint tree (right panel) grows and gets pruned every time the two agents contend for the doorway, and that failure or success feeds directly into whether the search tree (middle panel) gains a new node or backtracks.

What I’d change

The greedy assignment inside each constraint tree node has no lookahead, so on this doorway instance it backtracks a fair amount before it finds the crossing order that works — you can see it in the trace. The published LaCam paper adds a PIBT-style priority scheme to cut that down substantially. I kept mine simple on purpose: the assignment above is exactly what’s in hard_mapf.py, and it’s fast enough to pass every instance in the course’s test suite well under the time limit, which was the actual assignment.

Let's connect

Have something hard that has to work?

Contract work, a full-time role, or a problem you want a second opinion on — I read every message and reply to the ones that aren't a template.

Email me

Usually replies within 24 hours · St. Louis, Missouri

ESC

Home

Main landing page & highlights

Page

Work

All engineering projects & case studies

Page

Writing

Technical notes & engineering articles

Page

Photos

Photography & outdoor expeditions

Page

About

Background, career timeline & skills

Page

Contact

Email, inquiries & consultation

Page

Copy Email Address

davidgraymi@gmail.com

Action

Toggle Light / Dark Theme

Switch color theme

Action

Download Résumé

Word document (.docx)

Download

Play Wordlet

Launch the Flutter word puzzle in browser

Game

Visit GitHub

github.com/davidgraymi

External

Visit LinkedIn

linkedin.com/in/david-gray-mi

External

A processor built from the ground up

Kernel, task scheduler and communication protocol written from nothing, in C and Ada, now in production and distributed globally.

Professional

Sign Language Interpretation System

Control any smart device with American Sign Language — hand tracking, a gesture classifier and grammar correction, wired together over a client-server link.

Capstone

A system library that runs anywhere

One portable C++ library targeting any RTOS or bare-metal system, packaged with Docker, Bazel and GitLab CI — cutting porting work by 4×.

Professional

LaCam for Multi-Agent Path Finding

A from-scratch LaCam solver for CS440 — agents searching a space of joint configurations, each expanded by a constraint tree that rules out collisions and swaps.

School

Patient Care Portal Triage

An NLP pipeline that routes patient portal messages to the right team and surfaces relevant evidence-based research while they wait for a human.

Research

Bindersnap

GitHub for everyone who doesn't use GitHub — document management, review lifecycle, team collaboration, search and an in-browser editor.

Personal

Wordlet

A word guessing game built in Flutter and compiled to the web — playable right here, no install, no account.

Personal

Reinforcement Learning applied to Tetris

An agent that learns to play Tetris — a genuinely fun excuse to get hands-on with reward shaping.

School

Handwritten digit recognition with weighted KNN

A KNN classifier for handwritten digits, plus a k-value optimizer that collapses repeated distance calculations into a single pass.

School

ID3 mushroom classification

A from-scratch ID3 decision tree that predicts whether a mushroom is edible or poisonous from its attributes.

School

Healthcare length-of-stay estimator

A data mining project predicting how long a patient will stay, from admission-time features.

School

This website

A static site built with Astro, hosted free on GitHub Pages, designed so writing a post costs one markdown file and nothing else.

Personal

GPU vs. CPU: Roofline Benchmarks on Apple Silicon MPS with neural-cost v0.2.0

The neural-cost v0.2.0 GPU benchmark moves from CPU roofline analysis to Apple Silicon's MPS GPU backend — measuring PyTorch and JAX across five architectures, four batch sizes, and both eager and compiled execution modes.

Post

PyTorch vs. JAX vs. TensorFlow: A Roofline Benchmark Across 5 Architectures

An empirical investigation comparing PyTorch 2.14, JAX 0.11, and TensorFlow across five model architectures and four batch sizes, measuring compiler speedups, kernel overheads, and hardware limits on Apple Silicon.

Post

neural-cost: Roofline Analysis Across PyTorch, JAX, and TensorFlow

A framework-neutral Python library that estimates a neural network's FLOPs and tensor traffic, measures its runtime, and uses a roofline model to tell you exactly where performance is being left on the table.

Post

I rebuilt this site so that publishing costs one file

The old site was a single 41,000-character index.html. Every change was archaeology. Here is what I replaced it with and why the constraint was maintenance, not design.

Post
↑ ↓ to navigate↵ to select
Linear / Raycast mode