Esempio n. 1
0
class Economy:
    def __init__(self, n, d, netstring, directed, j0, a0, q, b):

        # Network initialization
        self.n = n
        self.j = create_net(netstring, directed, n, d)
        self.j0 = j0
        self.j_a = None
        self.a0 = a0
        a = np.multiply(np.random.uniform(0, 1, (n, n)), self.j)
        self.a = np.array([(1 - a0[i]) * a[i] / np.sum(a[i])
                           for i in range(n)])
        self.a_a = None
        self.q = q
        self.zeta = 1 / (q + 1)
        self.b = b

        # Auxiliary network variables
        self.lamb = None
        self.a_a = None
        self.j_a = None
        self.lamb_a = None
        self.m_cal = None
        self.v = None
        self.kappa = None
        self.zeros_j_a = None

        # Firms and household sub-classes
        self.firms = None
        self.house = None

        # Equilibrium quantities
        self.p_eq = None
        self.g_eq = None
        self.mu_eq = None
        self.labour_eq = None
        self.cons_eq = None
        self.b_eq = None
        self.utility_eq = None

    def init_house(self, l_0, theta, gamma, phi, omega_p=None, f=None, r=None):
        """
        Initialize a household object as instance of economy class. Refer to household class.
        :param l_0: baseline work offer,
        :param theta: preferency factors,
        :param gamma: aversion to work parameter,
        :param phi: Frisch index,
        :param omega_p: confidence parameter
        :param f: fraction of budget to save,
        :param r: savings growth rate.
        :return: Initializes household class with given parameters.
        """
        self.house = Household(l_0, theta, gamma, phi, omega_p, f, r)

    def init_firms(self, z, sigma, alpha, alpha_p, beta, beta_p, omega):
        """
        Initialize a firms object as instance of economy class. Refer to firms class.
        :param z: Productivity factors,
        :param sigma: Depreciation of stocks parameters,
        :param alpha: Log-elasticity of prices' growth rates against surplus,
        :param alpha_p: Log-elasticity of prices' growth rates against profits,
        :param beta: Log-elasticity of productions' growth rates against profits,
        :param beta_p: Log-elasticity of productions' growth rates against surplus,
        :param omega: Log-elasticity of wages' growth rates against labor-market tensions.
        :return: Initializes firms class with given parameters.
        """
        self.firms = Firms(z, sigma, alpha, alpha_p, beta, beta_p, omega)

    # Setters for class instances

    def set_house(self, house):
        """
        Sets a household object as instance of economy class.
        :param house:
        :return:
        """
        self.house = house

    def set_firms(self, firms):
        """
        Sets a firms object as instance of economy class.
        :param firms:
        :return:
        """
        self.firms = firms

    # Update methods for firms and household

    def update_firms_z(self, z):
        self.firms.update_z(z)
        self.set_quantities()
        self.compute_eq()

    def update_firms_sigma(self, sigma):
        self.firms.update_sigma(sigma)

    def update_firms_alpha(self, alpha):
        self.firms.update_alpha(alpha)

    def update_firms_alpha_p(self, alpha_p):
        self.firms.update_alpha_p(alpha_p)

    def update_firms_beta(self, beta):
        self.firms.update_beta(beta)

    def update_firms_beta_p(self, beta_p):
        self.firms.update_beta_p(beta_p)

    def update_firms_w(self, omega):
        self.firms.update_w(omega)

    def update_house_labour(self, labour):
        self.house.update_labour(labour)
        self.compute_eq()

    def update_house_theta(self, theta):
        self.house.update_theta(theta)
        self.compute_eq()

    def update_house_gamma(self, gamma):
        self.house.update_gamma(gamma)
        self.compute_eq()

    def update_house_phi(self, phi):
        self.house.update_phi(phi)
        self.compute_eq()

    def update_house_w_p(self, omega_p):
        self.house.update_w_p(omega_p)

    def update_house_f(self, f):
        self.house.update_f(f)

    def update_house_r(self, r):
        self.house.update_r(r)

    # Setters for the networks and subsequent instances

    def set_j(self, j):
        """
        Sets a particular input-output network.
        :param j: a n by n matrix.
        :return: side effect
        """
        if j.shape != (self.n, self.n):
            raise ValueError('Input-output network must be of size (%d, %d)' %
                             (self.n, self.n))

        self.j = j
        self.set_quantities()
        self.compute_eq()

    def set_a(self, a):
        """
        Sets a particular input-output network.
        :param a: a n by n matrix.
        :return: side effect
        """
        if a.shape != (self.n, self.n):
            raise ValueError('Substitution network must be of size (%d, %d)' %
                             (self.n, self.n))
        self.a = a
        self.set_quantities()
        self.compute_eq()

    def set_quantities(self):
        """
        Sets redundant economy quantities as class instances.
        :return: side effect
        """
        if self.q == 0:
            self.lamb = self.j
            self.a_a = np.hstack((np.array([self.a0]).T, self.a))
            self.j_a = np.hstack((np.array([self.j0]).T, self.j))
            self.lamb_a = self.j_a
            self.m_cal = np.diag(self.firms.z) - self.lamb
            self.v = np.array(self.lamb_a[:, 0])
        elif self.q == np.inf:
            self.lamb = self.a
            self.a_a = np.hstack((np.array([self.a0]).T, self.a))
            self.j_a = np.hstack((np.array([self.j0]).T, self.j))
            self.lamb_a = self.a_a
            self.m_cal = np.eye(self.n) - self.lamb
            self.v = np.array(self.lamb_a[:, 0])
        else:
            self.lamb = np.multiply(np.power(self.a, self.q * self.zeta),
                                    np.power(self.j, self.zeta))
            self.a_a = np.hstack((np.array([self.a0]).T, self.a))
            self.j_a = np.hstack((np.array([self.j0]).T, self.j))
            self.lamb_a = np.multiply(np.power(self.a_a, self.q * self.zeta),
                                      np.power(self.j_a, self.zeta))
            self.m_cal = np.diag(np.power(self.firms.z, self.zeta)) - self.lamb
            self.v = np.array(self.lamb_a[:, 0])
        self.mu_eq = np.power(
            np.power(self.house.gamma, 1. / self.house.phi) *
            np.sum(self.house.theta) * (1 - (1 - self.house.f) *
                                        (1 + self.house.r)) /
            (self.house.f * np.power(self.house.l_0, 1 + 1. / self.house.phi)),
            self.house.phi / (1 + self.house.phi))
        self.kappa = self.house.theta / self.mu_eq
        self.zeros_j_a = self.j_a != 0

    def get_eps_cal(self):
        """
        Computes the smallest eigenvalue of the economy matrix
        :return: smallest eigenvalue
        """
        return np.min(np.real(np.linalg.eigvals(self.m_cal)))

    def set_eps_cal(self, eps):
        """
        Modifies firms instance to set smallest eigenvalue of economy matrix to given epsilon.
        :param eps: a real number,
        :return: side effect.
        """
        min_eig = self.get_eps_cal()
        z_n = self.firms.z * np.power(
            1 +
            (eps - min_eig) / np.power(self.firms.z, self.zeta), self.q + 1)
        sigma = self.firms.sigma
        alpha = self.firms.alpha
        alpha_p = self.firms.alpha_p
        beta = self.firms.beta
        beta_p = self.firms.beta_p
        omega = self.firms.omega
        self.init_firms(z_n, sigma, alpha, alpha_p, beta, beta_p, omega)
        self.set_quantities()
        self.compute_eq()

    def update_b(self, b):
        """
        Sets return to scale parameter
        :param b: return to scale
        :return: side effect
        """
        self.b = b
        self.compute_eq()

    def update_q(self, q):
        self.q = q
        self.zeta = 1 / (q + 1)
        self.set_quantities()
        self.compute_eq()

    def update_network(self, netstring, directed, d, n):
        self.j = create_net(netstring, directed, n, d)
        a = np.multiply(np.random.uniform(0, 1, (n, n)), self.j)
        self.a = np.array([(1 - self.a0[i]) * a[i] / np.sum(a[i])
                           for i in range(n)])
        self.set_quantities()
        self.compute_eq()

    def update_a0(self, a0):
        self.a0 = a0
        a = np.multiply(np.random.uniform(0, 1, (self.n, self.n)), self.j)
        self.a = np.array([(1 - self.a0[i]) * a[i] / np.sum(a[i])
                           for i in range(self.n)])
        self.set_quantities()
        self.compute_eq()

    def update_j0(self, j0):
        self.j0 = j0
        self.set_quantities()
        self.compute_eq()

    def production_function(self, q_available):
        """
        CES production function.
        :param q_available: matrix of available labour and goods for production,
        :return: production levels of the firms.
        """
        if self.q == 0:
            return np.power(
                np.nanmin(np.divide(q_available, self.j_a), axis=1), self.b)
        elif self.q == np.inf:
            return np.power(
                np.nanprod(np.power(np.divide(q_available, self.j_a),
                                    self.a_a),
                           axis=1), self.b)
        else:
            return np.power(
                np.nansum(self.a_a * np.power(self.j_a, 1. / self.q) /
                          np.power(q_available, 1. / self.q),
                          axis=1), -self.b * self.q)

    def compute_eq(self):
        """
        Computes the competitive equilibrium of the economy. We use least-squares to compute solutions of linear
        systems Ax=b for memory and computational efficiency. The non-linear equations for non-constant return to scale
        parameters are solved using generalized least-squares with initial guesses taken to be the solution of the b=1
        linear equation. For a high number of firms, high heterogeneity of close to 0 epsilon, this function might
        can output erroneous results or errors.
        :return: side effect.
        """
        if self.q == np.inf:
            h = np.sum(
                self.a_a *
                np.log(np.ma.masked_invalid(np.divide(self.j_a, self.a_a))),
                axis=1)
            v = lstsq(np.eye(self.n) - self.a.T, self.kappa, rcond=10e-7)[0]
            log_p = lstsq(np.eye(self.n) / self.b - self.a,
                          -np.log(self.firms.z) / self.b +
                          (1 - self.b) * np.log(v) / self.b + h,
                          rcond=10e-7)[0]
            log_g = -np.log(self.firms.z) - log_p + np.log(v)
            self.p_eq, self.g_eq = np.exp(log_p), np.exp(log_g)
        else:
            if self.b != 1:
                if self.q == 0:
                    init_guess_peq = lstsq(self.m_cal, self.v, rcond=10e-7)[0]
                    init_guess_geq = lstsq(self.m_cal.T,
                                           np.divide(self.kappa,
                                                     init_guess_peq),
                                           rcond=10e-7)[0]

                    par = (self.firms.z, self.v, self.m_cal, self.b - 1,
                           self.kappa)

                    pert_peq = lstsq(self.m_cal,
                                     self.firms.z * init_guess_peq *
                                     np.log(init_guess_geq),
                                     rcond=10e-7)[0]

                    pert_geq = lstsq(
                        np.transpose(self.m_cal),
                        -np.divide(self.kappa, np.power(init_guess_peq, 2)) *
                        pert_peq +
                        self.firms.z * init_guess_geq * np.log(init_guess_geq),
                        rcond=10e-7)[0]

                    pg = leastsq(
                        lambda x: self.non_linear_eq_qzero(x, *par),
                        np.array(
                            np.concatenate(
                                (init_guess_peq + (1 - self.b) * pert_peq,
                                 np.power(
                                     init_guess_geq + (1 - self.b) *
                                     (pert_geq -
                                      init_guess_geq * np.log(init_guess_geq)),
                                     1 / self.b))).reshape(2 * self.n)))[0]

                    # pylint: disable=unbalanced-tuple-unpacking
                    self.p_eq, g = np.split(pg, 2)
                    self.g_eq = np.power(g, self.b)

                else:

                    # The numerical solving is done for variables u = p_eq ^ zeta and
                    # w = z ^ (q * zeta) * u ^ q * g_eq ^ (zeta * (bq+1) / b)

                    init_guess_u = lstsq(self.m_cal, self.v, rcond=None)[0]
                    init_guess_w = lstsq(self.m_cal.T,
                                         np.divide(self.kappa, init_guess_u),
                                         rcond=None)[0]

                    par = (np.power(self.firms.z,
                                    self.zeta), self.v, self.m_cal, self.q,
                           (self.b - 1) / (self.b * self.q + 1), self.kappa)

                    uw = leastsq(
                        lambda x: self.non_linear_eq_qnonzero(x, *par),
                        np.concatenate((init_guess_u, init_guess_w)),
                    )[0]

                    # pylint: disable=unbalanced-tuple-unpacking
                    u, w = np.split(uw, 2)
                    self.p_eq = np.power(u, 1. / self.zeta)
                    self.g_eq = np.power(
                        np.divide(
                            w,
                            np.power(self.firms.z, self.q * self.zeta) *
                            np.power(u, self.q)),
                        self.b / (self.zeta * (self.b * self.q + 1)))
            else:
                if self.q == 0:
                    self.p_eq = lstsq(self.m_cal, self.v, rcond=10e-7)[0]
                    self.g_eq = lstsq(self.m_cal.T,
                                      np.divide(self.kappa, self.p_eq),
                                      rcond=10e-7)[0]
                else:

                    # The numerical solving is done for variables u = p_eq ^ zeta and
                    # w = z ^ (q * zeta) * u ^ q * g_eq

                    u = lstsq(self.m_cal, self.v, rcond=None)[0]
                    self.p_eq = np.power(u, 1. / self.zeta)
                    w = lstsq(self.m_cal.T,
                              np.divide(self.kappa, u),
                              rcond=None)[0]
                    self.g_eq = np.divide(
                        w,
                        np.power(self.firms.z, self.q * self.zeta) *
                        np.power(u, self.q))

        self.labour_eq = np.power(self.mu_eq * self.house.f,
                                  1. / self.house.phi) / self.house.v_phi
        self.cons_eq = self.kappa / self.p_eq
        self.b_eq = np.sum(self.house.theta) / self.mu_eq
        self.utility_eq = np.dot(self.house.theta, np.log(
            self.cons_eq)) - self.house.gamma * np.power(
                self.labour_eq / self.house.l_0,
                self.house.phi + 1) / (self.house.phi + 1)

    def save_eco(self, name):
        """
        Saves the economy as multi-indexed data-frame in hdf format along with networks in npy format.
        :param name: name of file,
        """
        first_index = np.concatenate(
            (np.repeat('Firms', 11), np.repeat('Household', 11)))
        second_index = np.concatenate(([
            'q', 'b', 'z', 'sigma', 'alpha', 'alpha_p', 'beta', 'beta_p', 'w',
            'p_eq', 'g_eq'
        ], ['l', 'theta', 'gamma', 'phi']))
        multi_index = [first_index, second_index]
        values = np.vstack((
            self.q * np.ones(self.n),
            self.b * np.ones(self.n),
            self.firms.z,
            self.firms.sigma,
            self.firms.alpha * np.ones(self.n),
            self.firms.alpha_p * np.ones(self.n),
            self.firms.beta * np.ones(self.n),
            self.firms.beta_p * np.ones(self.n),
            self.firms.w * np.ones(self.n),
            self.p_eq,
            self.g_eq,
            self.house.l_0 * np.ones(self.n),
            self.house.theta,
            self.house.gamma * np.ones(self.n),
            self.house.phi * np.ones(self.n),
        ))

        df_eco = pd.DataFrame(values,
                              index=multi_index,
                              columns=[np.arange(1, self.n + 1)])
        df_eco.to_hdf(name + '/eco.h5', key='df', mode='w')
        np.save(name + '/network.npy', self.j_a)
        if self.q != 0:
            np.save(name + '/sub_network.npy', self.a_a)

    # Fixed point equations for equilibrium computation

    @staticmethod
    def non_linear_eq_qnonzero(x, *p):
        """
        Function used for computation of equilibrium with non constant return to scale
        and general CES production function.
        :param x: guess for equilibrium
        :param p: tuple z, z_zeta, v, m_cal, zeta, q, theta, theta_bar, power
        :return: function's value at x
        """
        if len(x) % 2 != 0:
            raise ValueError("x must be of even length")

        # pylint: disable=unbalanced-tuple-unpacking
        u, w = np.split(x, 2)
        z_zeta, v, m_cal, q, exponent, kappa = p
        w_over_uq_p = np.power(
            np.divide(w,
                      np.power(z_zeta, q) * np.power(u, q)), exponent)
        v1 = np.multiply(z_zeta, np.multiply(u, 1 - w_over_uq_p))
        m1 = np.dot(m_cal, u)
        m2 = u * np.dot(m_cal.T, w) - w * m1
        return np.concatenate((m1 - v1 - v, m2 + w * v - kappa))

    @staticmethod
    def non_linear_eq_qzero(x, *par):
        """
        Function used for computation of equilibrium with non constant return to scale
        and Leontieff production function.
        :param x: guess for equilibrium
        :param par: tuple z_zeta, v, m_cal, q power
        :return: function's value at x
        """

        if len(x) % 2 != 0:
            raise ValueError('x must be of even length')

        # pylint: disable=unbalanced-tuple-unpacking
        p, g = np.split(x, 2)
        z, v, m_cal, exponent, kappa = par
        v1 = np.multiply(z, np.multiply(p, 1 - np.power(g, exponent)))
        m1 = np.dot(m_cal, p)
        m2 = g * m1 - p * np.dot(m_cal.T, g)
        return np.concatenate((m1 - v1 - v, m2 - g * v + kappa))