Skip to content

Comments

953 stochastic noise#2275

Open
maxwhitemet wants to merge 11 commits intometoppv:masterfrom
maxwhitemet:953_stochastic_noise
Open

953 stochastic noise#2275
maxwhitemet wants to merge 11 commits intometoppv:masterfrom
maxwhitemet:953_stochastic_noise

Conversation

@maxwhitemet
Copy link
Contributor

@maxwhitemet maxwhitemet commented Jan 8, 2026

Addresses #953.

This PR adds the plugin, CLI, and tests for stochastic noise generation.

The acceptance tests show the impact, with data available here.
image

Testing:

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

@cpelley
Copy link
Contributor

cpelley commented Jan 12, 2026

@maxwhitemet, could you merge/rebase master into your branch. The failing "CI Tests / Test-Coverage (pull_request)" should hopefully be addressed by the now merged #2273

image

There does appear to be a new issue potentially with the improver documentation building now though... (different issue)

@cpelley
Copy link
Contributor

cpelley commented Jan 13, 2026

Ignore the test coverage failure. Fixed in #2282

Copy link
Contributor

@gavinevans gavinevans left a comment

Choose a reason for hiding this comment

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

Thanks @maxwhitemet 👍

I've added some comments below.

Copy link
Contributor Author

@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.

Thank you for the review @gavinevans. I have now made the requested changes.

Copy link
Contributor

@gavinevans gavinevans left a comment

Choose a reason for hiding this comment

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

I've added some minor comments.

Copy link
Contributor Author

@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 implemented your feedback

Copy link
Contributor

@bayliffe bayliffe left a comment

Choose a reason for hiding this comment

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

Thanks Max, a few comments. The thrust of some of my comments it to make this less precipitation specific, but I've not highlighted everywhere this needs to happen, so think about variable names (e.g. dry_mask) and comments to make it less precip specific, using precip as an example if that's instructive.

I'm also interested in you making changes to better handle the expected shape of cubes and the coordinate order, rather than assuming realization is first and simply indexing it with that assumption.

pysteps.noise.fftgenerators.initialize_nonparam_2d_ssft_filter.
Provide as Python dict string,
e.g., "{'win_size': (100, 100), 'overlap': 0.3}".
Recommended keys: win_size, overlap, war_thr.
Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't want to re-define these here, can we include a link to the pysteps documentation that can be clicked in the read-the-docs pages to make these quick to get to.

https://pysteps.readthedocs.io/en/stable/generated/pysteps.noise.fftgenerators.initialize_nonparam_2d_ssft_filter.html#pysteps.noise.fftgenerators.initialize_nonparam_2d_ssft_filter

Recommended keys: overlap, seed.
db_threshold:
Threshold value below which data will be set to a constant in dB scale
to avoid issues with log(0).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
to avoid issues with log(0).
to avoid issues with log(0). Value provided in units of `db_threshold_units`.

Comment on lines +49 to +56
scale_dry_noise:
If True, noise in dry regions (where template.data <= 0) will be scaled
such that the maximum noise value in those regions is zero and all other
noise values are negative.
This prevents the addition of positive noise to dry regions, which could
artificially increase precipitation values where the input cube
indicates no precipitation should occur.
Default is False.
Copy link
Contributor

Choose a reason for hiding this comment

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

You've written a relatively generic CLI here and then this one option is incredibly specific to precipitation. Could this be rewritten to be scale_zero_noise? There is the question of what happens to diagnostic for which the value could be negative as we've assumed a zero-bounded quantity by the looks of this. I'll get to that later I guess. It probably has implications for whether my suggested variable name is sensible.

Comment on lines +26 to +31
correlated rather than independent. This is particularly useful for Ensemble Copula
Coupling-Quantile (ECC-Q) realization generation, where post-processing may indicate
non-zero precipitation should occur at locations where all raw ensemble members had
zero. In ECC reordering, these locations create ties (all raw members have identical
zero values) that cannot be meaningfully reordered. The spatially-structured noise
breaks these ties by adding small contiguous precipitation patches in dry regions,
Copy link
Contributor

Choose a reason for hiding this comment

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

We could make this generic by using the precipitation as an example rather than making it the sole use case as it appears as currently written, or just write in more general terms.

Number of worker threads for parallel FFT computation.
If not specified, uses the smaller of the plugin's default (number of
available CPUs) or the number of realizations in the input cube.
scale_dry_noise:
Copy link
Contributor

Choose a reason for hiding this comment

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

See comment in CLI.

return self.db_threshold

def _to_dB(self, cube: Cube) -> Cube:
"""Convert cube data to dB scale with thresholding.
Copy link
Contributor

Choose a reason for hiding this comment

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

This description could be a little more expansive.

template_dB = self._to_dB(template.copy())

# Build delayed processing tasks for each realization
n_realiz = template.coord("realization").points.size
Copy link
Contributor

Choose a reason for hiding this comment

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

Go on, we can stretch to n_realizations

n_realiz = template.coord("realization").points.size
tasks = []
for k in range(n_realiz):
realiz_data = template_dB.data[k].astype(np.float32)
Copy link
Contributor

Choose a reason for hiding this comment

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

There is an implicit assumption that the first index of the cube here corresponds to realization but I don't think that's been written anywhere, checked, or enforeced.

# noise addition only to dry regions)
if not np.any(dry_mask):
output_cube = input_cube.copy()
return output_cube
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we copying this to return it? You could quite happily just return input_cube.

assert result.data.dtype == np.float32

# All values in simple_cube are non-zero, so output should equal input
np.testing.assert_array_equal(result.data, simple_cube.data)
Copy link
Contributor

Choose a reason for hiding this comment

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

This can go away with the process method.

@bayliffe
Copy link
Contributor

I forgot to say that this code is currently within the precipitation directory. If you do make it less precip-specific then it should probably move.

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.

4 participants