def test_calculator_using_nonstd_input(rawinputfile):
    """
    Test Calculator using non-standard input records.
    """
    # check Calculator handling of raw, non-standard input data with no aging
    pol = Policy()
    pol.set_year(RAWINPUTFILE_YEAR)  # set policy params to input data year
    nonstd = Records(data=rawinputfile.name,
                     gfactors=None,  # keeps raw data unchanged
                     weights=None,
                     start_year=RAWINPUTFILE_YEAR)  # set raw input data year
    assert nonstd.array_length == RAWINPUTFILE_FUNITS
    calc = Calculator(policy=pol, records=nonstd,
                      sync_years=False)  # keeps raw data unchanged
    assert calc.current_year == RAWINPUTFILE_YEAR
    calc.calc_all()
    assert calc.weighted_total('e00200') == 0
    assert calc.total_weight() == 0
    varlist = ['RECID', 'MARS']
    dframe = calc.dataframe(varlist)
    assert isinstance(dframe, pd.DataFrame)
    assert dframe.shape == (RAWINPUTFILE_FUNITS, len(varlist))
    mars = calc.array('MARS')
    assert isinstance(mars, np.ndarray)
    assert mars.shape == (RAWINPUTFILE_FUNITS,)
    exp_iitax = np.zeros((nonstd.array_length,))
    assert np.allclose(calc.array('iitax'), exp_iitax)
    mtr_ptax, _, _ = calc.mtr(wrt_full_compensation=False)
    exp_mtr_ptax = np.zeros((nonstd.array_length,))
    exp_mtr_ptax.fill(0.153)
    assert np.allclose(mtr_ptax, exp_mtr_ptax)
def test_consumption_response(cps_subsample):
    consump = Consumption()
    mpc = 0.5
    consumption_response = {'MPC_e20400': {2013: mpc}}
    consump.update_consumption(consumption_response)
    # test incorrect call to response method
    with pytest.raises(ValueError):
        consump.response(list(), 1)
    # test correct call to response method
    rec = Records.cps_constructor(data=cps_subsample)
    pre = copy.deepcopy(rec.e20400)
    consump.response(rec, 1.0)
    post = rec.e20400
    actual_diff = post - pre
    expected_diff = np.ones(rec.array_length) * mpc
    assert np.allclose(actual_diff, expected_diff)
    # compute earnings mtr with no consumption response
    rec = Records.cps_constructor(data=cps_subsample)
    ided0 = copy.deepcopy(rec.e20400)
    calc0 = Calculator(policy=Policy(), records=rec, consumption=None)
    (mtr0_ptax, mtr0_itax, _) = calc0.mtr(variable_str='e00200p',
                                          wrt_full_compensation=False)
    assert np.allclose(calc0.array('e20400'), ided0)
    # compute earnings mtr with consumption response
    calc1 = Calculator(policy=Policy(), records=rec, consumption=consump)
    mtr1_ptax, mtr1_itax, _ = calc1.mtr(variable_str='e00200p',
                                        wrt_full_compensation=False)
    assert np.allclose(calc1.array('e20400'), ided0)
    # confirm that payroll mtr values are no different
    assert np.allclose(mtr1_ptax, mtr0_ptax)
    # confirm that all mtr with cons-resp are no greater than without cons-resp
    assert np.all(np.less_equal(np.around(mtr1_itax, decimals=5),
                                np.around(mtr0_itax, decimals=5)))
    # confirm that some mtr with cons-resp are less than without cons-resp
    assert np.any(np.less(mtr1_itax, mtr0_itax))
def test_calculator_using_nonstd_input(rawinputfile):
    # check Calculator handling of raw, non-standard input data with no aging
    pol = Policy()
    pol.set_year(RAWINPUTFILE_YEAR)  # set policy params to input data year
    nonstd = Records(
        data=rawinputfile.name,
        gfactors=None,  # keeps raw data unchanged
        weights=None,
        start_year=RAWINPUTFILE_YEAR)  # set raw input data year
    assert nonstd.dim == RAWINPUTFILE_FUNITS
    calc = Calculator(policy=pol, records=nonstd,
                      sync_years=False)  # keeps raw data unchanged
    assert calc.current_year == RAWINPUTFILE_YEAR
    calc.calc_all()
    assert calc.weighted_total('e00200') == 0
    assert calc.total_weight() == 0
    varlist = ['RECID', 'MARS']
    pdf = calc.dataframe(varlist)
    assert isinstance(pdf, pd.DataFrame)
    assert pdf.shape == (RAWINPUTFILE_FUNITS, len(varlist))
    mars = calc.array('MARS')
    assert isinstance(mars, np.ndarray)
    assert mars.shape == (RAWINPUTFILE_FUNITS, )
    exp_iitax = np.zeros((nonstd.dim, ))
    assert np.allclose(calc.records.iitax, exp_iitax)
    mtr_ptax, _, _ = calc.mtr(wrt_full_compensation=False)
    exp_mtr_ptax = np.zeros((nonstd.dim, ))
    exp_mtr_ptax.fill(0.153)
    assert np.allclose(mtr_ptax, exp_mtr_ptax)
def reform_results(rid, reform_dict, puf_data, reform_2017_law):
    """
    Return actual results of the reform specified by rid and reform_dict.
    """
    # pylint: disable=too-many-locals
    rec = Records(data=puf_data)
    # create baseline Calculator object, calc1
    pol = Policy()
    if reform_dict['baseline'] == '2017_law.json':
        pol.implement_reform(reform_2017_law)
    elif reform_dict['baseline'] == 'policy_current_law.json':
        pass
    else:
        msg = 'illegal baseline value {}'
        raise ValueError(msg.format(reform_dict['baseline']))
    calc1 = Calculator(policy=pol, records=rec, verbose=False)
    # create reform Calculator object, calc2
    start_year = reform_dict['start_year']
    reform = dict()
    for name, value in reform_dict['value'].items():
        reform[name] = {start_year: value}
    pol.implement_reform(reform)
    calc2 = Calculator(policy=pol, records=rec, verbose=False)
    # increment both Calculator objects to reform's start_year
    calc1.advance_to_year(start_year)
    calc2.advance_to_year(start_year)
    # calculate baseline and reform output for several years
    output_type = reform_dict['output_type']
    num_years = 4
    results = list()
    for _ in range(0, num_years):
        calc1.calc_all()
        baseline = calc1.array(output_type)
        calc2.calc_all()
        reform = calc2.array(output_type)
        diff = reform - baseline
        weighted_sum_diff = (diff * calc1.array('s006')).sum() * 1.0e-9
        results.append(weighted_sum_diff)
        calc1.increment_year()
        calc2.increment_year()
    # write actual results to actual_str
    actual_str = '{}'.format(rid)
    for iyr in range(0, num_years):
        actual_str += ',{:.1f}'.format(results[iyr])
    return actual_str
def test_ID_RealEstate_HC_vs_CRT(cps_subsample):
    """
    Test that a cap on all state, local, and foreign real estate tax deductions
    at 0 percent of AGI is equivalent to a complete haircut on the same real
    estate tax deductions.
    """
    rec = Records.cps_constructor(data=cps_subsample)
    # specify real estate complete haircut reform policy and Calculator object
    hc_reform = {2013: {'_ID_RealEstate_hc': [1.0]}}
    hc_policy = Policy()
    hc_policy.implement_reform(hc_reform)
    hc_calc = Calculator(policy=hc_policy, records=rec)
    hc_calc.calc_all()
    # specify AGI cap reform policy and Calculator object
    crt_reform = {2013: {'_ID_RealEstate_crt': [0.0]}}
    crt_policy = Policy()
    crt_policy.implement_reform(crt_reform)
    crt_calc = Calculator(policy=crt_policy, records=rec)
    crt_calc.calc_all()
    # compare calculated tax results generated by the two reforms
    assert np.allclose(hc_calc.array('payrolltax'),
                       crt_calc.array('payrolltax'))
    assert np.allclose(hc_calc.array('iitax'),
                       crt_calc.array('iitax'))
def test_ID_StateLocal_HC_vs_CRT(cps_subsample):
    """
    Test that a cap on state/local income and sales tax deductions at 0 percent
    of AGI is equivalent to a complete haircut on the same state/local tax
    deductions.
    """
    rec = Records.cps_constructor(data=cps_subsample)
    # specify state/local complete haircut reform policy and Calculator object
    hc_policy = Policy()
    hc_reform = {'ID_StateLocalTax_hc': {2013: 1.0}}
    hc_policy.implement_reform(hc_reform)
    hc_calc = Calculator(policy=hc_policy, records=rec)
    hc_calc.calc_all()
    # specify AGI cap reform policy and Calculator object
    crt_policy = Policy()
    crt_reform = {'ID_StateLocalTax_crt': {2013: 0.0}}
    crt_policy.implement_reform(crt_reform)
    crt_calc = Calculator(policy=crt_policy, records=rec)
    crt_calc.calc_all()
    # compare calculated tax results generated by the two reforms
    assert np.allclose(hc_calc.array('payrolltax'),
                       crt_calc.array('payrolltax'))
    assert np.allclose(hc_calc.array('iitax'),
                       crt_calc.array('iitax'))
def test_calculator_mtr(cps_subsample):
    """
    Test Calculator mtr method.
    """
    rec = Records.cps_constructor(data=cps_subsample)
    calcx = Calculator(policy=Policy(), records=rec)
    calcx.calc_all()
    combinedx = calcx.array('combined')
    c00100x = calcx.array('c00100')
    calc = Calculator(policy=Policy(), records=rec)
    recs_pre_e00200p = copy.deepcopy(calc.array('e00200p'))
    (mtr_ptx, mtr_itx, mtr_cmb) = calc.mtr(variable_str='e00200p',
                                           zero_out_calculated_vars=True)
    recs_post_e00200p = calc.array('e00200p')
    assert np.allclose(recs_post_e00200p, recs_pre_e00200p)
    assert np.allclose(calc.array('combined'), combinedx)
    assert np.allclose(calc.array('c00100'), c00100x)
    assert np.array_equal(mtr_cmb, mtr_ptx) is False
    assert np.array_equal(mtr_ptx, mtr_itx) is False
    with pytest.raises(ValueError):
        calc.mtr(variable_str='bad_income_type')
    (_, _, mtr_combined) = calc.mtr(variable_str='e00200s',
                                    calc_all_already_called=True)
    assert isinstance(mtr_combined, np.ndarray)
    (_, _, mtr_combined) = calc.mtr(variable_str='e00650',
                                    negative_finite_diff=True,
                                    calc_all_already_called=True)
    assert isinstance(mtr_combined, np.ndarray)
    (_, _, mtr_combined) = calc.mtr(variable_str='e00900p',
                                    calc_all_already_called=True)
    assert isinstance(mtr_combined, np.ndarray)
    (_, _, mtr_combined) = calc.mtr(variable_str='e01700',
                                    calc_all_already_called=True)
    assert isinstance(mtr_combined, np.ndarray)
    (_, _, mtr_combined) = calc.mtr(variable_str='e26270',
                                    calc_all_already_called=True)
    assert isinstance(mtr_combined, np.ndarray)
    (_, _, mtr_combined) = calc.mtr(variable_str='e00200p',
                                    calc_all_already_called=True)
    assert np.allclose(mtr_combined, mtr_cmb)
    assert np.allclose(calc.array('combined'), combinedx)
    assert np.allclose(calc.array('c00100'), c00100x)
def test_benefits(tests_path, cps_fullsample):
    """
    Test CPS benefits.
    """
    # pylint: disable=too-many-locals
    benefit_names = ['ssi', 'mcare', 'mcaid', 'snap', 'wic',
                     'tanf', 'vet', 'housing']
    # write benefits_actual.csv file
    recs = Records.cps_constructor(data=cps_fullsample)
    start_year = recs.current_year
    calc = Calculator(policy=Policy(), records=recs, verbose=False)
    assert calc.current_year == start_year
    year_list = list()
    bname_list = list()
    benamt_list = list()
    bencnt_list = list()
    benavg_list = list()
    for year in range(start_year, Policy.LAST_BUDGET_YEAR + 1):
        calc.advance_to_year(year)
        size = calc.array('XTOT')
        wght = calc.array('s006')
        # compute benefit aggregate amounts and head counts and average benefit
        # (head counts include all members of filing unit receiving a benefit,
        #  which means benavg is f.unit benefit amount divided by f.unit size)
        for bname in benefit_names:
            ben = calc.array('{}_ben'.format(bname))
            benamt = round((ben * wght).sum() * 1e-9, 3)
            bencnt = round((size[ben > 0] * wght[ben > 0]).sum() * 1e-6, 3)
            benavg = round(benamt / bencnt, 1)
            year_list.append(year)
            bname_list.append(bname)
            benamt_list.append(benamt)
            bencnt_list.append(bencnt)
            benavg_list.append(benavg)
    adict = {'year': year_list,
             'bname': bname_list,
             'benamt': benamt_list,
             'bencnt': bencnt_list,
             'benavg': benavg_list}
    adf = pd.DataFrame(data=adict,
                       columns=['year', 'bname', 'benamt', 'bencnt', 'benavg'])
    ben_act_path = os.path.join(tests_path, 'benefits_actual.csv')
    adf.to_csv(ben_act_path, index=False)
    # read benefits_expect.csv file
    ben_exp_path = os.path.join(tests_path, 'benefits_expect.csv')
    edf = pd.read_csv(ben_exp_path)
    # compare benefit information
    atol = 0.0001
    rtol = 0.0
    diffs = False
    for col in ['benamt', 'bencnt', 'benavg']:
        if not np.allclose(adf[col], edf[col], atol=atol, rtol=rtol):
            diffs = True
    if diffs:
        msg = 'CPS BENEFITS RESULTS DIFFER\n'
        msg += '-------------------------------------------------\n'
        msg += '--- NEW RESULTS IN benefits_actual.txt FILE   ---\n'
        msg += '--- if new OK, copy benefits_actual.txt to    ---\n'
        msg += '---                 benefits_expect.txt       ---\n'
        msg += '---            and rerun test.                ---\n'
        msg += '-------------------------------------------------\n'
        raise ValueError(msg)
    else:
        os.remove(ben_act_path)
def test_mtr(tests_path, puf_path):
    """
    Test Tax-Calculator marginal tax rates with no policy reform using puf.csv

    Compute histograms for each marginal tax rate income type using
    sample input from the puf.csv file and writing output to a string,
    which is then compared for differences with EXPECTED_MTR_RESULTS.
    """
    # pylint: disable=too-many-locals,too-many-statements
    assert len(PTAX_MTR_BIN_EDGES) == len(ITAX_MTR_BIN_EDGES)
    # construct actual results string, res
    res = ''
    if MTR_NEG_DIFF:
        res += 'MTR computed using NEGATIVE finite_diff '
    else:
        res += 'MTR computed using POSITIVE finite_diff '
    res += 'for tax year {}\n'.format(MTR_TAX_YEAR)
    # create a Policy object (clp) containing current-law policy parameters
    clp = Policy()
    clp.set_year(MTR_TAX_YEAR)
    # create a Records object (puf) containing puf.csv input records
    puf = Records(data=puf_path)
    recid = puf.RECID  # pylint: disable=no-member
    # create a Calculator object using clp policy and puf records
    calc = Calculator(policy=clp, records=puf)
    res += '{} = {}\n'.format('Total number of data records', puf.array_length)
    res += 'PTAX mtr histogram bin edges:\n'
    res += '     {}\n'.format(PTAX_MTR_BIN_EDGES)
    res += 'ITAX mtr histogram bin edges:\n'
    res += '     {}\n'.format(ITAX_MTR_BIN_EDGES)
    variable_header = 'PTAX and ITAX mtr histogram bin counts for'
    # compute marginal tax rate (mtr) histograms for each mtr variable
    for var_str in Calculator.MTR_VALID_VARIABLES:
        zero_out = (var_str == 'e01400')
        (mtr_ptax, mtr_itax, _) = calc.mtr(variable_str=var_str,
                                           negative_finite_diff=MTR_NEG_DIFF,
                                           zero_out_calculated_vars=zero_out,
                                           wrt_full_compensation=False)
        if zero_out:
            # check that calculated variables are consistent
            assert np.allclose((calc.array('iitax') +
                                calc.array('payrolltax')),
                               calc.array('combined'))
            assert np.allclose((calc.array('ptax_was') +
                                calc.array('setax') +
                                calc.array('ptax_amc')),
                               calc.array('payrolltax'))
            assert np.allclose(calc.array('c21060') - calc.array('c21040'),
                               calc.array('c04470'))
            assert np.allclose(calc.array('taxbc') + calc.array('c09600'),
                               calc.array('c05800'))
            assert np.allclose((calc.array('c05800') +
                                calc.array('othertaxes') -
                                calc.array('c07100')),
                               calc.array('c09200'))
            assert np.allclose(calc.array('c09200') - calc.array('refund'),
                               calc.array('iitax'))
        if var_str == 'e00200s':
            # only MARS==2 filing units have valid MTR values
            mtr_ptax = mtr_ptax[calc.array('MARS') == 2]
            mtr_itax = mtr_itax[calc.array('MARS') == 2]
        res += '{} {}:\n'.format(variable_header, var_str)
        res += mtr_bin_counts(mtr_ptax, PTAX_MTR_BIN_EDGES, recid)
        res += mtr_bin_counts(mtr_itax, ITAX_MTR_BIN_EDGES, recid)
    # check for differences between actual and expected results
    mtrres_path = os.path.join(tests_path, 'pufcsv_mtr_expect.txt')
    with open(mtrres_path, 'r') as expected_file:
        txt = expected_file.read()
    expected_results = txt.rstrip('\n\t ') + '\n'  # cleanup end of file txt
    if nonsmall_diffs(res.splitlines(True), expected_results.splitlines(True)):
        new_filename = '{}{}'.format(mtrres_path[:-10], 'actual.txt')
        with open(new_filename, 'w') as new_file:
            new_file.write(res)
        msg = 'PUFCSV MTR RESULTS DIFFER\n'
        msg += '-------------------------------------------------\n'
        msg += '--- NEW RESULTS IN pufcsv_mtr_actual.txt FILE ---\n'
        msg += '--- if new OK, copy pufcsv_mtr_actual.txt to  ---\n'
        msg += '---                 pufcsv_mtr_expect.txt     ---\n'
        msg += '---            and rerun test.                ---\n'
        msg += '-------------------------------------------------\n'
        raise ValueError(msg)
def test_itemded_component_amounts(year, cvname, hcname, puf_fullsample):
    """
    Check that all c04470 components are adjusted to reflect the filing
    unit's standard-vs-itemized-deduction decision.  Check for 2018
    (when current law has no Pease phaseout of itemized deductions and
    already has complete haircuts for Casualty and Miscellaneous deductions)
    and 2017 (when current law has a Pease phaseout of itemized deductions
    and has no haircuts).  The calcfunctions.py code makes no attempt to
    adjust the components for the effects of Pease-like phaseout or any other
    type of limitation on total itemized deductions, so the pre-2018 tests
    here use c21060, instead of c04470, as the itemized deductions total.
    """
    # pylint: disable=too-many-locals
    recs = Records(data=puf_fullsample)
    # policy1 such that everybody itemizes deductions and all are allowed
    policy1 = Policy()
    reform1 = {
        'STD_Aged': {year: [0.0, 0.0, 0.0, 0.0, 0.0]},
        'STD': {year: [0.0, 0.0, 0.0, 0.0, 0.0]}
    }
    policy1.implement_reform(reform1)
    assert not policy1.parameter_errors
    # policy2 such that everybody itemizes deductions but one is disallowed
    policy2 = Policy()
    reform2 = {
        'STD_Aged': {year: [0.0, 0.0, 0.0, 0.0, 0.0]},
        'STD': {year: [0.0, 0.0, 0.0, 0.0, 0.0]},
        hcname: {year: 1.0}
    }
    policy2.implement_reform(reform2)
    assert not policy2.parameter_errors
    # compute tax liability in specified year
    calc1 = Calculator(policy=policy1, records=recs, verbose=False)
    calc1.advance_to_year(year)
    calc1.calc_all()
    calc2 = Calculator(policy=policy2, records=recs, verbose=False)
    calc2.advance_to_year(year)
    calc2.calc_all()
    # confirm that nobody is taking the standard deduction
    assert np.allclose(calc1.array('standard'), 0.)
    assert np.allclose(calc2.array('standard'), 0.)
    # calculate different in total itemized deductions
    if year == 2017:
        # pre-Pease limitation total itemized deductions
        itmded1 = calc1.weighted_total('c21060') * 1e-9
        itmded2 = calc2.weighted_total('c21060') * 1e-9
    elif year == 2018:
        # total itemized deductions (no Pease-like limitation)
        itmded1 = calc1.weighted_total('c04470') * 1e-9
        itmded2 = calc2.weighted_total('c04470') * 1e-9
    else:
        raise ValueError('illegal year value = {}'.format(year))
    difference_in_total_itmded = itmded1 - itmded2
    # calculate itemized component amount
    component_amt = calc1.weighted_total(cvname) * 1e-9
    # confirm that component amount is equal to difference in total deductions
    if year == 2017 and cvname == 'c19700':
        atol = 0.009
    elif year == 2017 and cvname == 'c19200':
        atol = 0.010
    else:
        atol = 0.00001
    if not np.allclose(component_amt, difference_in_total_itmded, atol=atol):
        txt = '\n{}={:.3f}  !=  {:.3f}=difference_in_total_itemized_deductions'
        msg = txt.format(cvname, component_amt, difference_in_total_itmded)
        raise ValueError(msg)