168168
169169+++
170170
171+ ### Optimal Policy
171172
172- ## Computational Approach
173-
174- To solve this problem, we use the employed worker's Bellman equation to express
175- $v_e$ in terms of $Pv_u$
173+ Once we have the solutions $v_e$ and $v_u$ to these Bellman equations, we can compute the optimal policy: accept at current wage offer $w$ if
176174
177175$$
178- v_e(w) =
179- \frac{1}{1-\beta(1-\alpha)} \cdot (u(w) + \alpha\beta(Pv_u)(w))
176+ v_e(w) ≥ u(c) + β(Pv_u)(w)
180177$$
181178
182- Next we substitute into the unemployed agent's Bellman equation to get
179+ The optimal policy turns out to be a reservation wage strategy: accept all wages above some threshold.
183180
184181+++
185182
186- $$
187- v_u(w) =
188- \max
189- \left\{
190- \frac{1}{1-\beta(1-\alpha)} \cdot (u(w) + \alpha\beta(Pv_u)(w)),
191- u(c) + \beta(Pv_u)(w)
192- \right\}
193- $$
194-
195- Then we use value function iteration to solve for $v_u$.
196-
197- With $v_u$ in hand, we can
198-
199- 1 . recover $v_e$ through the equations above and
200- 2 . compute optimal policy: accept if $v_e(w) ≥ u(c) + β(Pv_u)(w)$
201183
202- The optimal policy turns out to be a reservation wage strategy: accept all wages above some threshold.
184+ ## Code
203185
204- +++
186+ Let's now implement the model.
205187
206- ## Code
188+ ### Set Up
207189
208190The default utility function is a CRRA utility function
209191
@@ -251,7 +233,152 @@ def create_js_with_sep_model(
251233 return Model(n, w_vals, P, P_cumsum, β, c, α, γ)
252234```
253235
254- Here's the Bellman operator for the unemployed worker's value function:
236+
237+ ### Solution: First Pass
238+
239+ Let's put together a (not very efficient) routine for calculating the
240+ reservation wage.
241+
242+ (We will think carefully about efficiency below.)
243+
244+ It works by starting with guesses for $v_e$ and $v_u$ and iterating to
245+ convergence.
246+
247+ Here's are Bellman operators that update $v_u$ and $v_e$ respectively.
248+
249+
250+ ``` {code-cell} ipython3
251+ def T_u(model, v_u, v_e):
252+ """
253+ Apply the unemployment Bellman update rule and return new guess of v_u.
254+
255+ """
256+ n, w_vals, P, P_cumsum, β, c, α, γ = model
257+ h = u(c, γ) + β * P @ v_u
258+ v_u_new = jnp.maximum(v_e, h)
259+ return v_u_new
260+ ```
261+
262+ ``` {code-cell} ipython3
263+ def T_e(model, v_u, v_e):
264+ """
265+ Apply the employment Bellman update rule and return new guess of v_e.
266+
267+ """
268+ n, w_vals, P, P_cumsum, β, c, α, γ = model
269+ v_e_new = u(w_vals, γ) + β * ((1 - α) * v_e + α * P @ v_u)
270+ return v_e_new
271+ ```
272+
273+ Here's a routine to iterate to convergence and then compute the reservation
274+ wage.
275+
276+ ``` {code-cell} ipython3
277+ def solve_model_first_pass(
278+ model: Model, # instance containing default parameters
279+ v_u_init: jnp.ndarray, # initial condition for v_u
280+ v_e_init: jnp.ndarray, # initial condition for v_e
281+ tol: float=1e-6, # error tolerance
282+ max_iter: int=1_000, # maximum number of iterations for loop
283+ ):
284+ n, w_vals, P, P_cumsum, β, c, α, γ = model
285+ i = 0
286+ error = tol + 1
287+ v_u = v_u_init
288+ v_e = v_e_init
289+
290+ while i < max_iter and error > tol:
291+ v_u_next = T_u(model, v_u, v_e)
292+ v_e_next = T_e(model, v_u, v_e)
293+ error_u = jnp.max(jnp.abs(v_u_next - v_u))
294+ error_e = jnp.max(jnp.abs(v_e_next - v_e))
295+ error = jnp.maximum(error_u, error_e)
296+ v_u = v_u_next
297+ v_e = v_e_next
298+ i += 1
299+
300+ # Compute accept and reject values
301+ continuation_values = u(c, γ) + β * P @ v_u
302+
303+ # Find where acceptance becomes optimal
304+ accept_indices = v_e >= continuation_values
305+ first_accept_idx = jnp.argmax(accept_indices) # index of first True
306+
307+ # If no acceptance (all False), return infinity
308+ # Otherwise return the wage at the first acceptance index
309+ w_bar = jnp.where(
310+ jnp.any(accept_indices), w_vals[first_accept_idx], jnp.inf
311+ )
312+ return v_u, v_e, w_bar
313+ ```
314+
315+
316+ ### Road Test
317+
318+ Let's solve the model:
319+
320+ ``` {code-cell} ipython3
321+ model = create_js_with_sep_model()
322+ n, w_vals, P, P_cumsum, β, c, α, γ = model
323+ v_u_init = jnp.zeros(n)
324+ v_e_init = jnp.zeros(n)
325+ v_u, v_e, w_bar_first = solve_model_first_pass(model, v_u_init, v_e_init)
326+ ```
327+
328+ Next we compute the continuation values.
329+
330+ ``` {code-cell} ipython3
331+ h = u(c, γ) + β * P @ v_u
332+ ```
333+
334+ Let's plot our results.
335+
336+ ``` {code-cell} ipython3
337+ fig, ax = plt.subplots(figsize=(9, 5.2))
338+ ax.plot(w_vals, h, 'g-', linewidth=2,
339+ label="continuation value function $h$")
340+ ax.plot(w_vals, v_e, 'b-', linewidth=2,
341+ label="employment value function $v_e$")
342+ ax.legend(frameon=False)
343+ ax.set_xlabel(r"$w$")
344+ plt.show()
345+ ```
346+
347+ The reservation wage is at the intersection of $v_e$, and the continuation value
348+ function, which is the value of rejecting.
349+
350+
351+ ## Improving Efficiency
352+
353+ The solution method desribed above works fine but we can do much better.
354+
355+ First, we use the employed worker's Bellman equation to express
356+ $v_e$ in terms of $Pv_u$
357+
358+ $$
359+ v_e(w) =
360+ \frac{1}{1-\beta(1-\alpha)} \cdot (u(w) + \alpha\beta(Pv_u)(w))
361+ $$
362+
363+ Next we substitute into the unemployed agent's Bellman equation to get
364+
365+ +++
366+
367+ $$
368+ v_u(w) =
369+ \max
370+ \left\{
371+ \frac{1}{1-\beta(1-\alpha)} \cdot (u(w) + \alpha\beta(Pv_u)(w)),
372+ u(c) + \beta(Pv_u)(w)
373+ \right\}
374+ $$
375+
376+ Then we use value function iteration to solve for $v_u$.
377+
378+ With $v_u$ in hand, we can recover $v_e$ through the equations above and
379+ then compute the reservation wage.
380+
381+ Here's the new Bellman operator for the unemployed worker's value function:
255382
256383``` {code-cell} ipython3
257384def T(v: jnp.ndarray, model: Model) -> jnp.ndarray:
@@ -326,9 +453,7 @@ def get_reservation_wage(v: jnp.ndarray, model: Model) -> float:
326453```
327454
328455
329- ## Computing the Solution
330-
331- Let's solve the model:
456+ Let's solve the model using our new method:
332457
333458``` {code-cell} ipython3
334459model = create_js_with_sep_model()
@@ -337,6 +462,14 @@ v_u = vfi(model)
337462w_bar = get_reservation_wage(v_u, model)
338463```
339464
465+ Let's verify that both methods produce the same reservation wage:
466+
467+ ``` {code-cell} ipython3
468+ print(f"Reservation wage (first method): {w_bar_first:.6f}")
469+ print(f"Reservation wage (second method): {w_bar:.6f}")
470+ print(f"Difference: {abs(w_bar - w_bar_first):.2e}")
471+ ```
472+
340473Next we compute some related quantities for plotting.
341474
342475``` {code-cell} ipython3
@@ -358,9 +491,9 @@ ax.set_xlabel(r"$w$")
358491plt.show()
359492```
360493
361- The reservation wage is at the intersection of the stopping value function, which is
362- equal to $v_e$, and the continuation value function, which is the value of
363- rejecting
494+ The result is the same as before but we only iterate on one array --- and also
495+ our JAX code is more efficient.
496+
364497
365498## Sensitivity Analysis
366499
@@ -704,15 +837,14 @@ def simulate_cross_section(
704837This function generates a histogram showing the distribution of employment status across many agents:
705838
706839``` {code-cell} ipython3
707- def plot_cross_sectional_unemployment(model: Model, t_snapshot: int = 200,
708- n_agents: int = 20_000):
840+ def plot_cross_sectional_unemployment(
841+ model: Model,
842+ t_snapshot: int = 200, # Time of cross-sectional snapshot
843+ n_agents: int = 20_000 # Number of agents to simulate
844+ ):
709845 """
710846 Generate histogram of cross-sectional unemployment at a specific time.
711847
712- Parameters:
713- - model: Model instance with parameters
714- - t_snapshot: Time period at which to take the cross-sectional snapshot
715- - n_agents: Number of agents to simulate
716848 """
717849 # Get final employment state directly
718850 key = jax.random.PRNGKey(42)
@@ -743,7 +875,9 @@ def plot_cross_sectional_unemployment(model: Model, t_snapshot: int = 200,
743875 plt.show()
744876```
745877
746- Now let's compare the time-average unemployment rate (from a single agent's long simulation) with the cross-sectional unemployment rate (from many agents at a single point in time).
878+ Now let's compare the time-average unemployment rate (from a single agent's long
879+ simulation) with the cross-sectional unemployment rate (from many agents at a
880+ single point in time).
747881
748882We claimed above that these numbers will be approximately equal in large
749883samples, due to ergodicity.
@@ -790,6 +924,11 @@ plot_cross_sectional_unemployment(model_low_c)
790924Create a plot that investigates more carefully how the steady state cross-sectional unemployment rate
791925changes with unemployment compensation.
792926
927+ Try a range of values for unemployment compensation ` c ` , such as ` c = 0.2, 0.4, 0.6, 0.8, 1.0 ` .
928+ For each value, compute the steady-state cross-sectional unemployment rate and plot it against ` c ` .
929+
930+ What relationship do you observe between unemployment compensation and the unemployment rate?
931+
793932``` {exercise-end}
794933```
795934
0 commit comments