Skip to content

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\):

\[ V_t^1(a) = \max_{d \in \{\text{work}, \text{retire}\}} Q_t^{d}(a) \]

where

\[ Q_t^{\text{work}}(a) = \max_c \left\{ \log(c) - \delta + \beta V_{t+1}^1\bigl((1+r)a + y - c\bigr) \right\} \]
\[ Q_t^{\text{retire}}(a) = \max_c \left\{ \log(c) + \beta V_{t+1}^0\bigl((1+r)a - c\bigr) \right\} \]

The retiree solves a standard consumption-savings problem with no income:

\[ V_t^0(a) = \max_c \left\{ \log(c) + \beta V_{t+1}^0\bigl((1+r)a - c\bigr) \right\} \]

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}}\}\)):

\[ \mathrm{g}_{\prec\sim}: \quad a = a_\prec \qquad\text{(identity arrival)} \]
\[ \mathrm{g}_{\sim\succ}^{\text{work}}: \quad a_\succ = a, \qquad \mathrm{g}_{\sim\succ}^{\text{retire}}: \quad a_{\text{ret},\succ} = a \]
\[ \mathbb{B}: \quad \mathrm{v}(a) = \max\bigl\{\mathrm{v}_\succ^{\text{work}}(a) - \delta,\; \mathrm{v}_\succ^{\text{retire}}(a)\bigr\} \]
\[ \mathbb{I}: \quad \mathrm{v}_\prec(a) = \mathrm{v}(a) \qquad\text{(identity)} \]

WorkerConsumption (work branch, \(x_\prec = \{a\}\), \(x = \{w\}\), \(x_\succ = \{b\}\)):

\[ \mathrm{g}_{\prec\sim}: \quad w = (1+r)\,a_\prec + y \]
\[ \mathrm{g}_{\sim\succ}: \quad b = w - c \]
\[ \mathbb{B}: \quad \mathrm{v}(w) = \max_c\bigl\{\log(c) + \beta\, \mathrm{v}_\succ(w - c)\bigr\} \]
\[ \mathbb{I}: \quad \mathrm{v}_\prec(a) = \mathrm{v}\bigl((1+r)a + y\bigr) \]

RetireeConsumption (retire branch + retiree direct entry, \(\mathsf{X}_\prec = \{a_{\text{ret}}\}\), \(\mathsf{X} = \{w_{\text{ret}}\}\), \(\mathsf{X}_\succ = \{b_{\text{ret}}\}\)):

\[ \mathrm{g}_{\prec\sim}: \quad w_{\text{ret}} = (1+r)\,a_{\text{ret},\prec} \]
\[ \mathrm{g}_{\sim\succ}: \quad b_{\text{ret}} = w_{\text{ret}} - c \]
\[ \mathbb{B}: \quad \mathrm{v}(w_{\text{ret}}) = \max_c\bigl\{\log(c) + \beta\, \mathrm{v}_\succ(w_{\text{ret}} - c)\bigr\} \]
\[ \mathbb{I}: \quad \mathrm{v}_\prec(a_{\text{ret}}) = \mathrm{v}\bigl((1+r)\,a_{\text{ret}}\bigr) \]

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):

\[ \mathrm{v}_\prec(a) = \max\Bigl\{ \underbrace{\max_c\bigl\{\log(c) - \delta + \beta\, \mathrm{v}_\prec^{+}\bigl((1+r)a + y - c\bigr)\bigr\}}_{Q^{\text{work}}(a)},\; \underbrace{\max_c\bigl\{\log(c) + \beta\, \mathrm{v}_{\text{ret},\prec}^{+}\bigl((1+r)a - c\bigr)\bigr\}}_{Q^{\text{retire}}(a)} \Bigr\} \]

Retiree entry (bypasses branching stage, same retire_cons stage):

\[ \mathrm{v}_{\text{ret},\prec}(a_{\text{ret}}) = \max_c\bigl\{\log(c) + \beta\, \mathrm{v}_{\text{ret},\prec}^{+}\bigl((1+r)a_{\text{ret}} - c\bigr)\bigr\} \]

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:

LifecyclePeriod stage perch field a aret labour_mkt_decision work_cons retire_cons b bret work retire

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.

Period[0] ──▶ Period[1] ──▶ ... ──▶ Period[T-1] ──▶ terminal

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 via a_ret (directly into retire_cons). The inter-period connector {b: a, b_ret: a_ret} makes retirement structurally absorbing.
  • Minimal branching stage: WorkerDecision is pure discrete choice (max aggregator). 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.