diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 75c6b0f11..c0c2b9f2c 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -1,4 +1,4 @@ -name: Build and test [Python 3.9, 3.10] +name: Build and test [Python 3.9, 3.10, 3.11] on: [push, pull_request] # paths: @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.9", "3.10"] + python-version: ["3.9", "3.10", "3.11"] steps: - name: Checkout diff --git a/ogcore/__init__.py b/ogcore/__init__.py index a0111b9d7..61f79adca 100644 --- a/ogcore/__init__.py +++ b/ogcore/__init__.py @@ -19,4 +19,4 @@ from ogcore.txfunc import * from ogcore.utils import * -__version__ = "0.10.9" +__version__ = "0.10.10" diff --git a/ogcore/default_parameters.json b/ogcore/default_parameters.json index 5507437c9..e9cce702f 100644 --- a/ogcore/default_parameters.json +++ b/ogcore/default_parameters.json @@ -3560,25 +3560,6 @@ } } }, - "surv_rate": { - "title": "Age-specific survival rates.", - "description": "Age-specific survival rates.", - "section_1": "Demographic Parameters", - "notes": "", - "type": "float", - "number_dims": 1, - "value": [ - { - "value": [0.9992671466037937, 0.9991828802577581, 0.9991211270330613, 0.9990883474791409, 0.9990770197674355, 0.9990726593185169, 0.9990642897606029, 0.9990499046296879, 0.9990240264434502, 0.9989896445418176, 0.9989502540254145, 0.998910349441698, 0.998870943541464, 0.9988345543962669, 0.9987956760741559, 0.9987488226134084, 0.9986889954653082, 0.9986171744272776, 0.9985303646213153, 0.998427079774127, 0.9983098400876006, 0.9981746848684268, 0.9980121413191697, 0.9978177515814708, 0.997594541650623, 0.9973505043019144, 0.9970876400979336, 0.9968034781080352, 0.9964985595886034, 0.9961734146946981, 0.9958240228000609, 0.995453999920133, 0.9950706133080164, 0.9946765593201183, 0.9942678373891507, 0.9938170507984443, 0.9933349369903478, 0.9928571072472757, 0.9923946652766062, 0.991920706393161, 0.9913928161973069, 0.9907804595728024, 0.9900830226826921, 0.989286205338525, 0.9883813193193698, 0.9873488243699589, 0.9861937992775882, 0.9849307685778957, 0.983560091293199, 0.982053714634729, 0.9803171600092101, 0.9783569087279551, 0.9762533665241926, 0.9740162807067863, 0.9715717265593735, 0.9687414019675059, 0.965488871011245, 0.9619129374942117, 0.9580075267785504, 0.9536731613575886, 0.9486689819644183, 0.9429581421788265, 0.9366798451956814, 0.9298230368449151, 0.9222311641627915, 0.9137096685771553, 0.9040938100791817, 0.8932757445596693, 0.8812014250865317, 0.8678747435632396, 0.8533171772195793, 0.8375675224210112, 0.8206730335100753, 0.8026865855423447, 0.7836605522031078, 0.764642367296632, 0.7459401555112076, 0.7278808285655526, 0.7108048201866021, 0.0] - } - ], - "validators": { - "range": { - "min": 0.0, - "max": 1.0 - } - } - }, "etr_params": { "title": "Effective tax rate function parameters.", "description": "Effective tax rate function parameters.", diff --git a/ogcore/output_tables.py b/ogcore/output_tables.py index f93ee7806..d0514831b 100644 --- a/ogcore/output_tables.py +++ b/ogcore/output_tables.py @@ -605,18 +605,27 @@ def dynamic_revenue_decomposition( year_list.append("SS") table_dict = {"Year": year_list} T, S, J = base_params.T, base_params.S, base_params.J - base_etr_params_4D = np.tile( - base_params.etr_params[:T, :, :].reshape( - T, S, 1, base_params.etr_params.shape[2] - ), - (1, 1, J, 1), - ) - reform_etr_params_4D = np.tile( - reform_params.etr_params[:T, :, :].reshape( - T, S, 1, reform_params.etr_params.shape[2] - ), - (1, 1, J, 1), - ) + num_params = len(base_params.etr_params[0][0]) + base_etr_params_4D = [ + [ + [ + [base_params.etr_params[t][s][i] for i in range(num_params)] + for j in range(J) + ] + for s in range(S) + ] + for t in range(T) + ] + reform_etr_params_4D = [ + [ + [ + [reform_params.etr_params[t][s][i] for i in range(num_params)] + for j in range(J) + ] + for s in range(S) + ] + for t in range(T) + ] tax_rev_dict = {"indiv": {}, "biz": {}, "total": {}} indiv_liab = {} # Baseline IIT + payroll tax liability diff --git a/ogcore/parameters.py b/ogcore/parameters.py index 31f8cad4a..56d62d823 100644 --- a/ogcore/parameters.py +++ b/ogcore/parameters.py @@ -574,8 +574,9 @@ def compute_default_params(self): if self.constant_demographics: self.g_n_ss = 0.0 self.g_n = np.zeros(self.T + self.S) + surv_rate = np.ones_like(self.rho) - self.rho surv_rate1 = np.ones((self.S,)) # prob start at age S - surv_rate1[1:] = np.cumprod(self.surv_rate[:-1], dtype=float) + surv_rate1[1:] = np.cumprod(surv_rate[-1, :-1], dtype=float) # number of each age alive at any time omega_SS = np.ones(self.S) * surv_rate1 self.omega_SS = omega_SS / omega_SS.sum() diff --git a/setup.py b/setup.py index e57d52f1b..724ff9f8c 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="ogcore", - version="0.10.9", + version="0.10.10", author="Jason DeBacker and Richard W. Evans", license="CC0 1.0 Universal (CC0 1.0) Public Domain Dedication", description="A general equilibribum overlapping generations model for fiscal policy analysis", diff --git a/tests/test_TPI.py b/tests/test_TPI.py index 797f2c2c5..180c5bce9 100644 --- a/tests/test_TPI.py +++ b/tests/test_TPI.py @@ -4,6 +4,7 @@ import pickle import numpy as np import os +import sys import json from ogcore import SS, TPI, utils import ogcore.aggregates as aggr @@ -570,38 +571,36 @@ def test_run_TPI(baseline, param_updates, filename, tmpdir, dask_client): CUR_PATH, "test_io_data", "run_TPI_outputs_baseline_Kg_nonzero_2.pkl" ) # read in mono tax funcs (not age specific) -dict_params = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_mono_nonage.pkl") -) -p = Specifications() -etr_params = [[None] * p.S] * p.T -mtrx_params = [[None] * p.S] * p.T -mtry_params = [[None] * p.S] * p.T -for s in range(p.S): - for t in range(p.T): - if t < p.BW: - etr_params[t][s] = dict_params["tfunc_etr_params_S"][t][s] - mtrx_params[t][s] = dict_params["tfunc_mtrx_params_S"][t][s] - mtry_params[t][s] = dict_params["tfunc_mtry_params_S"][t][s] - else: - etr_params[t][s] = dict_params["tfunc_etr_params_S"][-1][s] - mtrx_params[t][s] = dict_params["tfunc_mtrx_params_S"][-1][s] - mtry_params[t][s] = dict_params["tfunc_mtry_params_S"][-1][s] -param_updates9 = { - "tax_func_type": "mono", - "etr_params": etr_params, - "mtrx_params": mtrx_params, - "mtry_params": mtry_params, -} -filename9 = os.path.join( - CUR_PATH, "test_io_data", "run_TPI_outputs_mono_2.pkl" -) - +if sys.version_info[1] < 11: + dict_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_mono_nonage.pkl") + ) + p = Specifications() + etr_params = [[None] * p.S] * p.T + mtrx_params = [[None] * p.S] * p.T + mtry_params = [[None] * p.S] * p.T + for s in range(p.S): + for t in range(p.T): + if t < p.BW: + etr_params[t][s] = dict_params["tfunc_etr_params_S"][t][s] + mtrx_params[t][s] = dict_params["tfunc_mtrx_params_S"][t][s] + mtry_params[t][s] = dict_params["tfunc_mtry_params_S"][t][s] + else: + etr_params[t][s] = dict_params["tfunc_etr_params_S"][-1][s] + mtrx_params[t][s] = dict_params["tfunc_mtrx_params_S"][-1][s] + mtry_params[t][s] = dict_params["tfunc_mtry_params_S"][-1][s] + param_updates9 = { + "tax_func_type": "mono", + "etr_params": etr_params, + "mtrx_params": mtrx_params, + "mtry_params": mtry_params, + } + filename9 = os.path.join( + CUR_PATH, "test_io_data", "run_TPI_outputs_mono_2.pkl" + ) -@pytest.mark.local -@pytest.mark.parametrize( - "baseline,param_updates,filename", - [ +if sys.version_info[1] < 11: + test_list = [ (True, param_updates2, filename2), (True, param_updates5, filename5), (True, param_updates6, filename6), @@ -610,8 +609,8 @@ def test_run_TPI(baseline, param_updates, filename, tmpdir, dask_client): (False, param_updates4, filename4), (True, param_updates8, filename8), (True, param_updates9, filename9), - ], - ids=[ + ] + id_list = [ "Baseline, balanced budget", "Baseline, small open", "Baseline, small open for some periods", @@ -620,7 +619,33 @@ def test_run_TPI(baseline, param_updates, filename, tmpdir, dask_client): "Reform, baseline spending", "Baseline, Kg>0", "mono tax functions", - ], + ] +else: + test_list = [ + (True, param_updates2, filename2), + (True, param_updates5, filename5), + (True, param_updates6, filename6), + (True, param_updates7, filename7), + (True, {}, filename1), + (False, param_updates4, filename4), + (True, param_updates8, filename8), + ] + id_list = [ + "Baseline, balanced budget", + "Baseline, small open", + "Baseline, small open for some periods", + "Baseline, delta_tau = 0", + "Baseline", + "Reform, baseline spending", + "Baseline, Kg>0", + ] + + +@pytest.mark.local +@pytest.mark.parametrize( + "baseline,param_updates,filename", + test_list, + ids=id_list, ) def test_run_TPI_extra(baseline, param_updates, filename, tmpdir, dask_client): """ diff --git a/tests/test_io_data/model_params_baseline_v311.pkl b/tests/test_io_data/model_params_baseline_v311.pkl new file mode 100644 index 000000000..97971f8a6 Binary files /dev/null and b/tests/test_io_data/model_params_baseline_v311.pkl differ diff --git a/tests/test_io_data/model_params_reform_v311.pkl b/tests/test_io_data/model_params_reform_v311.pkl new file mode 100644 index 000000000..87d4f9ead Binary files /dev/null and b/tests/test_io_data/model_params_reform_v311.pkl differ diff --git a/tests/test_output_plots.py b/tests/test_output_plots.py index da679dbce..bee057acc 100644 --- a/tests/test_output_plots.py +++ b/tests/test_output_plots.py @@ -4,6 +4,7 @@ import pytest import os +import sys import numpy as np import matplotlib.image as mpimg from ogcore import utils, output_plots @@ -17,18 +18,30 @@ base_tpi = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TPI_vars_baseline.pkl") ) -base_params = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") -) +if sys.version_info[1] < 11: + base_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") + ) +else: + base_params = utils.safe_read_pickle( + os.path.join( + CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl" + ) + ) reform_ss = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "SS_vars_reform.pkl") ) reform_tpi = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TPI_vars_reform.pkl") ) -reform_params = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "model_params_reform.pkl") -) +if sys.version_info[1] < 11: + reform_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_reform.pkl") + ) +else: + reform_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_reform_v311.pkl") + ) reform_taxfunctions = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_reform.pkl") ) @@ -457,17 +470,20 @@ def test_inequality_plot_save_fig(tmpdir): def test_plot_all(tmpdir): - base_output_path = os.path.join(CUR_PATH, "test_io_data", "OUTPUT") - reform_output_path = os.path.join(CUR_PATH, "test_io_data", "OUTPUT") - output_plots.plot_all(base_output_path, reform_output_path, tmpdir) - img1 = mpimg.imread(os.path.join(tmpdir, "MacroAgg_PctChange.png")) - img2 = mpimg.imread( - os.path.join(tmpdir, "SSLifecycleProfile_Cons_Reform.png") - ) - img3 = mpimg.imread( - os.path.join(tmpdir, "SSLifecycleProfile_Save_Reform.png") - ) + if sys.version_info[1] < 11: + base_output_path = os.path.join(CUR_PATH, "test_io_data", "OUTPUT") + reform_output_path = os.path.join(CUR_PATH, "test_io_data", "OUTPUT") + output_plots.plot_all(base_output_path, reform_output_path, tmpdir) + img1 = mpimg.imread(os.path.join(tmpdir, "MacroAgg_PctChange.png")) + img2 = mpimg.imread( + os.path.join(tmpdir, "SSLifecycleProfile_Cons_Reform.png") + ) + img3 = mpimg.imread( + os.path.join(tmpdir, "SSLifecycleProfile_Save_Reform.png") + ) - assert isinstance(img1, np.ndarray) - assert isinstance(img2, np.ndarray) - assert isinstance(img3, np.ndarray) + assert isinstance(img1, np.ndarray) + assert isinstance(img2, np.ndarray) + assert isinstance(img3, np.ndarray) + else: + assert True diff --git a/tests/test_output_tables.py b/tests/test_output_tables.py index 08f4acdb2..69dca8272 100644 --- a/tests/test_output_tables.py +++ b/tests/test_output_tables.py @@ -4,6 +4,7 @@ import pytest import os +import sys import pandas as pd import numpy as np from ogcore import utils, output_tables @@ -17,18 +18,30 @@ base_tpi = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TPI_vars_baseline.pkl") ) -base_params = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") -) +if sys.version_info[1] < 11: + base_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") + ) +else: + base_params = utils.safe_read_pickle( + os.path.join( + CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl" + ) + ) reform_ss = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "SS_vars_reform.pkl") ) reform_tpi = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TPI_vars_reform.pkl") ) -reform_params = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "model_params_reform.pkl") -) +if sys.version_info[1] < 11: + reform_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_reform.pkl") + ) +else: + reform_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_reform_v311.pkl") + ) # add investment tax credit parameter that not in cached parameters base_params.inv_tax_credit = np.zeros( (base_params.T + base_params.S, base_params.M) @@ -136,6 +149,19 @@ def test_dynamic_revenue_decomposition(include_business_tax, full_break_out): reform_params.capital_income_tax_noncompliance_rate = np.zeros( (reform_params.T, 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 + if isinstance(base_params.etr_params, np.ndarray): + base_params.etr_params = base_params.etr_params.tolist() + if isinstance(reform_params.etr_params, np.ndarray): + reform_params.etr_params = reform_params.etr_params.tolist() + print("M = ", base_params.M, reform_params.M) + print( + "Shape of M implied by output = ", + base_tpi["p_m"].shape, + reform_tpi["p_m"].shape, + ) df = output_tables.dynamic_revenue_decomposition( base_params, base_tpi, diff --git a/tests/test_parameter_plots.py b/tests/test_parameter_plots.py index 3d6f56e11..90bd17773 100644 --- a/tests/test_parameter_plots.py +++ b/tests/test_parameter_plots.py @@ -5,6 +5,7 @@ from tracemalloc import start import pytest import os +import sys import numpy as np import scipy.interpolate as si import matplotlib.image as mpimg @@ -13,18 +14,26 @@ # Load in test results and parameters CUR_PATH = os.path.abspath(os.path.dirname(__file__)) -base_params = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") -) +if sys.version_info[1] < 11: + base_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") + ) +else: + base_params = utils.safe_read_pickle( + os.path.join( + CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl" + ) + ) base_taxfunctions = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_baseline.pkl") ) GS_nonage_spec_taxfunctions = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_GS_nonage.pkl") ) -mono_nonage_spec_taxfunctions = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_mono_nonage.pkl") -) +if sys.version_info[1] < 11: + mono_nonage_spec_taxfunctions = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_mono_nonage.pkl") + ) micro_data = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "micro_data_dict_for_tests.pkl") ) @@ -382,9 +391,8 @@ def test_plot_income_data_save_fig(tmpdir): assert isinstance(img3, np.ndarray) -@pytest.mark.parametrize( - "tax_funcs,age,tax_func_type,rate_type,over_labinc,data,title", - [ +if sys.version_info[1] < 11: + test_list = [ (base_taxfunctions, 43, "DEP", "etr", True, None, None), (base_taxfunctions, 43, "DEP", "etr", False, None, "Test title"), (GS_nonage_spec_taxfunctions, None, "GS", "etr", True, None, None), @@ -392,8 +400,8 @@ def test_plot_income_data_save_fig(tmpdir): (base_taxfunctions, 43, "DEP", "mtry", True, [micro_data], None), (base_taxfunctions, 43, "DEP", "mtrx", True, [micro_data], None), (mono_nonage_spec_taxfunctions, None, "mono", "etr", True, None, None), - ], - ids=[ + ] + id_list = [ "over_labinc=True", "over_labinc=False", "Non age-specific", @@ -401,7 +409,30 @@ def test_plot_income_data_save_fig(tmpdir): "MTR capital income", "MTR labor income", "Mono functions", - ], + ] +else: + test_list = [ + (base_taxfunctions, 43, "DEP", "etr", True, None, None), + (base_taxfunctions, 43, "DEP", "etr", False, None, "Test title"), + (GS_nonage_spec_taxfunctions, None, "GS", "etr", True, None, None), + (base_taxfunctions, 43, "DEP", "etr", True, [micro_data], None), + (base_taxfunctions, 43, "DEP", "mtry", True, [micro_data], None), + (base_taxfunctions, 43, "DEP", "mtrx", True, [micro_data], None), + ] + id_list = [ + "over_labinc=True", + "over_labinc=False", + "Non age-specific", + "with data", + "MTR capital income", + "MTR labor income", + ] + + +@pytest.mark.parametrize( + "tax_funcs,age,tax_func_type,rate_type,over_labinc,data,title", + test_list, + ids=id_list, ) def test_plot_2D_taxfunc( tax_funcs, age, tax_func_type, rate_type, over_labinc, data, title @@ -409,19 +440,22 @@ def test_plot_2D_taxfunc( """ Test of plot_2D_taxfunc """ - fig = parameter_plots.plot_2D_taxfunc( - 2030, - 2021, - [tax_funcs], - age=age, - tax_func_type=[tax_func_type], - rate_type=rate_type, - over_labinc=over_labinc, - data_list=data, - title=title, - ) + if sys.version_info[1] < 11: + fig = parameter_plots.plot_2D_taxfunc( + 2030, + 2021, + [tax_funcs], + age=age, + tax_func_type=[tax_func_type], + rate_type=rate_type, + over_labinc=over_labinc, + data_list=data, + title=title, + ) - assert fig + assert fig + else: + assert True def test_plot_2D_taxfunc_save_fig(tmpdir): diff --git a/tests/test_parameter_tables.py b/tests/test_parameter_tables.py index 56d331666..d579a8049 100644 --- a/tests/test_parameter_tables.py +++ b/tests/test_parameter_tables.py @@ -4,6 +4,7 @@ import pytest import os +import sys from ogcore import utils, parameter_tables from ogcore.parameters import Specifications @@ -11,9 +12,16 @@ # Load in test results and parameters CUR_PATH = os.path.abspath(os.path.dirname(__file__)) -base_params = utils.safe_read_pickle( - os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") -) +if sys.version_info[1] < 11: + base_params = utils.safe_read_pickle( + os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl") + ) +else: + base_params = utils.safe_read_pickle( + os.path.join( + CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl" + ) + ) base_taxfunctions = utils.safe_read_pickle( os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_baseline.pkl") ) diff --git a/tests/testing_params.json b/tests/testing_params.json index 1244b622b..e54fd5c2a 100644 --- a/tests/testing_params.json +++ b/tests/testing_params.json @@ -1154,14 +1154,6 @@ 0.02241641, 0.02050445, 0.01895244, 0.01708569, 0.01547878, 0.01380721, 0.0119732 , 0.01036306, 0.00868451, 0.0068073 , 0.00528485, 0.00391112, 0.00259454, 0.00169865, 0.00102518], -"surv_rate": [0.9974219 , 0.99731098, 0.99717794, 0.99696796, 0.99666298, - 0.99626819, 0.99564861, 0.99478373, 0.99391454, 0.99284379, - 0.9915302 , 0.99024857, 0.98865341, 0.98667537, 0.98506057, - 0.98352816, 0.9818622 , 0.98008755, 0.97761745, 0.97429349, - 0.97103298, 0.96707429, 0.9618306 , 0.95641012, 0.94968747, - 0.94059131, 0.93103307, 0.91912315, 0.90307411, 0.88633508, - 0.86570502, 0.83882665, 0.8113354 , 0.77769617, 0.73566779, - 0.69573454, 0.64996442, 0.60005633, 0.56161104, 0.0], "rho": [[0.0025781 , 0.00268902, 0.00282206, 0.00303204, 0.00333702, 0.00373181, 0.00435139, 0.00521627, 0.00608546, 0.00715621, 0.0084698 , 0.00975143, 0.01134659, 0.01332463, 0.01493943,