OpenFisca-Core 44.3.0__tar.gz → 44.5.0__tar.gz
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-44.3.0 → openfisca_core-44.5.0}/CHANGELOG.md +33 -3
- {openfisca_core-44.3.0 → openfisca_core-44.5.0/OpenFisca_Core.egg-info}/PKG-INFO +1 -1
- {openfisca_core-44.3.0/OpenFisca_Core.egg-info → openfisca_core-44.5.0}/PKG-INFO +1 -1
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/holder.py +65 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/implicit.py +39 -1
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/many2one.py +84 -1
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/one2many.py +64 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_many2one.py +28 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/_core_population.py +32 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/_build_default_simulation.py +11 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/simulation_builder.py +2 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/variable.py +28 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/setup.py +1 -1
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/LICENSE +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/MANIFEST.in +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/SOURCES.txt +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/dependency_links.txt +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/entry_points.txt +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/requires.txt +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/top_level.txt +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/README.md +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/dummy.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/formulas.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/misc.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/py.typed +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/rates.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_dummy.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_formulas.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_rates.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/in_memory_storage.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/on_disk_storage.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/types.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/_core_entity.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/_description.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/entity.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/group_entity.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/py.typed +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/role.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_entity.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_group_entity.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_role.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/types.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/cycle_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/empty_argument_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/nan_creation_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/parameter_not_found_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/parameter_parsing_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/period_mismatch_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/situation_parsing_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/spiral_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/variable_name_config_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/variable_not_found_error.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/experimental/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/experimental/_errors.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/experimental/_memory_config.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/formula_helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/tests/test_helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/types.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_enum_type.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_errors.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_guards.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_utils.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/config.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/enum.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/enum_array.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/py.typed +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/test_enum.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/test_enum_array.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/types.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/link.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_benchmark.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_edge_cases.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_implicit.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_integration.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_one2many.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_one2many_logic.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/memory_config.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/model_api.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/at_instant_like.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/config.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_at_instant.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_node.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_node_at_instant.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_scale.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_scale_bracket.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/values_history.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/vectorial_parameter_node_at_instant.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/_errors.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/_parsers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/config.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/date_unit.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/instant_.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/period_.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/py.typed +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_instant.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_period.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_instant.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_parsers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_period.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/_errors.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/group_population.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/population.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/tests/test_members_position.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/types.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/entity_to_person_projector.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/first_person_to_entity_projector.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/projector.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/typing.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/unique_role_to_entity_projector.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/rates.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/in_yaml_test_reform.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/reform.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/reform_excel.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/assets/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/assets/index.html +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/find_placeholders.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_numpy_condition_notations.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_performances.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_performances_fancy_indexing.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v24_to_25.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/openfisca_command.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/remove_fuzzy.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/run_test.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/simulation_generator.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulation_builder.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/_build_from_variables.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/_type_guards.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/simulation.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/typing.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxbenefitsystems/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxbenefitsystems/tax_benefit_system.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/abstract_rate_tax_scale.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/abstract_tax_scale.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/amount_tax_scale_like.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/linear_average_rate_tax_scale.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/marginal_amount_tax_scale.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/marginal_rate_tax_scale.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/rate_tax_scale_like.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/single_amount_tax_scale.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/tax_scale_like.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/parallel_plugin.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/simulation_dumper.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/test_runner.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/computation_log.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/flat_trace.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/full_tracer.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/performance_log.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/simple_tracer.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/trace_node.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/tracing_parameter_node_at_instant.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/types.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/config.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/helpers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/tests/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/tests/test_definition_period.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/warnings/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/warnings/libyaml_warning.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/warnings/tempfile_warning.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/app.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/errors.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/handlers.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/entities.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/parameters.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/spec.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/tax_benefit_system.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/variables.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/openAPI.yml +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/scripts/__init__.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/scripts/serve.py +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/pyproject.toml +0 -0
- {openfisca_core-44.3.0 → openfisca_core-44.5.0}/setup.cfg +0 -0
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 44.
|
|
3
|
+
## 44.5.0
|
|
4
4
|
|
|
5
|
-
#### New
|
|
5
|
+
#### New features
|
|
6
|
+
|
|
7
|
+
- Add `as_of` attribute to `Variable` for persistent vector variables.
|
|
8
|
+
- A variable declared with `as_of = True` (or `"start"` / `"end"`) stores its value at a given instant and automatically returns that value for any later period, until a new value is explicitly set — the vectorial analogue of OpenFisca parameters.
|
|
9
|
+
- `as_of = "start"` (default when `True`): lookup uses the start of the requested period as reference instant.
|
|
10
|
+
- `as_of = "end"`: lookup uses the end of the requested period (useful for annual variables like income tax).
|
|
11
|
+
- Lookup is O(log P) via `bisect` on a sorted instants index.
|
|
12
|
+
- Reference sharing: when consecutive stored values are identical, the same array object is reused, reducing memory usage for stable variables (e.g. `marital_status`, `housing_occupancy_status`).
|
|
13
|
+
- Stored arrays are read-only (`writeable=False`) to prevent accidental in-place mutation.
|
|
14
|
+
- Combining `as_of` with `set_input` dispatch helpers raises a `ValueError` at variable definition time.
|
|
15
|
+
|
|
16
|
+
## 44.4.0 [#1364](https://github.com/openfisca/openfisca-core/pull/1364)
|
|
17
|
+
|
|
18
|
+
#### New features
|
|
19
|
+
|
|
20
|
+
- **Entity links**: role-based and positional accessors, and dynamic population period-index helpers.
|
|
21
|
+
- `Many2OneLink.get_by_role(variable_name, period, role_value=...)`, `One2ManyLink.get_by_role(...)` and `ImplicitOne2ManyLink.get_by_role(...)`.
|
|
22
|
+
- `Many2OneLink.rank(variable_name, period)` (and on chained getter, e.g. `person.links["mother"].household.rank("age", period)`).
|
|
23
|
+
- `One2ManyLink.nth(n, variable_name, period, role=..., condition=...)` for the n-th target member per source.
|
|
24
|
+
- `has_role(role_value)` now supports `Role` objects (comparison by `.key`) in addition to raw values.
|
|
25
|
+
- `CorePopulation.snapshot_period(period)` and `get_period_id_to_rownum(period)` for optional dynamic-population period indexing.
|
|
26
|
+
|
|
27
|
+
#### Technical changes
|
|
28
|
+
|
|
29
|
+
- Removed unused `openfisca_core.model_api` import in `tests/core/parameters_date_indexing/test_date_indexing.py`.
|
|
30
|
+
- SimulationBuilder sets `_id_to_rownum` identity mapping for static simulations (`build_default_simulation`, `build_from_dict` / `build_from_entities`), for dynamic-population support.
|
|
31
|
+
- Add `PYTHON` variable to `tasks/lint.mk` so `make lint PYTHON=.venv/bin/python` works; fix style in `test_link_accessors.py` and remove unused variable in `test_many2one.py`.
|
|
32
|
+
|
|
33
|
+
## 44.3.0 [#1365](https://github.com/openfisca/openfisca-core/pull/1365)
|
|
34
|
+
|
|
35
|
+
#### New features
|
|
6
36
|
|
|
7
37
|
- **Generic Entity Links (Phase 1-6)**: Introduced a new Liam2-inspired generic entity linking system avoiding rigid hierarchies like `Person -> Household`.
|
|
8
38
|
- Added new `Many2OneLink` and `One2ManyLink` models to create powerful inter-entity networks (e.g., `Person -> Employer`).
|
|
@@ -10,7 +40,7 @@
|
|
|
10
40
|
- Full capability to chain relationships via python: `person.mother.household.get("rent", period)`.
|
|
11
41
|
- Powerful vectorized declarative aggregations out-of-the-box (e.g., `households.persons.sum("salary", period, condition=is_female)`).
|
|
12
42
|
|
|
13
|
-
#### Technical
|
|
43
|
+
#### Technical changes
|
|
14
44
|
|
|
15
45
|
- Backward compatibility is 100% maintained. Existing syntax via Projectors natively redirects to implicit links via modified `__getattr__`.
|
|
16
46
|
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
+
import bisect
|
|
6
7
|
import os
|
|
7
8
|
import warnings
|
|
8
9
|
|
|
@@ -29,7 +30,10 @@ class Holder:
|
|
|
29
30
|
self.variable = variable
|
|
30
31
|
self.simulation = population.simulation
|
|
31
32
|
self._eternal = self.variable.definition_period == periods.DateUnit.ETERNITY
|
|
33
|
+
self._as_of = getattr(self.variable, "as_of", False)
|
|
32
34
|
self._memory_storage = storage.InMemoryStorage(is_eternal=self._eternal)
|
|
35
|
+
if self._as_of:
|
|
36
|
+
self._sorted_instants: list = []
|
|
33
37
|
|
|
34
38
|
# By default, do not activate on-disk storage, or variable dropping
|
|
35
39
|
self._disk_storage = None
|
|
@@ -54,6 +58,11 @@ class Holder:
|
|
|
54
58
|
if key not in ("population", "formula", "simulation"):
|
|
55
59
|
new_dict[key] = value
|
|
56
60
|
|
|
61
|
+
# _sorted_instants must be an independent copy so that writes to the
|
|
62
|
+
# clone don't corrupt the original's index.
|
|
63
|
+
if self._as_of:
|
|
64
|
+
new_dict["_sorted_instants"] = list(self._sorted_instants)
|
|
65
|
+
|
|
57
66
|
new_dict["population"] = population
|
|
58
67
|
new_dict["simulation"] = population.simulation
|
|
59
68
|
|
|
@@ -90,10 +99,52 @@ class Holder:
|
|
|
90
99
|
value = self._memory_storage.get(period)
|
|
91
100
|
if value is not None:
|
|
92
101
|
return value
|
|
102
|
+
if self._as_of:
|
|
103
|
+
value = self._get_as_of(period)
|
|
104
|
+
if value is not None:
|
|
105
|
+
return value
|
|
93
106
|
if self._disk_storage:
|
|
94
107
|
return self._disk_storage.get(period)
|
|
95
108
|
return None
|
|
96
109
|
|
|
110
|
+
def _get_as_of(self, period):
|
|
111
|
+
"""Return the most recent stored value at or before the reference instant.
|
|
112
|
+
|
|
113
|
+
Uses a sorted list of instants + bisect for O(log P) lookup.
|
|
114
|
+
Falls back to a linear scan if _sorted_instants is not yet initialised
|
|
115
|
+
(e.g. after clone()).
|
|
116
|
+
"""
|
|
117
|
+
target = period.start if self._as_of == "start" else period.stop
|
|
118
|
+
sorted_instants = getattr(self, "_sorted_instants", None)
|
|
119
|
+
|
|
120
|
+
if sorted_instants is not None:
|
|
121
|
+
pos = bisect.bisect_right(sorted_instants, target)
|
|
122
|
+
if pos == 0:
|
|
123
|
+
return None
|
|
124
|
+
best_instant = sorted_instants[pos - 1]
|
|
125
|
+
else:
|
|
126
|
+
# Fallback linear scan (e.g. after clone without _sorted_instants)
|
|
127
|
+
best_instant = None
|
|
128
|
+
for known_period in self._memory_storage.get_known_periods():
|
|
129
|
+
start = known_period.start
|
|
130
|
+
if start <= target:
|
|
131
|
+
if best_instant is None or start > best_instant:
|
|
132
|
+
best_instant = start
|
|
133
|
+
if best_instant is None:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
for known_period in self._memory_storage.get_known_periods():
|
|
137
|
+
if known_period.start == best_instant:
|
|
138
|
+
return self._memory_storage.get(known_period)
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
def _register_instant(self, period) -> None:
|
|
142
|
+
"""Insert period.start into the sorted instants list (no duplicates)."""
|
|
143
|
+
instant = period.start
|
|
144
|
+
pos = bisect.bisect_left(self._sorted_instants, instant)
|
|
145
|
+
if pos == len(self._sorted_instants) or self._sorted_instants[pos] != instant:
|
|
146
|
+
self._sorted_instants.insert(pos, instant)
|
|
147
|
+
|
|
97
148
|
def get_memory_usage(self) -> t.MemoryUsage:
|
|
98
149
|
"""Get data about the virtual memory usage of the Holder.
|
|
99
150
|
|
|
@@ -302,11 +353,25 @@ class Holder:
|
|
|
302
353
|
>= self.simulation.memory_config.max_memory_occupation_pc
|
|
303
354
|
)
|
|
304
355
|
|
|
356
|
+
if self._as_of:
|
|
357
|
+
# Reference sharing: reuse existing array object when value unchanged,
|
|
358
|
+
# otherwise store a read-only defensive copy so that callers cannot
|
|
359
|
+
# corrupt stored data by mutating their original array in-place.
|
|
360
|
+
prev = self._get_as_of(period)
|
|
361
|
+
if prev is not None and numpy.array_equal(value, prev):
|
|
362
|
+
value = prev # prev is already read-only
|
|
363
|
+
else:
|
|
364
|
+
value = value.copy()
|
|
365
|
+
value.flags.writeable = False
|
|
366
|
+
|
|
305
367
|
if should_store_on_disk:
|
|
306
368
|
self._disk_storage.put(value, period)
|
|
307
369
|
else:
|
|
308
370
|
self._memory_storage.put(value, period)
|
|
309
371
|
|
|
372
|
+
if self._as_of:
|
|
373
|
+
self._register_instant(period)
|
|
374
|
+
|
|
310
375
|
def put_in_cache(self, value, period) -> None:
|
|
311
376
|
if self._do_not_store:
|
|
312
377
|
return
|
|
@@ -58,7 +58,19 @@ class ImplicitOne2ManyLink(One2ManyLink):
|
|
|
58
58
|
|
|
59
59
|
if role is not None:
|
|
60
60
|
roles = self._source_population.members_role
|
|
61
|
-
|
|
61
|
+
# roles may be an object array of Role instances, so compare by key
|
|
62
|
+
if roles.dtype == object:
|
|
63
|
+
try:
|
|
64
|
+
keys = numpy.fromiter(
|
|
65
|
+
(getattr(x, "key", x) for x in roles),
|
|
66
|
+
dtype=object,
|
|
67
|
+
)
|
|
68
|
+
except Exception:
|
|
69
|
+
mask &= roles == role
|
|
70
|
+
else:
|
|
71
|
+
mask &= keys == role
|
|
72
|
+
else:
|
|
73
|
+
mask &= roles == role
|
|
62
74
|
|
|
63
75
|
if condition is not None:
|
|
64
76
|
mask &= condition
|
|
@@ -69,5 +81,31 @@ class ImplicitOne2ManyLink(One2ManyLink):
|
|
|
69
81
|
valid = source_rows >= 0
|
|
70
82
|
return source_rows[valid], values[valid]
|
|
71
83
|
|
|
84
|
+
# override to avoid relying on ``role_field`` which is meaningless for
|
|
85
|
+
# implicit links (the role information is stored on the source population)
|
|
86
|
+
def get_by_role(
|
|
87
|
+
self,
|
|
88
|
+
variable_name: str,
|
|
89
|
+
period,
|
|
90
|
+
role_value,
|
|
91
|
+
*,
|
|
92
|
+
condition: numpy.ndarray | None = None,
|
|
93
|
+
) -> numpy.ndarray:
|
|
94
|
+
"""Fetch value for a specific role value on a one-to-many implicit link.
|
|
95
|
+
|
|
96
|
+
This mirrors :meth:`One2ManyLink.get_by_role` but uses
|
|
97
|
+
``self._source_population.members_role`` instead of a named role field
|
|
98
|
+
on the target population.
|
|
99
|
+
"""
|
|
100
|
+
values = self._target_population.simulation.calculate(variable_name, period)
|
|
101
|
+
source_rows, values = self._apply_filters(period, values, role_value, condition)
|
|
102
|
+
|
|
103
|
+
result = numpy.zeros(self._source_population.count, dtype=values.dtype)
|
|
104
|
+
# last value wins (same semantics as GroupPopulation.value_from_person)
|
|
105
|
+
for tgt_idx, src in enumerate(source_rows):
|
|
106
|
+
if src >= 0:
|
|
107
|
+
result[src] = values[tgt_idx]
|
|
108
|
+
return result
|
|
109
|
+
|
|
72
110
|
|
|
73
111
|
__all__ = ["ImplicitMany2OneLink", "ImplicitOne2ManyLink"]
|
|
@@ -129,13 +129,70 @@ class Many2OneLink(Link):
|
|
|
129
129
|
)
|
|
130
130
|
|
|
131
131
|
def has_role(self, role_value) -> numpy.ndarray:
|
|
132
|
-
"""Boolean mask: does each source member have the given role?
|
|
132
|
+
"""Boolean mask: does each source member have the given role?
|
|
133
|
+
|
|
134
|
+
The ``role`` array may contain raw values (ints, strings) or
|
|
135
|
+
``Role`` objects depending on how the population was built. When
|
|
136
|
+
``role_value`` is a string we compare against the ``key`` of each
|
|
137
|
+
element to make the API ergonomic for callers such as
|
|
138
|
+
``link.has_role("parent")`` or ``link.get_by_role(..., role_value="foo")``.
|
|
139
|
+
"""
|
|
133
140
|
r = self.role
|
|
134
141
|
if r is None:
|
|
135
142
|
msg = f"Link '{self.name}' has no role_field"
|
|
136
143
|
raise ValueError(msg)
|
|
144
|
+
|
|
145
|
+
# if array holds object references, convert to their keys
|
|
146
|
+
if r.dtype == object:
|
|
147
|
+
try:
|
|
148
|
+
keys = numpy.fromiter(
|
|
149
|
+
(getattr(x, "key", x) for x in r),
|
|
150
|
+
dtype=object,
|
|
151
|
+
)
|
|
152
|
+
except Exception:
|
|
153
|
+
# fallback to direct comparison
|
|
154
|
+
return r == role_value
|
|
155
|
+
return keys == role_value
|
|
156
|
+
|
|
157
|
+
# numpy will perform elementwise comparison for numeric or string
|
|
137
158
|
return r == role_value
|
|
138
159
|
|
|
160
|
+
# -- role-based access --------------------------------------------------
|
|
161
|
+
|
|
162
|
+
def get_by_role(
|
|
163
|
+
self,
|
|
164
|
+
variable_name: str,
|
|
165
|
+
period,
|
|
166
|
+
*,
|
|
167
|
+
role_value,
|
|
168
|
+
) -> numpy.ndarray:
|
|
169
|
+
"""Fetch a variable on the target only for members with a given role.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
variable_name : str
|
|
174
|
+
Name of the variable defined on the target entity.
|
|
175
|
+
period : Period
|
|
176
|
+
Period for which to calculate the variable.
|
|
177
|
+
role_value : object
|
|
178
|
+
The role to filter on (e.g. ``"parent"``).
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
numpy.ndarray
|
|
183
|
+
Array of shape ``(n_source,)`` where only members whose
|
|
184
|
+
``has_role(role_value)`` return ``True`` keep their computed
|
|
185
|
+
value; all others receive the variable's default (usually 0).
|
|
186
|
+
"""
|
|
187
|
+
mask = self.has_role(role_value)
|
|
188
|
+
result = self.get(variable_name, period)
|
|
189
|
+
# zero out non-matching rows using dtype-preserving fill
|
|
190
|
+
if not mask.all():
|
|
191
|
+
# create a copy to avoid mutating cached results
|
|
192
|
+
result = result.copy()
|
|
193
|
+
result[~mask] = 0
|
|
194
|
+
return result
|
|
195
|
+
|
|
139
196
|
# -- ID resolution ------------------------------------------------------
|
|
140
197
|
|
|
141
198
|
def _get_target_ids(self, period) -> numpy.ndarray:
|
|
@@ -169,6 +226,28 @@ class Many2OneLink(Link):
|
|
|
169
226
|
|
|
170
227
|
return rows
|
|
171
228
|
|
|
229
|
+
# -- ranking -----------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
def rank(self, variable_name: str, period) -> numpy.ndarray:
|
|
232
|
+
"""Rank each source member within its group by a variable value.
|
|
233
|
+
|
|
234
|
+
The rank is computed among all members sharing the same target
|
|
235
|
+
entity, sorted by the value of ``variable_name`` evaluated on the
|
|
236
|
+
*source* population. The lowest value receives rank ``0``.
|
|
237
|
+
|
|
238
|
+
This is essentially a thin wrapper around
|
|
239
|
+
:meth:`~openfisca_core.populations.Population.get_rank`:
|
|
240
|
+
|
|
241
|
+
>>> person = simulation.persons
|
|
242
|
+
>>> person.links['household'].rank('age', period)
|
|
243
|
+
array([...])
|
|
244
|
+
"""
|
|
245
|
+
source_pop = self._source_population
|
|
246
|
+
# criteria on source population
|
|
247
|
+
criteria = source_pop.simulation.calculate(variable_name, period)
|
|
248
|
+
# let Population.get_rank handle grouping and sorting
|
|
249
|
+
return source_pop.get_rank(self, criteria)
|
|
250
|
+
|
|
172
251
|
|
|
173
252
|
# ---------------------------------------------------------------------------
|
|
174
253
|
# Chained link getter
|
|
@@ -223,5 +302,9 @@ class _ChainedGetter:
|
|
|
223
302
|
target_entity = target_pop.entity
|
|
224
303
|
raise AttributeError(f"Entity '{target_entity.key}' has no link named '{name}'")
|
|
225
304
|
|
|
305
|
+
def rank(self, variable_name: str, period) -> numpy.ndarray:
|
|
306
|
+
# forward to outer link so that chaining keeps semantics
|
|
307
|
+
return self._outer.rank(variable_name, period)
|
|
308
|
+
|
|
226
309
|
|
|
227
310
|
__all__ = ["Many2OneLink"]
|
|
@@ -176,6 +176,70 @@ class One2ManyLink(Link):
|
|
|
176
176
|
c = self.count(period, role=role, condition=condition)
|
|
177
177
|
return numpy.where(c > 0, s / c, 0)
|
|
178
178
|
|
|
179
|
+
# -- positional and role-based accessors --------------------------------
|
|
180
|
+
|
|
181
|
+
def nth(
|
|
182
|
+
self,
|
|
183
|
+
n: int,
|
|
184
|
+
variable_name: str,
|
|
185
|
+
period,
|
|
186
|
+
*,
|
|
187
|
+
role=None,
|
|
188
|
+
condition: numpy.ndarray | None = None,
|
|
189
|
+
) -> numpy.ndarray:
|
|
190
|
+
"""Value of the n-th target member for each source entity.
|
|
191
|
+
|
|
192
|
+
Parameters mirror :meth:`sum` plus ``n``. If a source has fewer than
|
|
193
|
+
``n+1`` targets the default value ``0`` is returned for that source.
|
|
194
|
+
The ordering of targets is the same as encountered in the underlying
|
|
195
|
+
population arrays (i.e. no particular sort).
|
|
196
|
+
"""
|
|
197
|
+
values = self._target_values(variable_name, period)
|
|
198
|
+
source_rows, values = self._apply_filters(period, values, role, condition)
|
|
199
|
+
|
|
200
|
+
result = numpy.zeros(self._source_population.count, dtype=values.dtype)
|
|
201
|
+
# collect indices per source and pick the n-th
|
|
202
|
+
for src in range(self._source_population.count):
|
|
203
|
+
idxs = numpy.nonzero(source_rows == src)[0]
|
|
204
|
+
if n < len(idxs):
|
|
205
|
+
result[src] = values[idxs[n]]
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
def get_by_role(
|
|
209
|
+
self,
|
|
210
|
+
variable_name: str,
|
|
211
|
+
period,
|
|
212
|
+
role_value,
|
|
213
|
+
*,
|
|
214
|
+
condition: numpy.ndarray | None = None,
|
|
215
|
+
) -> numpy.ndarray:
|
|
216
|
+
"""Value of the target having a unique role per source.
|
|
217
|
+
|
|
218
|
+
``role_value`` is compared against the ``role_field`` on the target
|
|
219
|
+
population. If multiple targets share the same role for a given source
|
|
220
|
+
the last encountered value is returned (behaviour mirrors
|
|
221
|
+
``GroupPopulation.value_from_person``).
|
|
222
|
+
"""
|
|
223
|
+
if self.role_field is None:
|
|
224
|
+
raise ValueError("Link has no role_field")
|
|
225
|
+
|
|
226
|
+
values = self._target_values(variable_name, period)
|
|
227
|
+
source_rows = self._source_rows(period)
|
|
228
|
+
roles = self._target_population.simulation.calculate(
|
|
229
|
+
self.role_field,
|
|
230
|
+
"eternity",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
result = numpy.zeros(self._source_population.count, dtype=values.dtype)
|
|
234
|
+
mask = numpy.ones(len(source_rows), dtype=bool)
|
|
235
|
+
if condition is not None:
|
|
236
|
+
mask &= condition
|
|
237
|
+
|
|
238
|
+
for tgt_idx, src in enumerate(source_rows[mask]):
|
|
239
|
+
if roles[mask][tgt_idx] == role_value and src >= 0:
|
|
240
|
+
result[src] = values[mask][tgt_idx]
|
|
241
|
+
return result
|
|
242
|
+
|
|
179
243
|
# -- internal -----------------------------------------------------------
|
|
180
244
|
|
|
181
245
|
def _target_values(
|
|
@@ -119,3 +119,31 @@ def test_many2one_role_helpers(sim):
|
|
|
119
119
|
|
|
120
120
|
has_role_10 = link.has_role(10)
|
|
121
121
|
assert numpy.array_equal(has_role_10, [True, False, True, False])
|
|
122
|
+
|
|
123
|
+
# also exercise the new helper
|
|
124
|
+
parent_rents = link.get_by_role("rent", "2024", role_value=10)
|
|
125
|
+
assert numpy.array_equal(parent_rents, [800.0, 0.0, 500.0, 0.0])
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_many2one_rank(sim):
|
|
129
|
+
"""Ranking people by age within their household via the link.
|
|
130
|
+
|
|
131
|
+
The default ``sim`` fixture uses ``build_default_simulation`` which
|
|
132
|
+
does not populate ``household.members_entity_id`` correctly, so we
|
|
133
|
+
patch the group population manually using the input variable.
|
|
134
|
+
"""
|
|
135
|
+
# ensure household group mappings match the input variable
|
|
136
|
+
sim.household.members_entity_id = sim.persons("household_id", "2024")
|
|
137
|
+
# reset any cached position so rank uses updated mapping
|
|
138
|
+
sim.household._members_position = None
|
|
139
|
+
|
|
140
|
+
link = sim.persons.links["household"]
|
|
141
|
+
# ages [50, 25, 20, 5] per person
|
|
142
|
+
ranks = link.rank("age", "2024")
|
|
143
|
+
# households: h0->[50,25] -> ranks [1,0]; h1->[20,5] -> ranks [1,0]
|
|
144
|
+
assert numpy.array_equal(ranks, [1, 0, 1, 0])
|
|
145
|
+
|
|
146
|
+
# chaining should also forward to outer link (no behavioural change)
|
|
147
|
+
chained = sim.persons.links["mother"].household
|
|
148
|
+
ranks2 = chained.rank("age", "2024")
|
|
149
|
+
assert numpy.array_equal(ranks2, ranks)
|
{openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/_core_population.py
RENAMED
|
@@ -42,6 +42,12 @@ class CorePopulation:
|
|
|
42
42
|
|
|
43
43
|
#: The :class:`~simulations.Simulation` for which the population is calculated.
|
|
44
44
|
simulation: None | t.Simulation = None
|
|
45
|
+
# === Dynamic population helpers (optional) ===
|
|
46
|
+
_dynamic: bool = False
|
|
47
|
+
_permanent_ids: None | numpy.ndarray = None
|
|
48
|
+
_id_to_rownum: None | numpy.ndarray = None
|
|
49
|
+
_next_id: None | int = None
|
|
50
|
+
_period_index: None | dict = None
|
|
45
51
|
|
|
46
52
|
def __init__(self, entity: t.CoreEntity, *__args: object, **__kwds: object) -> None:
|
|
47
53
|
self.entity = entity
|
|
@@ -451,5 +457,31 @@ class CorePopulation:
|
|
|
451
457
|
by_variable=holders_memory_usage,
|
|
452
458
|
)
|
|
453
459
|
|
|
460
|
+
# -- Dynamic population period index helpers -------------------------
|
|
461
|
+
|
|
462
|
+
def snapshot_period(self, period: t.PeriodLike) -> None:
|
|
463
|
+
"""Save the current ``_id_to_rownum`` mapping for ``period``.
|
|
464
|
+
|
|
465
|
+
If no ``_id_to_rownum`` exists yet, a default identity mapping is
|
|
466
|
+
created (positions == ids) sized to ``self.count``.
|
|
467
|
+
"""
|
|
468
|
+
if self._period_index is None:
|
|
469
|
+
self._period_index = {}
|
|
470
|
+
|
|
471
|
+
if self._id_to_rownum is None:
|
|
472
|
+
# Default identity mapping: id -> row (0..count-1)
|
|
473
|
+
self._id_to_rownum = numpy.arange(self.count, dtype=numpy.intp)
|
|
474
|
+
|
|
475
|
+
self._period_index[periods.period(period)] = self._id_to_rownum.copy()
|
|
476
|
+
|
|
477
|
+
def get_period_id_to_rownum(self, period: t.PeriodLike) -> None | numpy.ndarray:
|
|
478
|
+
"""Return the saved ``id -> rownum`` mapping for ``period`` or ``None``.
|
|
479
|
+
|
|
480
|
+
Returns ``None`` when no index was saved for that period.
|
|
481
|
+
"""
|
|
482
|
+
if self._period_index is None:
|
|
483
|
+
return None
|
|
484
|
+
return self._period_index.get(periods.period(period))
|
|
485
|
+
|
|
454
486
|
|
|
455
487
|
__all__ = ["CorePopulation"]
|
|
@@ -30,6 +30,7 @@ class _BuildDefaultSimulation:
|
|
|
30
30
|
... .add_count()
|
|
31
31
|
... .add_ids()
|
|
32
32
|
... .add_members_entity_id()
|
|
33
|
+
... .add_id_to_rownum()
|
|
33
34
|
... )
|
|
34
35
|
|
|
35
36
|
>>> builder.count
|
|
@@ -121,6 +122,16 @@ class _BuildDefaultSimulation:
|
|
|
121
122
|
|
|
122
123
|
return self
|
|
123
124
|
|
|
125
|
+
def add_id_to_rownum(self) -> Self:
|
|
126
|
+
"""Set identity id_to_rownum mapping on all populations.
|
|
127
|
+
|
|
128
|
+
For static simulations, each entity's permanent ID equals its row
|
|
129
|
+
position, so id_to_rownum is the identity: id_to_rownum[i] = i.
|
|
130
|
+
"""
|
|
131
|
+
for population in self.populations.values():
|
|
132
|
+
population._id_to_rownum = numpy.arange(self.count, dtype=numpy.intp)
|
|
133
|
+
return self
|
|
134
|
+
|
|
124
135
|
def add_members_entity_id(self) -> Self:
|
|
125
136
|
"""Add ???
|
|
126
137
|
|
{openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/simulation_builder.py
RENAMED
|
@@ -327,6 +327,7 @@ class SimulationBuilder:
|
|
|
327
327
|
.add_count()
|
|
328
328
|
.add_ids()
|
|
329
329
|
.add_members_entity_id()
|
|
330
|
+
.add_id_to_rownum()
|
|
330
331
|
.simulation
|
|
331
332
|
)
|
|
332
333
|
|
|
@@ -659,6 +660,7 @@ class SimulationBuilder:
|
|
|
659
660
|
if plural_key in self.entity_counts:
|
|
660
661
|
population.count = self.get_count(plural_key)
|
|
661
662
|
population.ids = self.get_ids(plural_key)
|
|
663
|
+
population._id_to_rownum = numpy.arange(population.count, dtype=numpy.intp)
|
|
662
664
|
if plural_key in self.memberships:
|
|
663
665
|
population.members_entity_id = numpy.array(self.get_memberships(plural_key))
|
|
664
666
|
population.members_role = numpy.array(self.get_roles(plural_key))
|
|
@@ -174,6 +174,21 @@ class Variable:
|
|
|
174
174
|
"introspection_data",
|
|
175
175
|
)
|
|
176
176
|
|
|
177
|
+
self.as_of = self.set(
|
|
178
|
+
attr,
|
|
179
|
+
"as_of",
|
|
180
|
+
setter=self.set_as_of,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if self.as_of and self.set_input:
|
|
184
|
+
msg = (
|
|
185
|
+
f'Variable "{self.name}" declares both as_of and set_input, '
|
|
186
|
+
f"which are incompatible. set_input helpers like "
|
|
187
|
+
f"set_input_divide_by_period scatter values across sub-periods "
|
|
188
|
+
f"and have no meaningful semantics for as_of variables."
|
|
189
|
+
)
|
|
190
|
+
raise ValueError(msg)
|
|
191
|
+
|
|
177
192
|
formulas_attr, unexpected_attrs = helpers._partition(
|
|
178
193
|
attr,
|
|
179
194
|
lambda name, value: name.startswith(config.FORMULA_NAME_PREFIX),
|
|
@@ -297,6 +312,19 @@ class Variable:
|
|
|
297
312
|
return textwrap.dedent(documentation)
|
|
298
313
|
return None
|
|
299
314
|
|
|
315
|
+
def set_as_of(self, value):
|
|
316
|
+
if value is None or value is False:
|
|
317
|
+
return False
|
|
318
|
+
if value is True or value == "start":
|
|
319
|
+
return "start"
|
|
320
|
+
if value == "end":
|
|
321
|
+
return "end"
|
|
322
|
+
msg = (
|
|
323
|
+
f"Invalid value '{value}' for attribute 'as_of' in variable "
|
|
324
|
+
f"'{self.name}'. Allowed values are: True, 'start', 'end' (or False to disable)."
|
|
325
|
+
)
|
|
326
|
+
raise ValueError(msg)
|
|
327
|
+
|
|
300
328
|
def set_set_input(self, set_input):
|
|
301
329
|
if not set_input and self.baseline_variable:
|
|
302
330
|
return self.baseline_variable.set_input
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_formulas.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/in_memory_storage.py
RENAMED
|
File without changes
|
{openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/on_disk_storage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|