Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize policy parameter suffix logic to handle non-default baseline Growdiff #1520

Merged
merged 2 commits into from
Aug 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Release 0.9.3 on 2017-??-??
by Martin Holmer]
- Add option to use policy parameter suffixes in JSON reform files
[[#1505](https://github.com/open-source-economics/Tax-Calculator/pull/1505)
by Martin Holmer] and
[[#1520](https://github.com/open-source-economics/Tax-Calculator/pull/1520)
by Martin Holmer]
- Add rounding of wage-inflated or price-inflated parameter values to nearest cent
[[#1506](https://github.com/open-source-economics/Tax-Calculator/pull/1506)
Expand Down
37 changes: 20 additions & 17 deletions taxcalc/calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,20 +374,7 @@ def read_json_param_files(reform_filename, assump_filename,
in which case the file reading is skipped and the read_json_*_text
method is called.
"""
# process first reform parameter
if reform_filename is None:
rpol_dict = dict()
elif isinstance(reform_filename, str):
if os.path.isfile(reform_filename):
txt = open(reform_filename, 'r').read()
else:
txt = reform_filename
rpol_dict = (
Calculator._read_json_policy_reform_text(txt,
arrays_not_lists))
else:
raise ValueError('reform_filename is neither None nor str')
# process second assump parameter
# first process second assump parameter
if assump_filename is None:
cons_dict = dict()
behv_dict = dict()
Expand All @@ -406,7 +393,21 @@ def read_json_param_files(reform_filename, assump_filename,
arrays_not_lists))
else:
raise ValueError('assump_filename is neither None nor str')
# construct and return single composite dictionary
# next process first reform parameter
if reform_filename is None:
rpol_dict = dict()
elif isinstance(reform_filename, str):
if os.path.isfile(reform_filename):
txt = open(reform_filename, 'r').read()
else:
txt = reform_filename
rpol_dict = (
Calculator._read_json_policy_reform_text(txt,
arrays_not_lists,
gdiff_base_dict))
else:
raise ValueError('reform_filename is neither None nor str')
# finally construct and return single composite dictionary
param_dict = dict()
param_dict['policy'] = rpol_dict
param_dict['consumption'] = cons_dict
Expand Down Expand Up @@ -495,7 +496,8 @@ def _calc_one_year(self, zero_out_calc_vars=False):
IITAX(self.policy, self.records)

@staticmethod
def _read_json_policy_reform_text(text_string, arrays_not_lists):
def _read_json_policy_reform_text(text_string, arrays_not_lists,
growdiff_baseline_dict):
"""
Strip //-comments from text_string and return 1 dict based on the JSON.

Expand Down Expand Up @@ -547,7 +549,8 @@ def _read_json_policy_reform_text(text_string, arrays_not_lists):
msg = 'key "{}" should be in economic assumption file'
raise ValueError(msg.format(rkey))
# convert raw_dict['policy'] dictionary into prdict
tdict = Policy.translate_json_reform_suffixes(raw_dict['policy'])
tdict = Policy.translate_json_reform_suffixes(raw_dict['policy'],
growdiff_baseline_dict)
prdict = Calculator._convert_parameter_dict(tdict, arrays_not_lists)
return prdict

Expand Down
5 changes: 2 additions & 3 deletions taxcalc/growdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# pylint --disable=locally-disabled growdiff.py

import numpy as np
from taxcalc.policy import Policy
from taxcalc.parameters import ParametersBase


Expand Down Expand Up @@ -41,9 +40,9 @@ class Growdiff(ParametersBase):
class instance: Growdiff
"""

JSON_START_YEAR = Policy.JSON_START_YEAR
JSON_START_YEAR = 2013 # must be same as Policy.JSON_START_YEAR
DEFAULTS_FILENAME = 'growdiff.json'
DEFAULT_NUM_YEARS = Policy.DEFAULT_NUM_YEARS
DEFAULT_NUM_YEARS = 14 # must be same as Policy.DEFAULT_NUM_YEARS

def __init__(self, growdiff_dict=None,
start_year=JSON_START_YEAR,
Expand Down
31 changes: 21 additions & 10 deletions taxcalc/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import numpy as np
from taxcalc.parameters import ParametersBase
from taxcalc.growfactors import Growfactors
from taxcalc.growdiff import Growdiff


class Policy(ParametersBase):
Expand Down Expand Up @@ -54,15 +55,18 @@ class instance: Policy
DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1

def __init__(self,
gfactors=Growfactors(),
gfactors=None,
parameter_dict=None,
start_year=JSON_START_YEAR,
num_years=DEFAULT_NUM_YEARS):
super(Policy, self).__init__()

if not isinstance(gfactors, Growfactors):
raise ValueError('gfactors is not a Growfactors instance')
self._gfactors = gfactors
if gfactors is None:
self._gfactors = Growfactors()
elif isinstance(gfactors, Growfactors):
self._gfactors = gfactors
else:
raise ValueError('gfactors is not None or a Growfactors instance')

if parameter_dict is None: # read default parameters
self._vals = self._params_dict_from_json_file()
Expand All @@ -76,8 +80,8 @@ def __init__(self,

syr = start_year
lyr = start_year + num_years - 1
self._inflation_rates = gfactors.price_inflation_rates(syr, lyr)
self._wage_growth_rates = gfactors.wage_growth_rates(syr, lyr)
self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)

self.initialize(start_year, num_years)

Expand Down Expand Up @@ -227,7 +231,7 @@ def current_law_version(self):
}

@staticmethod
def translate_json_reform_suffixes(indict):
def translate_json_reform_suffixes(indict, growdiff_baseline_param_dict):
"""
Replace any array parameters with suffixes in the specified
JSON-derived "policy" dictionary, indict, and
Expand Down Expand Up @@ -271,11 +275,18 @@ def suffix_group_dict(idict):
return gdict

# define with_suffix function used only in this method
def with_suffix(gdict):
def with_suffix(gdict, growdiff_baseline_param_dict):
"""
Return param_base:year dictionary having only suffix parameters.
"""
pol = Policy()
if bool(growdiff_baseline_param_dict):
gdiff_baseline = Growdiff()
gdiff_baseline.update_growdiff(growdiff_baseline_param_dict)
growfactors = Growfactors()
gdiff_baseline.apply_to(growfactors)
else:
growfactors = None
pol = Policy(gfactors=growfactors)
odict = dict()
for param in gdict.keys():
odict[param] = dict()
Expand All @@ -298,7 +309,7 @@ def with_suffix(gdict):
gdict = suffix_group_dict(indict)
# - add to odict the consolidated values for parameters with a suffix
if len(gdict) > 0:
odict.update(with_suffix(gdict))
odict.update(with_suffix(gdict, growdiff_baseline_param_dict))
# - return policy dictionary containing constructed parameter arrays
return odict

Expand Down
20 changes: 14 additions & 6 deletions taxcalc/tests/test_calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,19 +697,27 @@ def test_translate_json_reform_suffixes_mars_indexed():
"_STD_single": {"2018": [18000], "2016": [16000]},
"_STD_widow": {"2017": [17000], "2019": [19000]}
}}"""
assump_json = """{
"consumption": {},
"behavior": {},
"growdiff_baseline": {
"_ACPIU": {"2013": [0.01]},
"_AWAGE": {"2013": [0.01]}},
"growdiff_response": {}
}"""
pdict1 = Calculator.read_json_param_files(reform_filename=json1,
assump_filename=None,
assump_filename=assump_json,
arrays_not_lists=True)
rdict1 = pdict1['policy']
json2 = """{"policy": {
"_STD": {"2016": [[16000.00, 12600.00, 6300.00, 9300.00, 12600.00]],
"2017": [[16364.80, 12887.28, 6443.64, 9512.04, 17000.00]],
"2018": [[18000.00, 13173.38, 6586.69, 9723.21, 17377.40]],
"2019": [[18412.20, 13475.05, 6737.52, 9945.87, 19000.00]]},
"_STD": {"2016": [[16000.00, 12600.00, 6300.00, 9300.00, 12600.00]],
"2017": [[16524.80, 13013.28, 6506.64, 9605.04, 17000.00]],
"2018": [[18000.00, 13432.31, 6716.15, 9914.32, 17547.40]],
"2019": [[18592.20, 13874.23, 6937.11, 10240.50, 19000.00]]},
"_II_em": {"2020": [20000], "2015": [15000]}
}}"""
pdict2 = Calculator.read_json_param_files(reform_filename=json2,
assump_filename=None,
assump_filename=assump_json,
arrays_not_lists=True)
rdict2 = pdict2['policy']
assert len(rdict2) == len(rdict1)
Expand Down
7 changes: 6 additions & 1 deletion taxcalc/tests/test_growdiff.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from numpy.testing import assert_allclose
import pytest
from taxcalc import Growdiff, Growfactors
from taxcalc import Growdiff, Growfactors, Policy


def test_year_consistency():
assert Growdiff.JSON_START_YEAR == Policy.JSON_START_YEAR
assert Growdiff.DEFAULT_NUM_YEARS == Policy.DEFAULT_NUM_YEARS


def test_incorrect_growdiff_ctor():
Expand Down
5 changes: 4 additions & 1 deletion taxcalc/tests/test_reforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ def test_reform_json(tests_path):
jpf_text = jfile.read()
# check that jpf_text has "policy" that can be implemented as a reform
if '"policy"' in jpf_text:
arrays_not_lists = True
growdiff_dict = {}
# pylint: disable=protected-access
policy_dict = (
Calculator._read_json_policy_reform_text(jpf_text,
arrays_not_lists=True)
arrays_not_lists,
growdiff_dict)
)
policy = Policy()
policy.implement_reform(policy_dict)
Expand Down