From fec740e95fdbcb6a8f8714355c8b0d3fb2411a1f Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Mon, 16 Dec 2024 09:18:36 -0500 Subject: [PATCH 01/11] Update environment, setup, and package test --- conda.recipe/meta.yaml | 8 +++---- environment.yml | 5 ++-- setup.py | 10 ++++---- taxcalc/tests/test_4package.py | 42 ++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index d618a778f..790148875 100755 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -9,10 +9,10 @@ build: requirements: build: - "python>=3.10, <3.13" - - "numpy>=1.26,<1.27" + - "numpy>=1.26" - "pandas>=2.2" - "bokeh>=2.4" - - "paramtools>=0.18.3" + - "paramtools>=0.19.0" - numba - curl - openpyxl @@ -20,10 +20,10 @@ requirements: run: - "python>=3.10, <3.13" - - "numpy>=1.26,<1.27" + - "numpy>=1.26" - "pandas>=2.2" - "bokeh>=2.4" - - "paramtools>=0.18.3" + - "paramtools>=0.19.0" - numba - curl - openpyxl diff --git a/environment.yml b/environment.yml index 8032c1e8d..1d18ee34f 100644 --- a/environment.yml +++ b/environment.yml @@ -3,11 +3,12 @@ channels: - conda-forge dependencies: - "python>=3.10, <3.13" -- "numpy>=1.26,<1.27" +- "numpy>=1.26" - "pandas>=2.2" - "bokeh>=2.4" - numba - curl +- setuptools - pytest - pytest-xdist - pycodestyle @@ -18,4 +19,4 @@ dependencies: - pip - pip: - jupyter-book - - "paramtools>=0.18.3" + - "paramtools>=0.19.0" diff --git a/setup.py b/setup.py index d748c0007..002ba7542 100644 --- a/setup.py +++ b/setup.py @@ -18,13 +18,11 @@ "include_package_data": True, "name": "taxcalc", "install_requires": [ - "setuptools", - "numpy", - "pandas", - "bokeh", + "numpy>=1.26", + "pandas>=2.2", + "bokeh>=2.4", "numba", - "requests", - "paramtools>=0.18.3", + "paramtools>=0.19.0", ], "classifiers": [ "Development Status :: 4 - Beta", diff --git a/taxcalc/tests/test_4package.py b/taxcalc/tests/test_4package.py index 6e6e89de3..0c5d24e76 100644 --- a/taxcalc/tests/test_4package.py +++ b/taxcalc/tests/test_4package.py @@ -10,6 +10,34 @@ import subprocess import yaml import pytest +import ast + + +def extract_install_requires(setup_py_content): + """ + Extract the install_requires list from a setup.py file content. + + Args: + setup_py_content (str): The full content of the setup.py file + + Returns: + list: A list of package requirements + """ + # Use regex to find the install_requires list + match = re.search(r'"install_requires"\s*:\s*\[([^\]]+)\]', setup_py_content, re.DOTALL) + + if match: + # Extract the contents of the list and split into packages + packages_str = match.group(1) + # Use ast.literal_eval to safely parse the string representations + packages = [ + ast.literal_eval(f'{pkg.strip()}') + for pkg in packages_str.split(',') + if pkg.strip() + ] + return packages + + return [] @pytest.mark.local @@ -38,6 +66,7 @@ def test_for_consistency(tests_path): 'coverage', "pip", "jupyter-book", + "setuptools" ]) # read conda.recipe/meta.yaml requirements meta_file = os.path.join(tests_path, '..', '..', @@ -65,3 +94,16 @@ def test_for_consistency(tests_path): # confirm that extras in env (relative to run) equal the dev_pkgs set extras = env - run assert extras == dev_pkgs + # Read the setup.py file and extract the install_requires list + setup_file = os.path.join(tests_path, '..', '..', + 'setup.py') + with open(setup_file, 'r') as f: + setup_py_content = f.read() + setup = set(extract_install_requires(setup_py_content)) + # confirm that setup.py + print("Setup packages = ", setup) + print("Meta packages = ", bld) + # if package in both, confirm that the version is the same + for pkg in setup.intersection(bld): + assert pkg in setup + assert pkg in bld From db9625c8a6604a0571114a6313e83fbac3fa4bcb Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Mon, 16 Dec 2024 10:03:49 -0500 Subject: [PATCH 02/11] Make parameter classes instances have a flexible last budget year --- taxcalc/calculator.py | 10 +++---- taxcalc/consumption.py | 10 +++---- taxcalc/growdiff.py | 15 ++++------- taxcalc/parameters.py | 13 ++++----- taxcalc/policy.py | 28 +++++++++++++++----- taxcalc/tests/test_cpscsv.py | 51 +++++++++++++++++++++++++++++++++++- 6 files changed, 92 insertions(+), 35 deletions(-) diff --git a/taxcalc/calculator.py b/taxcalc/calculator.py index 3c1cac849..d0b92a598 100644 --- a/taxcalc/calculator.py +++ b/taxcalc/calculator.py @@ -1110,7 +1110,7 @@ def read_json_param_objects(reform, assump): return param_dict @staticmethod - def reform_documentation(params, growfactors, policy_dicts=None): + def reform_documentation(params, policy_dicts=None): """ Generate reform documentation versus current-law policy. @@ -1120,9 +1120,6 @@ def reform_documentation(params, growfactors, policy_dicts=None): dictionary is structured like dict returned from the static Calculator.read_json_param_objects() method - growfactors: GrowFactors - GrowFactors object used to construct Calculator Policy object - policy_dicts : list of dict or None each dictionary in list is a params['policy'] dictionary representing second and subsequent elements of a compound @@ -1258,14 +1255,13 @@ def lines(text, num_indent_spaces, max_line_length=77): # create Policy object with current-law-policy values gdiff_base = GrowDiff() gdiff_base.update_growdiff(params['growdiff_baseline']) - assert isinstance(growfactors, GrowFactors) - gfactors_clp = copy.deepcopy(growfactors) + gfactors_clp = GrowFactors() gdiff_base.apply_to(gfactors_clp) clp = Policy(gfactors=gfactors_clp) # create Policy object with post-reform values gdiff_resp = GrowDiff() gdiff_resp.update_growdiff(params['growdiff_response']) - gfactors_ref = copy.deepcopy(growfactors) + gfactors_ref = GrowFactors() gdiff_base.apply_to(gfactors_ref) gdiff_resp.apply_to(gfactors_ref) ref = Policy(gfactors=gfactors_ref) diff --git a/taxcalc/consumption.py b/taxcalc/consumption.py index 0be9ea904..dbfd2d607 100644 --- a/taxcalc/consumption.py +++ b/taxcalc/consumption.py @@ -20,7 +20,8 @@ class Consumption(Parameters): Parameters ---------- - none + last_budget_year: integer + user-defined last parameter extrapolation year Returns ------- @@ -28,14 +29,13 @@ class instance: Consumption """ JSON_START_YEAR = Policy.JSON_START_YEAR - DEFAULT_NUM_YEARS = Policy.DEFAULT_NUM_YEARS DEFAULTS_FILE_NAME = 'consumption.json' DEFAULTS_FILE_PATH = os.path.abspath(os.path.dirname(__file__)) - def __init__(self): + def __init__(self, last_budget_year=Policy.LAST_BUDGET_YEAR): super().__init__() - self.initialize(Consumption.JSON_START_YEAR, - Consumption.DEFAULT_NUM_YEARS) + nyrs = Policy.number_of_years(last_budget_year) + self.initialize(Consumption.JSON_START_YEAR, nyrs) @staticmethod def read_json_update(obj): diff --git a/taxcalc/growdiff.py b/taxcalc/growdiff.py index e4cc4ac9e..770443df6 100644 --- a/taxcalc/growdiff.py +++ b/taxcalc/growdiff.py @@ -20,7 +20,8 @@ class GrowDiff(Parameters): Parameters ---------- - none + last_budget_year: integer + user-defined last parameter extrapolation year Returns ------- @@ -28,14 +29,13 @@ class instance: GrowDiff """ JSON_START_YEAR = Policy.JSON_START_YEAR - DEFAULT_NUM_YEARS = Policy.DEFAULT_NUM_YEARS DEFAULTS_FILE_NAME = 'growdiff.json' DEFAULTS_FILE_PATH = os.path.abspath(os.path.dirname(__file__)) - def __init__(self): + def __init__(self, last_budget_year=Policy.LAST_BUDGET_YEAR): super().__init__() - self.initialize(GrowDiff.JSON_START_YEAR, - GrowDiff.DEFAULT_NUM_YEARS) + nyrs = Policy.number_of_years(last_budget_year) + self.initialize(GrowDiff.JSON_START_YEAR, nyrs) @staticmethod def read_json_update(obj, topkey): @@ -81,8 +81,3 @@ def apply_to(self, growfactors): cyr = i + self.start_year diff_array = getattr(self, _gfvn) growfactors.update(gfvn, cyr, diff_array[i]) - - def set_rates(self): - """ - Unimplemented base class method that is not used here. - """ diff --git a/taxcalc/parameters.py b/taxcalc/parameters.py index 2ec9ba004..7b9b83e78 100644 --- a/taxcalc/parameters.py +++ b/taxcalc/parameters.py @@ -98,19 +98,16 @@ def __init__(self, start_year=None, num_years=None, last_known_year=None, self.DEFAULTS_FILE_PATH, self.DEFAULTS_FILE_NAME ) - + last_budget_year = start_year + num_years - 1 if last_known_year is None: self._last_known_year = start_year else: assert last_known_year >= start_year - assert last_known_year <= self.LAST_BUDGET_YEAR + assert last_known_year <= last_budget_year self._last_known_year = last_known_year - self._removed_params = removed or self.REMOVED_PARAMS self._redefined_params = redefined or self.REDEFINED_PARAMS - self._wage_indexed = wage_indexed or self.WAGE_INDEXED_PARAMS - if ( (start_year or self.JSON_START_YEAR) and "initial_state" not in kwargs @@ -118,6 +115,10 @@ def __init__(self, start_year=None, num_years=None, last_known_year=None, kwargs["initial_state"] = { "year": start_year or self.JSON_START_YEAR } + # update defaults to correspond to user-defined parameter years + self.defaults = super().get_defaults() + label = self.defaults["schema"]["labels"]["year"] + label["validators"]["range"]["max"] = last_budget_year super().__init__(**kwargs) def adjust( @@ -774,7 +775,7 @@ def __getattr__(self, attr): attr[1:], year=list(range(self.start_year, self.end_year + 1)) ) else: - raise AttributeError(f"{attr} not definied.") + raise AttributeError(f"{attr} is not defined.") TaxcalcReform = Union[str, Mapping[int, Any]] diff --git a/taxcalc/policy.py b/taxcalc/policy.py index 8f101dd6d..e804f30fc 100644 --- a/taxcalc/policy.py +++ b/taxcalc/policy.py @@ -24,6 +24,9 @@ class Policy(Parameters): gfactors: GrowFactors class instance containing price inflation rates and wage growth rates + last_budget_year: integer + user-defined last parameter extrapolation year + Raises ------ ValueError: @@ -39,9 +42,16 @@ class instance: Policy JSON_START_YEAR = 2013 # remains the same unless earlier data added LAST_KNOWN_YEAR = 2025 # last year for which indexed param vals are known # should increase LAST_KNOWN_YEAR by one every calendar year - LAST_BUDGET_YEAR = 2034 # last extrapolation year + LAST_BUDGET_YEAR = 2034 # default value of last extrapolation year # should increase LAST_BUDGET_YEAR by one every calendar year - DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1 + + @staticmethod + def number_of_years(last_budget_year=LAST_BUDGET_YEAR): + """ + Static method returns number of policy parameters years given + user-defined last_budget_year. + """ + return last_budget_year - Policy.JSON_START_YEAR + 1 # NOTE: the following three data structures use internal parameter names: # (1) specify which Policy parameters have been removed or renamed @@ -80,7 +90,10 @@ class instance: Policy # (3) specify which Policy parameters are wage (rather than price) indexed WAGE_INDEXED_PARAMS = ['SS_Earnings_c', 'SS_Earnings_thd'] - def __init__(self, gfactors=None, **kwargs): + def __init__(self, + gfactors=None, + last_budget_year=LAST_BUDGET_YEAR, + **kwargs): # put JSON contents of DEFAULTS_FILE_NAME into self._vals dictionary super().__init__() # handle gfactors argument @@ -92,7 +105,7 @@ def __init__(self, gfactors=None, **kwargs): raise ValueError('gfactors is not None or a GrowFactors instance') # read default parameters and initialize syr = Policy.JSON_START_YEAR - nyrs = Policy.DEFAULT_NUM_YEARS + nyrs = Policy.number_of_years(last_budget_year) self._inflation_rates = None self._wage_growth_rates = None self.initialize(syr, nyrs, Policy.LAST_KNOWN_YEAR, @@ -101,7 +114,10 @@ def __init__(self, gfactors=None, **kwargs): Policy.WAGE_INDEXED_PARAMS, **kwargs) @staticmethod - def tmd_constructor(growfactors: Path | GrowFactors): # pragma: no cover + def tmd_constructor( + growfactors: Path | GrowFactors, + last_budget_year=LAST_BUDGET_YEAR, + ): # pragma: no cover """ Static method returns a Policy object instantiated with TMD input data. This convenience method works in a analogous way @@ -112,7 +128,7 @@ def tmd_constructor(growfactors: Path | GrowFactors): # pragma: no cover growfactors = GrowFactors(growfactors_filename=str(growfactors)) else: assert isinstance(growfactors, GrowFactors) - return Policy(gfactors=growfactors) + return Policy(gfactors=growfactors, last_budget_year=last_budget_year) @staticmethod def read_json_reform(obj): diff --git a/taxcalc/tests/test_cpscsv.py b/taxcalc/tests/test_cpscsv.py index 7517b6adb..82100e630 100644 --- a/taxcalc/tests/test_cpscsv.py +++ b/taxcalc/tests/test_cpscsv.py @@ -18,7 +18,7 @@ import numpy as np import pandas as pd # pylint: disable=import-error -from taxcalc import Policy, Records, Calculator +from taxcalc import GrowFactors, GrowDiff, Policy, Records, Calculator START_YEAR = 2017 @@ -161,3 +161,52 @@ def isfloat(value): else: return True return False + + +def test_flexible_last_budget_year(cps_fullsample): + """ + Test flexible LAST_BUDGET_YEAR logic using cps.csv file. + """ + tax_calc_year = Policy.LAST_BUDGET_YEAR - 1 + growdiff_year = tax_calc_year - 1 + growdiff_dict = {'AWAGE': {growdiff_year: 0.01, tax_calc_year: 0.0}} + + def default_calculator(growdiff_dictionary): + """ + Return CPS-based Calculator object using default LAST_BUDGET_YEAR. + """ + g_factors = GrowFactors() + gdiff = GrowDiff() + gdiff.update_growdiff(growdiff_dictionary) + gdiff.apply_to(g_factors) + pol = Policy(gfactors=g_factors) + rec = Records.cps_constructor(data=cps_fullsample, gfactors=g_factors) + calc = Calculator(policy=pol, records=rec) + return calc + + def flexible_calculator(growdiff_dictionary, last_b_year, num_years): + """ + Return CPS-based Calculator object using custom LAST_BUDGET_YEAR. + """ + g_factors = GrowFactors() + gdiff = GrowDiff(last_budget_year=last_b_year) + gdiff.update_growdiff(growdiff_dictionary) + gdiff.apply_to(g_factors) + pol = Policy(gfactors=g_factors, last_budget_year=last_b_year) + rec = Records.cps_constructor(data=cps_fullsample, gfactors=g_factors) + calc = Calculator(policy=pol, records=rec) + return calc + + # begin main test logic + cdef = default_calculator(growdiff_dict) + cdef.advance_to_year(tax_calc_year) + cdef.calc_all() + iitax_def = round(cdef.weighted_total('iitax')) + + num_years = tax_calc_year - Policy.JSON_START_YEAR + 1 + cflx = flexible_calculator(growdiff_dict, tax_calc_year, num_years) + cflx.advance_to_year(tax_calc_year) + cflx.calc_all() + iitax_flx = round(cflx.weighted_total('iitax')) + + assert np.allclose([iitax_flx], [iitax_def]) From f9525e7ac82b5aac4565cc843486ee98637b7ae0 Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Mon, 16 Dec 2024 11:50:27 -0500 Subject: [PATCH 03/11] Update test_calculator.py to work with flexible last budget year --- taxcalc/calculator.py | 5 ++++- taxcalc/tests/test_calculator.py | 7 +++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/taxcalc/calculator.py b/taxcalc/calculator.py index d0b92a598..684e43cdd 100644 --- a/taxcalc/calculator.py +++ b/taxcalc/calculator.py @@ -1110,7 +1110,7 @@ def read_json_param_objects(reform, assump): return param_dict @staticmethod - def reform_documentation(params, policy_dicts=None): + def reform_documentation(params, growfactors, policy_dicts=None): """ Generate reform documentation versus current-law policy. @@ -1120,6 +1120,9 @@ def reform_documentation(params, policy_dicts=None): dictionary is structured like dict returned from the static Calculator.read_json_param_objects() method + growfactors: GrowFactors + GrowFactors object used to construct Calculator Policy object + policy_dicts : list of dict or None each dictionary in list is a params['policy'] dictionary representing second and subsequent elements of a compound diff --git a/taxcalc/tests/test_calculator.py b/taxcalc/tests/test_calculator.py index ac2e18004..1516465f0 100644 --- a/taxcalc/tests/test_calculator.py +++ b/taxcalc/tests/test_calculator.py @@ -74,9 +74,9 @@ def test_make_calculator_with_policy_reform(cps_subsample): assert calc.current_year == year assert calc.policy_param('II_em') == 4000 assert np.allclose(calc.policy_param('_II_em'), - np.array([4000] * Policy.DEFAULT_NUM_YEARS)) + np.array([4000] * Policy.number_of_years())) exp_STD_Aged = [[1600, 1300, 1300, - 1600, 1600]] * Policy.DEFAULT_NUM_YEARS + 1600, 1600]] * Policy.number_of_years() assert np.allclose(calc.policy_param('_STD_Aged'), np.array(exp_STD_Aged)) assert np.allclose(calc.policy_param('STD_Aged'), @@ -100,10 +100,9 @@ def test_make_calculator_with_multiyear_reform(cps_subsample): # create a Calculator object using this policy-reform calc = Calculator(policy=pol, records=rec) # check that Policy object embedded in Calculator object is correct - assert pol.num_years == Policy.DEFAULT_NUM_YEARS assert calc.current_year == year assert calc.policy_param('II_em') == 3950 - exp_II_em = [3900, 3950, 5000] + [6000] * (Policy.DEFAULT_NUM_YEARS - 3) + exp_II_em = [3900, 3950, 5000] + [6000] * (Policy.number_of_years() - 3) assert np.allclose(calc.policy_param('_II_em'), np.array(exp_II_em)) calc.increment_year() From 01706d610c1fadfe861d10037b652da9d1f92c4b Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Mon, 16 Dec 2024 12:23:26 -0500 Subject: [PATCH 04/11] Update more tests to work with flexible last budget year logic --- taxcalc/tests/test_consumption.py | 11 +++++------ taxcalc/tests/test_growdiff.py | 12 +++++------- taxcalc/tests/test_policy.py | 4 ++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/taxcalc/tests/test_consumption.py b/taxcalc/tests/test_consumption.py index eaaa24831..21c01502b 100644 --- a/taxcalc/tests/test_consumption.py +++ b/taxcalc/tests/test_consumption.py @@ -8,9 +8,8 @@ from taxcalc import Policy, Records, Calculator, Consumption -def test_year_consistency(): +def test_start_year_consistency(): assert Consumption.JSON_START_YEAR == Policy.JSON_START_YEAR - assert Consumption.DEFAULT_NUM_YEARS == Policy.DEFAULT_NUM_YEARS def test_validity_of_consumption_vars_set(): @@ -31,23 +30,23 @@ def test_update_consumption(): 2015: 0.80} } consump.update_consumption(revision) - expected_mpc_e20400 = np.full((Consumption.DEFAULT_NUM_YEARS,), 0.06) + expected_mpc_e20400 = np.full((consump.num_years,), 0.06) expected_mpc_e20400[0] = 0.0 expected_mpc_e20400[1] = 0.05 assert np.allclose(consump._MPC_e20400, expected_mpc_e20400, rtol=0.0) assert np.allclose(consump._MPC_e17500, - np.zeros((Consumption.DEFAULT_NUM_YEARS,)), + np.zeros((consump.num_years,)), rtol=0.0) - expected_ben_mcare_value = np.full((Consumption.DEFAULT_NUM_YEARS,), 0.80) + expected_ben_mcare_value = np.full((consump.num_years,), 0.80) expected_ben_mcare_value[0] = 1.0 expected_ben_mcare_value[1] = 0.75 assert np.allclose(consump._BEN_mcare_value, expected_ben_mcare_value, rtol=0.0) assert np.allclose(consump._BEN_snap_value, - np.ones((Consumption.DEFAULT_NUM_YEARS,)), + np.ones((consump.num_years,)), rtol=0.0) consump.set_year(2015) assert consump.current_year == 2015 diff --git a/taxcalc/tests/test_growdiff.py b/taxcalc/tests/test_growdiff.py index 74bd49db5..bc48061e9 100644 --- a/taxcalc/tests/test_growdiff.py +++ b/taxcalc/tests/test_growdiff.py @@ -8,9 +8,8 @@ from taxcalc import GrowDiff, GrowFactors, Policy -def test_year_consistency(): +def test_start_year_consistency(): assert GrowDiff.JSON_START_YEAR == Policy.JSON_START_YEAR - assert GrowDiff.DEFAULT_NUM_YEARS == Policy.DEFAULT_NUM_YEARS def test_update_and_apply_growdiff(): @@ -22,14 +21,13 @@ def test_update_and_apply_growdiff(): } gdiff.update_growdiff(diffs) expected_wage_diffs = [0.00, 0.01, 0.01, 0.02, 0.02] - extra_years = GrowDiff.DEFAULT_NUM_YEARS - len(expected_wage_diffs) + extra_years = gdiff.num_years - len(expected_wage_diffs) expected_wage_diffs.extend([0.02] * extra_years) assert np.allclose(gdiff._AWAGE, expected_wage_diffs, atol=0.0, rtol=0.0) # apply growdiff to GrowFactors instance gf = GrowFactors() - syr = GrowDiff.JSON_START_YEAR - nyrs = GrowDiff.DEFAULT_NUM_YEARS - lyr = syr + nyrs - 1 + syr = gdiff.start_year + lyr = gdiff.end_year pir_pre = gf.price_inflation_rates(syr, lyr) wgr_pre = gf.wage_growth_rates(syr, lyr) gfactors = GrowFactors() @@ -37,7 +35,7 @@ def test_update_and_apply_growdiff(): pir_pst = gfactors.price_inflation_rates(syr, lyr) wgr_pst = gfactors.wage_growth_rates(syr, lyr) expected_wgr_pst = [wgr_pre[i] + expected_wage_diffs[i] - for i in range(0, nyrs)] + for i in range(0, gdiff.num_years)] assert np.allclose(pir_pre, pir_pst, atol=0.0, rtol=0.0) assert np.allclose(wgr_pst, expected_wgr_pst, atol=1.0e-9, rtol=0.0) diff --git a/taxcalc/tests/test_policy.py b/taxcalc/tests/test_policy.py index 41ba8db3e..68d396b69 100644 --- a/taxcalc/tests/test_policy.py +++ b/taxcalc/tests/test_policy.py @@ -247,9 +247,9 @@ def test_multi_year_reform(): Test multi-year reform involving 1D and 2D parameters. """ # specify dimensions of policy Policy object - syr = Policy.JSON_START_YEAR - nyrs = Policy.DEFAULT_NUM_YEARS pol = Policy() + syr = pol.start_year + nyrs = pol.num_years iratelist = pol.inflation_rates() ifactor = {} for i in range(0, nyrs): From d1fc8811d78c1e621643b6d884df0b68b3e2b70f Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Mon, 16 Dec 2024 14:38:47 -0500 Subject: [PATCH 05/11] Mark this branch as 4.3.5a version --- taxcalc.egg-info/PKG-INFO | 12 +++++------- taxcalc.egg-info/requires.txt | 10 ++++------ taxcalc/__init__.py | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/taxcalc.egg-info/PKG-INFO b/taxcalc.egg-info/PKG-INFO index 60eb54244..38364fb1a 100644 --- a/taxcalc.egg-info/PKG-INFO +++ b/taxcalc.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: taxcalc -Version: 4.3.4 +Version: 4.3.5 Summary: taxcalc Home-page: https://github.com/PSLmodels/Tax-Calculator Download-URL: https://github.com/PSLmodels/Tax-Calculator @@ -18,13 +18,11 @@ Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Software Development :: Libraries :: Python Modules Description-Content-Type: text/markdown License-File: LICENSE -Requires-Dist: setuptools -Requires-Dist: numpy -Requires-Dist: pandas -Requires-Dist: bokeh +Requires-Dist: numpy>=1.26 +Requires-Dist: pandas>=2.2 +Requires-Dist: bokeh>=2.4 Requires-Dist: numba -Requires-Dist: requests -Requires-Dist: paramtools>=0.18.3 +Requires-Dist: paramtools>=0.19.0 | | | | --- | --- | diff --git a/taxcalc.egg-info/requires.txt b/taxcalc.egg-info/requires.txt index 2e5f43376..2cda2894d 100644 --- a/taxcalc.egg-info/requires.txt +++ b/taxcalc.egg-info/requires.txt @@ -1,7 +1,5 @@ -setuptools -numpy -pandas -bokeh +numpy>=1.26 +pandas>=2.2 +bokeh>=2.4 numba -requests -paramtools>=0.18.3 +paramtools>=0.19.0 diff --git a/taxcalc/__init__.py b/taxcalc/__init__.py index d5be98ba0..9d09a31c6 100644 --- a/taxcalc/__init__.py +++ b/taxcalc/__init__.py @@ -14,6 +14,6 @@ from taxcalc.utils import * from taxcalc.cli import * -__version__ = '4.3.5' +__version__ = '4.3.5a' __min_python3_version__ = 10 __max_python3_version__ = 12 From 567eb3f435230e7677019df4a176c0db92a13c1b Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Mon, 16 Dec 2024 18:03:40 -0500 Subject: [PATCH 06/11] Make calculator.py code be same as on master branch --- taxcalc/calculator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/taxcalc/calculator.py b/taxcalc/calculator.py index 684e43cdd..3c1cac849 100644 --- a/taxcalc/calculator.py +++ b/taxcalc/calculator.py @@ -1258,13 +1258,14 @@ def lines(text, num_indent_spaces, max_line_length=77): # create Policy object with current-law-policy values gdiff_base = GrowDiff() gdiff_base.update_growdiff(params['growdiff_baseline']) - gfactors_clp = GrowFactors() + assert isinstance(growfactors, GrowFactors) + gfactors_clp = copy.deepcopy(growfactors) gdiff_base.apply_to(gfactors_clp) clp = Policy(gfactors=gfactors_clp) # create Policy object with post-reform values gdiff_resp = GrowDiff() gdiff_resp.update_growdiff(params['growdiff_response']) - gfactors_ref = GrowFactors() + gfactors_ref = copy.deepcopy(growfactors) gdiff_base.apply_to(gfactors_ref) gdiff_resp.apply_to(gfactors_ref) ref = Policy(gfactors=gfactors_ref) From 52bd4e68649d5a3601f3ce4114de19d3faaf4ea6 Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Mon, 16 Dec 2024 18:23:28 -0500 Subject: [PATCH 07/11] Simplify test_calculator.py code --- taxcalc/tests/test_calculator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/taxcalc/tests/test_calculator.py b/taxcalc/tests/test_calculator.py index 1516465f0..110916b31 100644 --- a/taxcalc/tests/test_calculator.py +++ b/taxcalc/tests/test_calculator.py @@ -74,9 +74,9 @@ def test_make_calculator_with_policy_reform(cps_subsample): assert calc.current_year == year assert calc.policy_param('II_em') == 4000 assert np.allclose(calc.policy_param('_II_em'), - np.array([4000] * Policy.number_of_years())) + np.array([4000] * pol.num_years)) exp_STD_Aged = [[1600, 1300, 1300, - 1600, 1600]] * Policy.number_of_years() + 1600, 1600]] * pol.num_years assert np.allclose(calc.policy_param('_STD_Aged'), np.array(exp_STD_Aged)) assert np.allclose(calc.policy_param('STD_Aged'), @@ -102,7 +102,7 @@ def test_make_calculator_with_multiyear_reform(cps_subsample): # check that Policy object embedded in Calculator object is correct assert calc.current_year == year assert calc.policy_param('II_em') == 3950 - exp_II_em = [3900, 3950, 5000] + [6000] * (Policy.number_of_years() - 3) + exp_II_em = [3900, 3950, 5000] + [6000] * (pol.num_years - 3) assert np.allclose(calc.policy_param('_II_em'), np.array(exp_II_em)) calc.increment_year() From e4b629e0f6e9dcea8b6e69c01c7903da9869e860 Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Tue, 17 Dec 2024 14:30:18 -0500 Subject: [PATCH 08/11] Streamline test code; no substantive changes --- taxcalc/tests/test_cpscsv.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/taxcalc/tests/test_cpscsv.py b/taxcalc/tests/test_cpscsv.py index 82100e630..691be717b 100644 --- a/taxcalc/tests/test_cpscsv.py +++ b/taxcalc/tests/test_cpscsv.py @@ -184,7 +184,7 @@ def default_calculator(growdiff_dictionary): calc = Calculator(policy=pol, records=rec) return calc - def flexible_calculator(growdiff_dictionary, last_b_year, num_years): + def flexible_calculator(growdiff_dictionary, last_b_year): """ Return CPS-based Calculator object using custom LAST_BUDGET_YEAR. """ @@ -203,8 +203,7 @@ def flexible_calculator(growdiff_dictionary, last_b_year, num_years): cdef.calc_all() iitax_def = round(cdef.weighted_total('iitax')) - num_years = tax_calc_year - Policy.JSON_START_YEAR + 1 - cflx = flexible_calculator(growdiff_dict, tax_calc_year, num_years) + cflx = flexible_calculator(growdiff_dict, tax_calc_year) cflx.advance_to_year(tax_calc_year) cflx.calc_all() iitax_flx = round(cflx.weighted_total('iitax')) From 13921611903f63d1a4e3f38328c752e9d7d2293b Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Tue, 17 Dec 2024 14:36:20 -0500 Subject: [PATCH 09/11] Revise Parameter-derived class constructors in taxcalcio.py --- taxcalc/taxcalcio.py | 60 ++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/taxcalc/taxcalcio.py b/taxcalc/taxcalcio.py index 963731c13..2809a3059 100644 --- a/taxcalc/taxcalcio.py +++ b/taxcalc/taxcalcio.py @@ -249,6 +249,31 @@ def init(self, input_data, tax_year, baseline, reform, assump, # pylint: disable=too-many-arguments,too-many-locals # pylint: disable=too-many-statements,too-many-branches self.errmsg = '' + # instantiate base and reform GrowFactors objects + if self.tmd_input_data: + gfactors_base = GrowFactors(self.tmd_gfactor) # pragma: no cover + gfactors_ref = GrowFactors(self.tmd_gfactor) # pragma: no cover + else: + gfactors_base = GrowFactors() + gfactors_ref = GrowFactors() + # check tax_year validity + max_tax_year = gfactors_base.last_year + if tax_year > max_tax_year: + msg = f'TAXYEAR={tax_year} is greater than {max_tax_year}' + self.errmsg += f'ERROR: {msg}\n' + min_tax_year = Policy.JSON_START_YEAR + if self.puf_input_data: + min_tax_year = max(Policy.JSON_START_YEAR, Records.PUFCSV_YEAR) + if self.cps_input_data: + min_tax_year = max(Policy.JSON_START_YEAR, Records.CPSCSV_YEAR) + if self.tmd_input_data: + min_tax_year = max(Policy.JSON_START_YEAR, Records.TMDCSV_YEAR) + if tax_year < min_tax_year: + msg = f'TAXYEAR={tax_year} is less than {min_tax_year}' + self.errmsg += f'ERROR: {msg}\n' + # tax_year out of range means cannot proceed with calculations + if self.errmsg: + return # get policy parameter dictionary from --baseline file basedict = Calculator.read_json_param_objects(baseline, None) # get assumption sub-dictionaries @@ -264,35 +289,29 @@ def init(self, input_data, tax_year, baseline, reform, assump, # remember parameters for reform documentation self.param_dict = paramdict self.policy_dicts = policydicts + # set last_b_year + last_b_year = max(tax_year, Policy.LAST_BUDGET_YEAR) # create gdiff_baseline object - gdiff_baseline = GrowDiff() + gdiff_baseline = GrowDiff(last_budget_year=last_b_year) try: gdiff_baseline.update_growdiff(paramdict['growdiff_baseline']) except paramtools.ValidationError as valerr_msg: self.errmsg += valerr_msg.__str__() - # create GrowFactors base object that incorporates gdiff_baseline - if self.tmd_input_data: - gfactors_base = GrowFactors(self.tmd_gfactor) # pragma: no cover - else: - gfactors_base = GrowFactors() + # apply gdiff_baseline to gfactor_base gdiff_baseline.apply_to(gfactors_base) # specify gdiff_response object - gdiff_response = GrowDiff() + gdiff_response = GrowDiff(last_budget_year=last_b_year) try: gdiff_response.update_growdiff(paramdict['growdiff_response']) except paramtools.ValidationError as valerr_msg: self.errmsg += valerr_msg.__str__() - # create GrowFactors ref object that has all gdiff objects applied - if self.tmd_input_data: - gfactors_ref = GrowFactors(self.tmd_gfactor) # pragma: no cover - else: - gfactors_ref = GrowFactors() + # apply gdiff_baseline and gdiff_response to gfactor_ref gdiff_baseline.apply_to(gfactors_ref) gdiff_response.apply_to(gfactors_ref) self.gf_reform = copy.deepcopy(gfactors_ref) # create Policy objects: # ... the baseline Policy object - base = Policy(gfactors=gfactors_base) + base = Policy(gfactors=gfactors_base, last_budget_year=last_b_year) try: base.implement_reform(basedict['policy'], print_warnings=True, @@ -303,7 +322,7 @@ def init(self, input_data, tax_year, baseline, reform, assump, self.errmsg += valerr_msg.__str__() # ... the reform Policy object if self.specified_reform: - pol = Policy(gfactors=gfactors_ref) + pol = Policy(gfactors=gfactors_ref, last_budget_year=last_b_year) for poldict in policydicts: try: pol.implement_reform(poldict, @@ -316,22 +335,13 @@ def init(self, input_data, tax_year, baseline, reform, assump, except paramtools.ValidationError as valerr_msg: self.errmsg += valerr_msg.__str__() else: - pol = Policy(gfactors=gfactors_base) + pol = Policy(gfactors=gfactors_base, last_budget_year=last_b_year) # create Consumption object - con = Consumption() + con = Consumption(last_budget_year=last_b_year) try: con.update_consumption(paramdict['consumption']) except paramtools.ValidationError as valerr_msg: self.errmsg += valerr_msg.__str__() - # check for valid tax_year value - if tax_year < pol.start_year: - msg = 'tax_year {} less than policy.start_year {}' - msg = msg.format(tax_year, pol.start_year) - self.errmsg += 'ERROR: {}\n'.format(msg) - if tax_year > pol.end_year: - msg = 'tax_year {} greater than policy.end_year {}' - msg = msg.format(tax_year, pol.end_year) - self.errmsg += 'ERROR: {}\n'.format(msg) # any errors imply cannot proceed with calculations if self.errmsg: return From 433e3523d1d2576e66f6fdf7fe859be195b98aa2 Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Tue, 17 Dec 2024 15:36:34 -0500 Subject: [PATCH 10/11] Remove obsolete code from taxcalcio.py --- taxcalc/taxcalcio.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/taxcalc/taxcalcio.py b/taxcalc/taxcalcio.py index 2809a3059..834682c80 100644 --- a/taxcalc/taxcalcio.py +++ b/taxcalc/taxcalcio.py @@ -263,11 +263,14 @@ def init(self, input_data, tax_year, baseline, reform, assump, self.errmsg += f'ERROR: {msg}\n' min_tax_year = Policy.JSON_START_YEAR if self.puf_input_data: - min_tax_year = max(Policy.JSON_START_YEAR, Records.PUFCSV_YEAR) + min_tax_year = max( # pragma: no cover + Policy.JSON_START_YEAR, Records.PUFCSV_YEAR) if self.cps_input_data: - min_tax_year = max(Policy.JSON_START_YEAR, Records.CPSCSV_YEAR) + min_tax_year = max( + Policy.JSON_START_YEAR, Records.CPSCSV_YEAR) if self.tmd_input_data: - min_tax_year = max(Policy.JSON_START_YEAR, Records.TMDCSV_YEAR) + min_tax_year = max( # pragma: no cover + Policy.JSON_START_YEAR, Records.TMDCSV_YEAR) if tax_year < min_tax_year: msg = f'TAXYEAR={tax_year} is less than {min_tax_year}' self.errmsg += f'ERROR: {msg}\n' @@ -398,10 +401,6 @@ def init(self, input_data, tax_year, baseline, reform, assump, adjust_ratios=None, exact_calculations=exact_calculations) recs_base = copy.deepcopy(recs) - if tax_year < recs.data_year: - msg = 'tax_year {} less than records.data_year {}' - msg = msg.format(tax_year, recs.data_year) - self.errmsg += 'ERROR: {}\n'.format(msg) # create Calculator objects self.calc = Calculator(policy=pol, records=recs, verbose=True, From 43464dd9c92e843e3bcffcab8f9a85e0845fb8e5 Mon Sep 17 00:00:00 2001 From: "martin.holmer@gmail.com" Date: Wed, 18 Dec 2024 08:46:24 -0500 Subject: [PATCH 11/11] Cosmetic change to taxcalcio.py code Co-authored-by: jdebacker --- taxcalc/taxcalcio.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/taxcalc/taxcalcio.py b/taxcalc/taxcalcio.py index 834682c80..8eb495225 100644 --- a/taxcalc/taxcalcio.py +++ b/taxcalc/taxcalcio.py @@ -261,20 +261,21 @@ def init(self, input_data, tax_year, baseline, reform, assump, if tax_year > max_tax_year: msg = f'TAXYEAR={tax_year} is greater than {max_tax_year}' self.errmsg += f'ERROR: {msg}\n' - min_tax_year = Policy.JSON_START_YEAR if self.puf_input_data: min_tax_year = max( # pragma: no cover Policy.JSON_START_YEAR, Records.PUFCSV_YEAR) - if self.cps_input_data: + elif self.cps_input_data: min_tax_year = max( Policy.JSON_START_YEAR, Records.CPSCSV_YEAR) - if self.tmd_input_data: + elif self.tmd_input_data: min_tax_year = max( # pragma: no cover Policy.JSON_START_YEAR, Records.TMDCSV_YEAR) + else: + min_tax_year = Policy.JSON_START_YEAR if tax_year < min_tax_year: msg = f'TAXYEAR={tax_year} is less than {min_tax_year}' self.errmsg += f'ERROR: {msg}\n' - # tax_year out of range means cannot proceed with calculations + # tax_year out of valid range means cannot proceed with calculations if self.errmsg: return # get policy parameter dictionary from --baseline file