Models / PolicyEngine microsimulation

model 05 — tax-benefit microsimulation · pe-microsim · UK · US · hosted

Calculate taxes and benefits.

Compare current law with a reform for a UK or US household, or estimate population-wide revenue and distributional effects.

01 — what it is

Statute as code, at household resolution.

The package encodes UK and US tax-and-benefit rules as parameterised statute and computes every variable for a household you describe — for the UK: income tax, National Insurance, Universal Credit, Child Benefit, Pension Credit; for the US: federal and state income tax, payroll tax, SNAP, TANF, EITC, CTC, SSI and Social Security. A reform is a flat dict of parameter path → new value, the same shape at household and population level.

Four layers: policyengine-core (the simulation framework, originally forked from OpenFisca), policyengine-uk and policyengine-us (the statutory rules per country), and policyengine — the analyst-facing package this page covers, which adds dataset management, reforms, structured outputs and version-pinned release bundles for reproducibility.

Within the suite it is the complement to the macro engines, and it feeds two of them: the OLG model consumes its effective and marginal rates to build tax functions, and the OBR emulator takes its static costing as the direct effect and returns the second-round one. This page is the direct route to the engine itself.

02 — how it works

Rules, entities, and a dependency graph.

Every quantity in the system — an allowance, a taper, a means test — is a variable with a formula and a set of dated parameters. Asking for one variable resolves the graph beneath it, so a household net-income figure is computed from the statutory chain rather than approximated by a schedule. Variables are attached to entities, and the entity structure differs by country: the UK has three (person, benefit unit, household), the US six (person, tax unit, SPM unit, family, marital unit, household), because US programmes are assessed on differently drawn units.

A reform re-dates a parameter and re-resolves the same graph, which is why the household and population APIs take reforms in the identical format. Each release pins the country-model and dataset versions it certifies, so a published estimate can be traced to the exact bundle that produced it.

03 — how to run it

A household in one call.

Published on PyPI. The base install is shared tooling only; add [models] for the pinned UK + US country models, or a single country extra.

pip install policyengine            # shared tooling, no country models
pip install "policyengine[models]"  # + pinned UK and US models
pip install "policyengine[uk]"      # UK model only
pip install "policyengine[us]"      # US model only

Describe the people, pick a year, read the results. Household calculations need no data access and no credentials.

import policyengine as pe

# UK: single adult earning £50,000 — current law
uk = pe.uk.calculate_household(
    people=[{"age": 35, "employment_income": 50_000}],
    year=2026,
)
print(uk.person[0].income_tax)                   # £7,486
print(uk.person[0].national_insurance)           # £2,994
print(uk.household.hbai_household_net_income)    # £39,520

# US: single filer in California, with a reform applied
us = pe.us.calculate_household(
    people=[{"age": 35, "employment_income": 60_000}],
    tax_unit={"filing_status": "SINGLE"},
    household={"state_code": "CA"},
    year=2026,
    reform={"gov.irs.credits.ctc.amount.adult_dependent": 1000},
)
print(us.tax_unit.income_tax, us.household.household_net_income)

run the same household with and without reform and difference the results — that is the household-level reform impact. For the UK earner above, raising the basic rate to 25% ({"gov.hmrc.income_tax.rates.uk[0].rate": 0.25}) adds £1,872 of income tax and cuts net income by the same amount.

The same package runs whole representative datasets — the enhanced UK Family Resources Survey, the US CPS — so any variable can be aggregated by decile, entity or demographic:

import policyengine as pe
from policyengine.core import Simulation
from policyengine.outputs.aggregate import Aggregate, AggregateType

datasets = pe.uk.ensure_datasets(datasets=["enhanced_frs_2023_24"],
                                 years=[2026], data_folder="./data")
simulation = Simulation(dataset=datasets["enhanced_frs_2023_24_2026"],
                        tax_benefit_model_version=pe.uk.model)
simulation.run()

agg = Aggregate(simulation=simulation, variable="universal_credit",
                aggregate_type=AggregateType.SUM, entity="benunit")
agg.run()
print(f"Total UC spending: £{agg.result / 1e9:.1f}bn")
UK population microdata is in a gated Hugging Face repository — set HUGGING_FACE_TOKEN from an account with access to policyengine/policyengine-uk-data before running UK population examples. On the hosted MCP server this credential is provisioned server-side, so hosted population scoring needs nothing from you.
04 — what it can answer

From one family to the whole distribution.

Common microsimulation questions and how to answer them
questionhow
What does this reform do to this family? calculate_household / household_reform_impact — every tax, benefit and income variable at person, benefit-unit/tax-unit and household level. Live on the hosted MCP server and the pe-macro CLI.
What does it cost, and who wins? population_reform_impact and pe.uk.economic_impact_analysis — budgetary impact, decile changes, winners and losers, poverty, inequality, regional impacts. Hosted, with the enhanced-FRS credential provisioned server-side.
What levers exist? list_reform_parameters — the parameter tree you can write reforms against.
It cannot tell you what a reform does to GDP, interest rates, or the fiscal forecast. It is static and partial-equilibrium by design: behavioural responses are optional and post-hoc (e.g. labour-supply elasticities), and there is no general equilibrium at all. That second act is precisely what the macro members add — short-run and long-run.
05 — how far to trust it

Checked rule by rule, not against a forecast.

The verification question here is different in kind from the rest of the suite. There is no forecast error to report, because the model is not forecasting: it is applying published rules. The claim is that it applies them correctly, and that claim is checkable by hand. For the £50,000 UK earner in section 03: £50,000 − £12,570 personal allowance = £37,430 of basic-rate income; at 20% that is £7,486 of income tax, at 8% £2,994 of National Insurance, leaving £39,520 net. Raising the basic rate 5pp adds 5% of £37,430 = £1,872. Every figure printed by the code sample is deterministic arithmetic from the implemented rules. This example verifies those rules; it is not a claim that every area of legislation has complete test coverage.

What is not exact is anything resting on survey microdata — population aggregates inherit sampling error, imputation and ageing assumptions from the enhanced FRS and the CPS, so a headline budgetary cost is an estimate in a way a household calculation is not. Comparisons of PolicyEngine's static costings against official sources are set out for the UK 1p basic-rate case in the OBR emulator working paper: £6.46bn in 2026 rising to £7.38bn by 2030, inside the range of HMRC's June 2025 ready reckoner (£6.9bn rising to ~£8.2bn), toward its lower end. Suite-wide evidence: validation.

household calculation deterministic implemented rules applied, not estimated
countries 2 UK · US tax-benefit systems
UK / US entities 3/ 6 benefit unit vs tax unit, SPM unit, family, marital unit
06 — limits

Scope and vintage.

Known limits of the tax-benefit microsimulation
limitdetail
Static, no general equilibrium Reforms are modelled statically with optional post-hoc behavioural responses. Prices, wages and output do not move.
UK data vintage UK population runs use enhanced_frs_2023_24. Upstream now publishes an FRS 2024-25 vintage, which this integration has not adopted.
Survey-based aggregates Population estimates inherit sampling, imputation and ageing error. HMRC's own costings use administrative Survey of Personal Incomes data and embed taxable-income elasticities, so differences of a few hundred million on a broad-based rate change are expected.
Gated UK microdata A HUGGING_FACE_TOKEN with access is required locally. Household calculations are unaffected.
Statute-vintage divergence with OLG This package tracks the latest release; the OLG member is pinned to policyengine-uk==2.88.0, so the two can disagree about baseline statute. A declared inconsistency.
Two countries UK and US only.

no standalone working paper for this member yet — its behaviour is documented through the country model repositories and, where it feeds the macro members, in the OBR and OLG papers.