GitHub

@@ -45,7 +45,7 @@ Let's put these algorithm and code optimizations to one side for now.

45454646

We will use the following imports:

474748-

```{code-cell} ipython

48+

```{code-cell} python3

4949

import matplotlib.pyplot as plt

5050

import numpy as np

5151

from scipy.optimize import minimize_scalar, bisect

@@ -142,14 +142,16 @@ The process looks like this:

142142

1. Begin with an array of values $\{ v_0, \ldots, v_I \}$ representing

143143

the values of some initial function $v$ on the grid points $\{ x_0, \ldots, x_I \}$.

144144

1. Build a function $\hat v$ on the state space $\mathbb R_+$ by

145-

linear interpolation, based on these data points.

146-

1. Obtain and record the value $T \hat v(x_i)$ on each grid point

147-

$x_i$ by repeatedly solving the maximization problem in the Bellman

148-

equation.

145+

interpolation, based on the interpolation points $\{(x_i, v_i)\}$.

146+

1. Insert $\hat v$ into the right hand side of the Bellman equation and

147+

obtain and record the value $T \hat v(x_i)$ on each grid point

148+

$x_i$.

149149

1. Unless some stopping condition is satisfied, set

150150

$\{ v_0, \ldots, v_I \} = \{ T \hat v(x_0), \ldots, T \hat v(x_I) \}$ and go to step 2.

151151152-

In step 2 we'll use continuous piecewise linear interpolation.

152+

In step 2 we'll use piecewise linear interpolation.

153+154+153155154156

### Implementation

155157

@@ -177,26 +179,31 @@ def maximize(g, a, b, args):

177179

We'll store the parameters $\beta$ and $\gamma$ and the grid in a

178180

`NamedTuple` called `Model`.

179181182+

We'll also create a helper function called `create_cake_eating_model` to store

183+

default parameters and build an instance of `Model`.

184+180185

```{code-cell} python3

181-

# Create model data structure

182186

class Model(NamedTuple):

183187

β: float

184188

γ: float

185189

x_grid: np.ndarray

186190187-

def create_cake_eating_model(β=0.96, # discount factor

188-

γ=1.5, # degree of relative risk aversion

189-

x_grid_min=1e-3, # exclude zero for numerical stability

190-

x_grid_max=2.5, # size of cake

191-

x_grid_size=120):

191+

def create_cake_eating_model(

192+

β: float = 0.96, # discount factor

193+

γ: float = 1.5, # degree of relative risk aversion

194+

x_grid_min: float = 1e-3, # exclude zero for numerical stability

195+

x_grid_max: float = 2.5, # size of cake

196+

x_grid_size: int = 120

197+

):

192198

"""

193199

Creates an instance of the cake eating model.

200+194201

"""

195202

x_grid = np.linspace(x_grid_min, x_grid_max, x_grid_size)

196-

return Model(β, γ=γ, x_grid=x_grid)

203+

return Model(β, γ, x_grid)

197204

```

198205199-

Now we define utility functions that operate on the model:

206+

Here's the CRRA utility function.

200207201208

```{code-cell} python3

202209

def u(c, γ):

@@ -207,33 +214,42 @@ def u(c, γ):

207214

return np.log(c)

208215

else:

209216

return (c ** (1 - γ)) / (1 - γ)

217+

```

210218211-

def u_prime(c, γ):

212-

"""

213-

First derivative of utility function.

214-

"""

215-

return c ** (-γ)

219+

The next function is the unmaximized right hand side of the Bellman equation.

220+221+

The array `v` is the current guess of $v$, stored as an array on the grid

222+

points.

216223217-

def state_action_value(c, x, v_array, model):

224+

```{code-cell} python3

225+

def state_action_value(

226+

c: float, # current consumption

227+

x: float, # the current state (remaining cake)

228+

v: np.ndarray, # current guess of the value function

229+

model: Model # instance of cake eating model

230+

):

218231

"""

219232

Right hand side of the Bellman equation given x and c.

233+220234

"""

235+

# Unpack

221236

β, γ, x_grid = model.β, model.γ, model.x_grid

222-

v = lambda x: np.interp(x, x_grid, v_array)

223-224-

return u(c, γ) + β * v(x - c)

237+

# Convert array into function

238+

vf = lambda x: np.interp(x, x_grid, v)

239+

# Return unmaximmized RHS of Bellman equation

240+

return u(c, γ) + β * vf(x - c)

225241

```

226242227243

We now define the Bellman operation:

228244229245

```{code-cell} python3

230-

def T(v, model):

246+

def T(

247+

v: np.ndarray, # current guess of the value function

248+

model: Model # instance of cake eating model

249+

):

231250

"""

232251

The Bellman operator. Updates the guess of the value function.

233252234-

* model is an instance of Model

235-

* v is an array representing a guess of the value function

236-237253

"""

238254

v_new = np.empty_like(v)

239255

@@ -261,34 +277,42 @@ $x$ grid point.

261277

x_grid = model.x_grid

262278

v = u(x_grid, model.γ) # Initial guess

263279

n = 12 # Number of iterations

264-265280

fig, ax = plt.subplots()

266281282+

# Initial plot

267283

ax.plot(x_grid, v, color=plt.cm.jet(0),

268284

lw=2, alpha=0.6, label='Initial guess')

269285286+

# Iterate

270287

for i in range(n):

271288

v = T(v, model) # Apply the Bellman operator

272289

ax.plot(x_grid, v, color=plt.cm.jet(i / n), lw=2, alpha=0.6)

273290291+

# One last update and plot

292+

v = T(v, model)

293+

ax.plot(x_grid, v, color=plt.cm.jet(0),

294+

lw=2, alpha=0.6, label='Final guess')

295+274296

ax.legend()

275297

ax.set_ylabel('value', fontsize=12)

276298

ax.set_xlabel('cake size $x$', fontsize=12)

277299

ax.set_title('Value function iterations')

278-279300

plt.show()

280301

```

281302282-

To do this more systematically, we introduce a wrapper function called

283-

`compute_value_function` that iterates until some convergence conditions are

284-

satisfied.

303+

To iterate more systematically, we introduce a wrapper function called

304+

`compute_value_function`.

305+306+

It's task is to iterate using $T$ until some convergence conditions are satisfied.

285307286308

```{code-cell} python3

287-

def compute_value_function(model,

288-

tol=1e-4,

289-

max_iter=1000,

290-

verbose=True,

291-

print_skip=25):

309+

def compute_value_function(

310+

model: Model,

311+

tol: float = 1e-4,

312+

max_iter: int = 1_000,

313+

verbose: bool = True,

314+

print_skip: int = 25

315+

):

292316293317

# Set up loop

294318

v = np.zeros(len(model.x_grid)) # Initial guess

@@ -367,6 +391,9 @@ working with policy functions.

367391

We will see that value function iteration can be avoided by iterating on a guess

368392

of the policy function instead.

369393394+

The policy function has less curvature and hence is easier to interpolate than

395+

the value function.

396+370397

These ideas will be explored over the next few lectures.

371398

```

372399

@@ -398,14 +425,14 @@ above.

398425

Here's the function:

399426400427

```{code-cell} python3

401-

def σ(model, v):

428+

def σ(

429+

v: np.ndarray, # current guess of the value function

430+

model: Model # instance of cake eating model

431+

):

402432

"""

403433

The optimal policy function. Given the value function,

404434

it finds optimal consumption in each state.

405435406-

* model is an instance of Model

407-

* v is a value function array

408-409436

"""

410437

c = np.empty_like(v)

411438

@@ -420,7 +447,7 @@ def σ(model, v):

420447

Now let's pass the approximate value function and compute optimal consumption:

421448422449

```{code-cell} python3

423-

c = σ(model, v)

450+

c = σ(v, model)

424451

```

425452426453

(pol_an)=

Read the original on github.com ↗