tabench.estimation

The T2 OD-estimation track: the static estimators (entropy, GLS, Spiess, SPSA, Yang 1992, DN-Kalman) and the within-day dynamic estimators (Cascetta 1993), with their registries. The spsa-sumo guarded estimator registers only under the sumo extra.

T2 estimation: recover OD demand from link counts (ADR-002).

A demand-free contract (ODEstimator over EstimationTask) parallel to the T1 model contract, the shipped classical estimators, and the shared MSA-averaged proportion extraction they score against. The harness certifies every emitted OD matrix through a pinned reference assignment (tabench.metrics.estimation).

class tabench.estimation.CallableEstimator(fn, name='callable_estimator', paradigm='learned', deterministic=False, seedable=True, trained_on=())[source]

Bases: ODEstimator

Adapter turning fn(task, rng) -> od_matrix into a benchmark estimator.

capabilities are instance-level (they describe the wrapped artifact): pass trained_on lineage for learned inverse models so the fairness gate can act on it, exactly as CallableModel does for T1.

Parameters:
  • fn (ODFn)

  • name (ClassVar[str])

  • paradigm (str)

  • deterministic (bool)

  • seedable (bool)

  • trained_on (tuple[str, ...])

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

class tabench.estimation.EstimationTask(name, network, prior, dataset, identifiability, scenario_hash, certificate=<factory>, seed=0, heldout_digest='')[source]

Bases: object

Everything a T2 estimator may see. Contains NO true demand, by design.

network carries its declared cost functions, so white-box estimators may run their own inner assignments (their inner solver and gap are declared factors, paid from their own budget); prior is the stale seed/target matrix; dataset is the LinkCounts payload; and identifiability is the public per-task report (Decision 4). The true scenario.demand is simply absent from this object — there is nothing to peek at.

Parameters:
  • name (str)

  • network (Network)

  • prior (Demand)

  • dataset (Dataset)

  • identifiability (Mapping[str, Any])

  • scenario_hash (str)

  • certificate (Mapping[str, Any])

  • seed (int)

  • heldout_digest (str)

content_hash()[source]

SHA-256 instance pin over scenario hash + prior bytes + sensor links + observed counts + dataset dials + certificate pin + held-out digest + seed.

The sensor-link and count bytes make this a true instance pin: two explicit sensor placements of equal coverage, or two macroreps of a cv=0 task, no longer collide. heldout_digest (a SHA-256 the runner computes over the sorted held-out links + held-out n_periods) folds the held-out design in without leaking held-out sensor identities to the estimator (P7).

Return type:

str

class tabench.estimation.ODEstimator(**factor_overrides)[source]

Bases: ABC

Base class every T2 estimator or wrapper implements.

Parameters:

factor_overrides (Any)

abstractmethod estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

class tabench.estimation.ODResultBundle(estimator_name, final, trace, factors=<factory>, seed_info=<factory>)[source]

Bases: object

Everything one estimate call produced, with provenance.

Parameters:
  • estimator_name (str)

  • final (ODState)

  • trace (ODTrace)

  • factors (dict[str, Any])

  • seed_info (dict[str, Any])

class tabench.estimation.ODState(od_matrix, coords, self_report=<factory>)[source]

Bases: object

One emitted OD estimate, mirroring FlowState.

Parameters:
  • od_matrix (ndarray)

  • coords (Any)

  • self_report (dict[str, float])

class tabench.estimation.ODTrace[source]

Bases: object

Ordered stream of OD checkpoints emitted during one estimate call.

record(od_matrix, coords, **self_report)[source]

Record a checkpoint. The OD matrix is copied defensively.

Parameters:
  • od_matrix (ndarray)

  • coords (Any)

  • self_report (float)

Return type:

None

class tabench.estimation.PriorBaseline(**factor_overrides)[source]

Bases: ODEstimator

Do-nothing anchor: emit the prior unchanged (BO4Mob Improvement%).

Every T2 leaderboard needs the baseline every improvement is measured against; a prior that is already the truth is unbeatable, one that is far off certifies a terrible (honest) count-fit.

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

tabench.estimation.register_estimator(cls)[source]

Class decorator adding an estimator to the name registry.

Mirrors register_model: only self-contained estimators belong here (the CLI instantiates them with no arguments), so a registered class must declare class-level name and capabilities. Adapter-style estimators with per-instance capabilities (CallableEstimator) are passed to the runner directly and must not be registered.

Parameters:

cls (type[ODEstimator])

Return type:

type[ODEstimator]

tabench.estimation.active_pairs(demand_matrix)[source]

Ordered 0-based (origin, destination) pairs with positive off-diagonal demand.

Parameters:

demand_matrix (ndarray)

Return type:

list[tuple[int, int]]

tabench.estimation.od_from_pairs(prior_matrix, pairs, g)[source]

Scatter a per-pair demand vector into a full OD matrix.

Off-diagonal cells outside pairs stay zero (support is fixed by the prior); the diagonal (intrazonal demand, which never enters the network) is carried over from prior_matrix unchanged.

Parameters:
  • prior_matrix (ndarray)

  • pairs (list[tuple[int, int]])

  • g (ndarray)

Return type:

ndarray

tabench.estimation.proportion_matrix(network, demand, k_inner, pairs=None, engine=None)[source]

Extract (P, pairs, msa_flows) at the equilibrium of demand.

P has shape (n_links, len(pairs)); msa_flows = P @ g where g is the per-pair demand vector, so the identity v = P g is exact. k_inner >= 1 is the number of MSA/AON sweeps (one shortest-path call each). Pairs default to the positive off-diagonal support of demand.

Parameters:
  • network (Network)

  • demand (Demand)

  • k_inner (int)

  • pairs (list[tuple[int, int]] | None)

  • engine (PathEngine | None)

Return type:

tuple[ndarray, list[tuple[int, int]], ndarray]

class tabench.estimation.VZWEntropyEstimator(**factor_overrides)[source]

Bases: ODEstimator

Maximum-entropy OD estimation by cyclic count-balancing (Van Zuylen & Willumsen 1980).

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

tabench.estimation.vzw_balance(prior, p_obs, counts, n_passes, tol=1e-09)[source]

Cyclic multiplicative count-balancing of prior toward counts.

p_obs is the (n_obs, n_pairs) proportion block on the observed sensors; counts the per-period-mean count per sensor. Returns (g, trajectory, counts_consistent) where g is the converged iterate after n_passes damped balancing passes and counts_consistent flags whether its observed-count residual has settled below tol (mutually inconsistent counts converge to a compromise that stays above tolerance). trajectory records g after every single-link update.

Parameters:
  • prior (ndarray)

  • p_obs (ndarray)

  • counts (ndarray)

  • n_passes (int)

  • tol (float)

Return type:

tuple[ndarray, list[ndarray], bool]

class tabench.estimation.GLSEstimator(**factor_overrides)[source]

Bases: ODEstimator

Generalized-least-squares OD estimation (Cascetta 1984).

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

tabench.estimation.gls_solve(p_obs, counts, prior, w_var, v_var)[source]

Nonnegative GLS estimate on the whitened stacked system.

w_var / v_var are the prior and count variances (the diagonals of W and V). Returns the minimizing g >= 0. For a single pair and a single sensor with W = V = 1 this is the closed form (g_pr + p*cbar) / (1 + p^2) (Decision-6 anchor 4).

Parameters:
  • p_obs (ndarray)

  • counts (ndarray)

  • prior (ndarray)

  • w_var (ndarray)

  • v_var (ndarray)

Return type:

ndarray

class tabench.estimation.SpiessEstimator(**factor_overrides)[source]

Bases: ODEstimator

Gradient OD adjustment on the count misfit (Spiess 1990).

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

tabench.estimation.spiess_step(p_obs, g, counts, damp=1.0)[source]

One damped Spiess descent step at fixed proportions p_obs.

Returns max(g * (1 - damp * lambda* * grad), 0) with the linearized optimum lambda* capped at 1 / max grad for feasibility. If no descent direction exists (degenerate w) or the optimal step is nonpositive, g is returned unchanged. The Armijo safeguard is applied retrospectively in the outer loop (SpiessEstimator), where Z is re-assessed under a fresh assignment — the frozen-proportion Z this step minimizes is convex in lambda and would never trigger a halving (ADR-002 Decision 3.3).

Parameters:
  • p_obs (ndarray)

  • g (ndarray)

  • counts (ndarray)

  • damp (float)

Return type:

ndarray

class tabench.estimation.SPSAEstimator(**factor_overrides)[source]

Bases: ODEstimator

Black-box OD calibration by simultaneous perturbation (Spall 1992).

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

class tabench.estimation.Yang1992Estimator(**factor_overrides)[source]

Bases: ODEstimator

Bilevel OD estimation on congested networks (Yang et al. 1992).

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

tabench.estimation.yang_solve(p_obs, counts, prior, theta)[source]

Nonnegative minimizer of Yang’s theta-weighted upper-level QP at fixed P.

argmin_{g>=0} theta * ||g - g_pr||^2 + (1 - theta) * ||P_obs g - cbar||^2, solved as a whitened stacked nonnegative least squares with weight sqrt(theta) on the prior block and sqrt(1 - theta) on the count block (scipy.optimize.lsq_linear, bounded VLS). This is the scalar-theta case of the gls whitened system: the single deterministic trade-off in place of the statistical covariances W, V. Strictly convex for any theta in (0, 1) (the prior block alone has full column rank), so the minimizer is unique for any sensor set. For a single pair and single sensor with proportion p this is the closed form (theta*g_pr + (1-theta)*p*cbar) / (theta + (1-theta)*p^2).

Parameters:
  • p_obs (ndarray)

  • counts (ndarray)

  • prior (ndarray)

  • theta (float)

Return type:

ndarray

class tabench.estimation.DavisNihanKalmanEstimator(**factor_overrides)[source]

Bases: ODEstimator

Davis & Nihan (1993) linear-Gaussian OD estimation from a count series.

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate an OD matrix, emitting checkpoints to trace.

Implementations must respect budget (expressed in assignment-equivalent sp_calls) and record at least one checkpoint. Certification runs a full pinned assignment per checkpoint, so emit O(10-20) checkpoints – sparsely spaced (e.g. every max(1, iters // 15) iterations) plus always the final iterate – not one per iteration (ADR-002 Decision 2). Self-reported metrics go into checkpoint self_report entries; they are provenance, never scores.

Parameters:
Return type:

ODResultBundle

tabench.estimation.ar1_tau(counts)[source]

AR(1) integrated-autocorrelation time of a count series (T x S).

Estimates the pooled lag-1 autocorrelation as the trace ratio sum_{t,s} z_{t,s} z_{t+1,s} / sum_{t,s} z_{t,s}^2 of the demeaned series (z = counts - counts.mean(0)) and returns (tau, rho_hat) with tau = (1 + rho_hat) / (1 - rho_hat) – the variance-inflation factor of the time-mean of an AR(1). Summing each sensor’s OWN autocovariance (rather than pooling sensors into a scalar mean) is robust to cross-link anti-correlation: for a VAR(1) with Phi = rho I and innovation covariance Q the numerator is rho * tr(Q) and the denominator tr(Q), so rho_hat -> rho even when the monitored links are on competing routes whose fluctuations cancel in the mean (a mean-pooled estimate collapses to tau ~ 1 there, silently discarding the temporal correction). rho_hat is clipped to [0, 0.98] (negative / anti-persistent -> no inflation, tau = 1; the ceiling keeps tau finite); tau = 1 for an IID series.

Parameters:

counts (ndarray)

Return type:

tuple[float, float]

tabench.estimation.dn_gls_solve(p_obs, counts_mean, prior, sigma_mean, w_var)[source]

Nonnegative GLS whitened by the FULL Davis-Nihan count-mean covariance.

Solves:

argmin_{g>=0} (P g - cbar)^T Sigma_mean^{-1} (P g - cbar)
              + (g - g_pr)^T W^{-1} (g - g_pr)

as a bounded least squares on the whitened stacked system [W^-1/2 ; Sigma_mean^-1/2 P] g ~ [W^-1/2 g_pr ; Sigma_mean^-1/2 cbar]. sigma_mean is the (S, S) covariance of the count time-mean (Sigma_stat * tau / T); its off-diagonals (cross-link DN structure) and the tau inflation are what distinguish this from gls (diagonal V, tau = 1). Because W^-1 is strictly positive definite the problem is strictly convex for any sensor set. For a single pair and single sensor with scalar Sigma_mean = s^2 and W = w^2 this is the closed form (g_pr / w^2 + p * cbar / s^2) / (1 / w^2 + p^2 / s^2).

Parameters:
  • p_obs (ndarray)

  • counts_mean (ndarray)

  • prior (ndarray)

  • sigma_mean (ndarray)

  • w_var (ndarray)

Return type:

ndarray

class tabench.estimation.DynamicEstimationTask(name, network, prior_profile, dataset, identifiability, scenario_hash, certificate=<factory>, seed=0, heldout_digest='')[source]

Bases: object

Everything a dynamic estimator may see. Contains NO true profile, by design.

prior_profile is the stale (H, Z, Z) seed (one prior OD matrix per departure slice). dataset is the DynamicLinkCounts payload: counts (n_days, T, S), the sensor_links, the sensor-restricted exogenous lag_tensor (L+1, S, P), and the active pairs (the profile column order). The map is exogenous and hashed; the certifier regenerates the full-network map from the same recipe and never trusts this payload’s tensor.

Parameters:
  • name (str)

  • network (Network)

  • prior_profile (ndarray)

  • dataset (Dataset)

  • identifiability (Mapping[str, Any])

  • scenario_hash (str)

  • certificate (Mapping[str, Any])

  • seed (int)

  • heldout_digest (str)

content_hash()[source]

SHA-256 instance pin (domain-prefixed tabench-t2d-task-v2;).

Covers the scenario hash, the slicing dials (H, T, slice length, map recipe id) from dataset.meta, the prior-profile bytes, the active pairs (the estimand’s cell layout — two tasks whose estimands live in different OD cells must never hash equal, even hand-built ones; adr-023 review), the sensor links, the observed-count bytes, the exogenous lag-tensor bytes, the certificate pin, the held-out digest, and the seed. The lag-tensor and per-slice count bytes make two macroreps of a cv=0 / noise='none' task, or two slicings of one scenario, distinct instances.

Return type:

str

class tabench.estimation.DynamicODEstimator(**factor_overrides)[source]

Bases: ABC

Base class every within-day dynamic OD estimator implements.

Mirrors ODEstimator piece for piece over a DynamicEstimationTask; the emitted artifact is a full (H, Z, Z) profile at every checkpoint, recorded through the shared ODTrace.

Parameters:

factor_overrides (Any)

abstractmethod estimate(task, budget, rng, trace)[source]

Estimate the (H, Z, Z) profile, recording it to trace.

The exogenous map is given, so there is no inner assignment: both shipped estimators are single-shot solves with sp_calls = 0. Emit the full profile at every checkpoint so certification is always well-defined.

Parameters:
Return type:

ODResultBundle

class tabench.estimation.DynamicPriorBaseline(**factor_overrides)[source]

Bases: DynamicODEstimator

Do-nothing anchor: emit the prior profile unchanged (BO4Mob Improvement%).

Every dynamic leaderboard needs the baseline every improvement is measured against; a prior profile that is already the truth is unbeatable, one that is far off certifies a terrible (honest) per-interval count-fit.

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate the (H, Z, Z) profile, recording it to trace.

The exogenous map is given, so there is no inner assignment: both shipped estimators are single-shot solves with sp_calls = 0. Emit the full profile at every checkpoint so certification is always well-defined.

Parameters:
Return type:

ODResultBundle

tabench.estimation.register_dynamic_estimator(cls)[source]

Class decorator adding a dynamic estimator to its own name registry.

Deliberately separate from ESTIMATOR_REGISTRY: a static estimator would mis-read the (H, Z, Z) task and a dynamic one would mis-read a static (Z, Z) task, so the registries are the type gate (ADR-002 Decision 1 rationale, ADR-023).

Parameters:

cls (type[DynamicODEstimator])

Return type:

type[DynamicODEstimator]

class tabench.estimation.Bo4MobEstimationTask(name, instance_key, pairs, prior_vector, dataset, identifiability, engine, certificate=<factory>, seed=0, heldout_digest='')[source]

Bases: object

Everything a BO4Mob T2 estimator may see. Contains NO held-out data, by design.

pairs is the fixed, ordered active (fromTaz, toTaz) OD-cell layout (from the instance’s od.xml template); the estimand is a 1-D OD vector over it. prior_vector is the stale seed (BO4Mob’s od_for_single_run example OD). dataset is the TRAIN-only sensor payload: the anchor (date, hour) PeMS counts the estimator may fit — payload['link_ids'] and payload['counts'] (P7: only the TRAIN anchor; the held-out panel lives solely inside the certifier and is folded in through heldout_digest, never constructed into this object). identifiability is a light, provenance-only diagnostic (sensor-vs-active-pair coverage — BO4Mob has no declared assignment for Hazelton’s rank test, so it never gates; adr-041). engine pins the exact eclipse-sumo version the certificate re-runs.

Parameters:
  • name (str)

  • instance_key (str)

  • pairs (tuple[tuple[str, str], ...])

  • prior_vector (ndarray)

  • dataset (Dataset)

  • identifiability (Mapping[str, Any])

  • engine (Mapping[str, Any])

  • certificate (Mapping[str, Any])

  • seed (int)

  • heldout_digest (str)

content_hash()[source]

SHA-256 instance pin (domain-prefixed tabench-t2-bo4mob-task-v1;).

Covers the instance key, the active pair LAYOUT (two tasks whose estimands live in different OD cells must never hash equal — the adr-023 lesson carried to BO4Mob’s string-keyed pairs), the prior-vector bytes, the TRAIN link ids + count bytes, the TRAIN metadata dials, the exact engine pin, the certificate (engine flags + sim/OD window), the held-out digest (the ONLY held-out design that enters task identity, P7), and the seed.

Return type:

str

class tabench.estimation.Bo4MobODEstimator(**factor_overrides)[source]

Bases: ABC

Base class every BO4Mob OD estimator implements.

Mirrors ODEstimator piece for piece over a Bo4MobEstimationTask; the emitted artifact is a 1-D OD vector over task.pairs at every checkpoint, recorded through the shared ODTrace.

Parameters:

factor_overrides (Any)

abstractmethod estimate(task, budget, rng, trace)[source]

Estimate the OD vector over task.pairs, recording it to trace.

A black-box estimator (a future SPSA over the pipeline) runs the engine as its own inner oracle, paid from its own budget; the do-nothing prior baseline is a single-shot emit. Record at least one checkpoint (the final emitted vector) so certification is always well-defined.

Parameters:
Return type:

ODResultBundle

class tabench.estimation.Bo4MobPriorBaseline(**factor_overrides)[source]

Bases: Bo4MobODEstimator

Do-nothing anchor: emit the prior OD vector unchanged (BO4Mob Improvement%).

Every BO4Mob leaderboard needs the baseline every improvement is measured against; a prior already fitting the held-out days is unbeatable, one far off certifies a terrible (honest) held-out count-fit. Registers unconditionally and imports no sumo — the engine is the certifier’s job, not the estimator’s (unlike the simulator-in-the-loop spsa-sumo guard; adr-041).

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate the OD vector over task.pairs, recording it to trace.

A black-box estimator (a future SPSA over the pipeline) runs the engine as its own inner oracle, paid from its own budget; the do-nothing prior baseline is a single-shot emit. Record at least one checkpoint (the final emitted vector) so certification is always well-defined.

Parameters:
Return type:

ODResultBundle

tabench.estimation.register_bo4mob_estimator(cls)[source]

Class decorator adding a BO4Mob estimator to its own name registry.

Deliberately separate from ESTIMATOR_REGISTRY / DYNAMIC_ESTIMATOR_REGISTRY: a static or dynamic estimator would mis-read a BO4Mob task (1-D vector over string pairs, no (Z, Z) matrix) and vice versa, so the registries are the type gate (ADR-002 Decision 1 rationale, ADR-023, adr-041).

Parameters:

cls (type[Bo4MobODEstimator])

Return type:

type[Bo4MobODEstimator]

class tabench.estimation.SimultaneousDynamicGLSEstimator(**factor_overrides)[source]

Bases: DynamicODEstimator

Simultaneous dynamic GLS (Cascetta et al. 1993): all slices jointly.

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate the (H, Z, Z) profile, recording it to trace.

The exogenous map is given, so there is no inner assignment: both shipped estimators are single-shot solves with sp_calls = 0. Emit the full profile at every checkpoint so certification is always well-defined.

Parameters:
Return type:

ODResultBundle

class tabench.estimation.SequentialDynamicGLSEstimator(**factor_overrides)[source]

Bases: DynamicODEstimator

Sequential dynamic GLS (Cascetta et al. 1993): slice by slice, frozen carryover.

Parameters:

factor_overrides (Any)

estimate(task, budget, rng, trace)[source]

Estimate the (H, Z, Z) profile, recording it to trace.

The exogenous map is given, so there is no inner assignment: both shipped estimators are single-shot solves with sp_calls = 0. Emit the full profile at every checkpoint so certification is always well-defined.

Parameters:
Return type:

ODResultBundle

tabench.estimation.dynamic_gls_simultaneous(a_stacked, counts, prior, w_prior, v_count)[source]

One whitened nonnegative GLS on the full stacked system (all slices jointly).

argmin_{x>=0} (x - z)^T W^-1 (x - z) + (A x - c)^T V^-1 (A x - c) on the stacked unknown x (H*P), solved as bounded least squares on the whitened system [W^-1/2 ; V^-1/2 A] x ~ [W^-1/2 z ; V^-1/2 c]. a_stacked is (T*S, H*P); counts / v_count are (T*S,) (per interval-sensor count and its variance); prior / w_prior are (H*P,) (per slice-pair prior and its variance). Because W^-1 is positive definite the problem is strictly convex for any sensor set. Returns the flat (H*P,) estimate.

Parameters:
  • a_stacked (ndarray)

  • counts (ndarray)

  • prior (ndarray)

  • w_prior (ndarray)

  • v_count (ndarray)

Return type:

ndarray

tabench.estimation.dynamic_gls_sequential(blocks, counts_mean, prior_profile, w_prior, v_count, n_intervals)[source]

Slice-by-slice GLS: each slice from its earliest observed interval, frozen.

For h = 0..H-1 in order, find the earliest interval t_h >= h with a nonzero block for slice h (the slice’s first crossing of a monitored link); subtract the frozen contributions of already-estimated earlier slices from c_{t_h}; solve the slice-local nonnegative GLS for d_h against that block. The two information losses that make the sequential estimator provably less efficient than the simultaneous one: (i) the slice’s own LATER crossings (intervals > t_h) are discarded, and (ii) earlier-slice estimates are frozen with no covariance propagation. (Later-slice contamination at t_h cannot occur on a time-invariant tensor — a later slice’s earliest nonzero lag is the same, so its first crossing is strictly after t_h; on a general time-varying block map any such contribution is treated as zero, a documented bias of the plug-in scheme.) A slice never observed within the horizon keeps its prior. blocks[t] = {h: (S,P)}; counts_mean / v_count are (T, S); prior_profile / w_prior are (H, P). Returns the (H, P) estimate.

Parameters:
  • blocks (list[dict[int, ndarray]])

  • counts_mean (ndarray)

  • prior_profile (ndarray)

  • w_prior (ndarray)

  • v_count (ndarray)

  • n_intervals (int)

Return type:

ndarray

tabench.estimation.lagged_assignment_tensor(network, pairs, slice_length, n_lags=None, engine=None)[source]

Frozen free-flow two-interval-split lag tensor M ((L+1, n_links, P)).

M[l, a, k] is the fraction of pair-pairs[k] demand crossing link a exactly l slices after departure, under free-flow path choice and uniform within-slice departures. slice_length is Delta (native time units). L is the largest lag any path induces; pass n_lags to fix a larger horizon (padded with zeros) — a smaller one raises, since dropping a contribution would break the column-mass identity. Deterministic and RNG-free.

Parameters:
  • network (Network)

  • pairs (list[tuple[int, int]])

  • slice_length (float)

  • n_lags (int | None)

  • engine (PathEngine | None)

Return type:

ndarray

tabench.estimation.predict_interval_counts(m, profile, n_intervals)[source]

Expected crossing counts c_t = sum_l M[l] @ d_{t-l} (shape (T, R)).

m is (L+1, R, P) (R = n_links or a sensor subset); profile is the (H, P) per-slice per-pair demand. Slices outside 0..H-1 contribute nothing (causality and horizon truncation), so counts are exact linear algebra.

Parameters:
  • m (ndarray)

  • profile (ndarray)

  • n_intervals (int)

Return type:

ndarray

tabench.estimation.stacked_tensor_map(m, n_slices, n_intervals)[source]

Stacked observation map A ((T*R, H*P)) from a time-invariant tensor.

Block (t, h) is M[t-h] on the causal band 0 <= t-h <= L and zero elsewhere, so A is block-lower-banded. Row-major over (t, sensor) and (h, pair) — the layout the whitened simultaneous GLS and the identifiability rank test both use.

Parameters:
  • m (ndarray)

  • n_slices (int)

  • n_intervals (int)

Return type:

ndarray

tabench.estimation.tensor_blocks(m, n_slices, n_intervals)[source]

Time-invariant tensor -> per-interval block map blocks[t] = {h: M[t-h]}.

The general (possibly time-varying) representation the GLS solvers consume: blocks[t][h] is the (R, P) block mapping slice h demand to interval t counts. For the frozen tensor it is simply M[t-h] on the causal band.

Parameters:
  • m (ndarray)

  • n_slices (int)

  • n_intervals (int)

Return type:

list[dict[int, ndarray]]