OpenFisca-Core 44.2.2__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.2.2 → openfisca_core-44.5.0}/CHANGELOG.md +44 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0/OpenFisca_Core.egg-info}/PKG-INFO +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/SOURCES.txt +15 -0
- {openfisca_core-44.2.2/OpenFisca_Core.egg-info → openfisca_core-44.5.0}/PKG-INFO +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/_core_entity.py +40 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/holders/holder.py +65 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/enum_array.py +2 -4
- openfisca_core-44.5.0/openfisca_core/links/__init__.py +36 -0
- openfisca_core-44.5.0/openfisca_core/links/implicit.py +111 -0
- openfisca_core-44.5.0/openfisca_core/links/link.py +109 -0
- openfisca_core-44.5.0/openfisca_core/links/many2one.py +310 -0
- openfisca_core-44.5.0/openfisca_core/links/one2many.py +306 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/__init__.py +58 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/test_benchmark.py +93 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/test_edge_cases.py +467 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/test_implicit.py +76 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/test_integration.py +192 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/test_many2one.py +149 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/test_one2many.py +112 -0
- openfisca_core-44.5.0/openfisca_core/links/tests/test_one2many_logic.py +398 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/populations/_core_population.py +32 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/populations/group_population.py +81 -21
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/populations/population.py +7 -5
- openfisca_core-44.5.0/openfisca_core/populations/tests/test_members_position.py +116 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/_build_default_simulation.py +11 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/simulation.py +48 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/simulation_builder.py +2 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/abstract_rate_tax_scale.py +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/abstract_tax_scale.py +2 -2
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/tax_scale_like.py +2 -2
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/variables/variable.py +28 -0
- openfisca_core-44.5.0/openfisca_web_api/scripts/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/pyproject.toml +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/setup.py +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/LICENSE +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/MANIFEST.in +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/dependency_links.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/entry_points.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/requires.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/top_level.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/README.md +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/dummy.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/formulas.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/misc.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/rates.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_dummy.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_formulas.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_rates.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/data_storage/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/data_storage/in_memory_storage.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/data_storage/on_disk_storage.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/data_storage/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/_description.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/group_entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/role.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_group_entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_role.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/entities/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/cycle_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/empty_argument_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/nan_creation_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/parameter_not_found_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/parameter_parsing_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/period_mismatch_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/situation_parsing_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/spiral_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/variable_name_config_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/errors/variable_not_found_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/experimental/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/experimental/_errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/experimental/_memory_config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/formula_helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/holders/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/holders/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/holders/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/holders/tests/test_helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/holders/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_enum_type.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_guards.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_utils.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/enum.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/test_enum.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/test_enum_array.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/memory_config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/model_api.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/at_instant_like.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_node.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_node_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_scale_bracket.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/values_history.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/parameters/vectorial_parameter_node_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/_errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/_parsers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/date_unit.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/instant_.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/period_.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_period.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_parsers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_period.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/populations/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/populations/_errors.py +0 -0
- {openfisca_core-44.2.2/openfisca_core/scripts/assets → openfisca_core-44.5.0/openfisca_core/populations/tests}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/populations/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/projectors/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/projectors/entity_to_person_projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/projectors/first_person_to_entity_projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/projectors/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/projectors/projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/projectors/typing.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/projectors/unique_role_to_entity_projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/rates.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/reforms/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/reforms/in_yaml_test_reform.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/reforms/reform.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/reforms/reform_excel.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/__init__.py +0 -0
- {openfisca_core-44.2.2/openfisca_core/scripts/migrations → openfisca_core-44.5.0/openfisca_core/scripts/assets}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/assets/index.html +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/find_placeholders.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_numpy_condition_notations.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_performances.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_performances_fancy_indexing.py +0 -0
- {openfisca_core-44.2.2/openfisca_core/scripts/migrations/v16_2_to_v17 → openfisca_core-44.5.0/openfisca_core/scripts/migrations}/__init__.py +0 -0
- {openfisca_core-44.2.2/openfisca_core/variables/tests → openfisca_core-44.5.0/openfisca_core/scripts/migrations/v16_2_to_v17}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v24_to_25.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/openfisca_command.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/remove_fuzzy.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/run_test.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/scripts/simulation_generator.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulation_builder.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/_build_from_variables.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/_type_guards.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/simulations/typing.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxbenefitsystems/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxbenefitsystems/tax_benefit_system.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/amount_tax_scale_like.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/linear_average_rate_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/marginal_amount_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/marginal_rate_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/rate_tax_scale_like.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/taxscales/single_amount_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tools/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tools/parallel_plugin.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tools/simulation_dumper.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tools/test_runner.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/computation_log.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/flat_trace.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/full_tracer.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/performance_log.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/simple_tracer.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/trace_node.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/tracers/tracing_parameter_node_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/variables/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/variables/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/variables/helpers.py +0 -0
- {openfisca_core-44.2.2/openfisca_web_api → openfisca_core-44.5.0/openfisca_core/variables/tests}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/variables/tests/test_definition_period.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/warnings/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/warnings/libyaml_warning.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_core/warnings/tempfile_warning.py +0 -0
- {openfisca_core-44.2.2/openfisca_web_api/scripts → openfisca_core-44.5.0/openfisca_web_api}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/app.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/handlers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/loader/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/loader/entities.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/loader/parameters.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/loader/spec.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/loader/tax_benefit_system.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/loader/variables.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/openAPI.yml +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/openfisca_web_api/scripts/serve.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.5.0}/setup.cfg +0 -0
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 44.5.0
|
|
4
|
+
|
|
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
|
|
36
|
+
|
|
37
|
+
- **Generic Entity Links (Phase 1-6)**: Introduced a new Liam2-inspired generic entity linking system avoiding rigid hierarchies like `Person -> Household`.
|
|
38
|
+
- Added new `Many2OneLink` and `One2ManyLink` models to create powerful inter-entity networks (e.g., `Person -> Employer`).
|
|
39
|
+
- Added implicit links directly binding members arrays. This powers the new `population.links` property natively inside `TaxBenefitSystem.instantiate_entities()`.
|
|
40
|
+
- Full capability to chain relationships via python: `person.mother.household.get("rent", period)`.
|
|
41
|
+
- Powerful vectorized declarative aggregations out-of-the-box (e.g., `households.persons.sum("salary", period, condition=is_female)`).
|
|
42
|
+
|
|
43
|
+
#### Technical changes
|
|
44
|
+
|
|
45
|
+
- Backward compatibility is 100% maintained. Existing syntax via Projectors natively redirects to implicit links via modified `__getattr__`.
|
|
46
|
+
|
|
3
47
|
## 44.2.2
|
|
4
48
|
|
|
5
49
|
#### Bug fixes
|
|
@@ -78,6 +78,19 @@ openfisca_core/indexed_enums/types.py
|
|
|
78
78
|
openfisca_core/indexed_enums/tests/__init__.py
|
|
79
79
|
openfisca_core/indexed_enums/tests/test_enum.py
|
|
80
80
|
openfisca_core/indexed_enums/tests/test_enum_array.py
|
|
81
|
+
openfisca_core/links/__init__.py
|
|
82
|
+
openfisca_core/links/implicit.py
|
|
83
|
+
openfisca_core/links/link.py
|
|
84
|
+
openfisca_core/links/many2one.py
|
|
85
|
+
openfisca_core/links/one2many.py
|
|
86
|
+
openfisca_core/links/tests/__init__.py
|
|
87
|
+
openfisca_core/links/tests/test_benchmark.py
|
|
88
|
+
openfisca_core/links/tests/test_edge_cases.py
|
|
89
|
+
openfisca_core/links/tests/test_implicit.py
|
|
90
|
+
openfisca_core/links/tests/test_integration.py
|
|
91
|
+
openfisca_core/links/tests/test_many2one.py
|
|
92
|
+
openfisca_core/links/tests/test_one2many.py
|
|
93
|
+
openfisca_core/links/tests/test_one2many_logic.py
|
|
81
94
|
openfisca_core/parameters/__init__.py
|
|
82
95
|
openfisca_core/parameters/at_instant_like.py
|
|
83
96
|
openfisca_core/parameters/config.py
|
|
@@ -113,6 +126,8 @@ openfisca_core/populations/_errors.py
|
|
|
113
126
|
openfisca_core/populations/group_population.py
|
|
114
127
|
openfisca_core/populations/population.py
|
|
115
128
|
openfisca_core/populations/types.py
|
|
129
|
+
openfisca_core/populations/tests/__init__.py
|
|
130
|
+
openfisca_core/populations/tests/test_members_position.py
|
|
116
131
|
openfisca_core/projectors/__init__.py
|
|
117
132
|
openfisca_core/projectors/entity_to_person_projector.py
|
|
118
133
|
openfisca_core/projectors/first_person_to_entity_projector.py
|
|
@@ -47,6 +47,9 @@ class CoreEntity:
|
|
|
47
47
|
#: A ``TaxBenefitSystem`` instance.
|
|
48
48
|
_tax_benefit_system: None | t.TaxBenefitSystem = None
|
|
49
49
|
|
|
50
|
+
#: Named links to other entities (Many2One, One2Many, etc.).
|
|
51
|
+
_links: dict
|
|
52
|
+
|
|
50
53
|
@abc.abstractmethod
|
|
51
54
|
def __init__(self, *__args: object, **__kwargs: object) -> None: ...
|
|
52
55
|
|
|
@@ -57,6 +60,43 @@ class CoreEntity:
|
|
|
57
60
|
"""A ``CoreEntity`` belongs to a ``TaxBenefitSystem``."""
|
|
58
61
|
self._tax_benefit_system = tax_benefit_system
|
|
59
62
|
|
|
63
|
+
# -- Link management --------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def add_link(self, link) -> None:
|
|
66
|
+
"""Register a named link on this entity.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
link: A ``Link`` instance (Many2OneLink, One2ManyLink, etc.).
|
|
70
|
+
|
|
71
|
+
Example::
|
|
72
|
+
|
|
73
|
+
from openfisca_core.links import Many2OneLink
|
|
74
|
+
|
|
75
|
+
mother = Many2OneLink(
|
|
76
|
+
name="mother",
|
|
77
|
+
link_field="mother_id",
|
|
78
|
+
target_entity_key="person",
|
|
79
|
+
)
|
|
80
|
+
person_entity.add_link(mother)
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
if not hasattr(self, "_links") or self._links is None:
|
|
84
|
+
self._links = {}
|
|
85
|
+
self._links[link.name] = link
|
|
86
|
+
|
|
87
|
+
def get_link(self, name: str):
|
|
88
|
+
"""Retrieve a link by name, or ``None`` if not found."""
|
|
89
|
+
if not hasattr(self, "_links") or self._links is None:
|
|
90
|
+
return None
|
|
91
|
+
return self._links.get(name)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def links(self) -> dict:
|
|
95
|
+
"""All links registered on this entity."""
|
|
96
|
+
if not hasattr(self, "_links") or self._links is None:
|
|
97
|
+
self._links = {}
|
|
98
|
+
return self._links
|
|
99
|
+
|
|
60
100
|
def get_variable(
|
|
61
101
|
self,
|
|
62
102
|
variable_name: t.VariableName,
|
|
@@ -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
|
|
@@ -266,8 +266,7 @@ class EnumArray(t.EnumArray):
|
|
|
266
266
|
result: t.ObjArray
|
|
267
267
|
if self.possible_values is None:
|
|
268
268
|
msg = (
|
|
269
|
-
f"The possible values of the {self.__class__.__name__} are "
|
|
270
|
-
f"not defined."
|
|
269
|
+
f"The possible values of the {self.__class__.__name__} are not defined."
|
|
271
270
|
)
|
|
272
271
|
raise TypeError(msg)
|
|
273
272
|
array = self.reshape(1).astype(t.EnumDType) if self.ndim == 0 else self
|
|
@@ -301,8 +300,7 @@ class EnumArray(t.EnumArray):
|
|
|
301
300
|
result: t.StrArray
|
|
302
301
|
if self.possible_values is None:
|
|
303
302
|
msg = (
|
|
304
|
-
f"The possible values of the {self.__class__.__name__} are "
|
|
305
|
-
f"not defined."
|
|
303
|
+
f"The possible values of the {self.__class__.__name__} are not defined."
|
|
306
304
|
)
|
|
307
305
|
raise TypeError(msg)
|
|
308
306
|
array = self.reshape(1).astype(t.EnumDType) if self.ndim == 0 else self
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Links between entities (Many2One, One2Many, chaining).
|
|
2
|
+
|
|
3
|
+
This module implements a generic link system inspired by LIAM2, enriched
|
|
4
|
+
with OpenFisca's role semantics. Links are orthogonal to the existing
|
|
5
|
+
GroupEntity/Projector machinery: current code keeps working unchanged,
|
|
6
|
+
and links provide additional capabilities (intra-entity links, chaining,
|
|
7
|
+
arbitrary named links).
|
|
8
|
+
|
|
9
|
+
Usage in a country package::
|
|
10
|
+
|
|
11
|
+
from openfisca_core.links import Many2OneLink, One2ManyLink
|
|
12
|
+
|
|
13
|
+
# Declare a person→person link (intra-entity)
|
|
14
|
+
mother = Many2OneLink(
|
|
15
|
+
name="mother",
|
|
16
|
+
link_field="mother_id",
|
|
17
|
+
target_entity_key="person",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Declare a person→employer link (inter-entity)
|
|
21
|
+
employer = Many2OneLink(
|
|
22
|
+
name="employer",
|
|
23
|
+
link_field="employer_id",
|
|
24
|
+
target_entity_key="employer",
|
|
25
|
+
)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .link import Link
|
|
29
|
+
from .many2one import Many2OneLink
|
|
30
|
+
from .one2many import One2ManyLink
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"Link",
|
|
34
|
+
"Many2OneLink",
|
|
35
|
+
"One2ManyLink",
|
|
36
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Implicit links dynamically inferred from OpenFisca's GroupPopulation system."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
|
|
7
|
+
from .many2one import Many2OneLink
|
|
8
|
+
from .one2many import One2ManyLink
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ImplicitMany2OneLink(Many2OneLink):
|
|
12
|
+
"""A person → group link using GroupPopulation's internal arrays.
|
|
13
|
+
|
|
14
|
+
This bypasses the usual Variable lookup (``simulation.calculate``)
|
|
15
|
+
and directly reads ``members_entity_id`` and ``members_role`` from
|
|
16
|
+
the target GroupPopulation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, group_entity_key: str):
|
|
20
|
+
super().__init__(
|
|
21
|
+
name=group_entity_key,
|
|
22
|
+
link_field="", # Not used
|
|
23
|
+
target_entity_key=group_entity_key,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _get_target_ids(self, period) -> numpy.ndarray:
|
|
27
|
+
return self._target_population.members_entity_id
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def role(self) -> numpy.ndarray | None:
|
|
31
|
+
return self._target_population.members_role
|
|
32
|
+
|
|
33
|
+
def _project_implicit(self, result: numpy.ndarray) -> numpy.ndarray:
|
|
34
|
+
# Fully compatible with old Projector logic
|
|
35
|
+
return self._target_population.project(result)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ImplicitOne2ManyLink(One2ManyLink):
|
|
39
|
+
"""A group → person link using GroupPopulation's internal arrays."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, name: str, group_entity_key: str):
|
|
42
|
+
super().__init__(
|
|
43
|
+
name=name,
|
|
44
|
+
link_field="", # Not used
|
|
45
|
+
target_entity_key="person", # The target of the O2M is persons
|
|
46
|
+
)
|
|
47
|
+
self._group_entity_key = group_entity_key
|
|
48
|
+
|
|
49
|
+
def _source_rows(self, period) -> numpy.ndarray:
|
|
50
|
+
# For a group->person O2M, the source is the group, the target is the person.
|
|
51
|
+
# members_entity_id is an array of length person.count, containing the group index.
|
|
52
|
+
# So members_entity_id IS the array of source rows for each target member.
|
|
53
|
+
return self._source_population.members_entity_id
|
|
54
|
+
|
|
55
|
+
def _apply_filters(self, period, values, role, condition):
|
|
56
|
+
source_rows = self._source_rows(period)
|
|
57
|
+
mask = numpy.ones(len(source_rows), dtype=bool)
|
|
58
|
+
|
|
59
|
+
if role is not None:
|
|
60
|
+
roles = self._source_population.members_role
|
|
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
|
|
74
|
+
|
|
75
|
+
if condition is not None:
|
|
76
|
+
mask &= condition
|
|
77
|
+
|
|
78
|
+
source_rows = source_rows[mask]
|
|
79
|
+
values = values[mask]
|
|
80
|
+
|
|
81
|
+
valid = source_rows >= 0
|
|
82
|
+
return source_rows[valid], values[valid]
|
|
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
|
+
|
|
110
|
+
|
|
111
|
+
__all__ = ["ImplicitMany2OneLink", "ImplicitOne2ManyLink"]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Base Link class for entity relationships."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
import numpy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Link:
|
|
12
|
+
"""A named, directed relationship between two entity populations.
|
|
13
|
+
|
|
14
|
+
A Link is defined by:
|
|
15
|
+
|
|
16
|
+
- A *name* used to access the link from formulas (e.g. "mother", "household").
|
|
17
|
+
- A *link_field*: the name of the Variable (on the source entity) that holds
|
|
18
|
+
the target entity IDs.
|
|
19
|
+
- A *target_entity_key*: the entity key of the target population.
|
|
20
|
+
- Optionally, a *role_field* and *position_field* to attach OpenFisca-style
|
|
21
|
+
role and position metadata to the relationship.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Sub-classes ``Many2OneLink`` and ``One2ManyLink`` add resolution logic.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
name : str
|
|
29
|
+
Human-readable name of the link (e.g. ``"mother"``).
|
|
30
|
+
link_field : str
|
|
31
|
+
Variable name on the **source** entity that holds IDs pointing to
|
|
32
|
+
the target entity.
|
|
33
|
+
target_entity_key : str
|
|
34
|
+
The ``Entity.key`` of the target entity.
|
|
35
|
+
role_field : str or None
|
|
36
|
+
Optional variable name holding the role of each source member
|
|
37
|
+
within the target group.
|
|
38
|
+
position_field : str or None
|
|
39
|
+
Optional variable name holding the positional index of each
|
|
40
|
+
source member within the target group.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
name: str,
|
|
46
|
+
link_field: str,
|
|
47
|
+
target_entity_key: str,
|
|
48
|
+
*,
|
|
49
|
+
role_field: str | None = None,
|
|
50
|
+
position_field: str | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
self.name = name
|
|
53
|
+
self.link_field = link_field
|
|
54
|
+
self.target_entity_key = target_entity_key
|
|
55
|
+
self.role_field = role_field
|
|
56
|
+
self.position_field = position_field
|
|
57
|
+
|
|
58
|
+
# Resolved after simulation setup
|
|
59
|
+
self._source_population = None
|
|
60
|
+
self._target_population = None
|
|
61
|
+
|
|
62
|
+
# -- lifecycle ----------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def attach(self, source_population) -> None:
|
|
65
|
+
"""Bind this link to its source population."""
|
|
66
|
+
self._source_population = source_population
|
|
67
|
+
|
|
68
|
+
def resolve(self, populations: dict) -> None:
|
|
69
|
+
"""Resolve ``target_entity_key`` to an actual population object.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
populations : dict[str, Population]
|
|
74
|
+
All populations in the simulation, keyed by entity key.
|
|
75
|
+
"""
|
|
76
|
+
if self.target_entity_key not in populations:
|
|
77
|
+
msg = (
|
|
78
|
+
f"Link '{self.name}': target entity "
|
|
79
|
+
f"'{self.target_entity_key}' not found in populations "
|
|
80
|
+
f"{list(populations.keys())}"
|
|
81
|
+
)
|
|
82
|
+
raise KeyError(msg)
|
|
83
|
+
self._target_population = populations[self.target_entity_key]
|
|
84
|
+
|
|
85
|
+
# -- helpers ------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def is_resolved(self) -> bool:
|
|
89
|
+
return (
|
|
90
|
+
self._source_population is not None and self._target_population is not None
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _get_link_ids(self, period) -> numpy.ndarray:
|
|
94
|
+
"""Return the array of target IDs for every source member.
|
|
95
|
+
|
|
96
|
+
This reads the ``link_field`` variable from the source population.
|
|
97
|
+
"""
|
|
98
|
+
return self._source_population(self.link_field, period)
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
return (
|
|
102
|
+
f"{self.__class__.__name__}("
|
|
103
|
+
f"name={self.name!r}, "
|
|
104
|
+
f"link_field={self.link_field!r}, "
|
|
105
|
+
f"target={self.target_entity_key!r})"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
__all__ = ["Link"]
|