Recipe 5: Redefining Expanded IncomeΒΆ

This is an advanced recipe that should be followed only after mastering the basic recipe. This recipe is almost exactly the same as Directly Comparing Two Reforms, so you might want to read that recipe first.

This recipe introduces a powerful technique for customizing the operation of Tax-Calculator. This calculator-customization technique is used in this recipe to redefine expanded income in a way that allows the redefined income measure to be used seamlessly with all the other (table and graph) methods of the Calculator class. The basic idea behind the calculator-customization technique is to derive a customized Calculator class from the Tax-Calculator Calculator class. This is a standard object-oriented programming technique.

Recipe that illustrates how to customize the taxcalc Calculator class so that it can seamlessly use an alternative definition of expanded income.

The technique for doing this customization is standard in object-oriented programming: a child class is derived from a parent class and then customized. The derived child class inherits all the data and methods of the parent class, but can be customized by adding new data and methods or by overriding inherited methods.

import pandas as pd
import taxcalc as tc


# override the ExpandIncome calcfunction that computes "market income"


@tc.iterate_jit(nopython=True)
def ExpandIncome(e00200, pencon_p, pencon_s, e00300, e00400, e00600,
                 e00700, e00800, e00900, e01100, e01200, e01400, e01500,
                 e02000, e02100, p22250, p23250, cmbtp, ptax_was,
                 expanded_income):
    """
    Calculates expanded_income as "market income" from component income types.
    """
    expanded_income = (
        e00200 +  # wage and salary income net of DC pension contributions
        pencon_p +  # tax-advantaged DC pension contributions for taxpayer
        pencon_s +  # tax-advantaged DC pension contributions for spouse
        e00300 +  # taxable interest income
        e00400 +  # non-taxable interest income
        e00600 +  # dividends
        e00700 +  # state and local income tax refunds
        e00800 +  # alimony received
        e00900 +  # Sch C business net income/loss
        e01100 +  # capital gain distributions not reported on Sch D
        e01200 +  # Form 4797 other net gain/loss
        e01400 +  # taxable IRA distributions
        e01500 +  # total pension & annuity income (including DB-plan benefits)
        e02000 +  # Sch E total rental, ..., partnership, S-corp income/loss
        e02100 +  # Sch F farm net income/loss
        p22250 +  # Sch D: net short-term capital gain/loss
        p23250 +  # Sch D: net long-term capital gain/loss
        cmbtp +  # other AMT taxable income items from Form 6251
        0.5 * ptax_was  # employer share of FICA taxes on wages/salaries
        # excluding:
        # ubi +  # total UBI benefit
        # benefit_value_total  # consumption value of all benefits received;
    )
    return expanded_income

# end overrided calcfunction used by customized Calculator class


class Calculator(tc.Calculator):
    """
    Customized Calculator class that inherits all tc.Calculator data and
    methods, overriding one method to get the desired customization.
    """
    def __init__(self, policy=None, records=None, verbose=False,
                 sync_years=True, consumption=None):
        # use same class constructor arguments as tc.Calculator class
        super().__init__(policy=policy, records=records,
                         verbose=verbose, sync_years=sync_years,
                         consumption=consumption)

    def calc_all(self, zero_out_calc_vars=False):
        """
        Call all tax-calculation functions for the current_year.
        """
        tc.BenefitPrograms(self)
        self._calc_one_year(zero_out_calc_vars)
        tc.BenefitSurtax(self)
        tc.BenefitLimitation(self)
        tc.FairShareTax(self.__policy, self.__records)
        tc.LumpSumTax(self.__policy, self.__records)
        ExpandIncome(self.__policy, self.__records)  # customized (see above)
        tc.AfterTaxIncome(self.__policy, self.__records)

# end of customized Calculator class definition


# top-level logic of program that uses customized Calculator class

# read an "old" reform file
# ("old" means the reform file is defined relative to pre-TCJA policy)
# specify reform dictionary for pre-TCJA policy
reform1 = tc.Policy.read_json_reform('github://PSLmodels:[email protected]/psl_examples/taxcalc/2017_law.json')

# specify reform dictionary for TCJA as passed by Congress in late 2017
reform2 = tc.Policy.read_json_reform('github://PSLmodels:[email protected]/psl_examples/taxcalc/TCJA.json')

# specify Policy object for pre-TCJA policy
bpolicy = tc.Policy()
bpolicy.implement_reform(reform1, print_warnings=False, raise_errors=False)
assert not bpolicy.parameter_errors

# specify Policy object for TCJA reform relative to pre-TCJA policy
rpolicy = tc.Policy()
rpolicy.implement_reform(reform1, print_warnings=False, raise_errors=False)
assert not rpolicy.parameter_errors
rpolicy.implement_reform(reform2, print_warnings=False, raise_errors=False)
assert not rpolicy.parameter_errors

# specify customized Calculator objects using bpolicy and rpolicy
recs = tc.Records.cps_constructor()
calc1 = Calculator(policy=bpolicy, records=recs)
calc2 = Calculator(policy=rpolicy, records=recs)

CYR = 2018

# calculate for specified CYR
calc1.advance_to_year(CYR)
calc1.calc_all()
calc2.advance_to_year(CYR)
calc2.calc_all()

# compare aggregate individual income tax revenue in CYR
iitax_rev1 = calc1.weighted_total('iitax')
iitax_rev2 = calc2.weighted_total('iitax')

# construct reform-vs-baseline difference table with results for income deciles
diff_table = calc1.difference_table(calc2, 'standard_income_bins', 'iitax')
assert isinstance(diff_table, pd.DataFrame)
diff_extract = pd.DataFrame()
dif_colnames = ['count', 'tax_cut', 'tax_inc',
                'tot_change', 'mean', 'pc_aftertaxinc']
ext_colnames = ['funits(#m)', 'taxfall(#m)', 'taxrise(#m)',
                'agg_diff($b)', 'mean_diff($)', 'aftertax_income_diff(%)']
for dname, ename in zip(dif_colnames, ext_colnames):
    diff_extract[ename] = diff_table[dname]

# print total revenue estimates for CYR
# (estimates in billons of dollars)
print('{}_REFORM1_iitax_rev($B)= {:.3f}'.format(CYR, iitax_rev1 * 1e-9))
print('{}_REFORM2_iitax_rev($B)= {:.3f}'.format(CYR, iitax_rev2 * 1e-9))
print('')

# print reform2-vs-reform1 difference table
title = 'Extract of {} income-tax difference table by expanded-income decile'
print(title.format(CYR))
print('(taxfall is count of funits with cut in income tax in reform 2 vs 1)')
print('(taxrise is count of funits with rise in income tax in reform 2 vs 1)')
print(diff_extract.to_string())
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-2-50611885f56c> in <module>
    102 # calculate for specified CYR
    103 calc1.advance_to_year(CYR)
--> 104 calc1.calc_all()
    105 calc2.advance_to_year(CYR)
    106 calc2.calc_all()

<ipython-input-2-50611885f56c> in calc_all(self, zero_out_calc_vars)
     60         """
     61         tc.BenefitPrograms(self)
---> 62         self._calc_one_year(zero_out_calc_vars)
     63         tc.BenefitSurtax(self)
     64         tc.BenefitLimitation(self)

~/work/Tax-Calculator/Tax-Calculator/taxcalc/calculator.py in _calc_one_year(self, zero_out_calc_vars)
   1382         SSBenefits(self.__policy, self.__records)
   1383         AGI(self.__policy, self.__records)
-> 1384         ItemDedCap(self.__policy, self.__records)
   1385         ItemDed(self.__policy, self.__records)
   1386         AdditionalMedicareTax(self.__policy, self.__records)

~/work/Tax-Calculator/Tax-Calculator/taxcalc/decorators.py in wrapper(*args, **kwargs)
    323                  {"applied_f": applied_jitted_f}, fakeglobals)
    324             high_level_fn = fakeglobals['hl_func']
--> 325             ans = high_level_fn(*args, **kwargs)
    326             return ans
    327 

<string> in hl_func(pm, pf)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_for_args(self, *args, **kws)
    370         return_val = None
    371         try:
--> 372             return_val = self.compile(tuple(argtypes))
    373         except errors.ForceLiteralArg as e:
    374             # Received request for compiler re-entry with the list of arguments

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, sig)
    907                 with ev.trigger_event("numba:compile", data=ev_details):
    908                     try:
--> 909                         cres = self._compiler.compile(args, return_type)
    910                     except errors.ForceLiteralArg as e:
    911                         def folded(args, kws):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, args, return_type)
     77 
     78     def compile(self, args, return_type):
---> 79         status, retval = self._compile_cached(args, return_type)
     80         if status:
     81             return retval

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_cached(self, args, return_type)
     91 
     92         try:
---> 93             retval = self._compile_core(args, return_type)
     94         except errors.TypingError as e:
     95             self._failed_cache[key] = e

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_core(self, args, return_type)
    104 
    105         impl = self._get_implementation(args, {})
--> 106         cres = compiler.compile_extra(self.targetdescr.typing_context,
    107                                       self.targetdescr.target_context,
    108                                       impl,

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)
    604     pipeline = pipeline_class(typingctx, targetctx, library,
    605                               args, return_type, flags, locals)
--> 606     return pipeline.compile_extra(func)
    607 
    608 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(self, func)
    351         self.state.lifted = ()
    352         self.state.lifted_from = None
--> 353         return self._compile_bytecode()
    354 
    355     def compile_ir(self, func_ir, lifted=(), lifted_from=None):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in _compile_bytecode(self)
    413         """
    414         assert self.state.func_ir is None
--> 415         return self._compile_core()
    416 
    417     def _compile_ir(self):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in _compile_core(self)
    384             res = None
    385             try:
--> 386                 pm.run(self.state)
    387                 if self.state.cr is not None:
    388                     break

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in run(self, state)
    328                 pass_inst = _pass_registry.get(pss).pass_inst
    329                 if isinstance(pass_inst, CompilerPass):
--> 330                     self._runPass(idx, pass_inst, state)
    331                 else:
    332                     raise BaseException("Legacy pass in use")

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     33         def _acquire_compile_lock(*args, **kwargs):
     34             with self:
---> 35                 return func(*args, **kwargs)
     36         return _acquire_compile_lock
     37 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in _runPass(self, index, pss, internal_state)
    287             mutated |= check(pss.run_initialization, internal_state)
    288         with SimpleTimer() as pass_time:
--> 289             mutated |= check(pss.run_pass, internal_state)
    290         with SimpleTimer() as finalize_time:
    291             mutated |= check(pss.run_finalizer, internal_state)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in check(func, compiler_state)
    260 
    261         def check(func, compiler_state):
--> 262             mangled = func(compiler_state)
    263             if mangled not in (True, False):
    264                 msg = ("CompilerPass implementations should return True/False. "

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typed_passes.py in run_pass(self, state)
    461 
    462         # TODO: Pull this out into the pipeline
--> 463         NativeLowering().run_pass(state)
    464         lowered = state['cr']
    465         signature = typing.signature(state.return_type, *state.args)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typed_passes.py in run_pass(self, state)
    382                 lower = lowering.Lower(targetctx, library, fndesc, interp,
    383                                        metadata=metadata)
--> 384                 lower.lower()
    385                 if not flags.no_cpython_wrapper:
    386                     lower.create_cpython_wrapper(flags.release_gil)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower(self)
    134         if self.generator_info is None:
    135             self.genlower = None
--> 136             self.lower_normal_function(self.fndesc)
    137         else:
    138             self.genlower = self.GeneratorLower(self)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_normal_function(self, fndesc)
    188         # Init argument values
    189         self.extract_function_arguments()
--> 190         entry_block_tail = self.lower_function_body()
    191 
    192         # Close tail of entry block

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_function_body(self)
    214             bb = self.blkmap[offset]
    215             self.builder.position_at_end(bb)
--> 216             self.lower_block(block)
    217         self.post_lower()
    218         return entry_block_tail

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_block(self, block)
    228             with new_error_context('lowering "{inst}" at {loc}', inst=inst,
    229                                    loc=self.loc, errcls_=defaulterrcls):
--> 230                 self.lower_inst(inst)
    231         self.post_block(block)
    232 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_inst(self, inst)
    325         if isinstance(inst, ir.Assign):
    326             ty = self.typeof(inst.target.name)
--> 327             val = self.lower_assign(ty, inst)
    328             self.storevar(val, inst.target.name)
    329 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_assign(self, ty, inst)
    526                 val = self.fnargs[value.index]
    527                 res = self.context.cast(self.builder, val, argty, ty)
--> 528             self.incref(ty, res)
    529             return res
    530 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in incref(self, typ, val)
   1317             return
   1318 
-> 1319         self.context.nrt.incref(self.builder, typ, val)
   1320 
   1321     def decref(self, typ, val):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/runtime/context.py in incref(self, builder, typ, value)
    216         Recursively incref the given *value* and its members.
    217         """
--> 218         self._call_incref_decref(builder, typ, value, "NRT_incref")
    219 
    220     def decref(self, builder, typ, value):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/runtime/context.py in _call_incref_decref(self, builder, typ, value, funcname)
    208             # XXX "nonnull" causes a crash in test_dyn_array: can this
    209             # function be called with a NULL pointer?
--> 210             fn.args[0].add_attribute("noalias")
    211             fn.args[0].add_attribute("nocapture")
    212             builder.call(fn, [mi])

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/llvmlite/ir/values.py in add_attribute(self, attr)
    738 
    739     def add_attribute(self, attr):
--> 740         self.attributes.add(attr)
    741 
    742 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/llvmlite/ir/values.py in add(self, name)
    534         if name not in self._known:
    535             raise ValueError('unknown attr {!r} for {}'.format(name, self))
--> 536         return super(AttributeSet, self).add(name)
    537 
    538     def __iter__(self):

KeyboardInterrupt: