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,40 @@
1
+ # Transitional imports to ensure non-breaking changes.
2
+ # Could be deprecated in the next major release.
3
+ #
4
+ # How imports are being used today:
5
+ #
6
+ # >>> from openfisca_core.module import symbol
7
+ #
8
+ # The previous example provokes cyclic dependency problems
9
+ # that prevent us from modularizing the different components
10
+ # of the library so to make them easier to test and to maintain.
11
+ #
12
+ # How could them be used after the next major release:
13
+ #
14
+ # >>> from openfisca_core import module
15
+ # >>> module.symbol()
16
+ #
17
+ # And for classes:
18
+ #
19
+ # >>> from openfisca_core import module
20
+ # >>> module.Symbol()
21
+ #
22
+ # See: https://www.python.org/dev/peps/pep-0008/#imports
23
+
24
+ from .computation_log import ComputationLog
25
+ from .flat_trace import FlatTrace
26
+ from .full_tracer import FullTracer
27
+ from .performance_log import PerformanceLog
28
+ from .simple_tracer import SimpleTracer
29
+ from .trace_node import TraceNode
30
+ from .tracing_parameter_node_at_instant import TracingParameterNodeAtInstant
31
+
32
+ __all__ = [
33
+ "ComputationLog",
34
+ "FlatTrace",
35
+ "FullTracer",
36
+ "PerformanceLog",
37
+ "SimpleTracer",
38
+ "TraceNode",
39
+ "TracingParameterNodeAtInstant",
40
+ ]
@@ -0,0 +1,196 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ import numpy
6
+
7
+ from openfisca_core import types as t
8
+ from openfisca_core.indexed_enums import Enum, EnumArray
9
+
10
+
11
+ class ComputationLog:
12
+ _full_tracer: t.FullTracer
13
+
14
+ def __init__(self, full_tracer: t.FullTracer) -> None:
15
+ self._full_tracer = full_tracer
16
+
17
+ def lines(
18
+ self,
19
+ aggregate: bool = False,
20
+ max_depth: int = sys.maxsize,
21
+ ignore_default: bool = False,
22
+ tax_benefit_system: t.TaxBenefitSystem | None = None,
23
+ ) -> list[str]:
24
+ """Generate lines for the computation log.
25
+
26
+ Args:
27
+ aggregate: Show aggregated statistics instead of individual values.
28
+ max_depth: Maximum depth to display in the computation tree.
29
+ ignore_default: Hide variables with default values and their children.
30
+ tax_benefit_system: Used to get default values for variables.
31
+
32
+ Returns:
33
+ List of strings representing the computation log lines.
34
+
35
+ """
36
+ depth = 1
37
+
38
+ lines_by_tree = [
39
+ self._get_node_log(
40
+ node, depth, aggregate, max_depth, ignore_default, tax_benefit_system
41
+ )
42
+ for node in self._full_tracer.trees
43
+ ]
44
+
45
+ return self._flatten(lines_by_tree)
46
+
47
+ def print_log(
48
+ self,
49
+ aggregate: bool = False,
50
+ max_depth: int = sys.maxsize,
51
+ ignore_default: bool = False,
52
+ tax_benefit_system: t.TaxBenefitSystem | None = None,
53
+ ) -> None:
54
+ """Print the computation log of a simulation.
55
+
56
+ If ``aggregate`` is ``False`` (default), print the value of each
57
+ computed vector.
58
+
59
+ If ``aggregate`` is ``True``, only print the minimum, maximum, and
60
+ average value of each computed vector.
61
+
62
+ This mode is more suited for simulations on a large population.
63
+
64
+ If ``max_depth`` is ``sys.maxsize`` (default), print the entire
65
+ computation.
66
+
67
+ If ``max_depth`` is set, for example to ``3``, only print computed
68
+ vectors up to a depth of ``max_depth``.
69
+
70
+ If ``ignore_default`` is ``True``, do not display variables that have
71
+ their default value. When a variable with its default value is hidden,
72
+ its children are also hidden, even if they have non-default values.
73
+ """
74
+ for line in self.lines(
75
+ aggregate, max_depth, ignore_default, tax_benefit_system
76
+ ):
77
+ print(line)
78
+
79
+ def _get_node_log(
80
+ self,
81
+ node: t.TraceNode,
82
+ depth: int,
83
+ aggregate: bool,
84
+ max_depth: int = sys.maxsize,
85
+ ignore_default: bool = False,
86
+ tax_benefit_system: t.TaxBenefitSystem | None = None,
87
+ ) -> list[str]:
88
+ if depth > max_depth:
89
+ return []
90
+
91
+ # Check if we should ignore this node because it has its default value
92
+ if ignore_default and self._is_default_value(node, tax_benefit_system):
93
+ # Don't display this node or its children
94
+ return []
95
+
96
+ node_log = [self._print_line(depth, node, aggregate)]
97
+
98
+ children_logs = [
99
+ self._get_node_log(
100
+ child,
101
+ depth + 1,
102
+ aggregate,
103
+ max_depth,
104
+ ignore_default,
105
+ tax_benefit_system,
106
+ )
107
+ for child in node.children
108
+ ]
109
+
110
+ return node_log + self._flatten(children_logs)
111
+
112
+ def _is_default_value(
113
+ self, node: t.TraceNode, tax_benefit_system: t.TaxBenefitSystem | None
114
+ ) -> bool:
115
+ """Check if a node's value is the default value for its variable.
116
+
117
+ Args:
118
+ node: The trace node to check
119
+ tax_benefit_system: The tax benefit system to get variable information
120
+
121
+ Returns:
122
+ True if the value is the default value, False otherwise
123
+ """
124
+ if tax_benefit_system is None or node.value is None:
125
+ return False
126
+
127
+ try:
128
+ variable = tax_benefit_system.get_variable(node.name)
129
+ if variable is None:
130
+ return False
131
+
132
+ default_value = variable.default_value
133
+ if default_value is None:
134
+ return False
135
+
136
+ # For arrays, check if all values equal the default
137
+ if isinstance(node.value, numpy.ndarray):
138
+ # Handle EnumArray specially
139
+ if isinstance(node.value, EnumArray):
140
+ # For Enum, compare the enum values
141
+ if variable.value_type == Enum:
142
+ # Check if all values in the array are the default enum value
143
+ return numpy.all(node.value == default_value)
144
+ return False
145
+
146
+ # For numeric/boolean arrays, check if all values equal default
147
+ try:
148
+ return numpy.all(node.value == default_value)
149
+ except (ValueError, TypeError):
150
+ # If comparison fails, assume not default
151
+ return False
152
+
153
+ # For scalar values, direct comparison
154
+ return node.value == default_value
155
+
156
+ except Exception:
157
+ # If we can't determine, assume not default to be safe
158
+ return False
159
+
160
+ def _print_line(self, depth: int, node: t.TraceNode, aggregate: bool) -> str:
161
+ indent = " " * depth
162
+ value = node.value
163
+
164
+ if value is None:
165
+ formatted_value = "{'avg': '?', 'max': '?', 'min': '?'}"
166
+
167
+ elif aggregate:
168
+ try:
169
+ formatted_value = str( # pyright: ignore[reportCallIssue]
170
+ {
171
+ "avg": numpy.mean(value), # pyright: ignore[reportArgumentType,reportCallIssue]
172
+ "max": numpy.max(value),
173
+ "min": numpy.min(value),
174
+ },
175
+ )
176
+
177
+ except TypeError:
178
+ formatted_value = "{'avg': '?', 'max': '?', 'min': '?'}"
179
+
180
+ else:
181
+ formatted_value = self.display(value)
182
+
183
+ return f"{indent}{node.name}<{node.period}> >> {formatted_value}"
184
+
185
+ @staticmethod
186
+ def display(value: t.VarArray, max_depth: int = sys.maxsize) -> str:
187
+ if isinstance(value, EnumArray):
188
+ value = value.decode_to_str()
189
+ return numpy.array2string(value, max_line_width=max_depth)
190
+
191
+ @staticmethod
192
+ def _flatten(lists: list[list[str]]) -> list[str]:
193
+ return [item for list_ in lists for item in list_]
194
+
195
+
196
+ __all__ = ["ComputationLog"]
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy
4
+
5
+ from openfisca_core import types as t
6
+ from openfisca_core.indexed_enums import EnumArray
7
+
8
+
9
+ class FlatTrace:
10
+ _full_tracer: t.FullTracer
11
+
12
+ def __init__(self, full_tracer: t.FullTracer) -> None:
13
+ self._full_tracer = full_tracer
14
+
15
+ def get_trace(self) -> t.FlatNodeMap:
16
+ trace: t.FlatNodeMap = {}
17
+
18
+ for node in self._full_tracer.browse_trace():
19
+ # We don't want cache read to overwrite data about the initial
20
+ # calculation.
21
+ #
22
+ # We therefore use a non-overwriting update.
23
+ trace.update(
24
+ {
25
+ key: node_trace
26
+ for key, node_trace in self._get_flat_trace(node).items()
27
+ if key not in trace
28
+ },
29
+ )
30
+
31
+ return trace
32
+
33
+ def get_serialized_trace(self) -> t.SerializedNodeMap:
34
+ return {
35
+ key: {**flat_trace, "value": self.serialize(flat_trace["value"])}
36
+ for key, flat_trace in self.get_trace().items()
37
+ }
38
+
39
+ def _get_flat_trace(
40
+ self,
41
+ node: t.TraceNode,
42
+ ) -> t.FlatNodeMap:
43
+ key = self.key(node)
44
+
45
+ return {
46
+ key: {
47
+ "dependencies": [self.key(child) for child in node.children],
48
+ "parameters": {
49
+ self.key(parameter): self.serialize(parameter.value)
50
+ for parameter in node.parameters
51
+ },
52
+ "value": node.value,
53
+ "calculation_time": node.calculation_time(),
54
+ "formula_time": node.formula_time(),
55
+ },
56
+ }
57
+
58
+ @staticmethod
59
+ def key(node: t.TraceNode) -> t.NodeKey:
60
+ """Return the key of a node."""
61
+ name = node.name
62
+ period = node.period
63
+ return t.NodeKey(f"{name}<{period}>")
64
+
65
+ @staticmethod
66
+ def serialize(
67
+ value: None | t.VarArray | t.ArrayLike[object],
68
+ ) -> None | t.ArrayLike[object]:
69
+ if value is None:
70
+ return None
71
+
72
+ if isinstance(value, EnumArray):
73
+ return value.decode_to_str().tolist()
74
+
75
+ if isinstance(value, numpy.ndarray) and numpy.issubdtype(
76
+ value.dtype,
77
+ numpy.dtype(bytes),
78
+ ):
79
+ return value.astype(numpy.dtype(str)).tolist()
80
+
81
+ if isinstance(value, numpy.ndarray):
82
+ return value.tolist()
83
+
84
+ return value
85
+
86
+
87
+ __all__ = ["FlatTrace"]
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import time
5
+ from collections.abc import Iterator
6
+
7
+ from openfisca_core import types as t
8
+
9
+ from .computation_log import ComputationLog
10
+ from .flat_trace import FlatTrace
11
+ from .performance_log import PerformanceLog
12
+ from .simple_tracer import SimpleTracer
13
+ from .trace_node import TraceNode
14
+
15
+
16
+ class FullTracer:
17
+ _simple_tracer: t.SimpleTracer
18
+ _trees: list[t.TraceNode]
19
+ _current_node: None | t.TraceNode
20
+
21
+ def __init__(self) -> None:
22
+ self._simple_tracer = SimpleTracer()
23
+ self._trees = []
24
+ self._current_node = None
25
+
26
+ @property
27
+ def stack(self) -> t.SimpleStack:
28
+ """Return the stack of traces."""
29
+ return self._simple_tracer.stack
30
+
31
+ @property
32
+ def trees(self) -> list[t.TraceNode]:
33
+ """Return the tree of traces."""
34
+ return self._trees
35
+
36
+ @property
37
+ def computation_log(self) -> t.ComputationLog:
38
+ """Return the computation log."""
39
+ return ComputationLog(self)
40
+
41
+ @property
42
+ def performance_log(self) -> t.PerformanceLog:
43
+ """Return the performance log."""
44
+ return PerformanceLog(self)
45
+
46
+ @property
47
+ def flat_trace(self) -> t.FlatTrace:
48
+ """Return the flat trace."""
49
+ return FlatTrace(self)
50
+
51
+ def record_calculation_start(
52
+ self,
53
+ variable: t.VariableName,
54
+ period: t.PeriodInt | t.Period,
55
+ ) -> None:
56
+ self._simple_tracer.record_calculation_start(variable, period)
57
+ self._enter_calculation(variable, period)
58
+ self._record_start_time()
59
+
60
+ def record_parameter_access(
61
+ self,
62
+ parameter: str,
63
+ period: t.Period,
64
+ value: t.VarArray,
65
+ ) -> None:
66
+ if self._current_node is not None:
67
+ self._current_node.parameters.append(
68
+ TraceNode(name=parameter, period=period, value=value),
69
+ )
70
+
71
+ def record_calculation_result(self, value: t.VarArray) -> None:
72
+ if self._current_node is not None:
73
+ self._current_node.value = value
74
+
75
+ def record_calculation_end(self) -> None:
76
+ self._simple_tracer.record_calculation_end()
77
+ self._record_end_time()
78
+ self._exit_calculation()
79
+
80
+ def print_computation_log(
81
+ self,
82
+ aggregate: bool = False,
83
+ max_depth: int = sys.maxsize,
84
+ ignore_default: bool = False,
85
+ tax_benefit_system: t.TaxBenefitSystem | None = None,
86
+ ) -> None:
87
+ self.computation_log.print_log(
88
+ aggregate, max_depth, ignore_default, tax_benefit_system
89
+ )
90
+
91
+ def generate_performance_graph(self, dir_path: str) -> None:
92
+ self.performance_log.generate_graph(dir_path)
93
+
94
+ def generate_performance_tables(self, dir_path: str) -> None:
95
+ self.performance_log.generate_performance_tables(dir_path)
96
+
97
+ def get_nb_requests(self, variable: str) -> int:
98
+ return sum(self._get_nb_requests(tree, variable) for tree in self.trees)
99
+
100
+ def get_flat_trace(self) -> t.FlatNodeMap:
101
+ return self.flat_trace.get_trace()
102
+
103
+ def get_serialized_flat_trace(self) -> t.SerializedNodeMap:
104
+ return self.flat_trace.get_serialized_trace()
105
+
106
+ def browse_trace(self) -> Iterator[t.TraceNode]:
107
+ def _browse_node(node: t.TraceNode) -> Iterator[t.TraceNode]:
108
+ yield node
109
+
110
+ for child in node.children:
111
+ yield from _browse_node(child)
112
+
113
+ for node in self._trees:
114
+ yield from _browse_node(node)
115
+
116
+ def _enter_calculation(
117
+ self,
118
+ variable: t.VariableName,
119
+ period: t.PeriodInt | t.Period,
120
+ ) -> None:
121
+ new_node = TraceNode(
122
+ name=variable,
123
+ period=period,
124
+ parent=self._current_node,
125
+ )
126
+
127
+ if self._current_node is None:
128
+ self._trees.append(new_node)
129
+
130
+ else:
131
+ self._current_node.append_child(new_node)
132
+
133
+ self._current_node = new_node
134
+
135
+ def _record_start_time(
136
+ self,
137
+ time_in_s: float | None = None,
138
+ ) -> None:
139
+ if time_in_s is None:
140
+ time_in_s = self._get_time_in_sec()
141
+
142
+ if self._current_node is not None:
143
+ self._current_node.start = time_in_s
144
+
145
+ def _record_end_time(
146
+ self,
147
+ time_in_s: None | t.Time = None,
148
+ ) -> None:
149
+ if time_in_s is None:
150
+ time_in_s = self._get_time_in_sec()
151
+
152
+ if self._current_node is not None:
153
+ self._current_node.end = time_in_s
154
+
155
+ def _exit_calculation(self) -> None:
156
+ if self._current_node is not None:
157
+ self._current_node = self._current_node.parent
158
+
159
+ def _get_nb_requests(self, tree: t.TraceNode, variable: str) -> int:
160
+ tree_call = tree.name == variable
161
+ children_calls = sum(
162
+ self._get_nb_requests(child, variable) for child in tree.children
163
+ )
164
+
165
+ return tree_call + children_calls
166
+
167
+ @staticmethod
168
+ def _get_time_in_sec() -> t.Time:
169
+ return time.time_ns() / (10**9)
170
+
171
+
172
+ __all__ = ["FullTracer"]
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ import csv
4
+ import importlib.resources
5
+ import itertools
6
+ import json
7
+ import os
8
+ import typing
9
+
10
+ from openfisca_core import tracers
11
+
12
+ if typing.TYPE_CHECKING:
13
+ Trace = dict[str, dict]
14
+ Calculation = tuple[str, dict]
15
+ SortedTrace = list[Calculation]
16
+
17
+
18
+ class PerformanceLog:
19
+ def __init__(self, full_tracer: tracers.FullTracer) -> None:
20
+ self._full_tracer = full_tracer
21
+
22
+ def generate_graph(self, dir_path: str) -> None:
23
+ with open(os.path.join(dir_path, "performance_graph.html"), "w") as f:
24
+ template = importlib.resources.read_text(
25
+ "openfisca_core.scripts.assets",
26
+ "index.html",
27
+ )
28
+
29
+ perf_graph_html = template.replace(
30
+ "{{data}}",
31
+ json.dumps(self._json()),
32
+ )
33
+
34
+ f.write(perf_graph_html)
35
+
36
+ def generate_performance_tables(self, dir_path: str) -> None:
37
+ flat_trace = self._full_tracer.get_flat_trace()
38
+
39
+ csv_rows = [
40
+ {
41
+ "name": key,
42
+ "calculation_time": trace["calculation_time"],
43
+ "formula_time": trace["formula_time"],
44
+ }
45
+ for key, trace in flat_trace.items()
46
+ ]
47
+
48
+ self._write_csv(
49
+ os.path.join(dir_path, "performance_table.csv"),
50
+ csv_rows,
51
+ )
52
+
53
+ aggregated_csv_rows = [
54
+ {"name": key, **aggregated_time}
55
+ for key, aggregated_time in self.aggregate_calculation_times(
56
+ flat_trace,
57
+ ).items()
58
+ ]
59
+
60
+ self._write_csv(
61
+ os.path.join(dir_path, "aggregated_performance_table.csv"),
62
+ aggregated_csv_rows,
63
+ )
64
+
65
+ def aggregate_calculation_times(
66
+ self,
67
+ flat_trace: Trace,
68
+ ) -> dict[str, dict]:
69
+ def _aggregate_calculations(calculations: list) -> dict:
70
+ calculation_count = len(calculations)
71
+
72
+ calculation_time = sum(
73
+ calculation[1]["calculation_time"] for calculation in calculations
74
+ )
75
+
76
+ formula_time = sum(
77
+ calculation[1]["formula_time"] for calculation in calculations
78
+ )
79
+
80
+ return {
81
+ "calculation_count": calculation_count,
82
+ "calculation_time": tracers.TraceNode.round(calculation_time),
83
+ "formula_time": tracers.TraceNode.round(formula_time),
84
+ "avg_calculation_time": tracers.TraceNode.round(
85
+ calculation_time / calculation_count,
86
+ ),
87
+ "avg_formula_time": tracers.TraceNode.round(
88
+ formula_time / calculation_count,
89
+ ),
90
+ }
91
+
92
+ def _groupby(calculation: Calculation) -> str:
93
+ return calculation[0].split("<")[0]
94
+
95
+ all_calculations: SortedTrace = sorted(flat_trace.items())
96
+
97
+ return {
98
+ variable_name: _aggregate_calculations(list(calculations))
99
+ for variable_name, calculations in itertools.groupby(
100
+ all_calculations,
101
+ _groupby,
102
+ )
103
+ }
104
+
105
+ def _json(self) -> dict:
106
+ children = [self._json_tree(tree) for tree in self._full_tracer.trees]
107
+ calculations_total_time = sum(child["value"] for child in children)
108
+
109
+ return {
110
+ "name": "All calculations",
111
+ "value": calculations_total_time,
112
+ "children": children,
113
+ }
114
+
115
+ def _json_tree(self, tree: tracers.TraceNode) -> dict:
116
+ calculation_total_time = tree.calculation_time()
117
+ children = [self._json_tree(child) for child in tree.children]
118
+
119
+ return {
120
+ "name": f"{tree.name}<{tree.period}>",
121
+ "value": calculation_total_time,
122
+ "children": children,
123
+ }
124
+
125
+ def _write_csv(self, path: str, rows: list[dict]) -> None:
126
+ fieldnames = list(rows[0].keys())
127
+
128
+ with open(path, "w") as csv_file:
129
+ writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
130
+ writer.writeheader()
131
+
132
+ for row in rows:
133
+ writer.writerow(row)
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from openfisca_core import types as t
4
+
5
+
6
+ class SimpleTracer:
7
+ """A simple tracer that records a stack of traces."""
8
+
9
+ #: The stack of traces.
10
+ _stack: t.SimpleStack
11
+
12
+ def __init__(self) -> None:
13
+ self._stack = []
14
+
15
+ @property
16
+ def stack(self) -> t.SimpleStack:
17
+ """Return the stack of traces."""
18
+ return self._stack
19
+
20
+ def record_calculation_start(
21
+ self, variable: t.VariableName, period: t.PeriodInt | t.Period
22
+ ) -> None:
23
+ """Record the start of a calculation.
24
+
25
+ Args:
26
+ variable: The variable being calculated.
27
+ period: The period for which the variable is being calculated.
28
+
29
+ Examples:
30
+ >>> from openfisca_core import tracers
31
+
32
+ >>> tracer = tracers.SimpleTracer()
33
+ >>> tracer.record_calculation_start("variable", 2020)
34
+ >>> tracer.stack
35
+ [{'name': 'variable', 'period': 2020}]
36
+
37
+ """
38
+ self.stack.append({"name": variable, "period": period})
39
+
40
+ def record_calculation_result(self, value: t.ArrayLike[object]) -> None:
41
+ """Ignore calculation result."""
42
+
43
+ def record_parameter_access(
44
+ self, parameter: str, period: t.Period, value: t.ArrayLike[object]
45
+ ) -> None:
46
+ """Ignore parameter access."""
47
+
48
+ def record_calculation_end(self) -> None:
49
+ """Record the end of a calculation.
50
+
51
+ Examples:
52
+ >>> from openfisca_core import tracers
53
+
54
+ >>> tracer = tracers.SimpleTracer()
55
+ >>> tracer.record_calculation_start("variable", 2020)
56
+ >>> tracer.record_calculation_end()
57
+ >>> tracer.stack
58
+ []
59
+
60
+ """
61
+ self.stack.pop()
62
+
63
+
64
+ __all__ = ["SimpleTracer"]