OpenFisca-Core 44.2.2__tar.gz → 44.3.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.3.0}/CHANGELOG.md +14 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0/OpenFisca_Core.egg-info}/PKG-INFO +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/OpenFisca_Core.egg-info/SOURCES.txt +15 -0
- {openfisca_core-44.2.2/OpenFisca_Core.egg-info → openfisca_core-44.3.0}/PKG-INFO +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/_core_entity.py +40 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/enum_array.py +2 -4
- openfisca_core-44.3.0/openfisca_core/links/__init__.py +36 -0
- openfisca_core-44.3.0/openfisca_core/links/implicit.py +73 -0
- openfisca_core-44.3.0/openfisca_core/links/link.py +109 -0
- openfisca_core-44.3.0/openfisca_core/links/many2one.py +227 -0
- openfisca_core-44.3.0/openfisca_core/links/one2many.py +242 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/__init__.py +58 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/test_benchmark.py +93 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/test_edge_cases.py +467 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/test_implicit.py +76 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/test_integration.py +192 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/test_many2one.py +121 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/test_one2many.py +112 -0
- openfisca_core-44.3.0/openfisca_core/links/tests/test_one2many_logic.py +398 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/populations/group_population.py +81 -21
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/populations/population.py +7 -5
- openfisca_core-44.3.0/openfisca_core/populations/tests/test_members_position.py +116 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/simulation.py +48 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/abstract_rate_tax_scale.py +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/abstract_tax_scale.py +2 -2
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/tax_scale_like.py +2 -2
- openfisca_core-44.3.0/openfisca_web_api/scripts/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/pyproject.toml +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/setup.py +1 -1
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/LICENSE +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/MANIFEST.in +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/OpenFisca_Core.egg-info/dependency_links.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/OpenFisca_Core.egg-info/entry_points.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/OpenFisca_Core.egg-info/requires.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/OpenFisca_Core.egg-info/top_level.txt +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/README.md +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/dummy.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/formulas.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/misc.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/rates.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/tests/test_dummy.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/tests/test_formulas.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/commons/tests/test_rates.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/data_storage/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/data_storage/in_memory_storage.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/data_storage/on_disk_storage.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/data_storage/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/_description.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/group_entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/role.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/tests/test_entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/tests/test_group_entity.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/tests/test_role.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/entities/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/cycle_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/empty_argument_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/nan_creation_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/parameter_not_found_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/parameter_parsing_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/period_mismatch_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/situation_parsing_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/spiral_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/variable_name_config_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/errors/variable_not_found_error.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/experimental/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/experimental/_errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/experimental/_memory_config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/formula_helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/holders/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/holders/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/holders/holder.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/holders/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/holders/tests/test_helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/holders/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/_enum_type.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/_errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/_guards.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/_utils.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/enum.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/tests/test_enum.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/tests/test_enum_array.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/indexed_enums/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/memory_config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/model_api.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/at_instant_like.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/parameter.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/parameter_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/parameter_node.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/parameter_node_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/parameter_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/parameter_scale_bracket.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/values_history.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/parameters/vectorial_parameter_node_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/_errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/_parsers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/date_unit.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/instant_.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/period_.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/py.typed +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/helpers/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/helpers/test_helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/helpers/test_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/helpers/test_period.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/test_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/test_parsers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/periods/tests/test_period.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/populations/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/populations/_core_population.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/populations/_errors.py +0 -0
- {openfisca_core-44.2.2/openfisca_core/scripts/assets → openfisca_core-44.3.0/openfisca_core/populations/tests}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/populations/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/projectors/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/projectors/entity_to_person_projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/projectors/first_person_to_entity_projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/projectors/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/projectors/projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/projectors/typing.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/projectors/unique_role_to_entity_projector.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/rates.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/reforms/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/reforms/in_yaml_test_reform.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/reforms/reform.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/reforms/reform_excel.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/__init__.py +0 -0
- {openfisca_core-44.2.2/openfisca_core/scripts/migrations → openfisca_core-44.3.0/openfisca_core/scripts/assets}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/assets/index.html +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/find_placeholders.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/measure_numpy_condition_notations.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/measure_performances.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.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.3.0/openfisca_core/scripts/migrations}/__init__.py +0 -0
- {openfisca_core-44.2.2/openfisca_core/variables/tests → openfisca_core-44.3.0/openfisca_core/scripts/migrations/v16_2_to_v17}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.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.3.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.3.0}/openfisca_core/scripts/migrations/v24_to_25.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/openfisca_command.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/remove_fuzzy.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/run_test.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/scripts/simulation_generator.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulation_builder.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/_build_default_simulation.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/_build_from_variables.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/_type_guards.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/simulation_builder.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/simulations/typing.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxbenefitsystems/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxbenefitsystems/tax_benefit_system.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/amount_tax_scale_like.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/helpers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/linear_average_rate_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/marginal_amount_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/marginal_rate_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/rate_tax_scale_like.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/taxscales/single_amount_tax_scale.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tools/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tools/parallel_plugin.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tools/simulation_dumper.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tools/test_runner.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/computation_log.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/flat_trace.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/full_tracer.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/performance_log.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/simple_tracer.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/trace_node.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/tracers/tracing_parameter_node_at_instant.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/types.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/variables/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/variables/config.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/variables/helpers.py +0 -0
- {openfisca_core-44.2.2/openfisca_web_api → openfisca_core-44.3.0/openfisca_core/variables/tests}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/variables/tests/test_definition_period.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/variables/variable.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/warnings/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/warnings/libyaml_warning.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_core/warnings/tempfile_warning.py +0 -0
- {openfisca_core-44.2.2/openfisca_web_api/scripts → openfisca_core-44.3.0/openfisca_web_api}/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/app.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/errors.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/handlers.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/loader/__init__.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/loader/entities.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/loader/parameters.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/loader/spec.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/loader/tax_benefit_system.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/loader/variables.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/openAPI.yml +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/openfisca_web_api/scripts/serve.py +0 -0
- {openfisca_core-44.2.2 → openfisca_core-44.3.0}/setup.cfg +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 44.3.0
|
|
4
|
+
|
|
5
|
+
#### New Features
|
|
6
|
+
|
|
7
|
+
- **Generic Entity Links (Phase 1-6)**: Introduced a new Liam2-inspired generic entity linking system avoiding rigid hierarchies like `Person -> Household`.
|
|
8
|
+
- Added new `Many2OneLink` and `One2ManyLink` models to create powerful inter-entity networks (e.g., `Person -> Employer`).
|
|
9
|
+
- Added implicit links directly binding members arrays. This powers the new `population.links` property natively inside `TaxBenefitSystem.instantiate_entities()`.
|
|
10
|
+
- Full capability to chain relationships via python: `person.mother.household.get("rent", period)`.
|
|
11
|
+
- Powerful vectorized declarative aggregations out-of-the-box (e.g., `households.persons.sum("salary", period, condition=is_female)`).
|
|
12
|
+
|
|
13
|
+
#### Technical Changes
|
|
14
|
+
|
|
15
|
+
- Backward compatibility is 100% maintained. Existing syntax via Projectors natively redirects to implicit links via modified `__getattr__`.
|
|
16
|
+
|
|
3
17
|
## 44.2.2
|
|
4
18
|
|
|
5
19
|
#### 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,
|
|
@@ -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,73 @@
|
|
|
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
|
+
mask &= roles == role
|
|
62
|
+
|
|
63
|
+
if condition is not None:
|
|
64
|
+
mask &= condition
|
|
65
|
+
|
|
66
|
+
source_rows = source_rows[mask]
|
|
67
|
+
values = values[mask]
|
|
68
|
+
|
|
69
|
+
valid = source_rows >= 0
|
|
70
|
+
return source_rows[valid], values[valid]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__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"]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Many-to-one link: N source members → 1 target entity."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import numpy
|
|
8
|
+
|
|
9
|
+
from .link import Link
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Many2OneLink(Link):
|
|
16
|
+
"""Navigate from many source members to one target entity.
|
|
17
|
+
|
|
18
|
+
Example: ``person.household`` (each person belongs to one household),
|
|
19
|
+
or ``person.mother`` (each person has one mother).
|
|
20
|
+
|
|
21
|
+
The resolution follows the LIAM2 pattern::
|
|
22
|
+
|
|
23
|
+
target_ids = source_pop(link_field) # e.g. [0, 0, 1, 2, 0]
|
|
24
|
+
target_values = target_pop(variable) # e.g. [800, 650, 900]
|
|
25
|
+
result = target_values[target_ids] # e.g. [800, 800, 650, 900, 800]
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def get(self, variable_name: str, period) -> numpy.ndarray:
|
|
29
|
+
"""Get a target variable's value for each source member.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
variable_name : str
|
|
34
|
+
Name of the variable defined on the target entity.
|
|
35
|
+
period : Period
|
|
36
|
+
The period for which to compute the variable.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
numpy.ndarray
|
|
41
|
+
Array of shape ``(n_source,)`` containing the target variable
|
|
42
|
+
value for each source member. Members with an invalid link
|
|
43
|
+
(target_id < 0) receive the variable's default value.
|
|
44
|
+
"""
|
|
45
|
+
source_pop = self._source_population
|
|
46
|
+
simulation = source_pop.simulation
|
|
47
|
+
|
|
48
|
+
# 1. Target IDs for each source member
|
|
49
|
+
target_ids = self._get_target_ids(period)
|
|
50
|
+
|
|
51
|
+
# 2. Variable values on the target entity
|
|
52
|
+
target_values = simulation.calculate(variable_name, period)
|
|
53
|
+
|
|
54
|
+
# 3. Resolve IDs to row positions (handles id_to_rownum if needed)
|
|
55
|
+
target_rows = self._resolve_ids(target_ids)
|
|
56
|
+
|
|
57
|
+
# 4. Gather with missing-value handling
|
|
58
|
+
variable = simulation.tax_benefit_system.get_variable(variable_name)
|
|
59
|
+
default = variable.default_value if variable else 0
|
|
60
|
+
|
|
61
|
+
from openfisca_core.indexed_enums import Enum, EnumArray
|
|
62
|
+
|
|
63
|
+
if isinstance(default, Enum):
|
|
64
|
+
default = default.index
|
|
65
|
+
|
|
66
|
+
result = numpy.full(
|
|
67
|
+
source_pop.count,
|
|
68
|
+
default,
|
|
69
|
+
dtype=target_values.dtype,
|
|
70
|
+
)
|
|
71
|
+
valid = target_rows >= 0
|
|
72
|
+
result[valid] = target_values[target_rows[valid]]
|
|
73
|
+
|
|
74
|
+
if isinstance(target_values, EnumArray):
|
|
75
|
+
result = EnumArray(result, target_values.possible_values)
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
# -- syntactic sugar ----------------------------------------------------
|
|
80
|
+
|
|
81
|
+
def __call__(self, variable_name: str, period) -> numpy.ndarray:
|
|
82
|
+
"""Shorthand: ``person.mother("age", period)``."""
|
|
83
|
+
return self.get(variable_name, period)
|
|
84
|
+
|
|
85
|
+
def __getattr__(self, name: str):
|
|
86
|
+
"""Chain links: ``person.mother.household``."""
|
|
87
|
+
if name.startswith("_"):
|
|
88
|
+
raise AttributeError(name)
|
|
89
|
+
|
|
90
|
+
target_pop = self._target_population
|
|
91
|
+
if target_pop is None:
|
|
92
|
+
raise AttributeError("Link is not bound to a simulation")
|
|
93
|
+
|
|
94
|
+
if hasattr(target_pop, "links") and name in target_pop.links:
|
|
95
|
+
target_link = target_pop.links[name]
|
|
96
|
+
return _ChainedGetter(self, target_link)
|
|
97
|
+
|
|
98
|
+
target_attr = getattr(target_pop, name, None)
|
|
99
|
+
if target_attr is not None:
|
|
100
|
+
if hasattr(target_attr, "projectable"):
|
|
101
|
+
|
|
102
|
+
def projector_function(*args, **kwargs):
|
|
103
|
+
result = target_attr(*args, **kwargs)
|
|
104
|
+
return self._project_result(result)
|
|
105
|
+
|
|
106
|
+
return projector_function
|
|
107
|
+
return target_attr
|
|
108
|
+
|
|
109
|
+
target_entity = target_pop.entity
|
|
110
|
+
msg = f"Entity '{target_entity.key}' has no link named '{name}'"
|
|
111
|
+
raise AttributeError(msg)
|
|
112
|
+
|
|
113
|
+
def _project_result(self, result: numpy.ndarray) -> numpy.ndarray:
|
|
114
|
+
if hasattr(self, "_project_implicit"):
|
|
115
|
+
return self._project_implicit(result)
|
|
116
|
+
msg = "Chained method calls computing arrays on explicit links are not supported because the period cannot be inferred."
|
|
117
|
+
raise NotImplementedError(msg)
|
|
118
|
+
|
|
119
|
+
# -- role helpers -------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def role(self) -> numpy.ndarray | None:
|
|
123
|
+
"""Role of each source member, if ``role_field`` is set."""
|
|
124
|
+
if self.role_field is None:
|
|
125
|
+
return None
|
|
126
|
+
return self._source_population.simulation.calculate(
|
|
127
|
+
self.role_field,
|
|
128
|
+
"eternity",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def has_role(self, role_value) -> numpy.ndarray:
|
|
132
|
+
"""Boolean mask: does each source member have the given role?"""
|
|
133
|
+
r = self.role
|
|
134
|
+
if r is None:
|
|
135
|
+
msg = f"Link '{self.name}' has no role_field"
|
|
136
|
+
raise ValueError(msg)
|
|
137
|
+
return r == role_value
|
|
138
|
+
|
|
139
|
+
# -- ID resolution ------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
def _get_target_ids(self, period) -> numpy.ndarray:
|
|
142
|
+
"""Fetch the target IDs from the link_field variable."""
|
|
143
|
+
return self._source_population.simulation.calculate(
|
|
144
|
+
self.link_field,
|
|
145
|
+
period,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def _resolve_ids(self, target_ids: numpy.ndarray) -> numpy.ndarray:
|
|
149
|
+
"""Convert target IDs to row indices.
|
|
150
|
+
|
|
151
|
+
If the target population has an ``_id_to_rownum`` mapping
|
|
152
|
+
(e.g. for intra-entity links where IDs ≠ row positions), use it.
|
|
153
|
+
Otherwise treat IDs as direct row indices (the OpenFisca convention
|
|
154
|
+
for GroupPopulation.members_entity_id).
|
|
155
|
+
"""
|
|
156
|
+
target_pop = self._target_population
|
|
157
|
+
rows = numpy.full_like(target_ids, -1, dtype=numpy.intp)
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
hasattr(target_pop, "_id_to_rownum")
|
|
161
|
+
and target_pop._id_to_rownum is not None
|
|
162
|
+
):
|
|
163
|
+
id_to_rownum = target_pop._id_to_rownum
|
|
164
|
+
valid = (target_ids >= 0) & (target_ids < len(id_to_rownum))
|
|
165
|
+
rows[valid] = id_to_rownum[target_ids[valid]]
|
|
166
|
+
else:
|
|
167
|
+
valid = (target_ids >= 0) & (target_ids < target_pop.count)
|
|
168
|
+
rows[valid] = target_ids[valid]
|
|
169
|
+
|
|
170
|
+
return rows
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Chained link getter
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class _ChainedGetter:
|
|
179
|
+
"""Intermediate object for link chaining: ``person.mother.household``."""
|
|
180
|
+
|
|
181
|
+
def __init__(self, outer_link: Many2OneLink, inner_link: Link) -> None:
|
|
182
|
+
self._outer = outer_link
|
|
183
|
+
self._inner = inner_link
|
|
184
|
+
|
|
185
|
+
def get(self, variable_name: str, period) -> numpy.ndarray:
|
|
186
|
+
"""Resolve ``person.mother.household.get("rent", period)``."""
|
|
187
|
+
# 1. Resolve inner link value on inner entity
|
|
188
|
+
inner_values = self._inner.get(variable_name, period)
|
|
189
|
+
|
|
190
|
+
# 2. Map back through outer link
|
|
191
|
+
target_ids = self._outer._source_population.simulation.calculate(
|
|
192
|
+
self._outer.link_field,
|
|
193
|
+
period,
|
|
194
|
+
)
|
|
195
|
+
target_rows = self._outer._resolve_ids(target_ids)
|
|
196
|
+
|
|
197
|
+
result = numpy.full(
|
|
198
|
+
self._outer._source_population.count,
|
|
199
|
+
0,
|
|
200
|
+
dtype=inner_values.dtype,
|
|
201
|
+
)
|
|
202
|
+
valid = target_rows >= 0
|
|
203
|
+
result[valid] = inner_values[target_rows[valid]]
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
def __call__(self, variable_name: str, period) -> numpy.ndarray:
|
|
207
|
+
"""Shorthand for get(): ``person.mother.household("rent", period)``."""
|
|
208
|
+
return self.get(variable_name, period)
|
|
209
|
+
|
|
210
|
+
def __getattr__(self, name: str):
|
|
211
|
+
"""Continue chaining: ``person.mother.household.region``."""
|
|
212
|
+
if name.startswith("_"):
|
|
213
|
+
raise AttributeError(name)
|
|
214
|
+
|
|
215
|
+
target_pop = self._inner._target_population
|
|
216
|
+
if target_pop is None:
|
|
217
|
+
raise AttributeError("Link is not bound to a simulation")
|
|
218
|
+
|
|
219
|
+
if hasattr(target_pop, "links") and name in target_pop.links:
|
|
220
|
+
next_link = target_pop.links[name]
|
|
221
|
+
return _ChainedGetter(self._outer, next_link)
|
|
222
|
+
|
|
223
|
+
target_entity = target_pop.entity
|
|
224
|
+
raise AttributeError(f"Entity '{target_entity.key}' has no link named '{name}'")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
__all__ = ["Many2OneLink"]
|