Simulation¶
Data-generating process¶
Data generating process for microPURC simulation experiments.
One trip is produced in two steps: the forward QP is solved at
\(\beta_{\mathrm{true}}\) for a sampled OD pair, yielding the optimal link
flow \(x^{\star}\), and a route is then drawn from \(x^{\star}\) by the
Markovian flow decomposition of
MarkovSampler.
Every candidate OD passes the same acceptance filters the estimator applies, so a generated dataset contains no trip the estimator would later drop.
- class micropurc.simulation.dgp.DGPConfig(max_tries_per_trip=20, require_multiple_paths=True, batch_sampling=True, sampler_threads=-1)[source]¶
Bases:
objectConfiguration for the data generating process.
- Variables:
max_tries_per_trip (int) – OD draws allowed per requested trip; the total attempt budget is
max_tries_per_trip * n_trips. It also caps how often the batch path re-seeds a walk that failed to complete.require_multiple_paths (bool) – Accept an OD only when its active subgraph carries at least two simple origin-destination paths, so every observation records a genuine route choice.
batch_sampling (bool) – Draw the route walks with the multi-threaded native batch sampler. False selects the serial per-trip walk, which reproduces datasets generated before the batch sampler existed bit for bit.
sampler_threads (int) – Worker threads for the batch sampler; -1 uses all hardware threads.
- Parameters:
- class micropurc.simulation.dgp.DGP(network, beta_true, forward_solver, route_sampler, od_dist, rng=None, config=None, active_set_config=None)[source]¶
Bases:
objectData generating process for microPURC simulation.
Samples datasets whose observations satisfy the acceptance filters the estimator applies to real data: a dual-slack active set with more than one link, an acyclic active subgraph, and a grounding with at least one retained node row. Two further filters are specific to generation and controlled by
DGPConfig: the multi-path requirement and the unit origin outflow that guarantees the route walk terminates.- Parameters:
network (Network)
beta_true (np.ndarray)
forward_solver (ForwardSolver)
route_sampler (MarkovSampler)
od_dist (ODDistribution)
rng (np.random.Generator | None)
config (DGPConfig | None)
active_set_config (ActiveSetConfig | None)
- __init__(network, beta_true, forward_solver, route_sampler, od_dist, rng=None, config=None, active_set_config=None)[source]¶
Initializes the DGP.
- Parameters:
network (Network) – Network the trips are generated on.
beta_true (ndarray) – True parameter vector \(\beta_{\mathrm{true}}\), shape (K,); link costs are \(c = Z \beta_{\mathrm{true}}\).
forward_solver (ForwardSolver) – Solver for the per-OD forward QP.
route_sampler (MarkovSampler) – Sampler that decomposes a link flow into a route.
od_dist (ODDistribution) – Distribution the candidate OD pairs are drawn from.
rng (Generator | None) – Generator for the OD draws, the route walks, and the batch sampler seeds; defaults to a fresh unseeded generator.
config (DGPConfig | None) – Sampling and acceptance settings.
active_set_config (ActiveSetConfig | None) – Dual-slack tolerance for the active-set test.
- Raises:
ValueError – If
beta_truelength does not match the attribute count K.- Return type:
None
- sample_dataset(n_trips)[source]¶
Samples a dataset of
n_tripsaccepted observations.DGPConfig.batch_samplingselects between the multi-threaded native batch sampler and the serial per-trip walk. Both draw ODs fromod_distand apply the same acceptance filters, so they agree in distribution. ARuntimeErroris raised when the attempt budgetmax_tries_per_trip * n_tripsruns out beforen_tripstrips have been accepted.- Parameters:
n_trips (int) – Number of accepted trips to generate.
- Returns:
y (N,L), Z (L,K), b (N,V), origins (N,), destinations (N,).
- Return type:
Dict with keys
- sample_clustered_dataset(n_travelers, trips_per_traveler, rho, rng=None)[source]¶
Samples a clustered dataset with repeated trips per traveler.
Tastes are homogeneous: every trip uses \(\beta_{\mathrm{true}}\) and the choice model is held fixed throughout. Each of the \(H\) travelers is a commuter with a single fixed OD drawn i.i.d. from
od_dist, so several travelers may share an OD and the traveler clusters are nested within OD clusters. For traveler \(h\):draw a habitual route \(R_h\) once from the \(\beta_{\mathrm{true}}\) flow of its OD;
generate \(T_h\) trips, where trip \(i\) repeats \(R_h\) with probability \(\rho_h\) and otherwise takes a fresh independent draw from the same flow.
Write \(P\) for the route law induced by the \(\beta_{\mathrm{true}}\) flow. Since \(R_h \sim P\) and each fresh draw is also \(P\), the marginal law of trip \((h, i)\) is
\[\rho_h P + (1 - \rho_h) P = P ,\]so the mean estimate stays consistent for \(\beta_{\mathrm{true}}\) at every \(\rho\). What \(\rho\) moves is the within-traveler dependence, and through it the standard errors. At \(\rho = 0\) the trips are i.i.d. route draws grouped by traveler; at \(\rho = 1\) all \(T_h\) trips of a traveler are the same route, so the traveler contributes one independent observation.
Fresh routes are drawn on demand, only for trips whose uniform draw satisfies \(U_{h,i} \ge \rho_h\). The consumption of
rngis therefore a function of the realised draws, and a rerun with the same seed and the same \(\rho\) reproduces the dataset exactly.- Parameters:
- Returns:
Dict with keys y (N, L), Z (L, K), b (N, V), origins (N,), destinations (N,), traveler_id (N,), od_id (N,), where
N = sum(T_h).traveler_idin [0, H);od_idindexes distinct ODs for OD-level clustering. Trips are ordered by traveler.- Raises:
ValueError – If
trips_per_travelerorrhois neither a scalar nor a length-H array, if anytrips_per_traveler< 1, or if anyrholies outside [0, 1].- Return type:
- add_utility_noise(c, sigma)[source]¶
Adds i.i.d. Gaussian noise to a cost vector.
Produces \(\tilde c = c + \varepsilon\) with \(\varepsilon_j \sim \mathcal{N}(0, \sigma^2)\). Whenever \(K < L\), such a draw almost surely leaves the column space of \(Z\), so no \(\beta\) reproduces the perturbed costs; this is the misspecification lever for robustness experiments.
Route sampler¶
Route sampling from optimal link flows via Markovian decomposition.
The forward QP returns a link flow \(x^{\star}\) for a unit demand, whose mass in general spreads over several origin-destination paths. This module realises the corresponding path distribution: a walk that leaves each node along an edge drawn in proportion to its flow reproduces \(x^{\star}\) in expectation, so a sampled route is an observation the model could have produced.
- class micropurc.simulation.route_sampler.SampledRoute(y, nodes, valid)[source]¶
Bases:
objectResult of sampling a route from optimal flows.
- Variables:
y (numpy.ndarray) – Link indicator vector, shape (L,). y[e]=1 iff edge e is on the path.
nodes (list[int]) – Ordered node path from origin to destination.
valid (bool) – Whether the sampling succeeded. When False,
yis all zeros andnodesis empty.
- Parameters:
- class micropurc.simulation.route_sampler.MarkovSampler(network, active_set_config=None)[source]¶
Bases:
objectMarkovian route sampler with \(\mathbb{E}[y] = x^{\star}\) per link.
At node \(v\) the walk leaves along outgoing edge \(e\) with probability
\[p(e \mid v) = \frac{x_e}{\sum_{e' \in \mathrm{out}(v)} x_{e'}} ,\]and follows that policy from origin to destination. Flow conservation at the interior nodes is what makes the per-link expectation come back to \(x^{\star}\).
- Parameters:
network (Network)
active_set_config (ActiveSetConfig | None)
- __init__(network, active_set_config=None)[source]¶
Initializes the sampler.
- Parameters:
network (Network) – Network whose
tail/headarrays define the walk.active_set_config (ActiveSetConfig | None) – Supplies the threshold below which a link counts as carrying no flow, so numerical dust cannot be walked on.
- Return type:
None
- sample_route(x_star, origin, destination, rng=None)[source]¶
Samples one random route consistent with the flow vector.
The flow is clamped at zero and links below the configured activity threshold are dropped before the walk starts. Three conditions make the result invalid: an origin whose remaining outflow misses one unit, a node the walk reaches with no outgoing flow, and a revisit, since only simple paths are accepted. The step budget
2 * (V + L)bounds the walk on a flow that carries a cycle.- Parameters:
- Returns:
SampledRoute with the link indicator and node path.
- Return type:
- sample_routes_batch(flows, origins, dests, seeds=None, *, flow_row=None, rng=None, n_threads=-1, use_native=True)[source]¶
Samples many routes in parallel, one per trip.
Each trip
idraws a flow-proportional simple path fromorigins[i]todests[i]on the flow vectorflows[flow_row[i]], seeded byseeds[i]. This is the batched, multi-threaded counterpart ofsample_route(): same transition law (\(\mathbb{E}[y] = x^{\star}\) per link), same activity threshold, and the same validity rules (unit origin outflow, simple paths only).Each route is drawn from its own
mt19937_64. The two methods therefore agree in distribution, and a given trip’s realised route differs between them. Fixingseedsmakes this method reproducible on its own terms, for any thread count and any scheduling order.- Parameters:
flows (ndarray) – Link-flow vectors, shape (M, L). Rows need not be distinct; use
flow_rowto point several trips at one row.origins (ndarray) – Origin node indices, shape (N,).
dests (ndarray) – Destination node indices, shape (N,).
seeds (ndarray | None) – Per-trip RNG seeds, shape (N,), dtype uint64. If
None, seeds are drawn fromrng(or a default generator).flow_row (ndarray | None) – Index into
flowsper trip, shape (N,). Defaults toarange(N)(requiresM == N).rng (Generator | None) – Generator used to draw
seedswhen they are not supplied.n_threads (int) – Worker threads;
-1uses all hardware threads.use_native (bool) – If
False, fall back to a serial Python loop oversample_route()(for environments without the extension).
- Returns:
Tuple
(Y, valid)withYof shape (N, L) float64 (link indicators) andvalidof shape (N,) bool.- Raises:
ValueError – If array shapes are inconsistent.
- Return type: