Repository navigation
Add parametric nowcasting and calibrated stochastic rainfall generation for v2 #604
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
alanseed
wants to merge
17
commits into
pySTEPS:main
Choose a base branch
from
alanseed:parametric-nowcasting-v2
base: main
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
17 commits
Select commit
Hold shift + click to select a range
2e6b2b7
parametric engine done
alanseed 523e702
examples added
alanseed 4dd2c03
Update estimator guide for implemented parametric engine
alanseed e3d1a02
Fix parametric documentation headings
alanseed 52bf915
Allow platform variation in fitted marginal statistics
alanseed c1c4da5
parametric temporal fallback added
alanseed de22a03
tests for parametric temporal fallback added
alanseed f9e9bfe
black reformatting completed
alanseed 51b8945
pysteps_param xarray model
alanseed ff91f36
Update doc/source/pysteps_reference/noise.rst
alanseed fad848e
Update doc/source/pysteps_reference/noise.rst
alanseed 53a847e
Update doc/source/pysteps_reference/noise.rst
alanseed 0350dd0
Update doc/source/pysteps_reference/noise.rst
alanseed bba6845
improved comments
alanseed 02c52c6
simulation method added
alanseed 0cbc9a8
end-to-end test added
alanseed 72929d8
added defaults as explicit keyword arguments to forecast()
alanseed 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
Some comments aren't visible on the classic Files Changed page.
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
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 | ||
|---|---|---|---|---|
|
|
@@ -8,8 +8,9 @@ Implementation of deterministic and ensemble nowcasting methods. | |||
| .. automodule:: pysteps.nowcasts.interface | ||||
| .. automodule:: pysteps.nowcasts.anvil | ||||
| .. automodule:: pysteps.nowcasts.extrapolation | ||||
| .. automodule:: pysteps.nowcasts.linda | ||||
| .. automodule:: pysteps.nowcasts.lagrangian_probability | ||||
|
Contributor
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.
Suggested change
|
||||
| .. automodule:: pysteps.nowcasts.linda | ||||
| .. automodule:: pysteps.nowcasts.parametric | ||||
| .. automodule:: pysteps.nowcasts.sprog | ||||
| .. automodule:: pysteps.nowcasts.sseps | ||||
| .. automodule:: pysteps.nowcasts.steps | ||||
|
|
||||
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
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 |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| Calibrated stochastic rainfall fields | ||
| ===================================== | ||
|
|
||
| The calibrated generator creates random rainfall patterns from a rain fraction, | ||
| a rain-intensity distribution, and spectral slopes describing spatial structure. | ||
| It returns a NumPy array in transformed rainfall units. In parametric nowcasting, | ||
| these fields provide new spatial variability at each forecast step. | ||
|
|
||
| Here, the marginal distribution means the distribution of values across pixels, | ||
| without regard to their positions. Spectral slopes describe how variability is | ||
| distributed across spatial scales. See :ref:`sphx_glr_auto_examples_plot_calibrated_noise_generator.py` | ||
| for a radar-data example using rainfall statistics, parameter selection, | ||
| calibration diagnostics, rainfall distributions, spectra, and normalized | ||
| cascade innovations. It uses the same Swiss composite as the original | ||
| stochastic-noise tutorial on a complete subdomain. | ||
|
|
||
| Generating a field | ||
| ------------------ | ||
|
|
||
| Supply signed spectral slopes, rain fraction, and caller-selected Normal location | ||
| and scale in transformed rainfall units. The example values illustrate the | ||
| interface; the parametric engine estimates and selects these parameters from | ||
| the observations:: | ||
|
|
||
| import numpy as np | ||
| from pysteps.noise.parametric import generate_calibrated_field | ||
|
|
||
| parameters = { | ||
| "beta_1": -2.4, | ||
| "beta_2": -3.1, | ||
| "rain_fraction": 0.08, | ||
| "mean_db": 6.0, | ||
| "stdev_db": 3.5, | ||
| } | ||
| field, diagnostics = generate_calibrated_field( | ||
| (256, 256), parameters, | ||
| kmperpixel=1.0, scale_break=15.0, threshold=0.0, | ||
| seed=0, return_diagnostics=True, | ||
| ) | ||
| assert field.shape == (256, 256) and np.isfinite(field).all() | ||
|
|
||
| ``shape`` is in ``(y, x)`` order; pixel spacing and break wavelength are in km. | ||
| The threshold is in the field's transformed units, so 1 mm/h rainfall occurrence | ||
| corresponds to a 0 dBR threshold. Location and scale are typically latent Normal | ||
| parameters, but this helper consumes the active moments supplied by the caller | ||
| and performs no latent-versus-ordinary moment selection or regional slope repair. | ||
| The input mapping is not modified. | ||
|
|
||
| The default result is the field alone. Optional diagnostics report estimated | ||
| spectral slopes, distance, number of attempts, selected attempt, tolerance status, | ||
| target and calibrated parameters, settings, and all candidate estimates and | ||
| distances. A finite returned field does not imply that the requested spectral | ||
| tolerance was met. | ||
|
|
||
| Generation and spectral correction | ||
| ---------------------------------- | ||
|
|
||
| Independent ``U(0,1)`` samples are FFT-filtered using half the signed power slopes | ||
| as amplitude exponents. The resulting correlated field is mapped to the target | ||
| rain-intensity distribution. This mapping also changes its spectrum. To | ||
| compensate, both filter slopes receive an additive correction, default -0.5, | ||
| before generation; the Normal location and scale are used as supplied. | ||
|
|
||
| The histogram marginal mapper uses 500 bins and clips probabilities to | ||
| ``[0.001, 0.999]``. It selects a wet-area cutoff using a bin's upper edge and maps | ||
| the selected sample to Normal quantiles using bin-centre interpolation. The | ||
| returned dry anomalies remain continuous. Mapping can change the realised wet | ||
| fraction and spectrum, and the clipped tails constrain extremes; occurrence and | ||
| marginal matching are approximate. | ||
|
|
||
| Spectra are assessed on a copy with values at or below the transformed rain | ||
| threshold replaced by the dry value. The distance is | ||
| ``sqrt(sum((estimated_beta-target_beta)**2/beta_variances))`` with default | ||
| variances ``(0.067738, 0.076405)``. These are empirical weights. Fractions at or | ||
| below 0.1 permit at most five attempts; higher fractions use one. The first | ||
| candidate with distance at or below 1 is accepted. Otherwise the closest finite | ||
| candidate is selected, retaining the first exact tie. If all spectral assessments | ||
| are unavailable, the last field is returned with an explicit failure status. | ||
|
|
||
| The correction, weights, acceptance threshold, retry settings, bin count, and | ||
| quantile clip can all be overridden. The correction and error weights are | ||
| empirical and may need calibration for other rainfall data. | ||
|
|
||
| Random streams and dry cases | ||
| ---------------------------- | ||
|
|
||
| ``seed`` creates a local NumPy random stream without changing global random state. | ||
| Alternatively, pass a caller-owned ``rng``; it advances in place and must be | ||
| independent for each ensemble member. Supply ``rng`` or ``seed``. Retry counts | ||
| affect stream consumption, so reproducibility requires matching parameters, | ||
| settings, and numerical implementation as well as the initial seed. | ||
|
|
||
| An explicit zero rain fraction produces a constant dry field with zero attempts | ||
| and no spectral-success claim. Positive-fraction mappings with too few distinct | ||
| wet pixels raise an error. Missing/nonfinite input and invalid parameters also | ||
| raise errors; they are not classified as dry rainfall. | ||
|
|
||
| Cascade innovations and marginal adjustment | ||
| ------------------------------------------- | ||
|
|
||
| The raw field retains its rainfall marginal. The parametric engine generates it | ||
| first, then obtains normalized cascade innovations through decomposition:: | ||
|
|
||
| from pysteps.cascade.bandpass_filters import filter_gaussian | ||
| from pysteps.cascade.decomposition import decomposition_fft | ||
|
|
||
| bandpass = filter_gaussian(field.shape, 6) | ||
| innovations = decomposition_fft( | ||
| field, bandpass, compute_stats=True, normalize=True | ||
| ) | ||
| assert np.isfinite(innovations["cascade_levels"]).all() | ||
|
|
||
| The raw field is not guaranteed to have global mean zero and variance one. | ||
| Existing native STEPS noise-method registrations retain their perturbation | ||
| interface; the calibrated parametric engine consumes this helper directly. | ||
|
|
||
| The shared :func:`pysteps.postprocessing.probmatching.match_rainfall_distribution` | ||
| also supports the post-AR marginal adjustment. Its default suppresses no positive | ||
| rain fraction. The forecast passes ``min_rain_fraction=0.025`` to suppress | ||
| very small target fractions after an AR update. Stochastic generation itself | ||
| does not apply that cutoff. |
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 |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| Parametric rainfall estimators | ||
| ============================== | ||
|
|
||
| The parametric estimators provide NumPy-based spatial statistics and temporal | ||
| model utilities. They support the three-observation forecast interface described in :doc:`parametric_nowcast` | ||
| and can also be used independently. | ||
|
|
||
| Spatial estimation | ||
| ------------------ | ||
|
|
||
| Use a finite rainfall field and retain the threshold and dry-value convention | ||
| when transforming it. Occurrence and marginal fitting use explicitly separate | ||
| cutoffs: one identifies rainy pixels, and the other selects the intensity | ||
| samples used for fitting. This example uses illustrative settings:: | ||
|
|
||
| import numpy as np | ||
| import xarray as xr | ||
| from pysteps.utils.transformation import dB_transform | ||
| from pysteps.utils.statistics import compute_field_statistics | ||
| from pysteps.utils.spectral import rapsd_logarithmic, fit_power_law_spectrum | ||
|
|
||
| rng = np.random.default_rng(42) | ||
| rain = rng.lognormal(1.0, 0.8, size=(256, 256)) | ||
| rain[rng.uniform(size=rain.shape) > 0.3] = 0.0 | ||
| dataset = xr.Dataset( | ||
| {"rain": (("y", "x"), rain, {"units": "mm/h"})}, | ||
| attrs={"precip_var": "rain"}, | ||
| ) | ||
| transformed_dataset = dB_transform(dataset, threshold=1.0, zerovalue=-0.1) | ||
| transformed = transformed_dataset["rain"].values | ||
| metadata = transformed_dataset["rain"].attrs | ||
| statistics = compute_field_statistics( | ||
| transformed, metadata["threshold"], lower_truncation=1.25 | ||
| ) | ||
| frequency, power = rapsd_logarithmic(transformed) | ||
| pixel_size_km = 1.0 | ||
| scale_break_km = 15.0 | ||
| spectrum_fit = fit_power_law_spectrum( | ||
| frequency, power, scale_break=scale_break_km / pixel_size_km | ||
| ) | ||
|
|
||
| ``ordinary_mean`` and ``ordinary_stdev`` describe pixels at or above the | ||
| occurrence threshold. ``latent_mean`` and ``latent_stdev`` estimate the parent | ||
| Normal from samples at or above the fitting cutoff. They are different estimates; | ||
| the fitting cutoff does not change the wet fraction. ``nonzero_fit`` reports | ||
| sample count and fitting status, including finite bounded solutions whose | ||
| parameters should not be interpreted as an unrestricted interior fit. | ||
|
|
||
| The radial estimator subtracts the field mean, divides by its standard | ||
| deviation, and averages unscaled FFT power in logarithmic frequency bins. It differs from | ||
| :func:`pysteps.utils.spectral.rapsd`, which uses different binning and | ||
| normalization. Its returned arrays contain linear frequencies in cycles per | ||
| pixel and mean linear power per Fourier mode, not annulus-integrated energy. | ||
| The fit uses signed slopes, an optional prescribed break, and a continuous | ||
| piecewise-linear model in 10*log10 units. The two initial bins and frequencies | ||
| at or above 10**(-0.4) cycles per pixel are excluded by default. The fixed-break | ||
| model is linear in its coefficients and is solved by unweighted least squares. | ||
|
|
||
| Insufficient variance produces empty spectrum arrays. Insufficient or | ||
| unidentified fits produce explicit statuses and unavailable coefficients, not | ||
| climatological substitutions. Field statistics and spectra reject missing input | ||
| pixels; callers must apply a deliberate missing-data policy before using these | ||
| complete-grid estimators. Real inputs are converted to float64 for estimation | ||
| without being modified. Neither function clips fitted parameters. | ||
|
|
||
| Temporal estimation and modelling | ||
| --------------------------------- | ||
|
|
||
| :func:`pysteps.timeseries.parametric.lagrangian_autocorrelation` accepts prepared | ||
| cascade histories with shape ``(time, level, y, x)`` and a fixed velocity array | ||
| with shape ``(2, y, x)`` in pixels per timestep. It uses successive unit-step | ||
| advection. Its default ``boundary="zero"`` fills values outside the grid with | ||
| zero after each step and correlates the whole grid. Zero is the mean anomaly | ||
| of a normalized cascade. The ``boundary="mask"`` alternative excludes pixels | ||
| that are outside the valid support of any aligned history. These two boundary | ||
| choices produce different correlation estimates. | ||
|
|
||
| :func:`pysteps.timeseries.parametric.ar2_correlation_time` converts observed lag | ||
| pairs into the stationary AR(2) one-sided discrete ACF sum including lag zero. | ||
| Output time units equal the supplied timestep units. Invalid and nonstationary | ||
| pairs remain unavailable. This quantity is not a forecast-skill horizon. | ||
|
|
||
| Reference-time estimation rescales the selected level times to one reference | ||
| wavelength and takes their geometric mean. By default it uses the first three | ||
| levels; every contributing time must be positive and finite. Use ``levels=(0,)`` | ||
| to estimate from the largest level alone, as the forecast does by default. | ||
| Supply the scaling exponent separately. Use nominal wavelengths from the | ||
| cascade filter:: | ||
|
|
||
| from pysteps.cascade.bandpass_filters import filter_gaussian | ||
| from pysteps.timeseries.parametric import ( | ||
| estimate_reference_correlation_time, model_cascade_correlations | ||
| ) | ||
|
|
||
| bandpass = filter_gaussian((256, 256), 6, d=1 / pixel_size_km) | ||
| wavelengths_km = 1 / bandpass["central_freqs"] | ||
| observed_times_minutes = np.array([180.0, 70.0, 40.0, 20.0, 10.0, 5.0]) | ||
| exponent = 0.8 # illustrative temporal scaling exponent | ||
| reference_minutes = estimate_reference_correlation_time( | ||
| observed_times_minutes, wavelengths_km, exponent | ||
| ) | ||
| correlations, diagnostics = model_cascade_correlations( | ||
| reference_minutes, wavelengths_km, exponent, | ||
| alpha=2.72, timestep=5.0, return_diagnostics=True | ||
| ) | ||
|
|
||
| The first band's nominal wavelength is a label for a broad low-pass response, | ||
| not an energy-weighted effective wavelength. The temporal model applies the | ||
| nonperiodic lag-2 adjustment when inverting the target timescale. Requested times | ||
| at or below one timestep use white-noise lag pairs, achieving one timestep. | ||
| Diagnostics retain requested and achieved times and identify sub-timestep limits. | ||
|
|
||
| The estimators leave regional parameter selection and bounds to the caller. | ||
| See :doc:`parametric_nowcast` for their use within a complete forecast and | ||
| :doc:`calibrated_generator` for generating fields from the estimated parameters. |
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.
Uh oh!
There was an error while loading. Please reload this page.