# 9. Job Search#

GPU

This lecture was built using a machine with JAX installed and access to a GPU.

To run this lecture on Google Colab, click on the “play” icon top right, select Colab, and set the runtime environment to include a GPU.

To run this lecture on your own machine, you need to install Google JAX.

In this lecture we study a basic infinite-horizon job search with Markov wage draws

The exercise at the end asks you to add recursive preferences and compare the result.

In addition to what’s in Anaconda, this lecture will need the following libraries:

```
!pip install quantecon
```

## Show code cell output

```
Requirement already satisfied: quantecon in /opt/conda/envs/quantecon/lib/python3.11/site-packages (0.7.2)
```

```
Requirement already satisfied: numba>=0.49.0 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from quantecon) (0.59.0)
Requirement already satisfied: numpy>=1.17.0 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from quantecon) (1.26.4)
Requirement already satisfied: requests in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from quantecon) (2.31.0)
Requirement already satisfied: scipy>=1.5.0 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from quantecon) (1.11.4)
Requirement already satisfied: sympy in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from quantecon) (1.12)
Requirement already satisfied: llvmlite<0.43,>=0.42.0dev0 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from numba>=0.49.0->quantecon) (0.42.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from requests->quantecon) (2.0.4)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from requests->quantecon) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from requests->quantecon) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from requests->quantecon) (2024.2.2)
Requirement already satisfied: mpmath>=0.19 in /opt/conda/envs/quantecon/lib/python3.11/site-packages (from sympy->quantecon) (1.3.0)
```

```
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
```

We use the following imports.

```
import matplotlib.pyplot as plt
import quantecon as qe
import jax
import jax.numpy as jnp
from collections import namedtuple
jax.config.update("jax_enable_x64", True)
```

## 9.1. Model#

We study an elementary model where

jobs are permanent

unemployed workers receive current compensation \(c\)

the wage offer distribution \(\{W_t\}\) is Markovian

the horizon is infinite

an unemployment agent discounts the future via discount factor \(\beta \in (0,1)\)

The wage process obeys

We discretize this using Tauchen’s method to produce a stochastic matrix \(P\)

Since jobs are permanent, the return to accepting wage offer \(w\) today is

The Bellman equation is

We solve this model using value function iteration.

Let’s set up a `namedtuple`

to store information needed to solve the model.

```
Model = namedtuple('Model', ('n', 'w_vals', 'P', 'β', 'c'))
```

The function below holds default values and populates the namedtuple.

```
def create_js_model(
n=500, # wage grid size
ρ=0.9, # wage persistence
ν=0.2, # wage volatility
β=0.99, # discount factor
c=1.0 # unemployment compensation
):
"Creates an instance of the job search model with Markov wages."
mc = qe.tauchen(n, ρ, ν)
w_vals, P = jnp.exp(mc.state_values), mc.P
P = jnp.array(P)
return Model(n, w_vals, P, β, c)
```

Here’s the Bellman operator.

```
@jax.jit
def T(v, model):
"""
The Bellman operator Tv = max{e, c + β E v} with
e(w) = w / (1-β) and (Ev)(w) = E_w[ v(W')]
"""
n, w_vals, P, β, c = model
h = c + β * P @ v
e = w_vals / (1 - β)
return jnp.maximum(e, h)
```

The next function computes the optimal policy under the assumption that \(v\) is the value function.

The policy takes the form

Here \(\mathbf 1\) is an indicator function.

The statement above means that the worker accepts (\(\sigma(w) = 1\)) when the value of stopping is higher than the value of continuing.

```
@jax.jit
def get_greedy(v, model):
"""Get a v-greedy policy."""
n, w_vals, P, β, c = model
e = w_vals / (1 - β)
h = c + β * P @ v
σ = jnp.where(e >= h, 1, 0)
return σ
```

Here’s a routine for value function iteration.

```
def vfi(model, max_iter=10_000, tol=1e-4):
"""Solve the infinite-horizon Markov job search model by VFI."""
print("Starting VFI iteration.")
v = jnp.zeros_like(model.w_vals) # Initial guess
i = 0
error = tol + 1
while error > tol and i < max_iter:
new_v = T(v, model)
error = jnp.max(jnp.abs(new_v - v))
i += 1
v = new_v
v_star = v
σ_star = get_greedy(v_star, model)
return v_star, σ_star
```

### 9.1.1. Computing the solution#

Let’s set up and solve the model.

```
model = create_js_model()
n, w_vals, P, β, c = model
%time v_star, σ_star = vfi(model)
```

```
Starting VFI iteration.
```

```
CPU times: user 307 ms, sys: 157 ms, total: 463 ms
Wall time: 441 ms
```

We run it again to eliminate compile time.

```
%time v_star, σ_star = vfi(model)
```

```
Starting VFI iteration.
CPU times: user 205 ms, sys: 76.9 ms, total: 282 ms
Wall time: 167 ms
```

We compute the reservation wage as the first \(w\) such that \(\sigma(w)=1\).

```
res_wage = w_vals[jnp.searchsorted(σ_star, 1.0)]
```

## 9.2. Exercise#

In the setting above, the agent is risk-neutral vis-a-vis future utility risk.

Now solve the same problem but this time assuming that the agent has risk-sensitive preferences, which are a type of nonlinear recursive preferences.

The Bellman equation becomes

When \(\theta < 0\) the agent is risk sensitive.

Solve the model when \(\theta = -0.1\) and compare your result to the risk neutral case.

Try to interpret your result.

Solution to Exercise 9.1

```
RiskModel = namedtuple('Model', ('n', 'w_vals', 'P', 'β', 'c', 'θ'))
def create_risk_sensitive_js_model(
n=500, # wage grid size
ρ=0.9, # wage persistence
ν=0.2, # wage volatility
β=0.99, # discount factor
c=1.0, # unemployment compensation
θ=-0.1 # risk parameter
):
"Creates an instance of the job search model with Markov wages."
mc = qe.tauchen(n, ρ, ν)
w_vals, P = jnp.exp(mc.state_values), mc.P
P = jnp.array(P)
return RiskModel(n, w_vals, P, β, c, θ)
@jax.jit
def T_rs(v, model):
"""
The Bellman operator Tv = max{e, c + β R v} with
e(w) = w / (1-β) and
(Rv)(w) = (1/θ) ln{E_w[ exp(θ v(W'))]}
"""
n, w_vals, P, β, c, θ = model
h = c + (β / θ) * jnp.log(P @ (jnp.exp(θ * v)))
e = w_vals / (1 - β)
return jnp.maximum(e, h)
@jax.jit
def get_greedy_rs(v, model):
" Get a v-greedy policy."
n, w_vals, P, β, c, θ = model
e = w_vals / (1 - β)
h = c + (β / θ) * jnp.log(P @ (jnp.exp(θ * v)))
σ = jnp.where(e >= h, 1, 0)
return σ
def vfi(model, max_iter=10_000, tol=1e-4):
"Solve the infinite-horizon Markov job search model by VFI."
print("Starting VFI iteration.")
v = jnp.zeros_like(model.w_vals) # Initial guess
i = 0
error = tol + 1
while error > tol and i < max_iter:
new_v = T_rs(v, model)
error = jnp.max(jnp.abs(new_v - v))
i += 1
v = new_v
v_star = v
σ_star = get_greedy_rs(v_star, model)
return v_star, σ_star
model_rs = create_risk_sensitive_js_model()
n, w_vals, P, β, c, θ = model_rs
%time v_star_rs, σ_star_rs = vfi(model_rs)
```

```
Starting VFI iteration.
```

```
CPU times: user 349 ms, sys: 141 ms, total: 490 ms
Wall time: 386 ms
```

We run it again to eliminate the compilation time.

```
%time v_star_rs, σ_star_rs = vfi(model_rs)
```

```
Starting VFI iteration.
```

```
CPU times: user 229 ms, sys: 191 ms, total: 420 ms
Wall time: 255 ms
```

```
res_wage_rs = w_vals[jnp.searchsorted(σ_star_rs, 1.0)]
```

```
fig, ax = plt.subplots()
ax.plot(w_vals, v_star, alpha=0.8, label="RN $v$")
ax.plot(w_vals, v_star_rs, alpha=0.8, label="RS $v$")
ax.vlines((res_wage,), 150, 400, ls='--', color='darkblue', alpha=0.5, label=r"RV $\bar w$")
ax.vlines((res_wage_rs,), 150, 400, ls='--', color='orange', alpha=0.5, label=r"RS $\bar w$")
ax.legend(frameon=False, fontsize=12, loc="lower right")
ax.set_xlabel("$w$", fontsize=12)
plt.show()
```

The figure shows that the reservation wage under risk sensitive preferences (RS \(\bar w\)) shifts down.

This makes sense – the agent does not like risk and hence is more inclined to accept the current offer, even when it’s lower.