From 3ec4d6851af8cbea9b551ddf71fbb41963fc4752 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 5 Oct 2026 13:42:29 -0400 Subject: [PATCH 1/4] Test a Form 4952 election with section 911(f) stacking end to end Neither the Form 4952 line 4g election test (#9781) nor the section 911(f) stacking test (#9761) covered households with both. The new Hypothesis tests check, on generated households with an election and an exclusion: - closed forms for net capital gain and the section 911 amounts after the election (min(D, N), min(N, T), min(D, N, T)); - that the Schedule D Tax Worksheet route and the section 1(h) route give the same capital gain excess and reduced amounts, regular tax and AMT; - that a zero election or exclusion matches the situation without that input, bit for bit; - monotonicity in both amounts, with the intended 12%/15% overlap exception in the election bounded from the parameters; - the regular tax and AMT against the transcribed 2025 Schedule D Tax Worksheet, Foreign Earned Income Tax Worksheet and Form 6251. The election and the exclusion can change taxable income through the choice to itemize (an AMT filer may itemize below the standard deduction), so amounts that depend on taxable income are compared only where that choice holds; income_tax, which it minimizes, is compared across it. Co-Authored-By: Claude Opus 5.5 --- ...form-4952-911-combined-property.changed.md | 1 + .../test_form_4952_section_911_interaction.py | 1011 +++++++++++++++++ 2 files changed, 1012 insertions(+) create mode 100644 changelog.d/form-4952-911-combined-property.changed.md create mode 100644 policyengine_us/tests/test_form_4952_section_911_interaction.py diff --git a/changelog.d/form-4952-911-combined-property.changed.md b/changelog.d/form-4952-911-combined-property.changed.md new file mode 100644 index 00000000000..768e48f661a --- /dev/null +++ b/changelog.d/form-4952-911-combined-property.changed.md @@ -0,0 +1 @@ +Test a Form 4952 line 4g election together with the section 911(f) stacked tax on generated households. diff --git a/policyengine_us/tests/test_form_4952_section_911_interaction.py b/policyengine_us/tests/test_form_4952_section_911_interaction.py new file mode 100644 index 00000000000..b3817ed611b --- /dev/null +++ b/policyengine_us/tests/test_form_4952_section_911_interaction.py @@ -0,0 +1,1011 @@ +"""Property tests for a Form 4952 election together with section 911(f). + +A taxpayer may elect, on Form 4952 line 4g, to treat net capital gain and +qualified dividends as investment income (26 U.S.C. 163(d)(4)(B)); the +elected amounts lose the capital gains rates (section 1(h)(2) and +1(h)(11)(D)(i)). A taxpayer who excludes foreign earned income under section +911(a) pays tax on the rest at the rates it would bear on top of the excluded +amount, and any capital gain excess is taken from the gains first (section +911(f)). test_form_4952_election_worksheet.py checks the election without an +exclusion, and test_section_911_tax_stacking.py the exclusion without an +election. This file checks households with both. + +On a return the two meet in the Schedule D Tax Worksheet, which a filer with +an amount on Form 4952 line 4g must use (2025 Instructions for Schedule D, +page 15). The Foreign Earned Income Tax Worksheet measures the capital gain +excess from worksheet line 10 and takes it from line 9 before line 6 (2025 +Instructions for Form 1040, page 37; the AMT does the same with Form 6251 +line 6, 2025 Instructions for Form 6251, Part III, "Form 2555"). The model +reaches these amounts by two routes: the worksheet (dwks09, dwks10, +schedule_d_tax_worksheet_after_capital_gain_excess) and section 1(h) +(net_capital_gain and the section_911_* variables). + +Write G for the gain of section 1222(11) (net long-term gain, with capital +gain distributions, less net short-term loss, if positive), D for qualified +dividends, E for the election, X for the exclusion, T for taxable income and +N = max(0, G - E) + max(0, D - max(0, E - G)), the net capital gain the +election leaves. The properties, for every generated household: + +1. Closed forms. net_capital_gain and dwks10 are N; worksheet line 6 is + min(D, N). Without an exclusion the section 911 amounts are the originals. + With one, the capital gain excess is max(0, N - T), + section_911_net_capital_gain is min(N, T) and + section_911_qualified_dividend_income is min(D, N, T). Without an excess, + section_911_qualified_dividend_income is min(D, net_capital_gain), bit for + bit. The gain other than dividends plus those dividends is + section_911_net_capital_gain. +2. The two routes agree. The worksheet's capital gain excess, line 10, line + 9, line 10 less line 9, line 13 and Schedule D line 19 equal the section + 1(h) excess, net capital gain, gain other than dividends, qualified + dividends, adjusted net capital gain and unrecaptured section 1250 gain, + and lines 6 and 9 after the excess equal modifications 1 and 2 of the + Foreign Earned Income Tax Worksheet footnote applied to the transcribed + worksheet. The AMT excess is max(0, N - max(0, Form 6251 line 6)). +3. Zero amounts change nothing. A household that elects nothing gets the + same results, bit for bit, as when the situation has no election input + (the situation of test_section_911_tax_stacking.py), and one that excludes + nothing the same as when it has no exclusion input (that of + test_form_4952_election_worksheet.py), whatever the other households in + the batch elect or exclude. With no capital gain excess, every amount that + section 911(f) replaces equals its original, bit for bit. +4. Monotonicity. E and X change taxable income only through the choice to + itemize (see assert_monotone). As E rises, net_capital_gain and dwks10 + never rise, and net capital gain falls by at most the extra amount + elected. While the choice to itemize holds, the section 911 net capital + gain and qualified dividends and both capital gain excesses never rise + either. As X rises the regular tax and income_tax_before_credits never + fall while the choice holds, and stacking never lowers them. income_tax, + which the choice minimizes, never falls as X rises, whatever the choice, + for households without dependents. + The tax does not always rise with E: from the 0% capital gains threshold + to the top of the 12% bracket (2025: $48,350 to $48,475 single), gain is + taxed at 15% and ordinary income at 12%, so a dollar elected there lowers + the tax by 3 cents, on the return as in the model + (test_election_can_lower_the_tax_where_15_percent_exceeds_12). The tax + (each of the three) falls by at most the 3 points times the width of that + band ($3.75 single, $7.50 joint in 2025), which `rate_overlap` computes + from the parameters. +5. Worksheet lines. dwks10 equals net_capital_gain exactly for whole-dollar + amounts and within single-precision rounding for amounts with cents; the + model's lines 6, 9 and 10 equal the transcription in + test_form_4952_election_worksheet.py. +6. End to end, against the 2025 forms. Schedule D Tax Worksheet lines 2 to + 10 (transcribed in test_form_4952_election_worksheet.py) feed the Foreign + Earned Income Tax Worksheet and Form 6251 line 7 (transcribed in + test_section_911_tax_stacking.py). With Schedule D lines 18 and 19 zero, + Schedule D Tax Worksheet lines 14 to 47 are the Qualified Dividends and + Capital Gain Tax Worksheet with worksheet line 6 for its line 2 and line 9 + for its line 3, and Form 6251 line 27 equals line 20. The model's regular + tax and AMT match. + +Amounts are whole dollars below 2**24 except where a household is drawn with +cents. Whole-dollar households without itemized deductions have whole-dollar +taxable income, so for them the closed forms hold exactly; the others are +checked within single-precision rounding. + +The scope limits of test_section_911_tax_stacking.py apply to the tax +comparisons (properties 4 and 6), which therefore use households without +28-percent rate gain or unrecaptured section 1250 gain. The model omits the +section 1(h)(1) cap at the tax on all taxable income at ordinary rates. It +uses the 26%/28% breakpoint of other filers for married filing separately on +Form 6251 line 18. And capital_gains_tax taxes 28-percent rate gain at 28% in +full, even where it exceeds taxable income or the net capital gain an election +leaves (Schedule D Tax Worksheet line 12 limits it to line 9): $10,000 of +collectibles gain and no taxable income gives $2,800 of tax. The households +are 45, so the exclusion does not reach the senior deduction. +""" + +import numpy as np +import pytest +from policyengine_core.periods import period as make_period + +from policyengine_us import Simulation +from policyengine_us.tests.test_form_4952_election_worksheet import ( + schedule_d_tax_worksheet, +) +from policyengine_us.tests.test_section_911_tax_stacking import ( + STATUSES, + foreign_earned_income_tax_worksheet, + form_6251_line_7, + qualified_dividends_and_capital_gain_tax_worksheet, + reduce_by_capital_gain_excess, + tax_rate_schedule, + tolerance, +) +from policyengine_us.variables.gov.irs.tax.federal_income.foreign_earned_income_exclusion.schedule_d_tax_worksheet_after_capital_gain_excess import ( + schedule_d_tax_worksheet_after_capital_gain_excess, +) +from policyengine_us.variables.gov.irs.tax.federal_income.foreign_earned_income_exclusion.section_911_net_capital_gain_other_than_dividends import ( + section_911_net_capital_gain_other_than_dividends, +) + +# Hypothesis is a dev extra; skip rather than fail collection without it. +hypothesis = pytest.importorskip("hypothesis") +st = pytest.importorskip("hypothesis.strategies") + +YEARS = [2018, 2022, 2025, 2026] + +# --------------------------------------------------------------------------- +# The law, in closed form. +# --------------------------------------------------------------------------- + + +def section_1222_gain(h): + """Net long-term capital gain (capital gain distributions included) over + net short-term capital loss, if positive: 26 U.S.C. 1222(11), before + section 1(h)(11). It is also Form 4952 line 4e, worksheet line 4.""" + long_term = h["long_term"] + h["distributions"] + return max(0, long_term - max(0, -h["short_term"])) + + +def net_capital_gain_after_election(h): + """The election comes first from the gain and then from qualified + dividends (2025 Form 4952, line 4g instructions).""" + gain = section_1222_gain(h) + dividends = max(0, h["dividends"]) + return max(0, gain - h["election"]) + max( + 0, dividends - max(0, h["election"] - gain) + ) + + +def rate_overlap(parameters, status): + """How far the regular tax can fall when more gain is taxed at ordinary + rates: the integral over income of the excess, where positive, of the + capital gains rate over the ordinary rate. Moving a dollar of gain into + ordinary income at income level x changes the tax by the ordinary rate + less the capital gains rate at x.""" + bracket = parameters.gov.irs.income.bracket + gains = parameters.gov.irs.capital_gains + count = len(list(bracket.rates.__iter__())) + ordinary = [ + (float(bracket.thresholds[str(i)][status]), float(bracket.rates[str(i)])) + for i in range(1, count + 1) + ] + capital_gains = [ + (float(gains.thresholds["1"][status]), float(gains.rates["1"])), + (float(gains.thresholds["2"][status]), float(gains.rates["2"])), + (np.inf, float(gains.rates["3"])), + ] + + def rate(schedule, x): + return next(r for top, r in schedule if x < top) + + edges = sorted({0.0} | {top for top, _ in ordinary + capital_gains}) + overlap = 0.0 + for bottom, top in zip(edges, edges[1:]): + middle = bottom + 1 if np.isinf(top) else (bottom + top) / 2 + excess = rate(capital_gains, middle) - rate(ordinary, middle) + if excess > 0: + assert np.isfinite(top), status + overlap += excess * (top - bottom) + return overlap + + +# --------------------------------------------------------------------------- +# The model. +# --------------------------------------------------------------------------- + +OUTPUTS = [ + "taxable_income", + "net_capital_gain", + "dividend_income_reduced_by_investment_income", + "dwks09", + "dwks10", + "dwks13", + "adjusted_net_capital_gain", + "unrecaptured_section_1250_gain", + "capital_gains_28_percent_rate_gain", + "foreign_earned_income_exclusion", + "section_911_capital_gain_excess", + "section_911_net_capital_gain", + "section_911_qualified_dividend_income", + "section_911_adjusted_net_capital_gain", + "section_911_28_percent_rate_gain", + "section_911_unrecaptured_section_1250_gain", + "taxable_income_plus_section_911_exclusion", + "amt_income_less_exemptions", + "amt_income_less_exemptions_plus_section_911_exclusion", + "amt_section_911_capital_gain_excess", + "income_tax_main_rates", + "capital_gains_tax", + "regular_tax_before_credits", + "alternative_minimum_tax", + "income_tax_before_credits", + "tax_unit_itemizes", + "income_tax", +] + + +def split(amount, joint): + """The head's and the spouse's shares. Joint filers split dividends and + the election, so the model has to add them over the tax unit.""" + if not joint: + return amount, 0 + spouse = amount // 2 if float(amount).is_integer() else round(amount / 2, 2) + return amount - spouse, spouse + + +def build_situation(households, year, with_election=True, with_exclusion=True): + people, tax_units, marital_units = {}, {}, {} + groups = {"households": {}, "spm_units": {}, "families": {}} + for i, h in enumerate(households): + joint = h["status"] == "JOINT" + dividends = split(h["dividends"], joint) + election = split(h["election"], joint) + head = f"head_{i}" + people[head] = { + "age": {year: 45}, + "employment_income": {year: h["wages"]}, + "qualified_dividend_income": {year: dividends[0]}, + "long_term_capital_gains": {year: h["long_term"]}, + "short_term_capital_gains": {year: h["short_term"]}, + "non_sch_d_capital_gains": {year: h["distributions"]}, + "real_estate_taxes": {year: h["real_estate_taxes"]}, + "deductible_mortgage_interest": {year: h["mortgage_interest"]}, + "charitable_cash_donations": {year: h["charitable_gifts"]}, + } + if with_election: + people[head]["investment_income_elected_form_4952"] = {year: election[0]} + members = [head] + marital_units[f"marital_unit_{i}"] = {"members": [head]} + if joint: + spouse = f"spouse_{i}" + people[spouse] = { + "age": {year: 45}, + "qualified_dividend_income": {year: dividends[1]}, + } + if with_election: + people[spouse]["investment_income_elected_form_4952"] = { + year: election[1] + } + members.append(spouse) + marital_units[f"marital_unit_{i}"]["members"].append(spouse) + if h["status"] == "HEAD_OF_HOUSEHOLD": + # Old enough to be a dependent without a child tax credit. + child = f"child_{i}" + people[child] = {"age": {year: 17}} + members.append(child) + marital_units[f"marital_unit_{i}_child"] = {"members": [child]} + tax_units[f"tax_unit_{i}"] = { + "members": members, + # Set for every tax unit, as in test_section_911_tax_stacking.py. + "filing_status": {year: h["status"]}, + "unrecaptured_section_1250_gain": {year: h["section_1250"]}, + "capital_gains_28_percent_rate_gain": {year: h["rate_gain_28"]}, + } + if with_exclusion: + tax_units[f"tax_unit_{i}"]["foreign_earned_income_exclusion"] = { + year: h["exclusion"] + } + groups["households"][f"household_{i}"] = { + "members": members, + "state_code": {year: "TX"}, + } + groups["spm_units"][f"spm_unit_{i}"] = {"members": members} + groups["families"][f"family_{i}"] = {"members": members} + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + **groups, + } + + +def calculate(households, year, with_election=True, with_exclusion=True): + simulation = Simulation( + situation=build_situation(households, year, with_election, with_exclusion) + ) + results = {v: np.asarray(simulation.calculate(v, year)) for v in OUTPUTS} + results["filing_status"] = simulation.calculate( + "filing_status", year + ).decode_to_str() + results["qualified_dividends"] = np.asarray( + simulation.calculate("qualified_dividend_income", year, map_to="tax_unit") + ) + results["regular_tax"] = ( + results["income_tax_main_rates"] + results["capital_gains_tax"] + ) + tax_unit = simulation.populations["tax_unit"] + period = make_period(year) + results["gain_other_than_dividends"] = np.asarray( + section_911_net_capital_gain_other_than_dividends(tax_unit, period) + ) + # The worksheet refigured for the regular tax (taxable income) and the + # AMT (Form 6251 line 6). + for prefix, base in [ + ("worksheet", "taxable_income"), + ("amt_worksheet", "amt_income_less_exemptions"), + ]: + worksheet = schedule_d_tax_worksheet_after_capital_gain_excess( + tax_unit, period, results[base] + ) + for line in worksheet._fields: + results[f"{prefix}_{line}"] = np.asarray(getattr(worksheet, line)) + parameters = simulation.tax_benefit_system.parameters(period.start) + overlaps = { + status: rate_overlap(parameters, str(status)) + for status in set(results["filing_status"]) + } + results["rate_overlap"] = np.array( + [overlaps[status] for status in results["filing_status"]] + ) + return results + + +def column(households, key): + return np.array([h[key] for h in households], dtype=float) + + +def rounding(scale): + """Four units in the last place of single precision at `scale`: the + rounding of a few stored amounts of that size.""" + return 4 * np.spacing(np.abs(np.asarray(scale)).astype(np.float32)).astype(float) + + +def assert_close(actual, expected, exact, scale, label): + """Equal where `exact`, within single-precision rounding elsewhere.""" + actual = np.asarray(actual, dtype=float) + expected = np.asarray(expected, dtype=float) + slack = np.where(exact, 0.0, rounding(scale)) + bad = np.flatnonzero(np.abs(actual - expected) > slack) + assert not bad.size, (label, bad[:5], actual[bad[:5]], expected[bad[:5]]) + + +# --------------------------------------------------------------------------- +# The properties. +# --------------------------------------------------------------------------- + + +def assert_gain_invariants(households, law): + """Properties 1, 2, 3 (no excess) and 5 for every household.""" + dividends = np.maximum(0, column(households, "dividends")) + net_gain = np.array([net_capital_gain_after_election(h) for h in households]) + excluded = column(households, "exclusion") + excludes = excluded > 0 + taxable_income = law["taxable_income"].astype(float) + taxable_excess = law["amt_income_less_exemptions"].astype(float) + cents = np.array([h["cents"] for h in households]) + scale = ( + sum( + np.abs(column(households, key)) + for key in [ + "long_term", + "short_term", + "distributions", + "dividends", + "election", + "section_1250", + "rate_gain_28", + ] + ) + + np.abs(taxable_income) + + np.abs(taxable_excess) + ) + # Whole-dollar amounts below 2**24 are exact in single precision, and so + # is every sum and difference of them the model forms. + exact = ~cents & (taxable_income == np.round(taxable_income)) & (scale < 2**24) + exact_amt = exact & (taxable_excess == np.round(taxable_excess)) + + def check(variable, expected, mask=exact): + assert_close(law[variable], expected, mask, scale, variable) + + # Stored in single precision, like every input. + assert np.array_equal( + law["foreign_earned_income_exclusion"], excluded.astype(np.float32) + ) + + # 5. The worksheet transcription, and dwks10 against net capital gain. + lines = np.array([schedule_d_tax_worksheet(h) for h in households], dtype=float) + assert_close(lines[:, 2], net_gain, ~cents, scale, "closed form") + check("dividend_income_reduced_by_investment_income", lines[:, 0]) + check("dwks09", lines[:, 1]) + check("dwks10", lines[:, 2]) + assert_close(law["dwks10"], law["net_capital_gain"], exact, scale, "dwks10") + + # 1. Closed forms. + check("net_capital_gain", net_gain) + check( + "dividend_income_reduced_by_investment_income", np.minimum(dividends, net_gain) + ) + floor_income = np.maximum(0, taxable_income) + check( + "section_911_capital_gain_excess", + np.where(excludes, np.maximum(0, net_gain - taxable_income), 0), + ) + check( + "section_911_net_capital_gain", + np.where(excludes, np.minimum(net_gain, floor_income), net_gain), + ) + check( + "section_911_qualified_dividend_income", + np.where( + excludes, + np.minimum(np.minimum(dividends, net_gain), floor_income), + np.minimum(dividends, net_gain), + ), + ) + # Without an excess: qualified dividends after the election are the + # smaller of the dividends and net capital gain, bit for bit. The tax + # unit's dividends are summed in double precision, as the model sums + # them, and the result is stored in single precision. + no_excess = law["section_911_capital_gain_excess"] == 0 + after_election = np.minimum( + np.maximum(0, law["qualified_dividends"]), + law["net_capital_gain"], + ).astype(np.float32) + assert np.array_equal( + law["section_911_qualified_dividend_income"][no_excess], + after_election[no_excess], + ) + # Section 911(f)(2)(A)(i)-(ii): the gain other than dividends and the + # dividends that remain make up the reduced net capital gain. + assert_close( + law["gain_other_than_dividends"] + law["section_911_qualified_dividend_income"], + law["section_911_net_capital_gain"], + exact, + scale, + "reduced gain plus dividends", + ) + + # 2. The worksheet route equals the section 1(h) route. + for line, variable in [ + ("capital_gain_excess", "section_911_capital_gain_excess"), + ("line_10", "section_911_net_capital_gain"), + ("line_13", "section_911_adjusted_net_capital_gain"), + ( + "unrecaptured_section_1250_gain", + "section_911_unrecaptured_section_1250_gain", + ), + ]: + assert_close( + law[f"worksheet_{line}"], law[variable], exact, scale, (line, variable) + ) + assert_close( + law["worksheet_line_9"], + law["gain_other_than_dividends"], + exact, + scale, + "line_9", + ) + assert_close( + law["worksheet_line_10"] - law["worksheet_line_9"], + law["section_911_qualified_dividend_income"], + exact, + scale, + "line_10 - line_9", + ) + # Modifications 1 and 2 of the Foreign Earned Income Tax Worksheet + # footnote, on the transcribed worksheet. + reduced = np.array( + [ + reduce_by_capital_gain_excess( + max(0, line_10 - max(0, income)) if x > 0 else 0, line_6, line_9 + ) + for (line_6, line_9, line_10), income, x in zip( + lines, taxable_income, excluded + ) + ] + ) + assert_close( + law["worksheet_line_10"] - law["worksheet_line_9"], + reduced[:, 0], + exact, + scale, + "modification 2", + ) + assert_close(law["worksheet_line_9"], reduced[:, 1], exact, scale, "modification 1") + # The AMT excess is measured from Form 6251 line 6. + floor_excess = np.maximum(0, taxable_excess) + for variable in [ + "amt_section_911_capital_gain_excess", + "amt_worksheet_capital_gain_excess", + ]: + assert_close( + law[variable], + np.where(excludes, np.maximum(0, net_gain - floor_excess), 0), + exact_amt, + scale, + variable, + ) + assert_close( + law["amt_worksheet_line_10"], + np.where(excludes, np.minimum(net_gain, floor_excess), net_gain), + exact_amt, + scale, + "amt line_10", + ) + + # 3. Without an excess, each amount that section 911(f) replaces equals + # its original, bit for bit; without an exclusion so do the stacked bases. + # The worksheet measures its own excess, from line 10 (with cents the two + # measures can differ by rounding). + no_worksheet_excess = law["worksheet_capital_gain_excess"] == 0 + for variable, original, mask in [ + ("section_911_net_capital_gain", "net_capital_gain", no_excess), + ( + "section_911_adjusted_net_capital_gain", + "adjusted_net_capital_gain", + no_excess, + ), + ( + "section_911_28_percent_rate_gain", + "capital_gains_28_percent_rate_gain", + no_excess, + ), + ( + "section_911_unrecaptured_section_1250_gain", + "unrecaptured_section_1250_gain", + no_excess, + ), + ("worksheet_line_9", "dwks09", no_worksheet_excess), + ("worksheet_line_10", "dwks10", no_worksheet_excess), + ("worksheet_line_13", "dwks13", no_worksheet_excess), + ( + "worksheet_unrecaptured_section_1250_gain", + "unrecaptured_section_1250_gain", + no_worksheet_excess, + ), + ]: + assert np.array_equal(law[variable][mask], law[original][mask]), variable + for stacked, original in [ + ("taxable_income_plus_section_911_exclusion", "taxable_income"), + ( + "amt_income_less_exemptions_plus_section_911_exclusion", + "amt_income_less_exemptions", + ), + ]: + assert np.array_equal(law[stacked][~excludes], law[original][~excludes]) + for variable in [ + "section_911_capital_gain_excess", + "amt_section_911_capital_gain_excess", + ]: + assert not law[variable][~excludes].any(), variable + + +def plain(households): + """Households inside the scope of the tax comparisons (see the module + docstring): no 28-percent rate or unrecaptured section 1250 gain.""" + return (column(households, "section_1250") == 0) & ( + column(households, "rate_gain_28") == 0 + ) + + +def assert_matches_2025_forms(households, law): + """Property 6: the model against the transcribed 2025 forms.""" + for i, h in enumerate(households): + if h["section_1250"] or h["rate_gain_28"]: + continue + status = law["filing_status"][i] + assert status == h["status"], (i, status) + taxable_income = float(law["taxable_income"][i]) + # Schedule D Tax Worksheet lines 6 and 9 take the place of lines 2 + # and 3 of the Qualified Dividends and Capital Gain Tax Worksheet. + line_6, line_9, _ = schedule_d_tax_worksheet(h) + line_6_feitw, line_6_capped, ordinary_income = ( + foreign_earned_income_tax_worksheet( + taxable_income, h["exclusion"], line_6, line_9, status + ) + ) + stacked_income = taxable_income + h["exclusion"] + slack = tolerance(stacked_income) + regular_tax = float(law["regular_tax"][i]) + assert regular_tax == pytest.approx(line_6_feitw, abs=slack), (i, h) + # The line 47 cap, which the model's section 1(h) formulas omit, + # binds by at most the rate overlap. + cap_gap = regular_tax - line_6_capped + assert -slack <= cap_gap <= law["rate_overlap"][i] + slack, (i, h) + gain = section_1222_gain(h) + if status == "SEPARATE" and h["dividends"] + gain > 0: + # Form 6251 line 18 (see the module docstring). + continue + taxable_excess = float(law["amt_income_less_exemptions"][i]) + line_7 = form_6251_line_7( + taxable_excess, + h["exclusion"], + line_6, + line_9, + ordinary_income, + status, + ) + amt = max(0, line_7 - line_6_feitw) + assert float(law["alternative_minimum_tax"][i]) == pytest.approx( + amt, abs=tolerance(stacked_income, taxable_excess) + ), (i, h) + + +VARIANTS = ["law", "more_elected", "more_excluded", "unstacked", "no_election"] + + +def with_variants(households): + """The households, then each one with more elected, more excluded, + nothing excluded and nothing elected, in one batch.""" + return ( + households + + [{**h, "election": h["election"] + h["more_elected"]} for h in households] + + [{**h, "exclusion": 1.5 * h["exclusion"] + 500} for h in households] + + [{**h, "exclusion": 0} for h in households] + + [{**h, "election": 0} for h in households] + ) + + +def split_variants(results, count): + return { + name: {v: values[k * count : (k + 1) * count] for v, values in results.items()} + for k, name in enumerate(VARIANTS) + } + + +def assert_monotone(households, groups): + """Property 4. + + For these households, the election and the exclusion reach taxable + income only through the choice to itemize. The model itemizes when that gives the lower + income_tax (tax_unit_itemizes), and an AMT filer can lower the tax by + itemizing deductions smaller than the standard deduction, which reduce + alternative minimum taxable income. An election or exclusion that raises + the regular tax can then change the choice. So amounts that depend on + taxable income are compared only between variants that make the same + choice. income_tax, the amount the choice minimizes, is compared across + a change of choice, for households without dependents. For them no + credit but the EITC applies; neither the EITC nor the net investment + income tax depends on the election or the choice, and as the exclusion + rises the EITC never rises and the net investment income tax never + falls. + """ + law = groups["law"] + taxable_income = law["taxable_income"] + itemizes = law["tax_unit_itemizes"] + for name in VARIANTS: + same = groups[name]["tax_unit_itemizes"] == itemizes + assert np.array_equal( + groups[name]["taxable_income"][same], taxable_income[same] + ), name + + more = column(households, "more_elected") + scale = ( + np.abs(column(households, "long_term")) + + np.abs(column(households, "short_term")) + + column(households, "distributions") + + column(households, "dividends") + + column(households, "election") + + more + + np.abs(taxable_income) + ) + slack = rounding(scale) + election_pairs = [ + (groups["no_election"], law), + (law, groups["more_elected"]), + ] + exclusion_pairs = [ + (groups["unstacked"], law), + (law, groups["more_excluded"]), + ] + + def same_choice(lower, higher): + return lower["tax_unit_itemizes"] == higher["tax_unit_itemizes"] + + # More elected: the gains never rise, net capital gain falls by at most + # the extra amount elected, and amounts limited by taxable income or + # Form 6251 line 6 never rise while the choice to itemize holds. + for variable, depends_on_income in [ + ("net_capital_gain", False), + ("dwks10", False), + ("section_911_net_capital_gain", True), + ("section_911_qualified_dividend_income", True), + ("section_911_capital_gain_excess", True), + ("amt_section_911_capital_gain_excess", True), + ]: + for lower, higher in election_pairs: + rise = higher[variable] - lower[variable] + rows = same_choice(lower, higher) | (not depends_on_income) + assert (rise <= slack)[rows].all(), variable + fall = law["net_capital_gain"] - groups["more_elected"]["net_capital_gain"] + assert (fall <= more + slack).all() + + inside = plain(households) + excluded = np.maximum( + column(households, "exclusion"), + groups["more_excluded"]["foreign_earned_income_exclusion"], + ) + tax_slack = tolerance( + taxable_income + excluded, + groups["more_excluded"][ + "amt_income_less_exemptions_plus_section_911_exclusion" + ], + ) + overlap = law["rate_overlap"] + no_dependents = np.array([h["status"] != "HEAD_OF_HOUSEHOLD" for h in households]) + for v in ["regular_tax", "income_tax_before_credits", "income_tax"]: + # tax_unit_itemizes treats liabilities within $0.01 as equal. + margin = tax_slack + (0.01 if v == "income_tax" else 0) + if v != "income_tax": + assert (law[v] >= 0).all(), v + + def rows(lower, higher): + if v == "income_tax": + return inside & no_dependents + return inside & same_choice(lower, higher) + + # More elected: the tax falls by at most the rate overlap. + for lower, higher in election_pairs: + dip = lower[v] - higher[v] + mask = rows(lower, higher) + assert (dip[mask] <= (overlap + margin)[mask]).all(), v + # Stacking never lowers the tax, and the tax never falls as the + # excluded amount rises. + for lower, higher in exclusion_pairs: + dip = lower[v] - higher[v] + mask = rows(lower, higher) + assert (dip[mask] <= margin[mask]).all(), v + # Section 911(f)(1)(A) applies "if such taxpayer has taxable income". + for name in VARIANTS: + no_income = inside & (groups[name]["taxable_income"] == 0) + assert not groups[name]["regular_tax"][no_income].any(), name + + +def assert_all(households, year, forms=False): + count = len(households) + batch = with_variants(households) + results = calculate(batch, year) + assert_gain_invariants(batch, results) + groups = split_variants(results, count) + assert_monotone(households, groups) + if forms: + assert_matches_2025_forms(batch, results) + return groups + + +# --------------------------------------------------------------------------- +# A grid that reaches every branch, and generated households. +# --------------------------------------------------------------------------- + + +def household(**amounts): + h = { + "status": "SINGLE", + "wages": 0, + "dividends": 0, + "long_term": 0, + "short_term": 0, + "distributions": 0, + "election": 0, + "more_elected": 1_000, + "exclusion": 0, + "real_estate_taxes": 0, + "mortgage_interest": 0, + "charitable_gifts": 0, + "section_1250": 0, + "rate_gain_28": 0, + "cents": False, + } + h.update(amounts) + return h + + +# Wages, dividends, long-term gain, short-term gain, distributions. +GRID_INCOMES = [ + (60_000, 10_000, 50_000, 0, 0), + (0, 8_000, 30_000, -5_000, 2_000), + (0, 40_000, 0, 0, 0), + (700_000, 25_000, 900_000, 0, 0), + # Gains in the AMT exemption phase-out, where the AMT exceeds the regular + # tax. + (20_000, 25_000, 900_000, 0, 0), + (0, 0, -8_000, 5_000, 0), + (20_000, 5_000, 15_000, 3_000, 1_000), +] + + +def grid(): + rows = [] + for status in STATUSES: + for wages, dividends, long_term, short_term, distributions in GRID_INCOMES: + base = household( + status=status, + wages=wages, + dividends=dividends, + long_term=long_term, + short_term=short_term, + distributions=distributions, + ) + gain = section_1222_gain(base) + for election in sorted( + {0, 2_000, gain, gain + dividends // 2, gain + dividends + 50_000} + ): + for exclusion in (0, 40_025, 260_000): + rows.append({**base, "election": election, "exclusion": exclusion}) + # Itemizers, and 28-percent rate and unrecaptured section 1250 gain. + rows += [ + { + **row, + "real_estate_taxes": 12_000, + "charitable_gifts": 40_000, + "section_1250": 15_000, + "rate_gain_28": 10_000, + } + for row in rows[::7] + ] + return rows + + +GRID = grid() + + +def test_grid(): + groups = assert_all(GRID, 2025, forms=True) + law = groups["law"] + elects = column(GRID, "election") > 0 + excludes = column(GRID, "exclusion") > 0 + both = elects & excludes + excess = law["section_911_capital_gain_excess"] + # The grid reaches each part of the interaction: an election with and + # without a capital gain excess, an excess that reaches the dividends the + # election leaves, an election that reaches the dividends, the AMT, the + # capital gains tax and no taxable income. + assert (both & (excess > 0)).any() + assert (both & (excess == 0)).any() + assert (both & (excess > law["dwks09"]) & (law["dwks09"] > 0)).any() + reached_dividends = law["dividend_income_reduced_by_investment_income"] < ( + column(GRID, "dividends") + ) + assert (both & reached_dividends & (excess > 0)).any() + assert (both & (law["alternative_minimum_tax"] > 0)).any() + assert (both & (law["capital_gains_tax"] > 0)).any() + assert (both & (law["taxable_income"] == 0)).any() + assert (both & (law["amt_section_911_capital_gain_excess"] > 0)).any() + # Electing more lowers the tax of some households and stacking raises it. + assert (groups["more_elected"]["regular_tax"] > law["regular_tax"] + 1).any() + assert (law["regular_tax"] > groups["unstacked"]["regular_tax"] + 1).any() + + +def test_election_can_lower_the_tax_where_15_percent_exceeds_12(): + """An intended exception to "electing more never lowers the tax". + + 2025, single, no other income than wages and a $10,000 long-term gain. + Without an exclusion, $54,100 of wages leave $58,350 of taxable income, + $48,350 of it ordinary: the gain starts at the 15% rate, inside the 12% + bracket, which ends at $48,475. With $40,000 of wages and $8,350 excluded, + worksheet line 3 is the same $58,350. Electing $125 taxes $125 more at 12% + ($15) and $125 less at 15% ($18.75): the tax falls by $3.75. The + Schedule D Tax Worksheet with its line 47 cap falls by the same amount. + """ + households = [ + household(wages=wages, long_term=10_000, election=election, exclusion=x) + for wages, x in [(64_100, 0), (55_750, 8_350)] + for election in (0, 125) + ] + law = calculate(households, 2025) + assert (law["taxable_income"] + column(households, "exclusion") == 58_350).all() + assert list(law["rate_overlap"]) == pytest.approx([3.75] * 4) + regular_tax = law["regular_tax"].reshape(2, 2) + assert list(regular_tax[:, 1] - regular_tax[:, 0]) == pytest.approx([-3.75] * 2) + assert np.array_equal(law["income_tax_before_credits"].reshape(2, 2), regular_tax) + on_the_form = np.array( + [ + foreign_earned_income_tax_worksheet( + float(law["taxable_income"][i]), + h["exclusion"], + *schedule_d_tax_worksheet(h)[:2], + "SINGLE", + )[1] + for i, h in enumerate(households) + ] + ).reshape(2, 2) + assert list(on_the_form[:, 1] - on_the_form[:, 0]) == pytest.approx([-3.75] * 2) + # Without the exclusion the form's tax is the worksheet's line 47. + _, _, line_47 = qualified_dividends_and_capital_gain_tax_worksheet( + 58_350, 0, 10_000, "SINGLE" + ) + assert on_the_form[0, 0] == pytest.approx(line_47) + assert line_47 < tax_rate_schedule(58_350, "SINGLE") + + +@st.composite +def households(draw): + h = household( + status=draw(st.sampled_from(STATUSES)), + wages=draw(st.one_of(st.just(0), st.integers(1, 900_000))), + dividends=draw(st.one_of(st.just(0), st.integers(1, 300_000))), + long_term=draw(st.one_of(st.just(0), st.integers(-50_000, 1_500_000))), + short_term=draw(st.one_of(st.just(0), st.integers(-100_000, 100_000))), + distributions=draw(st.one_of(st.just(0), st.integers(1, 100_000))), + exclusion=draw(st.one_of(st.just(0), st.integers(1, 300_000))), + real_estate_taxes=draw(st.one_of(st.just(0), st.integers(1, 60_000))), + mortgage_interest=draw(st.one_of(st.just(0), st.integers(1, 80_000))), + charitable_gifts=draw(st.one_of(st.just(0), st.integers(1, 150_000))), + more_elected=draw(st.one_of(st.integers(1, 500), st.integers(1, 500_000))), + ) + gain = section_1222_gain(h) + # Elections at the gain and at the gain plus dividends are where the + # election starts and stops reducing qualified dividends. + h["election"] = draw( + st.one_of( + st.just(0), + st.integers(1, 2_000_000), + st.sampled_from([gain, gain + h["dividends"]]), + st.integers(max(0, gain - 5), gain + h["dividends"] + 5), + ) + ) + if draw(st.integers(0, 3)) == 0: + h["section_1250"] = draw(st.integers(0, 200_000)) + h["rate_gain_28"] = draw(st.integers(0, 200_000)) + if draw(st.integers(0, 3)) == 0: + # Amounts with cents, which single precision cannot store exactly. + for key in [ + "dividends", + "long_term", + "short_term", + "distributions", + "election", + "exclusion", + ]: + if h[key]: + h[key] = h[key] + draw(st.integers(1, 99)) / 100 + h["cents"] = True + return h + + +SETTINGS = dict( + deadline=None, + derandomize=True, + suppress_health_check=[ + hypothesis.HealthCheck.too_slow, + hypothesis.HealthCheck.data_too_large, + ], +) + + +@hypothesis.settings(max_examples=10, **SETTINGS) +@hypothesis.given( + st.lists(households(), min_size=1, max_size=20), st.sampled_from(YEARS) +) +def test_random_households_keep_the_invariants(batch, year): + assert_all(batch, year) + + +@hypothesis.settings(max_examples=10, **SETTINGS) +@hypothesis.given(st.lists(households(), min_size=1, max_size=20)) +# Found by this test. Without the election the filer is in the AMT and +# itemizes $1,915 (Texas sales tax and a $1 gift), less than the standard +# deduction, which lowers alternative minimum taxable income; electing $1,917 +# raises the regular tax above the tentative minimum tax, the filer takes the +# standard deduction, and taxable income falls by $13,835. +@hypothesis.example( + [household(long_term=971_091, election=1_917, more_elected=1, charitable_gifts=1)] +) +# Dividends with cents split between spouses: the model sums them in double +# precision and stores the result in single precision. +@hypothesis.example( + [ + household( + status="JOINT", + dividends=2_554.33, + long_term=30_000.57, + election=1_000.01, + exclusion=60_000.25, + cents=True, + ) + ] +) +def test_random_households_match_the_2025_forms(batch): + assert_all(batch, 2025, forms=True) + + +@hypothesis.settings(max_examples=6, **SETTINGS) +@hypothesis.given( + st.lists(households(), min_size=2, max_size=20), st.sampled_from(YEARS) +) +def test_zero_election_or_exclusion_matches_the_situation_without_it(batch, year): + """Property 3: a household that elects nothing gets the results of the + situation test_section_911_tax_stacking.py builds (no election input), + and one that excludes nothing those of the situation + test_form_4952_election_worksheet.py builds (no exclusion input), bit for + bit, whatever its neighbours in the batch elect or exclude.""" + both = calculate(batch, year) + no_election = calculate(batch, year, with_election=False) + no_exclusion = calculate(batch, year, with_exclusion=False) + elects = column(batch, "election") > 0 + excludes = column(batch, "exclusion") > 0 + for v in OUTPUTS + ["regular_tax", "gain_other_than_dividends"]: + assert np.array_equal(both[v][~elects], no_election[v][~elects]), v + assert np.array_equal(both[v][~excludes], no_exclusion[v][~excludes]), v From 143a9dcef8eea9418555c21cbdb9675154da8ebb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 5 Oct 2026 14:13:52 -0400 Subject: [PATCH 2/4] Address review: exact gain checks, AMT excess once, docstring fixes - Gains and worksheet lines 6-10 do not depend on taxable income, so they are checked exactly for every whole-dollar household, itemizers with fractional taxable income included (property 5 as stated). - The AMT capital gain excess has one route; check it once against its closed form instead of against itself. - Correct the wages in the 12%/15% overlap example and an inverted grid comment; say the zero-input test guards against leakage between households. Co-Authored-By: Claude Opus 5.5 --- .../test_form_4952_section_911_interaction.py | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/policyengine_us/tests/test_form_4952_section_911_interaction.py b/policyengine_us/tests/test_form_4952_section_911_interaction.py index b3817ed611b..6d691b99032 100644 --- a/policyengine_us/tests/test_form_4952_section_911_interaction.py +++ b/policyengine_us/tests/test_form_4952_section_911_interaction.py @@ -382,9 +382,13 @@ def assert_gain_invariants(households, law): + np.abs(taxable_excess) ) # Whole-dollar amounts below 2**24 are exact in single precision, and so - # is every sum and difference of them the model forms. - exact = ~cents & (taxable_income == np.round(taxable_income)) & (scale < 2**24) - exact_amt = exact & (taxable_excess == np.round(taxable_excess)) + # is every sum and difference of them the model forms. The gains and + # worksheet lines 6 to 10 do not depend on taxable income; the section + # 911 amounts are exact only when taxable income (or Form 6251 line 6) is + # a whole-dollar amount too. + exact_gains = ~cents & (scale < 2**24) + exact = exact_gains & (taxable_income == np.round(taxable_income)) + exact_amt = exact_gains & (taxable_excess == np.round(taxable_excess)) def check(variable, expected, mask=exact): assert_close(law[variable], expected, mask, scale, variable) @@ -396,16 +400,18 @@ def check(variable, expected, mask=exact): # 5. The worksheet transcription, and dwks10 against net capital gain. lines = np.array([schedule_d_tax_worksheet(h) for h in households], dtype=float) - assert_close(lines[:, 2], net_gain, ~cents, scale, "closed form") - check("dividend_income_reduced_by_investment_income", lines[:, 0]) - check("dwks09", lines[:, 1]) - check("dwks10", lines[:, 2]) - assert_close(law["dwks10"], law["net_capital_gain"], exact, scale, "dwks10") + assert_close(lines[:, 2], net_gain, exact_gains, scale, "closed form") + check("dividend_income_reduced_by_investment_income", lines[:, 0], exact_gains) + check("dwks09", lines[:, 1], exact_gains) + check("dwks10", lines[:, 2], exact_gains) + assert_close(law["dwks10"], law["net_capital_gain"], exact_gains, scale, "dwks10") # 1. Closed forms. - check("net_capital_gain", net_gain) + check("net_capital_gain", net_gain, exact_gains) check( - "dividend_income_reduced_by_investment_income", np.minimum(dividends, net_gain) + "dividend_income_reduced_by_investment_income", + np.minimum(dividends, net_gain), + exact_gains, ) floor_income = np.maximum(0, taxable_income) check( @@ -494,19 +500,17 @@ def check(variable, expected, mask=exact): "modification 2", ) assert_close(law["worksheet_line_9"], reduced[:, 1], exact, scale, "modification 1") - # The AMT excess is measured from Form 6251 line 6. + # The AMT excess is measured from Form 6251 line 6. It has one route + # (amt_section_911_capital_gain_excess is the worksheet helper's excess), + # so it is checked against its closed form. floor_excess = np.maximum(0, taxable_excess) - for variable in [ + assert_close( + law["amt_section_911_capital_gain_excess"], + np.where(excludes, np.maximum(0, net_gain - floor_excess), 0), + exact_amt, + scale, "amt_section_911_capital_gain_excess", - "amt_worksheet_capital_gain_excess", - ]: - assert_close( - law[variable], - np.where(excludes, np.maximum(0, net_gain - floor_excess), 0), - exact_amt, - scale, - variable, - ) + ) assert_close( law["amt_worksheet_line_10"], np.where(excludes, np.minimum(net_gain, floor_excess), net_gain), @@ -853,7 +857,7 @@ def test_grid(): assert (both & (law["capital_gains_tax"] > 0)).any() assert (both & (law["taxable_income"] == 0)).any() assert (both & (law["amt_section_911_capital_gain_excess"] > 0)).any() - # Electing more lowers the tax of some households and stacking raises it. + # Electing more and stacking each raise the tax of some households. assert (groups["more_elected"]["regular_tax"] > law["regular_tax"] + 1).any() assert (law["regular_tax"] > groups["unstacked"]["regular_tax"] + 1).any() @@ -862,10 +866,12 @@ def test_election_can_lower_the_tax_where_15_percent_exceeds_12(): """An intended exception to "electing more never lowers the tax". 2025, single, no other income than wages and a $10,000 long-term gain. - Without an exclusion, $54,100 of wages leave $58,350 of taxable income, - $48,350 of it ordinary: the gain starts at the 15% rate, inside the 12% - bracket, which ends at $48,475. With $40,000 of wages and $8,350 excluded, - worksheet line 3 is the same $58,350. Electing $125 taxes $125 more at 12% + Without an exclusion, $64,100 of wages and the gain, less the $15,750 + standard deduction, leave $58,350 of taxable income, $48,350 of it + ordinary: the gain starts at the 15% rate, inside the 12% bracket, which + ends at $48,475. With $55,750 of wages, taxable income is $50,000, and + with $8,350 excluded worksheet line 3 is the same $58,350 (no capital gain + excess, since the gain is below taxable income). Electing $125 taxes $125 more at 12% ($15) and $125 less at 15% ($18.75): the tax falls by $3.75. The Schedule D Tax Worksheet with its line 47 cap falls by the same amount. """ @@ -1000,7 +1006,12 @@ def test_zero_election_or_exclusion_matches_the_situation_without_it(batch, year situation test_section_911_tax_stacking.py builds (no election input), and one that excludes nothing those of the situation test_form_4952_election_worksheet.py builds (no exclusion input), bit for - bit, whatever its neighbours in the batch elect or exclude.""" + bit, whatever its neighbours in the batch elect or exclude. + + Both amounts are inputs without formulas, so a zero input equals no + input by construction; what this checks is that no household's election + or exclusion reaches another household's results. Property 1 checks the + no-election and no-exclusion paths themselves.""" both = calculate(batch, year) no_election = calculate(batch, year, with_election=False) no_exclusion = calculate(batch, year, with_exclusion=False) From 8253050b315946e7bbb8cdb98a0913219685c63e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 5 Oct 2026 14:22:26 -0400 Subject: [PATCH 3/4] Say which closed forms are exact for whole-dollar itemizers Co-Authored-By: Claude Opus 5.5 --- .../tests/test_form_4952_section_911_interaction.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/policyengine_us/tests/test_form_4952_section_911_interaction.py b/policyengine_us/tests/test_form_4952_section_911_interaction.py index 6d691b99032..81dd6f729ed 100644 --- a/policyengine_us/tests/test_form_4952_section_911_interaction.py +++ b/policyengine_us/tests/test_form_4952_section_911_interaction.py @@ -79,9 +79,10 @@ tax and AMT match. Amounts are whole dollars below 2**24 except where a household is drawn with -cents. Whole-dollar households without itemized deductions have whole-dollar -taxable income, so for them the closed forms hold exactly; the others are -checked within single-precision rounding. +cents. For every whole-dollar household the gains and worksheet lines 6 to 10 +hold exactly; the section 911 amounts hold exactly when taxable income (or +Form 6251 line 6) is whole dollars too, as it is without itemized deductions. +The rest are checked within single-precision rounding. The scope limits of test_section_911_tax_stacking.py apply to the tax comparisons (properties 4 and 6), which therefore use households without From 33f7ef802d83fef1dd830d3eb8fa947318c72cc6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 5 Oct 2026 14:25:45 -0400 Subject: [PATCH 4/4] Docstring: Form 6251 line 6 can have cents without itemizing Co-Authored-By: Claude Opus 5.5 --- .../tests/test_form_4952_section_911_interaction.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/policyengine_us/tests/test_form_4952_section_911_interaction.py b/policyengine_us/tests/test_form_4952_section_911_interaction.py index 81dd6f729ed..912637a8972 100644 --- a/policyengine_us/tests/test_form_4952_section_911_interaction.py +++ b/policyengine_us/tests/test_form_4952_section_911_interaction.py @@ -79,10 +79,11 @@ tax and AMT match. Amounts are whole dollars below 2**24 except where a household is drawn with -cents. For every whole-dollar household the gains and worksheet lines 6 to 10 -hold exactly; the section 911 amounts hold exactly when taxable income (or -Form 6251 line 6) is whole dollars too, as it is without itemized deductions. -The rest are checked within single-precision rounding. +cents. For every whole-dollar household the gains and the model's worksheet +lines 6 to 10 hold exactly; the section 911 amounts hold exactly when taxable +income (or Form 6251 line 6) is whole dollars too, as taxable income is +without itemized deductions. The rest are checked within single-precision +rounding. The scope limits of test_section_911_tax_stacking.py apply to the tax comparisons (properties 4 and 6), which therefore use households without