agent_urban_planning.SimulationEngine

class SimulationEngine(scenario, agent_config, engine=None, seed=None, verbose=False, clustering=None, llm_provider=None, llm_model=None, llm_temperature=None, llm_concurrency=None, price_elasticity=None, initial_damping=None, market_convergence_threshold=None, max_market_iterations=None)[source]

Bases: object

Orchestrate one end-to-end simulation: config to environment to market to metrics.

Top-level entry point of the library. Wires together a scenario, an agent population, a DecisionEngine, and a market clearer (HousingMarket for Singapore-style scenarios, AhlfeldtMarket for Berlin Cobb-Douglas + Fréchet spatial-equilibrium scenarios). Calling run() produces a SimulationResults object containing welfare metrics, per-agent allocations, the price-history trajectory, and run metadata.

Parameters:
  • scenario (ScenarioConfig) – A loaded ScenarioConfig describing zones, transport network, and (for Berlin) Ahlfeldt structural parameters.

  • agent_config (AgentDistributionalConfig) – A AgentDistributionalConfig describing the agent population (per-zone Census distributions or explicit agent records).

  • engine (Optional[DecisionEngine]) – An optional DecisionEngine. When None the engine is auto-selected based on scenario (Berlin scenarios → AhlfeldtUtilityEngine; everything else → the legacy Singapore UtilityEngine).

  • seed (Optional[int]) – Optional integer seed for the agent-sampling RNG. Defaults to scenario.simulation.random_seed.

  • verbose (bool) – When True, prints progress and intermediate results.

  • clustering (Optional[ClusteringConfig]) – Optional clustering configuration for wrapping the inner engine in a ClusterizedDecisionEngine (used with full-LLM mode to amortize calls across archetypes).

  • llm_provider (Optional[str]) – Provider name to record in run metadata ("codex-cli", "claude-code", "anthropic", …).

  • llm_model (Optional[str]) – Model name to record (e.g. "haiku").

  • llm_temperature (Optional[float]) – Sampling temperature to record.

  • llm_concurrency (Optional[int]) – Concurrency setting for the async LLM client.

  • price_elasticity (Optional[float]) – Override of the floor-price elasticity used by the tatonnement step. Falls back to engine default.

  • initial_damping (Optional[float]) – Initial Walrasian damping lambda.

  • market_convergence_threshold (Optional[float]) – Maximum absolute excess demand at which clearing is declared converged.

  • max_market_iterations (Optional[int]) – Iteration cap on the tatonnement loop.

Examples

>>> import agent_urban_planning as aup
>>> scenario = aup.data.builtin.load("singapore_real_v2")
>>> agents = aup.data.builtin.load_agents("singapore_real_v2")
>>> sim = aup.SimulationEngine(scenario=scenario, agent_config=agents)
>>> results = sim.run(policy=None)
>>> sorted(results.metrics.zone_populations.values())
[0.05, 0.07, 0.13, ...]

See also

agent_urban_planning.UtilityEngine — the closed-form decision engine used by default for Berlin scenarios. agent_urban_planning.AhlfeldtMarket — the two-market tatonnement clearer used for Berlin scenarios.

References

Ahlfeldt, G. M., Redding, S. J., Sturm, D. M., Wolf, N. (2015). The economics of density: Evidence from the Berlin Wall. Econometrica, 83(6), 2127-2189.

classmethod from_paths(scenario_path, agents_path, engine=None, seed=None)[source]

Build a SimulationEngine directly from YAML file paths.

Convenience constructor that loads both the scenario and agent configurations from disk before delegating to the regular constructor. Useful for command-line scripts and notebooks.

Parameters:
  • scenario_path (str) – Path to a scenario YAML file (passed to load_scenario()).

  • agents_path (str) – Path to an agent-population YAML file (passed to load_agents()).

  • engine (Optional[DecisionEngine]) – Optional DecisionEngine instance. None selects the default engine for the scenario.

  • seed (Optional[int]) – Optional integer seed.

Return type:

SimulationEngine

Returns:

A configured SimulationEngine ready to run.

Examples

>>> import agent_urban_planning as aup
>>> sim = aup.SimulationEngine.from_paths(
...     "config/scenarios/singapore_real_v2.yaml",
...     "config/agents/singapore_real_v2.yaml",
...     seed=42,
... )
run(policy=None, baseline=None, market_resume=None, market_checkpoint_callback=None, llm_cache_path=None)[source]

Run one simulation under the given policy and return full results.

Applies the policy to the base environment, runs the scenario-appropriate market clearer (HDB/private tatonnement for Singapore, Q + w joint tatonnement for Berlin), assembles per-agent results, and computes welfare metrics. When policy is None, the scenario’s built-in environment is used unchanged — no transit or facility investments are applied. This supports both Ahlfeldt replication runs (which have no notion of government investment) and baseline observational runs that report the pre-intervention equilibrium.

Parameters:
  • policy (Optional[PolicyConfig]) – Optional PolicyConfig describing transit and facility investments to apply before clearing. None runs the scenario as observed.

  • baseline (Optional[SimulationResults]) – Optional pre-computed SimulationResults from a prior run. When provided, each agent’s utility_vs_baseline field is filled in with the difference between their realized utility here and in baseline.

  • market_resume (Optional[dict]) – Optional checkpoint state from a prior interrupted run, returned by market_checkpoint_callback in a previous invocation. Lets long LLM runs survive process restarts.

  • market_checkpoint_callback – Optional callable invoked with a JSON-serializable checkpoint dict at every market iteration. Receives a dict payload that can be passed back as market_resume to resume.

  • llm_cache_path (Optional[str]) – Optional path to a disk-backed LLM cache. Reuses cached LLM completions across invocations.

Return type:

SimulationResults

Returns:

A SimulationResults object aggregating welfare metrics, per-agent allocations, market price history, and run metadata.

Examples

>>> import agent_urban_planning as aup
>>> sim = aup.SimulationEngine(scenario, agent_config)
>>> baseline = sim.run()
>>> with_policy = sim.run(policy, baseline=baseline)
>>> with_policy.metrics.avg_utility - baseline.metrics.avg_utility
0.04
compare_policies(policies)[source]

Run one simulation per policy and return results keyed by policy name.

Iterates over a list of policies, running run() for each and threading the first policy’s result through as the baseline for utility_vs_baseline computations on subsequent policies. Useful for cross-policy welfare comparisons in a single call.

Parameters:

policies (list[PolicyConfig]) – List of PolicyConfig objects. The first policy in the list is treated as the comparison baseline.

Return type:

dict[str, SimulationResults]

Returns:

A dict mapping each policy.name to its SimulationResults.

Examples

>>> import agent_urban_planning as aup
>>> sim = aup.SimulationEngine(scenario, agent_config)
>>> results = sim.compare_policies([baseline_policy, alt_policy])
>>> results["alt"].metrics.avg_utility
2.13
budget_sweep(base_policy, transit_shares)[source]

Sweep over transit budget share and return results for each share level.

Generates a series of policies that interpolate between facility-only (share=0) and transit-only (share=1) allocations of the policy’s total budget, and runs the simulation for each. At every share level transit investments scale travel time improvements proportionally (50% budget yields halfway between old and new time), while facility investments scale capacity and quality proportionally.

Parameters:
  • base_policy (PolicyConfig) – A baseline PolicyConfig whose transit_investments and facility_investments are used as the upper-budget reference. The policy’s total_budget defines the budget envelope swept.

  • transit_shares (list[float]) – Iterable of fractions in [0, 1] indicating what share of the total budget goes to transit at each point of the sweep. The complementary 1 - share goes to facilities.

Return type:

list[tuple[float, SimulationResults]]

Returns:

List of (share, SimulationResults) tuples in the order of transit_shares. Use these to build budget-allocation tradeoff plots.

Examples

>>> import agent_urban_planning as aup
>>> # sim = aup.SimulationEngine(scenario, agent_config)
>>> # sweep = sim.budget_sweep(policy, [0.0, 0.5, 1.0])
>>> # shares = [s for s, _ in sweep]
>>> # avg_utilities = [r.metrics.avg_utility for _, r in sweep]