GitHub

@@ -231,7 +231,7 @@ Below we provide code to solve the household problem, taking $r$ and $w$ as fixe

231231232232

### Primitives and operators

233233234-

We will solve the household problem using Howard policy iteration (see Ch 5 of [Dynamic Programming](https://dp.quantecon.org/)).

234+

We will solve the household problem using value function iteration.

235235236236

First we set up a `NamedTuple` to store the parameters that define a household asset accumulation problem, as well as the grids used to solve it

237237

@@ -245,8 +245,8 @@ class Household(NamedTuple):

245245

def create_household(β=0.96, # Discount factor

246246

Π=[[0.9, 0.1], [0.1, 0.9]], # Markov chain

247247

z_grid=[0.1, 1.0], # Exogenous states

248-

a_min=1e-10, a_max=20, # Asset grid

249-

a_size=200):

248+

a_min=1e-10, a_max=12.5, # Asset grid

249+

a_size=100):

250250

"""

251251

Create a Household namedtuple with custom grids.

252252

"""

@@ -278,7 +278,6 @@ $$

278278

for all $(a, z, a')$.

279279280280

```{code-cell} ipython3

281-

@jax.jit

282281

def B(v, household, prices):

283282

# Unpack

284283

β, a_grid, z_grid, Π = household

@@ -303,125 +302,54 @@ def B(v, household, prices):

303302

The next function computes greedy policies

304303305304

```{code-cell} ipython3

306-

@jax.jit

307305

def get_greedy(v, household, prices):

308306

"""

309-

Computes a v-greedy policy σ, returned as a set of indices. If

307+

Computes a v-greedy policy σ, returned as a set of indices. If

310308

σ[i, j] equals ip, then a_grid[ip] is the maximizer at i, j.

311309

"""

312310

# argmax over ap

313311

return jnp.argmax(B(v, household, prices), axis=-1)

314312

```

315313316-

The following function computes the array $r_{\sigma}$ which gives current rewards given policy $\sigma$

314+

We define the Bellman operator $T$, which takes a value function $v$ and returns $Tv$ as given in the Bellman equation

317315318316

```{code-cell} ipython3

319-

@jax.jit

320-

def compute_r_σ(σ, household, prices):

317+

def T(v, household, prices):

321318

"""

322-

Compute current rewards at each i, j under policy σ. In particular,

323-324-

r_σ[i, j] = u((1 + r)a[i] + wz[j] - a'[ip])

325-326-

when ip = σ[i, j].

319+

The Bellman operator. Takes a value function v and returns Tv.

327320

"""

328-

# Unpack

329-

β, a_grid, z_grid, Π = household

330-

a_size, z_size = len(a_grid), len(z_grid)

331-

r, w = prices

332-333-

# Compute r_σ[i, j]

334-

a = jnp.reshape(a_grid, (a_size, 1))

335-

z = jnp.reshape(z_grid, (1, z_size))

336-

ap = a_grid[σ]

337-

c = (1 + r) * a + w * z - ap

338-

r_σ = u(c)

339-340-

return r_σ

321+

return jnp.max(B(v, household, prices), axis=-1)

341322

```

342323343-

The value $v_{\sigma}$ of a policy $\sigma$ is defined as

344-345-

$$

346-

v_{\sigma} = (I - \beta P_{\sigma})^{-1} r_{\sigma}

347-

$$

348-349-

(See Ch 5 of [Dynamic Programming](https://dp.quantecon.org/) for notation and background on Howard policy iteration.)

350-351-

To compute this vector, we set up the linear map $v \rightarrow R_{\sigma} v$, where $R_{\sigma} := I - \beta P_{\sigma}$.

352-353-

This map can be expressed as

354-355-

$$

356-

(R_{\sigma} v)(a, z) = v(a, z) - \beta \sum_{z'} v(\sigma(a, z), z') \Pi(z, z')

357-

$$

358-359-

(Notice that $R_\sigma$ is expressed as a linear operator rather than a matrix—this is much easier and cleaner to code, and also exploits sparsity.)

324+

Here's value function iteration, which repeatedly applies the Bellman operator until convergence

360325361326

```{code-cell} ipython3

362327

@jax.jit

363-

def R_σ(v, σ, household):

364-

# Unpack

328+

def value_function_iteration(household, prices, tol=1e-4, max_iter=10_000):

329+

"""

330+

Implements value function iteration using a compiled JAX loop.

331+

"""

365332

β, a_grid, z_grid, Π = household

366333

a_size, z_size = len(a_grid), len(z_grid)

367334368-

# Set up the array v[σ[i, j], jp]

369-

zp_idx = jnp.arange(z_size)

370-

zp_idx = jnp.reshape(zp_idx, (1, 1, z_size))

371-

σ = jnp.reshape(σ, (a_size, z_size, 1))

372-

V = v[σ, zp_idx]

373-374-

# Expand Π[j, jp] to Π[i, j, jp]

375-

Π = jnp.reshape(Π, (1, z_size, z_size))

376-377-

# Compute and return v[i, j] - β Σ_jp v[σ[i, j], jp] * Π[j, jp]

378-

return v - β * jnp.sum(V * Π, axis=-1)

379-

```

335+

def condition_function(loop_state):

336+

i, v, error = loop_state

337+

return jnp.logical_and(error > tol, i < max_iter)

380338381-

The next function computes the lifetime value of a given policy

339+

def update(loop_state):

340+

i, v, error = loop_state

341+

v_new = T(v, household, prices)

342+

error = jnp.max(jnp.abs(v_new - v))

343+

return i + 1, v_new, error

382344383-

```{code-cell} ipython3

384-

@jax.jit

385-

def get_value(σ, household, prices):

386-

"""

387-

Get the lifetime value of policy σ by computing

345+

# Initial loop state

346+

v_init = jnp.zeros((a_size, z_size))

347+

loop_state_init = (0, v_init, tol + 1)

388348389-

v_σ = R_σ^{-1} r_σ

390-

"""

391-

r_σ = compute_r_σ(σ, household, prices)

392-393-

# Reduce R_σ to a function in v

394-

_R_σ = lambda v: R_σ(v, σ, household)

349+

# Run the fixed point iteration

350+

i, v, error = jax.lax.while_loop(condition_function, update, loop_state_init)

395351396-

# Compute v_σ = R_σ^{-1} r_σ using an iterative routine.

397-

return jax.scipy.sparse.linalg.bicgstab(_R_σ, r_σ)[0]

398-

```

399-400-

Here's the Howard policy iteration

401-402-

```{code-cell} ipython3

403-

def howard_policy_iteration(household, prices,

404-

tol=1e-4, max_iter=10_000, verbose=False):

405-

"""

406-

Howard policy iteration routine.

407-

"""

408-

β, a_grid, z_grid, Π = household

409-

a_size, z_size = len(a_grid), len(z_grid)

410-

σ = jnp.zeros((a_size, z_size), dtype=int)

411-412-

v_σ = get_value(σ, household, prices)

413-

i = 0

414-

error = tol + 1

415-

while error > tol and i < max_iter:

416-

σ_new = get_greedy(v_σ, household, prices)

417-

v_σ_new = get_value(σ_new, household, prices)

418-

error = jnp.max(jnp.abs(v_σ_new - v_σ))

419-

σ = σ_new

420-

v_σ = v_σ_new

421-

i = i + 1

422-

if verbose:

423-

print(f"iteration {i} with error {error}.")

424-

return σ

352+

return get_greedy(v, household, prices)

425353

```

426354427355

As a first example of what we can do, let's compute and plot an optimal accumulation policy at fixed prices

@@ -437,8 +365,7 @@ print(f"Interest rate: {r}, Wage: {w}")

437365438366

```{code-cell} ipython3

439367

with qe.Timer():

440-

σ_star = howard_policy_iteration(

441-

household, prices, verbose=True).block_until_ready()

368+

σ_star = value_function_iteration(household, prices).block_until_ready()

442369

```

443370444371

The next plot shows asset accumulation policies at different values of the exogenous state

@@ -560,7 +487,7 @@ def G(K, firm, household):

560487

# Generate a household object with these prices, compute

561488

# aggregate capital.

562489

prices = Prices(r=r, w=w)

563-

σ_star = howard_policy_iteration(household, prices)

490+

σ_star = value_function_iteration(household, prices)

564491

return capital_supply(σ_star, household)

565492

```

566493

@@ -640,8 +567,8 @@ def prices_to_capital_stock(household, r, firm):

640567

prices = Prices(r=r, w=w)

641568642569

# Compute the optimal policy

643-

σ_star = howard_policy_iteration(household, prices)

644-570+

σ_star = value_function_iteration(household, prices)

571+645572

# Compute capital supply

646573

return capital_supply(σ_star, household)

647574

@@ -752,3 +679,189 @@ plt.show()

752679753680

```{solution-end}

754681

```

682+683+

```{exercise-start}

684+

:label: aiyagari_ex3

685+

```

686+687+

In this lecture, we used value function iteration to solve the household problem.

688+689+

An alternative is Howard policy iteration (HPI), which is discussed in detail in [Dynamic Programming](https://dp.quantecon.org/).

690+691+

HPI can be faster than VFI for some problems because it uses fewer but more computationally intensive iterations.

692+693+

Your task is to implement Howard policy iteration and compare the results with value function iteration.

694+695+

**Key concepts you'll need:**

696+697+

Howard policy iteration requires computing the value $v_{\sigma}$ of a policy $\sigma$, defined as:

698+699+

$$

700+

v_{\sigma} = (I - \beta P_{\sigma})^{-1} r_{\sigma}

701+

$$

702+703+

where $r_{\sigma}$ is the reward vector under policy $\sigma$, and $P_{\sigma}$ is the transition matrix induced by $\sigma$.

704+705+

To solve this, you'll need to:

706+

1. Compute current rewards $r_{\sigma}(a, z) = u((1 + r)a + wz - \sigma(a, z))$

707+

2. Set up the linear operator $R_{\sigma}$ where $(R_{\sigma} v)(a, z) = v(a, z) - \beta \sum_{z'} v(\sigma(a, z), z') \Pi(z, z')$

708+

3. Solve $v_{\sigma} = R_{\sigma}^{-1} r_{\sigma}$ using `jax.scipy.sparse.linalg.bicgstab`

709+710+

You can use the `get_greedy` function that's already defined in this lecture.

711+712+

Implement the following Howard policy iteration routine:

713+714+

```python

715+

def howard_policy_iteration(household, prices,

716+

tol=1e-4, max_iter=10_000, verbose=False):

717+

"""

718+

Howard policy iteration routine.

719+

"""

720+

# Your code here

721+

pass

722+

```

723+724+

Once implemented, compute the equilibrium capital stock using HPI and verify that it produces approximately the same result as VFI at the default parameter values.

725+726+

```{exercise-end}

727+

```

728+729+

```{solution-start} aiyagari_ex3

730+

:class: dropdown

731+

```

732+733+

First, we need to implement the helper functions for Howard policy iteration.

734+735+

The following function computes the array $r_{\sigma}$ which gives current rewards given policy $\sigma$:

736+737+

```{code-cell} ipython3

738+

def compute_r_σ(σ, household, prices):

739+

"""

740+

Compute current rewards at each i, j under policy σ. In particular,

741+742+

r_σ[i, j] = u((1 + r)a[i] + wz[j] - a'[ip])

743+744+

when ip = σ[i, j].

745+

"""

746+

# Unpack

747+

β, a_grid, z_grid, Π = household

748+

a_size, z_size = len(a_grid), len(z_grid)

749+

r, w = prices

750+751+

# Compute r_σ[i, j]

752+

a = jnp.reshape(a_grid, (a_size, 1))

753+

z = jnp.reshape(z_grid, (1, z_size))

754+

ap = a_grid[σ]

755+

c = (1 + r) * a + w * z - ap

756+

r_σ = u(c)

757+758+

return r_σ

759+

```

760+761+

The linear operator $R_{\sigma}$ is defined as:

762+763+

```{code-cell} ipython3

764+

def R_σ(v, σ, household):

765+

# Unpack

766+

β, a_grid, z_grid, Π = household

767+

a_size, z_size = len(a_grid), len(z_grid)

768+769+

# Set up the array v[σ[i, j], jp]

770+

zp_idx = jnp.arange(z_size)

771+

zp_idx = jnp.reshape(zp_idx, (1, 1, z_size))

772+

σ = jnp.reshape(σ, (a_size, z_size, 1))

773+

V = v[σ, zp_idx]

774+775+

# Expand Π[j, jp] to Π[i, j, jp]

776+

Π = jnp.reshape(Π, (1, z_size, z_size))

777+778+

# Compute and return v[i, j] - β Σ_jp v[σ[i, j], jp] * Π[j, jp]

779+

return v - β * jnp.sum(V * Π, axis=-1)

780+

```

781+782+

The next function computes the lifetime value of a given policy:

783+784+

```{code-cell} ipython3

785+

def get_value(σ, household, prices):

786+

"""

787+

Get the lifetime value of policy σ by computing

788+789+

v_σ = R_σ^{-1} r_σ

790+

"""

791+

r_σ = compute_r_σ(σ, household, prices)

792+793+

# Reduce R_σ to a function in v

794+

_R_σ = lambda v: R_σ(v, σ, household)

795+796+

# Compute v_σ = R_σ^{-1} r_σ using an iterative routine.

797+

return jax.scipy.sparse.linalg.bicgstab(_R_σ, r_σ)[0]

798+

```

799+800+

Now we can implement Howard policy iteration:

801+802+

```{code-cell} ipython3

803+

@jax.jit

804+

def howard_policy_iteration(household, prices, tol=1e-4, max_iter=10_000):

805+

"""

806+

Howard policy iteration routine using a compiled JAX loop.

807+

"""

808+

β, a_grid, z_grid, Π = household

809+

a_size, z_size = len(a_grid), len(z_grid)

810+811+

def condition_function(loop_state):

812+

i, σ, v_σ, error = loop_state

813+

return jnp.logical_and(error > tol, i < max_iter)

814+815+

def update(loop_state):

816+

i, σ, v_σ, error = loop_state

817+

σ_new = get_greedy(v_σ, household, prices)

818+

v_σ_new = get_value(σ_new, household, prices)

819+

error = jnp.max(jnp.abs(v_σ_new - v_σ))

820+

return i + 1, σ_new, v_σ_new, error

821+822+

# Initial loop state

823+

σ_init = jnp.zeros((a_size, z_size), dtype=int)

824+

v_σ_init = get_value(σ_init, household, prices)

825+

loop_state_init = (0, σ_init, v_σ_init, tol + 1)

826+827+

# Run the fixed point iteration

828+

i, σ, v_σ, error = jax.lax.while_loop(condition_function, update, loop_state_init)

829+830+

return σ

831+

```

832+833+

Now let's create a modified version of the G function that uses HPI:

834+835+

```{code-cell} ipython3

836+

def G_hpi(K, firm, household):

837+

# Get prices r, w associated with K

838+

r = r_given_k(K, firm)

839+

w = r_to_w(r, firm)

840+841+

# Generate prices and compute aggregate capital using HPI.

842+

prices = Prices(r=r, w=w)

843+

σ_star = howard_policy_iteration(household, prices)

844+

return capital_supply(σ_star, household)

845+

```

846+847+

And compute the equilibrium using HPI:

848+849+

```{code-cell} ipython3

850+

def compute_equilibrium_bisect_hpi(firm, household, a=1.0, b=20.0):

851+

K = bisect(lambda k: k - G_hpi(k, firm, household), a, b, xtol=1e-4)

852+

return K

853+854+

firm = Firm()

855+

household = create_household()

856+

print("\nComputing equilibrium capital stock using HPI")

857+

with qe.Timer():

858+

K_star_hpi = compute_equilibrium_bisect_hpi(firm, household)

859+

print(f"Computed equilibrium capital stock with HPI: {K_star_hpi:.5}")

860+

print(f"Previous equilibrium capital stock with VFI: {K_star:.5}")

861+

print(f"Difference: {abs(K_star_hpi - K_star):.6}")

862+

```

863+864+

The results show that both methods produce approximately the same equilibrium, confirming that HPI is a valid alternative to VFI.

865+866+

```{solution-end}

867+

```

Read the original on github.com ↗