The Model

It’s a small open economy where two goods, \(X\) and \(Y\) are produced. Good \(Y\) is the numeraire, and the price of \(X\) is denoted by \(p\). Production is done under competitive conditions with constant-returns-to-scale technology \(q_i = Q_i (L_i , K_i )\), sector \(i\in\{X, Y\}\), \(L_i\) and \(K_i\) denote labor and capital employed in sector \(i\) respectively. Capital is inelastically supplied, and specific to its sector. Supply of labor in the economy is exogenously given at a value \(\bar L\).

Workers discount the future at the common rate \(\beta<1\) and their location decision are characterised as follows. In each period, each worker receives an idiosyncratic benefit \(\varepsilon_t^j\) if that worker is in sector \(j\in \{X, Y\}\) at the end of the period. This implies an idiosyncratic moving cost:

\[\mu_t^i\equiv\varepsilon_t^i-\varepsilon_t^j.\]

The cdf of this moving cost is denoted \(G\). A worker who changes sectors also incurs a common cost equal to \(C\geq 0\).

Worker optimization

\[ V^i(L_t)=w_t^i+E\max\{\varepsilon_t^i+\beta V^i(L_{t+1}), \varepsilon_t^j-C+\beta V^j(L_{t+1})\} \]

At any date \(t\) there is a threshold value of \(\mu_t^i\), say \(\bar\mu_t^i\), such that the worker will stay in \(i\) if \(\mu_t^i>\bar\mu_t^i\). Then,

\[ V^i(L_t)=w_t^i+\beta V^i(L_{t+1})+\underbrace{E\max\{0, \bar\mu_t^i - \mu_t^i\}}_{\Omega(\bar\mu_t^i)} \]

The law of motion for labor allocations can be written as:

\[ L_{t+1}^i=\underbrace{(1-G(\bar\mu_t^i))L_t^i}_{\text{stay in i}}+\underbrace{G(\bar\mu_t^j)L_t^j}_{\text{come from j}} \]

since the fraction of workers in \(i\) who move to \(j\) is \(G(\bar\mu_t^i)\).

Python implementation

Change the model to CES? Production or preferences, or both?

Here is a brief description of how to implement the model. We start by computing the free-trade steady-state, where \(L_{t+1}^i=L_t^i\) for \(i\in \{X, Y\}\), using steady-state versions of equations (1) through (4) from Artuç, Chaudhuri, and McLaren (2008). We will arrive at a vector of steady-state variables, \((L^i, w^i, V^i, \bar\mu^i)_{i\in\{X,Y\}}\).

Then, for the time path simulation, we follow the algorith from Artuç, Chaudhuri, and McLaren (2008) p. 05. The starting (i.e., autarky) price is \(p=1\). The authors also show that \(G(\mu)=\exp(\mu/\nu)/(1+\exp(\mu/\nu))\) and, \(\Omega(\mu)=\nu\log(1+\exp(\mu/\nu))\) for a given parameter \(\nu\). Suppose that the economy is in a tariff steady-state at date \(t = 0\), and then it is announced that at date \(T \geq 0\) and from that date forward, free trade will prevail. Thus, \(p_t = 1\) for \(t<T\), and \(p_t = p^W\) for \(t\geq T\).

We guess values for \(V_{t+1}^i\) using simple linear interpolation between steady-states. While there is no convergence we keep computing \(\bar\mu_t^i\), \(L_{t+1}^i\), \(w_{t+1}^i\) and update \(z_t^i=V^i(L_{t+1})\).

By the end of this loop, we have the time path of all endogenous variables: \(\bar\mu_t^i\), \(L_t^i\), \(w_t^i\) and \(V_t^i\).

The implementation below assumes a Cob-Douglas utility function, \(U(c^X, c^Y)=2(c^X)^{1/2}(c^Y)^{1/2}\) and, production technology, \(Q^i(L^i, K^i)=(L^i)^\alpha (K^i)^{1-\alpha}\).

"""
Created on Thu May  2 21:47:08 2019

@author: brunamirelle

Modified by: Rafael Felipe Bressan (https://github.com/rfbressan)
Date: 2022-02-10
"""

import numpy as np
from pandas import value_counts
from scipy.optimize import fsolve

#Resolve o modelo de Artuc (2008)
class EconomiaArtuc:
    
    def __init__(self, alpha, beta, C, nu, K_X, K_Y, L_bar, p0):
        """Inicializa economia
        
        :param alpha: parâmetro da função de produção (Cobb-Douglas)
        :param beta: fator de desconto dos trabalhadores
        :param C: custo de mudar de setor
        :param K_X, K_Y: dotações de capital em cada setor
        :param L_bar: dotação de trabalho na economia
        :param p0: preço pré-liberalização do bem X
        :return: objeto da classe EconomiaArtuc
        """
        # Checks alpha
        if self.valid_alpha_bool(alpha):
            self.alpha = alpha
        else:
            raise ValueError(f'Alpha parameter must be between zero and one, open set. Value passed was: {alpha}')
        
        # Checks beta
        if self.valid_beta_bool(beta):
            self.beta = beta
        else:
            raise ValueError(f'Beta parameter must be between zero and one, open set. Value passed was: {beta}')
        
        self.C = C
        self.nu = nu
        self.K_X = K_X
        self.K_Y = K_Y
        self.L_bar = L_bar
        self.p0 = p0
    
    def valid_alpha_bool(self, alpha):
        """Checks whether alpha is in (0, 1)

        :param alpha: Cobb-Douglas parameter
        :type alpha: float
        :return: a flag indicating alpha is in (0, 1)
        :rtype: bool
        """
        return (alpha > 0) and (alpha < 1)
        
    def valid_beta_bool(self, beta):
        """Checks whether beta is in (0, 1)

        :param beta: Preferences discount factor
        :type beta: float
        :return: a flag indicating beta is in (0, 1)
        :rtype: bool
        """
        return (beta > 0) and (beta < 1)

    #Função Omega no paper
    def Omega(self, mu):
        """Valor da opção do trabalhador
        
        Complete a função de acordo com o artigo ACM2008.

        :param mu: moving cost
        :type mu: float
        :return: value
        :rtype: float
        """
        pass
    
    #CDF da distribuição logística (diferença dos choques)
    def G(self, mu):
        """CDF da distribuição logística
        
        Complete a função de acordo com o artigo ACM2008. A função deve retornar
        valores válidos para valores extremos de mu (e.g., np.Inf)
        
        :param mu: moving cost
        :type mu: float
        :return: value
        :rtype: float
        """
        pass
    
    #Condição de primeira ordem para maximização dos lucros
    def FOC(self, L, K, p):
        """Condição de primeira ordem para maximização dos lucros
        
        Complete a função de acordo com o artigo ACM2008.
        
        :param L: Labor force
        :type L: float
        :param K: Capital
        :type K: float
        :param p: relative price
        :type p: float
        :return: value
        :rtype: float
        """
        pass
      
    #Indice de preços
    def phi(self,p):
        """Indice de preços
        
        Complete a função de acordo com o artigo ACM2008.
        
        :param p: relative price
        :type p: float
        :return: value
        :rtype: float
        """
        pass
    
    #Retorna as equações necessárias para calcular steady-state
    #vec_guess - valores, salários e thresholds (vetor)
    #p - preço relativo do bem X no steady-state desejado
    def steady_discrep(self,vec_guess, p):
        """Equações necessárias para steady-state

        :param vec_guess: initial guess vector
        :type vec_guess: ndarray of floats
        :param p: price
        :type p: float
        :return: vector of values
        :rtype: ndarray of floats
        """
        vx, vy, mux, muy, lx, ly, wx, wy = vec_guess
        discrep = np.zeros(8)
        discrep[0] = wx - self.FOC(lx, self.K_X, p)/self.phi(p)
        discrep[1] = wy - self.FOC(ly, self.K_Y, 1)/self.phi(p)
        discrep[2] = lx + ly - self.L_bar
        discrep[3] = mux - self.beta*(vy-vx) + self.C
        discrep[4] = muy - self.beta*(vx-vy) + self.C
        discrep[5] = vx - wx - self.beta*(vx) - self.Omega(mux)
        discrep[6] = vy - wy - self.beta*(vy) - self.Omega(muy)
        discrep[7] = lx - (1-self.G(mux))*lx - self.G(muy)*ly
        #discrep[8] = L_Y - (1-self.G(mu_Y))*L_Y - self.G(mu_X)*L_X
        return discrep  
    
    #Calcula estado estacionário para um dado valor do preço externo
    def solve_steady(self, p):
        def steady_price(vec_guess):
            return self.steady_discrep(vec_guess, p)
        return fsolve(steady_price, np.ones(8))
    
 
    def solve_model(self, p_after, T_SS = 30, effective_at = 0, tol = 1e-5):
        """Resolve o modelo para um dado valor pós-liberalização
        
        :param p_after: preço relativo de X pós-liberalização
        :param T_SS: número de períodos até novo steady-state (padrão 30, t=31 é steady-state)
        :param effective_at: a partir de que período vale a abertura (padrão é o zero)?
        :param tol: Tolerância para convergência do algoritmo
        :return: time path of endogenous variables
        :rtype: dictionary
        """
        #Calcula valores do steady-state inicial e final
        #Inicial
        vx0, vy0, mux0, muy0, lx0, ly0, wx0, wy0 = self.solve_steady(self.p0)
        #Final  
        vxt, vyt, muxt, muyt, lxt, lyt, wxt, wyt = self.solve_steady(p_after)
        
        #Chutes iniciais para a trajetória dos valores em cada setor
        zxt = np.linspace(vx0, vxt, T_SS + 1)
        zyt = np.linspace(vy0, vyt, T_SS + 1)
         
        #Trajetória de preços
        p_t = np.concatenate(
          (np.repeat(self.p0,effective_at),
           np.repeat(p_after,T_SS + 1 - effective_at))
          )
        
        #print(p_t)
        
        err = tol + 1
        
        while err > tol:
            #Calcula trajetórias para mu_X e mu_Y
            mu_X_t = self.beta*(np.concatenate((zyt[1:],[vyt])) -\
                                np.concatenate((zxt[1:],[vxt])) ) - self.C
            mu_Y_t = self.beta*(np.concatenate((zxt[1:],[vxt])) -\
                                np.concatenate((zyt[1:],[vyt])) ) - self.C
           
            L_X_t = np.empty(T_SS + 1)
            L_Y_t = np.empty(T_SS + 1)
            
            L_X_t[0] = lx0
            L_Y_t[0] = ly0
            
            for tt in range(1, T_SS + 1):
                L_X_t[tt] = (
                  (1-self.G(mu_X_t[tt-1]))*L_X_t[tt-1] +
                  self.G(mu_Y_t[tt-1])*L_Y_t[tt-1]
                )
                L_Y_t[tt] = self.L_bar - L_X_t[tt]
                
            w_Y_t = self.FOC(L_Y_t, self.K_Y, 1)/self.phi(p_t)
            w_X_t = self.FOC(L_X_t, self.K_X, p_t)/self.phi(p_t)
            
            z_tilde_X_t = (
              w_X_t + 
              self.beta*np.concatenate((zxt[1:], [vxt])) + 
              self.Omega(mu_X_t)
            )
            z_tilde_Y_t = (
              w_Y_t + 
              self.beta*np.concatenate((zyt[1:], [vyt])) + 
              self.Omega(mu_Y_t)
            )
            
            err = np.max((np.abs(z_tilde_X_t - zxt), np.abs(z_tilde_Y_t - zyt)))
            
            zxt = z_tilde_X_t 
            zyt = z_tilde_Y_t
            
            print(err)
        
        return {
            'wx': np.concatenate(([wx0],w_X_t,[wxt])),
            'wy': np.concatenate(([wy0],w_Y_t,[wyt])),
            'Vx': np.concatenate(([vx0],zxt,[vxt])),
            'Vy': np.concatenate(([vy0],zyt,[vyt])),
            'Lx': np.concatenate(([lx0],L_X_t,[lxt])),
            'Ly': np.concatenate(([ly0],L_Y_t,[lyt]))
            }

In order to test this class, we can append to the end of EconomiaArtuc.py file the following code. This will be a streamlit application, and you can run it as such:

$ streamlit run EconomiaArtuc.py

# Script for testing purposes
if __name__ == "__main__":
    # execute only if run as a script
    # This is a streamlit application, run it as such:
    # $ streamlit run EconomiaArtuc.py
    import matplotlib.pyplot as plt
    import streamlit as st
    
    st.set_page_config(
        page_title="Artuç Simulation",
        page_icon=":dollar:",
        layout="wide",
        initial_sidebar_state="expanded",
    )
    
    st.title("Artuç et. al. (2008) replication")
    # Solving the model and ploting wages  
    # Uses sidebar for economy parameters
    alpha = st.sidebar.number_input("alpha", min_value=0.01, max_value=0.99, value=0.5)
    beta = st.sidebar.number_input('beta', min_value=0.01, max_value=0.99, value=0.97)
    nu = st.sidebar.number_input('nu', value=0.31)

    econ_args = {'alpha': alpha,
                 'beta': beta,
                 'C': 1,
                 'nu': nu,
                 'K_X': 1,
                 'K_Y': 1,
                 'L_bar': 2,
                 'p0': 1}                      
    econ = EconomiaArtuc(**econ_args)
    eff_at = st.sidebar.number_input("Period where the tariff is actually reduced:",
        value=10,
        min_value=1,
        max_value=12,
        step=1,
        format="%d")
    pw = st.sidebar.number_input('pW', value=0.7)
    sol = econ.solve_model(pw, effective_at=int(eff_at))  

    fig1, ax1 = plt.subplots()          
    ax1.plot(sol['wx'], label = 'Sector X')
    ax1.plot(sol['wy'], label = 'Sector Y')
    w_extended = np.append(sol['wx'], sol['wy'])
    ax1.vlines(x=eff_at, 
               ymin=min(w_extended),
               ymax=max(w_extended),
               colors='red', linestyles='dashed')
    ax1.set_title("Delayed to t = " + str(eff_at))
    ax1.set_xlabel("time")
    ax1.set_ylabel("Wages")
    ax1.legend()
    
    fig2, ax2 = plt.subplots()
    ax2.plot(sol['Lx'], label = 'Sector X')
    ax2.plot(sol['Ly'], label = 'Sector Y')
    l_extended = np.append(sol['Lx'], sol['Ly'])
    ax2.vlines(x=eff_at,
               ymin=min(l_extended),
               ymax=max(l_extended),
               colors='red', linestyles='dashed')
    ax2.set_title("Delayed to t = " + str(eff_at))
    ax2.set_xlabel("time")
    ax2.set_ylabel("Labor force")
    ax2.legend()
    
    col1, col2 = st.columns(2)
    with col1:
        st.pyplot(fig1)
    with col2:
        st.pyplot(fig2)

References

Artuç, Erhan, Shubham Chaudhuri, and John McLaren. 2008. “Delay and Dynamics in Labor Market Adjustment: Simulation Results.” Journal of International Economics 75 (1): 1–13.