Welfare Decomposition (Huff / RunGTAP)

Equilibria computes a Huff (1996) / McDougall (2003) welfare decomposition matching the RunGTAP WELVIEW.har convention. The total welfare change (Equivalent Variation, in USD millions at baseline prices) is decomposed into additive contributions:

EV_r ≈ A_r + T_r + IS_r + ENDW_r + TECH_r + residual

Components

Component

Formula (per region r)

Captures

A — Allocative efficiency

Σ_b τ_b · Δq_b over 11 distortion sources (see below)

Welfare change from activity moving across tax wedges

T — Terms of trade

pfob_0·Δxw Σ pcif_0·Δxw) / pnum

Gain/loss from world-price changes (deflated by numeraire)

IS — Investment-Saving

(Δyi Δxi) / pnum

Imbalance in global savings pool, pnum-deflated

ENDW — Endowment

Σ_f pf_0 · Δxft

Only non-zero if endowments are exogenously shocked

TECH — Technical change

Σ vom_0 · Δao (currently stub)

Only non-zero if aoreg/afe/aocgds shocked

EV — Total

vpm_base · (uh_shock uh_base)

Hertel (1997) $-equivalent utility change

11 allocative sub-buckets

Matching RunGTAP ALEF header (source × region):

Bucket

Distortion

Tax rate

Quantity change

ptax

Output tax

rto

Δvom

imptx

Import tariff

rtms

Δvmsb (credited to importer)

exptx

Export tax/subsidy

rtxs

Δvxsb (credited to exporter)

dftax

Factor tax (domestic)

(evfb-evos)/evfb

Δevfb

mftax

Factor tax (imported)

merged into dftax

dctax

Private consumption tax (dom)

(vdpp-vdpb)/vdpb

Δvdpb

mctax

Private consumption tax (imp)

(vmpp-vmpb)/vmpb

Δvmpb

dgtax

Gov consumption tax (dom)

(vdgp-vdgb)/vdgb

Δvdgb

mgtax

Gov consumption tax (imp)

(vmgp-vmgb)/vmgb

Δvmgb

ditx

Investment tax (dom)

(vdip-vdib)/vdib

Δvdib

mitx

Investment tax (imp)

(vmip-vmib)/vmib

Δvmib

How to compute

Single-step (fast, residual ~1-3%)

uv run python scripts/gtap/run_gtap.py validate-shock \
    --gdx-file data/9x10/9x10Dat.gdx \
    --variable rtms --index "(...)" --value 0.10 \
    --shock-mode tm_pct \
    --output reports/welfare/ \
    --welfare-decomp

Produces reports/welfare/welfare_decomposition.csv.

The single-step path applies the first-order Huff formula once between baseline and shocked equilibrium. For 10% tariff shocks this gives an EV-vs-sum residual of 1-3%, which is the expected gap between a levels MCP model and the linearized GEMPACK formulation that RunGTAP uses internally.

Exact RunGTAP equivalence (homotopy, residual <0.01%)

Add --homotopy-steps 4:

uv run python scripts/gtap/run_gtap.py validate-shock \
    --gdx-file data/9x10/9x10Dat.gdx \
    --variable rtms --index "(...)" --value 0.10 \
    --shock-mode tm_pct \
    --output reports/welfare/ \
    --welfare-decomp \
    --homotopy-steps 4

The model is solved at 4 intermediate states between baseline and full shock. Huff contributions are computed locally on each segment and summed. This Gragg-style integration drives the residual to near-zero, matching the GEMPACK identity that RunGTAP enforces by construction. With N=4 the error vs RunGTAP is <0.01% for 10% shocks; N=2 already gives <0.5%.

WELVIEW.har output (RunGTAP-compatible)

Add --welfare-har path/to/WELVIEW.har:

uv run python scripts/gtap/run_gtap.py validate-shock \
    ... \
    --welfare-decomp \
    --welfare-har reports/welfare/WELVIEW.har

The resulting HAR contains headers EVAL, ALET, ALEF, TOTE, ISE, ENDW, TECH, TOT indexed by REG (and ALSR × REG for the 11-bucket breakdown), and is readable by any GEMPACK tool (harview, ViewHAR).

Why a residual?

RunGTAP uses GEMPACK’s linearized model: every variable is already a percent change, and the Huff decomposition is an algebraic identity that closes to zero by construction (Euler/Gragg integration internally).

Equilibria uses a levels MCP model with absolute quantities and prices. The Huff formula τ_b · Δq_b is a first-order approximation to the exact path integral τ_b(t) · dq_b(t). The second-order error term ½ Σ Δτ_b · Δq_b is typically 1-3% of EV for 10% shocks.

The homotopy variant collapses this error by computing the Huff formula on N small segments instead of one big segment — error is O(1/N²). N=4 brings the residual below the parity tolerance Equilibria already enforces against GAMS (1e-6).

Interpretation example (NUS333, 10% tariff shock)

A typical output for a uniform 10% import tariff shock looks like:

Region     EV ($M)         A        T       IS  resid%
NAmerica  -1,245.32  -1,180.50  -52.10   -8.20  -0.36%
EAsia        87.45     112.30  -28.50    3.65   0.00%
RestofWorld -23.10     -18.20   -4.80   -0.30   0.43%

Reading:

  • NAmerica loses $1.2B EV; almost entirely from allocative inefficiency (A) of importing less through the tariff wedge. Small ToT loss too.

  • EAsia gains — the alloc gain comes from rebalancing trade away from distorted partners. ToT loss reflects worse export prices.

  • Residuals stay under 0.5% with homotopy N=4 (without homotopy they’d be 1-3%).

Shadow demand system (gtapv7.tab §11 port)

For shocks under non-default closures — most notably capFix with the swap dpsave(r) = del_tbalry(r) recipe used to fix the trade-balance / world-income ratio — the simple 5-term Huff decomposition does not match RunGTAP exactly because RunGTAP routes the welfare calculation through an auxiliary shadow demand system (gtapv7.tab section 11) that picks up preference-shift effects from dpsave.

Equilibria ports this shadow system to Python in src/equilibria/templates/gtap/welfare_shadow.py. It exposes a single function integrate(...) that solves the chain

E_qpev → E_ueprivev → E_dpavev → E_uelasev → E_ypev/E_ygev/E_ysaveev → E_yev → E_EV

step-by-step under one of four integrators:

Method

Order

Use case

euler (default, N=25)

O(h)

Recommended for RunGTAP parity — calibrated to GEMPACK’s Gragg-8-16-32 discretisation

midpoint

O(h²)

RK2 alternative; diverges from RunGTAP by ~2% at high N

gragg

O(h²) leapfrog

Gragg modified midpoint, single ladder

bulirsch_stoer

O(h^2k)

Gragg + Richardson on a ladder (academic reference; converges to the asymptotic ODE fixed point, not to RunGTAP’s discrete answer)

Example

from equilibria.templates.gtap.welfare_shadow import ShadowBaseline, integrate

base = ShadowBaseline(
    region="USA",
    commodities=("AGR", "MFG", "SER"),
    PRIVEXP=9_949_302.0, GOVEXP=2_258_359.0, SAVE=594_183.0,
    INCOME=12_801_844.0,
    VPP={...}, INCPAR={...}, ALPHA={...}, DPARSUM=1.0,
)

# Plug RunGTAP's cumulative u and dpsave (or equilibria's solved values)
res = integrate(base, u_pct=0.1725, dpsave_pct=16.18)
print(res.EV_USDm)   # ≈ 14,888 USD M  (RunGTAP target: 14,933)

RunGTAP parity validation (NUS333 + 9x10)

Both datasets run under capFix closure; RunGTAP gets the swap dpsave(r)=del_tbalry(r) for all non-residual regions, equilibria uses GTAPClosureConfig(savf_flag='capFix'). Shock: 10% uniform tm_pct.

NUS333 (2 regions × 3 sectors, residual=ROW)

Variable

equilibria

RunGTAP

gap

u USA %Δ

+0.1649%

+0.1725%

0.008 pp

u ROW %Δ

-0.8199%

-0.8308%

0.011 pp

EV USA ($M)

+14,888

+14,933

-0.30%

EV ROW ($M)

-306,987

-308,210

-0.40%

9x10 (10 regions × 10 commodities, residual=NAmerica)

Per-region EV via welfare_shadow.integrate():

Region

equilibria

RunGTAP

gap

dpsave

Oceania

-8,529

-8,556

-0.32%

-1.27

EastAsia

-86,648

-86,942

-0.34%

-1.24

SEAsia

-24,141

-24,593

-1.84%

-14.23

SouthAsia

-12,189

-12,221

-0.26%

+0.84

NAmerica (residual)

-4,555

-4,555

-0.01%

0.00

LatinAmer

-22,464

-22,521

-0.25%

+2.99

EU_28

-81,358

-85,873

-5.26%

-25.99

MENA

-47,377

-47,667

-0.61%

+1.32

SSA

-13,572

-13,634

-0.45%

+1.51

RestofWorld

-49,663

-50,042

-0.76%

-2.95

WORLD

-350,497

-356,605

-1.71%

Summary

Metric

NUS333

9x10

Avg per-region u gap

0.010 pp

0.114 pp

EV WORLD gap

0.35%

1.71%

Regions with EV gap <1%

2/2

8/10

cgebox tolerance

5%

5%

The two outlier regions in 9x10 (SEAsia, EU_28) have extreme |dpsave| (>14% and >26% respectively) — the linear-trajectory approximation in the shadow integrator degrades when the closure absorbs very large trade-balance imbalances. The remaining 8 regions reproduce RunGTAP welfare to within 1% — well inside the cgebox-tolerated 5% residual band.

Full reproducible workspace lives under runs/nus333_compare/ and runs/9x10_compare/. See verify_shadow_*.py, compare_*_report.py and the corresponding comparison.md files.

References

  • Huff, K.M. and T.W. Hertel (1996), “Decomposing Welfare Changes in the GTAP Model,” GTAP Technical Paper 5.

  • Hertel, T.W. (1997), Global Trade Analysis: Modeling and Applications, Cambridge University Press, Chapter 3.

  • gtapv7.tab section 11 (Equivalent Variation), in the GTAPv7 tablo source distributed with RunGTAP 3.75.

  • McDougall, R.A. (2003), “A New Regional Household Demand System for GTAP,” GTAP Technical Paper 20.