diff --git a/CLAUDE.md b/CLAUDE.md index ed53c3f..5b6d370 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,8 +93,8 @@ simulate_dataset() → Simulate states (with optional policy effects) computation using Kalman filtering. The debug variant is not jitted and returns intermediate results (residuals, contributions, filtered states). - **constraints.py**: Generates parameter constraints (bounds, equalities from stagemap, - fixed values) for optimization. Exports `get_constraints_dicts()`, - `constraints_dicts_to_om()`, `enforce_fixed_constraints()`, `add_bounds()`. + fixed values) for optimization. Exports `get_constraints()`, + `enforce_fixed_constraints()`, `add_bounds()`, `FixedConstraintWithValue`. - **parse_params.py**: Converts flat parameter vectors to structured model parameters. Exports `create_parsing_info()` and `parse_params()`. - **params_index.py**: Builds the `pd.MultiIndex` for the params DataFrame via @@ -194,7 +194,8 @@ These are not in `__all__` but are imported directly by application projects: - `skillmodels.process_model.process_model` — central to all application code - `skillmodels.types.ProcessedModel`, `EndogenousFactorsInfo` - `skillmodels.decorators.register_params` — essential for custom transition functions -- `skillmodels.constraints.constraints_dicts_to_om`, `enforce_fixed_constraints` +- `skillmodels.constraints.get_constraints`, `enforce_fixed_constraints`, + `FixedConstraintWithValue`, `select_by_loc` - `skillmodels.utilities.extract_factors`, `update_parameter_values` - `skillmodels.process_data.pre_process_data` - `skillmodels.correlation_heatmap.get_measurements_corr`, `get_quasi_scores_corr`, diff --git a/pixi.lock b/pixi.lock index 08153c9..031ce22 100644 --- a/pixi.lock +++ b/pixi.lock @@ -34,7 +34,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda @@ -78,7 +78,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -171,7 +171,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.5-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.6-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda @@ -316,7 +316,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda @@ -336,7 +336,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda @@ -419,7 +419,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.5-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.6-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda @@ -545,7 +545,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py314hf8a3a22_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda @@ -565,7 +565,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda @@ -643,7 +643,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py314hab283cf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.5-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.6-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314ha14b1ff_0.conda @@ -769,7 +769,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py314hf309875_4.conda @@ -867,7 +867,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.1.1-py314h61b30b5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.5-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.6-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_0.conda @@ -1008,7 +1008,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda @@ -1029,7 +1029,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -1116,7 +1116,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.5-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.6-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda @@ -1238,7 +1238,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py314hf8a3a22_4.conda @@ -1259,7 +1259,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -1341,7 +1341,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py314hab283cf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.5-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.6-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314ha14b1ff_0.conda @@ -1463,7 +1463,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py314hf309875_4.conda @@ -1563,7 +1563,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.1.1-py314h61b30b5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.5-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.6-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_0.conda @@ -1703,11 +1703,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.4-py314h67df5f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda @@ -1729,7 +1729,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -1813,7 +1813,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/memray-1.19.1-py314hef15ded_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/memray-1.19.2-py314hef15ded_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda @@ -1836,7 +1836,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.5-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.6-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda @@ -1964,11 +1964,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py314hf8a3a22_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.4-py314h6e9b3f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.5-py314h6e9b3f0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda @@ -1987,7 +1987,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -2052,7 +2052,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/memray-1.19.1-py314habef2a7_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/memray-1.19.2-py314h44d60dd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda @@ -2073,7 +2073,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py314hab283cf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.5-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.6-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314ha14b1ff_0.conda @@ -2201,11 +2201,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py314hf309875_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.13.4-py314h2359020_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.13.5-py314h2359020_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py314hb98de8c_0.conda @@ -2303,7 +2303,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.1.1-py314h61b30b5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.5-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.6-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_0.conda @@ -2448,11 +2448,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.4-py314h67df5f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.86-ha770c72_2.conda @@ -2497,7 +2497,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -2587,7 +2587,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/memray-1.19.1-py314hef15ded_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/memray-1.19.2-py314hef15ded_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda @@ -2610,7 +2610,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.5-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.6-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda @@ -2761,7 +2761,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda @@ -2782,7 +2782,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -2869,7 +2869,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.5-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.6-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda @@ -2994,7 +2994,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py314hf8a3a22_4.conda @@ -3015,7 +3015,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -3097,7 +3097,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py314hab283cf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.5-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.6-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314ha14b1ff_0.conda @@ -3222,7 +3222,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py314hf309875_4.conda @@ -3322,7 +3322,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.1.1-py314h61b30b5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.5-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.6-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_0.conda @@ -3990,17 +3990,17 @@ packages: - pkg:pypi/cffi?source=hash-mapping size: 294731 timestamp: 1761203441365 -- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - sha256: 05ea76a016c77839b64f9f8ec581775f6c8a259044bd5b45a177e46ab4e7feac - md5: beb628209b2b354b98203066f90b3287 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda + sha256: d86dfd428b2e3c364fa90e07437c8405d635aa4ef54b25ab51d9c712be4112a5 + md5: 49ee13eb9b8f44d63879c69b8a40a74b depends: - python >=3.10 license: MIT license_family: MIT purls: - pkg:pypi/charset-normalizer?source=compressed-mapping - size: 53210 - timestamp: 1772816516728 + size: 58510 + timestamp: 1773660086450 - pypi: https://files.pythonhosted.org/packages/b7/9f/d73dfb85d7a5b1a56a99adc50f2074029468168c970ff5daeade4ad819e4/choreographer-1.2.1-py3-none-any.whl name: choreographer version: 1.2.1 @@ -4100,9 +4100,9 @@ packages: - pkg:pypi/contourpy?source=hash-mapping size: 247437 timestamp: 1769155978556 -- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.4-py314h67df5f8_0.conda - sha256: b84aa99886610e0c3856ee1b577fe5c2552a5677bb7d281b4a86e79248813898 - md5: 6c7efc167cee337d9c41200506d022b8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda + sha256: cf5f98a291c3a5489cb299bae38711d5dc21b88a00df981f3b1528781e18c909 + md5: 78f547b78ace7541c4f54c4268ac9d2e depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -4112,12 +4112,12 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/coverage?source=hash-mapping - size: 412776 - timestamp: 1770720660175 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.4-py314h6e9b3f0_0.conda - sha256: e8ef991376e43f1248f0a16108b29534b24ad633cc4848927d7cf74622961ee9 - md5: 96c7bf4f2b5010d29604a62e91e634bb + - pkg:pypi/coverage?source=compressed-mapping + size: 411308 + timestamp: 1773761119353 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.5-py314h6e9b3f0_0.conda + sha256: 808ebcb57027251f379f84e53a3755d2851918f78bdd512d131afe40ca64a041 + md5: cdbafe4a3e605024e7372c9580f9d734 depends: - __osx >=11.0 - python >=3.14,<3.15.0a0 @@ -4127,12 +4127,12 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/coverage?source=hash-mapping - size: 412030 - timestamp: 1770720628409 -- conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.13.4-py314h2359020_0.conda - sha256: be570faa6580aa8ddeead7f3639ae27c46e02446613c85af9c3eab395f8dedd6 - md5: ac7417dc27e19765fd50f547d9a9e445 + - pkg:pypi/coverage?source=compressed-mapping + size: 412458 + timestamp: 1773761280047 +- conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.13.5-py314h2359020_0.conda + sha256: 80a6a7be7eef784b8314a4cb563563c654e2180a0b2b31b232f79b2e7334aaf2 + md5: 849f0bd5b83d4fd59b41202b21bb3ca2 depends: - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 @@ -4143,9 +4143,9 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/coverage?source=hash-mapping - size: 438221 - timestamp: 1770720521756 + - pkg:pypi/coverage?source=compressed-mapping + size: 438927 + timestamp: 1773760993379 - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda noarch: generic sha256: 91b06300879df746214f7363d6c27c2489c80732e46a369eb2afc234bcafb44c @@ -4893,28 +4893,26 @@ packages: - pkg:pypi/hyperframe?source=hash-mapping size: 17397 timestamp: 1737618427549 -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 - md5: 186a18e3ba246eccfc7cff00cd19a870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a + md5: c80d8a3b84358cb967fa81e7075fbc8a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libstdcxx >=14 license: MIT - license_family: MIT purls: [] - size: 12728445 - timestamp: 1767969922681 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda - sha256: 24bc62335106c30fecbcc1dba62c5eba06d18b90ea1061abd111af7b9c89c2d7 - md5: 114e6bfe7c5ad2525eb3597acdbf2300 + size: 12723451 + timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 + md5: f1182c91c0de31a7abd40cedf6a5ebef depends: - __osx >=11.0 license: MIT - license_family: MIT purls: [] - size: 12389400 - timestamp: 1772209104304 + size: 12361647 + timestamp: 1773822915649 - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 md5: 53abe63df7e10a6ba605dc5f9f961d36 @@ -7167,7 +7165,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping + - pkg:pypi/markupsafe?source=hash-mapping size: 27424 timestamp: 1772445227915 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda @@ -7331,9 +7329,9 @@ packages: - pkg:pypi/mdurl?source=hash-mapping size: 14465 timestamp: 1733255681319 -- conda: https://conda.anaconda.org/conda-forge/linux-64/memray-1.19.1-py314hef15ded_3.conda - sha256: 43801200d3b8dcaa1f9ab47f527c9fe94028780b2760173a240e132e25be2194 - md5: cc1bee6de727d07ce2dad51a5e8364b9 +- conda: https://conda.anaconda.org/conda-forge/linux-64/memray-1.19.2-py314hef15ded_0.conda + sha256: 575b877b4ad4f93c44532e99b4dd8fe402ce34bff63f87b420f141e81f647de6 + md5: 2951224e244e0e4a27e2c4b24b76ffba depends: - python - rich >=11.2.0 @@ -7343,17 +7341,17 @@ packages: - libgcc >=14 - __glibc >=2.17,<3.0.a0 - lz4-c >=1.10.0,<1.11.0a0 - - elfutils >=0.194,<0.195.0a0 - - libunwind >=1.8.3,<1.9.0a0 - python_abi 3.14.* *_cp314 + - libunwind >=1.8.3,<1.9.0a0 + - elfutils >=0.194,<0.195.0a0 license: Apache-2.0 AND BSD-3-Clause purls: - pkg:pypi/memray?source=hash-mapping - size: 1824670 - timestamp: 1765821568349 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/memray-1.19.1-py314habef2a7_3.conda - sha256: 912a462c888a867a22e6ebf0607ad3a42d078260cafc7d4f25654989a17f41ac - md5: f8c08fd9eb42146b0489d42069fb270e + size: 1845157 + timestamp: 1773493681427 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/memray-1.19.2-py314h44d60dd_0.conda + sha256: 5e053a64dbcdc98bd470f358f3fce1cde9b9fe362280a87cb66f1587e9f09e26 + md5: f29f08f053a687bcfe09089f8a410bc9 depends: - python - rich >=11.2.0 @@ -7362,13 +7360,13 @@ packages: - python 3.14.* *_cp314 - __osx >=11.0 - libcxx >=19 - - lz4-c >=1.10.0,<1.11.0a0 - python_abi 3.14.* *_cp314 + - lz4-c >=1.10.0,<1.11.0a0 license: Apache-2.0 AND BSD-3-Clause purls: - pkg:pypi/memray?source=hash-mapping - size: 1721669 - timestamp: 1765821674618 + size: 1744827 + timestamp: 1773493881871 - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda sha256: d3fb4beb5e0a52b6cc33852c558e077e1bfe44df1159eb98332d69a264b14bae md5: b11e360fc4de2b0035fc8aaa74f17fd6 @@ -8492,9 +8490,9 @@ packages: - pkg:pypi/pluggy?source=compressed-mapping size: 25877 timestamp: 1764896838868 -- conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.5-hb17b654_0.conda - sha256: d1e626c747c75ad7403eb6021a9781f96a2293ce79726e493c0691c1a8c9ee2e - md5: e965d5ef067ec4e1b4ee9d92760f4f03 +- conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.6-hb17b654_0.conda + sha256: a26627790776987421ecb130240dfd5c26e706d6811e173f7bdf3029bec13e1e + md5: 903cc9fafd676d3c13d9c1e71a52231a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -8503,11 +8501,11 @@ packages: license: MIT license_family: MIT purls: [] - size: 5584276 - timestamp: 1773071198111 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.5-h6fdd925_0.conda - sha256: d44b0678d3f24e4aa22fafce1779dc61c05f992d953ce89e7b8674f76eadaf62 - md5: 9328b05853b2044f8fd9cdb9e3ae532d + size: 5638792 + timestamp: 1773659796309 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.6-h6fdd925_0.conda + sha256: 9acb2a629c5cf8b423532a18bd385fa1e2b06b587ced8055e0075d2232a6903a + md5: 4b7ebf6d9d829849c264171a00cb7395 depends: - __osx >=11.0 constrains: @@ -8515,11 +8513,11 @@ packages: license: MIT license_family: MIT purls: [] - size: 5185941 - timestamp: 1773071302258 -- conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.5-h18a1a76_0.conda - sha256: 3901b6c6376840c1de23a3991551771317112faf9df57cd26b738bcec9f9893f - md5: e775b274325df883f90fb4a6b57579bc + size: 5203524 + timestamp: 1773660234368 +- conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.6-h18a1a76_0.conda + sha256: 31984152d475d6854a64786b9308dd180e2a4b014fea519b4959513e51dcab26 + md5: 042c6b6426dbeef69ec7aef3864427bf depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 @@ -8527,8 +8525,8 @@ packages: license: MIT license_family: MIT purls: [] - size: 5893739 - timestamp: 1773071227406 + size: 5913998 + timestamp: 1773659816255 - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda sha256: 75b2589159d04b3fb92db16d9970b396b9124652c784ab05b66f584edc97f283 md5: 7526d20621b53440b0aae45d4797847e @@ -9404,7 +9402,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping + - pkg:pypi/scipy?source=hash-mapping size: 13986990 timestamp: 1771881110844 - conda: https://conda.anaconda.org/conda-forge/win-64/scipy-1.17.1-py314h221f224_0.conda @@ -9499,10 +9497,10 @@ packages: timestamp: 1753199211006 - pypi: ./ name: skillmodels - version: 0.0.24.dev297+gb1606b889.d20260318 - sha256: c1f639ce783d330e50caa5f38c2ac5567c7cab2b2f932f6e58fe17c662684892 + version: 0.0.24.dev302+g350c3b9e5.d20260318 + sha256: c3e259ba2e68a9ccf4593eecfac180a8d2db8d26b757a19d3784ca917060e6f6 requires_dist: - - dags>=0.5 + - dags>=0.5.1 - jax>=0.9 - jupyter-book>=2 - kaleido>=1.2 @@ -9995,7 +9993,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/unicodedata2?source=compressed-mapping + - pkg:pypi/unicodedata2?source=hash-mapping size: 406126 timestamp: 1770909191618 - pypi: https://files.pythonhosted.org/packages/dd/1a/5d9a402b39ec892d856bbdd9db502ff73ce28cdf4aff72eb1ce1d6843506/universal_pathlib-0.3.10-py3-none-any.whl diff --git a/pyproject.toml b/pyproject.toml index 5a29b2c..49566e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ classifiers = [ ] dynamic = [ "version" ] dependencies = [ - "dags>=0.5", + "dags>=0.5.1", "jax>=0.9", "jupyter-book>=2", "kaleido>=1.2", diff --git a/src/skillmodels/constraints.py b/src/skillmodels/constraints.py index 9319da6..4285ac0 100644 --- a/src/skillmodels/constraints.py +++ b/src/skillmodels/constraints.py @@ -1,4 +1,4 @@ -"""List of constraints for a model, which can be converted to optimagic constraints.""" +"""Constraint objects for a model specification.""" import functools import warnings @@ -21,18 +21,50 @@ ) -def get_constraints_dicts( +def select_by_loc(params: pd.DataFrame, loc: Any) -> pd.DataFrame: # noqa: ANN401 + """Select parameters by location.""" + return params.loc[loc] + + +@dataclass(frozen=True) +class FixedConstraintWithValue(om.FixedConstraint): + """Fixed constraint that carries the target value and parameter location. + + `om.FixedConstraint` fixes parameters at their start values but does not carry a + target value. This wrapper adds `loc` (the parameter location in the params + DataFrame) and `value` (the value to set before optimization). + """ + + loc: pd.MultiIndex | tuple | str | None = None + """Parameter location in the params DataFrame.""" + value: float | None = None + """Value to enforce on the parameter.""" + + def __post_init__(self) -> None: + """Validate that `loc` and `value` are not None and derive `selector`.""" + if self.loc is None: + msg = "loc must not be None" + raise TypeError(msg) + if self.value is None: + msg = "value must not be None" + raise TypeError(msg) + object.__setattr__( + self, + "selector", + functools.partial(select_by_loc, loc=self.loc), + ) + + +def get_constraints( dimensions: Dimensions, labels: Labels, anchoring_info: Anchoring, update_info: pd.DataFrame, normalizations: Mapping[str, Normalizations], endogenous_factors_info: EndogenousFactorsInfo, -) -> list[dict]: +) -> list[om.constraints.Constraint]: """Generate constraints implied by the model specification. - The result can easily be converted to optimagic-style constraints. - Args: dimensions: Dimensional information like n_states, n_periods, n_controls, n_mixtures. See :ref:`dimensions`. @@ -46,47 +78,38 @@ def get_constraints_dicts( endogenous_factors_info: Information about endogenous factors in the model. Returns: - A list of constraints dictionaries with entries: - - "type": str, one of "fixed", "equality", "probability", "increasing", - "pairwise_equality". Must map to an optimagic constraint, see - :func:`constraints_dicts_to_om`. - - "loc": The location of the affected row(s) in the params DataFrame - - "value": float, only present if type is "fixed" - - "description": str, optional description of the constraint + List of optimagic constraint objects. """ - constraints_dicts = [] + constraints: list[om.constraints.Constraint] = [] - constraints_dicts += _get_normalization_constraints( + constraints += _get_normalization_constraints( normalizations=normalizations, factors=labels.latent_factors ) - constraints_dicts += _get_mixture_weights_constraints(dimensions.n_mixtures) - constraints_dicts += _get_stage_constraints( + constraints += _get_mixture_weights_constraints(dimensions.n_mixtures) + constraints += _get_stage_constraints( stagemap=labels.aug_stagemap, stages=labels.aug_stages, ) - constraints_dicts += _get_constant_factors_constraints(labels=labels) - constraints_dicts += _get_initial_states_constraints( + constraints += _get_constant_factors_constraints(labels=labels) + constraints += _get_initial_states_constraints( n_mixtures=dimensions.n_mixtures, factors=labels.latent_factors, ) - constraints_dicts += _get_transition_constraints(labels=labels) - constraints_dicts += _get_anchoring_constraints( + constraints += _get_transition_constraints(labels=labels) + constraints += _get_anchoring_constraints( update_info=update_info, controls=labels.controls, anchoring_info=anchoring_info, periods=labels.aug_periods, ) if endogenous_factors_info.has_endogenous_factors: - constraints_dicts += _get_constraints_for_augmented_periods( + constraints += _get_constraints_for_augmented_periods( labels=labels, endogenous_factors_info=endogenous_factors_info, ) - for i, c in enumerate(constraints_dicts): - c["id"] = i - - return constraints_dicts + return constraints def add_bounds(params: pd.DataFrame, bounds_distance: float) -> pd.DataFrame: @@ -142,7 +165,7 @@ def _is_diagonal_entry(ind_tup: tuple[str, ...]) -> bool: def _get_normalization_constraints( normalizations: Mapping[str, Normalizations], factors: tuple[str, ...], -) -> list[dict]: +) -> list[om.constraints.Constraint]: """List of constraints to enforce normalizations. Args: @@ -150,102 +173,83 @@ def _get_normalization_constraints( factors: Tuple of factor names to process. Returns: - constraints_dicts + List of constraint objects. """ - msg = "This constraint was generated because of an explicit normalization." periods = range(len(normalizations[factors[0]].loadings)) - constraints_dicts = [] + constraints: list[om.constraints.Constraint] = [] for factor in factors: for period in periods: for meas, normval in normalizations[factor].loadings[period].items(): - constraints_dicts.append( - { - "loc": ("loadings", period, meas, factor), - "type": "fixed", - "value": normval, - "description": msg, - } - ) + loc = ("loadings", period, meas, factor) + constraints.append(FixedConstraintWithValue(loc=loc, value=normval)) for meas, normval in normalizations[factor].intercepts[period].items(): - constraints_dicts.append( - { - "loc": ("controls", period, meas, "constant"), - "type": "fixed", - "value": normval, - "description": msg, - } - ) + loc = ("controls", period, meas, "constant") + constraints.append(FixedConstraintWithValue(loc=loc, value=normval)) - return constraints_dicts + return constraints -def _get_mixture_weights_constraints(n_mixtures: int) -> list[dict]: +def _get_mixture_weights_constraints( + n_mixtures: int, +) -> list[om.constraints.Constraint]: """Constrain mixture weights to be between 0 and 1 and sum to 1.""" + loc = "mixture_weights" if n_mixtures == 1: - msg = "Set the mixture weight to 1 if there is only one mixture element." - constraints_dicts = [ - { - "loc": "mixture_weights", - "type": "fixed", - "value": 1.0, - "description": msg, - }, - ] - else: - msg = "Ensure that weights are between 0 and 1 and sum to 1." - constraints_dicts = [ - {"loc": "mixture_weights", "type": "probability", "description": msg} + return [ + FixedConstraintWithValue(loc=loc, value=1.0), ] - return constraints_dicts + return [ + om.ProbabilityConstraint(selector=functools.partial(select_by_loc, loc=loc)) + ] def _get_stage_constraints( stagemap: tuple[int, ...], stages: tuple[int, ...], -) -> list[dict]: +) -> list[om.constraints.Constraint]: """Equality constraints for transition and shock parameters within stages. Args: stagemap: map aug_periods to aug_stages stages: aug_stages + Returns: - constraints_dicts + List of constraint objects. """ - msg = ( - "This constraint was generated because all involved periods belong to stage {}." - ) - constraints_dicts = [] + constraints: list[om.constraints.Constraint] = [] - stages_to_periods = {stage: [] for stage in stages} + stages_to_periods: dict[int, list[int]] = {stage: [] for stage in stages} for aug_period, stage in enumerate(stagemap): stages_to_periods[stage].append(aug_period) - for stage, stage_periods in stages_to_periods.items(): + for stage_periods in stages_to_periods.values(): if len(stage_periods) > 1: loc_trans = [("transition", p) for p in stage_periods] loc_q = [("shock_sds", p) for p in stage_periods] - constraints_dicts.append( - { - "loc": loc_trans, - "type": "pairwise_equality", - "description": msg.format(stage), - }, + constraints.append( + om.PairwiseEqualityConstraint( + selectors=[ + functools.partial(select_by_loc, loc=loc) for loc in loc_trans + ], + ), ) - constraints_dicts.append( - { - "loc": loc_q, - "type": "pairwise_equality", - "description": msg.format(stage), - }, + constraints.append( + om.PairwiseEqualityConstraint( + selectors=[ + functools.partial(select_by_loc, loc=loc) for loc in loc_q + ], + ), ) - return constraints_dicts + return constraints -def _get_constant_factors_constraints(labels: Labels) -> list[dict]: +def _get_constant_factors_constraints( + labels: Labels, +) -> list[om.constraints.Constraint]: """Fix shock variances of constant factors to zero. Args: @@ -253,29 +257,24 @@ def _get_constant_factors_constraints(labels: Labels) -> list[dict]: factors, periods, controls, stagemap and stages. See :ref:`labels` Returns: - constraints_dicts + List of constraint objects. """ - constraints_dicts = [] + constraints: list[om.constraints.Constraint] = [] for f, factor in enumerate(labels.latent_factors): if labels.transition_names[f] == "constant": - msg = f"This constraint was generated because {factor} is constant." for aug_period in labels.aug_periods[:-1]: - constraints_dicts.append( - { - "loc": ("shock_sds", aug_period, factor, "-"), - "type": "fixed", - "value": 0.0, - "description": msg, - }, + loc = ("shock_sds", aug_period, factor, "-") + constraints.append( + FixedConstraintWithValue(loc=loc, value=0.0), ) - return constraints_dicts + return constraints def _get_initial_states_constraints( n_mixtures: int, factors: tuple[str, ...], -) -> list[dict]: +) -> list[om.constraints.Constraint]: """Enforce that the x values of the first factor are increasing. Otherwise the model would only be identified up to the order of the start factors. @@ -285,28 +284,23 @@ def _get_initial_states_constraints( factors: the latent factors of the model Returns: - constraints_dicts + List of constraint objects. """ - msg = ( - "This constraint enforces an ordering on the initial means of the states " - "across the components of the factor distribution. This is necessary to ensure " - "uniqueness of the maximum likelihood estimator." - ) - if n_mixtures > 1: locs = [ ("initial_states", 0, f"mixture_{emf}", factors[0]) for emf in range(n_mixtures) ] - constraints_dicts = [{"loc": locs, "type": "increasing", "description": msg}] - else: - constraints_dicts = [] - - return constraints_dicts + return [ + om.IncreasingConstraint(selector=functools.partial(select_by_loc, loc=locs)) + ] + return [] -def _get_transition_constraints(labels: Labels) -> list[dict]: +def _get_transition_constraints( + labels: Labels, +) -> list[om.constraints.Constraint]: """Collect possible constraints on transition parameters. Args: @@ -314,31 +308,31 @@ def _get_transition_constraints(labels: Labels) -> list[dict]: factors, periods, controls, stagemap and stages. See :ref:`labels` Returns: - constraints_dicts + List of constraint objects. """ - constraints_dicts = [] + constraints: list[om.constraints.Constraint] = [] for f, factor in enumerate(labels.latent_factors): tname = labels.transition_names[f] - msg = f"This constraint is inherent to the {tname} production function." for aug_period in labels.aug_periods[:-1]: funcname = f"constraints_{tname}" if func := getattr(t_f_module, funcname, False): - c = func( # ty: ignore[call-non-callable] - factor=factor, factors=labels.all_factors, aug_period=aug_period + constraints.append( + func( # ty: ignore[call-non-callable] + factor=factor, + factors=labels.all_factors, + aug_period=aug_period, + ) ) - if "description" not in c: - c["description"] = msg - constraints_dicts.append(c) - return constraints_dicts + return constraints -def _get_anchoring_constraints( +def _get_anchoring_constraints( # noqa: C901 update_info: pd.DataFrame, controls: tuple[str, ...], anchoring_info: Anchoring, periods: tuple[int, ...], -) -> list[dict]: +) -> list[om.constraints.Constraint]: """Constraints on anchoring parameters. Args: @@ -349,42 +343,34 @@ def _get_anchoring_constraints( periods: Period of the model Returns: - constraints_dicts + List of constraint objects. """ anchoring_updates = update_info[update_info["purpose"] == "anchoring"].index - constraints_dicts = [] + constraints: list[om.constraints.Constraint] = [] if not anchoring_info.free_constant: - msg = ( - "This constraint was generated because free_constant in the anchoring " - "section of the model specification is set to False." - ) locs = [] for period, meas in anchoring_updates: locs.append(("controls", period, meas, "constant")) - constraints_dicts.append( - {"loc": locs, "type": "fixed", "value": 0, "description": msg}, - ) + if locs: + loc = tuple(locs) + constraints.append( + FixedConstraintWithValue(loc=loc, value=0), + ) if not anchoring_info.free_controls: - msg = ( - "This constraint was generated because free_controls in the anchoring " - "section of the model specification is set to False." - ) ind_tups = [] for period, meas in anchoring_updates: for cont in [c for c in controls if c != "constant"]: ind_tups.append(("controls", period, meas, cont)) - constraints_dicts.append( - {"loc": ind_tups, "type": "fixed", "value": 0, "description": msg}, - ) + if ind_tups: + loc = tuple(ind_tups) + constraints.append( + FixedConstraintWithValue(loc=loc, value=0), + ) if not anchoring_info.free_loadings: - msg = ( - "This constraint was generated because free_loadings in the anchoring " - "section of the model specification is set to False." - ) ind_tups = [] for period in periods: for factor in anchoring_info.factors: @@ -392,17 +378,19 @@ def _get_anchoring_constraints( meas = f"{outcome}_{factor}" ind_tups.append(("loadings", period, meas, factor)) - constraints_dicts.append( - {"loc": ind_tups, "type": "fixed", "value": 1, "description": msg}, - ) + if ind_tups: + loc = tuple(ind_tups) + constraints.append( + FixedConstraintWithValue(loc=loc, value=1), + ) - return [c for c in constraints_dicts if c["loc"] != []] + return constraints def _get_constraints_for_augmented_periods( labels: Labels, endogenous_factors_info: EndogenousFactorsInfo, -) -> list[dict]: +) -> list[om.constraints.Constraint]: """Constraints for augmented periods. - Carry forward states from uneven periods to even periods @@ -418,10 +406,10 @@ def _get_constraints_for_augmented_periods( relationship to augmented periods. Returns: - constraints_dicts + List of constraint objects. """ - constraints_dicts = [] + constraints: list[om.constraints.Constraint] = [] for f, factor in enumerate(labels.latent_factors): tname = labels.transition_names[f] if tname == "constant": @@ -443,135 +431,26 @@ def _get_constraints_for_augmented_periods( ] for aug_period in aug_periods_to_constrain: if func := getattr(t_f_module, f"identity_constraints_{tname}", False): - constraints_dicts += func( # ty: ignore[call-non-callable] + constraints += func( # ty: ignore[call-non-callable] factor=factor, aug_period=aug_period, all_factors=labels.all_factors, ) for aug_period in aug_periods_to_constrain[:-1]: - constraints_dicts.append( - { - "loc": ("shock_sds", aug_period, factor, "-"), - "type": "fixed", - "value": endogenous_factors_info.bounds_distance, - "description": "Identity constraint.", - } - ) - - return constraints_dicts - - -def select_by_loc(params: pd.DataFrame, loc: Any) -> pd.DataFrame: # noqa: ANN401 - """Select parameters by location.""" - return params.loc[loc] - - -@dataclass(frozen=True) -class SkillmodelsPairwiseEqualityConstraint(om.PairwiseEqualityConstraint): - """Thin wrapper around om.PairwiseEqualityConstraint. - - Adds fields to preserve information from the internal constraints dictionary. - """ - - loc: pd.MultiIndex | tuple | str | None = None - description: str | None = None - type: str = "Just to be able to use **constraints_dict" - id: int | None = None - - -@dataclass(frozen=True) -class SkillmodelsFixedConstraint(om.FixedConstraint): - """Thin wrapper around om.FixedConstraint. - - Adds fields to preserve information from the internal constraints dictionary. - """ - - loc: pd.MultiIndex | tuple | str | None = None - description: str | None = None - type: str = "Just to be able to use **constraints_dict" - id: int | None = None - value: float | None = None - - -@dataclass(frozen=True) -class SkillmodelsEqualityConstraint(om.EqualityConstraint): - """Thin wrapper around om.EqualityConstraint. - - Adds fields to preserve information from the internal constraints dictionary. - """ - - loc: pd.MultiIndex | tuple | str | None = None - description: str | None = None - type: str = "Just to be able to use **constraints_dict" - id: int | None = None - - -@dataclass(frozen=True) -class SkillmodelsProbabilityConstraint(om.ProbabilityConstraint): - """Thin wrapper around om.ProbabilityConstraint. - - Adds fields to preserve information from the internal constraints dictionary. - """ - - loc: pd.MultiIndex | tuple | str | None = None - description: str | None = None - type: str = "Just to be able to use **constraints_dict" - id: int | None = None - - -@dataclass(frozen=True) -class SkillmodelsIncreasingConstraint(om.IncreasingConstraint): - """Thin wrapper around om.IncreasingConstraint. - - Adds fields to preserve information from the internal constraints dictionary. - """ - - loc: pd.MultiIndex | tuple | str | None = None - description: str | None = None - type: str = "Just to be able to use **constraints_dict" - id: int | None = None - - -def constraints_dicts_to_om( - constraints_dicts: list[dict], -) -> list[om.constraints.Constraint]: - """Convert constraints provided in dictionary form to optimagic constraints. - - Args: - constraints_dicts: see :ref:`get_constraints_dicts`. - - Returns: - List of optimagic constraints. - """ - om_style = [] - for c_d in constraints_dicts: - if c_d["type"] == "pairwise_equality": - om_style.append( - SkillmodelsPairwiseEqualityConstraint( - selectors=[ - functools.partial(select_by_loc, loc=loc) for loc in c_d["loc"] - ], - **c_d, + loc = ("shock_sds", aug_period, factor, "-") + constraints.append( + FixedConstraintWithValue( + loc=loc, + value=endogenous_factors_info.bounds_distance, ) ) - else: - sel = functools.partial(select_by_loc, loc=c_d["loc"]) - if c_d["type"] == "fixed": - om_style.append(SkillmodelsFixedConstraint(selector=sel, **c_d)) - elif c_d["type"] == "equality": - om_style.append(SkillmodelsEqualityConstraint(selector=sel, **c_d)) - elif c_d["type"] == "probability": - om_style.append(SkillmodelsProbabilityConstraint(selector=sel, **c_d)) - elif c_d["type"] == "increasing": - om_style.append(SkillmodelsIncreasingConstraint(selector=sel, **c_d)) - else: - raise TypeError(c_d["type"]) - return om_style + + return constraints def enforce_fixed_constraints( params_template: pd.DataFrame, - constraints_dicts: list[dict[str, Any]], + constraints: list[om.constraints.Constraint], ) -> pd.DataFrame: """Enforce fixed constraints on params_template. @@ -580,7 +459,7 @@ def enforce_fixed_constraints( Args: params_template: see :ref:`params_df`. - constraints_dicts: see :ref:`get_constraints_dicts`. + constraints: list of optimagic constraint objects. Returns: pd.DataFrame: modified copy of params_template @@ -591,9 +470,9 @@ def enforce_fixed_constraints( "ignore", message="indexing past lexsort depth may impact performance.", ) - for constraint in constraints_dicts: - if constraint["type"] == "fixed": - params.loc[constraint["loc"], "value"] = constraint["value"] + for constraint in constraints: + if isinstance(constraint, FixedConstraintWithValue): + params.loc[constraint.loc, "value"] = constraint.value # Setting via loc may expand the index, so reduce to the original index return params.loc[params_template.index].astype(float) diff --git a/src/skillmodels/maximization_inputs.py b/src/skillmodels/maximization_inputs.py index 31d0cda..4d3dcd3 100644 --- a/src/skillmodels/maximization_inputs.py +++ b/src/skillmodels/maximization_inputs.py @@ -15,9 +15,8 @@ import skillmodels.likelihood_function_debug as lfd from skillmodels.constraints import ( add_bounds, - constraints_dicts_to_om, enforce_fixed_constraints, - get_constraints_dicts, + get_constraints, ) from skillmodels.kalman_filters import calculate_sigma_scaling_factor_and_weights from skillmodels.model_spec import ModelSpec @@ -165,7 +164,7 @@ def debug_loglike(params: pd.DataFrame) -> dict[str, Any]: tmp["value"] = float(tmp["value"]) return process_debug_data(debug_data=tmp, model=processed_model) - _constraints_dicts = get_constraints_dicts( + constraints = get_constraints( dimensions=processed_model.dimensions, labels=processed_model.labels, anchoring_info=processed_model.anchoring, @@ -174,8 +173,6 @@ def debug_loglike(params: pd.DataFrame) -> dict[str, Any]: endogenous_factors_info=processed_model.endogenous_factors_info, ) - constraints = constraints_dicts_to_om(_constraints_dicts) - params_template = pd.DataFrame(columns=["value"], index=p_index) params_template = add_bounds( params=params_template, @@ -183,7 +180,7 @@ def debug_loglike(params: pd.DataFrame) -> dict[str, Any]: ) params_template = enforce_fixed_constraints( params_template=params_template, - constraints_dicts=_constraints_dicts, + constraints=constraints, ) if not params_template.index.equals(p_index): raise ValueError("params_template index is not equal to p_index") diff --git a/src/skillmodels/transition_functions.py b/src/skillmodels/transition_functions.py index 057ff96..9e38461 100644 --- a/src/skillmodels/transition_functions.py +++ b/src/skillmodels/transition_functions.py @@ -27,12 +27,23 @@ """ +import functools from itertools import combinations +from typing import TYPE_CHECKING, Any import jax import jax.numpy as jnp +import optimagic as om from jax import Array +if TYPE_CHECKING: + from skillmodels.constraints import FixedConstraintWithValue + + +def select_by_loc(params: Any, loc: Any) -> Any: # noqa: ANN401 + """Select parameters by location.""" + return params.loc[loc] + def linear(states: Array, params: Array) -> Array: """Linear production function where the constant is the last parameter.""" @@ -50,20 +61,16 @@ def identity_constraints_linear( factor: str, aug_period: int, all_factors: tuple[str, ...], -) -> list[dict]: +) -> list[FixedConstraintWithValue]: """Identity constraints for linear transition function.""" - constraints_dicts = [] + from skillmodels.constraints import FixedConstraintWithValue # noqa: PLC0415 + + constraints: list[FixedConstraintWithValue] = [] for regressor in params_linear(all_factors): val = 1.0 if factor == regressor else 0.0 - constraints_dicts.append( - { - "loc": ("transition", aug_period, factor, regressor), - "type": "fixed", - "value": val, - "description": "Identity constraint.", - } - ) - return constraints_dicts + loc = ("transition", aug_period, factor, regressor) + constraints.append(FixedConstraintWithValue(loc=loc, value=val)) + return constraints def translog(states: Array, params: Array) -> Array: @@ -102,20 +109,16 @@ def identity_constraints_translog( factor: str, aug_period: int, all_factors: tuple[str, ...], -) -> list[dict]: +) -> list[FixedConstraintWithValue]: """Identity constraints for translog transition function.""" - constraints_dicts = [] + from skillmodels.constraints import FixedConstraintWithValue # noqa: PLC0415 + + constraints: list[FixedConstraintWithValue] = [] for regressor in params_translog(all_factors): val = 1.0 if factor == regressor else 0.0 - constraints_dicts.append( - { - "loc": ("transition", aug_period, factor, regressor), - "type": "fixed", - "value": val, - "description": "Identity constraint.", - } - ) - return constraints_dicts + loc = ("transition", aug_period, factor, regressor) + constraints.append(FixedConstraintWithValue(loc=loc, value=val)) + return constraints def log_ces(states: Array, params: Array) -> Array: @@ -142,18 +145,18 @@ def constraints_log_ces( factor: str, factors: tuple[str, ...], aug_period: int, -) -> dict: +) -> om.constraints.Constraint: """Constraints for log_ces production function.""" names = params_log_ces(factors) loc = [("transition", aug_period, factor, name) for name in names[:-1]] - return {"loc": loc, "type": "probability"} + return om.ProbabilityConstraint(selector=functools.partial(select_by_loc, loc=loc)) def identity_constraints_log_ces( factors: tuple[str, ...], aug_period: int, all_factors: tuple[str, ...], -) -> list[dict]: +) -> list[om.constraints.Constraint]: """Identity constraints for log_ces.""" raise NotImplementedError @@ -193,7 +196,7 @@ def identity_constraints_robust_translog( factor: str, aug_period: int, all_factors: tuple[str, ...], -) -> list[dict]: +) -> list[FixedConstraintWithValue]: """Identity constraints for robust_translog.""" return identity_constraints_translog( factor=factor, aug_period=aug_period, all_factors=all_factors @@ -222,20 +225,16 @@ def identity_constraints_linear_and_squares( factor: str, aug_period: int, all_factors: tuple[str, ...], -) -> list[dict]: +) -> list[FixedConstraintWithValue]: """Identity constraints for linear_and_squares transition function.""" - constraints_dicts = [] + from skillmodels.constraints import FixedConstraintWithValue # noqa: PLC0415 + + constraints: list[FixedConstraintWithValue] = [] for regressor in params_linear_and_squares(all_factors): val = 1.0 if factor == regressor else 0.0 - constraints_dicts.append( - { - "loc": ("transition", aug_period, factor, regressor), - "type": "fixed", - "value": val, - "description": "Identity constraint.", - } - ) - return constraints_dicts + loc = ("transition", aug_period, factor, regressor) + constraints.append(FixedConstraintWithValue(loc=loc, value=val)) + return constraints def log_ces_general(states: Array, params: Array) -> Array: @@ -263,6 +262,6 @@ def identity_constraints_log_ces_general( factors: tuple[str, ...], aug_period: int, all_factors: tuple[str, ...], -) -> list[dict]: +) -> list[om.constraints.Constraint]: """Identity constraints for log_ces_general.""" raise NotImplementedError diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 6bb52c4..fede762 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -1,13 +1,16 @@ """Tests for constraints.""" from types import MappingProxyType +from typing import Any import numpy as np +import optimagic as om import pandas as pd import pytest from pandas.testing import assert_frame_equal from skillmodels.constraints import ( + FixedConstraintWithValue, _get_anchoring_constraints, _get_constant_factors_constraints, _get_constraints_for_augmented_periods, @@ -17,14 +20,29 @@ _get_stage_constraints, _get_transition_constraints, add_bounds, - constraints_dicts_to_om, - get_constraints_dicts, + get_constraints, ) from skillmodels.process_model import process_model from skillmodels.test_data.simplest_augmented_model import SIMPLEST_AUGMENTED_MODEL from skillmodels.types import Anchoring, Labels, Normalizations +def _to_dict(c: om.constraints.Constraint) -> dict[str, Any]: + """Convert a constraint object to a comparable dict for testing.""" + if isinstance(c, FixedConstraintWithValue): + return {"loc": c.loc, "type": "fixed", "value": c.value} + if isinstance(c, om.PairwiseEqualityConstraint): + locs = [s.keywords["loc"] for s in c.selectors] # ty: ignore[unresolved-attribute] + return {"loc": locs, "type": "pairwise_equality"} + if isinstance(c, om.ProbabilityConstraint): + return {"loc": c.selector.keywords["loc"], "type": "probability"} # ty: ignore[unresolved-attribute] + if isinstance(c, om.IncreasingConstraint): + return {"loc": c.selector.keywords["loc"], "type": "increasing"} # ty: ignore[unresolved-attribute] + if isinstance(c, om.EqualityConstraint): + return {"loc": c.selector.keywords["loc"], "type": "equality"} # ty: ignore[unresolved-attribute] + raise TypeError(type(c)) + + def test_add_bounds() -> None: ind_tups = [("shock_sds", i) for i in range(5)] + [ ("meas_sds", 4), @@ -85,26 +103,22 @@ def test_normalization_constraints() -> None: ] calculated = _get_normalization_constraints(norm, factors=("fac1", "fac2")) - for c in calculated: - del c["description"] - - assert_list_equal_except_for_order(calculated, expected) + as_dicts = [_to_dict(c) for c in calculated] + assert_list_equal_except_for_order(as_dicts, expected) def test_mixture_weight_constraints_mixture() -> None: calculated = _get_mixture_weights_constraints(n_mixtures=2) - for c in calculated: - del c["description"] + as_dicts = [_to_dict(c) for c in calculated] expected = [{"loc": "mixture_weights", "type": "probability"}] - assert_list_equal_except_for_order(calculated, expected) + assert_list_equal_except_for_order(as_dicts, expected) def test_mixture_weight_constraints_normal() -> None: calculated = _get_mixture_weights_constraints(n_mixtures=1) - for c in calculated: - del c["description"] + as_dicts = [_to_dict(c) for c in calculated] expected = [{"loc": "mixture_weights", "type": "fixed", "value": 1.0}] - assert_list_equal_except_for_order(calculated, expected) + assert_list_equal_except_for_order(as_dicts, expected) def test_stage_constraints() -> None: @@ -123,9 +137,8 @@ def test_stage_constraints() -> None: ] calculated = _get_stage_constraints(stagemap=stagemap, stages=stages) - for c in calculated: - del c["description"] - assert_list_equal_except_for_order(calculated, expected) + as_dicts = [_to_dict(c) for c in calculated] + assert_list_equal_except_for_order(as_dicts, expected) def test_stage_constraints_with_endogenous_factors() -> None: @@ -151,9 +164,8 @@ def test_stage_constraints_with_endogenous_factors() -> None: ] calculated = _get_stage_constraints(stagemap=stagemap, stages=stages) - for c in calculated: - del c["description"] - assert_list_equal_except_for_order(calculated, expected) + as_dicts = [_to_dict(c) for c in calculated] + assert_list_equal_except_for_order(as_dicts, expected) def test_constant_factor_constraints() -> None: @@ -178,9 +190,8 @@ def test_constant_factor_constraints() -> None: ] calculated = _get_constant_factors_constraints(labels) - for c in calculated: - del c["description"] - assert_list_equal_except_for_order(calculated, expected) + as_dicts = [_to_dict(c) for c in calculated] + assert_list_equal_except_for_order(as_dicts, expected) def test_initial_mean_constraints() -> None: @@ -195,9 +206,8 @@ def test_initial_mean_constraints() -> None: expected = [{"loc": ind_tups, "type": "increasing"}] calculated = _get_initial_states_constraints(nmixtures, factors) - for c in calculated: - del c["description"] - assert_list_equal_except_for_order(calculated, expected) + as_dicts = [_to_dict(c) for c in calculated] + assert_list_equal_except_for_order(as_dicts, expected) def test_trans_coeff_constraints() -> None: @@ -235,10 +245,8 @@ def test_trans_coeff_constraints() -> None: }, ] calculated = _get_transition_constraints(labels) - - for c in calculated: - del c["description"] - assert_list_equal_except_for_order(calculated, expected) + as_dicts = [_to_dict(c) for c in calculated] + assert_list_equal_except_for_order(as_dicts, expected) @pytest.fixture @@ -287,22 +295,22 @@ def test_anchoring_constraints_for_constants(anch_uinfo) -> None: ignore_constant_when_anchoring=False, ) calculated = _get_anchoring_constraints(anch_uinfo, (), anchoring_info, (0, 1)) + as_dicts = [_to_dict(c) for c in calculated] - del calculated[0]["description"] expected = [ { - "loc": [ + "loc": ( ("controls", 0, "outcome_f1", "constant"), ("controls", 0, "outcome_f2", "constant"), ("controls", 1, "outcome_f1", "constant"), ("controls", 1, "outcome_f2", "constant"), - ], + ), "type": "fixed", "value": 0, }, ] - assert calculated == expected + assert as_dicts == expected def test_anchoring_constraints_for_controls(anch_uinfo) -> None: @@ -321,13 +329,11 @@ def test_anchoring_constraints_for_controls(anch_uinfo) -> None: anchoring_info, (0, 1), ) - - for c_t in calculated: - del c_t["description"] + as_dicts = [_to_dict(c) for c in calculated] expected = [ { - "loc": [ + "loc": ( ("controls", 0, "outcome_f1", "c1"), ("controls", 0, "outcome_f1", "c2"), ("controls", 0, "outcome_f2", "c1"), @@ -336,13 +342,13 @@ def test_anchoring_constraints_for_controls(anch_uinfo) -> None: ("controls", 1, "outcome_f1", "c2"), ("controls", 1, "outcome_f2", "c1"), ("controls", 1, "outcome_f2", "c2"), - ], + ), "type": "fixed", "value": 0, }, ] - assert calculated == expected + assert as_dicts == expected def test_anchoring_constraints_for_loadings(anch_uinfo) -> None: @@ -356,24 +362,22 @@ def test_anchoring_constraints_for_loadings(anch_uinfo) -> None: ignore_constant_when_anchoring=False, ) calculated = _get_anchoring_constraints(anch_uinfo, (), anchoring_info, (0, 1)) + as_dicts = [_to_dict(c) for c in calculated] expected = [ { - "loc": [ + "loc": ( ("loadings", 0, "outcome_f1", "f1"), ("loadings", 0, "outcome_f2", "f2"), ("loadings", 1, "outcome_f1", "f1"), ("loadings", 1, "outcome_f2", "f2"), - ], + ), "type": "fixed", "value": 1, }, ] - for c_t in calculated: - del c_t["description"] - - assert calculated == expected + assert as_dicts == expected def assert_list_equal_except_for_order(list1, list2) -> None: @@ -388,10 +392,10 @@ def simplest_augmented_model(): return process_model(SIMPLEST_AUGMENTED_MODEL) -def test_get_constraints_dicts_with_endogenous_factors( +def test_get_constraints_with_endogenous_factors( simplest_augmented_model, ) -> None: - constraints = get_constraints_dicts( + constraints = get_constraints( update_info=simplest_augmented_model.update_info, labels=simplest_augmented_model.labels, dimensions=simplest_augmented_model.dimensions, @@ -400,11 +404,14 @@ def test_get_constraints_dicts_with_endogenous_factors( endogenous_factors_info=simplest_augmented_model.endogenous_factors_info, ) # Should contain augmented-period constraints - assert any(c.get("value") == 1e-08 for c in constraints) + assert any( + isinstance(c, FixedConstraintWithValue) and c.value == 1e-08 + for c in constraints + ) -def test_constraints_dicts_to_om_type_dispatch(simplest_augmented_model) -> None: - constraints = get_constraints_dicts( +def test_get_constraints_returns_om_objects(simplest_augmented_model) -> None: + constraints = get_constraints( update_info=simplest_augmented_model.update_info, labels=simplest_augmented_model.labels, dimensions=simplest_augmented_model.dimensions, @@ -412,63 +419,9 @@ def test_constraints_dicts_to_om_type_dispatch(simplest_augmented_model) -> None normalizations=simplest_augmented_model.normalizations, endogenous_factors_info=simplest_augmented_model.endogenous_factors_info, ) - om_constraints = constraints_dicts_to_om(constraints) - assert len(om_constraints) > 0 - - -def test_constraints_dicts_to_om_equality_type() -> None: - dicts = [ - { - "loc": ("loadings", 0, "y1", "f1"), - "type": "equality", - "id": 0, - "description": "test", - }, - ] - result = constraints_dicts_to_om(dicts) - assert len(result) == 1 - - -def test_constraints_dicts_to_om_probability_type() -> None: - dicts = [ - { - "loc": "mixture_weights", - "type": "probability", - "id": 0, - "description": "test", - }, - ] - result = constraints_dicts_to_om(dicts) - assert len(result) == 1 - - -def test_constraints_dicts_to_om_increasing_type() -> None: - dicts = [ - { - "loc": [ - ("initial_states", 0, "m0", "f1"), - ("initial_states", 0, "m1", "f1"), - ], - "type": "increasing", - "id": 0, - "description": "test", - }, - ] - result = constraints_dicts_to_om(dicts) - assert len(result) == 1 - - -def test_constraints_dicts_to_om_unknown_type_raises() -> None: - dicts = [ - { - "loc": ("loadings", 0, "y1", "f1"), - "type": "unknown_type", - "id": 0, - "description": "test", - }, - ] - with pytest.raises(TypeError, match="unknown_type"): - constraints_dicts_to_om(dicts) + assert len(constraints) > 0 + for c in constraints: + assert isinstance(c, om.constraints.Constraint) def test_get_constraints_for_augmented_periods(simplest_augmented_model) -> None: @@ -476,8 +429,7 @@ def test_get_constraints_for_augmented_periods(simplest_augmented_model) -> None labels=simplest_augmented_model.labels, endogenous_factors_info=simplest_augmented_model.endogenous_factors_info, ) - for c in calculated: - del c["description"] + as_dicts = [_to_dict(c) for c in calculated] expected = [ {"loc": ("transition", 0, "fac1", "fac1"), "type": "fixed", "value": 1.0}, {"loc": ("transition", 0, "fac1", "fac2"), "type": "fixed", "value": 0.0}, @@ -498,4 +450,4 @@ def test_get_constraints_for_augmented_periods(simplest_augmented_model) -> None {"loc": ("transition", 3, "fac2", "of"), "type": "fixed", "value": 0.0}, {"loc": ("transition", 3, "fac2", "constant"), "type": "fixed", "value": 0.0}, ] - assert_list_equal_except_for_order(calculated, expected) + assert_list_equal_except_for_order(as_dicts, expected) diff --git a/tests/test_transition_functions.py b/tests/test_transition_functions.py index 562dfbf..363f3bd 100644 --- a/tests/test_transition_functions.py +++ b/tests/test_transition_functions.py @@ -2,7 +2,7 @@ import jax import jax.numpy as jnp -import numpy as np +import optimagic as om import pytest from numpy.testing import assert_array_almost_equal as aaae @@ -222,11 +222,11 @@ def test_identity_constraints_linear() -> None: result = identity_constraints_linear("a", 0, all_factors) assert len(result) == 4 # 3 factors + constant # "a" regressor should be fixed at 1.0 - assert np.isclose(result[0]["value"], 1.0) - assert result[0]["loc"] == ("transition", 0, "a", "a") + assert result[0].value == pytest.approx(1.0) + assert result[0].loc == ("transition", 0, "a", "a") # others should be 0.0 - assert np.isclose(result[1]["value"], 0.0) - assert np.isclose(result[3]["value"], 0.0) # constant + assert result[1].value == pytest.approx(0.0) + assert result[3].value == pytest.approx(0.0) # constant def test_identity_constraints_translog() -> None: @@ -235,26 +235,29 @@ def test_identity_constraints_translog() -> None: # Should have one constraint per translog param assert len(result) == len(params_translog(all_factors)) # First constraint for "a" linear should be 1.0 - assert np.isclose(result[0]["value"], 1.0) + assert result[0].value == pytest.approx(1.0) # All others should be 0.0 for c in result[1:]: - assert np.isclose(c["value"], 0.0) + assert c.value == pytest.approx(0.0) def test_identity_constraints_robust_translog() -> None: all_factors = ("a", "b") result_robust = identity_constraints_robust_translog("a", 0, all_factors) result_translog = identity_constraints_translog("a", 0, all_factors) - assert result_robust == result_translog + assert len(result_robust) == len(result_translog) + for r, t in zip(result_robust, result_translog, strict=True): + assert r.loc == t.loc + assert r.value == t.value def test_identity_constraints_linear_and_squares() -> None: all_factors = ("a", "b", "c") result = identity_constraints_linear_and_squares("a", 0, all_factors) assert len(result) == len(params_linear_and_squares(all_factors)) - assert np.isclose(result[0]["value"], 1.0) # "a" linear + assert result[0].value == pytest.approx(1.0) # "a" linear for c in result[1:]: - assert np.isclose(c["value"], 0.0) + assert c.value == pytest.approx(0.0) def test_identity_constraints_log_ces_raises() -> None: @@ -269,9 +272,10 @@ def test_identity_constraints_log_ces_general_raises() -> None: def test_constraints_log_ces() -> None: result = constraints_log_ces("fac1", ("a", "b", "c"), 0) - assert result["type"] == "probability" - assert len(result["loc"]) == 3 # gamma constraints for a, b, c (not phi) - for loc in result["loc"]: - assert loc[0] == "transition" - assert loc[1] == 0 - assert loc[2] == "fac1" + assert isinstance(result, om.ProbabilityConstraint) + loc = result.selector.keywords["loc"] # ty: ignore[unresolved-attribute] + assert len(loc) == 3 # gamma constraints for a, b, c (not phi) + for entry in loc: + assert entry[0] == "transition" + assert entry[1] == 0 + assert entry[2] == "fac1"