Skip to content

Housing Model

Sequential composition and branching in a housing tenure-choice model.

This example demonstrates two compositions:

  1. Owner-only period (sequential): two stages composed via common namespace — a discrete housing adjustment followed by continuous consumption.
  2. Tenure-choice period (branching): a branching stage fans out to owner and renter paths, each with its own housing and consumption stages.

Model Overview

Feature Description
Stages 2 (housing_choice → consumption_choice)
Period HousingOwnerPeriod
Shocks Markov income process
Solution Discrete choice + EGM with upper envelope (FUES-style)

Economic Problem

The agent solves a two-stage problem each period:

Stage 1 (Housing Choice): $$ V^H(a, H, y) = \mathbb{E}{y'|y} \left[ \max V^C(w, H', y') \right] $$

Stage 2 (Consumption Choice): $$ V^C(w, H', y) = \max_c \left{ u(c, H') + \beta V^H(a', H', y) \right} $$

where: - a = beginning-of-period assets - H = current housing stock - y = income state (Markov) - H' = chosen housing (discrete) - w = cash-on-hand after housing adjustment - c = consumption - a' = end-of-period assets

Period Structure

┌──────────────── PERIOD ────────────────┐
│                                        │
│  [housing_choice] ──▶ [consumption_choice]
│       │                      │         │
│   discrete H'            EGM for c     │
│                                        │
└────────────────────────────────────────┘

State Spaces and Morphisms

(b, H, y_pre) ──shock y──▶ (w, H, y) ──g: H'──▶ (w, H_nxt, y) ──g: c──▶ (a_nxt, H_nxt, y)
   arvl             dcsn             cntn / arvl             cntn
        housing_choice                    consumption_choice
  • \((b, H, y_\text{pre}) \xrightarrow{y} (w, H, y)\): income shock realized, \(w = (1+r)b + z[y]\)
  • \((w, H, y) \xrightarrow{H'} (w, H_\text{nxt}, y)\): housing adjustment, \(w \mapsto w + H - (1+\phi\cdot\mathbb{1})H'\)
  • \((w, H_\text{nxt}, y) \xrightarrow{c} (a_\text{nxt}, H_\text{nxt}, y)\): consumption, \(a_\text{nxt} = w - c\)

Files

housing_owner_only_doloplus/
├── period.yaml              # Period definition (stage list)
├── calibration.yaml         # Parameter values
├── settings.yaml            # Grid sizes, bounds
└── stages/
    ├── housing_choice.yaml          # Stage 1 syntax
    ├── housing_choice_methods.yml   # Stage 1 solution scheme
    ├── consumption_choice.yaml      # Stage 2 syntax
    └── consumption_choice_methods.yml   # Stage 2 solution scheme

Period YAML

name: HousingOwnerPeriod

stages:
  - housing_choice
  - consumption_choice

# Connections are implicit via common namespace

Stage 1: Housing Choice

Discrete choice over housing stock with transaction costs. The housing grid XH is defined as part of the mathematical model via @def linspace(H_min, H_max, n_H).

name: OwnerHousingChoice

symbols:
  spaces:
    Xa: "@def R+"
    XH: "@def linspace(H_min, H_max, n_H)"  # discrete housing grid
    Xw: "@def R+"
    Y:  "@def Z+"

  prestate:
    b: "@in Xa"         # beginning assets
    H: "@in XH"         # current housing
    y_pre: "@in Y"      # previous income index

  exogenous:
    y:
      - "@in Y"
      - "@dist DiscreteMarkov(Pi, z_vals)"

  states:
    w: "@in Xa"         # cash-on-hand
    H: "@in XH"
    y: "@in Y"

  poststates:
    w: "@in Xw"         # cash after housing adjustment
    H_nxt: "@in XH"     # chosen housing
    y: "@in Y"

  controls:
    H_choice: "@in XH"  # discrete housing choice

  parameters:
    r: "@in R+"
    phi: "@in R+"
    n_H: "@in Z+"       # housing grid points (defines XH)
    H_min: "@in R+"     # housing grid lower bound (defines XH)
    H_max: "@in R+"     # housing grid upper bound (defines XH)

equations:
  # Income shock realized
  arvl_to_dcsn_transition: |
    w = (1 + r)*b[<] + z_vals[y]

  # Housing adjustment with transaction cost
  dcsn_to_cntn_transition: |
    w[>] = w + H - (1 + phi*(H_choice != H))*H_choice
    H_nxt[>] = H_choice

  # Discrete max over housing
  cntn_to_dcsn_mover:
    Bellman: |
      V = max_{H_choice}(V[>])

  # Markov expectation
  dcsn_to_arvl_mover:
    Bellman: |
      V[<] = E_{y|y_pre}(V)

Stage 2: Consumption Choice

Continuous consumption via EGM, given housing already chosen. The housing grid XH is again declared as linspace(...) since it defines the domain of the H_nxt state carried through this stage.

name: OwnerConsumptionChoice

symbols:
  spaces:
    Xa: "@def R+"
    XH: "@def linspace(H_min, H_max, n_H)"
    Xw: "@def R+"
    Y:  "@def Z+"

  prestate:
    w: "@in Xw"         # cash-on-hand (from housing stage)
    H_nxt: "@in XH"     # housing (already chosen)
    y: "@in Y"

  exogenous: {}         # No shock in this stage

  poststates:
    a_nxt: "@in Xa"     # end-of-period assets
    H_nxt: "@in XH"
    y: "@in Y"

  controls:
    c: "@in R+"

  parameters:
    beta: "@in (0,1)"
    gamma: "@in R+"
    theta: "@in (0,1)"
    kappa: "@in R+"
    iota: "@in R+"
    n_H: "@in Z+"       # housing grid points (defines XH)
    H_min: "@in R+"     # housing grid lower bound (defines XH)
    H_max: "@in R+"     # housing grid upper bound (defines XH)

equations:
  dcsn_to_cntn_transition: |
    a_nxt = w - c

  cntn_to_dcsn_transition: |
    w_dcsn[>] = a_nxt + c_fn[>]

  cntn_to_dcsn_mover:
    # Cobb-Douglas utility in (c, H)
    Bellman: |
      V = max_{c}(u(c, H_nxt) + β*V[>])
    InvEuler: |
      c_fn[>] = (β*dV[>] / (θ * (κ*H_nxt + ι)^(...)))^(-1/...)
    ShadowBellman: |
      dV[>] = θ * c^(...) * (κ*H_nxt + ι)^(...)

Key Features

Multi-Stage Composition

The period composes two stages via common namespace: - Stage 1 outputs (w, H_nxt, y) - Stage 2 inputs (w, H_nxt, y) - Connection is implicit (no explicit connector needed)

Discrete + Continuous

  • Stage 1: Discrete choice over finite housing grid
  • Stage 2: Continuous consumption via EGM

Markov Shocks

Income follows a Markov chain:

y:
  - "@in Y"
  - "@dist DiscreteMarkov(Pi, z_vals)"

The expectation in Stage 1 conditions on previous income:

V[<] = E_{y|y_pre}(V)

FUES Upper Envelope

Stage 2 uses FUES-style upper envelope to handle non-convexities from the discrete housing choice. The ue_kwargs setting configures the envelope algorithm.

Extension: Tenure-Choice Period (Branching)

The owner-only model above assumes the agent always owns. A more general model introduces a tenure choice: each period the agent decides whether to own or rent housing. This is a branching composition — the two paths have different state spaces and different downstream stages.

Branching syntax

See Spec 0.1l — Branching stages for the full specification.

Period Structure (DAG)

┌──────────────────── PERIOD ────────────────────────┐
│                                                    │
│                  ┌─ [owner_housing] ─▶ [owner_cons]│
│  [tenure_choice] ┤                                 │
│                  └─ [renter_housing] ▶ [renter_cons]│
│                                                    │
└────────────────────────────────────────────────────┘

The branching stage (tenure_choice) fans out to two paths. Each path has its own housing and consumption stages. The backward solve order is: owner_consrenter_consowner_housingrenter_housingtenure_choice.

State Spaces and Morphisms

                                    ┌─own──▶ (w_own, H, y) ──H'──▶ (w_oc, H_nxt, y) ──c──▶ (a_nxt, H_nxt, y)
(b, H, y_pre) ──shock y──▶ (a, H, y)┤
                                    └─rent─▶ (w_rent, y) ────h'──▶ (w_rc, h, y) ──────c──▶ (a_nxt, y)
  • \((b, H, y_\text{pre}) \xrightarrow{y} (a, H, y)\): income shock, \(a = (1+r)b + z[y]\)
  • Own branch: \((a, H, y) \xrightarrow{\text{own}} (w_\text{own}, H, y)\): \(w_\text{own} = a + H\)
  • \((w_\text{own}, H, y) \xrightarrow{H'} (w_\text{oc}, H_\text{nxt}, y)\): housing adjustment
  • \((w_\text{oc}, H_\text{nxt}, y) \xrightarrow{c} (a_\text{nxt}, H_\text{nxt}, y)\): consumption
  • Rent branch: \((a, H, y) \xrightarrow{\text{rent}} (w_\text{rent}, y)\): \(w_\text{rent} = (1+r)a + H\)
  • \((w_\text{rent}, y) \xrightarrow{h'} (w_\text{rc}, h, y)\): rent housing, \(w_\text{rc} = w_\text{rent} - p \cdot h'\)
  • \((w_\text{rc}, h, y) \xrightarrow{c} (a_\text{nxt}, y)\): consumption

Each intermediate state space has a unique name (w_own, w_oc, w_rent, w_rc). No overloading; no connectors needed.

Branching Stage: Tenure Choice

The tenure-choice stage declares two continuation perches (own and rent) with different state spaces. The backward mover combines the two branch values via max_d{…}. Each branch's poststate fields use unique names in the period namespace (w_own for the owner path, w_rent for the renter path).

name: TenureChoice

symbols:
  spaces:
    Xa: "@def R+"
    XH: "@def linspace(H_min, H_max, n_H)"
    XY: "@def {1, ..., n_y}"
  prestate:
    b: "@in Xa"
    H: "@in XH"
    y_pre: "@in XY"
  states:
    a: "@in Xa"
    H: "@in XH"
    y: "@in XY"
  poststates:
    own:
      w_own: "@in Xa"     # cash-on-hand for owner path
      H: "@in XH"
      y: "@in XY"
    rent:
      w_rent: "@in Xa"    # cash-on-hand after liquidating housing
      y: "@in XY"
  controls:
    d: "@in {own, rent}"   # discrete branch selector
  exogenous:
    y:
      - "@in XY"
      - "@dist MarkovChain(Pi, z_vals)"
  values:
    V[<]: "@in R"
    V: "@in R"
    V_cntn:
      own: "@in R"         # continuation value from owner path
      rent: "@in R"        # continuation value from renter path
  parameters: [r, phi, H_min, H_max, n_H, n_y]

equations:
  arvl_to_dcsn_transition: |
    a = (1 + r)*b[<] + z_vals[y]
    H = H[<]
    y = y_shock

  dcsn_to_cntn_transition:
    own: |
      w_own[>] = a + H
      H[>] = H
      y[>] = y
    rent: |
      w_rent[>] = (1+r)*a + H
      y[>] = y

  cntn_to_dcsn_mover:
    Bellman: |
      V = max_d{V_cntn[>][own], V_cntn[>][rent]}

  dcsn_to_arvl_mover:
    Bellman: |
      V[<] = E_y(V)

Key points:

  • poststates is branch-keyed: own has fields (w_own, H, y), rent has fields (w_rent, y). The unique names (w_own, w_rent) satisfy the no-overloading rule — each quantity has its own name in the period namespace.
  • dcsn_to_cntn_transition is also branch-keyed: the own branch computes owner cash-on-hand w_own = a + H; the rent branch liquidates housing w_rent = (1+r)*a + H.
  • V_cntn is declared as a branch-indexed family with keys matching the poststates keys.
  • The Bellman equation uses max_d{…} over the branch continuation values.

Owner Path: Housing and Consumption

The owner path receives (w_own, H, y) from the own continuation perch. Note the variable naming differs from the owner-only model above (which uses w generically) — in the branching context, w_own ensures uniqueness.

Owner Housing Stage (discrete \(H'\) choice):

name: OwnerHousingChoice

symbols:
  spaces:
    Xa: "@def R+"
    XH: "@def linspace(H_min, H_max, n_H)"
    XY: "@def {1, ..., n_y}"

  prestate:
    w_own: "@in Xa"       # cash-on-hand (from tenure-choice own branch)
    H: "@in XH"
    y: "@in XY"

  states:
    w_own: "@in Xa"
    H: "@in XH"
    y: "@in XY"

  poststates:
    w_oc: "@in Xa"        # cash after housing adjustment → owner consumption
    H_nxt: "@in XH"
    y: "@in XY"

  controls:
    H_choice: "@in XH"

  parameters:
    phi: "@in R+"
    n_H: "@in Z+"
    H_min: "@in R+"
    H_max: "@in R+"

equations:
  arvl_to_dcsn_transition: |
    w_own = w_own[<]
    H = H[<]
    y = y[<]

  dcsn_to_cntn_transition: |
    w_oc[>] = w_own + H - (1 + phi*(H_choice != H))*H_choice
    H_nxt[>] = H_choice
    y[>] = y

  cntn_to_dcsn_mover:
    Bellman: |
      V = max_{H_choice}(V[>])

  dcsn_to_arvl_mover:
    Bellman: |
      V[<] = V

Owner Consumption Stage (EGM):

name: OwnerConsumptionChoice

symbols:
  spaces:
    Xa: "@def R+"
    XH: "@def linspace(H_min, H_max, n_H)"
    XY: "@def {1, ..., n_y}"

  prestate:
    w_oc: "@in Xa"        # cash after housing adj. (from owner housing)
    H_nxt: "@in XH"
    y: "@in XY"

  poststates:
    a_nxt: "@in Xa"
    H_nxt: "@in XH"
    y: "@in XY"

  controls:
    c: "@in R+"

  parameters:
    beta: "@in (0,1)"
    gamma: "@in R+"
    theta: "@in (0,1)"
    kappa: "@in R+"
    iota: "@in R+"
    n_H: "@in Z+"
    H_min: "@in R+"
    H_max: "@in R+"

equations:
  dcsn_to_cntn_transition: |
    a_nxt[>] = w_oc - c

  cntn_to_dcsn_mover:
    Bellman: |
      V = max_{c}(u(c, H_nxt) + beta*V[>])

  dcsn_to_arvl_mover:
    Bellman: |
      V[<] = V

Renter Path: Housing and Consumption

The renter path receives (w_rent, y) from the rent continuation perch.

Renter Housing Stage (discrete rental housing choice):

name: RenterHousingChoice

symbols:
  spaces:
    Xa: "@def R+"
    Xh: "@def linspace(h_min, h_max, n_h)"
    XY: "@def {1, ..., n_y}"

  prestate:
    w_rent: "@in Xa"      # cash-on-hand (from tenure-choice rent branch)
    y: "@in XY"

  states:
    w_rent: "@in Xa"
    y: "@in XY"

  poststates:
    w_rc: "@in Xa"        # cash after paying rent → renter consumption
    h: "@in Xh"
    y: "@in XY"

  controls:
    h_choice: "@in Xh"

  parameters:
    p_rent: "@in R+"
    n_h: "@in Z+"
    h_min: "@in R+"
    h_max: "@in R+"

equations:
  arvl_to_dcsn_transition: |
    w_rent = w_rent[<]
    y = y[<]

  dcsn_to_cntn_transition: |
    w_rc[>] = w_rent - p_rent * h_choice
    h[>] = h_choice

  cntn_to_dcsn_mover:
    Bellman: |
      V = max_{h_choice}(V[>])

  dcsn_to_arvl_mover:
    Bellman: |
      V[<] = V

Renter Consumption Stage:

name: RenterConsumptionChoice

symbols:
  spaces:
    Xa: "@def R+"
    Xh: "@def linspace(h_min, h_max, n_h)"
    XY: "@def {1, ..., n_y}"

  prestate:
    w_rc: "@in Xa"        # cash after rent (from renter housing stage)
    h: "@in Xh"
    y: "@in XY"

  poststates:
    a_nxt: "@in Xa"
    y: "@in XY"
    H_nxt: "@in {0}"

  controls:
    c: "@in R+"

  parameters:
    beta: "@in (0,1)"
    gamma: "@in R+"
    theta: "@in (0,1)"
    n_h: "@in Z+"
    h_min: "@in R+"
    h_max: "@in R+"

equations:
  dcsn_to_cntn_transition: |
    a_nxt = w_rc - c
    H_nxt = 0

  cntn_to_dcsn_mover:
    Bellman: |
      V = max_{c}(u(c, h) + beta*V[>])

  dcsn_to_arvl_mover:
    Bellman: |
      V[<] = V

Period Template (Branching)

No connectors are needed — all variable names are unique in the period namespace by construction.

name: HousingTenurePeriod

stages:
  - tenure_choice: !stage         # branching stage (own vs rent)
  - owner_housing: !stage         # own path: discrete H' choice
  - owner_cons: !stage            # own path: consumption
  - renter_housing: !stage        # rent path: discrete h choice
  - renter_cons: !stage           # rent path: consumption

Wiring explanation:

Connection Namespace fields
tenure_choice.ownowner_housing (w_own, H, y) match by name
tenure_choice.rentrenter_housing (w_rent, y) match by name
owner_housingowner_cons (w_oc, H_nxt, y) match by name
renter_housingrenter_cons (w_rc, h, y) match by name

Each cash-on-hand variable has a unique name (w_own, w_oc, w_rent, w_rc), satisfying the no-overloading rule. No connectors needed because the stage templates are written with branching-compatible names from the start.