@@ -88,20 +88,23 @@ The reservation wage depends on the wage offer distribution and the parameters
8888* $\gamma$, the offer arrival rate
8989* $c$, unemployment compensation
9090
91+ The wage offer distribution will be a discretized version of a lognormal distribution.
9192
92- The wage offer distribution will be a discretized version of the lognormal distribution $LN(\log(20),1)$ .
93+ We first define a function to create such a discrete distribution .
9394
94- We first define a function to create a discretized wage distribution:
9595
9696``` {code-cell} ipython3
9797def create_wage_distribution(
9898 max_wage: float,
9999 wage_grid_size: int,
100100 log_wage_mean: float
101101 ):
102- w_vec_temp = jnp.linspace(
103- 1e-8, max_wage, wage_grid_size + 1
104- )
102+ """
103+ Creates a discretized version of a lognormal density LN(log(m),1), where
104+ m is log_wage_mean.
105+
106+ """
107+ w_vec_temp = jnp.linspace(1e-8, max_wage, wage_grid_size + 1)
105108 cdf = stats.norm.cdf(
106109 jnp.log(w_vec_temp), loc=jnp.log(log_wage_mean), scale=1
107110 )
@@ -111,7 +114,9 @@ def create_wage_distribution(
111114 return w_vec, p_vec
112115```
113116
114- To illustrate the code, let's create a wage distribution and visualize it:
117+
118+ The cell below creates a discretized $LN(\log(20),1)$ wage distribution and
119+ plots it.
115120
116121``` {code-cell} ipython3
117122w_vec, p_vec = create_wage_distribution(170, 200, 20)
@@ -125,7 +130,12 @@ plt.show()
125130```
126131
127132
128- Now we define the utility function and the McCall model data structure:
133+ Now we organize the code for solving the McCall model, given a set of parameters.
134+
135+ For background on the model and our solution method, see the {doc}` lecture on the McCall model with separation <mccall_model_with_separation> `
136+
137+ Our first step is to define the utility function and the McCall model data structure.
138+
129139
130140``` {code-cell} ipython3
131141def u(c, σ=2.0):
@@ -188,11 +198,11 @@ def solve_mccall_model(mcm: McCallModel, tol=1e-5, max_iter=2000):
188198 """
189199 Iterates to convergence on the Bellman equations.
190200 """
191- def cond_fun (state):
201+ def cond (state):
192202 V, U, i, error = state
193203 return jnp.logical_and(error > tol, i < max_iter)
194204
195- def body_fun (state):
205+ def update (state):
196206 V, U, i, error = state
197207 V_new, U_new = T(mcm, V, U)
198208 error_1 = jnp.max(jnp.abs(V_new - V))
@@ -208,7 +218,7 @@ def solve_mccall_model(mcm: McCallModel, tol=1e-5, max_iter=2000):
208218
209219 init_state = (V_init, U_init, i_init, error_init)
210220 V_final, U_final, _, _ = jax.lax.while_loop(
211- cond_fun, body_fun , init_state
221+ cond, update , init_state
212222 )
213223 return V_final, U_final
214224```
@@ -312,6 +322,20 @@ Here
312322* $\bar w$ is the reservation wage determined by the parameters and
313323* $p$ is the wage offer distribution.
314324
325+ Wage offers across the population of workers are independent draws from $p$.
326+
327+ Here we calculate $\lambda$ at the default parameters:
328+
329+
330+ ``` {code-cell} ipython3
331+ mcm = create_mccall_model(w_vec=w_vec, p_vec=p_vec)
332+ V, U = solve_mccall_model(mcm)
333+ w_idx = jnp.searchsorted(V - U, 0)
334+ w_bar = jnp.where(w_idx == len(V), jnp.inf, mcm.w_vec[w_idx])
335+ λ = γ * jnp.sum(p_vec * (w_vec > w_bar))
336+ print(f"Job finding rate at default paramters ={λ}.")
337+ ```
338+
315339
316340
317341## Fiscal policy
@@ -371,10 +395,9 @@ Now we set up the infrastructure to compute optimal unemployment insurance level
371395First, we define a container for the economy's parameters:
372396
373397``` {code-cell} ipython3
374- class EconomyParameters (NamedTuple):
398+ class Economy (NamedTuple):
375399 """Parameters for the economy"""
376400 α: float
377- α_q: float # Quarterly (α is monthly)
378401 b: float
379402 d: float
380403 β: float
@@ -384,14 +407,20 @@ class EconomyParameters(NamedTuple):
384407 wage_grid_size: int
385408 max_wage: float
386409
387- def create_economy_params(α=0.013, b=0.0124, d=0.00822,
388- β=0.98, γ=1.0, σ=2.0,
389- log_wage_mean=20,
390- wage_grid_size=200,
391- max_wage=170) -> EconomyParameters:
392- """Create economy parameters with default values"""
393- α_q = (1-(1-α)**3) # Convert monthly to quarterly
394- return EconomyParameters(α=α, α_q=α_q, b=b, d=d, β=β, γ=γ, σ=σ,
410+ def create_economy(
411+ α=0.013,
412+ b=0.0124,
413+ d=0.00822,
414+ β=0.98,
415+ γ=1.0,
416+ σ=2.0,
417+ log_wage_mean=20,
418+ wage_grid_size=200,
419+ max_wage=170
420+ ) -> Economy:
421+ """
422+ Create an economy with a set of default values"""
423+ return Economy(α=α, b=b, d=d, β=β, γ=γ, σ=σ,
395424 log_wage_mean=log_wage_mean,
396425 wage_grid_size=wage_grid_size,
397426 max_wage=max_wage)
@@ -401,51 +430,69 @@ Next, we define a function that computes optimal worker behavior given policy pa
401430
402431``` {code-cell} ipython3
403432@jax.jit
404- def compute_optimal_quantities(c, τ,
405- params: EconomyParameters, w_vec, p_vec):
433+ def compute_optimal_quantities(
434+ c: float,
435+ τ: float,
436+ economy: Economy,
437+ w_vec: jnp.array,
438+ p_vec: jnp.array
439+ ):
406440 """
407441 Compute the reservation wage, job finding rate and value functions
408442 of the workers given c and τ.
443+
409444 """
410445 mcm = create_mccall_model(
411- α=params.α_q ,
412- β=params .β,
413- γ=params .γ,
446+ α=economy.α ,
447+ β=economy .β,
448+ γ=economy .γ,
414449 c=c-τ, # Post tax compensation
415- σ=params .σ,
450+ σ=economy .σ,
416451 w_vec=w_vec-τ, # Post tax wages
417452 p_vec=p_vec
418453 )
419454
455+ # Compute reservation wage under given parameters
420456 V, U = solve_mccall_model(mcm)
421457 w_idx = jnp.searchsorted(V - U, 0)
422458 w_bar = jnp.where(w_idx == len(V), jnp.inf, mcm.w_vec[w_idx])
423459
424- λ = params.γ * jnp.sum(p_vec * (w_vec - τ > w_bar))
460+ # Compute job finding rate
461+ λ = economy.γ * jnp.sum(p_vec * (w_vec - τ > w_bar))
462+
425463 return w_bar, λ, V, U
426464```
427465
466+
428467This function computes the steady state outcomes given unemployment insurance and tax levels:
429468
430469``` {code-cell} ipython3
431470@jax.jit
432- def compute_steady_state_quantities(c, τ,
433- params: EconomyParameters, w_vec, p_vec):
471+ def compute_steady_state_quantities(
472+ c, τ, economy: Economy, w_vec, p_vec
473+ ):
434474 """
435475 Compute the steady state unemployment rate given c and τ using optimal
436476 quantities from the McCall model and computing corresponding steady
437477 state quantities
478+
438479 """
439- w_bar, λ, V, U = compute_optimal_quantities(c, τ,
440- params, w_vec, p_vec)
441480
442- # Compute steady state employment and unemployment rates
443- model = create_lake_model(λ=λ, α=params.α_q, b=params.b, d=params.d)
481+ # Find optimal values and policies by solving the McCall model, as well
482+ # as the corresponding job finding rate.
483+ w_bar, λ, V, U = compute_optimal_quantities(c, τ, economy, w_vec, p_vec)
484+
485+ # Set up a lake model using the given parameters and the job finding rate.
486+ model = create_lake_model(λ=λ, α=economy.α, b=economy.b, d=economy.d)
487+
488+ # Compute steady state employment and unemployment rates from this lake
489+ model.
444490 u, e = rate_steady_state(model)
445491
446- # Compute steady state welfare
492+ # Compute expected lifetime value conditional on being employed.
447493 mask = (w_vec - τ > w_bar)
448494 w = jnp.sum(V * p_vec * mask) / jnp.sum(p_vec * mask)
495+ # Compute steady state welfare.
449496 welfare = e * w + u * U
450497
451498 return e, u, welfare
@@ -454,25 +501,30 @@ def compute_steady_state_quantities(c, τ,
454501We need a function to find the tax rate that balances the government budget:
455502
456503``` {code-cell} ipython3
457- def find_balanced_budget_tax(c, params: EconomyParameters,
458- w_vec, p_vec):
504+ def find_balanced_budget_tax(c, economy: Economy, w_vec, p_vec):
459505 """
460- Find the tax level that will induce a balanced budget
506+ Find the tax rate that will induce a balanced budget given unemployment
507+ compensation c.
508+
461509 """
510+
462511 def steady_state_budget(t):
463- e, u, w = compute_steady_state_quantities(c, t,
464- params, w_vec, p_vec)
512+ """
513+ For given tax rate t, compute the budget surplus.
514+
515+ """
516+ e, u, w = compute_steady_state_quantities(c, t, economy, w_vec, p_vec)
465517 return t - u * c
466518
467- # Use a simple bisection method
519+ # Use a simple bisection method to find the tax rate that balances the
520+ # budget (but setting the surplus to zero
521+
468522 t_low, t_high = 0.0, 0.9 * c
469523 tol = 1e-6
470524 max_iter = 100
471-
472525 for i in range(max_iter):
473526 t_mid = (t_low + t_high) / 2
474527 budget = steady_state_budget(t_mid)
475-
476528 if abs(budget) < tol:
477529 return t_mid
478530 elif budget < 0:
@@ -486,24 +538,25 @@ def find_balanced_budget_tax(c, params: EconomyParameters,
486538Now we compute how employment, unemployment, taxes, and welfare vary with the unemployment compensation rate:
487539
488540``` {code-cell} ipython3
489- # Create economy parameters and wage distribution
490- params = create_economy_params ()
491- w_vec, p_vec = create_wage_distribution(params.max_wage,
492- params .wage_grid_size,
493- params.log_wage_mean )
541+ # Create economy and wage distribution
542+ economy = create_economy ()
543+ w_vec, p_vec = create_wage_distribution(
544+ economy.max_wage, economy .wage_grid_size, economy.log_wage_mean
545+ )
494546
495547# Levels of unemployment insurance we wish to study
496- c_vec = jnp.linspace(5, 140, 60 )
548+ c_vec = jnp.linspace(5, 140, 40 )
497549
498550tax_vec = []
499551unempl_vec = []
500552empl_vec = []
501553welfare_vec = []
502554
503555for c in c_vec:
504- t = find_balanced_budget_tax(c, params, w_vec, p_vec)
505- e_rate, u_rate, welfare = compute_steady_state_quantities(c, t, params,
506- w_vec, p_vec)
556+ t = find_balanced_budget_tax(c, economy, w_vec, p_vec)
557+ e_rate, u_rate, welfare = compute_steady_state_quantities(
558+ c, t, economy, w_vec, p_vec
559+ )
507560 tax_vec.append(t)
508561 unempl_vec.append(u_rate)
509562 empl_vec.append(e_rate)
@@ -535,11 +588,14 @@ The level that maximizes steady state welfare is approximately 62.
535588``` {exercise}
536589:label: endogenous_lake_ex1
537590
538- How does the welfare-maximizing level of unemployment compensation $c$ change with the job separation rate $\alpha$?
591+ How does the welfare-maximizing level of unemployment compensation $c$ change
592+ with the job separation rate $\alpha$?
539593
540- Compute and plot the optimal $c$ (the value that maximizes welfare) for a range of separation rates $\alpha$ from 0.01 to 0.025.
594+ Compute and plot the optimal $c$ (the value that maximizes welfare) for a range
595+ of separation rates $\alpha$ from 0.01 to 0.04.
541596
542- For each $\alpha$ value, find the optimal $c$ by computing welfare across the range of $c$ values and selecting the maximum.
597+ For each $\alpha$ value, find the optimal $c$ by computing welfare across the
598+ range of $c$ values and selecting the maximum.
543599```
544600
545601``` {solution-start} endogenous_lake_ex1
@@ -549,32 +605,46 @@ For each $\alpha$ value, find the optimal $c$ by computing welfare across the ra
549605Here is one solution:
550606
551607``` {code-cell} ipython3
552- # Range of separation rates to explore
553- α_values = jnp.linspace(0.01, 0.025, 15 )
608+ # Range of separation rates to explore (wider range, fewer points)
609+ α_values = jnp.linspace(0.01, 0.04, 8 )
554610
555611# We'll store the optimal c for each α
556612optimal_c_values = []
557613
614+ # Use a finer grid for c values to get better resolution
615+ c_vec_fine = jnp.linspace(5, 140, 150)
616+
558617for α_val in α_values:
559618 # Create economy parameters with this α
560- params_α = create_economy_params (α=α_val)
619+ params_α = create_economy (α=α_val)
561620
562621 # Create wage distribution
563- w_vec_α, p_vec_α = create_wage_distribution(params_α.max_wage,
564- params_α.wage_grid_size,
565- params_α.log_wage_mean )
622+ w_vec_α, p_vec_α = create_wage_distribution(
623+ params_α.max_wage, params_α.wage_grid_size, params_α.log_wage_mean
624+ )
566625
567626 # Compute welfare for each c value
568627 welfare_values = []
569- for c in c_vec :
628+ for c in c_vec_fine :
570629 t = find_balanced_budget_tax(c, params_α, w_vec_α, p_vec_α)
571- e_rate, u_rate, welfare = compute_steady_state_quantities(c, t, params_α,
572- w_vec_α, p_vec_α)
630+ e_rate, u_rate, welfare = compute_steady_state_quantities(
631+ c, t, params_α, w_vec_α, p_vec_α
632+ )
573633 welfare_values.append(welfare)
574634
575- # Find the c that maximizes welfare
576- max_idx = jnp.argmax(jnp.array(welfare_values))
577- optimal_c = c_vec[max_idx]
635+ # The welfare function is very flat near its maximum.
636+ # Using argmax on a single point can be unstable due to numerical noise.
637+ # Instead, we find all c values within 99.9% of maximum welfare and
638+ # compute their weighted average (centroid). This gives a more stable
639+ # estimate of the optimal unemployment compensation level.
640+ welfare_array = jnp.array(welfare_values)
641+ max_welfare = jnp.max(welfare_array)
642+ threshold = 0.999 * max_welfare
643+ near_optimal_mask = welfare_array >= threshold
644+
645+ # Compute weighted average of c values in the near-optimal region
646+ optimal_c = jnp.sum(c_vec_fine * near_optimal_mask * welfare_array) / \
647+ jnp.sum(near_optimal_mask * welfare_array)
578648 optimal_c_values.append(optimal_c)
579649
580650# Plot the relationship
@@ -588,7 +658,14 @@ plt.tight_layout()
588658plt.show()
589659```
590660
591- We see that as the separation rate increases (workers lose their jobs more frequently), the welfare-maximizing level of unemployment compensation also increases. This makes intuitive sense: when job loss is more common, more generous unemployment insurance becomes more valuable for smoothing consumption and maintaining worker welfare.
661+ We see that as the separation rate increases (workers lose their jobs more
662+ frequently), the welfare-maximizing level of unemployment compensation
663+ decreases.
664+
665+ This occurs because higher separation rates increase steady-state unemployment,
666+ which raises the tax burden needed to finance unemployment benefits. The
667+ optimal policy balances insurance against distortionary taxation.
668+
592669
593670``` {solution-end}
594671```
0 commit comments