replacement_rate_adjust is a (T+S, J) multiplier on the pension replacement rate — the natural way to model a country that automatically cuts benefits as it ages.
It only works under one of the four pension systems. Under the other three it is accepted, stored, and ignored.
The test. Same parameter, same call, two systems:
out = pensions.pension_amount(0.04, 1.0, n, 1.0, 0.1, -1, 0, False, "SS", e, 1e5, p)
US-Style Social Security adj=1.0 -> 3.60000 adj=0.5 -> 1.80000 scales exactly
Defined Benefits adj=1.0 -> 0.92659 adj=0.5 -> 0.92659 no effect
Why. In ogcore/pensions.py the parameter is read in six places, all inside SS_amount() (lines 159–199), which serves only "US-Style Social Security". DB_amount() (line 204), NDC_amount() (332) and PS_amount() (429) contain no reference to it.
Why it matters. Nothing warns. A calibration that sets it under Defined Benefits looks calibrated and is not — the solved steady state is byte-identical to one that never set it (pension/Y = 0.093040, theta = 0.0038901, both ways). This is the same trap as alpha_db defaulting to 0.0, where switching to Defined Benefits without setting it pays zero pensions. A parameter that silently does nothing is worse than a missing one, because it produces a confident wrong answer.
A concrete case. Japan's pension system has a macroeconomic slide that automatically reduces benefits to stay solvent — the MHLW 2024 actuarial review projects the replacement rate falling from 61.2% in FY2024 to 50.4% in FY2057. Without a working adjustment, the DB block pays a fixed replacement rate, so the pension bill rises mechanically with the old-age ratio: 12.6% of GDP against an actual 9.3% today. The calibration then has to choose between matching today's spending and matching the long run, when Japan's actual institution does both. Any country with a legislated benefit glide or a notional-account balancing mechanism hits this.
Suggested fix. Move the adjustment out of SS_amount and into pension_amount, which already receives t and method. Every system then honours it, and US-Style results are unchanged because the steady state keeps taking the terminal value:
def pension_amount(r, w, n, Y, theta, t, j, shift, method, e, factor, p):
if p.pension_system == "US-Style Social Security":
pension = SS_amount(...) # with its private copy removed
elif ...
adj = p.replacement_rate_adjust
idx = -1 if method == "SS" else t
scale = adj[idx, :] if j is None else adj[idx, j]
return pension * scale
happy to open a PR.
cc @jdebacker @rickecon
replacement_rate_adjustis a(T+S, J)multiplier on the pension replacement rate — the natural way to model a country that automatically cuts benefits as it ages.It only works under one of the four pension systems. Under the other three it is accepted, stored, and ignored.
The test. Same parameter, same call, two systems:
Why. In
ogcore/pensions.pythe parameter is read in six places, all insideSS_amount()(lines 159–199), which serves only"US-Style Social Security".DB_amount()(line 204),NDC_amount()(332) andPS_amount()(429) contain no reference to it.Why it matters. Nothing warns. A calibration that sets it under Defined Benefits looks calibrated and is not — the solved steady state is byte-identical to one that never set it (
pension/Y = 0.093040,theta = 0.0038901, both ways). This is the same trap asalpha_dbdefaulting to0.0, where switching to Defined Benefits without setting it pays zero pensions. A parameter that silently does nothing is worse than a missing one, because it produces a confident wrong answer.A concrete case. Japan's pension system has a macroeconomic slide that automatically reduces benefits to stay solvent — the MHLW 2024 actuarial review projects the replacement rate falling from 61.2% in FY2024 to 50.4% in FY2057. Without a working adjustment, the DB block pays a fixed replacement rate, so the pension bill rises mechanically with the old-age ratio: 12.6% of GDP against an actual 9.3% today. The calibration then has to choose between matching today's spending and matching the long run, when Japan's actual institution does both. Any country with a legislated benefit glide or a notional-account balancing mechanism hits this.
Suggested fix. Move the adjustment out of
SS_amountand intopension_amount, which already receivestandmethod. Every system then honours it, and US-Style results are unchanged because the steady state keeps taking the terminal value:happy to open a PR.
cc @jdebacker @rickecon