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

Add ability to simulate many ALD_Investment_ec_base definitions #1081

Merged
merged 15 commits into from
Dec 7, 2016
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
9 changes: 7 additions & 2 deletions taxcalc/calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ def calc_one_year(self, zero_out_calc_vars=False):
EI_PayrollTax(self.policy, self.records)
DependentCare(self.policy, self.records)
Adj(self.policy, self.records)
if self.policy.ALD_Investment_ec_base_code_active:
ALD_Investment_ec_base_code_function(self)
CapGains(self.policy, self.records)
SSBenefits(self.policy, self.records)
AGI(self.policy, self.records)
Expand Down Expand Up @@ -449,11 +451,14 @@ def read_json_reform_text(text_string):
if actual_keys != expect_keys:
msg = 'reform keys {} not equal to {}'
raise ValueError(msg.format(actual_keys, expect_keys))
# handle special param_code key in policy dictionary
# handle special param_code key in raw_dict policy component dictionary
paramcode = raw_dict['policy'].pop('param_code', None)
if paramcode:
if Policy.PROHIBIT_PARAM_CODE:
msg = 'JSON reform file containing "param_code" is not allowed'
raise ValueError(msg)
for param, code in paramcode.items():
raw_dict[param] = {'0': code}
raw_dict['policy'][param] = {'0': code}
# convert raw_dict component dictionaries
pol_dict = Calculator.convert_reform_dict(raw_dict['policy'])
beh_dict = Calculator.convert_reform_dict(raw_dict['behavior'])
Expand Down
10 changes: 5 additions & 5 deletions taxcalc/current_law_policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,18 @@
"value": [0.0]
},

"_ALD_Investment_ec_base_all": {
"long_name": "Investment income exclusion comprehensive base",
"description": "Defines scope of base: true implies investment income base includes all investment income; false implies base includes only qualified dividends, long-term capital gains, and taxable interest income.",
"_ALD_Investment_ec_base_code_active": {
"long_name": "Use code expression to compute investment income exclusion base",
"description": "False implies base includes all investment income; otherwise code defines base.",
"irs_ref": "",
"notes": "Value of false characterizes Ryan-Brady tax plan.",
"notes": "",
"start_year": 2013,
"col_var": "",
"row_var": "FLPDYR",
"row_label": ["2013"],
"cpi_inflated": false,
"col_label": "",
"value": [true]
"value": [false]
},

"_ALD_Dependents_hc": {
Expand Down
24 changes: 20 additions & 4 deletions taxcalc/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,25 @@ def Adj(e03150, e03210, c03260,
return (c02900, c02900_in_ei)


def ALD_Investment_ec_base_code_function(calc):
"""
Compute investment_ec_base from code
"""
code = calc.policy.param_code['ALD_Investment_ec_base_code']
visible = {'min': np.minimum, 'max': np.maximum, 'where': np.where}
variables = ['e00300', 'e00600', 'e00650', 'e01100', 'e01200',
'p22250', 'p23250', '_sep']
for var in variables:
visible[var] = getattr(calc.records, var)
# pylint: disable=eval-used
calc.records.investment_ec_base = eval(compile(code, '<str>', 'eval'),
{'__builtins__': {}}, visible)


@iterate_jit(nopython=True)
def CapGains(p23250, p22250, _sep, ALD_StudentLoan_hc,
ALD_Investment_ec_rt, ALD_Investment_ec_base_all,
ALD_Investment_ec_rt, ALD_Investment_ec_base_code_active,
investment_ec_base,
e00200, e00300, e00600, e00650, e00700, e00800,
CG_nodiff, CG_ec, CG_reinvest_ec_rt,
e00900, e01100, e01200, e01400, e01700, e02000, e02100,
Expand All @@ -203,10 +219,10 @@ def CapGains(p23250, p22250, _sep, ALD_StudentLoan_hc,
c01000 = max((-3000. / _sep), c23650)
# compute exclusion of investment income from AGI
invinc = e00300 + e00600 + c01000 + e01100 + e01200
if ALD_Investment_ec_base_all:
invinc_ec_base = invinc
if ALD_Investment_ec_base_code_active:
invinc_ec_base = investment_ec_base
else:
invinc_ec_base = e00300 + e00650 + p23250
invinc_ec_base = invinc
invinc_agi_ec = ALD_Investment_ec_rt * max(0., invinc_ec_base)
# compute ymod1 variable that is included in AGI
ymod1 = (e00200 + e00700 + e00800 + e00900 + e01400 + e01700 +
Expand Down
46 changes: 44 additions & 2 deletions taxcalc/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# (when importing numpy, add "--extension-pkg-whitelist=numpy" pylint option)


import re
from .parameters import ParametersBase


Expand Down Expand Up @@ -70,6 +71,10 @@ class instance: Policy
2021: 0.0403, 2022: 0.0413, 2023: 0.0417, 2024: 0.0417,
2025: 0.0415, 2026: 0.0416}

VALID_PARAM_CODE_NAMES = set(['ALD_Investment_ec_base_code'])

PROHIBIT_PARAM_CODE = False

@staticmethod
def default_inflation_rates():
"""
Expand Down Expand Up @@ -124,7 +129,7 @@ def __init__(self, parameter_dict=None,
raise ValueError('num_years cannot be less than one')

if inflation_rates is None: # read default rates
self._inflation_rates = [self.__pirates[start_year + i]
self._inflation_rates = [Policy.__pirates[start_year + i]
for i in range(0, num_years)]
elif isinstance(inflation_rates, dict):
if len(inflation_rates) != num_years:
Expand All @@ -138,7 +143,7 @@ def __init__(self, parameter_dict=None,
raise ValueError('inflation_rates is not None or a dictionary')

if wage_growth_rates is None: # read default rates
self._wage_growth_rates = [self.__wgrates[start_year + i]
self._wage_growth_rates = [Policy.__wgrates[start_year + i]
for i in range(0, num_years)]
elif isinstance(wage_growth_rates, dict):
if len(wage_growth_rates) != num_years:
Expand All @@ -151,6 +156,10 @@ def __init__(self, parameter_dict=None,
else:
raise ValueError('wage_growth_rates is not None or a dictionary')

self.param_code = dict()
for param in Policy.VALID_PARAM_CODE_NAMES:
self.param_code[param] = ''

self.initialize(start_year, num_years)

def inflation_rates(self):
Expand Down Expand Up @@ -237,6 +246,7 @@ def implement_reform(self, reform):
catch this error, so be careful to specify reform dictionaries
correctly.
"""
# check that all reform dictionary keys are integers
if not isinstance(reform, dict):
raise ValueError('reform is not a dictionary')
if len(reform) == 0:
Expand All @@ -246,6 +256,15 @@ def implement_reform(self, reform):
if not isinstance(year, int):
msg = 'key={} in reform is not an integer calendar year'
raise ValueError(msg.format(year))
# remove and process param_code information
zero = 0 # param_code information is marked with year equal to 0
param_code_dict = reform.pop(zero, None)
if param_code_dict:
reform_years.remove(zero)
for param, code in param_code_dict.items():
Policy.scan_param_code(code)
self.param_code[param] = code
# check range of remaining reform_years
first_reform_year = min(reform_years)
if first_reform_year < self.start_year:
msg = 'reform provision in year={} < start_year={}'
Expand All @@ -257,12 +276,35 @@ def implement_reform(self, reform):
if last_reform_year > self.end_year:
msg = 'reform provision in year={} > end_year={}'
raise ValueError(msg.format(last_reform_year, self.end_year))
# implement the reform year by year
precall_current_year = self.current_year
for year in reform_years:
self.set_year(year)
self._update({year: reform[year]})
self.set_year(precall_current_year)

@staticmethod
def scan_param_code(code):
"""
Raise ValueError if certain character strings found in specified code.
"""
if re.search(r'__', code) is not None:
msg = 'Following param_code includes illegal "__":\n'
msg += code
raise ValueError(msg)
if re.search(r'lambda', code) is not None:
msg = 'Following param_code includes illegal "lambda":\n'
msg += code
raise ValueError(msg)
if re.search(r'\[', code) is not None:
msg = 'Following param_code includes illegal "[":\n'
msg += code
raise ValueError(msg)
if re.search(r'\*\*', code) is not None:
msg = 'Following param_code includes illegal "**":\n'
msg += code
raise ValueError(msg)

def current_law_version(self):
"""
Return Policy object same as self except with current-law policy.
Expand Down
2 changes: 1 addition & 1 deletion taxcalc/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class instance: Records
'c00100', 'pre_c04600', 'c04600',
'c04470', 'c21060', 'c21040', 'c17000',
'c18300', 'c20800', 'c02900', 'c02900_in_ei', 'c23650',
'c01000', 'c02500', 'c19700', 'invinc_agi_ec',
'c01000', 'c02500', 'c19700', 'investment_ec_base', 'invinc_agi_ec',
'_sey', '_earned', '_earned_p', '_earned_s',
'ymod', 'ymod1',
'c04800', 'c19200', 'c20500',
Expand Down
12 changes: 11 additions & 1 deletion taxcalc/tests/test_calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,20 @@ def test_Calculator_create_difference_table(puf_1991, weights_1991):
policy1 = Policy()
puf1 = Records(data=puf_1991, weights=weights_1991, start_year=2009)
calc1 = Calculator(policy=policy1, records=puf1)
calc1.advance_to_year(2013)
calc1.calc_all()
# create policy-reform Policy object and use to create Calculator calc2
policy2 = Policy()
reform = {2013: {'_II_rt7': [0.45]}}
reform = {
2013: {'_II_rt7': [0.45]},
2013: {'_ALD_Investment_ec_base_code_active': [True]},
0: {'ALD_Investment_ec_base_code': 'e00300 + e00650 + p23250'}
}
policy2.implement_reform(reform)
puf2 = Records(data=puf_1991, weights=weights_1991, start_year=2009)
calc2 = Calculator(policy=policy2, records=puf2)
calc2.advance_to_year(2013)
calc2.calc_all()
# create difference table and check that it is a Pandas DataFrame
dtable = create_difference_table(calc1.records, calc2.records,
groupby="weighted_deciles")
Expand Down Expand Up @@ -428,6 +435,9 @@ def test_Calculator_using_nonstd_input(rawinputfile):
"param_code": {
"ALD_Investment_ec_base_code": "e00300 + e00650 + p23250"
},
"_ALD_Investment_ec_base_code_active":
{"2016": [true]
},
"_AMT_brk1": // top of first AMT tax bracket
{"2015": [200000],
"2017": [300000]
Expand Down
16 changes: 9 additions & 7 deletions taxcalc/tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def test_calc_and_used_vars(tests_path):
(2) Check that each variable that is calculated in a function and
returned by that function is an argument of that function.
"""
# pylint: disable=too-many-locals
funcpath = os.path.join(tests_path, '..', 'functions.py')
gfd = GetFuncDefs()
fnames, fargs, cvars, rvars = gfd.visit(ast.parse(open(funcpath).read()))
Expand All @@ -91,6 +92,8 @@ def test_calc_and_used_vars(tests_path):
# .. add to all_cvars set some variables calculated in Records class
all_cvars.update(set(['ID_Casualty_frt_in_pufcsv_year',
'_num', '_sep', '_exact']))
# .. add to all_cvars set some variables calculated in *_code_function
all_cvars.update(set(['investment_ec_base']))
# .. check that each var in Records.CALCULATED_VARS is in the all_cvars set
found_error1 = False
if not Records.CALCULATED_VARS <= all_cvars:
Expand All @@ -99,14 +102,14 @@ def test_calc_and_used_vars(tests_path):
found_error1 = True
msg1 += 'VAR NOT CALCULATED: {}\n'.format(var)
# Test (2):
faux_functions = ['EITCamount', 'ComputeBenefit',
'BenefitSurtax', 'BenefitLimitation',
'ALD_Investment_ec_base_code_function']
found_error2 = False
msg2 = 'calculated & returned variables are not function arguments\n'
for fname in fnames:
if (fname == 'EITCamount' or
fname == 'ComputeBenefit' or
fname == 'BenefitSurtax' or
fname == 'BenefitLimitation'):
continue # because fname is not really a function
if fname in faux_functions:
continue # because fname is not a genuine function
crvars_set = set(cvars[fname]) & set(rvars[fname])
if not crvars_set <= set(fargs[fname]):
found_error2 = True
Expand Down Expand Up @@ -234,8 +237,7 @@ def test_2():
policy_reform = {
2015: {
'_ACTC_Income_thd': [actc_thd],
'_SS_Earnings_c': [mte],
'_ALD_Investment_ec_base_all': [False]
'_SS_Earnings_c': [mte]
}
}
funit = (
Expand Down
Loading