diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..4f30c605f15 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: patch + changes: + added: + - Capability to select custom start time for simulations; this is a patch for structural reforms that occur at non-default time periods. diff --git a/docs/usage/structural_reform_dating.ipynb b/docs/usage/structural_reform_dating.ipynb new file mode 100644 index 00000000000..3e1874d3df9 --- /dev/null +++ b/docs/usage/structural_reform_dating.ipynb @@ -0,0 +1,146 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Inputting Dates for Structural Reforms\n", + "\n", + "\"Structural\" reforms are those reforms that modify not only set values in the tax-benefit system, but also the formulas used to calculate taxes and benefits. These are typcially larger, more involved reforms that require custom coding.\n", + "\n", + "Due to the current limitations of the `Microsimulation` class, a code patch is required when running structural reforms with parameters that begin at any date other than January 1st of the current year. \n", + "\n", + "For example, the code cell below illustrates a standard way to instantiating a structural reform, without the patch, when simulating in 2024:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/us-3.11/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Diff: -2605373719.2974854\n" + ] + } + ], + "source": [ + "from policyengine_us import Microsimulation\n", + "from policyengine_core.reforms import Reform\n", + "\n", + "reform_1 = Reform.from_dict({\n", + " \"gov.contrib.salt_phase_out.in_effect\": {\n", + " \"2024-01-01.2100-12-31\": True\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.joint[1].rate\": {\n", + " \"2024-01-01.2100-12-31\": 0.001\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.joint[1].threshold\": {\n", + " \"2024-01-01.2100-12-31\": 200000\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.other[1].rate\": {\n", + " \"2024-01-01.2100-12-31\": 0.001\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.other[1].threshold\": {\n", + " \"2024-01-01.2100-12-31\": 400000\n", + " }\n", + "}, country_id=\"us\")\n", + "\n", + "\n", + "baseline_sim_1 = Microsimulation()\n", + "reformed_sim_1 = Microsimulation(reform=reform_1)\n", + "baseline_salt_1 = baseline_sim_1.calculate(\"salt_deduction\", period=2026)\n", + "reformed_salt_1 = reformed_sim_1.calculate(\"salt_deduction\", period=2026)\n", + "print(f\"Diff: {reformed_salt_1.sum() - baseline_salt_1.sum()}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below shows a series of reforms that begin in 2026, later than the current year. To effectively handle this case, we need to add an argument to the `Microsimulation` classes that we call. \n", + "\n", + "This argument, called `start_instant`, should be set to the same date as the start of the reforms, in ISO date format. In the case of the example below, this is `2026-01-01`, so our altered call to `Microsimulation` looks like:\n", + "\n", + "```\n", + "baseline_sim_2 = Microsimulation(start_instant=\"2026-01-01\")\n", + "reformed_sim_2 = Microsimulation(reform=reform_2, start_instant=\"2026-01-01\")\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Diff: -2605373719.2974854\n" + ] + } + ], + "source": [ + "from policyengine_us import Microsimulation\n", + "from policyengine_core.reforms import Reform\n", + "\n", + "reform_2 = Reform.from_dict({\n", + " \"gov.contrib.salt_phase_out.in_effect\": {\n", + " \"2026-01-01.2100-12-31\": True\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.joint[1].rate\": {\n", + " \"2026-01-01.2100-12-31\": 0.001\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.joint[1].threshold\": {\n", + " \"2026-01-01.2100-12-31\": 200000\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.other[1].rate\": {\n", + " \"2026-01-01.2100-12-31\": 0.001\n", + " },\n", + " \"gov.contrib.salt_phase_out.rate.other[1].threshold\": {\n", + " \"2026-01-01.2100-12-31\": 400000\n", + " }\n", + "}, country_id=\"us\")\n", + "\n", + "\n", + "baseline_sim_2 = Microsimulation(start_instant=\"2026-01-01\")\n", + "reformed_sim_2 = Microsimulation(reform=reform_2, start_instant=\"2026-01-01\")\n", + "baseline_salt_2 = baseline_sim_2.calculate(\"salt_deduction\", period=2026)\n", + "reformed_salt_2 = reformed_sim_2.calculate(\"salt_deduction\", period=2026)\n", + "print(f\"Diff: {reformed_salt_2.sum() - baseline_salt_2.sum()}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "us-3.11", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/policyengine_us/system.py b/policyengine_us/system.py index e224cbe7e90..eab807e80fb 100644 --- a/policyengine_us/system.py +++ b/policyengine_us/system.py @@ -29,13 +29,33 @@ from .tools.default_uprating import add_default_uprating from policyengine_us_data import DATASETS, CPS_2024 +from typing import Annotated + + COUNTRY_DIR = Path(__file__).parent CURRENT_YEAR = 2024 -year_start = str(CURRENT_YEAR) + "-01-01" +DEFAULT_START_DATE = str(CURRENT_YEAR) + "-01-01" class CountryTaxBenefitSystem(TaxBenefitSystem): + """ + The tax-benefit system for the United States. + This structure is a modification of the -core + package's base TaxBenefitSystem class. + + Args: + reform (tuple | None): A tuple of reforms to apply to the system. + If no reform is applied, the system will be initialized with the + default tax/benefit parameters. + + start_instant(str: ISO date format YYYY-MM-DD): Optional; The date + at which the simulation begins; defaults to 2024-01-01; this is a + temporary patch for structural reforms, and must be set to the start + date of a structural reform parameter if it begins on a date other + than the first day of the current year. + """ + variables_dir = COUNTRY_DIR / "variables" auto_carry_over_input_variables = True basic_inputs = [ @@ -45,7 +65,13 @@ class CountryTaxBenefitSystem(TaxBenefitSystem): ] modelled_policies = COUNTRY_DIR / "modelled_policies.yaml" - def __init__(self, reform=None): + def __init__( + self, + reform: tuple | None = None, + start_instant: Annotated[ + str, "ISO date format YYYY-MM-DD" + ] = DEFAULT_START_DATE, + ): super().__init__(entities, reform=reform) self.load_parameters(COUNTRY_DIR / "parameters") self.add_abolition_parameters() @@ -62,7 +88,7 @@ def __init__(self, reform=None): add_default_uprating(self) structural_reform = create_structural_reforms_from_parameters( - self.parameters, year_start + self.parameters, start_instant ) if reform is None: reform = () @@ -85,6 +111,21 @@ def __init__(self, reform=None): class Simulation(CoreSimulation): + """ + A simulation of the tax-benefit system for the United States, + defined against the base simulation class in the -core package. + + This simulation is commonly used for household-level impacts, as it + does not include society-wide microdata. + + Args: + start_instant(str: ISO date format YYYY-MM-DD): Optional; The date + at which the simulation begins; defaults to 2024-01-01; this is a + temporary patch for structural reforms, and must be set to the start + date of a structural reform parameter if it begins on a date other + than the first day of the current year. + """ + default_tax_benefit_system = CountryTaxBenefitSystem default_tax_benefit_system_instance = system default_role = "member" @@ -93,10 +134,13 @@ class Simulation(CoreSimulation): datasets = DATASETS def __init__(self, *args, **kwargs): + start_instant: Annotated[str, "ISO date format YYYY-MM-DD"] = ( + kwargs.pop("start_instant", DEFAULT_START_DATE) + ) super().__init__(*args, **kwargs) reform = create_structural_reforms_from_parameters( - self.tax_benefit_system.parameters, year_start + self.tax_benefit_system.parameters, start_instant ) if reform is not None: self.apply_reform(reform) @@ -137,6 +181,21 @@ def __init__(self, *args, **kwargs): class Microsimulation(CoreMicrosimulation): + """ + A microsimulation of the tax-benefit system for the United States, + defined against the base microsimulation class in the -core package. + + This simulation contains society-wide representative microdata, and is + thus suitable for society-level impacts. + + Args: + start_instant(str: ISO date format YYYY-MM-DD): Optional; The date + at which the simulation begins; defaults to 2024-01-01; this is a + temporary patch for structural reforms, and must be set to the start + date of a structural reform parameter if it begins on a date other + than the first day of the current year. + """ + default_tax_benefit_system = CountryTaxBenefitSystem default_tax_benefit_system_instance = system default_dataset = CPS_2024 @@ -147,10 +206,13 @@ class Microsimulation(CoreMicrosimulation): datasets = DATASETS def __init__(self, *args, **kwargs): + start_instant: Annotated[str, "ISO date format YYYY-MM-DD"] = ( + kwargs.pop("start_instant", DEFAULT_START_DATE) + ) super().__init__(*args, **kwargs) reform = create_structural_reforms_from_parameters( - self.tax_benefit_system.parameters, year_start + self.tax_benefit_system.parameters, start_instant ) if reform is not None: self.apply_reform(reform)