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,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"]
|