agent_urban_planning.AhlfeldtUtilityEngine¶
- class AhlfeldtUtilityEngine(params, seed=None, large_N_threshold=200, budget_constraint=True, sampling_method='auto', deterministic=False, dtype='float64')[source]¶
Bases:
objectCobb-Douglas + Fréchet joint residence-workplace decision engine.
The closed-form softmax engine driving paper variant V1. Implements the Ahlfeldt et al. (2015) indirect utility
ln u_ij = ln B_i + ln w_j - (1 - beta) * ln Q_i - kappa * tau_ijand dispatches sampling to one of three paths depending on scenario scale and thesampling_methodkwarg:gumbel(small-N default) — per-agentN x NGumbel shock matrix stored in_shock_cache; argmax overlog_Phi + g / epsilon. Used for Bezirke (N=23) and Ortsteile (N=97) where memory is trivial.multinomial(factorized, large-N default) — sharedlog_Phicomputed once perdecide_batchcall; each agent samples fromsoftmax(epsilon * log_Phi)via inverse-CDF lookup. Memory footprint is independent of agent count; used at block scale (N=12k).deterministic— continuum-limit interpretation: each agent contributes fractional weightP_ij * weightto every (i, j). Matches Ahlfeldt’s closed-form aggregate equilibrium exactly with zero Monte Carlo noise. Used as the primary block-level path for pack-reproducing results.
Most users should configure this through
UtilityEnginerather than instantiating it directly.- Parameters:
params (
AhlfeldtParams) – Structural parameters (alpha,beta,epsilon,kappa, …).seed (
Optional[int]) – Optional integer seed for the per-agent shock RNG.large_N_threshold (
int) – Size at which auto-dispatch switches fromgumbeltomultinomial. Defaults to200.budget_constraint (
bool) – Whether to mask out (residence, workplace) pairs that violate a loose Cobb-Douglas affordability check.sampling_method (
str) –"auto"/"gumbel"/"multinomial".deterministic (
bool) – WhenTrue, force the continuum-limit path regardless ofsampling_method.dtype (
str) –"float32"or"float64".
Examples
>>> import agent_urban_planning as aup >>> # Prefer the public wrapper for V1 reproduction: >>> # engine = aup.UtilityEngine(params, mode="softmax") >>> # The advanced class is used internally by that wrapper.
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.
- set_cache(cache)[source]¶
Accept and discard a cache reference (no-op for this engine).
AhlfeldtUtilityEngineruns entirely closed-form and does not use an LLM cache. Implements the protocol to keep market loops uniform.- Parameters:
cache – Ignored.
- Return type:
- Returns:
None.
Examples
>>> import agent_urban_planning as aup >>> # engine = aup.AhlfeldtUtilityEngine(params) >>> # engine.set_cache(None) # safe no-op
- set_current_wages(wages)[source]¶
Inject the current wage vector from the market loop.
Called by
AhlfeldtMarketonce per iteration beforedecide_batchso the engine can use the freshly updated wages in its utility computation. Missing keys fall back toZone.wage_observedat utility-computation time.- Parameters:
wages (
dict[str,float]) – Mappingzone -> wagefor the current iteration.- Return type:
- Returns:
None.
Examples
>>> import agent_urban_planning as aup >>> # engine.set_current_wages({"Mitte": 1.1, "Charlottenburg": 0.9})
- set_current_productivity(A)[source]¶
Inject per-zone productivity
A_ifrom the market loop.Called each iteration by
AhlfeldtMarketwhen endogenous agglomeration is active. Missing keys fall back toZone.productivity_Aat utility-computation time.- Parameters:
A (
dict[str,float]) – Mappingzone -> A_i(damped post-update productivity).- Return type:
- Returns:
None.
Examples
>>> import agent_urban_planning as aup >>> # engine.set_current_productivity({"Mitte": 1.5})
- set_current_amenity(B)[source]¶
Inject per-zone amenity
B_ifrom the market loop.Mirrors
set_current_productivity(). The indirect-utility formula readsln B_ifrom this injection when present; otherwise falls back toZone.amenity_Bper zone.- Parameters:
B (
dict[str,float]) – Mappingzone -> B_i(damped post-update amenity).- Return type:
- Returns:
None.
Examples
>>> import agent_urban_planning as aup >>> # engine.set_current_amenity({"Mitte": 2.1})
- property price_elasticity: float¶
Market clearing code queries this for tatonnement step sizing.
For Ahlfeldt scenarios the floor-price elasticity is derived structurally:
(1 - beta) * epsilon.
- decide(agent, environment, zone_options, prices)[source]¶
Choose a (residence, workplace) pair for a single agent.
Delegates to
decide_batch()with a single-element list, so that single-agent and batch paths share the same numerical kernel.- Parameters:
environment (
Environment) – TheEnvironmentcarrying zones and travel-time matrix.prices (
dict[str,float]) – Mappingzone -> floor price Q_i.
- Return type:
- Returns:
A
LocationChoicewith the chosen residence, workplace, realized utility, and per-zone diagnostic utilities.
Examples
>>> import agent_urban_planning as aup >>> # engine = aup.AhlfeldtUtilityEngine(params) >>> # choice = engine.decide(agent, env, zones, prices)
- decide_batch(agents, environment, zone_options, prices)[source]¶
Choose joint (residence, workplace) pairs for a batch of agents.
Builds the shared
log u_ijmatrix once from the current prices, wages, productivity and amenity vectors, then dispatches to one of three sampling paths (gumbel / multinomial / deterministic) depending on scale and the engine’ssampling_method/deterministicflags.pricesholds residential floor pricesQ_ikeyed by zone name. Current wages are read from the value set viaset_current_wages()or fall back toZone.wage_observed.- Parameters:
agents (
list[Agent]) – List ofAgentinstances to decide for.environment (
Environment) – TheEnvironmentthey operate in.zone_options (
list[str]) – Allowed residence and workplace zone names.prices (
dict[str,float]) – Mappingzone -> Q_i(residential floor price).
- Return type:
- Returns:
List of
LocationChoice, one per input agent. Order matchesagents. The deterministic path additionally populatesself.last_choice_probabilitieswith the full(N, N)choice-probability matrix consumed byAhlfeldtMarketfor continuum aggregation.
Examples
>>> import agent_urban_planning as aup >>> # engine = aup.AhlfeldtUtilityEngine(params, deterministic=True) >>> # choices = engine.decide_batch(agents, env, zones, prices)