125. Social Security Trust Fund Depletion#

125.1. Overview#

The Social Security trustees project that the reserves of the Old-Age and Survivors Insurance trust fund will run out during \(2032\) [Board of Trustees, Federal Old-Age and Survivors Insurance and Federal Disability Insurance Trust Funds, 2026].

The program cannot borrow.

Unless Congress acts, current law then reduces every benefit by a common factor \(\phi_t \le 1\) that makes the program’s cash flow balance: a worker entitled to a scheduled benefit \(b^{sch}\) collects \(\phi_t b^{sch}\).

We call \(\phi_t\) the payable factor.

It is not a policy parameter that somebody chooses.

It is an equilibrium object, because the quantity it scales depends on decisions that workers take while looking ahead at it:

  • a worker who expects a smaller pension works longer and saves more

  • a worker inside the claiming window just before the cut has a reason to file early, while benefits are still paid in full

  • a worker who expects to live longer has a reason to file late, because the actuarial reward for waiting is fixed by statute

Those decisions determine taxable payroll and the scheduled benefit bill, which are exactly the two quantities that determine \(\phi_t\).

This lecture studies that fixed point, following Sargent and Shi [2026].

We write down the ledger that current law prescribes, define the map that sends a path for the payable factor into another path for the payable factor, and compute its fixed point in a quantitative overlapping generations model with

  • a life cycle from age \(20\) to age \(100\) with projected mortality

  • permanent heterogeneity in earnings ability and in the taste for work

  • the statutory benefit formula, the actuarial adjustment for the claim age, the taxable maximum, and the taxation of benefits

  • retirement and benefit claiming as separate irreversible decisions

The exercises then put the machinery to work: on the oscillation that a decreasing map produces, on raising the full retirement age, on what it would cost to guarantee scheduled benefits instead, and on how much of the long-run cut is the work of rising life expectancy.

Several symbols recur, so we record them here.

Symbol

Meaning

\(\phi_t\)

payable factor: the common fraction of scheduled benefits actually paid

\(F_t\), \(r^F\)

trust fund balance and the rate credited on it

\(N_t\)

population; every other aggregate is per capita

\(\mathit{Pay}_t\), \(B^{sch}_t\)

per capita taxable payroll and the per capita scheduled benefit bill

\(\tau^{ss}\), \(\kappa_b\), \(\bar y\)

payroll tax rate, tax rate on benefits received, taxable maximum

\(\mathrm{AIME}\), \(\mathrm{PIA}\), \(\psi\)

the earnings record, the benefit it buys, and the rate at which the record accrues

\(\mathrm{AF}(\chi)\), FRA, \(\varsigma\)

actuarial factor at claim age \(\chi\), the full retirement age, and a benefit scale

\(R\), \(\chi\)

a worker’s retirement age and claim age

\(j\), \(t\)

age index (\(0\) at age \(20\)) and calendar year

\(e\), \(h\)

permanent earnings ability and taste for work

\(\ell_j\), \(\Omega\)

survival from age \(20\) to age \(20+j\), and \(\sum_j \beta^j\ell_j\)

\(\tilde R\), \(a_0\), \(\mathcal W\)

after-tax gross return, assets on hand, and lifetime resources

This lecture is a companion to Two Computations to Fund Social Security, which studies two ways of moving from an unfunded to a fully funded system in the model of Huang et al. [1997].

Here nobody moves to a funded system: we ask what current law does on its own, and what it would cost to stop it.

Two other lectures supply background: Transitions in an Overlapping Generations Model introduces the Auerbach and Kotlikoff [1987] overlapping generations framework, and A Long-Lived, Heterogeneous Agents, Overlapping Generations Model adds uninsurable idiosyncratic risk to it.

We use the following imports.

import numpy as np
import matplotlib.pyplot as plt
from collections import namedtuple
from scipy.optimize import fsolve, brentq
Matplotlib is building the font cache; this may take a moment.

125.2. The institution#

Social Security’s ledger is a stock, \(F_t\), and two flows.

The fund earns an administered rate \(r^F\) on the special issue Treasury securities it holds, it receives the payroll tax, and it pays benefits.

Writing \(N_t\) for the population, \(\mathit{Pay}_t\) for per capita taxable payroll, and \(B^{sch}_t\) for the per capita scheduled benefit bill, the ledger evolves as

(125.1)#\[F_{t+1} = (1+r^F)F_t + N_t\left(\mathit{Rev}_t - \phi_t B^{sch}_t\right), \qquad F_t \ge 0 ,\]

where program revenue is the payroll tax plus the tax that returns to the program on the benefits it pays,

(125.2)#\[\mathit{Rev}_t = \tau^{ss}\mathit{Pay}_t + \kappa_b \phi_t B^{sch}_t .\]

The restriction \(F_t \ge 0\) in (125.1) is the law: the program may not borrow.

It is what forces \(\phi_t\) below one.

The program pays the largest common fraction of scheduled benefits that its resources allow,

(125.3)#\[\phi_t = \min\left\{1, \; \frac{(1+r^F)F_t + N_t\tau^{ss}\mathit{Pay}_t} {N_t(1-\kappa_b)B^{sch}_t}\right\} .\]

The numerator is what the program can draw on: the balance it carries in plus the payroll tax it collects.

The denominator is what it owes, net of the benefit tax that comes back to it.

We date depletion at the first year the fund is empty,

\[ t^{dep} = \min\{t : F_t = 0\} , \]

and we call \(t^{dep}-1\) the first shortfall year, the year in which the program pays out its last reserves and the first year in which \(\phi_t < 1\).

Note

The trustees date “reserve depletion” to the year the reserves run out, which is the first shortfall year here.

Their projected \(2032\) is therefore our \(t^{dep}-1\), and the first year in which the whole benefit is cut is \(2033\).

From \(t^{dep}\) on the fund is empty and (125.3) collapses to a cash flow condition,

(125.4)#\[\phi_t = \frac{\tau^{ss}\mathit{Pay}_t}{(1-\kappa_b)B^{sch}_t}, \qquad t \ge t^{dep} .\]

125.2.1. The payable factor is a fixed point#

Equation (125.4) looks like a formula for \(\phi_t\), but both \(\mathit{Pay}_t\) and \(B^{sch}_t\) are built from decisions that workers make while looking at the whole future path \(\{\phi_s\}\).

Let \(\Phi\) be the map that takes a candidate path \(\{\phi_s\}_{s\ge 0}\), hands it to workers, collects the taxable payroll and scheduled benefits that their optimal decisions generate, runs the ledger (125.1), and returns the payable path (125.3) that the ledger implies.

An equilibrium payable path is a fixed point,

(125.5)#\[\{\phi_t\} = \Phi\big(\{\phi_t\}\big) .\]

Important

\(\Phi\) is a decreasing map.

Raise the candidate path and benefits become more valuable, so workers claim earlier and retire earlier; taxable payroll falls and the scheduled bill rises; (125.3) then returns a smaller number.

Successive guesses therefore alternate around the fixed point rather than approaching it from one side.

Whether the alternation dies out depends on how strongly workers respond, and Exercise 125.1 finds dates at which it does not: the undamped iteration settles into a two-cycle and stays there.

Every computation below therefore takes a damped step.

One consequence of (125.4) is worth recording before we compute anything.

In a long run in which the fund is empty and the economy has settled down, the payable factor is pinned by the program’s cost rate, the ratio of scheduled benefits to taxable payroll:

(125.6)#\[\phi_\infty = \frac{\tau^{ss}}{(1-\kappa_b)}\cdot \frac{\mathit{Pay}_\infty}{B^{sch}_\infty} .\]

Everything demographic and behavioral that matters for the long-run benefit cut reaches it through that one ratio.

125.3. A model we can solve quickly#

Sargent and Shi [2026] compute general equilibrium transition paths of an economy with idiosyncratic earnings risk, a neoclassical firm, a consolidated government budget constraint, and a cross-section of \(80{,}000\) workers per age.

That calculation takes about \(22\) hours on an A100 GPU.

The model below keeps the institution exactly and strips the economy down until the whole fixed point runs in about a minute on a laptop.

What this lecture leaves out

  • Prices are fixed. The interest rate and the wage do not respond. In the paper, capital deepens as workers save against a smaller pension, the capital–output ratio rises from \(3.00\) to \(3.19\), and the interest rate falls from \(6.0\) to \(5.3\) percent.

  • There is no government budget constraint outside the program. In the paper, depletion removes the Social Security deficit from the consolidated budget, so the labor tax falls from \(0.220\) to \(0.190\): current law delivers a benefit cut to retirees and a tax cut to workers in the same year.

  • There is no idiosyncratic earnings risk and no borrowing constraint, so the life cycle has a closed form. Workers differ only in permanent ability and in their taste for work.

  • Annuity markets are complete, so there are no accidental bequests and no bequest motive.

  • Benefits are scaled rather than extended. The program’s spousal and survivor benefits go to additional beneficiaries; here a single scale factor on each worker’s own benefit matches the aggregate cost instead, which raises individual replacement rates by about a seventh.

  • Demography is stylized, a smooth birth path and a Gompertz mortality table calibrated to the trustees’ dependency ratios, rather than the published population projection.

What survives is the part this lecture is about: an endogenous payable factor, and claiming and retirement decisions that respond to it.

125.3.1. Parameters#

Units are the average wage index of \(2025\), so one model unit is \(\$72{,}025\) and mean earnings per worker are one.

The statutory amounts – the taxable maximum, the two bend points of the benefit formula, the payroll tax rate, the actuarial factors, the trust fund balance – take their legislated or recorded \(2025\) values.

The tax rates on labor, capital and consumption follow Kitao [2014], and the discount factor, the interest rate, the dispersion of earnings ability and the rate credited on the fund are those of Sargent and Shi [2026].

Two parameters are chosen inside this model, and we say which below.

AWI = 72025.07          # average wage index, 2025: dollars per model unit
AGE0, J = 20, 81        # ages 20 ... 100
Y0, T = 2026, 120       # first transition year, horizon
YEARS = np.arange(Y0, Y0+T+1)

Params = namedtuple('Params', [
    'β', 'η', 'r', 'τ_l', 'τ_k', 'τ_c', 'τ_ss', 'κ_b', 'r_F',
    'ycap', 'bend', 'prate', 'FRA', 'ψ', 'σ_a', 'σ_η', 'g_m', 'bscale', 'F_ratio'])

par = Params(
    β=0.9739,            # discount factor
    η=0.81,              # disutility of work (calibrated)
    r=0.060,             # interest rate
    τ_l=0.22, τ_k=0.30, τ_c=0.05,          # labor, capital, consumption taxes
    τ_ss=0.106,          # OASI payroll tax rate
    κ_b=0.03891,         # tax on benefits received
    r_F=0.02,            # rate credited on the trust fund
    ycap=176100/AWI,     # taxable maximum
    bend=np.array([1226.0, 7391.0])*12/AWI,   # PIA bend points
    prate=np.array([0.90, 0.32, 0.15]),       # PIA replacement rates
    FRA=67,              # full retirement age
    ψ=0.055,             # AIME accrual (a 35 year moving average)
    σ_a=0.315,           # dispersion of earnings ability
    σ_η=0.16,            # dispersion of the taste for work
    g_m=0.007,           # annual mortality improvement
    bscale=1.145,        # benefit scale ς (calibrated)
    F_ratio=1.6255)      # 2025 reserves relative to benefit payments

def after_tax_R(p):
    "The gross after-tax return on saving."
    return 1 + p.r*(1 - p.τ_k)

The benefit formula has two pieces.

The primary insurance amount is a concave, piecewise linear function of a worker’s average indexed earnings, with marginal replacement rates \(0.90\), \(0.32\) and \(0.15\) on the three segments.

The actuarial factor \(\mathrm{AF}(\chi)\) multiplies it, and depends on the age \(\chi\) at which the worker files: the statutory rule reduces benefits by \(5/9\) of one percent per month for the first \(36\) months of early claiming and \(5/12\) of one percent per month beyond that, and adds a delayed retirement credit of \(8\) percent a year after the full retirement age.

def claim_factors(fra=67, lo=62, hi=70):
    "Statutory actuarial factors AF(χ) for claim ages lo ... hi."
    out = []
    for a in range(lo, hi+1):
        if a <= fra:
            m = (fra - a)*12
            out.append(round(1 - (min(m, 36)*(5/9) + max(m-36, 0)*(5/12))/100, 4))
        else:
            out.append(round(1 + 0.08*(a - fra), 4))
    return np.array(out)

def pia(aime, p):
    "The primary insurance amount: a concave piecewise-linear function of AIME."
    s0 = np.minimum(aime, p.bend[0])
    s1 = np.clip(aime - p.bend[0], 0.0, p.bend[1] - p.bend[0])
    s2 = np.maximum(aime - p.bend[1], 0.0)
    return p.prate[0]*s0 + p.prate[1]*s1 + p.prate[2]*s2

X_GRID = np.arange(62, 71)
print("claim age     ", X_GRID)
print("AF(χ) at FRA 67", claim_factors(67))
print("AF(χ) at FRA 69", claim_factors(69))
claim age      [62 63 64 65 66 67 68 69 70]
AF(χ) at FRA 67 [0.7    0.75   0.8    0.8667 0.9333 1.     1.08   1.16   1.24  ]
AF(χ) at FRA 69 [0.6    0.65   0.7    0.75   0.8    0.8667 0.9333 1.     1.08  ]

Fig. 125.1 shows the progressivity of the program on the left and its price of waiting on the right.

fig, axes = plt.subplots(1, 2, figsize=(11, 4))

grid = np.linspace(0, 3, 400)
axes[0].plot(grid, pia(grid, par), lw=2)
for b in par.bend:
    axes[0].axvline(b, color='k', ls=':', lw=1)
axes[0].set_xlabel('AIME, multiples of the average wage')
axes[0].set_ylabel('primary insurance amount')
axes[0].set_title('the benefit formula, with its bend points')

axes[1].plot(X_GRID, claim_factors(67), 'o-', lw=2, label='full retirement age 67')
axes[1].plot(X_GRID, claim_factors(69), 's-', lw=2, label='full retirement age 69')
axes[1].axhline(1.0, color='k', lw=0.8)
axes[1].set_xlabel('claim age $\\chi$')
axes[1].set_ylabel('actuarial factor $\\mathrm{AF}(\\chi)$')
axes[1].set_title('the reward for waiting')
axes[1].legend()
plt.tight_layout()
plt.show()
_images/e1a304e6feb192332da8f8d5b65f0c81a52c9855236c9cfba4369ec9aa70ac00.png

Fig. 125.1 The benefit formula is progressive and the reward for waiting is statutory#

A worker whose earnings record is below the first bend point replaces \(90\) cents of every dollar; one above the second replaces \(15\).

Waiting from \(62\) to \(70\) raises the benefit by \(77\) percent, and the schedule is statutory: it does not move when mortality improves, and it does not move when the payable factor falls.

Exercise 125.2 raises the full retirement age, which is the orange line.

125.3.2. Demography#

Mortality follows a Gompertz–Makeham force of mortality that improves at \(0.7\) percent a year until \(2085\) and is frozen afterwards, so that the economy settles into a stationary equilibrium inside the horizon.

Cohort sizes grow at one rate before \(1990\) and another after, and we pick the two rates so that the model reproduces the trustees’ aged dependency ratio in \(2025\) and in \(2085\): the population aged \(65\) and over per person aged \(20\) to \(64\) rises from \(0.32\) to \(0.50\).

def mortality(age, year, p):
    "Force of mortality by age and calendar year; improvement stops in 2085."
    yr = np.minimum(year, 2085)
    return (5e-4 + 2.5e-5*np.exp(0.095*age))*np.exp(-p.g_m*(yr - 2025))

def cohort_survival(entry_years, p):
    "ℓ[c, j]: probability of reaching age 20+j, cohort by cohort."
    ent = np.asarray(entry_years, float).reshape(-1, 1)
    j = np.arange(J).reshape(1, -1)
    s = np.exp(-mortality(AGE0 + j, ent + j, p))
    s[:, -1] = 0.0
    return np.concatenate([np.ones((len(ent), 1)),
                           np.cumprod(s[:, :-1], axis=1)], axis=1)

YS = 1990          # the year the growth rate of entering cohorts changes

def entry_sizes(g, years):
    "Size of the cohort that turns 20 in each year."
    y = np.asarray(years, float)
    return np.where(y <= YS, np.exp(g[0]*(y - YS)), np.exp(g[1]*(y - YS)))

def population(g, years, p):
    "N[j, t]: people of age 20+j in each calendar year."
    years = np.atleast_1d(years)
    out = np.zeros((J, len(years)))
    for j in range(J):
        ent = years - j
        out[j] = entry_sizes(g, ent)*cohort_survival(ent, p)[:, j]
    return out

def dependency(g, t, p):
    "People aged 65 and over per person aged 20 to 64."
    n = population(g, [t], p)[:, 0]
    return n[45:].sum()/n[:45].sum()

g_pop = fsolve(lambda g: [dependency(g, 2025, par) - 0.32,
                          dependency(g, 2085, par) - 0.50], [0.02, 0.0])

print(f"entry cohorts grow at {g_pop[0]:.4f} before {YS} and {g_pop[1]:.4f} after")
for t in (2025, 2035, 2055, 2085):
    print(f"   dependency ratio in {t}: {dependency(g_pop, t, par):.3f}")
print(f"   life expectancy at 20 of the cohort of 2026: "
      f"{20 + cohort_survival([2026], par)[0].sum():.1f}")
entry cohorts grow at 0.0103 before 1990 and -0.0020 after
   dependency ratio in 2025: 0.320
   dependency ratio in 2035: 0.370
   dependency ratio in 2055: 0.449
   dependency ratio in 2085: 0.500
   life expectancy at 20 of the cohort of 2026: 84.3

Fig. 125.2 is the engine of everything that follows, so it is worth looking at.

span = np.arange(2000, 2121)
fig, axes = plt.subplots(1, 2, figsize=(11, 4))

axes[0].plot(span, [dependency(g_pop, t, par) for t in span], lw=2)
axes[0].plot([2025, 2085], [0.32, 0.50], 'o', ms=7, color='C1',
             label='trustees')
axes[0].set_xlabel('year')
axes[0].set_ylabel('people 65 and over per person 20 to 64')
axes[0].legend()

for t, style in ((2026, '-'), (2085, '--')):
    n_t = population(g_pop, [t], par)[:, 0]
    axes[1].plot(AGE0 + np.arange(J), n_t/n_t.sum(), style, lw=2, label=str(t))
axes[1].set_xlabel('age')
axes[1].set_ylabel('share of the population')
axes[1].legend()
plt.tight_layout()
plt.show()
_images/e3fd90ec69e8c214154701345a6055b2daffaa7aa1938baaa206927522a3537f.png

Fig. 125.2 Cohorts stopped growing and lives keep lengthening#

Two forces push the dependency ratio up, and the model carries both.

Cohorts entering the labor force stopped growing, so the base of the pyramid stops widening.

Mortality keeps improving, so its top keeps filling in.

Exercise 125.4 separates them.

125.3.3. Earnings and types#

Earnings are the wage times a hump shaped age-efficiency profile that peaks at age \(50\), times a permanent ability draw.

Workers also differ in their taste for work.

Both are five point discretizations of lognormal distributions, so there are \(25\) types.

def efficiency():
    "Age-efficiency profile, normalized to mean one over ages 20 to 64."
    j = np.arange(J)
    e = np.exp(0.0583*j - 0.000972*j**2)
    return e/e[:45].mean()

ε = efficiency()

def types(p, n=5):
    "Permanent earnings ability and taste for work, on a tensor grid."
    x, w = np.polynomial.hermite_e.hermegauss(n)
    w = w/w.sum()
    e = np.exp(p.σ_a*x); e = e/(w*e).sum()
    h = np.exp(p.σ_η*x)
    E, H = np.meshgrid(e, h, indexing='ij')
    return E.ravel(), H.ravel(), np.outer(w, w).ravel()

Earnings rise with experience, peak at age \(50\), and decline thereafter.

fig, ax = plt.subplots(figsize=(7, 3.6))
ax.plot(AGE0 + np.arange(J), ε, lw=2)
ax.set_xlabel('age')
ax.set_ylabel('efficiency units of labor')
ax.set_xlim(20, 80)
plt.tight_layout()
plt.show()
_images/48626c6e197b498aabb17e95d96604523a5d731646ecb76934ab605c536d4a3a.png

Fig. 125.3 Earnings peak at age 50 and decline thereafter#

125.3.4. The worker’s problem#

A worker chooses when to stop working and when to file for benefits.

Both decisions are irreversible, and they are separate: Coile et al. [2002] and the literature that follows them establish that leaving the labor force and claiming a benefit are distinct events.

Write \(R\) for the retirement age, \(\chi\) for the claim age, and \(\ell_j\) for the probability of surviving from age \(20\) to age \(20+j\).

A worker of type \((e, h)\) ranks plans by

(125.7)#\[\sum_{j} \beta^j \ell_j \big[\log c_j - \eta\, h\, n_j \big], \qquad n_j = \mathbf 1\{j < R - 20\} ,\]

where \(e\) is permanent earnings ability, \(h\) is the taste for work, and \(\eta h\) is therefore the worker’s own disutility of a year of work.

We normalize the wage to one.

While working, a worker earns \(y_j = \varepsilon_j e\), pays the labor tax and the payroll tax on earnings up to the taxable maximum \(\bar y\), and accumulates the earnings record

\[ \mathrm{AIME}_{j+1} = \mathrm{AIME}_j + \psi\,\mathbf 1\{j < R-20\}\big(\min(y_j, \bar y) - \mathrm{AIME}_j\big), \]

an exponentially weighted moving average whose effective window of \(2/\psi - 1 \approx 35\) years approximates the statutory highest-35-years rule.

Once filed, the worker collects

\[ b_j = \varsigma\,\phi_t\, \mathrm{AF}(\chi)\,\mathrm{PIA}(\mathrm{AIME}_j) , \]

taxed at \(\kappa_b\), where \(\varsigma\) is a benefit scale that the calibration below sets.

It stands in for the spousal and survivor benefits that a model of single workers leaves out.

With complete annuity markets and log utility the consumption problem has a closed form.

Annuities pay the gross return \(\tilde R/s_j\) to a survivor, where \(\tilde R = 1 + r(1-\tau_k)\) and \(s_j\) is the one-period survival probability, because the balances of those who die are shared among those who live.

Compounding those returns turns survival into a discount factor: a unit of income received at age \(20+j\) is worth \(\ell_j \tilde R^{-j}\) at age \(20\), and the lifetime budget constraint is

(125.8)#\[(1+\tau_c)\sum_j \ell_j \tilde R^{-j} c_j = a_0 + \sum_j \ell_j \tilde R^{-j} m_j(R, \chi) \equiv \mathcal W(R,\chi) ,\]

where \(m_j\) is net income at age \(j\): earnings net of the two taxes while working, and the payable benefit net of its own tax once filed.

Because the annuity return and the survival probability cancel, the Euler equation is the one from a model without mortality,

\[ c_j = c_0 (\beta \tilde R)^j . \]

Substituting it into (125.8) gives \((1+\tau_c)c_0\Omega = \mathcal W\) with \(\Omega = \sum_j \beta^j\ell_j\), and substituting it into (125.7) gives

\[ \sum_j \beta^j \ell_j \log c_j = \Omega \log c_0 + \log(\beta\tilde R)\sum_j j\,\beta^j \ell_j . \]

The second term does not depend on the plan, and neither does the \(\log[(1+\tau_c)\Omega]\) that comes out of \(c_0 = \mathcal W/[(1+\tau_c)\Omega]\).

Dropping both leaves a two-dimensional discrete choice:

(125.9)#\[\max_{R,\;\chi}\;\; \Omega \log \mathcal W(R,\chi) \;-\; \eta\, h \sum_{j < R-20} \beta^j \ell_j .\]

The first term is the present value of everything the plan delivers, including the whole future path of the payable factor.

The second is the cost of the extra years of work.

A worker who is already alive when the news arrives solves the same problem from where it stands.

Write \(j_0\) for its age index in \(2026\).

Then both sums in (125.9) run from \(j_0\) rather than from \(0\), with \(\ell_j\) and \(\beta^j\) measured from \(j_0\); \(a_0\) in (125.8) is the wealth it has accumulated by then; and the decisions it has already taken are not up for reconsideration.

That is what the arguments j0, a0 and plan0 carry in the code below.

Nothing there is special to \(2026\): Exercise 125.5 uses the same code to let workers re-plan in the year a surprise arrives.

Note

The paper also models the retirement earnings test, which withholds the benefits of a claimant who keeps working before the full retirement age and then credits them back by advancing the effective claim age.

For a worker who leaves work at or before the full retirement age the credit matches the withholding one for one, so filing earlier than retirement is payoff neutral in this deterministic model.

We therefore impose \(\chi \ge \min(R, 70)\) and drop the test.

Claiming remains a separate decision: a worker may file at retirement or wait, up to age \(70\).

The restriction does bind on one margin.

A worker who means to work past the full retirement age could file at \(62\), have the benefits withheld until the test stops applying, and then collect at the credited factor while still working.

That option is not quite equivalent to filing later, and this model rules it out.

We enumerate the admissible plans and index every cohort alive along the transition.

R_GRID = np.arange(58, 73)
# a worker files no earlier than it stops working, and no later than 70
PLANS = np.array([(R, X) for R in R_GRID for X in X_GRID if X >= min(R, 70)])
R_P, X_P = PLANS[:, 0], PLANS[:, 1]
NP = len(PLANS)

ENTRY = np.arange(Y0-J+1, Y0+T+1)      # entry year of every cohort ever alive
NC = len(ENTRY)
ENT_IX = {y: i for i, y in enumerate(ENTRY)}
IDX = np.array([[ENT_IX[t-j] for t in YEARS] for j in range(J)])

print(f"{NP} admissible plans, {NC} cohorts")
83 admissible plans, 201 cohorts

The retirement grid runs from \(58\) to \(72\), and it binds for the two most extreme tastes for work, which carry \(1.1\) percent of the population each.

Widening it to \(50\) to \(80\) moves those two types to \(56\) and \(73\) and moves nothing else: the depletion year, both mean ages, and the payable factor to four decimals are the same.

We keep the narrow grid, which is about twice as fast.

Everything that does not depend on the payable path is built once.

class World:
    "Demography, types, survival and discounting: fixed along the iteration."

    def __init__(self, p, g):
        self.p = p
        e, h, w = types(p)
        self.NE = len(e)
        self.C = NC*self.NE
        self.entry = np.repeat(ENTRY, self.NE)
        self.e = np.tile(e, NC)
        self.η = np.tile(h, NC)
        self.w = np.tile(w, NC)
        self.j0 = np.clip(Y0 - self.entry, 0, J-1)      # age index in 2026
        self.ell = np.repeat(cohort_survival(ENTRY, p), self.NE, axis=0)
        self.disc = self.ell/after_tax_R(p)**np.arange(J)
        self.AF = claim_factors(p.FRA)
        bet_ell = (p.β**np.arange(J))*self.ell
        self.tail = np.cumsum(bet_ell[:, ::-1], axis=1)[:, ::-1]
        self.pre = np.concatenate([np.zeros((self.C, 1)),
                                   np.cumsum(bet_ell, axis=1)], axis=1)
        self.N = population(g, YEARS, p)
        self.Npop = self.N.sum(axis=0)
        self.share = self.N/self.Npop

def phi_of(φ, year):
    "The payable factor: one before 2026, constant beyond the horizon."
    k = np.clip(year - Y0, 0, len(φ)-1)
    return np.where(year < Y0, 1.0, np.asarray(φ)[k])

The life cycle itself is one loop over ages.

def lifecycle(W, φ, Rj, Xa, j0, disc, keep=False):
    """Discounted net income from age index j0 on, for plans (Rj, Xa).

    With keep=True also returns taxable payroll and scheduled benefits at
    every age, which is what aggregation needs.
    """
    p = W.p
    nd = np.ndim(Rj)
    e_row = W.e.reshape(-1, *[1]*(nd-1))
    ent = W.entry.reshape(-1, *[1]*(nd-1))
    shape = np.broadcast(Rj, Xa, e_row).shape
    aime = np.zeros(shape)
    S = np.zeros(shape)
    AFx = np.interp(Xa, X_GRID, W.AF)*np.ones(shape)
    pay, ben = [], []
    for j in range(J):
        work = (j < Rj)*np.ones(shape)
        y = ε[j]*e_row*work
        capped = np.minimum(y, p.ycap)*work
        b = p.bscale*AFx*pia(aime, p)*((AGE0 + j >= Xa)*np.ones(shape))
        inc = ((1-p.τ_l)*y - p.τ_ss*capped
               + (1-p.κ_b)*phi_of(φ, ent + j)*b)
        S = S + np.where(j >= j0, disc[..., j]*inc, 0.0)
        if keep:
            pay.append(capped)
            ben.append(b)
        aime = aime + p.ψ*work*(capped - aime)
    return (S, np.array(pay), np.array(ben)) if keep else S

def solve_plans(W, φ, j0, plan0=None, a0=0.0, at_year=Y0):
    """The plan that maximizes the worker's objective, for every cohort and type.

    Workers re-plan in at_year, from the age index j0 they have reached, holding
    the wealth a0 they carry and the decisions in plan0 they cannot undo.
    """
    p, C = W.p, W.C
    j0 = np.asarray(j0)
    S = lifecycle(W, φ, (R_P-AGE0).reshape(1, NP), X_P.reshape(1, NP),
                  j0.reshape(C, 1), W.disc.reshape(C, 1, J))
    scale = (after_tax_R(p)**j0)/W.ell[np.arange(C), j0]
    wealth = a0 + S*scale.reshape(C, 1)
    norm = (p.β**j0)*W.ell[np.arange(C), j0]
    Om = W.tail[np.arange(C), j0]/norm
    Dw = (W.pre[np.arange(C)[:, None], np.clip(R_P-AGE0, 0, J)[None, :]]
          - W.pre[np.arange(C), j0][:, None])/norm[:, None]
    V = (Om[:, None]*np.log(np.maximum(wealth, 1e-9))
         - (p.η*W.η)[:, None]*np.maximum(Dw, 0.0))
    if plan0 is not None:
        # decisions already executed by at_year cannot be undone
        R0, X0 = plan0
        age_now = np.asarray(j0) + AGE0
        alive = W.entry < at_year
        ok = np.where((alive & (R0 <= age_now))[:, None],
                      R_P[None, :] == R0[:, None],
                      R_P[None, :] >= np.where(alive, age_now, 0)[:, None])
        ok &= np.where((alive & (X0 <= age_now))[:, None],
                       X_P[None, :] == X0[:, None],
                       X_P[None, :] >= np.where(alive, np.maximum(62, age_now), 62)[:, None])
        V = np.where(ok, V, -np.inf)
    k = V.argmax(axis=1)
    return R_P[k], X_P[k], wealth[np.arange(C), k], Om, Dw[np.arange(C), k]

def flows(W, φ, R, X, j0):
    "Taxable payroll and scheduled benefits at the chosen plans, shape (J, C)."
    _, pay, ben = lifecycle(W, φ, (R-AGE0), X, np.asarray(j0), W.disc, keep=True)
    return pay, ben

125.4. Aggregation and the ledger#

Aggregates are population weighted averages across ages, and the operator \(\Phi\) is the fund law (125.1) together with (125.3).

def aggregate(W, pay, ben):
    "Per capita taxable payroll and scheduled benefits in each calendar year."
    wt = W.w.reshape(NC, W.NE)[0]           # the type weights, tiled across cohorts
    Pj = pay.reshape(J, NC, W.NE) @ wt
    Bj = ben.reshape(J, NC, W.NE) @ wt
    Pay = (np.take_along_axis(Pj, IDX, axis=1)*W.share).sum(axis=0)
    Ben = (np.take_along_axis(Bj, IDX, axis=1)*W.share).sum(axis=0)
    return Pay, Ben

def fund_and_phi(W, Pay, Ben, F0):
    "The ledger and the payable factor: one pass of the operator Φ."
    p = W.p
    F = np.zeros(len(YEARS)+1)
    F[0] = F0
    φ = np.ones(len(YEARS))
    for t in range(len(YEARS)):
        obligations = W.Npop[t]*(1-p.κ_b)*Ben[t]
        resources = (1+p.r_F)*F[t] + W.Npop[t]*p.τ_ss*Pay[t]
        φ[t] = min(1.0, resources/obligations) if obligations > 0 else 1.0
        rev = p.τ_ss*Pay[t] + p.κ_b*φ[t]*Ben[t]
        F[t+1] = max((1+p.r_F)*F[t] + W.Npop[t]*(rev - φ[t]*Ben[t]), 0.0)
    return F, φ

125.5. The economy before the news#

Workers alive in \(2026\) made their plans expecting benefits to be paid in full forever.

We solve that economy first.

It supplies two things that the transition needs: the retirement and claim decisions that cohorts have already executed by \(2026\), and the assets they carry into \(2026\).

It also supplies the scale for the third, the opening trust fund balance, which we set below to the recorded end-of-\(2025\) reserves of \(1.63\) times annual benefit payments.

def income_path(W, φ, R, X, rows=slice(None)):
    "Net income at every age at the chosen plans, for all rows or a subset."
    p = W.p
    e, entry, R, X = W.e[rows], W.entry[rows], R[rows], X[rows]
    aime = np.zeros(len(e))
    out = np.zeros((J, len(e)))
    AFx = np.interp(X, X_GRID, W.AF)
    for j in range(J):
        work = (j < R - AGE0).astype(float)
        y = ε[j]*e*work
        capped = np.minimum(y, p.ycap)*work
        b = p.bscale*AFx*pia(aime, p)*((AGE0 + j >= X).astype(float))
        out[j] = ((1-p.τ_l)*y - p.τ_ss*capped
                  + (1-p.κ_b)*phi_of(φ, entry + j)*b)
        aime = aime + p.ψ*work*(capped - aime)
    return out

def pre_transition(W):
    "Plans, assets at 2026, and aggregates when φ ≡ 1 forever."
    p = W.p
    φ1 = np.ones(T+1)
    z = np.zeros(W.C, int)
    R, X, wealth, Om, _ = solve_plans(W, φ1, z)
    pay, ben = flows(W, φ1, R, X, z)
    Pay, Ben = aggregate(W, pay, ben)
    c0 = wealth/((1+p.τ_c)*Om)
    inc = income_path(W, φ1, R, X)
    a = np.zeros(W.C)
    A = np.zeros((J, W.C))
    for j in range(J-1):
        A[j] = a
        s = W.ell[:, j+1]/np.maximum(W.ell[:, j], 1e-12)
        a = (after_tax_R(p)/np.maximum(s, 1e-12))*(
            a + inc[j] - (1+p.τ_c)*c0*(p.β*after_tax_R(p))**j)
    A[J-1] = a
    return R, X, A[W.j0, np.arange(W.C)], Pay, Ben, wealth, Om

W = World(par, g_pop)
R_pre, X_pre, a26, Pay_pre, Ben_pre, wealth_pre, Om_pre = pre_transition(W)

mask = W.entry == 2026
wts = W.w[mask]/W.w[mask].sum()
print(f"cohort entering in 2026:  mean retirement age {(R_pre[mask]*wts).sum():.2f}, "
      f"mean claim age {(X_pre[mask]*wts).sum():.2f}")
print(f"cost rate in 2026: {Ben_pre[0]/Pay_pre[0]:.4f}"
      f"   (trustees for 2025: 0.1362)")
cohort entering in 2026:  mean retirement age 64.76, mean claim age 66.49
cost rate in 2026: 0.1350   (trustees for 2025: 0.1362)

Two parameters were chosen to make those two numbers come out.

The disutility of work \(\eta\) sets the retirement age: the cohort entering in \(2026\) retires at \(64.8\) on average, against the median retirement age of \(64.5\) that Sargent and Shi [2026] target.

The benefit scale \(\varsigma\) sets the cost rate, and brings it within about a tenth of a percentage point of the trustees’ \(2025\) figure.

The claim age is not targeted, and the model misses it by more: this cohort files at \(66.5\) on average, a year and a half later than the \(65.0\) the paper matches.

Claiming later than the data is a bias to keep in mind when reading the transition below, where the claim age is one of the two margins doing the work.

Two more statistics that nothing was fitted to are worth printing, because both bear on how hard the model pushes the two margins.

def replacement_rate(W, R, X):
    "The first benefit received, relative to earnings in the last year of work."
    p = W.p
    aime, last, first = (np.zeros(W.C) for _ in range(3))
    AFx = np.interp(X, X_GRID, W.AF)
    for j in range(J):
        work = (j < R - AGE0).astype(float)
        y = ε[j]*W.e*work
        capped = np.minimum(y, p.ycap)*work
        last = np.where(R - AGE0 - 1 == j, y, last)
        first = np.where(X - AGE0 == j, p.bscale*AFx*pia(aime, p), first)
        aime = aime + p.ψ*work*(capped - aime)
    return first/np.maximum(last, 1e-12)

rr = (replacement_rate(W, R_pre, X_pre)[mask]*wts).sum()
print(f"replacement rate, cohort entering in 2026:      {rr:.3f}")
print(f"   at the statutory formula, without ς:         {rr/par.bscale:.3f}")

shares = {int(a): float(W.w[mask][X_pre[mask] == a].sum()/W.w[mask].sum())
          for a in X_GRID}
print("   claim ages chosen:",
      {a: round(v, 3) for a, v in shares.items() if v > 0.005})
replacement rate, cohort entering in 2026:      0.492
   at the statutory formula, without ς:         0.430
   claim ages chosen: {66: 0.767, 68: 0.222, 70: 0.009}

The first number exposes something that the benefit scale \(\varsigma\) does quietly.

Spousal and survivor benefits are paid to additional people; here the whole \(14.5\) percent lands on each worker’s own benefit.

So a worker in this model collects about a seventh more than the statutory formula would pay it, and the incentives to retire and to file are correspondingly stronger.

Matching the program’s aggregate cost with a model of single workers has to distort something, and this is where it goes.

The second number exposes the other limitation.

Claiming piles up on a single age, and nobody files at \(62\).

In the data, and in the paper, there are large spikes at \(62\) and at the full retirement age; the forces that produce the spike at \(62\) – liquidity constraints, health, the bad earnings draw that ends a career – are exactly the ones this version leaves out.

125.6. Solving for the equilibrium payable path#

Now give workers the news.

Starting from \(\phi_t \equiv 1\), we apply \(\Phi\), take a damped step toward the result, and repeat.

The step length starts at one half and anneals toward a floor, as in the solver of Sargent and Shi [2026].

def transition(W, F0, plan0, a0, iters=40, tol=1e-4,
               ν0=0.5, anneal=0.92, floor=0.08, verbose=True):
    "Damped iteration on the payable path."
    φ = np.ones(T+1)
    for k in range(iters):
        R, X, _, _, _ = solve_plans(W, φ, W.j0, plan0=plan0,
                                    a0=a0.reshape(W.C, 1))
        pay, ben = flows(W, φ, R, X, W.j0)
        Pay, Ben = aggregate(W, pay, ben)
        F, φ_new = fund_and_phi(W, Pay, Ben, F0)
        gap = np.abs(φ_new - φ).max()
        ν = max(ν0*anneal**k, floor)
        φ = (1-ν)*φ + ν*φ_new
        if verbose and k % 5 == 0:
            print(f"   iteration {k:>3}   step {ν:.3f}   gap {gap:.2e}"
                  f"   φ in 2033 {φ[2033-Y0]:.4f}")
        if gap < tol:
            break
    return dict(φ=φ, F=F, R=R, X=X, Pay=Pay, Ben=Ben)

F0 = par.F_ratio*W.Npop[0]*Ben_pre[0]
base = transition(W, F0, (R_pre, X_pre), a26)
   iteration   0   step 0.500   gap 5.23e-01   φ in 2033 0.9725
   iteration   5   step 0.330   gap 4.31e-02   φ in 2033 0.7717
   iteration  10   step 0.217   gap 1.51e-02   φ in 2033 0.7578
   iteration  15   step 0.143   gap 1.72e-02   φ in 2033 0.7560
   iteration  20   step 0.094   gap 1.18e-02   φ in 2033 0.7555
   iteration  25   step 0.080   gap 1.44e-02   φ in 2033 0.7553
   iteration  30   step 0.080   gap 1.41e-02   φ in 2033 0.7551
   iteration  35   step 0.080   gap 1.48e-02   φ in 2033 0.7550

The converged path gives the two dates and the size of the cut.

φ, F = base['φ'], base['F']
depletion = YEARS[np.argmax(F[:-1] <= 1e-12)]
shortfall = YEARS[np.argmax(φ < 1)]
print(f"first shortfall year: {shortfall}")
print(f"depletion year:       {depletion}")
print(f"φ in {shortfall}: {φ[shortfall-Y0]:.3f},  in {depletion}: {φ[depletion-Y0]:.3f},"
      f"  in the long run: {φ[-1]:.3f}")
first shortfall year: 2032
depletion year:       2033
φ in 2032: 0.952,  in 2033: 0.755,  in the long run: 0.482

The dates are the trustees’ dates, and the first full-year cut is close to theirs: they project that continuing income will cover about \(78\) percent of scheduled benefits in the first year the fund is empty.

Neither date was targeted.

The calibration fixed the cost rate of a single year, \(2025\), and took the opening balance from the record; the year in which that balance runs out is then whatever the cash flows of the intervening years make it, and those depend on the claiming and retirement decisions that the falling payable factor itself provokes.

fig, axes = plt.subplots(1, 2, figsize=(11, 4))
axes[0].plot(YEARS, F[:-1]/(W.Npop*base['Ben']), lw=2)
axes[0].set_title('trust fund reserves / annual scheduled benefits')
axes[1].plot(YEARS, φ, lw=2)
axes[1].set_title('payable factor $\\phi_t$')
axes[1].set_ylim(0, 1.05)
for ax in axes:
    ax.axvline(depletion, color='k', ls='--', lw=1, alpha=0.7)
    ax.set_xlabel('year')
    ax.set_xlim(Y0, 2120)
plt.tight_layout()
plt.show()
_images/dc44c13339ea3f20124ceacc70faf0cacb0e51d6e1115654595852249400bc8f.png

Fig. 125.4 The reserves run out in 2032 and the payable factor keeps falling after the cut#

The payable factor does not settle at the level it reaches on the day the fund empties.

It falls further for the rest of the century, because the dependency ratio keeps rising.

By (125.6) the long-run cut is governed entirely by the program’s cost rate, and we can check that identity on the computed path.

cost = base['Ben']/base['Pay']
print(f"terminal cost rate: {cost[-1]:.4f}")
print(f"τ_ss/[(1-κ_b)·cost rate] = {par.τ_ss/((1-par.κ_b)*cost[-1]):.4f}"
      f"   against φ_∞ = {φ[-1]:.4f}")
terminal cost rate: 0.2291
τ_ss/[(1-κ_b)·cost rate] = 0.4814   against φ_∞ = 0.4816

125.7. Claiming and retirement along the transition#

Both decisions respond to the path of \(\phi_t\), and both reverse direction at the cut.

def flow_ages(W, R, X):
    "Population weighted mean age of new retirees and of new claimants."
    wt = W.w.reshape(NC, W.NE)[0]
    age = (AGE0 + np.arange(J)).reshape(J, 1, 1)
    hitR = (R.reshape(1, NC, W.NE) == age)*wt
    hitX = (X.reshape(1, NC, W.NE) == age)*wt
    nR = np.take_along_axis(hitR.sum(axis=2), IDX, axis=1)*W.share
    nX = np.take_along_axis(hitX.sum(axis=2), IDX, axis=1)*W.share
    ages = (AGE0 + np.arange(J)).reshape(J, 1)
    return ((nR*ages).sum(axis=0)/np.maximum(nR.sum(axis=0), 1e-12),
            (nX*ages).sum(axis=0)/np.maximum(nX.sum(axis=0), 1e-12))

mean_R, mean_X = flow_ages(W, base['R'], base['X'])

The two ages move together, and both turn at the cut.

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(YEARS, mean_R, lw=2, label='mean age of new retirees')
ax.plot(YEARS, mean_X, lw=2, label='mean age of new claimants')
ax.axvline(depletion, color='k', ls='--', lw=1, alpha=0.7)
ax.set_xlabel('year')
ax.set_ylabel('age')
ax.set_xlim(Y0, 2075)
ax.legend()
plt.tight_layout()
plt.show()
_images/b9853314eaecf5072f96d30ca19fad3ac59f024ed4bd777a86ee1f93733434e1.png

Fig. 125.5 Claiming and retirement rush forward before the cut and reverse after it#

As the fund runs down there is a rush.

Both ages fall year after year from \(2026\), and the mean age of new claimants reaches \(62.2\) in the first year the fund is empty, against \(64.7\) at the start.

Workers inside the claiming window file as early as they can, because filing fixes the actuarial factor while the payments made in those years are still paid in full or nearly so.

A worker who waits instead collects under the reduced factors of later years.

The year after, the pattern reverses: there is nothing left to rush for, and both ages jump back above \(64.5\) and then drift up, to \(65.3\) and \(66.6\) in the long run.

The pension is smaller, so careers lengthen; and mortality keeps improving while the actuarial schedule stays at its statutory values, so a later claim buys the higher benefit for more years.

Past about \(2075\) the mean claim age chatters between two adjacent integers, which is why Fig. 125.5 stops there; the note on computation below explains why.

fig, axes = plt.subplots(1, 2, figsize=(11, 4))
axes[0].plot(YEARS, base['Pay'], lw=2, label='taxable payroll')
axes[0].plot(YEARS, base['Ben'], lw=2, label='scheduled benefits')
axes[0].legend()
axes[0].set_title('per capita, model units')
axes[1].plot(YEARS, cost, lw=2)
axes[1].set_title('cost rate: scheduled benefits / taxable payroll')
for ax in axes:
    ax.axvline(depletion, color='k', ls='--', lw=1, alpha=0.7)
    ax.set_xlabel('year')
    ax.set_xlim(Y0, 2120)
plt.tight_layout()
plt.show()
_images/5db70d92a636d536d89041490bf2c750a794af22c802bd11cd804a2c684349ad.png

Fig. 125.6 Payroll gains arrive at once, the benefits of later claiming decades later#

The benefit formula runs on two clocks, and the right panel of Fig. 125.6 shows both.

Longer careers add contributors to taxable payroll immediately.

Later filing lowers the scheduled bill immediately, by reducing the number of claimants, and raises it only decades later, when the delayed filers collect benefits carrying higher actuarial factors.

The cost rate therefore bumps up in the years before the cut, as the rush adds claimants, and then falls back for a year or two afterwards, as filing is deferred again, before resuming its climb.

Once the fund is empty, (125.4) makes the payable factor exactly proportional to the inverse of the cost rate, so that dip is a benefit cut that does not happen.

At the cost rate of \(2031\) the formula would pay \(\phi = 0.702\); what workers’ own responses deliver in \(2033\) is \(0.755\).

Sargent and Shi [2026] find the asymmetry strong enough that the payable factor recovers for a few years after the cut before resuming its decline.

Here the recovery shows up in the cost rate but not in \(\phi_t\) itself: the dip is not large enough to outrun a dependency ratio that is still rising.

125.7.1. Who rushes#

The flow means hide who is doing the rushing, so it is worth looking at the decisions one cohort at a time.

Fig. 125.7 plots, against a worker’s age in \(2026\), the plan it had made before the news and the plan it chooses after.

ages26 = np.arange(21, 91)

def by_age26(vals):
    "Weighted mean of a decision across the types of each cohort alive in 2026."
    out = np.zeros(len(ages26))
    for i, a in enumerate(ages26):
        m = W.entry == Y0 - (a - AGE0)
        out[i] = (vals[m]*W.w[m]).sum()/W.w[m].sum()
    return out

fig, axes = plt.subplots(1, 2, figsize=(11, 4))
for ax, before, after, title in (
        (axes[0], R_pre, base['R'], 'retirement age'),
        (axes[1], X_pre, base['X'], 'claim age')):
    ax.plot(ages26, by_age26(before), lw=2, label='planned before the news')
    ax.plot(ages26, by_age26(after), lw=2, ls='--', label='chosen after')
    ax.set_xlabel('age in 2026')
    ax.set_ylabel('age')
    ax.set_title(title)
axes[0].legend()
plt.tight_layout()
plt.show()
_images/74cfeeeeaaf253775b413f8ce5c0072563db0a1cad0852ec6dcc4b5c8a8d61f7.png

Fig. 125.7 Irreversibility binds the old, while the young work longer but file sooner#

Three groups appear in Fig. 125.7.

Workers who have already retired and filed by \(2026\) sit on top of their old plans, because both decisions are irreversible: the two lines coincide at the right of each panel.

Workers in or near the claiming window move first and move earliest: they file sooner than they had meant to, and some of them leave work sooner too, to collect while benefits are still paid in full.

Workers in their forties and younger go the other way on one margin and with the crowd on the other.

They work longer, because their pension is smaller.

But they file sooner than they had planned, by more than a year: with the payable factor falling from one year to the next, a benefit deferred is a benefit collected at a lower factor, and that headwind works against the actuarial reward for waiting.

125.7.2. A worker’s life cycle#

What the news does to one worker is easier to see than what it does to an aggregate.

Fig. 125.8 follows the median type of the cohort entering in \(2026\) through its whole life, before and after.

R_b, X_b, wealth_b, Om_b, _ = solve_plans(W, φ, W.j0, plan0=(R_pre, X_pre),
                                          a0=a26.reshape(W.C, 1))

def life_profile(W_, φ_, R, X, wealth, Om, row):
    "Net income, consumption and assets by age for a single worker."
    p = W_.p
    m = income_path(W_, φ_, R, X, rows=[row])[:, 0]
    c = wealth[row]/((1+p.τ_c)*Om[row])*(p.β*after_tax_R(p))**np.arange(J)
    a = np.zeros(J)
    for j in range(J-1):
        s = W_.ell[row, j+1]/W_.ell[row, j]
        a[j+1] = (after_tax_R(p)/s)*(a[j] + m[j] - (1+p.τ_c)*c[j])
    return m, c, a

row = np.where(W.entry == 2026)[0][12]          # the median type
before = life_profile(W, np.ones(T+1), R_pre, X_pre, wealth_pre, Om_pre, row)
after = life_profile(W, φ, R_b, X_b, wealth_b, Om_b, row)

Three panels: what the worker receives, what it spends, and what it holds.

age = AGE0 + np.arange(J)
fig, axes = plt.subplots(1, 3, figsize=(13, 3.8))
for k, ttl in enumerate(('net income', 'consumption', 'assets')):
    axes[k].plot(age, before[k], lw=2, label='before the news')
    axes[k].plot(age, after[k], lw=2, ls='--', label='after')
    axes[k].set_title(ttl)
    axes[k].set_xlabel('age')
    axes[k].set_xlim(20, 90)
axes[0].legend()
plt.tight_layout()
plt.show()

print(f"retirement age {R_pre[row]} -> {R_b[row]},  claim age {X_pre[row]} -> {X_b[row]}")
print(f"peak assets    {before[2].max():.2f} -> {after[2].max():.2f}")
_images/e3eb3eadc1594cd1091e7fabf947b4419bba8f3f5f6e3034867c6167559906b4.png

Fig. 125.8 A smaller pension means less consumption, more saving, and no bridge year#

retirement age 65 -> 65,  claim age 66 -> 65
peak assets    7.53 -> 9.86

This worker’s retirement age does not move – the choice is an integer, and an aggregate that moves by a year moves because different workers switch at different dates – but it files a year earlier, it consumes less at every age, and it carries about a third more assets into retirement.

The income panel of Fig. 125.8 shows one more thing that the plan before the news was willing to pay for.

Retiring at \(65\) and filing at \(66\) leaves a year with no income at all, bridged out of savings, in exchange for a permanently higher actuarial factor.

That bridge is the first thing the news takes away.

The upward tilt of consumption is \(\beta\tilde R > 1\): this worker is patient enough, at an after-tax return of \(4.2\) percent, to want a rising profile.

The third panel is the saving response that, in general equilibrium, would deepen the capital stock and raise the wage, and with it taxable payroll: the channel this lecture shuts off and the paper keeps.

125.8. How this compares with the full model#

The table collects what the stripped-down model delivers next to the general equilibrium numbers of Sargent and Shi [2026].

rows = [("first shortfall year",            f"{shortfall}",            "2032"),
        ("fund empty from",                 f"{depletion}",            "2033"),
        ("φ in the first full-cut year",    f"{φ[depletion-Y0]:.3f}",  "0.773"),
        ("φ in the long run",               f"{φ[-1]:.3f}",            "0.546"),
        ("mean claim age, 2026",            f"{mean_X[0]:.1f}",        "65.0"),
        ("mean claim age, long run",        f"{mean_X[-1]:.1f}",       "69.2"),
        ("mean retirement age, 2026",       f"{mean_R[0]:.1f}",        "62.7"),
        ("mean retirement age, long run",   f"{mean_R[-1]:.1f}",       "66.2"),
        ("cost rate, 2026",                 f"{cost[0]:.3f}",          "0.136")]

print(f"{'':34}{'this lecture':>14}{'the paper':>12}")
for name, mine, theirs in rows:
    print(f"{name:34}{mine:>14}{theirs:>12}")
                                    this lecture   the paper
first shortfall year                        2032        2032
fund empty from                             2033        2033
φ in the first full-cut year               0.755       0.773
φ in the long run                          0.482       0.546
mean claim age, 2026                        64.7        65.0
mean claim age, long run                    66.6        69.2
mean retirement age, 2026                   64.4        62.7
mean retirement age, long run               65.3        66.2
cost rate, 2026                            0.137       0.136

The dates agree and the direction of every response agrees.

Two things do not, and both are worth seeing plainly.

The long-run cut is deeper here, \(0.482\) against \(0.546\).

Prices are fixed in this lecture, so the capital deepening that raises the wage, and with it taxable payroll, is missing; and the demography here is a smooth stylization rather than the trustees’ projection.

The behavioral responses are also smaller here than in the paper, not merely centered differently.

Over the transition the mean retirement age rises by \(0.9\) years against the paper’s \(3.5\), and the mean claim age by \(1.9\) against \(4.2\).

Three features of the stripped-down model mute them: workers face no idiosyncratic earnings risk, so nobody is pushed across a threshold by a bad draw; each worker’s choice is an integer, so a type responds only when the gain crosses a whole year; and prices do not move, so the wage never rewards the longer careers.

The retirement levels need one more caution: the paper’s \(62.7\) is a mean pulled down by a left tail of early retirements that idiosyncratic shocks produce, and its median, which is what the calibration here was matched to, is \(64.5\).

What the general equilibrium model adds

A tax cut arrives with the benefit cut. Depletion removes the Social Security deficit from the consolidated federal budget, so the labor tax falls from \(0.220\) to \(0.190\) in the long run. Current law cuts retirees’ benefits and workers’ taxes in the same year.

Capital deepens. Every working cohort saves against a smaller pension, the capital–output ratio rises from \(3.00\) to \(3.19\), the interest rate falls from \(6.0\) to \(5.3\) percent, and the wage rises.

Financing a bailout is expensive, and the ranking is not the ranking of capital. Paying every scheduled benefit in full costs a newborn in the terminal steady state \(4.8\) percent of lifetime consumption under consumption taxation, \(8.7\) percent under capital taxation and \(11.6\) percent under wage taxation. The capital tax contracts capital the most and is still cheaper than the wage tax, which distorts the retirement margin through which the economy had relieved the pressure on the program, and shrinks its own base.

One program reform changes the arithmetic. Raising the full retirement age from \(67\) to \(69\) delays depletion by ten years and raises the long-run payable factor from \(0.546\) to \(0.621\). Raising the taxable maximum and cutting the top replacement rate move the incidence and leave the depletion year where it is.

The announcement date redistributes among the living. A bailout is worth about \(+4.5\) percent of remaining lifetime consumption to a worker aged \(60\) in \(2026\) and about \(-2\) percent to a worker aged \(25\).

Computing the full model

The paper’s solver has the same shape as the one above and differs in scale.

The worker’s problem is a dynamic program with assets, an idiosyncratic productivity shock, permanent ability, the earnings record and the claim status as states, solved by backward induction over \(81\) ages and \(150\) calendar years; the cross-section is a panel of \(80{,}000\) workers per age cell, simulated forward with common random numbers so that every scenario faces the same workers and the same shocks.

Stationary equilibria are found with Anderson acceleration, and the transition is a damped iteration on prices, the bequest transfer, the payable path and a transitory tax rate, as here.

The code is written in JAX and runs on an A100 GPU: about \(22\) hours from a cold start for the full set of experiments, or about an hour to replay the stored converged paths.

The paper also reports a residual that damping does not remove.

Because the period is a year, the iteration can cycle between two adjacent first shortfall years, each implying the other.

Our own iteration stalls in the same way, for a related reason: retirement and claim ages are integers, so a mass of workers switches discontinuously, and \(\Phi\) is not continuous.

In the output above, the gap stops falling at about \(10^{-2}\) while the payable factor printed beside it settles to the fourth decimal.

125.9. Exercises#

Exercise 125.1

The box above claims that \(\Phi\) is a decreasing map, and that is the reason every iteration here is damped.

Check it, in two steps.

  1. Take the converged path, raise it by five percentage points in every year from \(2033\) on, and apply \(\Phi\) once. Does the image rise or fall?

  2. Run the iteration undamped, that is, with \(\phi_{k+1} = \Phi(\phi_k)\), for twenty steps starting from \(\phi \equiv 1\), and plot the payable factor in \(2040\) and in \(2080\) against the iteration count. Compare both with a damped iteration. Does the undamped iteration converge?

Exercise 125.2

Of the reforms that Sargent and Shi [2026] study, only one moves the depletion date: raising the full retirement age from \(67\) to \(69\).

The reform is announced in \(2026\) and applies to everyone at once, current beneficiaries included.

It works entirely through the actuarial factor: at a full retirement age of \(69\) the factor is about \(13\) percent lower at every claim age, which is the gap between the two lines in the right panel of Fig. 125.1, and the claim window still closes at \(70\), so a worker can recover only part of the cut by filing later.

Compute the reformed transition and report the depletion year and the long-run payable factor.

By how many years is depletion delayed, and how much of the long-run benefit cut does the reform undo?

Exercise 125.3

Suppose that instead of letting the cut happen, Congress guarantees every scheduled benefit, \(\phi_t = 1\) forever, and pays for it by raising the payroll tax \(\tau^{ss}\) to a new constant from \(2026\) on.

  1. Find the rate that balances the program’s cash flow in the long run, taking into account that workers respond to it.

  2. Compute the consumption equivalent variation of the guarantee for cohorts entering between \(2026\) and \(2096\). Under the closed form (125.9), a cohort’s lifetime value is \(V = \Omega\log\mathcal W - \eta h D\), so the compensation that equates the two economies is \(\lambda = \exp\big(\sum_i w_i \Delta V_i/\Omega\big) - 1\).

  3. Do the same for the workers who are already alive in \(2026\), as a function of their age. Who gains from the guarantee and who pays for it?

Exercise 125.4

Two forces raise the dependency ratio in this model: cohorts entering the labor force stopped growing, and mortality keeps improving.

Separate them.

Hold the birth path fixed and switch the mortality improvement off, par._replace(g_m=0.0), so that the life table of \(2025\) applies forever.

  1. What happens to the dependency ratio in \(2085\), and to the long-run payable factor?

  2. What happens to the depletion year? Explain why it moves so much less than the long-run cut does.

  3. What happens to the claim age, and what does that tell you about why claiming drifts later in the baseline?

Exercise 125.5

Exercise 125.3 guaranteed scheduled benefits from \(2026\), with workers told in \(2026\) and taxed from \(2026\).

Sargent and Shi [2026] also study the opposite timing, which they call the unanticipated scenario.

The government says nothing and does nothing.

Workers believe current law, plan against the cut they expect, and save against it.

Then, in the year the reserves are spent, the government announces the guarantee and raises the financing tax at once.

The cut that workers had expected never happens.

Reproduce that experiment here, and compare it with the immediate bailout of Exercise 125.3.

  1. Roll the baseline economy forward to the first shortfall year and collect the state it carries there: the assets each worker has accumulated and the retirement and claim decisions it has already executed.

  2. Let workers re-plan in that year under \(\phi_t = 1\) and the payroll tax \(\tau^{*}\), using the at_year argument of solve_plans.

  3. Value the three economies for the workers alive in \(2026\) and report the consumption equivalent variation of each bailout against the depletion baseline. Who prefers the surprise?

Exercise 125.6

Everything in this lecture rests on one calibration.

Two of its numbers deserve a sensitivity check: the demographic target, and the interest rate at which workers discount.

  1. Recalibrate the birth path to an aged dependency ratio in \(2085\) of \(0.45\) and of \(0.55\), instead of the trustees’ \(0.50\), and recompute the long run.

  2. Recompute it at interest rates of \(5\) and \(7\) percent, holding the preference parameters at their calibrated values.

  3. Which of the two moves the long-run benefit cut, and which moves behavior? Explain what you find using (125.6).