GitHub

@@ -512,16 +512,16 @@ We first create a class to set up a particular parametrization.

512512

class params_instance:

513513514514

def __init__(self,

515-

r,

516-

β = 0.999,

517-

σ = 0.500,

518-

w = 100,

519-

n_grid = 50):

515+

r,

516+

β=0.999,

517+

σ=0.500,

518+

w=100,

519+

n_grid=50):

520520521-

self.β,self.σ,self.w,self.r = β,σ,w,r

521+

self.β, self.σ, self.w, self.r = β, σ, w, r

522522

self.n_grid = n_grid

523-

uw = self.w**(1-self.σ)/(1-self.σ) #Utility from consuming all wage

524-

self.Ve = uw/(1-β)

523+

uw = self.w**(1 - self.σ) / (1 - self.σ) # Utility from consuming all wage

524+

self.Ve = uw / (1 - β)

525525

```

526526527527

### Parameter values

@@ -538,21 +538,25 @@ First, we create some helper functions.

538538539539

```{code-cell} ipython3

540540

# The probability of finding a job given search effort, a and parameter r.

541-

def p(a,r):

542-

return 1-np.exp(-r*a)

541+

def p(a, r):

542+

return 1 - np.exp(-r * a)

543543544-

def invp_prime(x,r):

545-

return -np.log(x/r)/r

546544547-

def p_prime(a,r):

548-

return r*np.exp(-r*a)

545+

def invp_prime(x, r):

546+

return -np.log(x / r) / r

549547550-

# The utiliy function

551-

def u(self,c):

552-

return (c**(1-self.σ))/(1-self.σ)

553548554-

def u_inv(self,x):

555-

return ((1-self.σ)*x)**(1/(1-self.σ))

549+

def p_prime(a, r):

550+

return r * np.exp(-r * a)

551+552+553+

# The utility function

554+

def u(self, c):

555+

return (c**(1 - self.σ)) / (1 - self.σ)

556+557+558+

def u_inv(self, x):

559+

return ((1 - self.σ) * x)**(1 / (1 - self.σ))

556560

```

557561558562

Recall that under autarky the value for an unemployed worker

@@ -585,12 +589,12 @@ We'll soon use this as an input to computing $V^u$.

585589

```{code-cell} ipython3

586590

# The error in the Bellman equation that requires equality at

587591

# the optimal choices.

588-

def Vu_error(self,Vu,r):

589-

β= self.β

592+

def Vu_error(self, Vu, r):

593+

β = self.β

590594

Ve = self.Ve

591595592-

a = invp_prime(1/(β*(Ve-Vu)),r)

593-

error = u(self,0) -a + β*(p(a,r)*Ve + (1-p(a,r))*Vu) - Vu

596+

a = max(0, invp_prime(1 / (β * (Ve - Vu)), r))

597+

error = u(self, 0) - a + β * (p(a, r) * Ve + (1 - p(a, r)) * Vu) - Vu

594598

return error

595599

```

596600

@@ -602,36 +606,36 @@ We'll use this to compute a calibrated $r^*$.

602606603607

```{code-cell} ipython3

604608

# The error of our p(a^*) relative to our calibration target

605-

def r_error(self,r):

609+

def r_error(self, r):

606610

β = self.β

607611

Ve = self.Ve

608612609-

Vu_star = sp.optimize.fsolve(Vu_error_Λ,15000,args = (r))

610-

a_star = invp_prime(1/(β*(Ve-Vu_star)),r) # Assuming a>0

611-

return p(a_star,r) - 0.1

613+

Vu_star = sp.optimize.fsolve(Vu_error_Λ, 15000, args=(r,))[0]

614+

a_star = invp_prime(1 / (β * (Ve - Vu_star)), r) # Assuming a > 0

615+

return p(a_star, r) - 0.1

612616

```

613617614618

Now, let us create an instance of the model with our parametrization

615619616620

```{code-cell} ipython3

617-

params = params_instance(r = 1e-2)

621+

params = params_instance(r=1e-2)

618622

# Create some lambda functions useful for fsolve function

619-

Vu_error_Λ = lambda Vu,r: Vu_error(params,Vu,r)

620-

r_error_Λ = lambda r: r_error(params,r)

623+

Vu_error_Λ = lambda Vu, r: Vu_error(params, Vu, r)

624+

r_error_Λ = lambda r: r_error(params, r)

621625

```

622626623627

We want to compute an $r$ that is consistent with the hazard rate 0.1 in autarky.

624628625629

To do so, we will use a bisection strategy.

626630627631

```{code-cell} ipython3

628-

r_calibrated = sp.optimize.brentq(r_error_Λ,1e-10,1-1e-10)

632+

r_calibrated = sp.optimize.brentq(r_error_Λ, 1e-10, 1 - 1e-10)

629633

print(f"Parameter to match 0.1 hazard rate: r = {r_calibrated}")

630634631-

Vu_aut = sp.optimize.fsolve(Vu_error_Λ,15000,args = (r_calibrated))[0]

632-

a_aut = invp_prime(1/(params.β*(params.Ve-Vu_aut)),r_calibrated)

635+

Vu_aut = sp.optimize.fsolve(Vu_error_Λ, 15000, args=(r_calibrated,))[0]

636+

a_aut = invp_prime(1 / (params.β * (params.Ve - Vu_aut)), r_calibrated)

633637634-

print(f"Check p at r: {p(a_aut,r_calibrated)}")

638+

print(f"Check p at r: {p(a_aut, r_calibrated)}")

635639

```

636640637641

Now that we have calibrated our the parameter $r$, we can continue with solving the model with private information.

@@ -664,25 +668,25 @@ We can substitute these equations for $c$ and $a$ and obtain the functional equa

664668665669666670

```{code-cell} ipython3

667-

def calc_c(self,Vu,V,a):

671+

def calc_c(self, Vu, V, a):

668672

'''

669673

Calculates the optimal consumption choice coming from the constraint of the insurer's problem

670674

(which is also a Bellman equation)

671675

'''

672-

β,Ve,r = self.β,self.Ve,self.r

676+

β, Ve, r = self.β, self.Ve, self.r

673677674-

c = u_inv(self,V + a - β*(p(a,r)*Ve + (1-p(a,r))*Vu))

678+

c = u_inv(self, V + a - β * (p(a, r) * Ve + (1 - p(a, r)) * Vu))

675679

return c

676680677-

def calc_a(self,Vu):

681+682+

def calc_a(self, Vu):

678683

'''

679684

Calculates the optimal effort choice coming from the worker's effort optimality condition.

680685

'''

686+

r, β, Ve = self.r, self.β, self.Ve

681687682-

r,β,Ve = self.r,self.β,self.Ve

683-684-

a_temp = np.log(r*β*(Ve - Vu))/r

685-

a = max(0,a_temp)

688+

a_temp = np.log(r * β * (Ve - Vu)) / r

689+

a = max(0, a_temp)

686690

return a

687691

```

688692

@@ -704,12 +708,11 @@ The function `iterate_C` below executes step 3 in the above algorithm.

704708705709

```{code-cell} ipython3

706710

# Operator iterate_C that calculates the next iteration of the cost function.

707-

def iterate_C(self,C_old,Vu_grid):

708-711+

def iterate_C(self, C_old, Vu_grid):

709712

'''

710713

We solve the model by minimising the value function across a grid of possible promised values.

711714

'''

712-

β,r,n_grid = self.β,self.r,self.n_grid

715+

β, r, n_grid = self.β, self.r, self.n_grid

713716714717

C_new = np.zeros(n_grid)

715718

cons_star = np.zeros(n_grid)

@@ -725,50 +728,51 @@ def iterate_C(self,C_old,Vu_grid):

725728

a_Vi_temp = np.zeros(n_grid)

726729727730

for Vu_i in range(n_grid):

728-

a_i = calc_a(self,Vu_grid[Vu_i])

729-

c_i = calc_c(self,Vu_grid[Vu_i],Vu_grid[V_i],a_i)

731+

a_i = calc_a(self, Vu_grid[Vu_i])

732+

c_i = calc_c(self, Vu_grid[Vu_i], Vu_grid[V_i], a_i)

730733731-

C_Vi_temp[Vu_i] = c_i + β*(1-p(a_i,r))*C_old[Vu_i]

734+

C_Vi_temp[Vu_i] = c_i + β * (1 - p(a_i, r)) * C_old[Vu_i]

732735

cons_Vi_temp[Vu_i] = c_i

733736

a_Vi_temp[Vu_i] = a_i

734737735738

# Interpolate across the grid to get better approximation of the minimum

736-

C_Vi_temp_interp = sp.interpolate.interp1d(Vu_grid,C_Vi_temp, kind = 'cubic')

737-

cons_Vi_temp_interp = sp.interpolate.interp1d(Vu_grid,cons_Vi_temp, kind = 'cubic')

738-

a_Vi_temp_interp = sp.interpolate.interp1d(Vu_grid,a_Vi_temp, kind = 'cubic')

739+

C_Vi_temp_interp = sp.interpolate.interp1d(Vu_grid, C_Vi_temp, kind='cubic')

740+

cons_Vi_temp_interp = sp.interpolate.interp1d(Vu_grid, cons_Vi_temp, kind='cubic')

741+

a_Vi_temp_interp = sp.interpolate.interp1d(Vu_grid, a_Vi_temp, kind='cubic')

739742740-

res = sp.optimize.minimize_scalar(C_Vi_temp_interp,method='bounded',bounds = (Vu_min,Vu_max))

743+

res = sp.optimize.minimize_scalar(C_Vi_temp_interp, method='bounded',

744+

bounds=(Vu_min, Vu_max))

741745

V_star[V_i] = res.x

742746

C_new[V_i] = res.fun

743747744-

# Save the associated consumpton and search policy functions as well

748+

# Save the associated consumption and search policy functions as well

745749

cons_star[V_i] = cons_Vi_temp_interp(V_star[V_i])

746750

a_star[V_i] = a_Vi_temp_interp(V_star[V_i])

747751748-

return C_new,V_star,cons_star,a_star

752+

return C_new, V_star, cons_star, a_star

749753

```

750754751755

The following code executes steps 4 and 5 in the Algorithm until convergence to a function $C^*(V)$.

752756753757

```{code-cell} ipython3

754-

def solve_incomplete_info_model(self,Vu_grid,Vu_aut,tol = 1e-6,max_iter = 10000):

758+

def solve_incomplete_info_model(self, Vu_grid, Vu_aut, tol=1e-6, max_iter=10000):

755759

iter = 0

756760

error = 1

757761758-

C_init = np.ones(self.n_grid)*0

762+

C_init = np.ones(self.n_grid) * 0

759763

C_old = np.copy(C_init)

760764761-

while iter<max_iter and error >tol:

762-

C_new,V_new,cons_star,a_star = iterate_C(self,C_old,Vu_grid)

765+

while iter < max_iter and error > tol:

766+

C_new, V_new, cons_star, a_star = iterate_C(self, C_old, Vu_grid)

763767

error = np.max(np.abs(C_new - C_old))

764768765-

#Only print the iterations every 50 steps

766-

if iter % 50 ==0:

769+

# Only print the iterations every 50 steps

770+

if iter % 50 == 0:

767771

print(f"Iteration: {iter}, error:{error}")

768772

C_old = np.copy(C_new)

769-

iter+=1

773+

iter += 1

770774771-

return C_new,V_new,cons_star,a_star

775+

return C_new, V_new, cons_star, a_star

772776

```

773777774778

## Outcomes

@@ -778,22 +782,23 @@ def solve_incomplete_info_model(self,Vu_grid,Vu_aut,tol = 1e-6,max_iter = 10000)

778782

Using the above functions, we create another instance of the parameters with our calibrated parameter $r$.

779783780784

```{code-cell} ipython3

781-

##? Create another instance with the correct r now

782-

params = params_instance(r = r_calibrated)

785+

# Create another instance with the correct r now

786+

params = params_instance(r=r_calibrated)

783787784-

#Set up grid

788+

# Set up grid

785789

Vu_min = Vu_aut

786-

Vu_max = params.Ve - 1/(params.β*p_prime(0,params.r))

787-

Vu_grid = np.linspace(Vu_min,Vu_max,params.n_grid)

790+

Vu_max = params.Ve - 1 / (params.β * p_prime(0, params.r))

791+

Vu_grid = np.linspace(Vu_min, Vu_max, params.n_grid)

788792789-

#Solve model

790-

C_star,V_star,cons_star,a_star = solve_incomplete_info_model(params,Vu_grid,Vu_aut,tol = 1e-6,max_iter = 10000) #,cons_star,a_star

793+

# Solve model

794+

C_star, V_star, cons_star, a_star = solve_incomplete_info_model(

795+

params, Vu_grid, Vu_aut, tol=1e-6, max_iter=10000)

791796792797

# Since we have the policy functions in grid form, we will interpolate them to be able to

793798

# evaluate any promised value

794-

cons_star_interp = sp.interpolate.interp1d(Vu_grid,cons_star)

795-

a_star_interp = sp.interpolate.interp1d(Vu_grid,a_star)

796-

V_star_interp = sp.interpolate.interp1d(Vu_grid,V_star)

799+

cons_star_interp = sp.interpolate.interp1d(Vu_grid, cons_star)

800+

a_star_interp = sp.interpolate.interp1d(Vu_grid, a_star)

801+

V_star_interp = sp.interpolate.interp1d(Vu_grid, V_star)

797802

```

798803799804

### Replacement ratios and continuation values

@@ -809,47 +814,50 @@ We accomplish this by using the optimal policy functions `V_star`, `cons_star` a

809814

```{code-cell} ipython3

810815

# Replacement ratio and effort as a function of unemployment duration

811816

T_max = 52

812-

Vu_t = np.empty((T_max,3))

813-

cons_t = np.empty((T_max-1,3))

814-

a_t = np.empty((T_max-1,3))

817+

Vu_t = np.empty((T_max, 3))

818+

cons_t = np.empty((T_max - 1, 3))

819+

a_t = np.empty((T_max - 1, 3))

815820816821

# Calculate the replacement ratios depending on different initial

817822

# promised values

818-

Vu_0_hold = np.array([Vu_aut,16942,17000])

823+

Vu_0_hold = np.array([Vu_aut, 16942, 17000])

819824

```

820825821826

```{code-cell} ipython3

822-

for i,Vu_0, in enumerate(Vu_0_hold):

823-

Vu_t[0,i] = Vu_0

824-

for t in range(1,T_max):

825-

cons_t[t-1,i] = cons_star_interp(Vu_t[t-1,i])

826-

a_t[t-1,i] = a_star_interp(Vu_t[t-1,i])

827-

Vu_t[t,i] = V_star_interp(Vu_t[t-1,i])

827+

for i, Vu_0 in enumerate(Vu_0_hold):

828+

Vu_t[0, i] = Vu_0

829+

for t in range(1, T_max):

830+

cons_t[t - 1, i] = cons_star_interp(Vu_t[t - 1, i])

831+

a_t[t - 1, i] = a_star_interp(Vu_t[t - 1, i])

832+

Vu_t[t, i] = V_star_interp(Vu_t[t - 1, i])

828833

```

829834830835

```{code-cell} ipython3

831836

fontSize = 10

832-

plt.rc('font', size=fontSize) # controls default text sizes

833-

plt.rc('axes', titlesize=fontSize) # fontsize of the axes title

834-

plt.rc('axes', labelsize=fontSize) # fontsize of the x and y labels

835-

plt.rc('xtick', labelsize=fontSize) # fontsize of the tick labels

836-

plt.rc('ytick', labelsize=fontSize) # fontsize of the tick labels

837-

plt.rc('legend', fontsize=fontSize) # legend fontsize

838-839-

f1 = plt.figure(figsize = (8,8))

840-

plt.subplot(2,1,1)

841-

plt.plot(range(T_max-1),cons_t[:,0]/params.w,label = '$V^u_0$ = 16759 (aut)',color = 'red')

842-

plt.plot(range(T_max-1),cons_t[:,1]/params.w,label = '$V^u_0$ = 16942',color = 'blue')

843-

plt.plot(range(T_max-1),cons_t[:,2]/params.w,label = '$V^u_0$ = 17000',color = 'green')

837+

plt.rc('font', size=fontSize) # controls default text sizes

838+

plt.rc('axes', titlesize=fontSize) # fontsize of the axes title

839+

plt.rc('axes', labelsize=fontSize) # fontsize of the x and y labels

840+

plt.rc('xtick', labelsize=fontSize) # fontsize of the tick labels

841+

plt.rc('ytick', labelsize=fontSize) # fontsize of the tick labels

842+

plt.rc('legend', fontsize=fontSize) # legend fontsize

843+844+

f1 = plt.figure(figsize=(8, 8))

845+

plt.subplot(2, 1, 1)

846+

plt.plot(range(T_max - 1), cons_t[:, 0] / params.w,

847+

label='$V^u_0$ = 16759 (aut)', color='red')

848+

plt.plot(range(T_max - 1), cons_t[:, 1] / params.w,

849+

label='$V^u_0$ = 16942', color='blue')

850+

plt.plot(range(T_max - 1), cons_t[:, 2] / params.w,

851+

label='$V^u_0$ = 17000', color='green')

844852

plt.ylabel("Replacement ratio (c/w)")

845853

plt.legend()

846854

plt.title("Optimal replacement ratio")

847855848-

plt.subplot(2,1,2)

849-

plt.plot(range(T_max-1),a_t[:,0],color = 'red')

850-

plt.plot(range(T_max-1),a_t[:,1],color = 'blue')

851-

plt.plot(range(T_max-1),a_t[:,2],color = 'green')

852-

plt.ylim(0,320)

856+

plt.subplot(2, 1, 2)

857+

plt.plot(range(T_max - 1), a_t[:, 0], color='red')

858+

plt.plot(range(T_max - 1), a_t[:, 1], color='blue')

859+

plt.plot(range(T_max - 1), a_t[:, 2], color='green')

860+

plt.ylim(0, 320)

853861

plt.ylabel("Optimal search effort (a)")

854862

plt.xlabel("Duration of unemployment")

855863

plt.title("Optimal search effort")

Read the original on github.com ↗