TECHNOLOGY · PDE TRAFFIC FIELD

The field that learned traffic by solving it, not by memorizing it.

A continuity-plus-potential PDE on a 256 × 64 grid that evolves a scalar risk surface across the 40 m in front of the ego. No learned weights. No hidden heuristics. Every cell is the arithmetic result of the equation on the cells next to it.

Three equations. That's it.

This is the whole model. The implementation is a direct finite-difference port in ~300 lines of Rust. Read it, step through it, prove it, there's nothing hidden.

CONTINUITY
∂ₜρ + ∇·(ρu) = 0

Traffic density ρ is conserved as it flows through the grid, mass in equals mass out, same property that governs fluids and crowds.

VELOCITY FIELD
u = −κ∇φ − α∇ρ

Flow descends the potential gradient (goal-seeking) while spreading away from high density (repulsion). Two constants, κ and α, set the trade-off.

POTENTIAL
∂ₜφ = cφ²∇²φ + Jego

The scalar potential diffuses across the grid and is sourced by the ego's intended path. The CFL condition sets dt ≤ dx² / (4·cφ²).

256 × 64
cells (lateral × longitudinal)
0.2 × 0.625 m
cell resolution
dt ≈ 0.004 s
CFL-stable time step
< 2 ms
field solve per 20 Hz tick

Why a PDE beside a neural stack?

This isn't an ideology. A deterministic PDE layer is useful in four places neural stacks are hardest to review.

Determinism, bit for bit

Same inputs produce the same observer outputs on any supported CPU. Replay an incident log and inspect the same hazard sequence every time.

Auditable without a PhD

A reviewer can step through the finite-difference update cell by cell. The risk surface at (i, j) is derived from explicit neighboring values.

Bounded failure modes

The CFL condition gives the field update an explicit stability envelope. Learned components can still be used around it, but the observer layer stays bounded.

Runs on commodity CPU

The field solve is small enough for commodity CPU evaluation, so teams can start replaying scenes without a new GPU-heavy training program.

IN · OUT

Trajectories in, hazards out

Predicted tracks from the motion-prediction stage get converted to weighted density seeds and deposited into the ρ field. The solver advances for one 20 Hz tick, then a threshold check surfaces any cell whose density crosses the hazard boundary.

  • Confidence-weighted seeds: lower confidence → softer density injection.
  • Weight-bucketed for deterministic summation independent of input order.
  • Neumann boundary (zero-gradient) keeps the field stable at the grid edges.
solver.rs
// Finite-difference step (excerpt)
// Continuity: ∂ₜρ + ∇·(ρu) = 0
let flux_x = upwind_flux(&rho, &u, dx);
let flux_y = upwind_flux(&rho, &v, dy);
rho -= dt * (flux_x + flux_y);

// Velocity from gradients
let grad_phi = gradient(&phi, dx, dy);
let grad_rho = gradient(&rho, dx, dy);
u = -kappa * grad_phi.0 - alpha * grad_rho.0;
v = -kappa * grad_phi.1 - alpha * grad_rho.1;

// Potential diffusion + ego source
phi += dt * (c_phi.powi(2) * laplacian(&phi)
            + j_ego(&ego_pose));

// Hazard threshold
let hazard = rho.iter()
    .any(|&r| r > cfg.hazard_threshold);

Read the solver. Run the solver.

The full PDE solver is in the nfs-traffic crate. Under 400 lines of Rust. No dependencies you haven't heard of.