Skip to content

Commit 83b7d39

Browse files
authored
misc (#699)
1 parent a012652 commit 83b7d39

File tree

1 file changed

+47
-50
lines changed

1 file changed

+47
-50
lines changed

lectures/mccall_model_with_sep_markov.md

Lines changed: 47 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ kernelspec:
3434
This 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

4040
This modification adds persistence to the wage offer process, meaning that
4141
today'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
299301
def 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

331329
Let's solve the model:
332330

333331
```{code-cell} ipython3
334332
model = create_js_with_sep_model()
335333
n, 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

340338
Next we compute some related quantities for plotting.
341339

342340
```{code-cell} ipython3
343341
d = 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

348346
Let's plot our results.
349347

350348
```{code-cell} ipython3
351349
fig, 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$")
357354
ax.legend(frameon=False)
358355
ax.set_xlabel(r"$w$")
359356
plt.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 = []
374371
for α 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
380377
fig, ax = plt.subplots(figsize=(9, 5.2))
381378
ax.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
)
384381
ax.legend(frameon=False)
385382
ax.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
463460
def 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:
499496
model = 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
507504
fig, (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
520517
ax2.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}')
523520
ax2.set_xlabel('time')
524521
ax2.set_ylabel('wage')
525522
ax2.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)
624621
update_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
633630
def _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

Comments
 (0)