Retirement Choice Model (DCEGM)¶
An agent lives for \(T\) periods. Each period she holds assets \(a \geq 0\) and makes two choices simultaneously: a discrete choice (work or retire) and a continuous choice (how much to consume). Working yields wage income \(y\) but costs disutility \(\delta\); retirement is absorbing — once chosen, the agent never works again.
This is the model of Iskhakov, Jorgensen, Rust & Schjerning (2017, Quantitative Economics), solved via DC-EGM with the FUES upper envelope of Dobrescu & Shanker (2024).
Branching syntax
See Spec 0.1l — Branching stages for the full specification. See also Housing model (tenure choice) for another branching example.
Bellman Equation¶
Let \(V_t^1(a)\) be the value of a worker with assets \(a\) at age \(t\), and \(V_t^0(a)\) the value of a retiree.
The worker chooses a regime \(d \in \{\text{work}, \text{retire}\}\) and consumption \(c > 0\):
where
The retiree solves a standard consumption-savings problem with no income:
Here \(r\) is the interest rate and \(\beta\) the discount factor. A worker who retires loses income \(y\) immediately — the retire branch has cash-on-hand \((1+r)a\) while the work branch has \((1+r)a + y\).
T-Calculus Decomposition¶
We decompose the model into three stages, each with forward transitions \(\mathrm{g}_{\prec\sim}\), \(\mathrm{g}_{\sim\succ}\) and backward movers \(\mathbb{B}\), \(\mathbb{I}\).
WorkerDecision (branching, \(x_\prec = x_\sim = \{a\}\), \(x_{\succ}^{\text{work}} = \{a\}\), \(x_{\succ}^{\text{retire}} = \{a_{\text{ret}}\}\)):
WorkerConsumption (work branch, \(x_\prec = \{a\}\), \(x = \{w\}\), \(x_\succ = \{b\}\)):
RetireeConsumption (retire branch + retiree direct entry, \(\mathsf{X}_\prec = \{a_{\text{ret}}\}\), \(\mathsf{X} = \{w_{\text{ret}}\}\), \(\mathsf{X}_\succ = \{b_{\text{ret}}\}\)):
Period composition¶
Let \(\mathrm{v}_\prec^{+}(a)\) and \(\mathrm{v}_{\text{ret},\prec}^{+}(a_{\text{ret}})\) denote the next period's worker and retiree entry values respectively.
Worker entry (through branching stage):
Retiree entry (bypasses branching stage, same retire_cons stage):
Both expressions use the same retire_cons stage operator — the worker entry and retiree entry differ only in the arrival value. The work branch reads \(\mathrm{v}_\prec^{+}\) (worker value at \(t+1\)), the retire branch reads \(\mathrm{v}_{\text{ret},\prec}^{+}\) (retiree value at \(t+1\)), matching \(V^1_t\) and \(V^0_t\) in Iskhakov et al. (2017).
Period Structure¶
The period has two entry points. Workers enter via a (into the branching stage); retirees enter via a_ret (directly into retire_cons). Both the retire branch and direct retiree entry feed into the same retire_cons stage instance:
Each downstream stage computes its own cash-on-hand: work_cons adds income (\(w = (1+r)a + y\)), retire_cons does not (\(w_{\text{ret}} = (1+r)a_{\text{ret}}\)). The disutility \(\delta\) is applied at the branching stage's mover, keeping the consumption stages free of regime-specific terms.
Stage 1: Worker Decision (Branching)¶
A pure discrete-choice stage. The agent arrives with assets \(a\), which pass through unchanged to both branches. The backward mover takes \(\max\) over the branch continuation values, applying the work penalty \(-\delta\).
name: WorkerDecision
kind: branching
branch_control: agent
symbols:
spaces:
Xa: "@def R+"
prestate:
a: "@in Xa"
states:
a: "@in Xa" # assets (pass-through)
poststates:
work:
a: "@in Xa" # pass assets to work consumption
retire:
a_ret: "@in Xa" # pass assets to retire consumption (distinct namespace)
controls:
d: "@in {work, retire}"
exogenous: {}
values:
V[<]: "@in [-inf, inf)"
V: "@in [-inf, inf)"
V_cntn:
work: "@in [-inf, inf)" # Q^work(a) from worker consumption stage
retire: "@in [-inf, inf)" # Q^retire(a) from retiree consumption stage
parameters: [delta]
equations:
arvl_to_dcsn_transition: |
a = a[<]
dcsn_to_cntn_transition:
work: |
a[>] = a
retire: |
a_ret[>] = a
cntn_to_dcsn_mover:
Bellman: |
V = max_{d}(V_cntn[>][work] - delta, V_cntn[>][retire])
dcsn_to_arvl_mover:
Bellman: |
V[<] = V
Here V_cntn[>][work] and V_cntn[>][retire] are the branch-keyed continuation values \(Q^{\text{work}}(a)\) and \(Q^{\text{retire}}(a)\) received from the downstream consumption stages.
Stage 2: Worker Consumption¶
Cash-on-hand includes wage income: \(w = (1+r)a + y\). The agent chooses consumption \(c\) and saves \(b = w - c\).
name: WorkerConsumption
symbols:
spaces:
Xa: "@def R+"
prestate:
a: "@in Xa" # assets (from branching stage)
states:
w: "@in Xa" # cash-on-hand = (1+r)*a + y
poststates:
b: "@in Xa" # end-of-period assets
controls:
c: "@in R+"
exogenous: {}
values:
V[>]: "@in [-inf, inf)"
V[<]: "@in [-inf, inf)"
V: "@in [-inf, inf)"
values_marginal:
dV[>]: "@in R+"
dV: "@in R+"
equations:
arvl_to_dcsn_transition: |
w = (1 + r) * a + y
dcsn_to_cntn_transition: |
b = w - c
cntn_to_dcsn_mover:
Bellman: |
V = max_c{log(c) + beta * V[>]}
InvEuler: |
c[>] = (beta * dV[>])^(-1)
cntn_to_dcsn_transition: |
w[>] = b + c[>]
MarginalBellman: |
dV = 1/c
dcsn_to_arvl_mover:
Bellman: |
V[<] = V
MarginalBellman: |
dV[<] = dV
Stage 3: Retiree Consumption¶
Same structure as the worker stage, but with no income: \(w_{\text{ret}} = (1+r)a_{\text{ret}}\). This single stage instance serves two roles — it receives workers who choose to retire (via the branching stage) and continuing retirees (via the inter-period connector).
name: RetireeConsumption
symbols:
spaces:
Xa: "@def R+"
prestate:
a_ret: "@in Xa" # assets (distinct namespace from worker)
states:
w_ret: "@in Xa" # cash-on-hand = (1+r)*a_ret (no income)
poststates:
b_ret: "@in Xa" # end-of-period assets (distinct from worker b)
controls:
c: "@in R+"
exogenous: {}
values:
V[>]: "@in [-inf, inf)"
V[<]: "@in [-inf, inf)"
V: "@in [-inf, inf)"
values_marginal:
dV[>]: "@in R+"
dV: "@in R+"
parameters: [beta, r]
equations:
arvl_to_dcsn_transition: |
w_ret = (1 + r) * a_ret
dcsn_to_cntn_transition: |
b_ret = w_ret - c
cntn_to_dcsn_mover:
Bellman: |
V = max_c{log(c) + beta * V[>]}
InvEuler: |
c[>] = (beta * dV[>])^(-1)
cntn_to_dcsn_transition: |
w_ret[>] = b_ret[>] + c[>]
MarginalBellman: |
dV = 1/c
dcsn_to_arvl_mover:
Bellman: |
V[<] = V
MarginalBellman: |
dV[<] = dV
The namespace (a_ret, w_ret, b_ret) is distinct from the worker's (a, w, b), so wiring is unambiguous.
Period Template¶
The three stages are wired by namespace matching — the branching stage's branch-keyed poststates fields match successor stages' prestate fields by name. No explicit connectors are needed.
name: LifecyclePeriod
stages:
- labour_mkt_decision: !stage # branching: work vs retire (WorkerDecision)
- work_cons: !stage # work-branch (WorkerConsumption)
- retire_cons: !stage # retire-branch (RetireeConsumption)
Methodization¶
The model is deterministic, so no expectation operators are needed.
WorkerDecision¶
The branching stage uses a max aggregator — no EGM, no grid.
stage: WorkerDecision
methods:
- on: cntn_to_dcsn_mover
schemes:
- scheme: branching_aggregator
method: !max
WorkerConsumption¶
EGM with FUES upper envelope. The continuation value has a kink at the retirement threshold, so the endogenous grid can have non-monotone segments that FUES cleans.
stage: WorkerConsumption
methods:
- on: cntn_to_dcsn_mover
schemes:
- scheme: bellman_backward
method: !egm
- scheme: interpolation
method: !Cartesian
settings:
orders: [n_w]
bounds: [[w_min, w_max]]
- scheme: upper_envelope
method: !FUES
settings:
m_bar: m_bar
RetireeConsumption¶
Standard EGM — no upper envelope needed (the retiree's value is concave).
stage: RetireeConsumption
methods:
- on: cntn_to_dcsn_mover
schemes:
- scheme: bellman_backward
method: !egm
- scheme: interpolation
method: !Cartesian
settings:
orders: [n_w_ret]
bounds: [[w_ret_min, w_ret_max]]
Nest Structure¶
Every time slot uses the same period template. The inter-period connector {b: a, b_ret: a_ret} routes worker output back to the branching stage and retiree output back to retire_cons.
Once an agent retires, she is routed to a_ret at every subsequent period, bypassing the branching stage — retirement is structurally absorbing.
name: RetirementLifecycle
periods:
- lifecycle_period: !period # index 0
- lifecycle_period: !period # index 1
# ... (repeat for T periods)
- terminal: !period # index T
# Inter-period connectors (renaming dicts).
# b → a (worker → branching stage), b_ret → a_ret (retiree → retire_cons).
inter_connectors:
- {b: a, b_ret: a_ret}
- {b: a, b_ret: a_ret}
# ... (one per period boundary)
Calibration¶
Following Iskhakov et al. (2017):
calibration:
r: 0.02 # interest rate
beta: 0.98 # discount factor
delta: 1.0 # utility cost of working
y: 20 # wage income
settings:
# Worker grids (work branch)
n_w: 300 # grid points for worker cash-on-hand
w_min: 1.0e-10 # borrowing constraint (near zero)
w_max: 520 # max cash-on-hand ~ (1+r)*grid_max_A + y
# Retiree grids (retire branch)
n_w_ret: 300 # grid points for retiree cash-on-hand
w_ret_min: 1.0e-10
w_ret_max: 510 # max cash-on-hand ~ (1+r)*grid_max_A
# FUES
m_bar: 1.2 # jump detection threshold (work branch only)
T: 20 # number of periods
Key Points¶
- One period, two entries: workers enter via
a(branching stage), retirees enter viaa_ret(directly intoretire_cons). The inter-period connector{b: a, b_ret: a_ret}makes retirement structurally absorbing. - Minimal branching stage: WorkerDecision is pure discrete choice (
maxaggregator). All economic content (income, budget constraint, Euler equation) lives in the downstream consumption stages. - FUES on the work branch only: the retiree's value is concave (standard EGM suffices), but the worker's has a kink at the retirement threshold, requiring upper envelope cleaning.