@@ -34,8 +34,8 @@ kernelspec:
3434This lecture builds on the job search model with separation presented in the
3535{doc}` previous lecture <mccall_model_with_separation> ` .
3636
37- The key difference is that wage offers now follow a ** Markov chain** rather than
38- being independent and identically distributed ( IID) .
37+ The key difference is that wage offers now follow a {doc} ` Markov chain <finite_markov> ` rather than
38+ being IID.
3939
4040This modification adds persistence to the wage offer process, meaning that
4141today's wage offer provides information about tomorrow's offer.
@@ -264,8 +264,7 @@ def T(v: jnp.ndarray, model: Model) -> jnp.ndarray:
264264 return jnp.maximum(accept, reject)
265265```
266266
267- Here's a routine for value function iteration, as well as a second routine that
268- computes the reservation wage directly from the value function.
267+ Here's a routine for value function iteration.
269268
270269``` {code-cell} ipython3
271270@jax.jit
@@ -293,67 +292,65 @@ def vfi(
293292 v_final, error, i = final_loop_state
294293
295294 return v_final
295+ ```
296296
297+ Here is a routine that computes the reservation wage from the value function.
297298
299+ ``` {code-cell} ipython3
298300@jax.jit
299301def get_reservation_wage(v: jnp.ndarray, model: Model) -> float:
300302 """
301- Calculate the reservation wage directly from the value function.
303+ Calculate the reservation wage from the unemployed agents
304+ value function v := v_u.
302305
303306 The reservation wage is the lowest wage w where accepting (v_e(w))
304- is at least as good as rejecting (u(c) + β(Pv)(w)).
305-
306- Parameters:
307- - v: Value function v_u
308- - model: Model instance containing parameters
307+ is at least as good as rejecting (u(c) + β(Pv_u)(w)).
309308
310- Returns:
311- - Reservation wage (lowest wage for which acceptance is optimal)
312309 """
313310 n, w_vals, P, P_cumsum, β, c, α, γ = model
314311
315312 # Compute accept and reject values
316313 d = 1 / (1 - β * (1 - α))
317- accept = d * (u(w_vals, γ) + α * β * P @ v)
318- reject = u(c, γ) + β * P @ v
314+ v_e = d * (u(w_vals, γ) + α * β * P @ v)
315+ continuation_value = u(c, γ) + β * P @ v
319316
320317 # Find where acceptance becomes optimal
321- should_accept = accept >= reject
322- first_accept_idx = jnp.argmax(should_accept)
318+ accept_indices = v_e >= continuation_value
319+ first_accept_idx = jnp.argmax(accept_indices) # index of first True
323320
324321 # If no acceptance (all False), return infinity
325322 # Otherwise return the wage at the first acceptance index
326- return jnp.where(jnp.any(should_accept ), w_vals[first_accept_idx], jnp.inf)
323+ return jnp.where(jnp.any(accept_indices ), w_vals[first_accept_idx], jnp.inf)
327324```
328325
326+
329327## Computing the Solution
330328
331329Let's solve the model:
332330
333331``` {code-cell} ipython3
334332model = create_js_with_sep_model()
335333n, w_vals, P, P_cumsum, β, c, α, γ = model
336- v_star = vfi(model)
337- w_star = get_reservation_wage(v_star , model)
334+ v_u = vfi(model)
335+ w_bar = get_reservation_wage(v_u , model)
338336```
339337
340338Next we compute some related quantities for plotting.
341339
342340``` {code-cell} ipython3
343341d = 1 / (1 - β * (1 - α))
344- accept = d * (u(w_vals, γ) + α * β * P @ v_star )
345- h_star = u(c, γ) + β * P @ v_star
342+ v_e = d * (u(w_vals, γ) + α * β * P @ v_u )
343+ h = u(c, γ) + β * P @ v_u
346344```
347345
348346Let's plot our results.
349347
350348``` {code-cell} ipython3
351349fig, ax = plt.subplots(figsize=(9, 5.2))
352- ax.plot(w_vals, h_star, linewidth=4, ls="--", alpha=0.4,
353- label="continuation value")
354- ax.plot(w_vals, accept, linewidth=4, ls="--", alpha=0.4,
355- label="stopping value")
356- ax.plot(w_vals, v_star, "k-", alpha=0.7, label=r"$v_u^*(w)$")
350+ ax.plot(w_vals, h, 'g-', linewidth=2,
351+ label="continuation value function $h$")
352+ ax.plot(w_vals, v_e, 'b-', linewidth=2,
353+ label="employment value function $v_e$")
357354ax.legend(frameon=False)
358355ax.set_xlabel(r"$w$")
359356plt.show()
@@ -370,16 +367,16 @@ Let's examine how reservation wages change with the separation rate.
370367``` {code-cell} ipython3
371368α_vals: jnp.ndarray = jnp.linspace(0.0, 1.0, 10)
372369
373- w_star_vec = []
370+ w_bar_vec = []
374371for α in α_vals:
375372 model = create_js_with_sep_model(α=α)
376- v_star = vfi(model)
377- w_star = get_reservation_wage(v_star , model)
378- w_star_vec .append(w_star )
373+ v_u = vfi(model)
374+ w_bar = get_reservation_wage(v_u , model)
375+ w_bar_vec .append(w_bar )
379376
380377fig, ax = plt.subplots(figsize=(9, 5.2))
381378ax.plot(
382- α_vals, w_star_vec , linewidth=2, alpha=0.6, label="reservation wage"
379+ α_vals, w_bar_vec , linewidth=2, alpha=0.6, label="reservation wage"
383380)
384381ax.legend(frameon=False)
385382ax.set_xlabel(r"$\alpha$")
@@ -414,7 +411,7 @@ unemployed, 1 if employed) and $w_t$ is
414411* their current wage, if employed.
415412
416413``` {code-cell} ipython3
417- def update_agent(key, status, wage_idx, model, w_star ):
414+ def update_agent(key, status, wage_idx, model, w_bar ):
418415 """
419416 Updates an agent's employment status and current wage.
420417
@@ -423,7 +420,7 @@ def update_agent(key, status, wage_idx, model, w_star):
423420 - status: Current employment status (0 or 1)
424421 - wage_idx: Current wage, recorded as an array index
425422 - model: Model instance
426- - w_star : Reservation wage
423+ - w_bar : Reservation wage
427424
428425 """
429426 n, w_vals, P, P_cumsum, β, c, α, γ = model
@@ -436,7 +433,7 @@ def update_agent(key, status, wage_idx, model, w_star):
436433 )
437434 separation_occurs = jax.random.uniform(key2) < α
438435 # Accept if current wage meets or exceeds reservation wage
439- accepts = w_vals[wage_idx] >= w_star
436+ accepts = w_vals[wage_idx] >= w_bar
440437
441438 # If employed: status = 1 if no separation, 0 if separation
442439 # If unemployed: status = 1 if accepts, 0 if rejects
@@ -462,7 +459,7 @@ Here's a function to simulate the employment path of a single agent.
462459``` {code-cell} ipython3
463460def simulate_employment_path(
464461 model: Model, # Model details
465- w_star : float, # Reservation wage
462+ w_bar : float, # Reservation wage
466463 T: int = 2_000, # Simulation length
467464 seed: int = 42 # Set seed for simulation
468465 ):
@@ -487,7 +484,7 @@ def simulate_employment_path(
487484
488485 key, subkey = jax.random.split(key)
489486 status, wage_idx = update_agent(
490- subkey, status, wage_idx, model, w_star
487+ subkey, status, wage_idx, model, w_bar
491488 )
492489
493490 return jnp.array(wage_path), jnp.array(status_path)
@@ -499,10 +496,10 @@ Let's create a comprehensive plot of the employment simulation:
499496model = create_js_with_sep_model()
500497
501498# Calculate reservation wage for plotting
502- v_star = vfi(model)
503- w_star = get_reservation_wage(v_star , model)
499+ v_u = vfi(model)
500+ w_bar = get_reservation_wage(v_u , model)
504501
505- wage_path, employment_status = simulate_employment_path(model, w_star )
502+ wage_path, employment_status = simulate_employment_path(model, w_bar )
506503
507504fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 6))
508505
@@ -518,8 +515,8 @@ ax1.set_ylim(-0.1, 1.1)
518515
519516# Plot wage path with employment status coloring
520517ax2.plot(wage_path, 'b-', alpha=0.7, linewidth=1)
521- ax2.axhline(y=w_star , color='black', linestyle='--', alpha=0.8,
522- label=f'Reservation wage: {w_star :.2f}')
518+ ax2.axhline(y=w_bar , color='black', linestyle='--', alpha=0.8,
519+ label=f'Reservation wage: {w_bar :.2f}')
523520ax2.set_xlabel('time')
524521ax2.set_ylabel('wage')
525522ax2.set_title('Wage path (actual and offers)')
@@ -620,7 +617,7 @@ We first create a vectorized version of `update_agent` to efficiently update all
620617
621618``` {code-cell} ipython3
622619# Create vectorized version of update_agent
623- # The last parameter is now w_star (scalar) instead of σ (array)
620+ # The last parameter is now w_bar (scalar) instead of σ (array)
624621update_agents_vmap = jax.vmap(
625622 update_agent, in_axes=(0, 0, 0, None, None)
626623)
@@ -633,7 +630,7 @@ Next we define the core simulation function, which uses `lax.fori_loop` to effic
633630def _simulate_cross_section_compiled(
634631 key: jnp.ndarray,
635632 model: Model,
636- w_star : float,
633+ w_bar : float,
637634 n_agents: int,
638635 T: int
639636 ):
@@ -653,7 +650,7 @@ def _simulate_cross_section_compiled(
653650 agent_keys = jax.random.split(subkey, n_agents)
654651
655652 status, wage_indices = update_agents_vmap(
656- agent_keys, status, wage_indices, model, w_star
653+ agent_keys, status, wage_indices, model, w_bar
657654 )
658655
659656 return key, status, wage_indices
@@ -688,12 +685,12 @@ def simulate_cross_section(
688685 key = jax.random.PRNGKey(seed)
689686
690687 # Solve for optimal reservation wage
691- v_star = vfi(model)
692- w_star = get_reservation_wage(v_star , model)
688+ v_u = vfi(model)
689+ w_bar = get_reservation_wage(v_u , model)
693690
694691 # Run JIT-compiled simulation
695692 final_status = _simulate_cross_section_compiled(
696- key, model, w_star , n_agents, T
693+ key, model, w_bar , n_agents, T
697694 )
698695
699696 # Calculate unemployment rate at final period
@@ -717,10 +714,10 @@ def plot_cross_sectional_unemployment(model: Model, t_snapshot: int = 200,
717714 """
718715 # Get final employment state directly
719716 key = jax.random.PRNGKey(42)
720- v_star = vfi(model)
721- w_star = get_reservation_wage(v_star , model)
717+ v_u = vfi(model)
718+ w_bar = get_reservation_wage(v_u , model)
722719 final_status = _simulate_cross_section_compiled(
723- key, model, w_star , n_agents, t_snapshot
720+ key, model, w_bar , n_agents, t_snapshot
724721 )
725722
726723 # Calculate unemployment rate
0 commit comments