Skip to content

Add the Ensemble Copula Coupling "transformation" approach#2303

Open
gavinevans wants to merge 20 commits into
metoppv:masterfrom
gavinevans:mobt_952_ecc_transformation
Open

Add the Ensemble Copula Coupling "transformation" approach#2303
gavinevans wants to merge 20 commits into
metoppv:masterfrom
gavinevans:mobt_952_ecc_transformation

Conversation

@gavinevans
Copy link
Copy Markdown
Contributor

Addresses https://github.com/metoppv/mo-blue-team/issues/952

Description
This PR adds the ECC-transformation (ECC-T) capability, largely based on Schefzik et al., 2013. Rather than sampling at equally spaced quantiles, the ECC-T fits a distribution based on data from a cube defining the intensities (e.g. of precipitation rate), at each grid point separately. This can allow for the regeneration of realizations that are more similar to the "raw" realizations than when sampling at equally spaced quantiles.

The number of lines changed in this PR have been vastly increased by conversions from unittest to pytest for the ConvertProbabilitiesToPercentiles, EnsembleReordering and RebadgePercentilesAsRealizations.

An outline of the files changed:

  • doc/source/examples/generate_realizations_simple_example.py –
    Provides a worked example of the ECC-Transformation approach (largely following Schefzik et al., 2013).

  • emos_calibration.py – Update to EnsembleReordering plugin interface.
    generate_realizations.py – Update CLI to include a sampling option and a ensure_evenly_spaced_realizations option, which will be passed to the ConvertProbabilitiesToPercentiles plugin.

  • ensemble_copula_coupling.py –

    1. Rearrange RebadgePercentilesAsRealizations plugin and add an ensure_evenly_spaced_percentiles argument.
    2. Update ConvertProbabilitiesToPercentiles plugin to handle the transformation sampling option. Currently only a gamma distribution is supported. In this particular plugin, the changes are related to handling a multi-dimensional array of percentiles to interpolate to, rather than a 1D array. More arguments are passed to the choose_set_of_percentiles.
    3. Support the passing through of the ensure_evenly_spaced_percentiles argument from the EnsembleReordering plugin to the RebadgePercentilesAsRealizations plugin. Some rearrangement of arguments.
  • numba_utilities.py –
    Add a separate function (fast_interp_same_y_2d) to perform linear interpolation using numba when the input is 2D. This must be a separate function with a static function signature for compatibility with numba. A small function, fast_interp_same_y_nd, is used to decide which function should be called.
    utilities.py –

    1. Modify the existing choose_set_of_percentiles function to take the arguments required to support ECC-Transformation.
    2. A CalculatePercentilesFromIntensityDistribution plugin that adds the core ECC-T functionality.
    3. A create_cube_with_percentile_index function is added, which is very similar to the existing create_cube_with_percentiles function, but handles a percentile_index coordinate instead.
    4. Add a slow_interp_same_y_2d function (where numba is not available), and update interpolate_multiple_rows_same_y.
  • SHA256SUMS – checksum update.

  • test_generate_realizations.py –
    Update the generate_realizations acceptance tests.

  • test_ConvertProbabilitiesToPercentiles.py –
    Most of the changes in this file are a conversion from unittest to pytest. The only new unit test is the test_process_transformation_sampling_3d unit test at the bottom of the file.

  • test_EnsembleReordering.py –
    These are almost entirely a conversion from unittest-based tests to pytest. The only new test is test_process_percentile_index.

  • test_RebadgePercentilesAsRealizations.py –
    These are almost entirely a conversion from unittest-based tests to pytest. The only new test is test_ensure_evenly_spaced_percentiles.

  • test_utilities.py –
    The choose_set_of_percentiles tests in this file have been converted from unittest-based tests to pytest. The test_choose_set_of_percentiles_transformation unit test is newly added.
    New unit tests have been added for the new CalculatePercentilesFromIntensityDistribution class.
    Update Test_interpolate_multiple_rows_same_y to cover testing of slow_interp_same_y_2d and fast_interp_same_y_nd.

  • test_probabilistic.py –
    I’ve added a new unit test for the find_percentile_coordinate function to show the a “percentile_index” coordinate is supported.

Testing:

  • Ran tests and they passed OK
  • Added new tests for the new feature(s)

CLA

  • If a new developer, signed up to CLA

Copy link
Copy Markdown
Contributor

@maxwhitemet maxwhitemet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gavinevans. I have added some comments - almost all documentation based rather than functionality concerns.

random_ordering=randomise,
random_seed=random_seed,
).process(
percentiles,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although extraneous to this PR's aims, the percentiles variable could be renamed to percentiles_forecast_cube to clarify the variable is a cube.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed.

and then reorder these percentiles to generate ensemble realizations. The example
illustrates both the "quantile" and "transformation" sampling options available
within ECC, highlighting the differences in the resulting percentiles and
realizations produced by each method
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
realizations produced by each method
realizations produced by each method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

# the bottom left grid point has a probability of 0.8 of exceeding 1 mm/h, so the
# quantile sampling at the 25th percentile gives a value above 1 mm/h. For
# transformation without scaling, the 0.103 percentile maps to a precipitation rate
# below 1 mm/h, whilst the scaling, scales the percentiles to be within the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# below 1 mm/h, whilst the scaling, scales the percentiles to be within the
# below 1 mm/h, whilst the scaling scales the percentiles to be within the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amended these sentences related to another comment.

Comment on lines +6 to +8
=======================================================================
Example: Convert probabilities to percentiles and perform reordering to generate realizations
=======================================================================
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the docs build, I get the error 'Title underline too short' for many titles. E.g.

improver/doc/source/auto_examples/generate_realizations_simple_example.rst:485: WARNING: Title underline too short.

Copy link
Copy Markdown
Contributor Author

@gavinevans gavinevans Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread doc/source/examples/generate_realizations_simple_example.py Outdated
Comment thread improver_tests/ensemble_copula_coupling/test_utilities.py
Comment thread improver_tests/ensemble_copula_coupling/test_utilities.py Outdated
Comment thread improver_tests/ensemble_copula_coupling/test_utilities.py Outdated
Comment thread improver_tests/ensemble_copula_coupling/test_ConvertProbabilitiesToPercentiles.py Outdated
Comment thread improver/ensemble_copula_coupling/utilities.py
…sformation

* upstream/master:
  EPPT3121 Add clip option to fine fuel moisture content calculation (metoppv#2317)
  Edit CLI docstring. (metoppv#2318)
  Update WeightAndBlend.process() to use *cubes and as_cubelist (metoppv#2314)
  Lighting in vicinity cell_method alignment for master (metoppv#2296)
  Add quantile mapping and associated tests (metoppv#2264)
  MOBT-1069: Using adjacent validity times in SAMOS (metoppv#2310)
  Mobt991 ApplySAMOS plugin distribution fix (metoppv#2311)
  Add attribute to realization clustering to record source realizations (metoppv#2305)
  MOBT-1070: Time-zone safe handling of time point extraction in SAMOS calibration (metoppv#2306)
  Set START_DATE_DICT to outside largest lag period to silence warnings (metoppv#2304)
  Eppt2408 implement initialisation process for fire severity iterative parameters (metoppv#2302)
  FireWeatherIndexBase plugin - change Error to Warning for input values outside expected ranges (metoppv#2301)
Copy link
Copy Markdown
Contributor

@maxwhitemet maxwhitemet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gavinevans. Happy with the changes made. Approved 👍

Copy link
Copy Markdown
Contributor

@mo-jbeaver mo-jbeaver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments added but all the tests ran successfully.

Comment on lines +215 to 216
cube.coord(percentile_coord_name).points = realization_numbers
cube.coord(percentile_coord_name).rename("realization")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cube.coord(percentile_coord_name).points = realization_numbers
cube.coord(percentile_coord_name).rename("realization")
cube.coord(percentile_coord_name).rename("realization")
cube.coord("realization").points = realization_numbers

Comment on lines 218 to 220
cube.coord("realization").points = cube.coord("realization").points.astype(
np.int32
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be combined or linked with the line above:
cube.coord(percentile_coord_name).points = realization_numbers

distribution: str = "gamma",
nan_mask_value: float = 0.0,
scale_percentiles_to_probability_lower_bound: bool = False,
) -> None:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the __init__ methods don't return anything but this one returns a None. Is there a reason for the difference?

distribution:
Valid if the "transformation" option is selected for sampling
the probability distribution. Type of distribution to fit
(currently only 'gamma' is supported).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, does there need to be a check for if something other than "gamma" is passed?

values generated are not reproducible.
tie_break:
The method of tie breaking to use when the first ordering method
contains ties. The available methods are "random", to tie-break
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
contains ties. The available methods are "random", to tie-break
contains ties. The available methods are the default "random", to tie-break

ignore_ecc_bounds_exceedance (bool):
If True where percentiles (calculated as an intermediate output
before realization) exceed the ECC bounds range, raises a
warning rather than an exception.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
warning rather than an exception. False is the default.

@@ -55,10 +86,36 @@
interpolation from the nearest available percentile, rather than using
linear interpolation between the nearest available percentile and
the ECC bound.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the ECC bound.
the ECC bound. The default is false.

@@ -5,12 +5,10 @@
"""
Unit tests for the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Unit tests for the
Pytest-based tests for the

Comment on lines +137 to +146
def test_rank_ecc_preserves_order(cubes_for_rank):
"""Test that rank_ecc preserves order when input is already sorted."""
cube, _ = cubes_for_rank
arr = np.ones_like(cube.data)
for i in range(arr.shape[0]):
arr[i] *= i + 1
raw_cube = cube.copy(data=arr)
cal_cube = cube.copy(data=arr)
result = Plugin().rank_ecc(cal_cube, raw_cube)
np.testing.assert_array_almost_equal(result.data, arr)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is the same as the one before but just testing something different something different. Could they be combined, testing it is a cube and the order is persevered?

# This file is part of 'IMPROVER' and is released under the BSD 3-Clause license.
# See LICENSE in the root of the repository for full licensing details.
"""
Unit tests for the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Pytest-based tests for the

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants