bo4mob-estimation — BO4Mob held-out-count OD estimation (D2 observational)

What. The T2 estimation track’s first observational certificate (adr-041). BO4Mob (Ryu, Kwon, Choi, Deshwal, Kang & Osorio 2025, arXiv:2510.18824) poses a San Jose freeway network as a black-box OD-estimation problem: choose a continuous OD vector over a fixed set of active (fromTaz, toTaz) pairs to fit real Caltrans PeMS link counts under a mesoscopic SUMO run. There is no declared BPR network, no true OD, and no equilibrium — so this is a NEW T2 sibling family (the adr-023 pattern), scored by re-running the pinned eclipse-sumo engine, not a bfw assignment.

Dual-benchmark honesty (the central constraint). BO4Mob is a UMN Choi Lab benchmark; TABenchmark hosts its instances as scenarios/data only (adr-034), not as validation of TABench methods, and does not reproduce BO4Mob’s published numbers. This estimation row additionally does not reproduce BO4Mob’s own SPSA/BO leaderboard rankings, and its D2 held-out NRMSE is not comparable to the static/dynamic T2 heldout_count_rmse scale (it is BO4Mob’s own n-scaled NRMSE, a same-lab scenario).

How this notebook is graded

A notebook never claims a number it does not compute in that cell. Every scored quantity below — od_feasible, obs_nrmse, heldout_nrmse — comes from the SAME Bo4MobODCertifier the harness uses: it re-runs the pinned od2trips + mesoscopic sumo pipeline ONCE on the emitted OD and compares the resulting link counts to real PeMS panels. Equilibrium is never claimed.

The ranking column is heldout_nrmse — the MEAN of BO4Mob’s count NRMSE over a temporally-disjoint, same-hour set of held-out DATES (the anchor hour 06-07, dates 221009221021). The estimator emits ONE fixed OD from the TRAIN anchor (221008); the certifier scores it against each held-out date’s real counts. The held-out CSV bytes and dates never enter the task — only their heldout_digest does (P7). The engine version is pinned (assert_engine_pin RAISES on a mismatch), so a drifted box fails loudly rather than silently scoring.

This notebook needs the optional sumo extra (pip install 'tabench[sumo]').

# Headless inline backend (Agg): figures render into the notebook so CI can execute
# tutorials without a display. Never call matplotlib.use() after pyplot is imported.
import matplotlib
matplotlib.use("Agg")
import json
import matplotlib.pyplot as plt
import numpy as np

from tabench.core.budget import Budget
from tabench.data.bo4mob import (
    BO4MOB_HELDOUT_DATES, BO4MOB_HELDOUT_HOUR, BO4MOB_REGISTRY,
    bo4mob_pairs, bo4mob_prior_vector, fetch_bo4mob, fetch_bo4mob_heldout,
)
from tabench.estimation.bo4mob_base import Bo4MobPriorBaseline
from tabench.experiments.runner import run_bo4mob_estimation_experiment
from tabench.metrics.estimation_bo4mob import Bo4MobODCertifier

INSTANCE = "1ramp"  # the smallest instance: ~0.4 s meso, the CI liveness case
spec = BO4MOB_REGISTRY[INSTANCE]
print(f"{INSTANCE}: {spec.n_od} OD pairs, {spec.n_sensors_anchor} anchor sensors")
1ramp: 3 OD pairs, 3 anchor sensors

The held-out split (framing b)

The estimator emits one fixed OD vector; “holding out a date” only withholds which ground-truth CSVs are used for scoring, never which OD to submit. This closes a free-riding hole that per-date OD emission would open (copy the nearest train date’s OD) and costs one meso run per certify regardless of how many held-out dates are scored. PeMS counts vary enormously across the day, so the hour window is held FIXED — held-out probes same-hour, different-DATE generalisation only.

# Run the prior baseline (emit BO4Mob's example OD unchanged) and certify it.
result = run_bo4mob_estimation_experiment(
    INSTANCE, [Bo4MobPriorBaseline()], Budget(sp_calls=1), seed=0,
)
row = result.rows[0]
print(f"estimator      : {row['estimator']}")
print(f"od_feasible    : {row['od_feasible']}")
print(f"obs_nrmse      : {row['obs_nrmse']:.6f}   (in-sample, TRAIN anchor 221008)")
print(f"heldout_nrmse  : {row['heldout_nrmse']:.6f}   <- ranking column (mean over "
      f"{int(row['n_heldout_dates'])} held-out dates)")
print(f"  min / max    : {row['heldout_nrmse_min']:.6f} / {row['heldout_nrmse_max']:.6f}")
# The prior OD is BO4Mob's low-demand anchor day, so its held-out MEAN (over higher-
# demand days) differs from its in-sample fit -- an honest generalisation signal.
estimator      : bo4mob-prior
od_feasible    : 1.0
obs_nrmse      : 2.432471   (in-sample, TRAIN anchor 221008)
heldout_nrmse  : 1.697988   <- ranking column (mean over 13 held-out dates)
  min / max    : 0.563911 / 3.297711

The anti-laundering control: the OD-window fill

The one real laundering vector, found and fixed once in stage 1 (adr-034 Decision 3), is the OD-window fill: the emitted vector must fill od.xml with the interval end rewritten to the instance’s od_end_time. On 1ramp (od_end_time=3300) keeping the template’s 3600 releases ~5% of demand past the OD window and silently biases the count NRMSE. fill_od_from_vector inherits this rewrite, so the prior baseline’s certified obs_nrmse reproduces the stage-1 faithful pipeline exactly (2.432471…); the unfixed value would be 2.314704….

# The per-held-out-date NRMSE (one pipeline run, K cheap comparisons). This is the
# spread the ranking `heldout_nrmse` averages over -- never a black box.
paths = fetch_bo4mob(spec)
heldout = fetch_bo4mob_heldout(INSTANCE)
cfg = json.loads(paths["config"].read_text())
pairs = bo4mob_pairs(paths["od"])
prior = bo4mob_prior_vector(paths["od"], paths["single_od"])
cert = Bo4MobODCertifier(
    instance_key=INSTANCE, pairs=pairs, train_sensor=paths["sensor"], heldout_sensors=heldout,
    paths=paths, od_end_time=int(cfg["od_end_time"]), sim_end_time=float(cfg["sim_end_time"]),
    sensor_start_time=float(cfg["sensor_start_time"]), sensor_end_time=float(cfg["sensor_end_time"]),
    engine_version="1.27.1", certificate={"seed": 0},
)
per_date = cert.per_date_heldout_nrmse(prior)
dates = list(per_date)
vals = [per_date[d] for d in dates]
fig, ax = plt.subplots(figsize=(8, 3))
ax.bar(range(len(dates)), vals, color="#4477aa")
ax.axhline(np.mean(vals), color="#cc3311", ls="--", label=f"heldout_nrmse = {np.mean(vals):.3f}")
ax.set_xticks(range(len(dates)))
ax.set_xticklabels([d[-4:] for d in dates], rotation=45, ha="right")
ax.set_ylabel("count NRMSE")
ax.set_title(f"{INSTANCE}: prior-OD NRMSE per held-out date (hour {BO4MOB_HELDOUT_HOUR})")
ax.legend()
plt.tight_layout()
print("held-out dates:", ", ".join(BO4MOB_HELDOUT_DATES))
print(f"mean held-out NRMSE = {np.mean(vals):.6f}")
held-out dates: 221009, 221010, 221011, 221012, 221013, 221014, 221015, 221016, 221017, 221018, 221019, 221020, 221021
mean held-out NRMSE = 1.697988

Reading the row

heldout_nrmse ranks the family: a prior OD already fitting the held-out days is unbeatable; one far off certifies a terrible (honest) held-out count-fit. A future black-box estimator (an SPSA over this pipeline) would run the engine as its own inner oracle and try to lower heldout_nrmse — improving out-of-sample count fit, never memorising a single day.

Honesty, restated. BO4Mob is a UMN Choi Lab benchmark; TABenchmark hosts its instances as scenarios/data only (adr-034), not as validation of TABench methods, and does not reproduce BO4Mob’s published numbers — including BO4Mob’s own SPSA/BO leaderboard rankings. Equilibrium is never claimed, and this D2 held-out NRMSE is not comparable to the static/dynamic T2 heldout_count_rmse scale.