Skip to content

Million-worker equilibrium wealth search

examples/optimize_equilibrium.py uses EquilibriumOptimizer to search for the highest average wealth found while household wealth, consumption, prices, and finances settle together. The search is deterministic and local; its result does not establish a global maximum.

Scenario and inputs

The worker cap is 1,000,000, and the workforce ratio is 0.4, allowing up to 2,500,000 people including dependents. Actual employment may be lower. The scenario excludes DEFAULT_BANNED_BGS from examples/__init__.py, and uses min_goods_balance=None. Infrastructure is unconstrained (min_net_infrastructure=None); usage remains visible in reports as a diagnostic, and the summary records an empty minimum.

Fixed throughput multipliers are all 1.0, and level-dependent economy-of-scale bonuses are disabled. Trade, resource limits, arable-land limits, and era restrictions retain their defaults. Household demand is endogenous, calculated from household wealth and population composition.

The script loads the production table, goods, professions, population needs, and buy packages from the existing artifact datasets. It restricts production to the union of active building/production-method configurations in the saved era_5_automation/*/processes.csv supply-chain reports. Other configurations are excluded from both the initial mix and the optimizer's search. Generate the datasets and supply-chain reports first:

uv run python -m examples.parse
uv run python -m examples.production_analysis
uv run python -m examples.supply_chain_compare

The initial mix starts with equal levels of every allowed configuration and scales to one million workers without balancing infrastructure. A mix with infrastructure consumption and no supplier is permitted. Missing reports, an empty active set, or configurations absent from the production table raise an error. The optimizer checks scenario feasibility before starting its search.

The default hard trial budget is 20,000 per local search:

uv run python -m examples.optimize_equilibrium --max-iterations 10

Proportional income tax funds operating losses by default. Disable it with:

uv run python -m examples.optimize_equilibrium --no-subsidize-operating-losses --max-iterations 10

The enabled policy taxes cooperative and dependent income without guaranteeing payroll, and applies to saved baselines and every restart. --resume uses the current invocation's policy, which defaults to enabled; summary.csv records it in subsidize_operating_losses. Imported calls also default to enabled; use run(..., subsidize_operating_losses=False) to disable it. If the initial or saved mix cannot fund its losses, the example still starts the optimizer. Rejected trials receive a penalty based on their funding shortfall; only verified, funded equilibria can become the best solution. An unfunded saved mix is not retained as a baseline. The summary records initial_equilibrium_status=insufficient_income and leaves its initial wealth/GDP metrics empty. If no valid result is found within the trial budget, the search fails without overwriting exports. Other initialization errors still propagate. See tax-funded operating losses.

Direct-file execution is also supported. A short run can validate the pipeline and regenerate reviewable snapshots:

uv run python examples/optimize_equilibrium.py --max-evaluations 8 --max-iterations 1

The CLI enables timestamped INFO progress on stderr by default, with periodic summaries every 100 trials. Use a shorter interval for a small-budget run:

uv run python -m examples.optimize_equilibrium --max-evaluations 8 --max-iterations 1 --log-every 2

--log-level DEBUG adds individual trial outcomes, new incumbents, and concise inner-solver start/finish/failure messages. --log-level WARNING suppresses the progress logs while retaining the printed result summary. --log-every must be positive. Imported calls to run(..., log_every=100) leave logging configuration to their caller. Standard logging stream handlers flush each record, so progress appears as the search runs; redirect stderr to save it.

Progress reports distinguish initialization from subsequent search, show uncached trials used versus budget, current and best unscaled objective values in weekly units, scenario feasibility, inner-solver iteration counts, failures, and elapsed seconds. See the progress-field reference. The CSV schemas and annualization rules are unaffected by logging options.

After each completed search, the example reduces materially loss-making building configurations by --loss-reduction (default 0.5) and restarts with a fresh trial budget. Profitability uses all output revenue, including coproducts, minus input costs at the returned equilibrium prices, with the same absolute/relative loss tolerance as the supply-chain example. Other levels are unchanged, and employment is not scaled back up. The adjusted mix must remain feasible under the scenario's worker cap and building exclusions.

Iteration stops when the returned objective is strictly lower than the preceding search's objective. The scenario maximizes average wealth; GDP remains a reported metric. Equal objectives continue, and the earliest result wins ties. --max-iterations optionally caps completed local searches. Omission means unlimited searches, so an equal-objective plateau can continue indefinitely. --loss-reduction 0 disables reductions for all restarts.

To start from the saved solution:

uv run python -m examples.optimize_equilibrium --resume --loss-reduction 0.5 --max-iterations 10

Resume aligns buildings.csv by building and production method, restores omitted zero configurations, and validates the saved levels against the current scenario. Duplicate or unknown configurations are rejected. The saved levels are re-evaluated, then adjusted for the first local search. The saved baseline remains eligible as the best solution; if the first new result is worse, iteration stops and exports that baseline. Use --resume --loss-reduction 0 --max-iterations 1 for one local search from the unchanged saved levels.

The budget limits optimizer trials. Evaluation of the initial baseline and fresh verification of the returned state are additional equilibrium solves. A very small budget can end during search initialization and retain the initial mix. Larger budgets can take tens of minutes or longer.

The console reports each search's initial and returned objective, the best objective, annual GDP, actual workers and population, and iteration termination. A budget-limited search retains its best verified equilibrium with search_success=False and an explicit search_message; equilibrium_status is still converged. Invalid inputs or a search without a feasible converged state raise an error before writing new outputs. Financial flags remain visible on successful household equilibria.

Summary and solution

The summary has one row per completed local search, including the worse attempt that triggers stopping. A resumed invocation also has an iteration-zero row for the re-evaluated saved baseline with empty search diagnostics. Each row records scenario settings, starting and returned metrics, search diagnostics, financial-flag counts, applied reduction, objective change, UTC start time, and elapsed seconds. is_best selects the row whose solution is exported. Every row also records invocation totals, the selected best iteration, optional iteration cap, and stop reason (objective_worsened or max_iterations). Monetary totals use equilibrium market prices; annual GDP is weekly GDP multiplied by 52. See the summary dictionary for all columns.

annual_gdp employment population average_wealth trial_evaluations search_success equilibrium_status
15198211.216975441 954868.326364183 2387170.815910457 21.666096016339544 1732 True converged

Download artifacts/analyses/optimization/equilibrium/summary.csv

The solution uses the existing building registry report: nonzero configuration levels, sorted by level descending. Configuration keys identify building and production-method combinations in the source table.

building production_method level construction_cost
building_oil_rig pm_combustion_derricks+pm_tanker_cars 29.692470427944457 11876.988171177783
building_railway pm_diesel_trains_principle_transport_3+pm_wooden_passenger_carr… 28.339219916246392 22671.375932997114
building_power_plant pm_oil-fired_plant 19.03149068754308 7612.596275017232
building_tooling_workshop pm_rubber_grips+pm_assembly_lines_building_tooling_workshop 14.038314482141365 8422.988689284819
building_iron_mine pm_diesel_pump_building_iron_mine+pm_dynamite_building_iron_min… 13.978474355367108 5591.389742146844
building_tea_plantation automatic_irrigation_building_tea_plantation+slave_exploitation… 13.938106222211609 2787.6212444423218
building_railway pm_diesel_trains_principle_transport_3+pm_no_passenger_trains 11.887933191544667 9510.346553235733
building_silk_plantation automatic_irrigation_building_silk_plantation+pm_steam_rail_tra… 11.75066731929551 2350.133463859102
building_urban_center pm_arcades+pm_electric_streetlights+pm_public_motor_carriages+p… 11.74521647653587 0.0
building_motor_industry pm_diesel_engines+pm_assembly_lines_building_motor_industry 11.425075845028694 9140.060676022955

Download artifacts/analyses/optimization/equilibrium/buildings.csv · View examples/optimize_equilibrium.py

The script also exports market.csv, pop.csv, and goods_buy_packages.csv from the other three Economy.df_ reports for the same verified state. The goods buy-package export preserves the wealth index as a wealth column.

After iteration ends, the best retained solution is written once to the four solution CSVs; summary.csv contains the full search history. Validation and equilibrium errors propagate before any files are overwritten. Read the recorded budgets and search status when comparing results. See the optimization guide for solver behavior and the optimization API.