model 05 — overlapping generations · psl-og · UK · local

Model long-run behavioural change.

Estimate how reforms affect work, saving, investment, and public finances over decades, for the UK.

code

From a few lines of Python to a full UK transition path.

OG-UK is open source, and every step below that carries logic ships as a runnable file under /olg/code/ on this site. Pick a step, run it on its own, swap the reform. Full context lives in PSL's OG-UK repository.

Local-only. A solve cannot fit the hosted MCP server's timeout, so hosted calls (score_reform with model="og", dynamic_reform_impact) return install/CLI instructions instead of results. Locally, until OG-UK#68 lands, oguk pins policyengine-uk==2.88.0 and needs its own environment: pe-macro og-score --reform '...' --json > og.json there, then pe-macro dynamic-score --reform '...' --og-payload og.json in the main one. Both need a HUGGING_FACE_TOKEN with access to the gated UK microdata. What the pin means for results is on the Validation tab.
1

Install

A Python package (3.11+); the recommended setup uses uv. The PolicyEngine enhanced-FRS microdata downloads on first run and is gated: you need a HUGGING_FACE_TOKEN with read access to policyengine/policyengine-uk-data.

runnable file: 01_install.sh. prefer conda? conda env create -f environment.yml && conda activate oguk-dev && pip install -e . works too.

2

Define a reform

Reforms use the PolicyEngine API: pick a parameter from the UK tax-and-benefit rule book, give it a new value and a start date. Anything PolicyEngine can represent — rates, thresholds, allowance tapers, new benefits — flows straight through. To simulate a different reform, swap the parameter path and value; the rest of the pipeline does not change.

runnable file: 02_reform.py — every later script imports REFORM from it.

Stack several ParameterValues in one Policy to score a package. Common parameter paths: gov.hmrc.income_tax.rates.uk[0..2].rate (basic 20%, higher 40%, additional 45%), gov.hmrc.income_tax.allowances.personal_allowance.amount (£12,570), and gov.hmrc.national_insurance.class_1.rates.employee.main / .higher.

Shocks that are not tax-and-benefit statute go through param_overrides, a dict of OG-Core parameters applied on top of everything else. Corporation tax lives here, not in PolicyEngine — it is a structural parameter of the macro model.

Use Z (TFP) for productivity shocks — never g_y_annual, which is the balanced-growth normalisation the model detrends by, not a productivity lever.
3

Solve the long-run steady state

The fastest way to see what a reform does. solve_steady_state finds the long-run equilibrium of the UK economy under a given policy: the prices, quantities and tax revenues that emerge once the economy has fully adjusted. Run it once for the baseline, once for the reform; the difference is the answer.

The output shown is illustrative — the format matches 03_steady_state.py, but the numbers depend on the calibration date and your data release. On the fast configuration, budget ~17 minutes per steady state.

The main configuration dials and their trade-offs:

mode
solve_steady_state() long-run answer, ~17 min per solve
run_transition_path() full 60-year path, multiple hours for baseline + reform
age_specific
"pooled" one tax function for all ages (fastest, most stable)
"brackets" one per age bracket (4 groups, split at state pension age)
"each" one per single year of age (80 functions, slowest)
multi_sector
False one production sector
True eight industries calibrated from ONS Blue Book supply-and-use tables by SIC section; needed for sector-level questions like energy price shocks

start with pooled + single-sector and scale up only once the fast run answers your question. non-converging solve? raise max_iter (default 250), fall back to pooled, and check whether the reform is extreme — OLG models can diverge on very large shocks.

4

Run the year-by-year transition path

The steady state tells you where the economy ends up. The transition path tells you how it gets there — year by year, 60 periods by default (configurable). This is what produced the reform paths in the Showcase tab. The transition costs more compute — the model solves every cohort's lifetime under rational expectations — so OG-UK uses Dask to parallelise across CPU cores.

runnable file: 04_transition.py. transition paths are available through the oguk API but are not wired into the PolicyEngine Macro CLI yet — the CLI is steady-state only (see step 7).

5

From abstract units to pounds

OG-UK solves in dimensionless model units. To translate them into figures a policymaker can read, map_to_real_world() returns a MacroImpact carrying, for each aggregate, the reform level, the change from baseline, and the percent change — all current-price £bn — plus baseline and reform interest rates:

  • .gdp / .gdp_change / .gdp_pct
  • .consumption, .investment, .government — same triple each
  • .tax_revenue, .debt — same triple each
  • .r_baseline, .r_reform — steady-state interest rates

The £bn mapping is GDP-anchored: one scale factor (real-world GDP ÷ model GDP) converts model-unit changes, with levels anchored to live ONS series (GDP, consumption, investment, government, debt ratio) and HMRC total receipts, falling back to cached values if ONS is unreachable. The transition-path variant returns NumPy arrays indexed by year.

It cannot give you a quarterly path, a costing on the OBR's forecast basis, or a distributional table. Those are the OBR emulator and PolicyEngine respectively. And it should not be read as a forecast of anything — see how far to trust it.
6

Bring in the eight industry sectors

Pass multi_sector=True and the same call returns the breakdown across the eight UK industry sectors (energy, manufacturing, construction, trade & transport, info & finance, real estate, business services, public & other) — sector-level output, capital and labour alongside the macro aggregates, and the basis for the industry charts in the Showcase tab.

runnable file: 06_multi_sector.py.

7

Where to go next

Every step above ships as a file you can download and run on its own:

Through this suite, two CLI commands are wired: pe-macro og-baseline and pe-macro og-score --reform '{"gov.hmrc.income_tax.rates.uk[0].rate": 0.21}', both on the fast configuration (pooled tax functions, single sector, steady state). For more variations, the upstream OG-UK examples directory carries additional pipelines (run_oguk_fast_tpi.py, run_oguk_fast_sector.py, plot.py). Full API reference and theory documentation: pslmodels.github.io/OG-UK.