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.
Files changed (237) hide show
  1. openfisca_core/__init__.py +0 -0
  2. openfisca_core/commons/__init__.py +18 -0
  3. openfisca_core/commons/dummy.py +25 -0
  4. openfisca_core/commons/formulas.py +121 -0
  5. openfisca_core/commons/misc.py +109 -0
  6. openfisca_core/commons/py.typed +0 -0
  7. openfisca_core/commons/rates.py +114 -0
  8. openfisca_core/commons/tests/__init__.py +0 -0
  9. openfisca_core/commons/tests/test_dummy.py +9 -0
  10. openfisca_core/commons/tests/test_formulas.py +75 -0
  11. openfisca_core/commons/tests/test_rates.py +26 -0
  12. openfisca_core/data_storage/__init__.py +7 -0
  13. openfisca_core/data_storage/in_memory_storage.py +198 -0
  14. openfisca_core/data_storage/on_disk_storage.py +307 -0
  15. openfisca_core/data_storage/types.py +14 -0
  16. openfisca_core/entities/__init__.py +23 -0
  17. openfisca_core/entities/_core_entity.py +217 -0
  18. openfisca_core/entities/_description.py +55 -0
  19. openfisca_core/entities/entity.py +60 -0
  20. openfisca_core/entities/group_entity.py +123 -0
  21. openfisca_core/entities/helpers.py +165 -0
  22. openfisca_core/entities/py.typed +0 -0
  23. openfisca_core/entities/role.py +92 -0
  24. openfisca_core/entities/tests/__init__.py +0 -0
  25. openfisca_core/entities/tests/test_entity.py +10 -0
  26. openfisca_core/entities/tests/test_group_entity.py +70 -0
  27. openfisca_core/entities/tests/test_role.py +11 -0
  28. openfisca_core/entities/types.py +44 -0
  29. openfisca_core/errors/__init__.py +64 -0
  30. openfisca_core/errors/cycle_error.py +2 -0
  31. openfisca_core/errors/empty_argument_error.py +39 -0
  32. openfisca_core/errors/nan_creation_error.py +2 -0
  33. openfisca_core/errors/parameter_not_found_error.py +16 -0
  34. openfisca_core/errors/parameter_parsing_error.py +18 -0
  35. openfisca_core/errors/period_mismatch_error.py +9 -0
  36. openfisca_core/errors/situation_parsing_error.py +26 -0
  37. openfisca_core/errors/spiral_error.py +2 -0
  38. openfisca_core/errors/variable_name_config_error.py +2 -0
  39. openfisca_core/errors/variable_not_found_error.py +29 -0
  40. openfisca_core/experimental/__init__.py +9 -0
  41. openfisca_core/experimental/_errors.py +5 -0
  42. openfisca_core/experimental/_memory_config.py +41 -0
  43. openfisca_core/formula_helpers.py +9 -0
  44. openfisca_core/holders/__init__.py +33 -0
  45. openfisca_core/holders/helpers.py +93 -0
  46. openfisca_core/holders/holder.py +328 -0
  47. openfisca_core/holders/tests/__init__.py +0 -0
  48. openfisca_core/holders/tests/test_helpers.py +134 -0
  49. openfisca_core/holders/types.py +3 -0
  50. openfisca_core/indexed_enums/__init__.py +18 -0
  51. openfisca_core/indexed_enums/_enum_type.py +79 -0
  52. openfisca_core/indexed_enums/_errors.py +35 -0
  53. openfisca_core/indexed_enums/_guards.py +209 -0
  54. openfisca_core/indexed_enums/_utils.py +180 -0
  55. openfisca_core/indexed_enums/config.py +6 -0
  56. openfisca_core/indexed_enums/enum.py +218 -0
  57. openfisca_core/indexed_enums/enum_array.py +316 -0
  58. openfisca_core/indexed_enums/py.typed +0 -0
  59. openfisca_core/indexed_enums/tests/__init__.py +0 -0
  60. openfisca_core/indexed_enums/tests/test_enum.py +135 -0
  61. openfisca_core/indexed_enums/tests/test_enum_array.py +30 -0
  62. openfisca_core/indexed_enums/types.py +49 -0
  63. openfisca_core/memory_config.py +9 -0
  64. openfisca_core/model_api.py +71 -0
  65. openfisca_core/parameters/__init__.py +71 -0
  66. openfisca_core/parameters/at_instant_like.py +17 -0
  67. openfisca_core/parameters/config.py +55 -0
  68. openfisca_core/parameters/helpers.py +106 -0
  69. openfisca_core/parameters/parameter.py +217 -0
  70. openfisca_core/parameters/parameter_at_instant.py +75 -0
  71. openfisca_core/parameters/parameter_node.py +176 -0
  72. openfisca_core/parameters/parameter_node_at_instant.py +57 -0
  73. openfisca_core/parameters/parameter_scale.py +121 -0
  74. openfisca_core/parameters/parameter_scale_bracket.py +7 -0
  75. openfisca_core/parameters/values_history.py +5 -0
  76. openfisca_core/parameters/vectorial_parameter_node_at_instant.py +179 -0
  77. openfisca_core/periods/__init__.py +77 -0
  78. openfisca_core/periods/_errors.py +28 -0
  79. openfisca_core/periods/_parsers.py +122 -0
  80. openfisca_core/periods/config.py +20 -0
  81. openfisca_core/periods/date_unit.py +110 -0
  82. openfisca_core/periods/helpers.py +314 -0
  83. openfisca_core/periods/instant_.py +226 -0
  84. openfisca_core/periods/period_.py +919 -0
  85. openfisca_core/periods/py.typed +0 -0
  86. openfisca_core/periods/tests/__init__.py +0 -0
  87. openfisca_core/periods/tests/helpers/__init__.py +0 -0
  88. openfisca_core/periods/tests/helpers/test_helpers.py +65 -0
  89. openfisca_core/periods/tests/helpers/test_instant.py +73 -0
  90. openfisca_core/periods/tests/helpers/test_period.py +134 -0
  91. openfisca_core/periods/tests/test_instant.py +32 -0
  92. openfisca_core/periods/tests/test_parsers.py +129 -0
  93. openfisca_core/periods/tests/test_period.py +283 -0
  94. openfisca_core/populations/__init__.py +64 -0
  95. openfisca_core/populations/_core_population.py +454 -0
  96. openfisca_core/populations/_errors.py +65 -0
  97. openfisca_core/populations/group_population.py +326 -0
  98. openfisca_core/populations/population.py +142 -0
  99. openfisca_core/populations/tests/__init__.py +0 -0
  100. openfisca_core/populations/tests/test_members_position.py +116 -0
  101. openfisca_core/populations/types.py +110 -0
  102. openfisca_core/projectors/__init__.py +39 -0
  103. openfisca_core/projectors/entity_to_person_projector.py +12 -0
  104. openfisca_core/projectors/first_person_to_entity_projector.py +13 -0
  105. openfisca_core/projectors/helpers.py +139 -0
  106. openfisca_core/projectors/projector.py +38 -0
  107. openfisca_core/projectors/typing.py +27 -0
  108. openfisca_core/projectors/unique_role_to_entity_projector.py +14 -0
  109. openfisca_core/rates.py +9 -0
  110. openfisca_core/reforms/__init__.py +24 -0
  111. openfisca_core/reforms/in_yaml_test_reform.py +28 -0
  112. openfisca_core/reforms/reform.py +86 -0
  113. openfisca_core/scripts/__init__.py +106 -0
  114. openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.3.3.pyc +0 -0
  115. openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.4.2.pyc +0 -0
  116. openfisca_core/scripts/__pycache__/__init__.cpython-310.pyc +0 -0
  117. openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.3.3.pyc +0 -0
  118. openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.4.2.pyc +0 -0
  119. openfisca_core/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  120. openfisca_core/scripts/__pycache__/__init__.cpython-312-pytest-8.4.2.pyc +0 -0
  121. openfisca_core/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  122. openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.3.3.pyc +0 -0
  123. openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.4.2.pyc +0 -0
  124. openfisca_core/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  125. openfisca_core/scripts/__pycache__/find_placeholders.cpython-311.pyc +0 -0
  126. openfisca_core/scripts/__pycache__/find_placeholders.cpython-313.pyc +0 -0
  127. openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-311.pyc +0 -0
  128. openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-313.pyc +0 -0
  129. openfisca_core/scripts/__pycache__/measure_performances.cpython-311.pyc +0 -0
  130. openfisca_core/scripts/__pycache__/measure_performances.cpython-313.pyc +0 -0
  131. openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-311.pyc +0 -0
  132. openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-313.pyc +0 -0
  133. openfisca_core/scripts/__pycache__/openfisca_command.cpython-310.pyc +0 -0
  134. openfisca_core/scripts/__pycache__/openfisca_command.cpython-311.pyc +0 -0
  135. openfisca_core/scripts/__pycache__/openfisca_command.cpython-312.pyc +0 -0
  136. openfisca_core/scripts/__pycache__/openfisca_command.cpython-313.pyc +0 -0
  137. openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-311.pyc +0 -0
  138. openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-313.pyc +0 -0
  139. openfisca_core/scripts/__pycache__/run_test.cpython-310.pyc +0 -0
  140. openfisca_core/scripts/__pycache__/run_test.cpython-311.pyc +0 -0
  141. openfisca_core/scripts/__pycache__/run_test.cpython-312.pyc +0 -0
  142. openfisca_core/scripts/__pycache__/run_test.cpython-313.pyc +0 -0
  143. openfisca_core/scripts/__pycache__/simulation_generator.cpython-311.pyc +0 -0
  144. openfisca_core/scripts/__pycache__/simulation_generator.cpython-313.pyc +0 -0
  145. openfisca_core/scripts/assets/__init__.py +0 -0
  146. openfisca_core/scripts/assets/__pycache__/__init__.cpython-311.pyc +0 -0
  147. openfisca_core/scripts/assets/__pycache__/__init__.cpython-313.pyc +0 -0
  148. openfisca_core/scripts/assets/index.html +140 -0
  149. openfisca_core/scripts/find_placeholders.py +51 -0
  150. openfisca_core/scripts/measure_numpy_condition_notations.py +137 -0
  151. openfisca_core/scripts/measure_performances.py +291 -0
  152. openfisca_core/scripts/measure_performances_fancy_indexing.py +89 -0
  153. openfisca_core/scripts/migrations/__init__.py +0 -0
  154. openfisca_core/scripts/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  155. openfisca_core/scripts/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  156. openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-311.pyc +0 -0
  157. openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-313.pyc +0 -0
  158. openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
  159. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-311.pyc +0 -0
  160. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-313.pyc +0 -0
  161. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-311.pyc +0 -0
  162. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-313.pyc +0 -0
  163. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-311.pyc +0 -0
  164. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-313.pyc +0 -0
  165. openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +102 -0
  166. openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +30 -0
  167. openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +26 -0
  168. openfisca_core/scripts/migrations/v24_to_25.py +156 -0
  169. openfisca_core/scripts/openfisca_command.py +192 -0
  170. openfisca_core/scripts/remove_fuzzy.py +213 -0
  171. openfisca_core/scripts/run_test.py +51 -0
  172. openfisca_core/scripts/simulation_generator.py +90 -0
  173. openfisca_core/simulation_builder.py +16 -0
  174. openfisca_core/simulations/__init__.py +45 -0
  175. openfisca_core/simulations/_build_default_simulation.py +158 -0
  176. openfisca_core/simulations/_build_from_variables.py +230 -0
  177. openfisca_core/simulations/_type_guards.py +298 -0
  178. openfisca_core/simulations/helpers.py +111 -0
  179. openfisca_core/simulations/simulation.py +618 -0
  180. openfisca_core/simulations/simulation_builder.py +855 -0
  181. openfisca_core/simulations/typing.py +203 -0
  182. openfisca_core/taxbenefitsystems/__init__.py +26 -0
  183. openfisca_core/taxbenefitsystems/tax_benefit_system.py +584 -0
  184. openfisca_core/taxscales/__init__.py +35 -0
  185. openfisca_core/taxscales/abstract_rate_tax_scale.py +41 -0
  186. openfisca_core/taxscales/abstract_tax_scale.py +53 -0
  187. openfisca_core/taxscales/amount_tax_scale_like.py +55 -0
  188. openfisca_core/taxscales/helpers.py +44 -0
  189. openfisca_core/taxscales/linear_average_rate_tax_scale.py +76 -0
  190. openfisca_core/taxscales/marginal_amount_tax_scale.py +34 -0
  191. openfisca_core/taxscales/marginal_rate_tax_scale.py +286 -0
  192. openfisca_core/taxscales/rate_tax_scale_like.py +214 -0
  193. openfisca_core/taxscales/single_amount_tax_scale.py +32 -0
  194. openfisca_core/taxscales/tax_scale_like.py +65 -0
  195. openfisca_core/tools/__init__.py +94 -0
  196. openfisca_core/tools/parallel_plugin.py +55 -0
  197. openfisca_core/tools/simulation_dumper.py +137 -0
  198. openfisca_core/tools/test_runner.py +802 -0
  199. openfisca_core/tracers/__init__.py +40 -0
  200. openfisca_core/tracers/computation_log.py +196 -0
  201. openfisca_core/tracers/flat_trace.py +87 -0
  202. openfisca_core/tracers/full_tracer.py +172 -0
  203. openfisca_core/tracers/performance_log.py +133 -0
  204. openfisca_core/tracers/simple_tracer.py +64 -0
  205. openfisca_core/tracers/trace_node.py +121 -0
  206. openfisca_core/tracers/tracing_parameter_node_at_instant.py +81 -0
  207. openfisca_core/types.py +548 -0
  208. openfisca_core/variables/__init__.py +26 -0
  209. openfisca_core/variables/config.py +53 -0
  210. openfisca_core/variables/helpers.py +67 -0
  211. openfisca_core/variables/tests/__init__.py +0 -0
  212. openfisca_core/variables/tests/test_definition_period.py +43 -0
  213. openfisca_core/variables/variable.py +474 -0
  214. openfisca_core/warnings/__init__.py +25 -0
  215. openfisca_core/warnings/libyaml_warning.py +2 -0
  216. openfisca_core/warnings/tempfile_warning.py +2 -0
  217. openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/CHANGELOG.md +4209 -0
  218. openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/README.md +152 -0
  219. openfisca_core_test-46.0.0.dist-info/METADATA +194 -0
  220. openfisca_core_test-46.0.0.dist-info/RECORD +237 -0
  221. openfisca_core_test-46.0.0.dist-info/WHEEL +5 -0
  222. openfisca_core_test-46.0.0.dist-info/entry_points.txt +3 -0
  223. openfisca_core_test-46.0.0.dist-info/licenses/LICENSE +661 -0
  224. openfisca_core_test-46.0.0.dist-info/top_level.txt +2 -0
  225. openfisca_web_api/__init__.py +0 -0
  226. openfisca_web_api/app.py +226 -0
  227. openfisca_web_api/errors.py +11 -0
  228. openfisca_web_api/handlers.py +90 -0
  229. openfisca_web_api/loader/__init__.py +22 -0
  230. openfisca_web_api/loader/entities.py +28 -0
  231. openfisca_web_api/loader/parameters.py +122 -0
  232. openfisca_web_api/loader/spec.py +168 -0
  233. openfisca_web_api/loader/tax_benefit_system.py +27 -0
  234. openfisca_web_api/loader/variables.py +121 -0
  235. openfisca_web_api/openAPI.yml +434 -0
  236. openfisca_web_api/scripts/__init__.py +0 -0
  237. 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