134134Here ** maximum lifetime value** means the value of {eq}` objective ` when
135135the worker makes optimal decisions at all future points in time.
136136
137- As we now show, these obtaining these functions is key to solving the new model.
137+ As we now show, obtaining these functions is key to solving the model.
138138
139139### The Bellman Equations
140140
@@ -199,27 +199,211 @@ enough information to solve for both $v_e$ and $v_u$.
199199
200200Once we have them in hand, we will be able to make optimal choices.
201201
202- (ast_mcm)=
203- ### A Simplifying Transformation
204202
205- Rather than jumping straight into solving these equations, let's see if we can
206- simplify them somewhat.
203+ ### The Reservation Wage
207204
208- (This process will be analogous to our {ref}` second pass <mm_op2> ` at the plain vanilla
209- McCall model, where we reduced the Bellman equation to an equation in an unknown
210- scalar value, rather than an unknown vector.)
211205
212- First, let
206+ Let
213207
214208``` {math}
215209:label: defh_mm
216210
217- h := u(c) + \beta \sum_{w' \in \mathbb W} v_u(w') q(w')
211+ h := u(c) + \beta \sum_{w' \in \mathbb W} v_u(w') q(w')
218212```
219213
220- be the continuation value associated with unemployment (the value of rejecting the current offer).
214+ This is the continuation value for an unemployed agent (the value of rejecting the current offer).
215+
216+ If we know $v_u$ then we can easily compute $h$.
221217
222- We can now write {eq}` bell2_mccall ` as
218+ From {eq}` bell2_mccall ` , we see that an unemployed agent accepts current offer $w$ if $v_e(w) \geq h$.
219+
220+ This means precisely that the value of accepting is higher than the value of rejecting.
221+
222+ It is clear that $v_e$ is (at least weakly) increasing in $w$, since the agent is never made worse off by a higher wage offer.
223+
224+ Hence, we can express the optimal choice as accepting wage offer $w$ if and only if $w \geq \bar w$,
225+ where the ** reservation wage** $\bar w$ is the first wage level $w$ such that
226+
227+ $$
228+ v_e(w) \geq h
229+ $$
230+
231+
232+
233+ ## Code
234+
235+ Let's now implement a solution method based on the two Bellman equations.
236+
237+ ### Set Up
238+
239+ In the code, you'll see that we use a class to store the various parameters and other
240+ objects associated with a given model.
241+
242+ This helps to tidy up the code and provides an object that's easy to pass to functions.
243+
244+ The default utility function is a CRRA utility function
245+
246+ ``` {code-cell} ipython3
247+ def u(c, γ):
248+ return (c**(1 - γ) - 1) / (1 - γ)
249+ ```
250+
251+ Also, here's a default wage distribution, based around the BetaBinomial
252+ distribution:
253+
254+ ``` {code-cell} ipython3
255+ n = 60 # n possible outcomes for w
256+ w_default = jnp.linspace(10, 20, n) # wages between 10 and 20
257+ a, b = 600, 400 # shape parameters
258+ dist = BetaBinomial(n-1, a, b) # distribution
259+ q_default = jnp.array(dist.pdf()) # probabilities as a JAX array
260+ ```
261+
262+ Here's our model class for the McCall model with separation.
263+
264+ ``` {code-cell} ipython3
265+ class Model(NamedTuple):
266+ α: float = 0.2 # job separation rate
267+ β: float = 0.98 # discount factor
268+ γ: float = 2.0 # utility parameter (CRRA)
269+ c: float = 6.0 # unemployment compensation
270+ w: jnp.ndarray = w_default # wage outcome space
271+ q: jnp.ndarray = q_default # probabilities over wage offers
272+ ```
273+
274+
275+ ### Operators
276+
277+ To iterate on the Bellman equations, we define two operators, one for each value function.
278+
279+ These operators take the current value functions as inputs and return updated versions.
280+
281+ ``` {code-cell} ipython3
282+ def T_u(model, v_u, v_e):
283+ """
284+ Apply the unemployment Bellman update rule.
285+
286+ """
287+ α, β, γ, c, w, q = model
288+ h = u(c, γ) + β * (v_u @ q)
289+ return jnp.maximum(v_e, h)
290+ ```
291+
292+ ``` {code-cell} ipython3
293+ def T_e(model, v_u, v_e):
294+ """
295+ Apply the employment Bellman update rule.
296+
297+ """
298+ α, β, γ, c, w, q = model
299+ v_e_new = u(w, γ) + β * ((1 - α) * v_e + α * (v_u @ q))
300+ return v_e_new
301+ ```
302+
303+
304+
305+ ### Iteration
306+
307+ Here's our iteration routine, which alternates between updating $v_u$ and $v_e$ until convergence.
308+
309+ We iterate until successive realizations are closer together than some small tolerance level.
310+
311+ ``` {code-cell} ipython3
312+ def solve_full_model(
313+ model,
314+ tol: float=1e-6,
315+ max_iter: int=1_000,
316+ ):
317+ """
318+ Solves for both value functions v_u and v_e iteratively.
319+
320+ """
321+ α, β, γ, c, w, q = model
322+ i = 0
323+ error = tol + 1
324+ v_e = v_u = w / (1 - β)
325+
326+ while i < max_iter and error > tol:
327+ v_u_next = T_u(model, v_u, v_e)
328+ v_e_next = T_e(model, v_u, v_e)
329+ error_u = jnp.max(jnp.abs(v_u_next - v_u))
330+ error_e = jnp.max(jnp.abs(v_e_next - v_e))
331+ error = jnp.max(jnp.array([error_u, error_e]))
332+ v_u = v_u_next
333+ v_e = v_e_next
334+ i += 1
335+
336+ return v_u, v_e
337+ ```
338+
339+
340+
341+ ### Computing the Reservation Wage
342+
343+ Now that we can solve for both value functions, let's compute the reservation wage.
344+
345+ Recall from above that the reservation wage $\bar w$ solves
346+ $v_e(\bar w) = h$, where $h$ is the continuation value defined in {eq}` defh_mm ` .
347+
348+ Let's compare $v_e$ and $h$ to see what they look like.
349+
350+ We'll use the default parameterizations found in the code above.
351+
352+ ``` {code-cell} ipython3
353+ model = Model()
354+ α, β, γ, c, w, q = model
355+ v_u, v_e = solve_full_model(model)
356+ h = u(c, γ) + β * (v_u @ q)
357+
358+ fig, ax = plt.subplots()
359+ ax.plot(w, v_e, 'b-', lw=2, alpha=0.7, label='$v_e$')
360+ ax.plot(w, [h] * len(w), 'g-', lw=2, alpha=0.7, label='$h$')
361+ ax.set_xlim(min(w), max(w))
362+ ax.legend()
363+ plt.show()
364+ ```
365+
366+ The value $v_e$ is increasing because higher $w$ generates a higher wage flow conditional on staying employed.
367+
368+ The reservation wage is the $w$ where these lines meet.
369+
370+ Let's compute this reservation wage explicitly:
371+
372+ ``` {code-cell} ipython3
373+ def compute_reservation_wage_full(model):
374+ """
375+ Computes the reservation wage using the full model solution.
376+ """
377+ α, β, γ, c, w, q = model
378+ v_u, v_e = solve_full_model(model)
379+ h = u(c, γ) + β * (v_u @ q)
380+ # Find the first w such that v_e(w) >= h
381+ accept = v_e >= h
382+ i = jnp.argmax(accept)
383+ w_bar = jnp.where(jnp.any(accept), w[i], jnp.inf)
384+ return w_bar
385+
386+ w_bar_full = compute_reservation_wage_full(model)
387+ print(f"Reservation wage (full model): {w_bar_full:.4f}")
388+ ```
389+
390+
391+
392+
393+ (ast_mcm)=
394+ ## A Simplifying Transformation
395+
396+ The approach above works, but iterating over two vector-valued functions is computationally expensive.
397+
398+ Let's return to the key equations and see if we can simplify them to reduce the problem to a single scalar equation.
399+
400+ (This process will be analogous to our {ref}` second pass <mm_op2> ` at the plain vanilla
401+ McCall model, where we reduced the Bellman equation to an equation in an unknown
402+ scalar value, rather than an unknown vector.)
403+
404+ First, recall $h$ as defined in {eq}` defh_mm ` .
405+
406+ Using $h$, we can now write {eq}` bell2_mccall ` as
223407
224408$$
225409v_u(w) = \max \left\{ v_e(w), \, h \right\}
@@ -291,28 +475,7 @@ h = u(c) + \beta \sum_{w' \in \mathbb W} \max \left\{ \frac{u(w') + \alpha(h - u
291475
292476This is a single scalar equation in $h$.
293477
294- ### The Reservation Wage
295-
296- Suppose we can use {eq}` bell_scalar ` to solve for $h$.
297-
298- Once we have $h$, we can obtain $v_e$ from {eq}` v_e_closed ` .
299478
300- We can then determine optimal behavior for the worker.
301-
302- From {eq}` bell2_mccall ` , we see that an unemployed agent accepts current offer
303- $w$ if $v_e(w) \geq h$.
304-
305- This means precisely that the value of accepting is higher than the value of rejecting.
306-
307- It is clear that $v_e$ is (at least weakly) increasing in $w$, since the agent is never made worse off by a higher wage offer.
308-
309- Hence, we can express the optimal choice as accepting wage offer $w$ if and only if
310-
311- $$
312- w \geq \bar w
313- \quad \text{where} \quad
314- \bar w \text{ solves } v_e(\bar w) = h
315- $$
316479
317480### Solving the Bellman Equations
318481
@@ -336,50 +499,11 @@ Once convergence is achieved, we can compute $v_e$ from {eq}`v_e_closed`.
336499
337500(It is possible to prove that {eq}` bell_iter ` converges via the Banach contraction mapping theorem.)
338501
339- ## Implementation
340-
341- Let's implement this iterative process.
342-
343- In the code, you'll see that we use a class to store the various parameters and other
344- objects associated with a given model.
345-
346- This helps to tidy up the code and provides an object that's easy to pass to functions.
347-
348- The default utility function is a CRRA utility function
349-
350- ``` {code-cell} ipython3
351- def u(c, γ):
352- return (c**(1 - γ) - 1) / (1 - γ)
353- ```
354-
355- Also, here's a default wage distribution, based around the BetaBinomial
356- distribution:
357-
358- ``` {code-cell} ipython3
359- n = 60 # n possible outcomes for w
360- w_default = jnp.linspace(10, 20, n) # wages between 10 and 20
361- a, b = 600, 400 # shape parameters
362- dist = BetaBinomial(n-1, a, b) # distribution
363- q_default = jnp.array(dist.pdf()) # probabilities as a JAX array
364- ```
365-
366- Here's our model class for the McCall model with separation.
367502
368- ``` {code-cell} ipython3
369- class Model(NamedTuple):
370- α: float = 0.2 # job separation rate
371- β: float = 0.98 # discount factor
372- γ: float = 2.0 # utility parameter (CRRA)
373- c: float = 6.0 # unemployment compensation
374- w: jnp.ndarray = w_default # wage outcome space
375- q: jnp.ndarray = q_default # probabilities over wage offers
376- ```
377503
378- Now we iterate until successive realizations are closer together than some small tolerance level.
379-
380- We then return the current iterate as an approximate solution.
504+ ## Implementation
381505
382- First, we define a function to compute $v_e$ from $h$:
506+ First, we define a function to compute $v_e$ from $h$ using {eq} ` v_e_closed ` .
383507
384508``` {code-cell} ipython3
385509def compute_v_e(model, h):
@@ -431,37 +555,6 @@ def solve_model(model, tol=1e-5, max_iter=2000):
431555 return v_e_final, h_final
432556```
433557
434- ### The Reservation Wage: First Pass
435-
436- The optimal choice of the agent is summarized by the reservation wage.
437-
438- As discussed above, the reservation wage is the $\bar w$ that solves
439- $v_e(\bar w) = h$ where $h$ is the continuation value.
440-
441- Let's compare $v_e$ and $h$ to see what they look like.
442-
443- We'll use the default parameterizations found in the code above.
444-
445- ``` {code-cell} ipython3
446- model = Model()
447- v_e, h = solve_model(model)
448-
449- fig, ax = plt.subplots()
450- ax.plot(model.w, v_e, 'b-', lw=2, alpha=0.7, label='$v_e$')
451- ax.plot(model.w, [h] * len(model.w),
452- 'g-', lw=2, alpha=0.7, label='$h$')
453- ax.set_xlim(min(model.w), max(model.w))
454- ax.legend()
455- plt.show()
456- ```
457-
458- The value $v_e$ is increasing because higher $w$ generates a higher wage flow conditional on staying employed.
459-
460-
461- The reservation wage is the $w$ where these lines meet.
462-
463-
464- ### Computing the Reservation Wage
465558
466559Here's a function ` compute_reservation_wage ` that takes an instance of ` Model `
467560and returns the associated reservation wage.
@@ -470,7 +563,7 @@ and returns the associated reservation wage.
470563def compute_reservation_wage(model):
471564 """
472565 Computes the reservation wage of an instance of the McCall model
473- by finding the smallest w such that v_e(w) >= h.
566+ by finding the smallest w such that v_e(w) >= h.
474567
475568 """
476569 # Find the first i such that v_e(w_i) >= h and return w[i]
@@ -482,6 +575,17 @@ def compute_reservation_wage(model):
482575 return w_bar
483576```
484577
578+ Let's verify that this simplified approach gives the same answer as the full model:
579+
580+ ``` {code-cell} ipython3
581+ w_bar_simplified = compute_reservation_wage(model)
582+ print(f"Reservation wage (simplified): {w_bar_simplified:.4f}")
583+ print(f"Reservation wage (full model): {w_bar_full:.4f}")
584+ print(f"Difference: {abs(w_bar_simplified - w_bar_full):.6f}")
585+ ```
586+
587+ As we can see, both methods produce essentially the same reservation wage, but the simplified method is much more efficient.
588+
485589Next we will investigate how the reservation wage varies with parameters.
486590
487591
0 commit comments