diff --git a/CHANGELOG.md b/CHANGELOG.md index 4074be398..fcb2e8d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.15.3] - 2025-01-24 12:00:00 + +### Added + +- Two new parameters, `income_tax_filer` and `wealth_tax_filer`, that determine whether certain types `j` pay income or wealth taxes, respectively. See PR [#1084](https://github.com/PSLmodels/OG-Core/pull/1084) + ## [0.15.2] - 2025-01-22 12:00:00 ### Added -- A new parameters, `r_gov_DY` and `r_gov_DY2`, that allow the government interest rate to be a function of the debt-to-GDP ratio. See PR [#1037](https://github.com/PSLmodels/OG-Core/pull/1037) +- Two new parameters, `r_gov_DY` and `r_gov_DY2`, that allow the government interest rate to be a function of the debt-to-GDP ratio. See PR [#1037](https://github.com/PSLmodels/OG-Core/pull/1037) ## [0.15.1] - 2026-01-19 12:00:00 @@ -499,6 +505,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Any earlier versions of OG-USA can be found in the [`OG-Core`](https://github.com/PSLmodels/OG-Core) repository [release history](https://github.com/PSLmodels/OG-Core/releases) from [v.0.6.4](https://github.com/PSLmodels/OG-Core/releases/tag/v0.6.4) (Jul. 20, 2021) or earlier. +[0.15.3]: https://github.com/PSLmodels/OG-Core/compare/v0.15.2...v0.15.3 [0.15.2]: https://github.com/PSLmodels/OG-Core/compare/v0.15.1...v0.15.2 [0.15.1]: https://github.com/PSLmodels/OG-Core/compare/v0.15.0...v0.15.1 [0.15.0]: https://github.com/PSLmodels/OG-Core/compare/v0.14.14...v0.15.0 diff --git a/ogcore/SS.py b/ogcore/SS.py index 3e40d2e22..ed7b82e54 100644 --- a/ogcore/SS.py +++ b/ogcore/SS.py @@ -914,6 +914,10 @@ def SS_solver( np.reshape(p.labor_income_tax_noncompliance_rate[-1, :], (1, p.J)), (p.S, 1), ) + income_tax_filer_2D = np.tile( + np.reshape(p.income_tax_filer[-1, :], (1, p.J)), + (p.S, 1), + ) mtry_ss = tax.MTR_income( r_p_ss, wss, @@ -925,6 +929,7 @@ def SS_solver( etr_params_3D, mtry_params_3D, capital_noncompliance_rate_2D, + income_tax_filer_2D, p, ) mtrx_ss = tax.MTR_income( @@ -938,6 +943,7 @@ def SS_solver( etr_params_3D, mtrx_params_3D, labor_noncompliance_rate_2D, + income_tax_filer_2D, p, ) etr_ss = tax.ETR_income( @@ -950,6 +956,7 @@ def SS_solver( etr_params_3D, labor_noncompliance_rate_2D, capital_noncompliance_rate_2D, + income_tax_filer_2D, p, ) diff --git a/ogcore/TPI.py b/ogcore/TPI.py index d65f8d7aa..7b38ad68f 100644 --- a/ogcore/TPI.py +++ b/ogcore/TPI.py @@ -22,6 +22,7 @@ import os import warnings import logging +from distributed import wait if not SHOW_RUNTIME: warnings.simplefilter("ignore", RuntimeWarning) @@ -819,8 +820,6 @@ def run_TPI(p, client=None): futures.append(f) try: # Wait for futures with timeout, then gather results - from distributed import wait - done, not_done = wait(futures, timeout=600) if not_done: # Some futures didn't complete in time @@ -1315,6 +1314,10 @@ def run_TPI(p, client=None): ), (1, p.S, 1), ) + income_tax_filer_3D = np.tile( + np.reshape(p.income_tax_filer[: p.T, :], (p.T, 1, p.J)), + (1, p.S, 1), + ) e_3D = p.e mtry_path = tax.MTR_income( r_p_path[: p.T], @@ -1327,6 +1330,7 @@ def run_TPI(p, client=None): etr_params_4D, mtry_params_4D, capital_noncompliance_rate_3D, + income_tax_filer_3D, p, ) mtrx_path = tax.MTR_income( @@ -1340,6 +1344,7 @@ def run_TPI(p, client=None): etr_params_4D, mtrx_params_4D, labor_noncompliance_rate_3D, + income_tax_filer_3D, p, ) etr_path = tax.ETR_income( @@ -1352,9 +1357,9 @@ def run_TPI(p, client=None): etr_params_4D, labor_noncompliance_rate_3D, capital_noncompliance_rate_3D, + income_tax_filer_3D, p, ) - # Note that implicitly in this computation is that immigrants' # wealth is all in the form of private capital I_d = aggr.get_I( diff --git a/ogcore/__init__.py b/ogcore/__init__.py index 3285e7377..18e501dbf 100644 --- a/ogcore/__init__.py +++ b/ogcore/__init__.py @@ -20,4 +20,4 @@ from ogcore.txfunc import * from ogcore.utils import * -__version__ = "0.15.2" +__version__ = "0.15.3" diff --git a/ogcore/default_parameters.json b/ogcore/default_parameters.json index 0591a4640..a7d1bcce2 100644 --- a/ogcore/default_parameters.json +++ b/ogcore/default_parameters.json @@ -4304,6 +4304,62 @@ } } }, + "income_tax_filer": { + "title": "Income tax filer indicator", + "description": "Binary indicator for whether lifetime income type j is subject to income taxes. Non-filers (tax_filer[j]=0) are not subject to income taxes but still pay payroll taxes.", + "section_1": "Fiscal Policy Parameters", + "section_2": "Taxes", + "notes": "Specified by time T and lifetime income group J. Defaults to 1.0 (all groups file). Can be set to values between 0 and 1 to represent the share of group j that files.", + "type": "float", + "number_dims": 2, + "value": [ + { + "value": [[ + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ]] + } + ], + "validators": { + "range": { + "min": 0.0, + "max": 1.0 + } + } + }, + "wealth_tax_filer": { + "title": "Wealth tax filer indicator", + "description": "Binary indicator for whether lifetime income type j is subject to wealth taxes. Non-filers (wealth_tax_filer[j]=0) are not subject to wealth taxes but still pay payroll taxes.", + "section_1": "Fiscal Policy Parameters", + "section_2": "Taxes", + "notes": "Specified by time T and lifetime income group J. Defaults to 1.0 (all groups file). Can be set to values between 0 and 1 to represent the share of group j that files.", + "type": "float", + "number_dims": 2, + "value": [ + { + "value": [[ + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ]] + } + ], + "validators": { + "range": { + "min": 0.0, + "max": 1.0 + } + } + }, "nu": { "title": "Parameter for convergence rate of functional iteration", "description": "Parameter for convergence rate of functional iteration.", diff --git a/ogcore/household.py b/ogcore/household.py index b68a61270..1a88e53b4 100644 --- a/ogcore/household.py +++ b/ogcore/household.py @@ -480,15 +480,21 @@ def FOC_savings( beta = p.beta[j] if method == "SS": tax_noncompliance = p.capital_income_tax_noncompliance_rate[-1, j] + income_tax_filer = p.income_tax_filer[-1, j] + wealth_tax_filer = p.wealth_tax_filer[-1, j] e = np.squeeze(p.e[-1, :, j]) elif method == "TPI_scalar": tax_noncompliance = p.capital_income_tax_noncompliance_rate[0, j] + income_tax_filer = p.income_tax_filer[0, j] + wealth_tax_filer = p.wealth_tax_filer[0, j] e = np.squeeze(p.e[0, :, j]) else: length = r.shape[0] tax_noncompliance = p.capital_income_tax_noncompliance_rate[ t : t + length, j ] + income_tax_filer = p.income_tax_filer[t : t + length, j] + wealth_tax_filer = p.wealth_tax_filer[t : t + length, j] e_long = np.concatenate( ( p.e, @@ -502,15 +508,21 @@ def FOC_savings( beta = p.beta if method == "SS": tax_noncompliance = p.capital_income_tax_noncompliance_rate[-1, :] + income_tax_filer = p.income_tax_filer[-1, :] + wealth_tax_filer = p.wealth_tax_filer[-1, :] e = np.squeeze(p.e[-1, :, :]) elif method == "TPI_scalar": tax_noncompliance = p.capital_income_tax_noncompliance_rate[0, :] + income_tax_filer = p.income_tax_filer[0, :] + wealth_tax_filer = p.wealth_tax_filer[0, :] e = np.squeeze(p.e[0, :, :]) else: length = r.shape[0] tax_noncompliance = p.capital_income_tax_noncompliance_rate[ t : t + length, : ] + income_tax_filer = p.income_tax_filer[t : t + length, :] + wealth_tax_filer = p.wealth_tax_filer[t : t + length, :] e_long = np.concatenate( ( p.e, @@ -572,10 +584,11 @@ def FOC_savings( etr_params, mtry_params, tax_noncompliance, + income_tax_filer, p, ) ) - - tax.MTR_wealth(b, h_wealth, m_wealth, p_wealth) + - tax.MTR_wealth(b, h_wealth, m_wealth, p_wealth, wealth_tax_filer) ) savings_ut = ( rho * np.exp(-p.sigma * p.g_y) * chi_b * b_splus1 ** (-p.sigma) @@ -685,14 +698,17 @@ def FOC_labor( if j is not None: if method == "SS": tax_noncompliance = p.labor_income_tax_noncompliance_rate[-1, j] + income_tax_filer = p.income_tax_filer[-1, j] e = np.squeeze(p.e[-1, :, j]) elif method == "TPI_scalar": tax_noncompliance = p.labor_income_tax_noncompliance_rate[0, j] + income_tax_filer = p.income_tax_filer[0, j] e = np.squeeze(p.e[0, -1, j]) else: tax_noncompliance = p.labor_income_tax_noncompliance_rate[ t : t + length, j ] + income_tax_filer = p.income_tax_filer[t : t + length, j] e_long = np.concatenate( ( p.e, @@ -704,14 +720,17 @@ def FOC_labor( else: if method == "SS": tax_noncompliance = p.labor_income_tax_noncompliance_rate[-1, :] + income_tax_filer = p.income_tax_filer[-1, :] e = np.squeeze(p.e[-1, :, :]) elif method == "TPI_scalar": tax_noncompliance = p.labor_income_tax_noncompliance_rate[0, :] + income_tax_filer = p.income_tax_filer[0, :] e = np.squeeze(p.e[0, -1, :]) else: tax_noncompliance = p.labor_income_tax_noncompliance_rate[ t : t + length, : ] + income_tax_filer = p.income_tax_filer[t : t + length, :] e_long = np.concatenate( ( p.e, @@ -761,6 +780,7 @@ def FOC_labor( etr_params, mtrx_params, tax_noncompliance, + income_tax_filer, p, ) ) diff --git a/ogcore/parameters.py b/ogcore/parameters.py index 0134d21fc..57df782ca 100644 --- a/ogcore/parameters.py +++ b/ogcore/parameters.py @@ -208,6 +208,8 @@ def compute_default_params(self): "labor_income_tax_noncompliance_rate", "capital_income_tax_noncompliance_rate", "replacement_rate_adjust", + "income_tax_filer", + "wealth_tax_filer", ] for item in tp_param_list3: param_in = getattr(self, item) diff --git a/ogcore/tax.py b/ogcore/tax.py index 4d0110463..b1e232b21 100644 --- a/ogcore/tax.py +++ b/ogcore/tax.py @@ -16,7 +16,7 @@ """ -def ETR_wealth(b, h_wealth, m_wealth, p_wealth): +def ETR_wealth(b, h_wealth, m_wealth, p_wealth, tax_filer): r""" Calculates the effective tax rate on wealth. @@ -29,6 +29,7 @@ def ETR_wealth(b, h_wealth, m_wealth, p_wealth): h_wealth (scalar): parameter of wealth tax function p_wealth (scalar): parameter of wealth tax function m_wealth (scalar): parameter of wealth tax function + tax_filer (Numpy array): array indicating tax filer status Returns: tau_w (Numpy array): effective tax rate on wealth, size = SxJ @@ -36,10 +37,13 @@ def ETR_wealth(b, h_wealth, m_wealth, p_wealth): """ tau_w = (p_wealth * h_wealth * b) / (h_wealth * b + m_wealth) + tau_w = tau_w * tax_filer + # TODO: figure out how to handle non-filers wih diff j and over time path + return tau_w -def MTR_wealth(b, h_wealth, m_wealth, p_wealth): +def MTR_wealth(b, h_wealth, m_wealth, p_wealth, tax_filer): r""" Calculates the marginal tax rate on wealth from the wealth tax. @@ -52,14 +56,17 @@ def MTR_wealth(b, h_wealth, m_wealth, p_wealth): h_wealth (scalar): parameter of wealth tax function p_wealth (scalar): parameter of wealth tax function m_wealth (scalar): parameter of wealth tax function + tax_filer (Numpy array): array indicating tax filer status Returns: tau_prime (Numpy array): marginal tax rate on wealth, size = SxJ """ - tau_prime = ETR_wealth(b, h_wealth, m_wealth, p_wealth) * 2 - ( + tau_prime = ETR_wealth(b, h_wealth, m_wealth, p_wealth, tax_filer) * 2 - ( (h_wealth**2 * p_wealth * b**2) / ((b * h_wealth + m_wealth) ** 2) ) + tau_prime = tau_prime * tax_filer + # TODO: figure out how to handle non-filers in TPI when j is None return tau_prime @@ -74,6 +81,7 @@ def ETR_income( etr_params, labor_noncompliance_rate, capital_noncompliance_rate, + tax_filer, p, ): """ @@ -91,6 +99,7 @@ def ETR_income( parameters or nonparametric function labor_noncompliance_rate (Numpy array): income tax noncompliance rate for labor income capital_noncompliance_rate (Numpy array): income tax noncompliance rate for capital income + tax_filer (Numpy array): array indicating tax filer status p (OG-Core Specifications object): model parameters Returns: @@ -107,7 +116,9 @@ def ETR_income( etr_params, X, Y, None, p.tax_func_type, "etr", for_estimation=False ) - return tau * (1 - noncompliance_rate) + tau = tau * (1 - noncompliance_rate) * tax_filer + + return tau def MTR_income( @@ -121,6 +132,7 @@ def MTR_income( etr_params, mtr_params, noncompliance_rate, + tax_filer, p, ): r""" @@ -141,6 +153,7 @@ def MTR_income( mtr_params (list): list of marginal tax rate function parameters or nonparametric function noncompliance_rate (Numpy array): income tax noncompliance rate + tax_filer (Numpy array): array indicating tax filer status p (OG-Core Specifications object): model parameters Returns: @@ -175,7 +188,9 @@ def MTR_income( for_estimation=False, ) - return tau * (1 - noncompliance_rate) + tau = tau * (1 - noncompliance_rate) * tax_filer + + return tau def get_biz_tax(w, Y, L, K, p_m, p, m, method): @@ -328,6 +343,7 @@ def income_tax_liab(r, w, b, n, factor, t, j, method, e, etr_params, p): capital_income_tax_compliance_rate = ( p.capital_income_tax_noncompliance_rate[t, j] ) + tax_filer = p.income_tax_filer[t, j] else: labor_income_tax_compliance_rate = ( p.labor_income_tax_noncompliance_rate[-1, j] @@ -335,6 +351,7 @@ def income_tax_liab(r, w, b, n, factor, t, j, method, e, etr_params, p): capital_income_tax_compliance_rate = ( p.capital_income_tax_noncompliance_rate[-1, j] ) + tax_filer = p.income_tax_filer[-1, j] else: if method == "TPI": r = utils.to_timepath_shape(r) @@ -345,6 +362,7 @@ def income_tax_liab(r, w, b, n, factor, t, j, method, e, etr_params, p): capital_income_tax_compliance_rate = ( p.capital_income_tax_noncompliance_rate[t, :] ) + tax_filer = p.income_tax_filer[t, :] else: labor_income_tax_compliance_rate = ( p.labor_income_tax_noncompliance_rate[-1, :] @@ -352,6 +370,7 @@ def income_tax_liab(r, w, b, n, factor, t, j, method, e, etr_params, p): capital_income_tax_compliance_rate = ( p.capital_income_tax_noncompliance_rate[-1, :] ) + tax_filer = p.income_tax_filer[-1, :] income = r * b + w * e * n labor_income = w * e * n T_I = ( @@ -365,10 +384,12 @@ def income_tax_liab(r, w, b, n, factor, t, j, method, e, etr_params, p): etr_params, labor_income_tax_compliance_rate, capital_income_tax_compliance_rate, + tax_filer, p, ) * income ) + if method == "SS": T_P = p.tau_payroll[-1] * labor_income elif method == "TPI": @@ -413,12 +434,25 @@ def wealth_tax_liab(r, b, t, j, method, p): if method == "TPI": if b.ndim == 2: r = r.reshape(r.shape[0], 1) + tax_filer = p.wealth_tax_filer[:, j] # TODO check this + else: + tax_filer = p.wealth_tax_filer[-1, j] else: if method == "TPI": r = utils.to_timepath_shape(r) + tax_filer = p.wealth_tax_filer[:, :] + elif method == "TPI_scalar": + tax_filer = p.tax_filer[t, :] + elif method == "SS": + tax_filer = p.wealth_tax_filer[-1, :] if method == "SS": - T_W = ETR_wealth(b, p.h_wealth[-1], p.m_wealth[-1], p.p_wealth[-1]) * b + T_W = ( + ETR_wealth( + b, p.h_wealth[-1], p.m_wealth[-1], p.p_wealth[-1], tax_filer + ) + * b + ) elif method == "TPI": length = r.shape[0] if len(b.shape) == 1: @@ -428,6 +462,7 @@ def wealth_tax_liab(r, b, t, j, method, p): p.h_wealth[t : t + length], p.m_wealth[t : t + length], p.p_wealth[t : t + length], + tax_filer[t : t + length], ) * b ) @@ -438,6 +473,7 @@ def wealth_tax_liab(r, b, t, j, method, p): p.h_wealth[t : t + length], p.m_wealth[t : t + length], p.p_wealth[t : t + length], + tax_filer[t : t + length], ) * b ) @@ -448,11 +484,17 @@ def wealth_tax_liab(r, b, t, j, method, p): p.h_wealth[t : t + length].reshape(length, 1, 1), p.m_wealth[t : t + length].reshape(length, 1, 1), p.p_wealth[t : t + length].reshape(length, 1, 1), + tax_filer[t : t + length, :].reshape(length, 1, p.J), ) * b ) elif method == "TPI_scalar": - T_W = ETR_wealth(b, p.h_wealth[0], p.m_wealth[0], p.p_wealth[0]) * b + T_W = ( + ETR_wealth( + b, p.h_wealth[0], p.m_wealth[0], p.p_wealth[0], tax_filer + ) + * b + ) return T_W diff --git a/setup.py b/setup.py index 75d9facbc..97237865c 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="ogcore", - version="0.15.2", + version="0.15.3", author="Jason DeBacker and Richard W. Evans", license="CC0 1.0 Universal (CC0 1.0) Public Domain Dedication", description="A general equilibrium overlapping generations model for fiscal policy analysis", diff --git a/tests/test_aggregates.py b/tests/test_aggregates.py index 16cfe1ffe..822a1fdaf 100644 --- a/tests/test_aggregates.py +++ b/tests/test_aggregates.py @@ -15,6 +15,8 @@ "chi_n": np.ones(2), "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "eta": (np.ones((40, 2)) / (40 * 2)), "lambdas": [0.6, 0.4], @@ -61,6 +63,8 @@ def test_get_L(n, p, method, expected): "e": np.ones((40, 2)), "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "eta": (np.ones((40, 2)) / (40 * 2)), "lambdas": [0.6, 0.4], @@ -144,6 +148,8 @@ def test_get_I(b_splus1, K_p1, K, p, method, expected): "e": np.ones((40, 2)), "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "eta": (np.ones((40, 2)) / (40 * 2)), "lambdas": [0.6, 0.4], @@ -203,6 +209,8 @@ def test_get_B(b, p, method, PreTP, expected): "e": np.ones((40, 2)), "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "eta": (np.ones((40, 2)) / (40 * 2)), "lambdas": [0.6, 0.4], @@ -1134,6 +1142,8 @@ def test_get_RM(Y, p, method, expected): "M": 3, "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "eta": (np.ones((40, 2)) / (40 * 2)), "lambdas": [0.6, 0.4], @@ -1185,6 +1195,8 @@ def test_get_C(c, p, method, expected): "e": np.ones((20, 2)), "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "eta": (np.ones((20, 2)) / (20 * 2)), "lambdas": [0.6, 0.4], @@ -1244,6 +1256,8 @@ def test_get_C(c, p, method, expected): "e": np.ones((20, 2)), "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "eta": (np.ones((20, 2)) / (20 * 2)), "lambdas": [0.6, 0.4], "tau_bq": [0.17], @@ -1286,6 +1300,8 @@ def test_get_C(c, p, method, expected): "e": np.ones((20, 2)), "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "eta": (np.ones((20, 2)) / (20 * 2)), "lambdas": [0.6, 0.4], diff --git a/tests/test_household.py b/tests/test_household.py index 8adc2ef9b..06f9f7dcf 100644 --- a/tests/test_household.py +++ b/tests/test_household.py @@ -507,6 +507,8 @@ def test_get_cons(model_args, expected): p1.T = 3 p1.labor_income_tax_noncompliance_rate = np.zeros((p1.T + p1.S, p1.J)) p1.capital_income_tax_noncompliance_rate = np.zeros((p1.T + p1.S, p1.J)) +p1.income_tax_filer = np.ones((p1.T, p1.J)) +p1.wealth_tax_filer = np.ones((p1.T, p1.J)) p1.analytical_mtrs = False etr_params = np.array( [ @@ -826,6 +828,8 @@ def test_FOC_savings(model_vars, in_params, expected): p1.T = 3 p1.labor_income_tax_noncompliance_rate = np.zeros((p1.T + p1.S, p1.J)) p1.capital_income_tax_noncompliance_rate = np.zeros((p1.T + p1.S, p1.J)) +p1.income_tax_filer = np.ones((p1.T, p1.J)) +p1.wealth_tax_filer = np.ones((p1.T, p1.J)) p1.analytical_mtrs = False etr_params = np.array( [ diff --git a/tests/test_output_tables.py b/tests/test_output_tables.py index 27c59c060..3da0614e7 100644 --- a/tests/test_output_tables.py +++ b/tests/test_output_tables.py @@ -157,12 +157,22 @@ def test_dynamic_revenue_decomposition(include_business_tax, full_break_out): base_params.capital_income_tax_noncompliance_rate = np.zeros( (base_params.T, base_params.J) ) + base_params.income_tax_filer = np.ones((base_params.T, base_params.J)) + base_params.wealth_tax_filer = np.ones((base_params.T, base_params.J)) reform_params.labor_income_tax_noncompliance_rate = np.zeros( (reform_params.T, reform_params.J) ) reform_params.capital_income_tax_noncompliance_rate = np.zeros( (reform_params.T, reform_params.J) ) + reform_params.income_tax_filer = np.ones( + (reform_params.T, reform_params.J) + ) + reform_params.wealth_tax_filer = np.ones( + (reform_params.T, reform_params.J) + ) + base_params.tax_filer = np.ones(base_params.J) + reform_params.tax_filer = np.ones(reform_params.J) # check if tax parameters are a numpy array # this is relevant for cached parameter arrays saved before # tax params were put in lists diff --git a/tests/test_pensions.py b/tests/test_pensions.py index 6fc472dcd..1666cc290 100644 --- a/tests/test_pensions.py +++ b/tests/test_pensions.py @@ -13,6 +13,8 @@ "lambdas": [1.0], "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1]], + "wealth_tax_filer": [[1]], "replacement_rate_adjust": [[1.0]], "J": 1, "T": 4, diff --git a/tests/test_tax.py b/tests/test_tax.py index 93aef196a..918c92033 100644 --- a/tests/test_tax.py +++ b/tests/test_tax.py @@ -22,6 +22,8 @@ "m_wealth": [4], "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": np.ones((3, 1)), + "wealth_tax_filer": np.ones((3, 1)), "replacement_rate_adjust": [[1.0]], } p1.update_specifications(new_param_values) @@ -41,6 +43,8 @@ "m_wealth": [3, 4, 3], "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": np.ones((3, 1)), + "wealth_tax_filer": np.ones((3, 1)), "replacement_rate_adjust": [[1.0]], } p2.update_specifications(new_param_values2) @@ -55,7 +59,11 @@ def test_ETR_wealth(b, p, expected): # Test wealth tax computation tau_w = tax.ETR_wealth( - b, p.h_wealth[: p.T], p.m_wealth[: p.T], p.p_wealth[: p.T] + b, + p.h_wealth[: p.T], + p.m_wealth[: p.T], + p.p_wealth[: p.T], + p.wealth_tax_filer[: p.T, :], ) assert np.allclose(tau_w, expected) @@ -77,6 +85,8 @@ def test_ETR_wealth(b, p, expected): "m_wealth": [5], "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": np.ones((3, 1)), + "wealth_tax_filer": np.ones((3, 1)), "replacement_rate_adjust": [[1.0]], } p1.update_specifications(new_param_values) @@ -97,6 +107,8 @@ def test_ETR_wealth(b, p, expected): "m_wealth": [3, 4, 3], "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": np.ones((3, 1)), + "wealth_tax_filer": np.ones((3, 1)), "replacement_rate_adjust": [[1.0]], } p2.update_specifications(new_param_values2) @@ -111,7 +123,11 @@ def test_ETR_wealth(b, p, expected): def test_MTR_wealth(b, p, expected): # Test marginal tax rate on wealth tau_w_prime = tax.MTR_wealth( - b, p.h_wealth[: p.T], p.m_wealth[: p.T], p.p_wealth[: p.T] + b, + p.h_wealth[: p.T], + p.m_wealth[: p.T], + p.p_wealth[: p.T], + p.wealth_tax_filer[: p.T, :], ) assert np.allclose(tau_w_prime, expected) @@ -122,6 +138,7 @@ def test_MTR_wealth(b, p, expected): p1.J = 1 p1.labor_income_tax_noncompliance_rate = np.zeros((p1.T, p1.S, p1.J)) p1.capital_income_tax_noncompliance_rate = np.zeros((p1.T, p1.S, p1.J)) +p1.income_tax_filer = np.ones((p1.T, p1.S, p1.J)) p1.e = np.array([0.5, 0.45]) p1.tax_func_type = "DEP" etr_params1 = np.reshape( @@ -165,6 +182,7 @@ def test_MTR_wealth(b, p, expected): p2.J = 1 p2.labor_income_tax_noncompliance_rate = np.zeros((p2.T, p2.S, p2.J)) p2.capital_income_tax_noncompliance_rate = np.zeros((p2.T, p2.S, p2.J)) +p2.income_tax_filer = np.ones((p2.T, p2.S, p2.J)) p2.e = np.array([0.5, 0.45]) p2.tax_func_type = "GS" etr_params2 = np.reshape( @@ -182,6 +200,7 @@ def test_MTR_wealth(b, p, expected): p3.J = 1 p3.labor_income_tax_noncompliance_rate = np.zeros((p3.T, p3.S, p3.J)) p3.capital_income_tax_noncompliance_rate = np.zeros((p3.T, p3.S, p3.J)) +p3.income_tax_filer = np.ones((p3.T, p3.S, p3.J)) p3.e = np.array([0.5, 0.45]) p3.tax_func_type = "DEP_totalinc" etr_params3 = np.reshape( @@ -199,6 +218,7 @@ def test_MTR_wealth(b, p, expected): p4.J = 1 p4.labor_income_tax_noncompliance_rate = np.zeros((p4.T, p4.S, p4.J)) p4.capital_income_tax_noncompliance_rate = np.zeros((p4.T, p4.S, p4.J)) +p4.income_tax_filer = np.ones((p4.T, p4.S, p4.J)) p4.e = np.array([0.5, 0.45, 0.3]) p4.tax_func_type = "DEP" etr_params4 = np.reshape( @@ -253,6 +273,7 @@ def test_MTR_wealth(b, p, expected): p5 = copy.deepcopy(p1) p5.labor_income_tax_noncompliance_rate = np.ones((p5.T, p5.S, p5.J)) * 0.05 p5.capital_income_tax_noncompliance_rate = np.ones((p5.T, p5.S, p5.J)) * 0.05 +p5.income_tax_filer = np.ones((p5.T, p5.S, p5.J)) @pytest.mark.parametrize( @@ -311,6 +332,7 @@ def test_ETR_income(b, n, etr_params, params, expected): etr_params, params.labor_income_tax_noncompliance_rate, params.capital_income_tax_noncompliance_rate, + params.income_tax_filer, params, ) assert np.allclose(test_ETR_income, expected) @@ -322,6 +344,7 @@ def test_ETR_income(b, n, etr_params, params, expected): p1.J = 1 p1.labor_income_tax_noncompliance_rate = np.zeros((p1.T, p1.S, p1.J)) p1.capital_income_tax_noncompliance_rate = np.zeros((p1.T, p1.S, p1.J)) +p1.income_tax_filer = np.ones((p1.T, p1.S, p1.J)) p1.tax_func_type = "DEP" p1.analytical_mtrs = True etr_params1 = np.reshape( @@ -428,6 +451,8 @@ def test_ETR_income(b, n, etr_params, params, expected): p2.J = 1 p2.labor_income_tax_noncompliance_rate = np.zeros((p2.T, p2.S, p2.J)) p2.capital_income_tax_noncompliance_rate = np.zeros((p2.T, p2.S, p2.J)) +p2.income_tax_filer = np.ones((p2.T, p2.S, p2.J)) +p2.wealth_tax_filer = np.ones((p2.T, p2.S, p2.J)) p2.tax_func_type = "DEP" p2.analytical_mtrs = True etr_params2 = np.reshape( @@ -534,6 +559,7 @@ def test_ETR_income(b, n, etr_params, params, expected): p3.J = 1 p3.labor_income_tax_noncompliance_rate = np.zeros((p3.T, p3.S, p3.J)) p3.capital_income_tax_noncompliance_rate = np.zeros((p3.T, p3.S, p3.J)) +p3.income_tax_filer = np.ones((p3.T, p3.S, p3.J)) p3.tax_func_type = "DEP" p3.analytical_mtrs = False etr_params3 = np.reshape( @@ -641,6 +667,7 @@ def test_ETR_income(b, n, etr_params, params, expected): p4.J = 1 p4.labor_income_tax_noncompliance_rate = np.zeros((p4.T, p4.S, p4.J)) p4.capital_income_tax_noncompliance_rate = np.zeros((p4.T, p4.S, p4.J)) +p4.income_tax_filer = np.ones((p4.T, p4.S, p4.J)) p4.tax_func_type = "GS" p4.analytical_mtrs = False etr_params4 = np.reshape( @@ -670,6 +697,7 @@ def test_ETR_income(b, n, etr_params, params, expected): p5.J = 1 p5.labor_income_tax_noncompliance_rate = np.zeros((p5.T, p5.S, p5.J)) p5.capital_income_tax_noncompliance_rate = np.zeros((p5.T, p5.S, p5.J)) +p5.income_tax_filer = np.ones((p5.T, p5.S, p5.J)) p5.tax_func_type = "DEP_totalinc" p5.analytical_mtrs = True etr_params5 = np.reshape( @@ -699,6 +727,7 @@ def test_ETR_income(b, n, etr_params, params, expected): p6.J = 1 p6.labor_income_tax_noncompliance_rate = np.zeros((p6.T, p6.S, p6.J)) p6.capital_income_tax_noncompliance_rate = np.zeros((p6.T, p6.S, p6.J)) +p6.income_tax_filer = np.ones((p6.T, p6.S, p6.J)) p6.tax_func_type = "DEP_totalinc" p6.analytical_mtrs = False etr_params6 = np.reshape( @@ -724,6 +753,7 @@ def test_ETR_income(b, n, etr_params, params, expected): p7 = copy.deepcopy(p4) p7.labor_income_tax_noncompliance_rate = np.ones((p7.T, p7.S, p7.J)) * 0.05 p7.capital_income_tax_noncompliance_rate = np.ones((p7.T, p7.S, p7.J)) * 0.05 +p7.income_tax_filer = np.ones((p7.T, p7.S, p7.J)) @pytest.mark.parametrize( @@ -814,6 +844,7 @@ def test_MTR_income(etr_params, mtr_params, params, mtr_capital, expected): etr_params, mtr_params, noncompliance_rate, + params.income_tax_filer, params, ) assert np.allclose(test_mtr, expected) @@ -899,6 +930,8 @@ def test_get_biz_tax(w, Y, L, K, p_m, p, m, method, expected): p.S = 3 p.labor_income_tax_noncompliance_rate = np.zeros((p.T, p.S, p.J)) p.capital_income_tax_noncompliance_rate = np.zeros((p.T, p.S, p.J)) +p.income_tax_filer = np.ones((p.T, p.J)) +p.wealth_tax_filer = np.ones((p.T, p.J)) p.replacement_rate_adjust = np.ones((p.T, p.J)) p.lambdas = np.array([1.0]) p.e = np.array([0.5, 0.45, 0.3]).reshape(3, 1) @@ -914,6 +947,8 @@ def test_get_biz_tax(w, Y, L, K, p_m, p, m, method, expected): p3.T = 3 p3.labor_income_tax_noncompliance_rate = np.zeros((p3.T, p3.S, p3.J)) p3.capital_income_tax_noncompliance_rate = np.zeros((p3.T, p3.S, p3.J)) +p3.income_tax_filer = np.ones((p3.T, p3.J)) +p3.wealth_tax_filer = np.ones((p3.T, p3.J)) p3.replacement_rate_adjust = np.ones((p3.T, p3.J)) p4 = copy.deepcopy(p) p5 = copy.deepcopy(p) @@ -922,6 +957,8 @@ def test_get_biz_tax(w, Y, L, K, p_m, p, m, method, expected): p5.T = 3 p5.labor_income_tax_noncompliance_rate = np.zeros((p5.T, p5.S, p5.J)) p5.capital_income_tax_noncompliance_rate = np.zeros((p5.T, p5.S, p5.J)) +p5.income_tax_filer = np.ones((p5.T, p5.J)) +p5.wealth_tax_filer = np.ones((p5.T, p5.J)) p5.replacement_rate_adjust = np.ones((p5.T, p5.J)) p5.lambdas = np.array([0.65, 0.35]) # set variables and other parameters for each case @@ -1169,6 +1206,8 @@ def test_get_biz_tax(w, Y, L, K, p_m, p, m, method, expected): "ubi_nom_65p": 500, "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], + "income_tax_filer": [[1.0]], + "wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], } p_u.update_specifications(new_param_values_ubi) @@ -1305,6 +1344,8 @@ def test_get_biz_tax(w, Y, L, K, p_m, p, m, method, expected): p12.capital_income_tax_noncompliance_rate = ( np.ones((p12.T + p12.S, p12.J)) * 0.05 ) +p12.income_tax_filer = np.ones((p12.T + p12.S, p12.J)) +p12.wealth_tax_filer = np.ones((p12.T + p12.S, p12.J)) p12.replacement_rate_adjust = np.ones((p12.T + p12.S, p12.J)) * 1.0 p13 = copy.deepcopy(p5) p13.labor_income_tax_noncompliance_rate = ( @@ -1313,6 +1354,8 @@ def test_get_biz_tax(w, Y, L, K, p_m, p, m, method, expected): p13.capital_income_tax_noncompliance_rate = ( np.ones((p13.T + p13.S, p13.J)) * 0.05 ) +p13.income_tax_filer = np.ones((p13.T + p13.S, p13.J)) +p13.wealth_tax_filer = np.ones((p13.T + p13.S, p13.J)) p13.replacement_rate_adjust = np.ones((p13.T + p13.S, p13.J)) * 1.0 expected1 = np.array([0.47374766, -0.09027663, 0.03871394]) @@ -1708,7 +1751,7 @@ def test_get_biz_tax(w, Y, L, K, p_m, p, m, method, expected): "TPI scalar UBI>0", "TPI UBI>0", "SS, noncomply", - "TPI 3D. noncomply", + "TPI 3D, noncomply", ], ) def test_net_taxes( diff --git a/tests/testing_params.json b/tests/testing_params.json index d38869512..2c6862d65 100644 --- a/tests/testing_params.json +++ b/tests/testing_params.json @@ -16,6 +16,8 @@ "chi_b": [80, 80], "labor_income_tax_noncompliance_rate": [[0.0]], "capital_income_tax_noncompliance_rate": [[0.0]], +"income_tax_filer": [[1.0]], +"wealth_tax_filer": [[1.0]], "replacement_rate_adjust": [[1.0]], "tax_func_type": "linear", "age_specific": false,