Source code for ogcore.aggregates

"""
-------------------------------------------------------------------------------
Functions to compute economic aggregates.
-------------------------------------------------------------------------------
"""

# Packages
import numpy as np
from ogcore import tax, pensions

"""
-------------------------------------------------------------------------------
    Functions
-------------------------------------------------------------------------------
"""


[docs] def get_L(n, p, method): r""" Calculate aggregate labor supply. .. math:: L_{t} = \sum_{s=E}^{E+S}\sum_{j=0}^{J}\omega_{s,t}\lambda_{j}n_{j,s,t} Args: n (Numpy array): labor supply of households p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on 'SS' or 'TPI' Returns: L (array_like): aggregate labor supply """ if method == "SS": # NOTE: avoid np.squeeze on p.e[-1, :, :] here — for J=1 it collapses # the J axis to a 1-D array, and the subsequent multiplication against # the (S, J) weight broadcasts into an (S, S) outer product, producing # a labor aggregate S times too large. p.e[-1, :, :] is already (S, J). L_presum = p.e[-1, :, :] * p.omega_SS * n L = L_presum.sum() elif method == "TPI": L_presum = n * (p.e * p.omega[: p.T, :, :]) L = L_presum.sum(1).sum(1) return L
[docs] def get_I(b_splus1, K_p1, K, p, method): r""" Calculate aggregate investment. .. math:: I_{t} = (1 + g_{n,t+1})e^{g_{y}}(K_{t+1} - \sum_{s=E}^{E+S} \sum_{j=0}^{J}\omega_{s+1,t}i_{s+1,t}\lambda_{j}b_{j,s+1,t+1} \ (1+ g_{n,t+1})) - (1 - \delta)K_{t} Args: b_splus1 (Numpy array): savings of households K_p1 (array_like): aggregate capital, one period ahead K (array_like): aggregate capital p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on 'SS' or 'TPI', also compute total investment (not net of immigrants) Returns: aggI (array_like): aggregate investment """ if method == "SS": omega_extended = np.append( p.omega_SS[1:, :], np.zeros((1, p.J)), axis=0 ) imm_extended = np.append( p.imm_rates[-1, 1:, :], np.zeros((1, p.J)), axis=0 ) part2 = ((b_splus1 * omega_extended * imm_extended).sum()) / ( 1 + p.g_n_ss ) aggI = (1 + p.g_n_ss) * np.exp(p.g_y) * (K_p1 - part2) - ( 1.0 - p.delta ) * K elif method == "TPI": omega_shift = np.append( p.omega[: p.T, 1:, :], np.zeros((p.T, 1, p.J)), axis=1 ) imm_shift = np.append( p.imm_rates[: p.T, 1:, :], np.zeros((p.T, 1, p.J)), axis=1 ) part2 = ((b_splus1 * imm_shift * omega_shift).sum(1).sum(1)) / ( 1 + np.squeeze(np.hstack((p.g_n[: p.T - 1], p.g_n_ss))) ) aggI = ( 1 + np.squeeze(np.hstack((p.g_n[: p.T - 1], p.g_n_ss))) ) * np.exp(p.g_y) * (K_p1 - part2) - (1.0 - p.delta) * K elif method == "total_ss": aggI = ((1 + p.g_n_ss) * np.exp(p.g_y) - 1 + p.delta) * K elif method == "total_tpi": aggI = (1 + p.g_n[: p.T]) * np.exp(p.g_y) * K_p1 - (1.0 - p.delta) * K return aggI
[docs] def get_B(b, p, method, preTP): r""" Calculate aggregate savings .. math:: B_{t} = \sum_{s=E}^{E+S}\sum_{j=0}^{J}\omega_{s,t}\lambda_{j}b_{j,s,t} Args: b (Numpy array): savings of households p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on 'SS' or 'TPI' preTP (bool): whether calculation is for the pre-time path period amount of savings. If True, then need to use `omega_S_preTP`. Returns: B (array_like): aggregate supply of savings """ if method == "SS": if preTP: part1 = b * p.omega_S_preTP omega_extended = np.append( p.omega_S_preTP[1:, :], np.zeros((1, p.J)), axis=0 ) imm_extended = np.append( p.imm_rates_preTP[1:, :], np.zeros((1, p.J)), axis=0 ) pop_growth_rate = p.g_n_preTP else: part1 = b * p.omega_SS omega_extended = np.append( p.omega_SS[1:, :], np.zeros((1, p.J)), axis=0 ) imm_extended = np.append( p.imm_rates[-1, 1:, :], np.zeros((1, p.J)), axis=0 ) pop_growth_rate = p.g_n_ss part2 = b * omega_extended * imm_extended B_presum = part1 + part2 B = B_presum.sum() B /= 1.0 + pop_growth_rate elif method == "TPI": part1 = b * p.omega[: p.T, :, :] omega_shift = np.append( p.omega[: p.T, 1:, :], np.zeros((p.T, 1, p.J)), axis=1 ) imm_shift = np.append( p.imm_rates[: p.T, 1:, :], np.zeros((p.T, 1, p.J)), axis=1 ) part2 = b * omega_shift * imm_shift B_presum = part1 + part2 B = B_presum.sum(1).sum(1) B /= 1.0 + np.hstack((p.g_n[: p.T - 1], p.g_n_ss)) return B
[docs] def get_BQ(r, b_splus1, j, p, method, preTP): r""" Calculation of aggregate bequests. If `use_zeta` is False, then computes aggregate bequests within each lifetime income group. .. math:: BQ_{t} = \sum_{s=E}^{E+S}\sum_{j=0}^{J}\rho_{s}\omega_{s,t} \lambda_{j}b_{j,s+1,1} Args: r (array_like): the real interest rate b_splus1 (numpy array): household savings one period ahead j (int): index of lifetime income group p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on SS or TPI preTP (bool): whether calculation is for the pre-time path period amount of savings. If True, then need to use `omega_S_preTP`. Returns: BQ (array_like): aggregate bequests, overall or by lifetime income group, depending on `use_zeta` value. """ if method == "SS": if preTP: omega = p.omega_S_preTP pop_growth_rate = p.g_n_preTP rho = p.rho_preTP else: omega = p.omega_SS pop_growth_rate = p.g_n_ss rho = p.rho[-1, :] if j is not None: BQ_presum = omega[:, j] * rho[:, j] * b_splus1 else: BQ_presum = omega * rho * b_splus1 BQ = BQ_presum.sum(0) BQ *= (1.0 + r) / (1.0 + pop_growth_rate) elif method == "TPI": pop = np.append( p.omega_S_preTP.reshape(1, p.S, p.J), p.omega[: p.T - 1, :, :], axis=0, ) rho = np.append( p.rho_preTP.reshape(1, p.S, p.J), p.rho[: p.T - 1, :, :], axis=0 ) if j is not None: BQ_presum = b_splus1 * pop[:, :, j] * rho[:, :, j] BQ = BQ_presum.sum(1) BQ *= (1.0 + r) / (1.0 + np.append(p.g_n_preTP, p.g_n[: p.T - 1])) else: BQ_presum = b_splus1 * pop * rho BQ = BQ_presum.sum(1) BQ *= np.tile( np.reshape( (1.0 + r) / (1.0 + np.append(p.g_n_preTP, p.g_n[: p.T - 1])), (p.T, 1), ), (1, p.J), ) if p.use_zeta: if method == "SS": BQ = BQ.sum() else: if not j: BQ = BQ.sum(1) return BQ
[docs] def get_RM(Y, p, method): r""" Calculation of aggregate remittances. .. math:: \hat{RM}_{t} = \begin{cases} \alpha_{RM,1}\hat{Y}_t \quad\text{for}\quad t=1 \\ \frac{(1+g_{RM,t})}{e^{g_y}(1+\tilde{g}_{n,t})}\hat{RM}_{t-1} \quad\text{for}\quad 2\leq t\leq T_{G1} \\ \rho_{RM}\alpha_{RM,T}\hat{Y}_t + (1-\rho_{RM})\frac{(1+g_{RM,t})}{e^{g_y}(1+\tilde{g}_{n,t})} \hat{RM}_{t-1} \quad\text{for}\quad T_{G1}< t<T_{G2} \\ \alpha_{RM,T}\hat{Y}_t \quad\text{for}\quad t\geq T_{G2} \end{cases} Args: Y (array_like): GDP steady state or time path p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on SS or TPI Returns: RM (array_like): aggregate remittances """ if method == "SS": RM = p.alpha_RM_T * Y elif method == "TPI": RM = np.zeros_like(Y) RM[0] = p.alpha_RM_1 * Y[0] for t in range(1, p.tG1): RM[t] = ( (1 + p.g_RM[t]) / (np.exp(p.g_y) * (1 + p.g_n[t - 1])) ) * RM[t - 1] rho_vec = np.linspace(0, 1, p.tG2 - p.tG1) for t in range(p.tG1, p.tG2 - 1): RM[t] = ( rho_vec[t - p.tG1] * p.alpha_RM_T * Y[t] + (1 - rho_vec[t - p.tG1]) * ((1 + p.g_RM[t]) / (np.exp(p.g_y) * (1 + p.g_n[t - 1]))) * RM[t - 1] ) RM[p.tG2 - 1 :] = p.alpha_RM_T * Y[p.tG2 - 1 :] return RM
[docs] def get_C(c, p, method): r""" Calculation of aggregate consumption. Set up to only take one consumption good at a time. This function is called in a loop to get consumption for all goods. .. math:: C_{t} = \sum_{s=E}^{E+S}\sum_{j=0}^{J}\omega_{s,t} \lambda_{j}c_{j,s,t} Args: c (Numpy array): consumption of households p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on 'SS' or 'TPI' Returns: C (array_like): aggregate consumption """ if method == "SS": aggC = (c * p.omega_SS).sum(-1).sum(-1) elif method == "TPI": aggC = (c * p.omega[: p.T, :, :]).sum(-1).sum(-1) return aggC
[docs] def revenue( r, w, b, n, bq, c, Y, L, K, p_m, factor, ubi, theta, etr_params, e, p, m, method, ): r""" Calculate aggregate tax revenue. .. math:: \begin{split} R_{t} &= \sum_{s=E}^{E+S}\sum_{j=0}^{J}\omega_{s,t}\lambda_{j} (T_{j,s,t} + \tau^{p}_{t}w_{t}e_{j,s}n_{j,s,t} - \theta_{j} w_{t} + \tau^{bq}bq_{j,s,t} + \tau^{c}_{s,t}c_{j,s,t} + \tau^{w}_{t}b_{j,s,t}) ... \\ &\qquad + \sum_{m=1}^{M}\tau^{b}_{m,t}(Y_{m,t}-w_{t}L_{m,t}) - \tau^{b}_{m,t}\delta^{\tau}_{m,t}K^{\tau}_{m,t} \end{split} Args: r (array_like): the real interest rate w (array_like): the real wage rate b (Numpy array): household savings n (Numpy array): household labor supply bq (Numpy array): household bequests received c (Numpy array): household consumption Y (array_like): aggregate output L (array_like): aggregate labor K (array_like): aggregate capital p_m (array_like): output prices factor (scalar): scaling factor converting model units to dollars ubi (array_like): universal basic income household distributions theta (Numpy array): social security replacement rate for each lifetime income group etr_params (list): list of parameters of the effective tax rate functions e (Numpy array): effective labor units p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on 'SS' or 'TPI' Returns: total_tax_revenue (array_like): aggregate tax revenue iit_payroll_tax_revenue (array_like): aggregate income and payroll tax revenue agg_pension_outlays (array_like): aggregate outlays for gov't pensions UBI_outlays (array_like): aggregate universal basic income (UBI) outlays bequest_tax_revenue (array_like): aggregate bequest tax revenue wealth_tax_revenue (array_like): aggregate wealth tax revenue cons_tax_revenue (array_like): aggregate consumption tax revenue business_tax_revenue (array_like): aggregate business tax revenue payroll_tax_revenue (array_like): aggregate payroll tax revenue iit_tax_revenue (array_like): aggregate income tax revenue """ inc_pay_tax_liab = tax.income_tax_liab( r, w, b, n, factor, 0, None, method, e, etr_params, p ) pension_benefits = pensions.pension_amount( r, w, n, Y, theta, 0, None, False, method, e, factor, p ) bq_tax_liab = tax.bequest_tax_liab(r, b, bq, 0, None, method, p) w_tax_liab = tax.wealth_tax_liab(r, b, 0, None, method, p) if method == "SS": p_i = np.dot(p.io_matrix, p_m) pop_weights = p.omega_SS iit_payroll_tax_revenue = (inc_pay_tax_liab * pop_weights).sum() agg_pension_outlays = (pension_benefits * pop_weights).sum() UBI_outlays = (ubi * pop_weights).sum() wealth_tax_revenue = (w_tax_liab * pop_weights).sum() bequest_tax_revenue = (bq_tax_liab * pop_weights).sum() cons_tax_revenue = ( tax.cons_tax_liab(c, p_i, p, method) * pop_weights ).sum() elif method == "TPI": p_i = ( np.tile(p.io_matrix.reshape(1, p.I, p.M), (p.T, 1, 1)) * np.tile(p_m[: p.T, :].reshape(p.T, 1, p.M), (1, p.I, 1)) ).sum(axis=2) pop_weights = p.omega[: p.T, :, :] iit_payroll_tax_revenue = ( (inc_pay_tax_liab * pop_weights).sum(1).sum(1) ) agg_pension_outlays = (pension_benefits * pop_weights).sum(1).sum(1) UBI_outlays = (ubi[: p.T, :, :] * pop_weights).sum(1).sum(1) wealth_tax_revenue = (w_tax_liab * pop_weights).sum(1).sum(1) bequest_tax_revenue = (bq_tax_liab * pop_weights).sum(1).sum(1) cons_tax_revenue = ( (tax.cons_tax_liab(c, p_i, p, method) * pop_weights).sum(1).sum(1) ) # Payroll tax revenue is already inside iit_payroll_tax_revenue: each # household's income_payroll_tax_liab includes T_P = tau_payroll * # labor_income (see tax.income_tax_liab). get_payroll_tax_revenue just # re-derives that same amount from the aggregate wage bill so the # income vs payroll split can be reported; it must NOT be added into # the total again (that double-counted payroll revenue -- Issue #1199). payroll_tax_revenue = get_payroll_tax_revenue( w, L, iit_payroll_tax_revenue, p, method ) business_tax_revenue = tax.get_biz_tax(w, Y, L, K, p_m, p, m, method).sum( -1 ) iit_revenue = iit_payroll_tax_revenue - payroll_tax_revenue total_tax_revenue = ( iit_payroll_tax_revenue + wealth_tax_revenue + bequest_tax_revenue + cons_tax_revenue + business_tax_revenue ) return ( total_tax_revenue, iit_payroll_tax_revenue, agg_pension_outlays, UBI_outlays, bequest_tax_revenue, wealth_tax_revenue, cons_tax_revenue, business_tax_revenue, payroll_tax_revenue, iit_revenue, )
[docs] def get_payroll_tax_revenue(w, L, iit_payroll_tax_revenue, p, method): r""" Calculate aggregate payroll tax revenue. How payroll tax revenue is computed depends on how the user has chosen to represent payroll taxes in the model. If payroll taxes are included directly through the ``tau_payroll`` parameter, then revenue is the payroll tax rate times aggregate labor income: .. math:: PR_{t} = \tau^{p}_{t}w_{t}L_{t} Otherwise, payroll taxes are assumed to be embedded in the estimated income and payroll tax functions (the default), and payroll tax revenue is separated out as a fraction ``frac_tax_payroll`` of the combined income and payroll tax revenue. These two calculations are identical when ``tau_payroll`` is zero. Args: w (array_like): the real wage rate L (array_like): aggregate labor by industry iit_payroll_tax_revenue (array_like): aggregate income and payroll tax revenue p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on 'SS' or 'TPI' Returns: payroll_tax_revenue (array_like): aggregate payroll tax revenue """ if np.any(p.tau_payroll != 0): # Payroll taxes are modeled explicitly via tau_payroll, so # revenue is the payroll tax rate times aggregate labor income # (summing labor across industries). L_total = L.sum(-1) if method == "SS": payroll_tax_revenue = p.tau_payroll[-1] * w * L_total else: # TPI payroll_tax_revenue = ( p.tau_payroll[: p.T] * w[: p.T] * L_total[: p.T] ) else: # Payroll taxes are embedded in the income and payroll tax # functions, so revenue is a fraction of the combined revenue. if method == "SS": payroll_tax_revenue = ( p.frac_tax_payroll[-1] * iit_payroll_tax_revenue ) else: # TPI payroll_tax_revenue = ( p.frac_tax_payroll[: p.T] * iit_payroll_tax_revenue ) return payroll_tax_revenue
[docs] def get_r_p(r, r_gov, p_m, K_vec, K_g, D, MPKg_vec, p, method): r""" Compute the interest rate on the household's portfolio of assets, a mix of government debt and private equity. .. math:: r_{p,t} = \frac{r_{gov,t}D_{t} + r_{K,t}K_{t}}{D_{t} + K_{t}} Args: r (array_like): the real interest rate r_gov (array_like): the real interest rate on government debt p_m (array_like): good prices K_vec (array_like): aggregate capital demand from each industry K_g (array_like): aggregate public capital D (array_like): aggregate government debt MPKg_vec (array_like): marginal product of government capital for each industry p (OG-Core Specifications object): model parameters method (str): adjusts calculation dimensions based on 'SS' or 'TPI' Returns: r_p (array_like): the real interest rate on the household portfolio """ if method == "SS": tau_b = p.tau_b[-1, :] T = 1 else: T = p.T tau_b = p.tau_b[: p.T, :].reshape((p.T, p.M)) K_g = K_g.reshape((p.T, 1)) r = r.reshape((p.T, 1)) r_gov = r_gov.reshape((p.T, 1)) D = D.reshape((p.T, 1)) p_m = p_m.reshape((p.T, p.M)) MPKg_vec = MPKg_vec.reshape((p.T, p.M)) K_vec = K_vec.reshape((p.T, p.M)) r_K = r + ( ((1 - tau_b) * p_m * MPKg_vec * K_g).sum(axis=-1).reshape((T, 1)) / K_vec.sum(axis=-1).reshape((T, 1)) ) r_p = ((r_gov * D) + (r_K * K_vec.sum(axis=-1).reshape((T, 1)))) / ( D + K_vec.sum(axis=-1).reshape((T, 1)) ) return np.squeeze(r_p)
[docs] def resource_constraint(Y, C, G, I_d, I_g, net_capital_flows, RM, foreign_aid): r""" Compute the error in the resource constraint. .. math:: \begin{split} \text{rc_error} &= \hat{Y}_t - \hat{C}_t - \Bigl(e^{g_y}\bigl[1 + \tilde{g}_{n,t+1}\bigr]\hat{K}^d_{t+1} - \hat{K}^d_t\Bigr) - \delta\hat{K}_t - \hat{G}_t - \hat{I}_{g,t} ... \\ &\qquad -\: \hat{\text{net capital outflows}}_t + \hat{RM}_t + \hat{\text{foreign aid}}_t \end{split} Args: Y (array_like): aggregate output by industry C (array_like): aggregate consumption by industry G (array_like): aggregate government spending by industry I_d (array_like): aggregate private investment from domestic households I_g (array_like): investment in government capital net_capital_flows (array_like): net capital outflows RM (array_like): aggregate remittances foreign_aid (array_like): foreign aid payments Returns: rc_error (array_like): error in the resource constraint """ rc_error = Y - C - I_d - I_g - G - net_capital_flows + RM + foreign_aid return rc_error
[docs] def get_capital_outflows(r, K_f, new_borrowing_f, debt_service_f, p): r""" Compute net capital outflows for open economy parameterizations .. math:: \text{net capital flows} &= r_{p,t}\hat{K}^f_t ... \\ &\quad\quad + \Bigl(e^{g_y}\bigl[1 + \tilde{g}_{n,t+1}\bigr]\hat{D}^f_{t+1} - \hat{D}^f_t\Bigr) - r_{p,t}\hat{D}^f_t \quad\forall t Args: r (array_like): the real interest rate K_f (array_like): aggregate capital that is foreign-owned new_borrowing_f (array_like): new borrowing of government debt from foreign investors debt_service_f (array_like): interest payments on government debt owned by foreigners p (OG-Core Specifications object): model parameters Returns: new_flow (array_like): net capital outflows """ net_flow = (r + p.delta) * K_f - new_borrowing_f + debt_service_f return net_flow
[docs] def get_K_splits(B, K_demand_open, D_d, zeta_K): r""" Returns total domestic capital as well as amounts of domestic capital held by domestic and foreign investors separately. .. math:: \begin{split} \hat{K}_{t} &= \hat{K}^{f}_{t} + \hat{K}^{d}_{t}\\ \hat{K}^{d}_{t} &= \hat{B}_{t} + \hat{D}^{d}_{t}\\ \hat{K}^{f}_{t} &= \zeta_{D}\left(\hat{K}^{open}_{t} - K^{d}_{t}\right) \end{split} Args: B (array_like): aggregate savings by domestic households K_demand_open (array_like): capital demand at the world interest rate D_d (array_like): governmet debt held by domestic households zeta_K (array_like): fraction of excess capital demand satisfied by foreign investors Returns: (tuple): series of capital stocks: * K (array_like): total capital * K_d (array_like): capital held by domestic households * K_f (array_like): capital held by foreign households """ K_d = B - D_d if np.any(K_d < 0): print( "K_d has negative elements. Setting them " + "positive to prevent NAN." ) K_d = np.fmax(K_d, 0.05 * B) K_f = zeta_K * (K_demand_open - B + D_d) K = K_f + K_d return K, K_d, K_f
[docs] def get_ptilde(p_i, tau_c, alpha_c, method="SS"): r""" Calculate price of composite good. .. math:: \tilde{p}_{t} = \prod_{i=1}^{I} \left(\frac{(1 + \tau^{c}_{i,t})p_{i,j}}{\alpha_{i,j}}\right)^{\alpha_{i,j}} Args: p_i (array_like): prices for consumption good i tau_c (array_like): consumption taxes on good i alpha_c (array_like): consumption share parameters Returns: p_tilde (array_like): tax-inclusive price of composite good """ if method == "SS": p_tilde = np.prod((((1 + tau_c) * p_i) / alpha_c) ** alpha_c) else: # TPI case alpha_c = alpha_c.reshape(1, alpha_c.shape[0]) p_tilde = np.prod((((1 + tau_c) * p_i) / alpha_c) ** alpha_c, axis=1) return p_tilde