OpenFisca-Core-test 46.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openfisca_core/__init__.py +0 -0
- openfisca_core/commons/__init__.py +18 -0
- openfisca_core/commons/dummy.py +25 -0
- openfisca_core/commons/formulas.py +121 -0
- openfisca_core/commons/misc.py +109 -0
- openfisca_core/commons/py.typed +0 -0
- openfisca_core/commons/rates.py +114 -0
- openfisca_core/commons/tests/__init__.py +0 -0
- openfisca_core/commons/tests/test_dummy.py +9 -0
- openfisca_core/commons/tests/test_formulas.py +75 -0
- openfisca_core/commons/tests/test_rates.py +26 -0
- openfisca_core/data_storage/__init__.py +7 -0
- openfisca_core/data_storage/in_memory_storage.py +198 -0
- openfisca_core/data_storage/on_disk_storage.py +307 -0
- openfisca_core/data_storage/types.py +14 -0
- openfisca_core/entities/__init__.py +23 -0
- openfisca_core/entities/_core_entity.py +217 -0
- openfisca_core/entities/_description.py +55 -0
- openfisca_core/entities/entity.py +60 -0
- openfisca_core/entities/group_entity.py +123 -0
- openfisca_core/entities/helpers.py +165 -0
- openfisca_core/entities/py.typed +0 -0
- openfisca_core/entities/role.py +92 -0
- openfisca_core/entities/tests/__init__.py +0 -0
- openfisca_core/entities/tests/test_entity.py +10 -0
- openfisca_core/entities/tests/test_group_entity.py +70 -0
- openfisca_core/entities/tests/test_role.py +11 -0
- openfisca_core/entities/types.py +44 -0
- openfisca_core/errors/__init__.py +64 -0
- openfisca_core/errors/cycle_error.py +2 -0
- openfisca_core/errors/empty_argument_error.py +39 -0
- openfisca_core/errors/nan_creation_error.py +2 -0
- openfisca_core/errors/parameter_not_found_error.py +16 -0
- openfisca_core/errors/parameter_parsing_error.py +18 -0
- openfisca_core/errors/period_mismatch_error.py +9 -0
- openfisca_core/errors/situation_parsing_error.py +26 -0
- openfisca_core/errors/spiral_error.py +2 -0
- openfisca_core/errors/variable_name_config_error.py +2 -0
- openfisca_core/errors/variable_not_found_error.py +29 -0
- openfisca_core/experimental/__init__.py +9 -0
- openfisca_core/experimental/_errors.py +5 -0
- openfisca_core/experimental/_memory_config.py +41 -0
- openfisca_core/formula_helpers.py +9 -0
- openfisca_core/holders/__init__.py +33 -0
- openfisca_core/holders/helpers.py +93 -0
- openfisca_core/holders/holder.py +328 -0
- openfisca_core/holders/tests/__init__.py +0 -0
- openfisca_core/holders/tests/test_helpers.py +134 -0
- openfisca_core/holders/types.py +3 -0
- openfisca_core/indexed_enums/__init__.py +18 -0
- openfisca_core/indexed_enums/_enum_type.py +79 -0
- openfisca_core/indexed_enums/_errors.py +35 -0
- openfisca_core/indexed_enums/_guards.py +209 -0
- openfisca_core/indexed_enums/_utils.py +180 -0
- openfisca_core/indexed_enums/config.py +6 -0
- openfisca_core/indexed_enums/enum.py +218 -0
- openfisca_core/indexed_enums/enum_array.py +316 -0
- openfisca_core/indexed_enums/py.typed +0 -0
- openfisca_core/indexed_enums/tests/__init__.py +0 -0
- openfisca_core/indexed_enums/tests/test_enum.py +135 -0
- openfisca_core/indexed_enums/tests/test_enum_array.py +30 -0
- openfisca_core/indexed_enums/types.py +49 -0
- openfisca_core/memory_config.py +9 -0
- openfisca_core/model_api.py +71 -0
- openfisca_core/parameters/__init__.py +71 -0
- openfisca_core/parameters/at_instant_like.py +17 -0
- openfisca_core/parameters/config.py +55 -0
- openfisca_core/parameters/helpers.py +106 -0
- openfisca_core/parameters/parameter.py +217 -0
- openfisca_core/parameters/parameter_at_instant.py +75 -0
- openfisca_core/parameters/parameter_node.py +176 -0
- openfisca_core/parameters/parameter_node_at_instant.py +57 -0
- openfisca_core/parameters/parameter_scale.py +121 -0
- openfisca_core/parameters/parameter_scale_bracket.py +7 -0
- openfisca_core/parameters/values_history.py +5 -0
- openfisca_core/parameters/vectorial_parameter_node_at_instant.py +179 -0
- openfisca_core/periods/__init__.py +77 -0
- openfisca_core/periods/_errors.py +28 -0
- openfisca_core/periods/_parsers.py +122 -0
- openfisca_core/periods/config.py +20 -0
- openfisca_core/periods/date_unit.py +110 -0
- openfisca_core/periods/helpers.py +314 -0
- openfisca_core/periods/instant_.py +226 -0
- openfisca_core/periods/period_.py +919 -0
- openfisca_core/periods/py.typed +0 -0
- openfisca_core/periods/tests/__init__.py +0 -0
- openfisca_core/periods/tests/helpers/__init__.py +0 -0
- openfisca_core/periods/tests/helpers/test_helpers.py +65 -0
- openfisca_core/periods/tests/helpers/test_instant.py +73 -0
- openfisca_core/periods/tests/helpers/test_period.py +134 -0
- openfisca_core/periods/tests/test_instant.py +32 -0
- openfisca_core/periods/tests/test_parsers.py +129 -0
- openfisca_core/periods/tests/test_period.py +283 -0
- openfisca_core/populations/__init__.py +64 -0
- openfisca_core/populations/_core_population.py +454 -0
- openfisca_core/populations/_errors.py +65 -0
- openfisca_core/populations/group_population.py +326 -0
- openfisca_core/populations/population.py +142 -0
- openfisca_core/populations/tests/__init__.py +0 -0
- openfisca_core/populations/tests/test_members_position.py +116 -0
- openfisca_core/populations/types.py +110 -0
- openfisca_core/projectors/__init__.py +39 -0
- openfisca_core/projectors/entity_to_person_projector.py +12 -0
- openfisca_core/projectors/first_person_to_entity_projector.py +13 -0
- openfisca_core/projectors/helpers.py +139 -0
- openfisca_core/projectors/projector.py +38 -0
- openfisca_core/projectors/typing.py +27 -0
- openfisca_core/projectors/unique_role_to_entity_projector.py +14 -0
- openfisca_core/rates.py +9 -0
- openfisca_core/reforms/__init__.py +24 -0
- openfisca_core/reforms/in_yaml_test_reform.py +28 -0
- openfisca_core/reforms/reform.py +86 -0
- openfisca_core/scripts/__init__.py +106 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-312-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/find_placeholders.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/find_placeholders.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/simulation_generator.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/simulation_generator.cpython-313.pyc +0 -0
- openfisca_core/scripts/assets/__init__.py +0 -0
- openfisca_core/scripts/assets/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/assets/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/assets/index.html +140 -0
- openfisca_core/scripts/find_placeholders.py +51 -0
- openfisca_core/scripts/measure_numpy_condition_notations.py +137 -0
- openfisca_core/scripts/measure_performances.py +291 -0
- openfisca_core/scripts/measure_performances_fancy_indexing.py +89 -0
- openfisca_core/scripts/migrations/__init__.py +0 -0
- openfisca_core/scripts/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +102 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +30 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +26 -0
- openfisca_core/scripts/migrations/v24_to_25.py +156 -0
- openfisca_core/scripts/openfisca_command.py +192 -0
- openfisca_core/scripts/remove_fuzzy.py +213 -0
- openfisca_core/scripts/run_test.py +51 -0
- openfisca_core/scripts/simulation_generator.py +90 -0
- openfisca_core/simulation_builder.py +16 -0
- openfisca_core/simulations/__init__.py +45 -0
- openfisca_core/simulations/_build_default_simulation.py +158 -0
- openfisca_core/simulations/_build_from_variables.py +230 -0
- openfisca_core/simulations/_type_guards.py +298 -0
- openfisca_core/simulations/helpers.py +111 -0
- openfisca_core/simulations/simulation.py +618 -0
- openfisca_core/simulations/simulation_builder.py +855 -0
- openfisca_core/simulations/typing.py +203 -0
- openfisca_core/taxbenefitsystems/__init__.py +26 -0
- openfisca_core/taxbenefitsystems/tax_benefit_system.py +584 -0
- openfisca_core/taxscales/__init__.py +35 -0
- openfisca_core/taxscales/abstract_rate_tax_scale.py +41 -0
- openfisca_core/taxscales/abstract_tax_scale.py +53 -0
- openfisca_core/taxscales/amount_tax_scale_like.py +55 -0
- openfisca_core/taxscales/helpers.py +44 -0
- openfisca_core/taxscales/linear_average_rate_tax_scale.py +76 -0
- openfisca_core/taxscales/marginal_amount_tax_scale.py +34 -0
- openfisca_core/taxscales/marginal_rate_tax_scale.py +286 -0
- openfisca_core/taxscales/rate_tax_scale_like.py +214 -0
- openfisca_core/taxscales/single_amount_tax_scale.py +32 -0
- openfisca_core/taxscales/tax_scale_like.py +65 -0
- openfisca_core/tools/__init__.py +94 -0
- openfisca_core/tools/parallel_plugin.py +55 -0
- openfisca_core/tools/simulation_dumper.py +137 -0
- openfisca_core/tools/test_runner.py +802 -0
- openfisca_core/tracers/__init__.py +40 -0
- openfisca_core/tracers/computation_log.py +196 -0
- openfisca_core/tracers/flat_trace.py +87 -0
- openfisca_core/tracers/full_tracer.py +172 -0
- openfisca_core/tracers/performance_log.py +133 -0
- openfisca_core/tracers/simple_tracer.py +64 -0
- openfisca_core/tracers/trace_node.py +121 -0
- openfisca_core/tracers/tracing_parameter_node_at_instant.py +81 -0
- openfisca_core/types.py +548 -0
- openfisca_core/variables/__init__.py +26 -0
- openfisca_core/variables/config.py +53 -0
- openfisca_core/variables/helpers.py +67 -0
- openfisca_core/variables/tests/__init__.py +0 -0
- openfisca_core/variables/tests/test_definition_period.py +43 -0
- openfisca_core/variables/variable.py +474 -0
- openfisca_core/warnings/__init__.py +25 -0
- openfisca_core/warnings/libyaml_warning.py +2 -0
- openfisca_core/warnings/tempfile_warning.py +2 -0
- openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/CHANGELOG.md +4209 -0
- openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/README.md +152 -0
- openfisca_core_test-46.0.0.dist-info/METADATA +194 -0
- openfisca_core_test-46.0.0.dist-info/RECORD +237 -0
- openfisca_core_test-46.0.0.dist-info/WHEEL +5 -0
- openfisca_core_test-46.0.0.dist-info/entry_points.txt +3 -0
- openfisca_core_test-46.0.0.dist-info/licenses/LICENSE +661 -0
- openfisca_core_test-46.0.0.dist-info/top_level.txt +2 -0
- openfisca_web_api/__init__.py +0 -0
- openfisca_web_api/app.py +226 -0
- openfisca_web_api/errors.py +11 -0
- openfisca_web_api/handlers.py +90 -0
- openfisca_web_api/loader/__init__.py +22 -0
- openfisca_web_api/loader/entities.py +28 -0
- openfisca_web_api/loader/parameters.py +122 -0
- openfisca_web_api/loader/spec.py +168 -0
- openfisca_web_api/loader/tax_benefit_system.py +27 -0
- openfisca_web_api/loader/variables.py +121 -0
- openfisca_web_api/openAPI.yml +434 -0
- openfisca_web_api/scripts/__init__.py +0 -0
- openfisca_web_api/scripts/serve.py +99 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from .tax_scale_like import TaxScaleLike
|
|
7
|
+
|
|
8
|
+
if typing.TYPE_CHECKING:
|
|
9
|
+
import numpy
|
|
10
|
+
|
|
11
|
+
NumericalArray = numpy.int32 | numpy.float32
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AbstractTaxScale(TaxScaleLike):
|
|
15
|
+
"""Base class for various types of tax scales: amount-based tax scales,
|
|
16
|
+
rate-based tax scales...
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
name: str | None = None,
|
|
22
|
+
option: typing.Any = None,
|
|
23
|
+
unit: numpy.int16 = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
message = [
|
|
26
|
+
"The 'AbstractTaxScale' class has been deprecated since",
|
|
27
|
+
"version 34.7.0, and will be removed in the future.",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
warnings.warn(" ".join(message), DeprecationWarning, stacklevel=2)
|
|
31
|
+
super().__init__(name, option, unit)
|
|
32
|
+
|
|
33
|
+
def __repr__(self) -> typing.NoReturn:
|
|
34
|
+
msg = f"Method '__repr__' is not implemented for {self.__class__.__name__}"
|
|
35
|
+
raise NotImplementedError(
|
|
36
|
+
msg,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def calc(
|
|
40
|
+
self,
|
|
41
|
+
tax_base: NumericalArray,
|
|
42
|
+
right: bool,
|
|
43
|
+
) -> typing.NoReturn:
|
|
44
|
+
msg = f"Method 'calc' is not implemented for {self.__class__.__name__}"
|
|
45
|
+
raise NotImplementedError(
|
|
46
|
+
msg,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def to_dict(self) -> typing.NoReturn:
|
|
50
|
+
msg = f"Method 'to_dict' is not implemented for {self.__class__.__name__}"
|
|
51
|
+
raise NotImplementedError(
|
|
52
|
+
msg,
|
|
53
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import bisect
|
|
3
|
+
import os
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from openfisca_core import tools
|
|
7
|
+
|
|
8
|
+
from .tax_scale_like import TaxScaleLike
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AmountTaxScaleLike(TaxScaleLike, abc.ABC):
|
|
12
|
+
"""Base class for various types of amount-based tax scales: single amount,
|
|
13
|
+
marginal amount...
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
amounts: list
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
name: str | None = None,
|
|
21
|
+
option: typing.Any = None,
|
|
22
|
+
unit: typing.Any = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(name, option, unit)
|
|
25
|
+
self.amounts = []
|
|
26
|
+
|
|
27
|
+
def __repr__(self) -> str:
|
|
28
|
+
return tools.indent(
|
|
29
|
+
os.linesep.join(
|
|
30
|
+
[
|
|
31
|
+
f"- threshold: {threshold}{os.linesep} amount: {amount}"
|
|
32
|
+
for (threshold, amount) in zip(self.thresholds, self.amounts)
|
|
33
|
+
],
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def add_bracket(
|
|
38
|
+
self,
|
|
39
|
+
threshold: int,
|
|
40
|
+
amount: float,
|
|
41
|
+
) -> None:
|
|
42
|
+
if threshold in self.thresholds:
|
|
43
|
+
i = self.thresholds.index(threshold)
|
|
44
|
+
self.amounts[i] += amount
|
|
45
|
+
|
|
46
|
+
else:
|
|
47
|
+
i = bisect.bisect_left(self.thresholds, threshold)
|
|
48
|
+
self.thresholds.insert(i, threshold)
|
|
49
|
+
self.amounts.insert(i, amount)
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict:
|
|
52
|
+
return {
|
|
53
|
+
str(threshold): self.amounts[index]
|
|
54
|
+
for index, threshold in enumerate(self.thresholds)
|
|
55
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from openfisca_core import taxscales
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
from openfisca_core.parameters import ParameterNodeAtInstant
|
|
12
|
+
|
|
13
|
+
TaxScales = taxscales.MarginalRateTaxScale | None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def combine_tax_scales(
|
|
17
|
+
node: ParameterNodeAtInstant,
|
|
18
|
+
combined_tax_scales: TaxScales = None,
|
|
19
|
+
) -> TaxScales:
|
|
20
|
+
"""Combine all the MarginalRateTaxScales in the node into a single
|
|
21
|
+
MarginalRateTaxScale.
|
|
22
|
+
"""
|
|
23
|
+
name = next(iter(node or []), None)
|
|
24
|
+
|
|
25
|
+
if name is None:
|
|
26
|
+
return combined_tax_scales
|
|
27
|
+
|
|
28
|
+
if combined_tax_scales is None:
|
|
29
|
+
combined_tax_scales = taxscales.MarginalRateTaxScale(name=name)
|
|
30
|
+
combined_tax_scales.add_bracket(0, 0)
|
|
31
|
+
|
|
32
|
+
for child_name in node:
|
|
33
|
+
child = node[child_name]
|
|
34
|
+
|
|
35
|
+
if isinstance(child, taxscales.MarginalRateTaxScale):
|
|
36
|
+
combined_tax_scales.add_tax_scale(child)
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
log.info(
|
|
40
|
+
f"Skipping {child_name} with value {child} "
|
|
41
|
+
"because it is not a marginal rate tax scale",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return combined_tax_scales
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
import numpy
|
|
7
|
+
|
|
8
|
+
from openfisca_core import taxscales
|
|
9
|
+
|
|
10
|
+
from .rate_tax_scale_like import RateTaxScaleLike
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
if typing.TYPE_CHECKING:
|
|
15
|
+
NumericalArray = numpy.int32 | numpy.float32
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LinearAverageRateTaxScale(RateTaxScaleLike):
|
|
19
|
+
def calc(
|
|
20
|
+
self,
|
|
21
|
+
tax_base: NumericalArray,
|
|
22
|
+
right: bool = False,
|
|
23
|
+
) -> numpy.float32:
|
|
24
|
+
if len(self.rates) == 1:
|
|
25
|
+
return tax_base * self.rates[0]
|
|
26
|
+
|
|
27
|
+
tiled_base = numpy.tile(tax_base, (len(self.thresholds) - 1, 1)).T
|
|
28
|
+
tiled_thresholds = numpy.tile(self.thresholds, (len(tax_base), 1))
|
|
29
|
+
|
|
30
|
+
bracket_dummy = (tiled_base >= tiled_thresholds[:, :-1]) * (
|
|
31
|
+
+tiled_base < tiled_thresholds[:, 1:]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
rates_array = numpy.array(self.rates)
|
|
35
|
+
thresholds_array = numpy.array(self.thresholds)
|
|
36
|
+
|
|
37
|
+
rate_slope = (rates_array[1:] - rates_array[:-1]) / (
|
|
38
|
+
+thresholds_array[1:] - thresholds_array[:-1]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
average_rate_slope = numpy.dot(bracket_dummy, rate_slope.T)
|
|
42
|
+
|
|
43
|
+
bracket_average_start_rate = numpy.dot(bracket_dummy, rates_array[:-1])
|
|
44
|
+
bracket_threshold = numpy.dot(bracket_dummy, thresholds_array[:-1])
|
|
45
|
+
|
|
46
|
+
log.info(f"bracket_average_start_rate : {bracket_average_start_rate}")
|
|
47
|
+
log.info(f"average_rate_slope: {average_rate_slope}")
|
|
48
|
+
|
|
49
|
+
return tax_base * (
|
|
50
|
+
+bracket_average_start_rate
|
|
51
|
+
+ (tax_base - bracket_threshold) * average_rate_slope
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def to_marginal(self) -> taxscales.MarginalRateTaxScale:
|
|
55
|
+
marginal_tax_scale = taxscales.MarginalRateTaxScale(
|
|
56
|
+
name=self.name,
|
|
57
|
+
option=self.option,
|
|
58
|
+
unit=self.unit,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
previous_i = 0
|
|
62
|
+
previous_threshold = 0
|
|
63
|
+
|
|
64
|
+
for threshold, rate in zip(self.thresholds[1:], self.rates[1:]):
|
|
65
|
+
if threshold != float("Inf"):
|
|
66
|
+
i = rate * threshold
|
|
67
|
+
marginal_tax_scale.add_bracket(
|
|
68
|
+
previous_threshold,
|
|
69
|
+
(i - previous_i) / (threshold - previous_threshold),
|
|
70
|
+
)
|
|
71
|
+
previous_i = i
|
|
72
|
+
previous_threshold = threshold
|
|
73
|
+
|
|
74
|
+
marginal_tax_scale.add_bracket(previous_threshold, rate)
|
|
75
|
+
|
|
76
|
+
return marginal_tax_scale
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
|
|
7
|
+
from .amount_tax_scale_like import AmountTaxScaleLike
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
NumericalArray = numpy.int32 | numpy.float32
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MarginalAmountTaxScale(AmountTaxScaleLike):
|
|
14
|
+
def calc(
|
|
15
|
+
self,
|
|
16
|
+
tax_base: NumericalArray,
|
|
17
|
+
right: bool = False,
|
|
18
|
+
) -> numpy.float32:
|
|
19
|
+
"""Matches the input amount to a set of brackets and returns the sum of
|
|
20
|
+
cell values from the lowest bracket to the one containing the input.
|
|
21
|
+
"""
|
|
22
|
+
base1 = numpy.tile(tax_base, (len(self.thresholds), 1)).T
|
|
23
|
+
|
|
24
|
+
thresholds1 = numpy.tile(
|
|
25
|
+
numpy.hstack((self.thresholds, numpy.inf)),
|
|
26
|
+
(len(tax_base), 1),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
a = numpy.maximum(
|
|
30
|
+
numpy.minimum(base1, thresholds1[:, 1:]) - thresholds1[:, :-1],
|
|
31
|
+
0,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return numpy.dot(self.amounts, a.T > 0)
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import bisect
|
|
4
|
+
import itertools
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
import numpy
|
|
8
|
+
|
|
9
|
+
from openfisca_core import taxscales
|
|
10
|
+
|
|
11
|
+
from .rate_tax_scale_like import RateTaxScaleLike
|
|
12
|
+
|
|
13
|
+
if typing.TYPE_CHECKING:
|
|
14
|
+
NumericalArray = numpy.int32 | numpy.float32
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MarginalRateTaxScale(RateTaxScaleLike):
|
|
18
|
+
def add_tax_scale(self, tax_scale: RateTaxScaleLike) -> None:
|
|
19
|
+
# So as not to have problems with empty scales
|
|
20
|
+
if len(tax_scale.thresholds) > 0:
|
|
21
|
+
for threshold_low, threshold_high, rate in zip(
|
|
22
|
+
tax_scale.thresholds[:-1],
|
|
23
|
+
tax_scale.thresholds[1:],
|
|
24
|
+
tax_scale.rates,
|
|
25
|
+
):
|
|
26
|
+
self.combine_bracket(rate, threshold_low, threshold_high)
|
|
27
|
+
|
|
28
|
+
# To process the last threshold
|
|
29
|
+
self.combine_bracket(
|
|
30
|
+
tax_scale.rates[-1],
|
|
31
|
+
tax_scale.thresholds[-1],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def calc(
|
|
35
|
+
self,
|
|
36
|
+
tax_base: NumericalArray,
|
|
37
|
+
factor: float = 1.0,
|
|
38
|
+
round_base_decimals: int | None = None,
|
|
39
|
+
) -> numpy.float32:
|
|
40
|
+
"""Compute the tax amount for the given tax bases by applying a taxscale.
|
|
41
|
+
|
|
42
|
+
:param ndarray tax_base: Array of the tax bases.
|
|
43
|
+
:param float factor: Factor to apply to the thresholds of the taxscale.
|
|
44
|
+
:param int round_base_decimals: Decimals to keep when rounding
|
|
45
|
+
thresholds.
|
|
46
|
+
|
|
47
|
+
:returns: Float array with tax amount for the given tax bases.
|
|
48
|
+
|
|
49
|
+
For instance:
|
|
50
|
+
|
|
51
|
+
>>> tax_scale = MarginalRateTaxScale()
|
|
52
|
+
>>> tax_scale.add_bracket(0, 0)
|
|
53
|
+
>>> tax_scale.add_bracket(100, 0.1)
|
|
54
|
+
>>> tax_base = array([0, 150])
|
|
55
|
+
>>> tax_scale.calc(tax_base)
|
|
56
|
+
[0.0, 5.0]
|
|
57
|
+
"""
|
|
58
|
+
base1 = numpy.tile(tax_base, (len(self.thresholds), 1)).T
|
|
59
|
+
factor = numpy.ones(len(tax_base)) * factor
|
|
60
|
+
|
|
61
|
+
# To avoid the creation of:
|
|
62
|
+
#
|
|
63
|
+
# numpy.nan = 0 * numpy.inf
|
|
64
|
+
#
|
|
65
|
+
# We use:
|
|
66
|
+
#
|
|
67
|
+
# numpy.finfo(float_).eps
|
|
68
|
+
thresholds1 = numpy.outer(
|
|
69
|
+
factor + numpy.finfo(numpy.float64).eps,
|
|
70
|
+
numpy.array([*self.thresholds, numpy.inf]),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if round_base_decimals is not None:
|
|
74
|
+
thresholds1 = numpy.round(thresholds1, round_base_decimals)
|
|
75
|
+
|
|
76
|
+
a = numpy.maximum(
|
|
77
|
+
numpy.minimum(base1, thresholds1[:, 1:]) - thresholds1[:, :-1],
|
|
78
|
+
0,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if round_base_decimals is None:
|
|
82
|
+
return numpy.dot(self.rates, a.T)
|
|
83
|
+
|
|
84
|
+
r = numpy.tile(self.rates, (len(tax_base), 1))
|
|
85
|
+
b = numpy.round(a, round_base_decimals)
|
|
86
|
+
return numpy.round(r * b, round_base_decimals).sum(axis=1)
|
|
87
|
+
|
|
88
|
+
def combine_bracket(
|
|
89
|
+
self,
|
|
90
|
+
rate: float,
|
|
91
|
+
threshold_low: int = 0,
|
|
92
|
+
threshold_high: int | bool = False,
|
|
93
|
+
) -> None:
|
|
94
|
+
# Insert threshold_low and threshold_high without modifying rates
|
|
95
|
+
if threshold_low not in self.thresholds:
|
|
96
|
+
index = bisect.bisect_right(self.thresholds, threshold_low) - 1
|
|
97
|
+
self.add_bracket(threshold_low, self.rates[index])
|
|
98
|
+
|
|
99
|
+
if threshold_high and threshold_high not in self.thresholds:
|
|
100
|
+
index = bisect.bisect_right(self.thresholds, threshold_high) - 1
|
|
101
|
+
self.add_bracket(threshold_high, self.rates[index])
|
|
102
|
+
|
|
103
|
+
# Use add_bracket to add rates where they belongs
|
|
104
|
+
i = self.thresholds.index(threshold_low)
|
|
105
|
+
|
|
106
|
+
if threshold_high:
|
|
107
|
+
j = self.thresholds.index(threshold_high) - 1
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
j = len(self.thresholds) - 1
|
|
111
|
+
|
|
112
|
+
while i <= j:
|
|
113
|
+
self.add_bracket(self.thresholds[i], rate)
|
|
114
|
+
i += 1
|
|
115
|
+
|
|
116
|
+
def marginal_rates(
|
|
117
|
+
self,
|
|
118
|
+
tax_base: NumericalArray,
|
|
119
|
+
factor: float = 1.0,
|
|
120
|
+
round_base_decimals: int | None = None,
|
|
121
|
+
) -> numpy.float32:
|
|
122
|
+
"""Compute the marginal tax rates relevant for the given tax bases.
|
|
123
|
+
|
|
124
|
+
:param ndarray tax_base: Array of the tax bases.
|
|
125
|
+
:param float factor: Factor to apply to the thresholds of a tax scale.
|
|
126
|
+
:param int round_base_decimals: Decimals to keep when rounding
|
|
127
|
+
thresholds.
|
|
128
|
+
|
|
129
|
+
:returns: Float array with relevant marginal tax rate for the given tax
|
|
130
|
+
bases.
|
|
131
|
+
|
|
132
|
+
For instance:
|
|
133
|
+
|
|
134
|
+
>>> tax_scale = MarginalRateTaxScale()
|
|
135
|
+
>>> tax_scale.add_bracket(0, 0)
|
|
136
|
+
>>> tax_scale.add_bracket(100, 0.1)
|
|
137
|
+
>>> tax_base = array([0, 150])
|
|
138
|
+
>>> tax_scale.marginal_rates(tax_base)
|
|
139
|
+
[0.0, 0.1]
|
|
140
|
+
"""
|
|
141
|
+
bracket_indices = self.bracket_indices(
|
|
142
|
+
tax_base,
|
|
143
|
+
factor,
|
|
144
|
+
round_base_decimals,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return numpy.array(self.rates)[bracket_indices]
|
|
148
|
+
|
|
149
|
+
def rate_from_bracket_indice(
|
|
150
|
+
self,
|
|
151
|
+
bracket_indice: numpy.int16,
|
|
152
|
+
) -> numpy.float32:
|
|
153
|
+
"""Compute the relevant tax rates for the given bracket indices.
|
|
154
|
+
|
|
155
|
+
:param: ndarray bracket_indice: Array of the bracket indices.
|
|
156
|
+
|
|
157
|
+
:returns: Floating array with relevant tax rates
|
|
158
|
+
for the given bracket indices.
|
|
159
|
+
|
|
160
|
+
For instance:
|
|
161
|
+
|
|
162
|
+
>>> import numpy
|
|
163
|
+
>>> tax_scale = MarginalRateTaxScale()
|
|
164
|
+
>>> tax_scale.add_bracket(0, 0)
|
|
165
|
+
>>> tax_scale.add_bracket(200, 0.1)
|
|
166
|
+
>>> tax_scale.add_bracket(500, 0.25)
|
|
167
|
+
>>> tax_base = numpy.array([50, 1_000, 250])
|
|
168
|
+
>>> bracket_indice = tax_scale.bracket_indices(tax_base)
|
|
169
|
+
>>> tax_scale.rate_from_bracket_indice(bracket_indice)
|
|
170
|
+
array([0. , 0.25, 0.1 ])
|
|
171
|
+
"""
|
|
172
|
+
if bracket_indice.max() > len(self.rates) - 1:
|
|
173
|
+
msg = (
|
|
174
|
+
f"bracket_indice parameter ({bracket_indice}) "
|
|
175
|
+
f"contains one or more bracket indice which is unavailable "
|
|
176
|
+
f"inside current {self.__class__.__name__} :\n"
|
|
177
|
+
f"{self}"
|
|
178
|
+
)
|
|
179
|
+
raise IndexError(
|
|
180
|
+
msg,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return numpy.array(self.rates)[bracket_indice]
|
|
184
|
+
|
|
185
|
+
def rate_from_tax_base(
|
|
186
|
+
self,
|
|
187
|
+
tax_base: NumericalArray,
|
|
188
|
+
) -> numpy.float32:
|
|
189
|
+
"""Compute the relevant tax rates for the given tax bases.
|
|
190
|
+
|
|
191
|
+
:param: ndarray tax_base: Array of the tax bases.
|
|
192
|
+
|
|
193
|
+
:returns: Floating array with relevant tax rates
|
|
194
|
+
for the given tax bases.
|
|
195
|
+
|
|
196
|
+
For instance:
|
|
197
|
+
|
|
198
|
+
>>> import numpy
|
|
199
|
+
>>> tax_scale = MarginalRateTaxScale()
|
|
200
|
+
>>> tax_scale.add_bracket(0, 0)
|
|
201
|
+
>>> tax_scale.add_bracket(200, 0.1)
|
|
202
|
+
>>> tax_scale.add_bracket(500, 0.25)
|
|
203
|
+
>>> tax_base = numpy.array([1_000, 50, 450])
|
|
204
|
+
>>> tax_scale.rate_from_tax_base(tax_base)
|
|
205
|
+
array([0.25, 0. , 0.1 ])
|
|
206
|
+
"""
|
|
207
|
+
return self.rate_from_bracket_indice(self.bracket_indices(tax_base))
|
|
208
|
+
|
|
209
|
+
def inverse(self) -> MarginalRateTaxScale:
|
|
210
|
+
"""Returns a new instance of MarginalRateTaxScale.
|
|
211
|
+
|
|
212
|
+
Invert a taxscale:
|
|
213
|
+
|
|
214
|
+
Assume tax_scale composed of bracket whose thresholds are expressed
|
|
215
|
+
in terms of gross revenue.
|
|
216
|
+
|
|
217
|
+
The inverse is another MarginalRateTaxScale whose thresholds are
|
|
218
|
+
expressed in terms of net revenue.
|
|
219
|
+
|
|
220
|
+
If net = gross_revenue - tax_scale.calc(gross_revenue)
|
|
221
|
+
Then gross = tax_scale.inverse().calc(net)
|
|
222
|
+
"""
|
|
223
|
+
# Threshold of net revenue.
|
|
224
|
+
net_threshold: int = 0
|
|
225
|
+
|
|
226
|
+
# Threshold of gross revenue.
|
|
227
|
+
threshold: int
|
|
228
|
+
|
|
229
|
+
# The intercept of the segments of the different thresholds in a
|
|
230
|
+
# representation of taxable revenue as a piecewise linear function
|
|
231
|
+
# of gross revenue.
|
|
232
|
+
theta: int
|
|
233
|
+
|
|
234
|
+
# Actually 1 / (1 - global_rate)
|
|
235
|
+
inverse = self.__class__(
|
|
236
|
+
name=str(self.name) + "'",
|
|
237
|
+
option=self.option,
|
|
238
|
+
unit=self.unit,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
for threshold, rate in zip(self.thresholds, self.rates):
|
|
242
|
+
if threshold == 0:
|
|
243
|
+
previous_rate = 0
|
|
244
|
+
theta = 0
|
|
245
|
+
|
|
246
|
+
# We calculate the taxable revenue threshold of the considered
|
|
247
|
+
# bracket.
|
|
248
|
+
net_threshold = (1 - previous_rate) * threshold + theta
|
|
249
|
+
inverse.add_bracket(net_threshold, 1 / (1 - rate))
|
|
250
|
+
theta = (rate - previous_rate) * threshold + theta
|
|
251
|
+
previous_rate = rate
|
|
252
|
+
|
|
253
|
+
return inverse
|
|
254
|
+
|
|
255
|
+
def scale_tax_scales(self, factor: float) -> MarginalRateTaxScale:
|
|
256
|
+
"""Scale all the MarginalRateTaxScales in the node."""
|
|
257
|
+
scaled_tax_scale = self.copy()
|
|
258
|
+
return scaled_tax_scale.multiply_thresholds(factor)
|
|
259
|
+
|
|
260
|
+
def to_average(self) -> taxscales.LinearAverageRateTaxScale:
|
|
261
|
+
average_tax_scale = taxscales.LinearAverageRateTaxScale(
|
|
262
|
+
name=self.name,
|
|
263
|
+
option=self.option,
|
|
264
|
+
unit=self.unit,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
average_tax_scale.add_bracket(0, 0)
|
|
268
|
+
|
|
269
|
+
if self.thresholds:
|
|
270
|
+
i = 0
|
|
271
|
+
previous_threshold = self.thresholds[0]
|
|
272
|
+
previous_rate = self.rates[0]
|
|
273
|
+
|
|
274
|
+
for threshold, rate in itertools.islice(
|
|
275
|
+
zip(self.thresholds, self.rates),
|
|
276
|
+
1,
|
|
277
|
+
None,
|
|
278
|
+
):
|
|
279
|
+
i += previous_rate * (threshold - previous_threshold)
|
|
280
|
+
average_tax_scale.add_bracket(threshold, i / threshold)
|
|
281
|
+
previous_threshold = threshold
|
|
282
|
+
previous_rate = rate
|
|
283
|
+
|
|
284
|
+
average_tax_scale.add_bracket(float("Inf"), rate)
|
|
285
|
+
|
|
286
|
+
return average_tax_scale
|