ADR-022: vi-due — Friesz et al. (1993) VI dynamic user equilibrium¶
Status: accepted (implemented)
Date: 2026-07-10
Deciders: analytical-DTA track — the dynamic USER equilibrium (closes the track)
File: docs/design/adr-022-vi-due.md
Context¶
Friesz, Bernstein, Smith, Tobin & Wie (1993) cast the simultaneous
route-AND-departure-time (SRDC) dynamic user equilibrium as a variational
inequality over path departure-rate profiles: find h* in the volume-feasible
set (sum_p int h_p = Q per OD) with <Psi(h*), h - h*> >= 0, where
Psi_p(t, h) is the effective delay (traversal plus schedule-delay penalty).
At a DUE every USED (route, departure-time) pair carries equal minimal
effective delay and unused pairs cost at least as much — the field’s standard
DUE definition, first stated in this paper. The VI is loading-agnostic; the
1993 paper’s own link-delay model is FIFO-safe only for affine delays, so the
computable-DUE literature instantiates the same VI with the generalized
Vickrey point-queue loading (Han, Friesz & Yao 2013 — the standing existence
theory). That is exactly the loading this repo already ships (bottleneck/,
ADR-019), which makes the faithful, certifiable benchmark unit: the Friesz
VI on parallel Vickrey-bottleneck routes — one route with f = 0 reduces
literally to the shipped vickrey model, and the route axis adds the
simultaneous-choice content the single-bottleneck model lacks.
Decision¶
Extend the
bottleneck/module (due.py) — the machinery is the departure-time-equilibrium machinery.DUEScenario(frozen, read-only, hashed under"tabench-due-scenario-v1;") is the Vickrey scalars plus per-route(f_r, s_r)arrays. The analytic structure is exposed on the scenario: withdelta = beta*gamma/(beta+gamma)each used route runs its own Vickrey equilibrium at the common cost levelC, soN_r = s_r (C - alpha f_r)/deltaandC = (delta*N + alpha * sum_U s_r f_r) / sum_U s_r
with the used set
Ufound by a greedy sweep in increasing-forder (a route is used iffalpha f_r < C).due_closed_formemits aDUEProfile— per-route cumulative departure curves on one shared grid (the multi-routeBottleneckSchedule).P1 certificate (
metrics/due_gaps.py,DUEEvaluator). From(scenario, emitted profile)alone: censor gates (hash, finiteness, grid monotonicity, zero starts, nondecreasing rows, TOTAL-volume conservation — the per-route split is deliberately ungated: the DUE chooses it); per route, shift byf_r, extend the grid until the queue provably clears, simulate the deterministic point queue, and score PER TRAVELER by level inversion of both curves (the ADR-019 lesson, unchanged). The route axis introduces a genuinely new failure mode the single-route certifier cannot see: an all-on-one-route profile equalizes its own used costs while the idle route is strictly cheaper. The reference minimum therefore scans the marginal-insertion cost of every route (used or not) — an infinitesimal deviator joins behindR_r(t)travelers, exiting atmax(t + f_r, D_r^{-1}(R_r(t)))— over the profile’s grid UNION a dense harness-chosen sweep of the analytic window UNION{t* - f_r}(a profile cannot hide the cheap region by its choice of grid). Score:due_gap = (max used per-traveler cost - min marginal cost anywhere) / C
0iff the discretized Friesz DUE conditions hold across BOTH choice dimensions; positive with the incentive to deviate in time OR route. Tier-B: recomputed totals (cost, expected cost, max queue, travel delay).
Analytic anchors (machine-verified — test_due.py)¶
Worked instance friesz_two_route_scenario (N=6000, routes (f=0.2, s=3000) and (f=0.7, s=1500), alpha=1, beta=0.5, gamma=2, t*=9):
C = 0.9; split(5250, 750); queue costs(0.7, 0.2); windows[7.4, 9.15]and[7.9, 8.4]; total costC*N = 5400; max queue 2100. The closed form certifiesdue_gap = 0.Both-used threshold: routes 2 is used iff
N > alpha*s_1*(f_2 - f_1)/delta = 3750; atN = 3000only route 1 runs (C = 0.6 < alpha f_2 = 0.7) and still certifies.Equal free-flow times: the split is capacity-proportional,
C = delta*N/(s_1+s_2).Single-route
f = 0reduction: identicalC*to the shippedvickreymodel, the emitted curve matchesue_closed_formpointwise, and the DUE profile certifiesequilibrium_gap ~ 1e-13under the ADR-019BottleneckEvaluator— a cross-certifier consistency pin.False equilibria rejected: all-on-one-route scores exactly
(1.0 - 0.7)/0.9 = 1/3(the marginal-insertion scan); the ADR-019 burst-dump and an SO-style metered profile score large positive gaps; non-conserving/non-monotone/wrong-hash profiles are censored.
During research the closed form was additionally verified by an independent
complementarity-bisection solve (scalar monotone fixed point on C — machine
precision agreement) and by discrete-event queue simulation; naive fixed-point
dynamics (Smith swap, raw projection) were confirmed to CYCLE on this operator
(it is non-monotone — the known instability of departure-time adjustment), and
a projection iterate’s ergodic average converges in aggregates while its
profile is NOT an equilibrium (repo-certified gap 0.48) — exactly why the P1
certifier, not the solver’s claim, is the arbiter.
Alternatives considered¶
A general-network VI-DUE with grid-based fixed-point dynamics: rejected for this sprint — the effective-delay operator is non-monotone and the naive dynamics demonstrably cycle; shipping a solver whose convergence cannot be certified would put the benchmark’s weight on luck. The parallel-bottleneck class has the exact closed form, full existence theory under GVM loading, and machine-checkable certificates; a general-network extension belongs with a route-and-time column-generation sprint of its own.
The 1993 link-delay-model loading: rejected — FIFO-safe only for affine delays, uncapacitated exits, and superseded by the GVM instantiation in the computable-DUE literature; the VI itself is loading-agnostic, so the GVM choice is faithful to the formulation.
Gating the per-route split: rejected — the DUE chooses the split; identical routes make it non-unique. Only total volume is conservation.
Adversarial review¶
Three independent attack lenses were launched; in round 1 two of them
(soundness, formulation) died on an infrastructure session limit and ONLY the
numerics lens completed — so the two dead lenses were re-run as a fresh round
2 against the round-1-fixed code. Full coverage was reached across the two
rounds; every finding below is CONFIRMED by an executed repro and every fix is
regression-pinned in test_due.py.
Round 1 (numerics lens). (a) MAJOR: the certifier extended the served
curve past the horizon with a 65-point interpolated linspace, bending the
queue-clearing kink — the same piecewise-linear plan scored up to 25.6×
differently depending on where its emitted grid ended. Fixed by
reconstructing the point-queue served curve EXACTLY: within each segment the
arrival rate is constant, so the queue either serves at s_r or tracks the
arrival curve, with at most one interior queue-empties kink — computed
analytically and inserted, plus the exact post-horizon clearing chord.
(b) MINOR: the greedy used-set broke at k = 1 when alpha*f_1 was so large
that the queue term rounded away (C = inf); the cheapest route is now
always used. (c) MINOR: N_r = s(C - alpha*f)/delta cancelled
catastrophically at large f (the closed form missed its own conservation
gate); replaced by the difference form
Cq_r = (delta*N + alpha*sum s_i(f_i - f_r))/sum s_i plus exact
renormalization. (d) MINOR: degeneracy floors alpha - beta >= 1e-9*alpha,
gamma <= 1e9*beta added as documented domain bounds. NOTEs: the per-step
monotonicity gate was replaced by a running-max total-retraction gate (the
DTA eps-accumulation family). Survived: greedy vs 2^R brute force on 12k
draws; closed-form exactness ~2.3e-10 across grid densities; cross-certifier
agreement with the adr-019 evaluator.
Round 2 (soundness + formulation lenses, on the fixed code). Both lenses
independently converged on the same CRITICAL: the marginal-insertion
reference scan swept linspace(min(span_lo, t[0]), max(span_hi, t[-1]), 4001) — a fixed COUNT over a hull the solver’s own grid stretches — and the
true cheap-insertion times (the queue-drain dip, where waiting for a residual
queue to vanish is cheap) are kinks that were never candidates. Executed
repros: a truncated-isocost two-route profile in which EVERY traveler pays
exactly 2.0 while deviating into the hidden drain window costs 1.6 (a 20%
improvement) certified due_gap = 0.0 once one flat pad point at t = 1e6
diluted the sweep; a single-ramp profile with true gap 0.765 certified
−0.0001. FIXED by making min_ref EXACT: the marginal-insertion cost is
piecewise linear in the insertion time with enumerable kinks — the profile’s
grid, the pullbacks A^{-1} of every served-curve kink level, the pullback
of level S(t*), and the queue-vanishing zeros of A(t) - S(t + f_r) — all
enumerated per route, with the dense sweep demoted to belt-and-braces over
the FIXED analytic window. The repros now score 0.2027 and 0.7653, invariant
under pads to 1e7. Also confirmed and fixed: MAJOR — scenarios with
N <= tol skipped every route and certified arbitrary conserving garbage at
due_gap = -inf (now censored explicitly); MAJOR — in-domain instances with
(beta+gamma)*ulp(t*) comparable to C score the honest closed form at gap
up to 1.0 because no float64 profile can resolve the equilibrium (98/1100
fuzz draws pre-fix; now rejected at construction by a conditioning gate
(beta+gamma)*eps_mach*(|t*|+window) <= 1e-9*C); MINOR — the level sampling
excluded the first/last traveler where the cost supremum is often attained
(boundary levels eps/n_r - eps now sampled); NOTEs — Tier-B totals now
use a grid-size-independent level count plus the switch/t*-crossing levels.
Survived round 2: greedy vs exact 2^R KKT enumeration on 6000 draws (zero
structural disagreements, near-ties at 1e-13, thresholds at ±1e-15); an
independent event-driven point-queue simulator matching the reconstructed
served curves on 120 profiles to <1e-9; all 15 numeric anchors re-derived
independently; gate-boundary float-dirt probes; the round-1 fixes themselves.
Post-fix, the reviewers’ 2200-draw property fuzz re-run: every accepted draw
certifies its closed form at worst |due_gap| = 8.4e-9 (pre-fix: up to 1.0),
zero false-censors, zero negative gaps, zero crashes.
Consequences¶
The analytical-DTA track is complete: departure-time UE (vickrey), SO-DTA in
both classical formulations (merchant-nemhauser, lp-so-dta), and the
dynamic USER equilibrium (vi-due) — Wardrop’s two principles, dynamic, under
one P1 pattern. All changes are additive; the 652-test suite and the golden
Braess hash are byte-untouched.
Sourcing¶
Friesz, Bernstein, Smith, Tobin & Wie (1993) “A Variational Inequality Formulation of the Dynamic Network User Equilibrium Problem,” Operations Research 41(1):179–191, doi:10.1287/opre.41.1.179 — paywalled, attributed. The VI statement, SRDC-DUE conditions, effective-delay operator, and the link-delay-model caveats are cross-verified from open sources read in full: Han, Friesz & Yao (arXiv:1211.0898 — the GVM instantiation and existence theory; also arXiv:1211.4621), the Friesz–Kwon–Bernstein handbook chapter (nested exit-time composition, LDM history), and Peeta & Ziliaskopoulos (2001) §2 (the 1993 attribution of the DUE conditions). The parallel-route closed form is standard bottleneck-theory material (Arnott–de Palma–Lindsey lineage, attributed), derived from scratch here from the isocost conditions and machine-verified by LP-free bisection + discrete-event simulation; it is deliberately NOT attributed to the 1993 paper, which is a formulation/existence paper with no closed form. No page-precise quotes reproduced.