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

Change param_code to have multiple-statement (rather than single-expression) format #1107

Merged
merged 29 commits into from
Jan 14, 2017
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bb27bb6
new_refundable_credit_code_function
MattHJensen Dec 12, 2016
b61eab1
Switch from eval() to exec() in new_refundable_credit_code_function.
martinholmer Dec 15, 2016
05fc3f0
Add np.equal function to visible.
martinholmer Dec 15, 2016
96c218f
Merge branch 'master' into pr-1103-alt
martinholmer Dec 15, 2016
98544f9
Add Policy cpi method and use in functions.py file.
martinholmer Dec 15, 2016
ec49b73
Revise Policy cpi method to use assert rather than raise.
martinholmer Dec 15, 2016
3b7a6d5
Add &&<param-code>&& to JSON reform file syntax.
martinholmer Dec 24, 2016
6131848
Revise ALD_Investment_ec_base_code to be statement not expression.
martinholmer Dec 24, 2016
bc9b7d2
Merge in recent master changes.
martinholmer Dec 24, 2016
47e6ea4
Remove duplicate entry in current_law_policy.json file.
martinholmer Dec 24, 2016
0b97087
Rename parameters related to new refundable CTC code.
martinholmer Dec 24, 2016
054f717
Change && to || in JSON reform files.
martinholmer Dec 24, 2016
bf44afb
Make re.sub pattern matching be non-greedy.
martinholmer Dec 28, 2016
6502d8d
Rename/reorganize investment income exclusion base logic.
martinholmer Dec 28, 2016
e0949f2
Rename ALD_invinc as ALD_InvInc.
martinholmer Jan 1, 2017
bdd7ce5
Move logic from IITAX to CTC_new_nocode function.
martinholmer Jan 1, 2017
4c45322
Add comments to test_calculate.py param_code section.
martinholmer Jan 1, 2017
d7e221b
Update taxcalc/reforms documentation.
martinholmer Jan 1, 2017
dffbc6c
Eliminate trailing whitespace in test_calculate.py file.
martinholmer Jan 1, 2017
b44d551
Streamline test_reforms.py logic.
martinholmer Jan 1, 2017
19aeb39
Merge branch 'master' into pr-1103-alt
martinholmer Jan 1, 2017
4274dfe
Merge branch 'master' into pr-1103-alt
martinholmer Jan 1, 2017
ae2d0d1
Merge branch 'master' into pr-1103-alt
martinholmer Jan 1, 2017
8cf50c0
Merge branch 'master' into pr-1103-alt
martinholmer Jan 2, 2017
d1d27a6
Update test_dropq.py for ALD_InvInc_ renames.
martinholmer Jan 2, 2017
3fc3609
Update var name in taxcalc/taxbrain files.
martinholmer Jan 9, 2017
fa71bf3
Merge branch 'master' into pr-1103-alt
martinholmer Jan 13, 2017
6a3aec0
Expand error checking in Policy.cpi_for_param_code(); add tests.
martinholmer Jan 14, 2017
d0b1b48
Add test and remove Policy code that can never be reached.
martinholmer Jan 14, 2017
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
26 changes: 21 additions & 5 deletions taxcalc/calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ 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)
if self.policy.ALD_InvInc_ec_base_code_active:
ALD_InvInc_ec_base_code(self)
else:
ALD_InvInc_ec_base_nocode(self.policy, self.records)
CapGains(self.policy, self.records)
SSBenefits(self.policy, self.records)
AGI(self.policy, self.records)
Expand Down Expand Up @@ -205,6 +207,10 @@ def calc_one_year(self, zero_out_calc_vars=False):
NonrefundableCredits(self.policy, self.records)
AdditionalCTC(self.policy, self.records)
C1040(self.policy, self.records)
if self.policy.CTC_new_code_active:
CTC_new_code(self)
else:
CTC_new_nocode(self.policy, self.records)
IITAX(self.policy, self.records)

def calc_all(self, zero_out_calc_vars=False):
Expand Down Expand Up @@ -416,7 +422,9 @@ def read_json_reform_text(text_string):
or may contain one or more pairs with parameter string primary keys
and string years as secondary keys. See tests/test_calculate.py for
an extended example of a commented JSON reform text that can be read
by this method.
by this method. Note that parameter code in the policy object is
enclosed inside a pair of double pipe characters (||) as shown
in the REFORM_CONTENTS string in the tests/test_calculate.py file.
Returned dictionaries (reform_policy, reform_behavior,
reform_growth reform_consumption)
have integer years as primary keys
Expand All @@ -429,9 +437,17 @@ def read_json_reform_text(text_string):
"""
# strip out //-comments without changing line numbers
json_without_comments = re.sub('//.*', ' ', text_string)
# convert multi-line string between pairs of || into a simple string

def repl(mat):
code = mat.group(2).replace('\r', '\\r').replace('\n', '\\n')
return '"' + code + '"'

json_str = re.sub('(\|\|)(.*?)(\|\|)', # pylint: disable=W1401
repl, json_without_comments, flags=re.DOTALL)
# convert JSON text into a Python dictionary
try:
raw_dict = json.loads(json_without_comments)
raw_dict = json.loads(json_str)
except ValueError as valerr:
msg = 'Policy reform text below contains invalid JSON:\n'
msg += str(valerr) + '\n'
Expand All @@ -441,7 +457,7 @@ def read_json_reform_text(text_string):
bline += '----.----5----.----6----.----7'
msg += bline + '\n'
linenum = 0
for line in json_without_comments.split('\n'):
for line in json_str.split('\n'):
linenum += 1
msg += '{:02d}{}'.format(linenum, line) + '\n'
msg += bline + '\n'
Expand Down
23 changes: 18 additions & 5 deletions taxcalc/current_law_policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,13 @@
"value": [0.0]
},

"_ALD_Investment_ec_rt": {
"_ALD_InvInc_ec_rt": {
"long_name": "Investment income exclusion rate",
"description": "Decimal fraction of investment income base that can be excluded from AGI.",
"section_1": "Above the line deductions",
"section_2": "Misc. exclusions",
"irs_ref": "Form 1040, line 8a",
"notes": "The final taxable investment income will be (1-_ALD_Investment_ec_rt)*investment_income_base. Even though the excluded portion of investment income is not included in AGI, it still is included in investment income used to calculate the Net Investment Income Tax and Earned Income Tax Credit.",
"notes": "The final taxable investment income will be (1-_ALD_InvInc_ec_rt)*investment_income_base. Even though the excluded portion of investment income is not included in AGI, it still is included in investment income used to calculate the Net Investment Income Tax and Earned Income Tax Credit.",
"start_year": 2013,
"col_var": "",
"row_var": "FLPDYR",
Expand All @@ -257,8 +257,8 @@
"value": [0.0]
},

"_ALD_Investment_ec_base_code_active": {
"long_name": "Use code expression to compute investment income exclusion base",
"_ALD_InvInc_ec_base_code_active": {
"long_name": "Use parameter code to compute investment income exclusion base",
"description": "False implies base includes all investment income; otherwise code defines base.",
"irs_ref": "",
"notes": "",
Expand Down Expand Up @@ -2432,6 +2432,20 @@
"value": [3]
},

"_CTC_new_code_active": {
"long_name": "Use parameter code to compute new refundable child tax credit",
"description": "False implies additional refundable child tax credit computed using numeric parameters; otherwise code defines credit.",
"irs_ref": "",
"notes": "",
"start_year": 2013,
"col_var": "",
"row_var": "FLPDYR",
"row_label": ["2013"],
"cpi_inflated": false,
"col_label": "",
"value": [false]
},

"_CTC_new_c": {
"long_name": "New refundable child tax credit maximum amount per child",
"description": "In addition to all credits currently available for dependents, this parameter gives each qualifying child a new refundable credit with this maximum amount.",
Expand All @@ -2448,7 +2462,6 @@
"value": [0.0]
},


"_CTC_new_c_under5_bonus": {
"long_name": "Bonus new refundable child tax credit maximum for qualifying children under five",
"description": "The maximum amount of the new refundable child tax credit allowed for each child is increased by this amount for qualifying children under 5 years old. ",
Expand Down
87 changes: 62 additions & 25 deletions taxcalc/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,25 +186,41 @@ def Adj(e03150, e03210, c03260,
return (c02900, c02900_in_ei)


def ALD_Investment_ec_base_code_function(calc):
@iterate_jit(nopython=True)
def ALD_InvInc_ec_base_nocode(p22250, p23250, _sep,
e00300, e00600, e01100, e01200,
invinc_ec_base):
"""
Compute invinc_ec_base without code
"""
# limitation on net short-term and long-term capital losses
cgain = max((-3000. / _sep), p22250 + p23250)
# compute exclusion of investment income from AGI
invinc_ec_base = e00300 + e00600 + cgain + e01100 + e01200
return invinc_ec_base


def ALD_InvInc_ec_base_code(calc):
"""
Compute investment_ec_base from code
Compute invinc_ec_base from code
"""
code = calc.policy.param_code['ALD_Investment_ec_base_code']
visible = {'min': np.minimum, 'max': np.maximum, 'where': np.where}
code = calc.policy.param_code['ALD_InvInc_ec_base_code']
visible = {'min': np.minimum, 'max': np.maximum,
'where': np.where, 'equal': np.equal}
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)
visible['cpi'] = calc.policy.cpi('ALD_InvInc_ec_base_code')
visible['returned_value'] = calc.records.invinc_ec_base
# pylint: disable=exec-used
exec(compile(code, '<str>', 'exec'), {'__builtins__': {}}, visible)
calc.records.invinc_ec_base = visible['returned_value']


@iterate_jit(nopython=True)
def CapGains(p23250, p22250, _sep, ALD_StudentLoan_hc,
ALD_Investment_ec_rt, ALD_Investment_ec_base_code_active,
investment_ec_base,
ALD_InvInc_ec_rt, invinc_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 @@ -217,13 +233,10 @@ def CapGains(p23250, p22250, _sep, ALD_StudentLoan_hc,
c23650 = p23250 + p22250
# limitation on capital losses
c01000 = max((-3000. / _sep), c23650)
# compute exclusion of investment income from AGI
# compute total investment income
invinc = e00300 + e00600 + c01000 + e01100 + e01200
if ALD_Investment_ec_base_code_active:
invinc_ec_base = investment_ec_base
else:
invinc_ec_base = invinc
invinc_agi_ec = ALD_Investment_ec_rt * max(0., invinc_ec_base)
# compute exclusion of investment income from AGI
invinc_agi_ec = ALD_InvInc_ec_rt * max(0., invinc_ec_base)
# compute ymod1 variable that is included in AGI
ymod1 = (e00200 + e00700 + e00800 + e00900 + e01400 + e01700 +
invinc - invinc_agi_ec +
Expand Down Expand Up @@ -1219,16 +1232,14 @@ def C1040(c05800, c07180, c07200, c07220, c07230, c07240, c07260, c07300,


@iterate_jit(nopython=True)
def IITAX(c59660, c11070, c10960, personal_credit,
c09200, _payrolltax, nu05,
CTC_new_c, CTC_new_rt, CTC_new_c_under5_bonus,
n24, c00100, MARS, ptax_oasdi, CTC_new_refund_limited,
CTC_new_ps, CTC_new_prt, CTC_new_refund_limit_payroll_rt,
ctc_new, _eitc, _refund, _iitax, _combined):
def CTC_new_nocode(CTC_new_c, CTC_new_rt, CTC_new_c_under5_bonus,
CTC_new_ps, CTC_new_prt,
CTC_new_refund_limited, CTC_new_refund_limit_payroll_rt,
n24, nu05, c00100, MARS, ptax_oasdi, c09200,
ctc_new):
"""
Compute final taxes including new refundable child tax credit
Compute new refundable child tax credit with numeric parameters
"""
# compute new refundable child tax credit
if n24 > 0:
posagi = max(c00100, 0.)
ctc_new = min(CTC_new_rt * posagi,
Expand All @@ -1245,12 +1256,38 @@ def IITAX(c59660, c11070, c10960, personal_credit,
ctc_new = max(0., ctc_new - limited_new)
else:
ctc_new = 0.
# compute final taxes
return ctc_new


def CTC_new_code(calc):
"""
Compute new refundable child tax credit using parameter code
"""
code = calc.policy.param_code['CTC_new_code']
visible = {'min': np.minimum, 'max': np.maximum,
'where': np.where, 'equal': np.equal}
variables = ['n24', 'c00100', 'nu05', 'MARS', 'ptax_oasdi', 'c09200']
for var in variables:
visible[var] = getattr(calc.records, var)
visible['cpi'] = calc.policy.cpi('CTC_new_code')
visible['returned_value'] = calc.records.ctc_new
# pylint: disable=exec-used
exec(compile(code, '<str>', 'exec'), {'__builtins__': {}}, visible)
calc.records.ctc_new = visible['returned_value']


@iterate_jit(nopython=True)
def IITAX(c59660, c11070, c10960, personal_credit, ctc_new,
c09200, _payrolltax,
_eitc, _refund, _iitax, _combined):
"""
Compute final taxes
"""
_eitc = c59660
_refund = _eitc + c11070 + c10960 + personal_credit + ctc_new
_iitax = c09200 - _refund
_combined = _iitax + _payrolltax
return (ctc_new, _eitc, _refund, _iitax, _combined)
return (_eitc, _refund, _iitax, _combined)


@jit(nopython=True)
Expand Down
29 changes: 28 additions & 1 deletion taxcalc/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ 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'])
VALID_PARAM_CODE_NAMES = set(['ALD_InvInc_ec_base_code',
'CTC_new_code'])

PROHIBIT_PARAM_CODE = False

Expand Down Expand Up @@ -142,6 +143,12 @@ def __init__(self, parameter_dict=None,
else:
raise ValueError('inflation_rates is not None or a dictionary')

cpi = 1.0
self._inflation_index = [cpi]
for idx in range(0, num_years):
cpi *= (1.0 + self._inflation_rates[idx])
self._inflation_index.append(cpi)

if wage_growth_rates is None: # read default rates
self._wage_growth_rates = [Policy.__wgrates[start_year + i]
for i in range(0, num_years)]
Expand Down Expand Up @@ -305,6 +312,26 @@ def scan_param_code(code):
msg += code
raise ValueError(msg)

def cpi(self, param_code_name):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to add a unit test for this function. Also, it seems to me that the use case is specific enough that a more descriptive name should be used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests and expanded function name for clarity.

"""
Return inflation index for current_year that has
a value of one in first year param_code is active.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be "a value of one if first year param_code is active"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No but I expanded the docstring to give a more complete explanation.

"""
assert param_code_name in Policy.VALID_PARAM_CODE_NAMES
active_name = '_{}_active'.format(param_code_name)
active_param = getattr(self, active_name, None)
zero_year = Policy.JSON_START_YEAR
first_active_year = 9999
for idx in range(0, len(active_param)):
if active_param[idx]:
first_active_year = idx + zero_year
break
assert first_active_year <= Policy.LAST_BUDGET_YEAR
cpi_current_year = self._inflation_index[self.current_year - zero_year]
cpi_active_year = self._inflation_index[first_active_year - zero_year]
cpi_rebased = cpi_current_year / cpi_active_year
return cpi_rebased

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', 'investment_ec_base', 'invinc_agi_ec',
'c01000', 'c02500', 'c19700', 'invinc_ec_base', 'invinc_agi_ec',
'_sey', '_earned', '_earned_p', '_earned_s',
'ymod', 'ymod1',
'c04800', 'c19200', 'c20500',
Expand Down
13 changes: 9 additions & 4 deletions taxcalc/reforms/REFORMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,17 @@ separate, head of household, widow, separate.
// primary keys are parameters and secondary keys are years.
// Both the primary and secondary key values must be enclosed in quotes (").
// Boolean variables are specified as true or false (no quotes; all lowercase).
// Parameter code in the policy object is enclosed inside a pair of double
// pipe characters (||).
{
"policy": {
"param_code": {
"ALD_Investment_ec_base_code": "e00300 + e00650 + p23250"
},
"_ALD_Investment_ec_base_code_active":
"param_code": { // all the parameter code must go in one place
"ALD_InvInc_ec_base_code":
||
// base is sum of taxable interest, qualified dividends and long-term cap gains
returned_value = e00300 + e00650 + p23250
||},
"_ALD_InvInc_ec_base_code_active":
{"2016": [true]
},
"_AMT_brk1": // top of first AMT tax bracket
Expand Down
11 changes: 7 additions & 4 deletions taxcalc/reforms/adjust0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
{
"policy": {
// specify fraction of investment income that can be excluded from AGI
"_ALD_Investment_ec_rt": {"2016": [0.50]},
"_ALD_InvInc_ec_rt": {"2016": [0.50]},

// specify "investment income" base to mean the sum of three things:
// taxable interest (e00300), qualified dividends (e00650), and
// long-term capital gains (p23250) [see functions.py for other
// types of investment income that can be included in the base]
"param_code": {"ALD_Investment_ec_base_code":
"e00300 + e00650 + p23250"},
"param_code": {
"ALD_InvInc_ec_base_code":
||
returned_value = e00300 + e00650 + p23250
||},

"_ALD_Investment_ec_base_code_active": {"2016": [true]}
"_ALD_InvInc_ec_base_code_active": {"2016": [true]}
// the dollar exclusion is the product of the base defined by code
// and the fraction defined above
},
Expand Down
6 changes: 3 additions & 3 deletions taxcalc/taxbrain/file0.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@
{"2018": false, // values in future years are same as this year value
"2020": true // values in future years indexed with this year as base
},
"_ALD_Investment_ec_rt": // investment income AGI exclusion rate
"_ALD_InvInc_ec_rt": // investment income AGI exclusion rate
{"2019": [0.20]
},
// investment income AGI exclusion base is not all investment income
// but rather the sum of taxable interest, qualified dividends, and
// long-term capital gains
"_ALD_Investment_ec_base_code_active":
"_ALD_InvInc_ec_base_code_active":
{"2019": [true]
},
"param_code":
{"ALD_Investment_ec_base_code": "e00300 + e00650 + p23250"
{"ALD_InvInc_ec_base_code": "e00300 + e00650 + p23250"
}
},
"behavior": {
Expand Down
6 changes: 3 additions & 3 deletions taxcalc/taxbrain/file1.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@
{"2018": false, // values in future years are same as this year value
"2020": true // values in future years indexed with this year as base
},
"_ALD_Investment_ec_rt": // investment income AGI exclusion rate
"_ALD_InvInc_ec_rt": // investment income AGI exclusion rate
{"2019": [0.20]
},
// investment income AGI exclusion base is not all investment income
// but rather the sum of taxable interest, qualified dividends, and
// long-term capital gains
"_ALD_Investment_ec_base_code_active":
"_ALD_InvInc_ec_base_code_active":
{"2019": [true]
},
"param_code":
{"ALD_Investment_ec_base_code": "e00300 + e00650 + p23250"
{"ALD_InvInc_ec_base_code": "e00300 + e00650 + p23250"
}
},
"behavior": {
Expand Down
Loading