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.
Files changed (214) hide show
  1. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/CHANGELOG.md +33 -3
  2. {openfisca_core-44.3.0 → openfisca_core-44.5.0/OpenFisca_Core.egg-info}/PKG-INFO +1 -1
  3. {openfisca_core-44.3.0/OpenFisca_Core.egg-info → openfisca_core-44.5.0}/PKG-INFO +1 -1
  4. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/holder.py +65 -0
  5. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/implicit.py +39 -1
  6. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/many2one.py +84 -1
  7. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/one2many.py +64 -0
  8. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_many2one.py +28 -0
  9. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/_core_population.py +32 -0
  10. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/_build_default_simulation.py +11 -0
  11. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/simulation_builder.py +2 -0
  12. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/variable.py +28 -0
  13. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/setup.py +1 -1
  14. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/LICENSE +0 -0
  15. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/MANIFEST.in +0 -0
  16. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/SOURCES.txt +0 -0
  17. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/dependency_links.txt +0 -0
  18. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/entry_points.txt +0 -0
  19. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/requires.txt +0 -0
  20. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/OpenFisca_Core.egg-info/top_level.txt +0 -0
  21. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/README.md +0 -0
  22. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/__init__.py +0 -0
  23. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/__init__.py +0 -0
  24. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/dummy.py +0 -0
  25. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/formulas.py +0 -0
  26. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/misc.py +0 -0
  27. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/py.typed +0 -0
  28. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/rates.py +0 -0
  29. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/__init__.py +0 -0
  30. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_dummy.py +0 -0
  31. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_formulas.py +0 -0
  32. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/commons/tests/test_rates.py +0 -0
  33. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/__init__.py +0 -0
  34. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/in_memory_storage.py +0 -0
  35. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/on_disk_storage.py +0 -0
  36. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/data_storage/types.py +0 -0
  37. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/__init__.py +0 -0
  38. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/_core_entity.py +0 -0
  39. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/_description.py +0 -0
  40. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/entity.py +0 -0
  41. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/group_entity.py +0 -0
  42. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/helpers.py +0 -0
  43. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/py.typed +0 -0
  44. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/role.py +0 -0
  45. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/__init__.py +0 -0
  46. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_entity.py +0 -0
  47. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_group_entity.py +0 -0
  48. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/tests/test_role.py +0 -0
  49. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/entities/types.py +0 -0
  50. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/__init__.py +0 -0
  51. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/cycle_error.py +0 -0
  52. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/empty_argument_error.py +0 -0
  53. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/nan_creation_error.py +0 -0
  54. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/parameter_not_found_error.py +0 -0
  55. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/parameter_parsing_error.py +0 -0
  56. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/period_mismatch_error.py +0 -0
  57. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/situation_parsing_error.py +0 -0
  58. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/spiral_error.py +0 -0
  59. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/variable_name_config_error.py +0 -0
  60. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/errors/variable_not_found_error.py +0 -0
  61. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/experimental/__init__.py +0 -0
  62. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/experimental/_errors.py +0 -0
  63. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/experimental/_memory_config.py +0 -0
  64. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/formula_helpers.py +0 -0
  65. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/__init__.py +0 -0
  66. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/helpers.py +0 -0
  67. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/tests/__init__.py +0 -0
  68. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/tests/test_helpers.py +0 -0
  69. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/holders/types.py +0 -0
  70. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/__init__.py +0 -0
  71. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_enum_type.py +0 -0
  72. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_errors.py +0 -0
  73. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_guards.py +0 -0
  74. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/_utils.py +0 -0
  75. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/config.py +0 -0
  76. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/enum.py +0 -0
  77. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/enum_array.py +0 -0
  78. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/py.typed +0 -0
  79. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/__init__.py +0 -0
  80. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/test_enum.py +0 -0
  81. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/tests/test_enum_array.py +0 -0
  82. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/indexed_enums/types.py +0 -0
  83. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/__init__.py +0 -0
  84. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/link.py +0 -0
  85. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/__init__.py +0 -0
  86. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_benchmark.py +0 -0
  87. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_edge_cases.py +0 -0
  88. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_implicit.py +0 -0
  89. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_integration.py +0 -0
  90. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_one2many.py +0 -0
  91. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/links/tests/test_one2many_logic.py +0 -0
  92. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/memory_config.py +0 -0
  93. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/model_api.py +0 -0
  94. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/__init__.py +0 -0
  95. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/at_instant_like.py +0 -0
  96. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/config.py +0 -0
  97. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/helpers.py +0 -0
  98. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter.py +0 -0
  99. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_at_instant.py +0 -0
  100. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_node.py +0 -0
  101. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_node_at_instant.py +0 -0
  102. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_scale.py +0 -0
  103. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/parameter_scale_bracket.py +0 -0
  104. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/values_history.py +0 -0
  105. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/parameters/vectorial_parameter_node_at_instant.py +0 -0
  106. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/__init__.py +0 -0
  107. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/_errors.py +0 -0
  108. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/_parsers.py +0 -0
  109. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/config.py +0 -0
  110. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/date_unit.py +0 -0
  111. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/helpers.py +0 -0
  112. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/instant_.py +0 -0
  113. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/period_.py +0 -0
  114. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/py.typed +0 -0
  115. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/__init__.py +0 -0
  116. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/__init__.py +0 -0
  117. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_helpers.py +0 -0
  118. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_instant.py +0 -0
  119. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/helpers/test_period.py +0 -0
  120. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_instant.py +0 -0
  121. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_parsers.py +0 -0
  122. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/periods/tests/test_period.py +0 -0
  123. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/__init__.py +0 -0
  124. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/_errors.py +0 -0
  125. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/group_population.py +0 -0
  126. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/population.py +0 -0
  127. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/tests/__init__.py +0 -0
  128. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/tests/test_members_position.py +0 -0
  129. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/populations/types.py +0 -0
  130. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/__init__.py +0 -0
  131. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/entity_to_person_projector.py +0 -0
  132. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/first_person_to_entity_projector.py +0 -0
  133. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/helpers.py +0 -0
  134. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/projector.py +0 -0
  135. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/typing.py +0 -0
  136. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/projectors/unique_role_to_entity_projector.py +0 -0
  137. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/rates.py +0 -0
  138. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/__init__.py +0 -0
  139. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/in_yaml_test_reform.py +0 -0
  140. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/reform.py +0 -0
  141. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/reforms/reform_excel.py +0 -0
  142. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/__init__.py +0 -0
  143. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/assets/__init__.py +0 -0
  144. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/assets/index.html +0 -0
  145. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/find_placeholders.py +0 -0
  146. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_numpy_condition_notations.py +0 -0
  147. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_performances.py +0 -0
  148. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/measure_performances_fancy_indexing.py +0 -0
  149. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/__init__.py +0 -0
  150. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
  151. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +0 -0
  152. {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
  153. {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
  154. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/migrations/v24_to_25.py +0 -0
  155. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/openfisca_command.py +0 -0
  156. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/remove_fuzzy.py +0 -0
  157. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/run_test.py +0 -0
  158. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/scripts/simulation_generator.py +0 -0
  159. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulation_builder.py +0 -0
  160. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/__init__.py +0 -0
  161. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/_build_from_variables.py +0 -0
  162. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/_type_guards.py +0 -0
  163. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/helpers.py +0 -0
  164. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/simulation.py +0 -0
  165. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/simulations/typing.py +0 -0
  166. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxbenefitsystems/__init__.py +0 -0
  167. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxbenefitsystems/tax_benefit_system.py +0 -0
  168. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/__init__.py +0 -0
  169. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/abstract_rate_tax_scale.py +0 -0
  170. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/abstract_tax_scale.py +0 -0
  171. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/amount_tax_scale_like.py +0 -0
  172. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/helpers.py +0 -0
  173. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/linear_average_rate_tax_scale.py +0 -0
  174. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/marginal_amount_tax_scale.py +0 -0
  175. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/marginal_rate_tax_scale.py +0 -0
  176. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/rate_tax_scale_like.py +0 -0
  177. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/single_amount_tax_scale.py +0 -0
  178. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/taxscales/tax_scale_like.py +0 -0
  179. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/__init__.py +0 -0
  180. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/parallel_plugin.py +0 -0
  181. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/simulation_dumper.py +0 -0
  182. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tools/test_runner.py +0 -0
  183. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/__init__.py +0 -0
  184. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/computation_log.py +0 -0
  185. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/flat_trace.py +0 -0
  186. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/full_tracer.py +0 -0
  187. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/performance_log.py +0 -0
  188. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/simple_tracer.py +0 -0
  189. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/trace_node.py +0 -0
  190. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/tracers/tracing_parameter_node_at_instant.py +0 -0
  191. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/types.py +0 -0
  192. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/__init__.py +0 -0
  193. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/config.py +0 -0
  194. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/helpers.py +0 -0
  195. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/tests/__init__.py +0 -0
  196. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/variables/tests/test_definition_period.py +0 -0
  197. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/warnings/__init__.py +0 -0
  198. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/warnings/libyaml_warning.py +0 -0
  199. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_core/warnings/tempfile_warning.py +0 -0
  200. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/__init__.py +0 -0
  201. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/app.py +0 -0
  202. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/errors.py +0 -0
  203. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/handlers.py +0 -0
  204. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/__init__.py +0 -0
  205. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/entities.py +0 -0
  206. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/parameters.py +0 -0
  207. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/spec.py +0 -0
  208. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/tax_benefit_system.py +0 -0
  209. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/loader/variables.py +0 -0
  210. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/openAPI.yml +0 -0
  211. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/scripts/__init__.py +0 -0
  212. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/openfisca_web_api/scripts/serve.py +0 -0
  213. {openfisca_core-44.3.0 → openfisca_core-44.5.0}/pyproject.toml +0 -0
  214. {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.0
3
+ ## 44.5.0
4
4
 
5
- #### New Features
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 Changes
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenFisca-Core
3
- Version: 44.3.0
3
+ Version: 44.5.0
4
4
  Summary: A versatile microsimulation free software
5
5
  Home-page: https://github.com/openfisca/openfisca-core
6
6
  Author: OpenFisca Team
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenFisca-Core
3
- Version: 44.3.0
3
+ Version: 44.5.0
4
4
  Summary: A versatile microsimulation free software
5
5
  Home-page: https://github.com/openfisca/openfisca-core
6
6
  Author: OpenFisca Team
@@ -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
- mask &= roles == 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
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)
@@ -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
 
@@ -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
@@ -84,7 +84,7 @@ dev_requirements = [
84
84
 
85
85
  setup(
86
86
  name="OpenFisca-Core",
87
- version="44.3.0",
87
+ version="44.5.0",
88
88
  author="OpenFisca Team",
89
89
  author_email="contact@openfisca.org",
90
90
  classifiers=[
File without changes