forked from metoppv/improver
-
Notifications
You must be signed in to change notification settings - Fork 0
Support deterministic forecast source when clustering #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gavinevans
wants to merge
2
commits into
mobt_783_partial_realization_clustering
Choose a base branch
from
mobt_783_deterministic_secondary_inputs
base: mobt_783_partial_realization_clustering
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3052,6 +3052,220 @@ def test_clusterandmatch_secondary_input_missing_primary_forecast_period( | |
| ) | ||
|
|
||
|
|
||
| def test_clusterandmatch_secondary_no_matching_cubes_warns(): | ||
| """Test that a warning is issued when a hierarchy secondary input has no cubes | ||
| matching the model_id_attr value in the specified forecast period range. | ||
|
|
||
| This verifies that when the hierarchy references a secondary model name that does | ||
| not match any cube's model_id attribute, the plugin warns and skips that input | ||
| rather than silently ignoring it. | ||
| """ | ||
| pytest.importorskip("kmedoids") | ||
| pytest.importorskip("esmf_regrid") | ||
|
|
||
| cubes = CubeList() | ||
| spatial_shape = (3, 3) | ||
|
|
||
| cubes.extend( | ||
| _create_4d_realization_cube( | ||
| n_realizations=3, | ||
| forecast_periods=[0, 6], | ||
| y_dim=spatial_shape[0], | ||
| x_dim=spatial_shape[1], | ||
| base_value=100.0, | ||
| model_id="primary_model", | ||
| merge=False, | ||
| ) | ||
| ) | ||
| cubes.append(_create_target_grid_cube(spatial_shape=spatial_shape)) | ||
|
|
||
| hierarchy = { | ||
| "primary_input": "primary_model", | ||
| "secondary_inputs": {"nonexistent_model": [0, 6]}, | ||
| } | ||
|
|
||
| plugin = RealizationClusterAndMatch( | ||
| hierarchy=hierarchy, | ||
| model_id_attr="model_id", | ||
| clustering_method="KMedoids", | ||
| target_grid_name="target_grid", | ||
| n_clusters=2, | ||
| random_state=42, | ||
| ) | ||
|
|
||
| with pytest.warns( | ||
| UserWarning, | ||
| match=( | ||
| r"Secondary input 'nonexistent_model' has no cubes matching " | ||
| r"model_id='nonexistent_model' in the forecast period range \[0, 6\]\." | ||
| ), | ||
| ): | ||
| plugin.process(cubes) | ||
|
|
||
|
|
||
| def test_clusterandmatch_unreferenced_model_id_warns(): | ||
| """Test that a warning is issued when input cubes contain model_id_attr values | ||
| not referenced in the hierarchy. | ||
|
|
||
| This verifies that when extra cubes are supplied with a model_id attribute value | ||
| that does not appear in the hierarchy (neither as primary_input nor as a secondary | ||
| input key), the plugin warns that those cubes will be ignored. | ||
| """ | ||
| pytest.importorskip("kmedoids") | ||
| pytest.importorskip("esmf_regrid") | ||
|
|
||
| cubes = CubeList() | ||
| spatial_shape = (3, 3) | ||
|
|
||
| cubes.extend( | ||
| _create_4d_realization_cube( | ||
| n_realizations=3, | ||
| forecast_periods=[0, 6], | ||
| y_dim=spatial_shape[0], | ||
| x_dim=spatial_shape[1], | ||
| base_value=100.0, | ||
| model_id="primary_model", | ||
| merge=False, | ||
| ) | ||
| ) | ||
| # Extra cube whose model_id is not in the hierarchy. | ||
| cubes.extend( | ||
| _create_4d_realization_cube( | ||
| n_realizations=3, | ||
| forecast_periods=[0, 6], | ||
| y_dim=spatial_shape[0], | ||
| x_dim=spatial_shape[1], | ||
| base_value=200.0, | ||
| model_id="unlisted_model", | ||
| merge=False, | ||
| ) | ||
| ) | ||
| cubes.append(_create_target_grid_cube(spatial_shape=spatial_shape)) | ||
|
|
||
| hierarchy = { | ||
| "primary_input": "primary_model", | ||
| "secondary_inputs": {}, | ||
| } | ||
|
|
||
| plugin = RealizationClusterAndMatch( | ||
| hierarchy=hierarchy, | ||
| model_id_attr="model_id", | ||
| clustering_method="KMedoids", | ||
| target_grid_name="target_grid", | ||
| n_clusters=2, | ||
| random_state=42, | ||
| ) | ||
|
|
||
| with pytest.warns( | ||
| UserWarning, | ||
| match=( | ||
| r"Input cubes have model_id values not referenced in the hierarchy: " | ||
| r"\['unlisted_model'\]\. These cubes will be ignored\." | ||
| ), | ||
| ): | ||
| result = plugin.process(cubes) | ||
|
|
||
| assert all( | ||
| "unlisted_model" not in str(value) | ||
| for value in result.attributes.values() | ||
| ) | ||
|
|
||
|
|
||
| def test_clusterandmatch_deterministic_secondary_input(): | ||
| """Test that a deterministic (no-realization) secondary input is supported. | ||
|
|
||
| The primary input provides the baseline clustered forecast for all forecast periods. | ||
| When a secondary input cube has no realization coordinate it should be treated | ||
| as having a single realization and follow the partial-realization path, replacing | ||
| the best-matching cluster at the relevant forecast periods with its data. | ||
|
Comment on lines
+3178
to
+3180
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can something be added here to explain the primary input sections of this test a bit more.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a sentence. |
||
| """ | ||
| pytest.importorskip("kmedoids") | ||
| pytest.importorskip("esmf_regrid") | ||
|
|
||
| cubes = CubeList() | ||
| spatial_shape = (3, 3) | ||
|
|
||
| # Primary ensemble input — 4 realizations, base value 100. | ||
| cubes.extend( | ||
| _create_4d_realization_cube( | ||
| n_realizations=4, | ||
| forecast_periods=[0, 6], | ||
| y_dim=spatial_shape[0], | ||
| x_dim=spatial_shape[1], | ||
| base_value=100.0, | ||
| model_id="primary_model", | ||
| merge=False, | ||
| ) | ||
| ) | ||
|
|
||
| # Deterministic secondary input — 2D cubes with no realization coordinate, | ||
| # base value 500 (far from primary so it will match the nearest cluster). | ||
| for fp_hours in [0, 6]: | ||
| det_data = np.full(spatial_shape, 500.0 + fp_hours, dtype=np.float32) | ||
| det_cube = set_up_variable_cube( | ||
| det_data, | ||
| name="air_temperature", | ||
| units="K", | ||
| spatial_grid="equalarea", | ||
| ) | ||
| det_cube.attributes["model_id"] = "det_model" | ||
| # Set time coordinates to match the primary cubes. | ||
| det_cube.coord("forecast_period").points = [fp_hours * 3600] | ||
| det_cube.coord("time").points = [ | ||
| det_cube.coord("forecast_reference_time").points[0] + fp_hours * 3600 | ||
| ] | ||
| assert not det_cube.coords("realization"), ( | ||
| "Deterministic cube should have no realization coordinate" | ||
| ) | ||
| cubes.append(det_cube) | ||
|
|
||
| cubes.append(_create_target_grid_cube(spatial_shape=spatial_shape)) | ||
|
|
||
| hierarchy = { | ||
| "primary_input": "primary_model", | ||
| "secondary_inputs": {"det_model": [0, 6]}, | ||
| } | ||
|
|
||
| plugin = RealizationClusterAndMatch( | ||
| hierarchy=hierarchy, | ||
| model_id_attr="model_id", | ||
| clustering_method="KMedoids", | ||
| target_grid_name="target_grid", | ||
| n_clusters=2, | ||
| random_state=42, | ||
| ) | ||
|
|
||
| result = plugin.process(cubes) | ||
|
|
||
| # Result should have realization as a dimension coordinate. | ||
| assert result.coords("realization", dim_coords=True) | ||
| assert result.coord("realization").points.size == 2 | ||
|
|
||
| # Both forecast periods should be present. | ||
| np.testing.assert_array_equal( | ||
| result.coord("forecast_period").points, [0, 6 * 3600] | ||
| ) | ||
|
|
||
| # The deterministic value (500 / 506) is far from the primary (100 / 106), so | ||
| # exactly one cluster should carry the deterministic data at each lead time. | ||
| for fp_hours in [0, 6]: | ||
| fp_data = result.extract( | ||
| iris.Constraint(forecast_period=fp_hours * 3600) | ||
| ).data | ||
| expected_det = 500.0 + fp_hours | ||
| expected_primary = 100.0 + fp_hours | ||
| det_clusters = np.isclose(fp_data, expected_det, atol=5.0).any(axis=(-1, -2)) | ||
| primary_clusters = np.isclose(fp_data, expected_primary, atol=5.0).any( | ||
| axis=(-1, -2) | ||
| ) | ||
| assert det_clusters.sum() == 1, ( | ||
| f"fp={fp_hours}h: exactly one cluster should carry deterministic data" | ||
| ) | ||
| assert primary_clusters.sum() == 1, ( | ||
| f"fp={fp_hours}h: exactly one cluster should carry primary data" | ||
| ) | ||
|
|
||
|
|
||
| def test_select_realizations_for_kmedoid_clusters_too_many_clusters(): | ||
| """Test that ValueError is raised if number of clusters > number of realizations.""" | ||
| # Create a cube with 2 realizations | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to check, or is it important to test, that these cubes are ignored?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added an assertion that the
unlisted_modelis absent from the attributes.