From 2e6b2b7f6afb1ffdc35e35459617e0fff0da1784 Mon Sep 17 00:00:00 2001 From: alanseed Date: Thu, 15 Oct 2026 07:54:36 +1100 Subject: [PATCH 01/17] parametric engine done --- MANIFEST.in | 3 +- doc/proposals/calibrated_generator.md | 146 ++++ .../calibrated_generator_validation.json | 127 ++++ doc/proposals/parametric_engine.md | 229 +++++++ doc/proposals/parametric_estimators.md | 158 +++++ .../parametric_estimators_validation.json | 64 ++ .../parametric_nowcast_validation.json | 389 +++++++++++ doc/proposals/parametric_nowcasting.md | 319 +++++++++ doc/source/index.rst | 3 + doc/source/pysteps_reference/noise.rst | 1 + doc/source/pysteps_reference/nowcasts.rst | 1 + doc/source/pysteps_reference/timeseries.rst | 1 + doc/source/pysteps_reference/utils.rst | 1 + .../user_guide/calibrated_generator.rst | 128 ++++ .../user_guide/parametric_estimators.rst | 120 ++++ doc/source/user_guide/parametric_nowcast.rst | 198 ++++++ examples/plot_parametric_nowcast.py | 66 ++ pysteps/noise/__init__.py | 2 +- pysteps/noise/parametric.py | 400 +++++++++++ pysteps/nowcasts/_cascade_advection.py | 41 ++ pysteps/nowcasts/interface.py | 10 + pysteps/nowcasts/parametric.py | 633 ++++++++++++++++++ pysteps/postprocessing/probmatching.py | 119 ++++ .../data/calibrated_generator_reference.json | 100 +++ .../data/calibrated_generator_reference.npz | Bin 0 -> 583668 bytes .../data/compare_calibrated_paper_fields.py | 166 +++++ .../data/compare_parametric_nowcast_paper.py | 252 +++++++ .../data/compare_parametric_paper_fields.py | 111 +++ .../data/generate_calibrated_reference.py | 159 +++++ .../tests/data/generate_nowcast_reference.py | 315 +++++++++ .../data/generate_parametric_reference.py | 164 +++++ .../data/parametric_estimators_reference.json | 420 ++++++++++++ .../data/parametric_nowcast_reference.json | 221 ++++++ .../data/parametric_nowcast_reference.npz | Bin 0 -> 216094 bytes pysteps/tests/test_noise_parametric.py | 354 ++++++++++ pysteps/tests/test_nowcasts_parametric.py | 423 ++++++++++++ pysteps/tests/test_parametric_estimators.py | 321 +++++++++ pysteps/timeseries/parametric.py | 363 ++++++++++ pysteps/utils/__init__.py | 1 + pysteps/utils/spectral.py | 174 +++++ pysteps/utils/statistics.py | 174 +++++ 41 files changed, 6874 insertions(+), 3 deletions(-) create mode 100644 doc/proposals/calibrated_generator.md create mode 100644 doc/proposals/calibrated_generator_validation.json create mode 100644 doc/proposals/parametric_engine.md create mode 100644 doc/proposals/parametric_estimators.md create mode 100644 doc/proposals/parametric_estimators_validation.json create mode 100644 doc/proposals/parametric_nowcast_validation.json create mode 100644 doc/proposals/parametric_nowcasting.md create mode 100644 doc/source/user_guide/calibrated_generator.rst create mode 100644 doc/source/user_guide/parametric_estimators.rst create mode 100644 doc/source/user_guide/parametric_nowcast.rst create mode 100644 examples/plot_parametric_nowcast.py create mode 100644 pysteps/noise/parametric.py create mode 100644 pysteps/nowcasts/_cascade_advection.py create mode 100644 pysteps/nowcasts/parametric.py create mode 100644 pysteps/tests/data/calibrated_generator_reference.json create mode 100644 pysteps/tests/data/calibrated_generator_reference.npz create mode 100644 pysteps/tests/data/compare_calibrated_paper_fields.py create mode 100644 pysteps/tests/data/compare_parametric_nowcast_paper.py create mode 100644 pysteps/tests/data/compare_parametric_paper_fields.py create mode 100644 pysteps/tests/data/generate_calibrated_reference.py create mode 100644 pysteps/tests/data/generate_nowcast_reference.py create mode 100644 pysteps/tests/data/generate_parametric_reference.py create mode 100644 pysteps/tests/data/parametric_estimators_reference.json create mode 100644 pysteps/tests/data/parametric_nowcast_reference.json create mode 100644 pysteps/tests/data/parametric_nowcast_reference.npz create mode 100644 pysteps/tests/test_noise_parametric.py create mode 100644 pysteps/tests/test_nowcasts_parametric.py create mode 100644 pysteps/tests/test_parametric_estimators.py create mode 100644 pysteps/timeseries/parametric.py create mode 100644 pysteps/utils/statistics.py diff --git a/MANIFEST.in b/MANIFEST.in index 1687be6d..4d86691f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,5 @@ include pysteps/pystepsrc_schema.json include pysteps/io/mch_lut_8bit_Metranet_AZC_V104.txt include pysteps/io/mch_lut_8bit_Metranet_v103.txt recursive-include pysteps *.pyx +recursive-include pysteps/tests/data *.json *.npz include pyproject.toml - - \ No newline at end of file diff --git a/doc/proposals/calibrated_generator.md b/doc/proposals/calibrated_generator.md new file mode 100644 index 00000000..85e663a7 --- /dev/null +++ b/doc/proposals/calibrated_generator.md @@ -0,0 +1,146 @@ +# Step 3: calibrated stochastic field generator + +Implemented on `parametric-nowcasting` under the +[agreed contribution scope](parametric_nowcasting.md), 14 October 2026. + +## Public components + +- `pysteps.noise.parametric.power_law_filter`: full-FFT amplitude filter with + signed power slopes, continuous prescribed break, and explicit physical units. +- `pysteps.noise.parametric.generate_stochastic_field`: source-compatible filtered + uniform-noise field and histogram marginal mapping, without inverse correction. +- `pysteps.noise.parametric.generate_calibrated_field`: inverse spectral correction, + candidate assessment and selection, and optional calibration diagnostics. +- `pysteps.postprocessing.probmatching.match_rainfall_distribution`: common + NumPy marginal mapper for generation and the later post-AR adjustment. + +The generator accepts shape `(y, x)`, a mapping containing the five active scalar +parameters (`beta_1`, `beta_2`, `rain_fraction`, `mean_db`, `stdev_db`), physical +pixel spacing and break wavelength in km, and a transformed rain threshold. It +returns a float64 NumPy field, or `(field, diagnostics)` when requested. Supplied +parameters are copied and remain unchanged. No database object, xarray container, +or new runtime dependency is required. + +The generator consumes caller-selected active moments; it does not automatically +choose latent or ordinary moments or apply regional generation QA. These policies +belong to the forthcoming engine. The raw field retains its rainfall marginal; +the engine obtains normalized innovations by decomposing it with `normalize=True`. +Existing native STEPS noise registrations retain their existing interface. + +## Reference algorithm and defaults + +The source is `pysteps_param/src/pysteps_param/stochastic_generator.py` at revision +`94bd4c1571637a8f246ac391470e1a820d6376a6`. Its SHA-256 is +`94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343`. +The source method identifier `inverse_calibrated_v2` is retained. + +| Setting | Preserved default | +| --- | --- | +| White-noise input | Independent `U(0,1)` samples | +| Additive spectral correction | `-0.5` on both power slopes | +| Marginal inverse correction | None on active Normal location/scale | +| Histogram bins | `500` | +| Normal quantile probability clip | `[0.001, 0.999]` | +| Default dry value for assessment | `threshold - 0.1` | +| Low-fraction retry boundary | `rain_fraction <= 0.1` | +| Maximum attempts at low fraction | `5` | +| Attempts at higher fraction | `1` | +| Spectral error variances | `(0.067738, 0.076405)` | +| Accepted weighted distance | `<= 1.0` | + +No calibration constants were retuned. The spectral bias, error weights, tolerance, +retry boundary/count, histogram bins, and quantile clip are configurable. + +The mapper preserves the source histogram cutoff's **upper edge**, followed by +strictly-above-threshold sample selection and bin-centre interpolation to clipped +Normal quantiles. Wet mapped values may cross back below the rain threshold, and +the returned dry anomalies remain continuous. Neither exact wet fraction nor +global standardization is claimed. + +Assess slopes on a copy whose values `<= threshold` are replaced by the configured +dry value. Select the first candidate meeting the distance tolerance; otherwise +select the smallest finite distance, preserving the first tie. If all assessments +fail, select the last candidate with an explicit `unavailable_spectrum` status, +infinite distance, and no success claim. The implementation retains only the +best/current candidate fields rather than all attempted grids. + +The shared marginal helper defaults to no suppression of positive fractions. +The source post-AR policy, which returns dry output for target fraction `< 0.025`, +is available through the explicit `min_rain_fraction` setting. The helper preserves +valid source mappings and rejects nonfinite/degenerate input instead of inventing +a dry field after an invalid calculation. + +An explicitly confirmed zero target is a documented interface extension: return +the dry value, consume no random draws, and report zero attempts without spectral +success. Invalid parameters and failed positive-fraction mappings remain errors. + +## Reproducibility and validation + +Randomness uses a local `default_rng(seed)` or a caller-owned NumPy Generator or +RandomState. Global NumPy random state is unchanged. Caller streams advance in +place; the engine must give each member an independent stream. Retry policy and +numerical implementation are part of reproducibility. + +The frozen [reference metadata](../../pysteps/tests/data/calibrated_generator_reference.json) +and [complete fields](../../pysteps/tests/data/calibrated_generator_reference.npz) +were generated from the original numerical functions, result class, and constants. +A minimal five-scalar dataclass supplies the attributes those functions require, +without operational metadata or QA. No database is accessed. The source spectral +estimator is used when creating expectations; the port uses the new estimators. + +Tests cover complete seeded fields, source diagnostics and selection decisions, +the post-AR mapping, independently calculated filter responses, physical-unit +invariance, marginal cutoff/quantile semantics, dry/error cases, and random streams. +The fixtures are explicitly included in the package manifest so installed tests +can find both these and the step-2 reference data. + +To regenerate using the source project's environment and this checkout available +on `PYTHONPATH`: + +```bash +python pysteps/tests/data/generate_calibrated_reference.py /path/to/pysteps_param +``` + +The saved paper cases `auckland_flood`, `isolated_convection`, and `maximum_war` +were compared at seeds `0` and `20260908`, with a 15 km break, 1 km pixels, +1 mm/h occurrence threshold, and a 1.25 dBR latent fitting cutoff. These checks +use **raw estimated targets**, without regional moment selection or spectral +repair, and therefore evaluate the primitives rather than production engine QA. + +For identical target parameters, all six generated fields matched the source +exactly in this run. With the complete ported estimator-to-generator chain, the +largest field difference was below `4e-6` dBR. This arises from small differences +between equivalent spectral regression solvers amplified by histogram +interpolation. Candidate counts, selected attempts, tolerance outcomes, and +rain-threshold classifications agreed in all six comparisons. The comparison +uses an explicit `1e-4` dBR absolute bound for this coupled check, separately from +the tight identical-target check. The +[validation report](calibrated_generator_validation.json) records source/input +identity and individual results. + +```bash +python pysteps/tests/data/compare_calibrated_paper_fields.py \ + /path/to/pysteps_param /path/to/psteps_param_paper \ + --output /tmp/calibrated_generator_validation.json +``` + +These are generator and estimator-chain checks, not temporal nowcast acceptance +or a forecast-skill comparison. Full ensemble evolution and generation-time +parameter safeguards remain work for the engine stage. + +Validation used Python 3.12, NumPy 2.5.1, and SciPy 1.18.0. The targeted suite +passed 161 tests covering the new components and existing probability matching, +noise, spectral, transformation, and autoregression behavior (11 warnings from +existing modules). A built wheel included all reference fixtures; its 100 +estimator/generator tests passed when run from the extracted wheel outside the +checkout. Both user-guide examples executed successfully. Python formatting and +`git diff --check` passed. This was not a full repository test run, and a full +Sphinx documentation build was not run because Sphinx was unavailable. + +## Documentation and next stage + +API docstrings and noise-module reference entries are provided, with executable +examples in `doc/source/user_guide/calibrated_generator.rst`. The next stage is +the three-observation parametric nowcast engine, assembling transformation, motion, +estimation, reference settings and parameter safeguards, calibrated innovations, +advection/AR updates, and marginal feedback within one forecast interface. diff --git a/doc/proposals/calibrated_generator_validation.json b/doc/proposals/calibrated_generator_validation.json new file mode 100644 index 00000000..6dae9ff8 --- /dev/null +++ b/doc/proposals/calibrated_generator_validation.json @@ -0,0 +1,127 @@ +{ + "provenance": { + "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", + "source_sha256": { + "rain_stats_compute.py": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", + "stochastic_generator.py": "94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343" + } + }, + "paper_revision": "335f7660775480d865d9771820a81923c461832a", + "seeds": [ + 0, + 20260908 + ], + "scale_break_km": 15.0, + "pixel_size_km": 1.0, + "threshold_mm_per_hour": 1.0, + "lower_truncation_dbr": 1.25, + "target_policy": "Raw estimator outputs; regional generation QA not applied", + "coupled_maximum_absolute_error_tolerance_dbr": 0.0001, + "cases": [ + { + "name": "auckland_flood", + "input_sha256": "f78b06cc1efeb8d9ee061426eac2f51ed82235e00f4941b1d47104d104f667bd", + "parameters": { + "beta_1": -2.273651863876293, + "beta_2": -3.662396072463196, + "rain_fraction": 0.5553436279296875, + "mean_db": 8.847101002812746, + "stdev_db": 3.8570109547579055 + }, + "comparisons": [ + { + "seed": 0, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": true, + "distance": 0.6383365056784039, + "identical_targets_maximum_field_difference": 0.0, + "coupled_estimators_maximum_field_difference": 2.5451289848987813e-06, + "coupled_distance_difference": 2.5999754238448247e-08, + "coupled_threshold_classification_changes": 0 + }, + { + "seed": 20260908, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": true, + "distance": 0.6095533515286298, + "identical_targets_maximum_field_difference": 0.0, + "coupled_estimators_maximum_field_difference": 2.0263810918130787e-06, + "coupled_distance_difference": 3.071912091368034e-08, + "coupled_threshold_classification_changes": 0 + } + ] + }, + { + "name": "isolated_convection", + "input_sha256": "89f2e91182e1d3e6ca25f72665c79bfbe21bb32b6e342820d009f00e5e4cd9cf", + "parameters": { + "beta_1": -2.6892617566424533, + "beta_2": -4.121223604980568, + "rain_fraction": 0.0120086669921875, + "mean_db": 7.35241634620303, + "stdev_db": 5.986137951026812 + }, + "comparisons": [ + { + "seed": 0, + "attempts": 5, + "selected_attempt": 1, + "tolerance_achieved": false, + "distance": 1.7682317797999607, + "identical_targets_maximum_field_difference": 0.0, + "coupled_estimators_maximum_field_difference": 3.0861900794931785e-06, + "coupled_distance_difference": 2.7100723221096246e-08, + "coupled_threshold_classification_changes": 0 + }, + { + "seed": 20260908, + "attempts": 5, + "selected_attempt": 5, + "tolerance_achieved": false, + "distance": 1.372658775569873, + "identical_targets_maximum_field_difference": 0.0, + "coupled_estimators_maximum_field_difference": 3.81796733961437e-06, + "coupled_distance_difference": 1.336142596564116e-08, + "coupled_threshold_classification_changes": 0 + } + ] + }, + { + "name": "maximum_war", + "input_sha256": "c4b4056eb831909d7743ed843cafe5d8e2d07ee51aaaae48baf5b13bcd2e228f", + "parameters": { + "beta_1": -2.0124000625157166, + "beta_2": -2.815792829116034, + "rain_fraction": 0.97393798828125, + "mean_db": 4.864743029213302, + "stdev_db": 2.3268029538539783 + }, + "comparisons": [ + { + "seed": 0, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false, + "distance": 2.239759280240739, + "identical_targets_maximum_field_difference": 0.0, + "coupled_estimators_maximum_field_difference": 7.808604465253666e-07, + "coupled_distance_difference": 1.093659296458327e-08, + "coupled_threshold_classification_changes": 0 + }, + { + "seed": 20260908, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false, + "distance": 1.8951416473971945, + "identical_targets_maximum_field_difference": 0.0, + "coupled_estimators_maximum_field_difference": 5.423179940677869e-07, + "coupled_distance_difference": 7.69973840242244e-09, + "coupled_threshold_classification_changes": 0 + } + ] + } + ] +} diff --git a/doc/proposals/parametric_engine.md b/doc/proposals/parametric_engine.md new file mode 100644 index 00000000..ac91a259 --- /dev/null +++ b/doc/proposals/parametric_engine.md @@ -0,0 +1,229 @@ +# Step 4: three-observation parametric nowcast engine + +Implemented on `parametric-nowcasting`, 15 October 2026, under the +[agreed scope](parametric_nowcasting.md), using the accepted +[estimators](parametric_estimators.md) and +[calibrated stochastic generator](calibrated_generator.md). + +## Endpoint + +```python +from pysteps import nowcasts + +ensemble, diagnostics = nowcasts.get_method("parametric")( + rain, # floating ndarray (3, y, x), mm/h + timesteps=12, + n_ens_members=20, + kmperpixel=1.0, + timestep=5.0, # minutes, fractional values supported + seed=42, + return_diagnostics=True, +) +``` + +The result is a float64 NumPy array `(member, lead, y, x)` in mm/h, starting +one observation interval after the newest field. Supplied motion is optional; +otherwise it is estimated from the newest two transformed observations. +All preparation and estimation happen internally. Public registration is +`pysteps.nowcasts.get_method("parametric")`; it is separate from native STEPS's +existing `noise_method="parametric"` option. + +The [engine](../../pysteps/nowcasts/parametric.py), +[user guide](../source/user_guide/parametric_nowcast.rst), and +[runnable gallery example](../../examples/plot_parametric_nowcast.py) describe +the settings and diagnostics. NumPy, SciPy, and existing pySTEPS components are +sufficient; internal Lucas-Kanade motion uses the existing OpenCV dependency. +No new runtime packages are required. + +## Preserved scientific behavior + +The engine uses dBR observations, normalized Gaussian cascades, source-compatible +Lagrangian correlation estimates, the discrete AR(2) persistence definition, +and the accepted spectral/marginal estimators. It selects ordinary or fitted +latent generation moments, applies the regional low-coverage triplet fallback, +then enforces joint spectral and persistence bounds. Raw estimates, selections, +bounds, and effective settings remain separate in diagnostics. + +Each forecast advances Eulerian cascade histories by their respective one/two +timestep backward trajectories, adds normalized calibrated innovations, applies +the source global normalization across levels and space, recomposes with +innovation moments, and performs the reference marginal mapping. The resulting +rainfall is returned before boundary tapering. The transformed field is then +tapered and decomposed again for the next state. This feedback, and stochastic +inflow following zero-anomaly advection, are part of the method. + +Independent member streams are spawned from a local SeedSequence. Every member +starts from observed initial states; kernels do not mutate shared observations. +Leading members agree when ensemble size increases. The old source member-state +aliasing bug is absent in the identified source revision and is explicitly +covered by the port's single-member/prefix regression test. + +Lead-wise callbacks receive all members in mm/h. They can modify their copy +without affecting state. `return_output=False` avoids storing the full horizon. +Callback exceptions propagate. Diagnostics record member seeds and per-lead +generator decisions without persisting anything. + +## Reference configuration and deliberate interface changes + +The numerical source revision is `pysteps_param` +`94bd4c1571637a8f246ac391470e1a820d6376a6`. The paper configuration and exported +inputs come from `psteps_param_paper` +`335f7660775480d865d9771820a81923c461832a`. + +Defaults are traced to `config/db/config.json` in the paper repository and +`config/db/nowcast_v2_acceptance.json` in the cases repository: six levels, +1 mm/h occurrence threshold, 20 km break, exponent 0.81, lag-pair exponent 2.72, +and the largest-level persistence reference. The marginal fitting cutoff is +1.25 dBR. Optical flow uses the source `dense_lucaskanade_v1` settings on the +latest pair. Source `StepsParameters` supplies moment selection (distance 0.5), +joint slope constraints, and reference-time bounds of 60–720 minutes. + +The low-coverage fallback applies below fraction 0.05, replacing the slopes and +reference time together with `-1.45`, `-3.253958088759979`, and +`330.61752959848894` minutes. The last two are regional medians frozen in the +source `config/low_war_fallback.json`, with its original archive provenance. +All these controls are configurable in `estimation_kwargs`; generator controls +are configurable in `noise_kwargs`. This extraction does not retune them. + +The later selected paper analysis uses the top three levels and exponent +0.7718612877. The standalone reference estimator supports it, and the engine +can select it explicitly through `reference_levels` and `temporal_exponent`. +It is not silently combined with the earlier evaluated forecast defaults. + +Interface changes are explicit: + +- Three complete chronological rain-rate arrays replace database lookups. +- Confirmed newest dry/subthreshold rainfall produces a zero ensemble; older + wet observations do not override the newest dry condition. +- Earlier constant fields produce zero normalized anomalies, avoiding division + by zero or normalized roundoff artifacts. Unavailable temporal estimates + outside the low-coverage policy raise; an investigated `reference_time` prior + can be supplied. Unavailable positive-rain marginal moments also raise. +- The post-AR fraction cutoff of 0.025 is preserved and configurable. The + operational issue-time eligibility gate at 0.03 is left to applications. +- Results are raw float64 rain rates before NetCDF packing. The operational + 200 mm/h rejection cap is left to applications; finite output is required. +- Integer lead counts, AR(2), and fixed parameters/motion are implemented. + Storage, NWP blending, irregular lead times, and velocity perturbations remain + outside this first endpoint. + +## Source parity and limits + +[Frozen complete forecast arrays](../../pysteps/tests/data/parametric_nowcast_reference.npz) +and [metadata](../../pysteps/tests/data/parametric_nowcast_reference.json) cover +two deterministic synthetic histories, including positive low coverage and a +7.5-minute cadence. Expectations were generated using original transformation, +estimators, generation QA, calibrated generation, and `update_field` definitions. +Only operational imports are removed; numerical statements are unchanged. +The source CLI's feedback/member loop is assembled explicitly outside storage. +Tests do not need either external repository or a database. + +Regenerate in the source environment, with this checkout on `PYTHONPATH`: + +```bash +python pysteps/tests/data/generate_nowcast_reference.py /path/to/pysteps_param +``` + +The [paper-history validation report](parametric_nowcast_validation.json) +records six comparisons: three archived 256×256 histories, two seeds, two +members, and twelve five-minute leads. Two histories produce positive-rain +forecasts; the low-coverage history exercises the explicit post-AR dry policy. +The archive layout is `[current, previous, future1, ...]`; indices `[1, 0, 2]` +provide three actual chronological observations. The forecast reference time +is therefore five minutes later than the old experiment cycle. Motion is +re-estimated for this latest pair rather than reusing older archived motion. + +With identical source-selected parameters and lag pairs, **all six complete +one-hour forecasts matched exactly** in this run. This isolates state evolution, +advection, calibrated innovations, marginal feedback, and member initialization. +The public call also runs both motion and parameter estimation internally. Its +selected parameters and lag pairs agree at the asserted numerical precision, +but the equivalent source nonlinear spectral regression and ported linear +least-squares solver differ in their final digits. + +Histogram interpolation, wet-pixel membership, and repeated feedback amplify +those perturbations. Across the six coupled runs: + +| Quantity | Largest observed difference | +| --- | ---: | +| Whole forecast RMSE | 0.004162 mm/h | +| Individual pixel rain rate | 1.406 mm/h | +| Mean field rain rate | 0.000401 mm/h | +| Wet fraction per field | 0.0001374 | +| Conditional dBR mean | 0.006208 dBR | +| Conditional dBR standard deviation | 0.004302 dBR | +| Estimated spectral slope | 0.000688 | +| Realized lag-1/lag-2 cascade correlation | 0.000229 | + +The largest run had 25 changed rain classifications among 1,572,864 output +pixels; calibration candidate selections agreed in every comparison. These +results support preservation of the evaluated method on the retained cases, +while documenting why the complete estimator-to-forecast chain is not claimed +to be pixel-identical. They are port checks, not new skill scores, a replay of +all published experiment cycles, or evidence of universal regional performance. +The prior scientific evaluation remains associated with its original revision. + +Reproduce without database access: + +```bash +python pysteps/tests/data/compare_parametric_nowcast_paper.py \ + /path/to/pysteps_param /path/to/psteps_param_paper \ + --output /tmp/parametric_nowcast_validation.json +``` + +The targeted regression suite passed **226 tests** (9 excluded existing STEPS +skill/export cases, 18 warnings from existing modules). This includes 54 new +engine tests, accepted components, existing noise/probability matching/spectral/ +transformation/AR tests, and existing STEPS nowcast behavior. Tests cover full +source forecasts, exact cached-advection parity for zero/constant/varying flow, +fractional cadence, independent streams and input purity, streaming equivalence, +dry/partly dry/sparse histories, safeguard choices, and invalid input/settings. +The gallery example executed successfully with internal motion and estimation. +The wheel built successfully; its engine, trajectory helper, and forecast +fixtures matched the checkout by SHA-256. All 154 packaged engine, estimator, +and generator tests passed from the extracted wheel outside the checkout. +Python formatting and `git diff --check` passed. These checks used Python +3.12.13, NumPy 2.5.1, and SciPy 1.18.0; the source/reference records also capture +hashes of the existing pySTEPS kernels used by both implementations. +This is a targeted test run; a full repository test suite and Sphinx build are +not claimed. + +## Non-parametric contribution: assessment, not implementation + +There is merit in a subsequent contribution, chiefly the **Eulerian stochastic +inflow treatment** evaluated in the source comparisons. Native upstream STEPS +already supports non-parametric spectral noise, observed cascade correlations, +and empirical CDF matching. Porting its entire operational wrapper would +duplicate existing functionality and bring database concerns into this scope. +See the [upstream STEPS forecast reference](https://pysteps.readthedocs.io/en/stable/generated/pysteps.nowcasts.steps.forecast.html). + +The source `pysteps_inflow.py` instead moves normalized histories before AR +updates, adds innovations throughout the Eulerian domain, and avoids a second +output-advection pass. That can sustain statistically generated rain at inflow +and enable the comparison used in the paper. It also changes interpolation and +boundary evolution; it should be evaluated and exposed as an explicit method +choice. Unlike the parametric engine, it retains native STEPS estimation and AR +state, with empirical output matching outside that state. + +The design implication is modest: + +1. Keep fixed-flow trajectory caching and zero-anomaly sampling independent of + rainfall parameter estimation and marginal choice. This engine does so in + `pysteps/nowcasts/_cascade_advection.py`, with parity tests of its numerical + behavior. The new helper remains private until a public use is justified. +2. Preserve separate state-update policies. Swapping parametric innovations for + non-parametric noise in this engine would retain the parametric marginal + feedback and would not reproduce the paper's non-parametric counterpart. +3. For a later upstream implementation, assess an explicit boundary/evolution + option within native STEPS that reuses its preparation and forecast kernels. + Avoid copying the source adapter's version-locked private method overrides; + it was validated specifically against pySTEPS 1.21.2, whereas this checkout + is 1.21.5. +4. A separate three-rain-field convenience interface could share validation, + transformation metadata, motion preparation, and streaming conventions if + useful to upstream. It need not change this parametric public interface or + force a general framework before either method is accepted. + +The non-parametric engine and native STEPS evolution are unchanged by this +contribution. Its useful boundary extension can be reviewed as a separate +scientific contribution after this engine is accepted. diff --git a/doc/proposals/parametric_estimators.md b/doc/proposals/parametric_estimators.md new file mode 100644 index 00000000..93dae1b3 --- /dev/null +++ b/doc/proposals/parametric_estimators.md @@ -0,0 +1,158 @@ +# Step 2: parameter estimators and temporal-model utilities + +Implemented on `parametric-nowcasting`, 14 October 2026, under the +[agreed contribution scope](parametric_nowcasting.md). The public forecast engine +will assemble these helpers internally; this stage provides the independently +callable numerical components, not the three-field forecast endpoint. + +## Public helpers + +| Module | Function | Purpose | +| --- | --- | --- | +| `pysteps.utils.statistics` | `compute_field_statistics` | Wet fraction, ordinary wet-pixel moments, and optional latent Normal fitting | +| `pysteps.utils.statistics` | `fit_lower_truncated_normal` | Profile-likelihood fit of the parent Normal from samples above a cutoff | +| `pysteps.utils.spectral` | `rapsd_logarithmic` | Source-compatible radial mean power and frequency arrays in logarithmic bins | +| `pysteps.utils.spectral` | `fit_power_law_spectrum` | Signed single-slope or continuous fixed-break spectral fit | +| `pysteps.timeseries.parametric` | `lagrangian_autocorrelation` | Lag-1/lag-2 estimates from supplied cascade histories and velocity | +| `pysteps.timeseries.parametric` | `ar2_correlation_time` | Stationary AR(2) discrete ACF sum including lag zero | +| `pysteps.timeseries.parametric` | `estimate_reference_correlation_time` | Geometric mean of selected scale-adjusted level times | +| `pysteps.timeseries.parametric` | `fit_ar2_correlations` | Invert a target time into an adjusted nonperiodic lag pair | +| `pysteps.timeseries.parametric` | `model_cascade_correlations` | Apply temporal scaling to nominal wavelengths, retaining resolution diagnostics | + +Interfaces use NumPy arrays and ordinary Python containers. NumPy and SciPy are +the only numerical dependencies added by these helpers, and both are already +pySTEPS dependencies. The extraction has no database, xarray, pandas, or statsmodels +requirement. Fixture regeneration and the comparison against external paper fields +use the source project's environment, including xarray, to evaluate its original +functions; these are developer checks, not dependencies of the port or CI tests. + +## Scientific conventions and intentional interface changes + +- Field statistics and radial spectra take finite, complete fields. Invalid + input is rejected. Dry and failed estimates remain explicit; estimates are + not clipped or replaced with regional climatology at this stage. +- Estimation converts real inputs to float64 and does not modify them. Source + parity is assessed on the same float64 fields; input-precision changes should + not be confused with a change to the fitting method. +- Rain occurrence includes samples equal to the occurrence threshold. The + denominator is the complete grid. Ordinary moments use population standard + deviations. Latent fitting has a separate cutoff and distinct result fields. +- The latent fit preserves the source profile likelihood and bounded standardized + cutoff interval `[-12, 12]`. Diagnostics identify insufficient/constant samples, + fitting failure, and boundary-limited finite solutions, without changing the + returned source-compatible location and scale. +- The spectrum is the radial mean of unscaled FFT power from a standardized + field. Real-FFT multiplicities reproduce the full-plane mean. Historical + logarithmic edges, including their endpoint rounding, are retained. This is + distinct from the existing `rapsd` binning/normalization convention. +- Spectrum arrays contain linear frequency and power. Conversion to the source + function's `10*log10` representation is explicit. The fit excludes two initial + bins and frequencies at or above `10**(-0.4)` cycles/pixel by default. The + breakpoint is a wavelength in units consistent with the supplied frequencies. +- A prescribed-break model is linear in its coefficients. The port solves the + same unweighted least-squares objective with `numpy.linalg.lstsq`, instead of + the source's nonlinear `curve_fit` call. Small optimizer/roundoff differences + are allowed in coefficient comparisons; the spectrum itself is unchanged. + Insufficient samples and rank-deficient models return explicit statuses. +- Lagrangian correlations retain successive unit-step advection and the + production `boundary="zero"` convention. An explicit `boundary="mask"` + alternative uses common finite support; it changes the estimates and is not + the reference default. Missing input observations are still rejected. +- AR correlation times use caller-supplied time units and the full lag-zero + contribution. Invalid/nonstationary pairs remain NaN. No AR(1) substitution is + applied. These are persistence summaries, not skill horizons. +- Reference-time estimation defaults to levels `(0, 1, 2)`, following the selected + paper method. Every selected time must be valid. Explicit `levels=(0,)` + reproduces the production CLI's largest-level reference. The source research + workflow selected the three-level method without changing the production CLI; + the port exposes the distinction rather than silently conflating them. +- Wavelengths come from the actual cascade filter's `central_freqs`. They remain + nominal filter labels. Scaling exponents and regional calibration settings are + supplied explicitly; this stage does not fit or retune them. +- Inverting a timescale uses the adjusted lag pair throughout. Requested cascade + times at or below one timestep resolve to white noise, achieving one timestep. + Diagnostics retain requested and achieved values and sub-timestep limit flags. + +## Source provenance + +Extraction was checked against: + +- `pysteps_param` revision `94bd4c1571637a8f246ac391470e1a820d6376a6`: + `src/pysteps_param/rain_stats_compute.py` and `shared_utils.py`. +- `psteps_param_paper` revision `335f7660775480d865d9771820a81923c461832a`: + `workflows/reference_timescale_experiment.py`, with the selected method described + in `docs/selected_correlation_scaling.md`. + +The frozen [numerical reference fixture](../../pysteps/tests/data/parametric_estimators_reference.json) +contains file SHA-256 hashes and numerical-library versions. Its expectations +were evaluated from the original numerical function definitions on deterministic +synthetic fields, rather than calculated by the port. Loading only those +definitions avoids package-level imports of operational/database modules. + +To regenerate in an environment containing the original project's dependencies, +with this pySTEPS checkout available on `PYTHONPATH`: + +```bash +python pysteps/tests/data/generate_parametric_reference.py \ + /path/to/pysteps_param /path/to/psteps_param_paper +``` + +Fixture updates should retain the source revision and explain any intended +numerical change. Normal test runs use the committed JSON and need neither source +repository nor a database. + +## Validation + +Recorded result: **90 targeted regression tests passed** under Python 3.12, +NumPy 2.5.1, and SciPy 1.18.0. Both documentation examples executed successfully. +The test run emitted nine warnings from existing transformation and +autoregression tests. This is a targeted suite, not a full pySTEPS test run or a +complete Sphinx documentation build. + +The targeted regression suite covers the new helpers and existing spectral, +transformation, and autoregression functions. It uses the normal pySTEPS FMI test +inputs for existing AR tests: + +```bash +PYSTEPSRC=/path/to/test/pystepsrc python -m pytest -q \ + pysteps/tests/test_parametric_estimators.py \ + pysteps/tests/test_utils_spectral.py \ + pysteps/tests/test_utils_transformation.py \ + pysteps/tests/test_timeseries_autoregression.py +``` + +Independent checks include full-complex-FFT radial means for even/odd square and +rectangular directional fields, orientation invariance, analytic piecewise power +laws, known truncated-Normal quantiles, explicit AR recurrence sums, reference +wavelength invariance, boundary treatments, and dry/invalid inputs. Source-parity +checks cover spectra, marginal estimates, advected correlations, timescale +inversion, and the selected paper reference calculation. + +The saved paper inputs `auckland_flood`, `isolated_convection`, and `maximum_war` +were compared at 1 km resolution, a 15 km spectral break, a 1 mm/h occurrence +threshold, and a 1.25 dBR latent fitting cutoff. Spectra matched exactly in this +run; marginal estimates matched to the asserted numerical precision. Maximum +spectral coefficient differences were below `3e-7`, arising from the equivalent +linear least-squares solution. The +[comparison report](parametric_estimators_validation.json) records input hashes, +source identity, individual differences, and fit statuses. + +The retained comparison script reproduces this check without database access: + +```bash +python pysteps/tests/data/compare_parametric_paper_fields.py \ + /path/to/pysteps_param /path/to/psteps_param_paper \ + --output /tmp/parametric_estimators_validation.json +``` + +These are estimator and temporal-model checks. They do not claim that the complete +forecast engine has been ported or that forecast skill has been re-evaluated. + +## Documentation and next stage + +API docstrings and reference entries are supplied, along with the executable +examples in `doc/source/user_guide/parametric_estimators.rst`. The next stage is +the calibrated stochastic field generator, which will consume these estimators +and return its calibration diagnostics. Forecast-state preparation, reference +configuration assembly, generation-time parameter safeguards, and the public +three-observation forecast call remain work for the engine stage. diff --git a/doc/proposals/parametric_estimators_validation.json b/doc/proposals/parametric_estimators_validation.json new file mode 100644 index 00000000..2852e2f9 --- /dev/null +++ b/doc/proposals/parametric_estimators_validation.json @@ -0,0 +1,64 @@ +{ + "source_sha256": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", + "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", + "threshold_mm_per_hour": 1.0, + "lower_truncation_dbr": 1.25, + "scale_break_km": 15.0, + "pixel_size_km": 1.0, + "cases": [ + { + "name": "auckland_flood", + "input_sha256": "f78b06cc1efeb8d9ee061426eac2f51ed82235e00f4941b1d47104d104f667bd", + "shape": [ + 256, + 256 + ], + "maximum_spectrum_error_db": 0.0, + "spectral_parameter_absolute_differences": { + "beta_1": 9.808779566355952e-09, + "beta_2": 5.952312864820897e-09, + "c1": 1.4661427982787245e-07, + "c2": 3.875054943591749e-08 + }, + "rain_fraction": 0.5553436279296875, + "marginal_fit_status": "ok", + "spectrum_fit_status": "ok" + }, + { + "name": "isolated_convection", + "input_sha256": "89f2e91182e1d3e6ca25f72665c79bfbe21bb32b6e342820d009f00e5e4cd9cf", + "shape": [ + 256, + 256 + ], + "maximum_spectrum_error_db": 0.0, + "spectral_parameter_absolute_differences": { + "beta_1": 6.97985180764249e-09, + "beta_2": 3.0828113395386936e-09, + "c1": 9.989584270897467e-08, + "c2": 1.8450258210123138e-08 + }, + "rain_fraction": 0.0120086669921875, + "marginal_fit_status": "ok", + "spectrum_fit_status": "ok" + }, + { + "name": "maximum_war", + "input_sha256": "c4b4056eb831909d7743ed843cafe5d8e2d07ee51aaaae48baf5b13bcd2e228f", + "shape": [ + 256, + 256 + ], + "maximum_spectrum_error_db": 0.0, + "spectral_parameter_absolute_differences": { + "beta_1": 1.841340635522215e-08, + "beta_2": 9.041391191288994e-09, + "c1": 2.6422485177590715e-07, + "c2": 5.8668618407864415e-08 + }, + "rain_fraction": 0.97393798828125, + "marginal_fit_status": "ok", + "spectrum_fit_status": "ok" + } + ] +} diff --git a/doc/proposals/parametric_nowcast_validation.json b/doc/proposals/parametric_nowcast_validation.json new file mode 100644 index 00000000..8a9d68d7 --- /dev/null +++ b/doc/proposals/parametric_nowcast_validation.json @@ -0,0 +1,389 @@ +{ + "provenance": { + "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", + "source_sha256": { + "rain_stats_compute.py": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", + "stochastic_generator.py": "94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343", + "cascade_utils.py": "77c362a23aa46b214f1d7f613fe0e747a5f6e7f861c17f85094c5b91db13d403", + "shared_utils.py": "4e7346cda389b99971246e4cf060adeec9c51cc9444704446a071ccc44a5288f", + "model_config.py": "3504c8492f8158cd84b155753cb205e0a7810cd04c37d35f253c9d25fc374128", + "transformer.py": "8693d646602538cd75332cd4367170b4387c9844102b011b7090dae033482703", + "steps_params.py": "265c31239ac8647fd599648e0f00dbdbc8c963e4a7bdaadfa89edb177c4c6c28", + "cli/nowcast.py": "d892c72f8daa608669fce0e185397a9664644b53663eeb1d5cc3708ceb7bec26", + "config/low_war_fallback.json": "bf0b3e0c9c12d4ce0f1cf64d3fe997a8e3364f285666071c24835c3141c45e87" + }, + "python_version": "3.12.13", + "numpy_version": "2.5.1", + "scipy_version": "1.18.0", + "native_pysteps_sha256": { + "cascade/decomposition.py": "2963be8c5399afa8c066ab6df93d199f8362f476a93d24add96a508aecb2b84e", + "cascade/bandpass_filters.py": "0527beb2aa67a620425479fa09fa18dbf8ef311109a1de72e9df6d436ee7403b", + "timeseries/autoregression.py": "08205f5de8d6dbe6fea0d3021c929d50990a6379098359a1aab6c4315808d851", + "extrapolation/semilagrangian.py": "5f870efe3b9152fefd417ecc61ffa188d8e9a306515f676b66890d9f1443e9c3" + } + }, + "paper_revision": "335f7660775480d865d9771820a81923c461832a", + "numpy_version": "2.5.1", + "scipy_version": "1.18.0", + "leads": 12, + "members": 2, + "seeds": [ + 0, + 20261015 + ], + "observation_policy": "Archive indices [1,0,2], oldest first; newest is original cycle +5 minutes", + "motion_policy": "Reference Lucas-Kanade settings, re-estimated from latest pair", + "config": { + "n_rows": 256, + "n_cols": 256, + "n_cascade_levels": 6, + "n_forecasts": 12, + "n_ensembles": 2, + "p_size": 1000.0, + "ht": 0.81, + "alpha": 2.72, + "timestep": 300.0, + "ar_order": 2, + "rain_threshold": 1.0, + "scale_break_km": 20.0, + "transform": "dbr" + }, + "comparisons": [ + { + "case": "control_1", + "seed": 0, + "input_sha256": "1a014857e4718864e14c1df0964168a2f1a31c2803d5ecb74e45add339c94b9c", + "observation_times": [ + "2024-02-26 07:05:00", + "2024-02-26 07:10:00", + "2024-02-26 07:15:00" + ], + "parameters": { + "rain_fraction": 0.1327667236328125, + "mean_db": 4.8145470424096395, + "stdev_db": 2.9715365932730777, + "beta_1": -2.0298196144411613, + "beta_2": -3.207739158955002, + "reference_time": 211.6473372524444 + }, + "source_parameters": { + "mean_db": 4.8145470424096395, + "stdev_db": 2.9715365932730777, + "rain_fraction": 0.1327667236328125, + "beta_1": -2.0298195905466616, + "beta_2": -3.207739166346171, + "corl_zero": 211.6473372524458, + "lag_1": [ + 0.9886705269210497, + 0.9665458039340769, + 0.9321040968079097, + 0.8544231076116594, + 0.5948506112677796, + 0.0 + ], + "lag_2": [ + 0.969483268768411, + 0.9116015105837252, + 0.8259299478064996, + 0.6518542775352314, + 0.2835793794265275, + 0.0 + ] + }, + "moment_source": "latent", + "low_coverage_fallback": false, + "identical_parameters_maximum_error_mm_per_hour": 0.0, + "coupled_maximum_error_mm_per_hour": 1.1143516455570275, + "coupled_rmse_mm_per_hour": 0.0038375352394328984, + "rain_classification_changes": 22, + "total_output_pixels": 1572864, + "candidate_selection_changes": 0, + "maximum_wet_fraction_difference": 0.0001373291015625, + "maximum_mean_rain_difference": 8.883720019947638e-05, + "maximum_wet_dbr_mean_difference": 0.006207566738429371, + "maximum_wet_dbr_stdev_difference": 0.004301183495847294, + "maximum_spectral_slope_difference": 0.0006874459001529054, + "maximum_lagrangian_correlation_difference": 0.00022803375540375592 + }, + { + "case": "control_1", + "seed": 20261015, + "input_sha256": "1a014857e4718864e14c1df0964168a2f1a31c2803d5ecb74e45add339c94b9c", + "observation_times": [ + "2024-02-26 07:05:00", + "2024-02-26 07:10:00", + "2024-02-26 07:15:00" + ], + "parameters": { + "rain_fraction": 0.1327667236328125, + "mean_db": 4.8145470424096395, + "stdev_db": 2.9715365932730777, + "beta_1": -2.0298196144411613, + "beta_2": -3.207739158955002, + "reference_time": 211.6473372524444 + }, + "source_parameters": { + "mean_db": 4.8145470424096395, + "stdev_db": 2.9715365932730777, + "rain_fraction": 0.1327667236328125, + "beta_1": -2.0298195905466616, + "beta_2": -3.207739166346171, + "corl_zero": 211.6473372524458, + "lag_1": [ + 0.9886705269210497, + 0.9665458039340769, + 0.9321040968079097, + 0.8544231076116594, + 0.5948506112677796, + 0.0 + ], + "lag_2": [ + 0.969483268768411, + 0.9116015105837252, + 0.8259299478064996, + 0.6518542775352314, + 0.2835793794265275, + 0.0 + ] + }, + "moment_source": "latent", + "low_coverage_fallback": false, + "identical_parameters_maximum_error_mm_per_hour": 0.0, + "coupled_maximum_error_mm_per_hour": 1.4053766415548472, + "coupled_rmse_mm_per_hour": 0.0041613734563649765, + "rain_classification_changes": 25, + "total_output_pixels": 1572864, + "candidate_selection_changes": 0, + "maximum_wet_fraction_difference": 0.0001373291015625, + "maximum_mean_rain_difference": 0.00040067873940896614, + "maximum_wet_dbr_mean_difference": 0.002161082378539625, + "maximum_wet_dbr_stdev_difference": 0.001632912831670108, + "maximum_spectral_slope_difference": 0.0005393250813998662, + "maximum_lagrangian_correlation_difference": 0.00011391220003056923 + }, + { + "case": "rain_fraction_low_2", + "seed": 0, + "input_sha256": "06cfc68af445b4f1c4433939a42bc7b3e33f26ef9e5b9cc96db6797a804e96dd", + "observation_times": [ + "2023-07-08 11:30:00", + "2023-07-08 11:35:00", + "2023-07-08 11:40:00" + ], + "parameters": { + "rain_fraction": 0.0113677978515625, + "mean_db": 1.9564918736104107, + "stdev_db": 1.693264993850012, + "beta_1": -1.45, + "beta_2": -3.253958088759979, + "reference_time": 330.61752959848894 + }, + "source_parameters": { + "mean_db": 1.9564918736104107, + "stdev_db": 1.693264993850012, + "rain_fraction": 0.0113677978515625, + "beta_1": -1.45, + "beta_2": -3.253958088759979, + "corl_zero": 330.61752959848894, + "lag_1": [ + 0.9927967740225752, + 0.9790070324134028, + 0.9582284221587539, + 0.9140934050829691, + 0.8057201684185906, + 0.24456150604323787 + ], + "lag_2": [ + 0.9805283882269249, + 0.9439248229189373, + 0.8904218562228328, + 0.7832391109096336, + 0.5593596606263346, + 0.04531524722317416 + ] + }, + "moment_source": "ordinary", + "low_coverage_fallback": true, + "identical_parameters_maximum_error_mm_per_hour": 0.0, + "coupled_maximum_error_mm_per_hour": 0.0, + "coupled_rmse_mm_per_hour": 0.0, + "rain_classification_changes": 0, + "total_output_pixels": 1572864, + "candidate_selection_changes": 0, + "maximum_wet_fraction_difference": 0.0, + "maximum_mean_rain_difference": 0.0, + "maximum_wet_dbr_mean_difference": null, + "maximum_wet_dbr_stdev_difference": null, + "maximum_spectral_slope_difference": null, + "maximum_lagrangian_correlation_difference": null + }, + { + "case": "rain_fraction_low_2", + "seed": 20261015, + "input_sha256": "06cfc68af445b4f1c4433939a42bc7b3e33f26ef9e5b9cc96db6797a804e96dd", + "observation_times": [ + "2023-07-08 11:30:00", + "2023-07-08 11:35:00", + "2023-07-08 11:40:00" + ], + "parameters": { + "rain_fraction": 0.0113677978515625, + "mean_db": 1.9564918736104107, + "stdev_db": 1.693264993850012, + "beta_1": -1.45, + "beta_2": -3.253958088759979, + "reference_time": 330.61752959848894 + }, + "source_parameters": { + "mean_db": 1.9564918736104107, + "stdev_db": 1.693264993850012, + "rain_fraction": 0.0113677978515625, + "beta_1": -1.45, + "beta_2": -3.253958088759979, + "corl_zero": 330.61752959848894, + "lag_1": [ + 0.9927967740225752, + 0.9790070324134028, + 0.9582284221587539, + 0.9140934050829691, + 0.8057201684185906, + 0.24456150604323787 + ], + "lag_2": [ + 0.9805283882269249, + 0.9439248229189373, + 0.8904218562228328, + 0.7832391109096336, + 0.5593596606263346, + 0.04531524722317416 + ] + }, + "moment_source": "ordinary", + "low_coverage_fallback": true, + "identical_parameters_maximum_error_mm_per_hour": 0.0, + "coupled_maximum_error_mm_per_hour": 0.0, + "coupled_rmse_mm_per_hour": 0.0, + "rain_classification_changes": 0, + "total_output_pixels": 1572864, + "candidate_selection_changes": 0, + "maximum_wet_fraction_difference": 0.0, + "maximum_mean_rain_difference": 0.0, + "maximum_wet_dbr_mean_difference": null, + "maximum_wet_dbr_stdev_difference": null, + "maximum_spectral_slope_difference": null, + "maximum_lagrangian_correlation_difference": null + }, + { + "case": "beta_1_low_2", + "seed": 0, + "input_sha256": "03ca980e074252c2d81881e11c5809b07d8f318e75e3b771b4584853ace19f5c", + "observation_times": [ + "2023-05-18 04:10:00", + "2023-05-18 04:15:00", + "2023-05-18 04:20:00" + ], + "parameters": { + "rain_fraction": 0.1277313232421875, + "mean_db": 4.680338144579789, + "stdev_db": 2.8004374842985467, + "beta_1": -3.3072923631770093, + "beta_2": -3.4072923631770093, + "reference_time": 106.34402803983085 + }, + "source_parameters": { + "mean_db": 4.680338144579789, + "stdev_db": 2.8004374842985467, + "rain_fraction": 0.1277313232421875, + "beta_1": -3.3072923670456933, + "beta_2": -3.4072923670456934, + "corl_zero": 106.3440280398298, + "lag_1": [ + 0.9770190561256513, + 0.9295443032634654, + 0.8483174997675328, + 0.5744147297713484, + 0.04464354973970493, + 0.0 + ], + "lag_2": [ + 0.9387203950980603, + 0.8197749713206718, + 0.6392620437195909, + 0.26310257812965865, + 0.0014952818008041493, + 0.0 + ] + }, + "moment_source": "ordinary", + "low_coverage_fallback": false, + "identical_parameters_maximum_error_mm_per_hour": 0.0, + "coupled_maximum_error_mm_per_hour": 0.12168902458015651, + "coupled_rmse_mm_per_hour": 0.00015763317986856312, + "rain_classification_changes": 0, + "total_output_pixels": 1572864, + "candidate_selection_changes": 0, + "maximum_wet_fraction_difference": 0.0, + "maximum_mean_rain_difference": 3.451076281324461e-05, + "maximum_wet_dbr_mean_difference": 0.00045008806622437447, + "maximum_wet_dbr_stdev_difference": 0.00025805494973374365, + "maximum_spectral_slope_difference": 0.00020641856839320738, + "maximum_lagrangian_correlation_difference": 7.662549719011924e-05 + }, + { + "case": "beta_1_low_2", + "seed": 20261015, + "input_sha256": "03ca980e074252c2d81881e11c5809b07d8f318e75e3b771b4584853ace19f5c", + "observation_times": [ + "2023-05-18 04:10:00", + "2023-05-18 04:15:00", + "2023-05-18 04:20:00" + ], + "parameters": { + "rain_fraction": 0.1277313232421875, + "mean_db": 4.680338144579789, + "stdev_db": 2.8004374842985467, + "beta_1": -3.3072923631770093, + "beta_2": -3.4072923631770093, + "reference_time": 106.34402803983085 + }, + "source_parameters": { + "mean_db": 4.680338144579789, + "stdev_db": 2.8004374842985467, + "rain_fraction": 0.1277313232421875, + "beta_1": -3.3072923670456933, + "beta_2": -3.4072923670456934, + "corl_zero": 106.3440280398298, + "lag_1": [ + 0.9770190561256513, + 0.9295443032634654, + 0.8483174997675328, + 0.5744147297713484, + 0.04464354973970493, + 0.0 + ], + "lag_2": [ + 0.9387203950980603, + 0.8197749713206718, + 0.6392620437195909, + 0.26310257812965865, + 0.0014952818008041493, + 0.0 + ] + }, + "moment_source": "ordinary", + "low_coverage_fallback": false, + "identical_parameters_maximum_error_mm_per_hour": 0.0, + "coupled_maximum_error_mm_per_hour": 1.0019848242084561, + "coupled_rmse_mm_per_hour": 0.001818660593679053, + "rain_classification_changes": 5, + "total_output_pixels": 1572864, + "candidate_selection_changes": 0, + "maximum_wet_fraction_difference": 4.57763671875e-05, + "maximum_mean_rain_difference": 0.0001150467184827253, + "maximum_wet_dbr_mean_difference": 0.0007748684326314148, + "maximum_wet_dbr_stdev_difference": 0.0005629812109284593, + "maximum_spectral_slope_difference": 0.0002413728122387937, + "maximum_lagrangian_correlation_difference": 6.59631984430975e-05 + } + ] +} diff --git a/doc/proposals/parametric_nowcasting.md b/doc/proposals/parametric_nowcasting.md new file mode 100644 index 00000000..ede5eff4 --- /dev/null +++ b/doc/proposals/parametric_nowcasting.md @@ -0,0 +1,319 @@ +# Scope proposal: parametric ensemble nowcasting + +Status: agreed development scope and broad public-interface outline for the +`parametric-nowcasting` branch; proposed for upstream discussion. This document +does not imply upstream approval. The forecast API described below is now +implemented in this branch. + +Date: 14 October 2026. + +## Objective and endpoint + +Contribute a self-contained parametric ensemble nowcasting method to pySTEPS, +derived from the scientific components of `pysteps_param`. + +The public endpoint is: + +**Three consecutive rainfall fields → internal preparation and parameter +estimation → a nowcast ensemble.** + +An ordinary caller supplies rainfall observations and forecast settings. The +engine performs transformation, motion estimation, cascade decomposition, +statistical parameter estimation, temporal modelling, and ensemble generation. +The caller does not need to prepare or persist intermediate artifacts. + +The contribution includes the scientific components needed to reach this +endpoint, with those components also available for independent use. Database +services and operational orchestration remain in projects that depend on +pySTEPS. This boundary follows the direction discussed in +[pySTEPS issue #472](https://github.com/pySTEPS/pysteps/issues/472). + +## Public forecast contract + +### Observations + +- Accept a floating-point NumPy `ndarray` with shape `(3, y, x)` containing rain + rates in `mm/h`. +- Order the fields from oldest to newest, at a regular observation interval. +- Require consistent grid geometry and units across all three fields. +- Treat the newest observation as the forecast reference time. +- Use the three fields for AR(2) initialization and lag-correlation estimation. + +The first implementation targets complete, finite input grids. Invalid or +missing observations produce a clear error rather than being silently converted +to dry rainfall. A confirmed dry input history is a valid physical case and must +have defined dry-output behaviour. Sparse rainfall and partially dry histories +require explicit, documented estimation safeguards. + +### Forecast settings + +The caller specifies the number of forecast timesteps, ensemble size, observation +timestep, and pixel size. The initial method advances at the observation timestep; +arbitrary output intervals and irregular lead times are subsequent extensions. +Units must be explicit and consistent with the surrounding pySTEPS interfaces. + +The method supplies documented defaults for rainfall thresholds, cascade +settings, statistical fitting, temporal scaling, and generator calibration. +Users can override these settings for their region and data. Random-number +control is part of the public interface. + +Motion is estimated internally by default. An optional externally supplied +velocity field, with shape `(2, y, x)` in pixels per observation timestep, +supports controlled comparisons and applications with an existing motion +estimate. Motion remains fixed through the initial forecast and is shared across +members. + +### Results + +Return rainfall in `mm/h` as a floating-point NumPy `ndarray` with shape +`(member, lead_time, y, x)`. The first forecast is one timestep after the newest +observation; the input observation is not included as forecast lead zero. + +The default return value is the ensemble. Optional diagnostics provide the +estimated parameters, effective settings, motion estimate, fitting and fallback +status, and relevant generation diagnostics. An incremental-output callback can +follow the existing pySTEPS forecast conventions and remain independent of any +storage backend. + +The precise function signature, keyword names, and diagnostic container are +implementation decisions to agree with upstream maintainers. These decisions +must preserve the three-field entry point and internal estimation. + +NumPy arrays are the computational input and output throughout the engine. +Timestep, pixel size, and method settings are supplied separately; the forecast +does not require labelled coordinates or an xarray object. Diagnostics can use +ordinary Python containers and NumPy arrays. Applications may wrap the resulting +ensemble in xarray for labelled coordinates, analysis, or storage. + +## Scientific method included in the contribution + +The engine assembles the following stages: + +1. Validate the rainfall history and forecast settings, and resolve the reference + defaults together with any user overrides. +2. Apply the configured rainfall transformation, retaining threshold and dry-value + metadata. The reference parametric method operates in dBR. +3. Estimate motion from the input history, unless the caller supplies velocity. +4. Decompose the observations into cascades and prepare the history needed by the + forecast recursion and Lagrangian correlation estimation. +5. Estimate rainfall occurrence, ordinary wet-pixel moments, latent marginal + parameters, and spectral slopes at the configured scale break. +6. Estimate cascade correlations and construct the reference persistence and + temporal scaling model using the method supported by the paper experiments. +7. Generate calibrated stochastic fields and decompose them into normalized + cascade innovations. +8. Advance each ensemble member using advection, the cascade AR model, and those + innovations; recompose and apply the reference marginal adjustment. +9. Prepare the adjusted field's cascade state for the next forecast step, + including the reference boundary taper, and return rainfall in physical units. + +The initial forecast holds the estimated statistical parameter set fixed through +lead time. Each member evolves independently from the same observed initial +history, with its own reproducible random stream. + +The port must retain the evaluated state-evolution method. In the existing +implementation, the marginal-adjusted field is tapered and decomposed again to +form the next state. This feedback is scientifically consequential and belongs +inside the engine. Adding only a noise option to native STEPS would not reproduce +the complete method. + +Generated stochastic rainfall fields retain their rainfall marginal; they are +not themselves guaranteed to have global mean zero and variance one. Cascade +decomposition supplies normalized innovations. Marginal adjustment and realised +spectral matching must be described with their finite-grid approximations and +diagnostics. + +## Reference defaults and scientific evidence + +The default configuration represents the method evaluated in the paper workflow +and WRNZ experiments. It provides a usable reference implementation and can be +modified by users who have investigated their region and data. + +Keep the following distinction explicit: + +| Estimated from the three observations | Configurable method settings and defaults | +| --- | --- | +| Rainfall occurrence and marginal parameters | Rain threshold, dry value, and marginal fitting cutoff | +| Spectral parameters | Scale break, spectral fitting conventions, and calibration corrections | +| Motion and observed cascade correlations | Motion-estimator settings, cascade definition, and temporal scaling settings | +| Reference persistence estimate | Estimation safeguards, constraints, and any regional fallback values | + +Ordinary retained wet-pixel moments and fitted latent Normal parameters are +different quantities and must remain distinguishable. Rain occurrence and +latent-distribution fitting may use different thresholds. Temporal correlation +times must retain an explicit definition and timestep convention; they are not +forecast-skill horizons. + +Numerical reference defaults will be transcribed from the identified evaluated +configuration during extraction. They must not be assembled from unrelated test +configurations or silently replaced with settings from another model revision. +Record the effective values and overrides in diagnostics. Regional empirical +fallbacks must be identified as such and be configurable. + +The evidence sources are: + +| Project | Role and starting points | +| --- | --- | +| `pysteps_param` | Numerical implementation, component tests, and method notes; especially `rain_stats_compute.py`, `stochastic_generator.py`, `shared_utils.py`, and `cli/nowcast.py` under `src/pysteps_param/` | +| `psteps_param_paper` | Paper methods, calibration investigations, reproducible figures, and scientific comparisons; start with `README.md`, `docs/`, and `workflows/` | +| `pysteps_param_wrnz_cases` | Regional experiments and forecast-output evaluation; start with `experiments/calibrated_field_generator_acceptance/` and `experiments/nowcast_v2_acceptance/` | + +The paper repository is locally named `psteps_param_paper` and was referred to as +`pysteps_param_paper` in the scope discussion. + +As of this proposal, the author reports that the manuscript is close to submission +to the *Quarterly Journal of the Royal Meteorological Society*. It is not yet a +published reference. The author also reports that subsequent output evaluation +following a minor bug finding supports continued use of the reference defaults. +The scope therefore does not require retuning those defaults before porting. + +During extraction, associate the reference configuration with exact source +revisions, experiment manifests, and relevant evaluation artifacts. Record the +bug and the implementation revision covered by the subsequent evaluation in that +evidence record. This proposal records the author's assessment; it does not claim +an independent rerun of that evaluation. Historical experiment results must retain +their original implementation and configuration identities. + +## Reusable components and integration + +The forecast function provides the normal user entry point. Its components remain +independently callable for scientific investigation and testing. + +| Component | Integration direction | +| --- | --- | +| Spectral estimation and prescribed-break fitting | Extend or reuse `pysteps.utils.spectral`, preserving documented numerical conventions | +| Marginal statistics and lower-truncated Normal fitting | Provide array-based statistical utilities used by the engine | +| Correlation times and temporal scaling | Integrate with `pysteps.timeseries` and reuse cascade-filter frequencies | +| Calibrated stochastic field generation | Add a distinct method under `pysteps.noise` with explicit target parameters and diagnostics | +| Marginal adjustment | Reuse or extend `pysteps.postprocessing.probmatching` where conventions agree | +| Forecast preparation and state evolution | Add a parametric method under `pysteps.nowcasts` and register it through the usual method interface | +| Scientific parameter representation | Use a small container separating estimated values, effective settings, and diagnostics from operational identities | + +These module placements are proposals, not a requirement to create a new package +hierarchy. Reuse existing primitives when doing so preserves the evaluated +method. Isolate kernels from database imports and avoid requiring pandas or +xarray solely to estimate parameters or generate an ensemble. + +The current `StepsParameters` object combines scientific values, database +metadata, and operational QA. Extract the scientific representation and the +method safeguards needed by the forecast. Storage identities and operational +audit persistence stay with the application. + +## Dependencies + +The planned scientific core can use NumPy, SciPy, and existing pySTEPS primitives. +The source implementation's package dependencies do not all become dependencies +of the upstream method: extract the required numerical operations together with +their array-based interfaces. + +| Dependency | Expected role and current repository support | +| --- | --- | +| NumPy | Arrays, FFT operations, and random streams; already a core pySTEPS dependency | +| SciPy | Distribution functions, interpolation, optimization, and advection sampling; already a core pySTEPS dependency | +| OpenCV | Used through the existing Lucas–Kanade motion method; already listed in the pySTEPS requirements and Conda environments, with availability checked by the motion implementation | +| pyproj and netCDF4 | Relevant to separate geometry and I/O work; already listed in the pySTEPS requirements and Conda environments | + +xarray is used for result packaging in the source estimators and I/O. Return +NumPy arrays and ordinary parameter containers from the extracted estimators. +pandas and statsmodels support parameter-table processing and NWP smoothing in +the source workflow; they are not required by the initial fixed-parameter +nowcast engine. MongoDB dependencies remain with the operational application. + +No new required package is currently anticipated for the scoped engine. +Additional dependencies or higher minimum versions may be introduced if a +specific implementation need emerges. Document the reason, distinguish required +from optional capabilities, and keep the appropriate package metadata, +requirements files, Conda environments, documentation, and CI configuration +consistent. Verify the engine using the supported pySTEPS environment rather +than relying on packages present only in the source project's environment. + +## Supporting contributions and scope boundaries + +A transformation object may be useful as a wrapper around existing pySTEPS +transformation functions. The endpoint only requires consistent transformation +and metadata handling; a new public object hierarchy is optional. + +CF NetCDF projection handling and interoperability improvements can be separate +I/O contributions. Existing pySTEPS NetCDF support should be extended rather than +duplicated. A validated domain-geometry adapter and example JSON configuration +may also be useful. Neither feature is a prerequisite for passing arrays to the +forecast engine. Geometry adapters must respect pySTEPS raster-boundary and +pixel-centre conventions. + +The initial contribution excludes: + +- MongoDB, GridFS, field catalogues, and persistent forecast-cycle records. +- Database configuration loading and database-oriented command-line workflows. +- Archive selection, storm catalogues, scheduling, operational reporting, and + application-specific viewers. +- Radar/NWP parameter blending and lead-dependent statistical parameter inputs. +- Velocity perturbations and externally supplied forecast-state initialization. +- A new operational framework or mandatory database, NetCDF, or JSON input path. + +Parameter blending is a subsequent scientific extension once the standalone +forecast engine is established. External projects can retain complete operational +workflows while using the upstream engine for computation. + +## Validation and completion criteria + +The contribution is complete when a caller can pass three observations and +forecast settings to the public method and obtain a reproducible ensemble without +precomputing parameters or accessing a database. + +Validation should establish: + +- Component correctness for spectra, marginal fitting, temporal correlations, + advection, and stochastic generation, with independent references where useful. +- Correct input ordering, output shape, units, and forecast lead-time convention. +- Explicit behaviour for dry and sparse histories, invalid input, and failed or + constrained parameter estimates. +- Reproducibility with a fixed seed, independent member state and random streams, + and consistent results when using incremental output. +- Numerical parity with the identified reference implementation using matching + inputs, defaults, random streams, and state evolution. Document justified + differences rather than assuming exact equality across numerical backends. +- End-to-end preservation of the evaluated rainfall distributions, spectra, and + temporal behaviour, using suitable retained cases from the existing evidence. +- Compatibility with the pySTEPS dependency and supported-Python conventions, + together with the required upstream test checks. + +The existing scientific evidence supports the reference defaults. Port validation +establishes that the new implementation preserves that method; it does not impose +a requirement to discover a new calibration or demonstrate superiority in every +region. Material numerical changes require proportionate evaluation. + +## Delivery and documentation + +Step 2, the reusable parameter estimators and temporal-model utilities, is +implemented in this branch. See the [extraction and validation record](parametric_estimators.md) +for the public helpers, numerical conventions, source provenance, and checks. +Step 3, the calibrated generator and shared marginal mapping, is also implemented; +see its [extraction and validation record](calibrated_generator.md). The complete +forecast engine and executable example are now implemented; see the +[engine extraction and validation record](parametric_engine.md) for the +public call, reference defaults, source comparisons, limitations, and a +separate assessment of the non-parametric inflow extension. + +Use this Markdown proposal as the scope and design record for upstream discussion. +Link it from issue #472 or a subsequent contribution discussion when ready. + +Deliver the work in reviewable stages: + +1. Identify the evaluated reference revision and configuration, and agree the + public interface and diagnostic conventions. +2. Extract the reusable estimators and temporal-model utilities. +3. Integrate calibrated stochastic generation and its diagnostics. +4. Assemble the three-field forecast engine, preserving reference state evolution + and adding reproducible random-number control. +5. Complete parity checks, an executable forecast example, and user documentation. + +The published pySTEPS documentation currently uses reStructuredText. During +implementation, add API docstrings and reference entries in that format, a gallery +example starting from three rainfall fields, documentation of each reference +default and its override, and manuscript or publication citations as available. +The example should run without the source projects' database infrastructure. + +Keep detailed experiment artifacts with their research projects and provide a +concise, revision-specific validation summary in the upstream contribution. +Update this scope document if implementation changes the agreed endpoint or +scientific behaviour. diff --git a/doc/source/index.rst b/doc/source/index.rst index daf8678a..349f64ad 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -34,6 +34,9 @@ __ https://github.com/pySTEPS/pysteps Gallery <../auto_examples/index> My first nowcast (Colab Notebook) API Reference + Parametric rainfall estimators + Calibrated stochastic rainfall fields + Parametric ensemble nowcasting Example data Configuration file (pystepsrc) Machine learning applications diff --git a/doc/source/pysteps_reference/noise.rst b/doc/source/pysteps_reference/noise.rst index 62ba24f9..bff02a21 100644 --- a/doc/source/pysteps_reference/noise.rst +++ b/doc/source/pysteps_reference/noise.rst @@ -7,5 +7,6 @@ Implementation of deterministic and ensemble nowcasting methods. .. automodule:: pysteps.noise.interface .. automodule:: pysteps.noise.fftgenerators +.. automodule:: pysteps.noise.parametric .. automodule:: pysteps.noise.motion .. automodule:: pysteps.noise.utils diff --git a/doc/source/pysteps_reference/nowcasts.rst b/doc/source/pysteps_reference/nowcasts.rst index 35d95cbb..5a4e2f3a 100644 --- a/doc/source/pysteps_reference/nowcasts.rst +++ b/doc/source/pysteps_reference/nowcasts.rst @@ -10,6 +10,7 @@ Implementation of deterministic and ensemble nowcasting methods. .. automodule:: pysteps.nowcasts.extrapolation .. automodule:: pysteps.nowcasts.linda .. automodule:: pysteps.nowcasts.lagrangian_probability +.. automodule:: pysteps.nowcasts.parametric .. automodule:: pysteps.nowcasts.sprog .. automodule:: pysteps.nowcasts.sseps .. automodule:: pysteps.nowcasts.steps diff --git a/doc/source/pysteps_reference/timeseries.rst b/doc/source/pysteps_reference/timeseries.rst index 74e9087f..efb9848d 100644 --- a/doc/source/pysteps_reference/timeseries.rst +++ b/doc/source/pysteps_reference/timeseries.rst @@ -7,4 +7,5 @@ Methods and models for time series analysis. .. automodule:: pysteps.timeseries.autoregression .. automodule:: pysteps.timeseries.correlation +.. automodule:: pysteps.timeseries.parametric diff --git a/doc/source/pysteps_reference/utils.rst b/doc/source/pysteps_reference/utils.rst index 6682dc66..78447824 100644 --- a/doc/source/pysteps_reference/utils.rst +++ b/doc/source/pysteps_reference/utils.rst @@ -16,5 +16,6 @@ Implementation of miscellaneous utility functions. .. automodule:: pysteps.utils.pca .. automodule:: pysteps.utils.reprojection .. automodule:: pysteps.utils.spectral +.. automodule:: pysteps.utils.statistics .. automodule:: pysteps.utils.tapering .. automodule:: pysteps.utils.transformation diff --git a/doc/source/user_guide/calibrated_generator.rst b/doc/source/user_guide/calibrated_generator.rst new file mode 100644 index 00000000..92b36e20 --- /dev/null +++ b/doc/source/user_guide/calibrated_generator.rst @@ -0,0 +1,128 @@ +Calibrated stochastic rainfall fields +===================================== + +The calibrated generator creates a NumPy rainfall-marginal field from an explicit +statistical parameter mapping. It implements the ``inverse_calibrated_v2`` method +extracted from ``pysteps_param``. It is a component of the proposed parametric +nowcast engine; ensemble state evolution and observation preparation are separate +stages. + +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 eventual engine will estimate and select 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. + +Preserved reference behaviour +----------------------------- + +Independent ``U(0,1)`` samples are FFT-filtered using half the signed power slopes +as amplitude exponents. Both supplied slopes receive the default additive +correction -0.5; the location and scale receive no inverse correction. + +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 reference +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. No reference defaults were retuned during +this port. + +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 will consume 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 source post-AR dry policy is reproduced by explicitly passing +``min_rain_fraction=0.025``; that policy is distinct from stochastic generation. + +Validation +---------- + +Frozen complete fields and diagnostics compare the port with original functions +on seeded square/rectangular configurations. Tests also exercise filter exponents, +physical units, histogram cutoff semantics, random-stream independence, all +candidate-selection outcomes, and the source post-AR mapping. Fixtures include +source revisions and hashes and are included in wheel package data. + +Saved paper fields provide additional comparisons at two seeds. Identical target +parameters reproduce the original fields exactly in the recorded run. When the +ported estimators supply slightly different least-squares slopes, the complete +estimator-to-generator chain shows small numerical differences from histogram +interpolation. The revision-specific report under ``doc/proposals/`` records +those differences separately from identical-target generator parity. These checks +do not constitute a complete nowcast or forecast-skill evaluation. diff --git a/doc/source/user_guide/parametric_estimators.rst b/doc/source/user_guide/parametric_estimators.rst new file mode 100644 index 00000000..d5fcac5b --- /dev/null +++ b/doc/source/user_guide/parametric_estimators.rst @@ -0,0 +1,120 @@ +Parametric rainfall estimators +============================== + +The parametric estimators provide NumPy-based spatial statistics and temporal +model utilities extracted from ``pysteps_param``. They are the first components +of a proposed parametric nowcast engine; they do not yet implement the public +three-observation forecast endpoint. + +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. This example uses illustrative settings, not a complete reference +forecast configuration:: + + import numpy as np + 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 + transformed, metadata = dB_transform(rain, threshold=1.0, zerovalue=-0.1) + 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 preserves the source method's logarithmic bin edges, +standardization, and unscaled FFT power convention. 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"`` reproduces production zero filling +after each step. The explicit ``boundary="mask"`` alternative uses common +finite support across the aligned histories and changes the estimates; it is +not a silent replacement of the evaluated convention. + +: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. + +The selected paper reference-time calculation uses the geometric mean of +scale-adjusted times from the first three levels. Every contributing time must +be positive and finite. Explicit ``levels=(0,)`` reproduces the production +largest-level reference convention. The scaling exponent is supplied separately; +the helper does not recalibrate it. Use nominal wavelengths from the actual +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.7718612877449645 + 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. + +Validation and provenance +------------------------- + +The estimator tests compare against frozen numerical references evaluated from +the original source functions, together with independent full-FFT spectra, +known truncated-Normal samples, and explicit AR recurrence sums. Reference +metadata records source revisions and SHA-256 hashes. Tests require neither +the external repositories nor a database. The reference-generation script in +``pysteps/tests/data/generate_parametric_reference.py`` documents how to refresh +the fixture using the source project's environment. + +The defaults used to configure the eventual engine will be associated with the +evaluated paper and case-study configuration. These utilities leave regional +calibration settings and operational parameter-repair policies explicit. The +scope and extraction record are maintained under ``doc/proposals/``. diff --git a/doc/source/user_guide/parametric_nowcast.rst b/doc/source/user_guide/parametric_nowcast.rst new file mode 100644 index 00000000..9ba73979 --- /dev/null +++ b/doc/source/user_guide/parametric_nowcast.rst @@ -0,0 +1,198 @@ +Parametric ensemble nowcasting +============================== + +``pysteps.nowcasts.parametric.forecast`` takes three consecutive rain-rate +observations and returns an ensemble in mm/h. Transformation, motion estimation, +cascade decomposition, statistical estimation, and forecast evolution happen +inside the call. No intermediate files or database are needed. + +For observations stored in ``rain`` as a floating NumPy array ``(3, y, x)``, +ordered oldest to newest:: + + from pysteps import nowcasts + + forecast = nowcasts.get_method("parametric") + ensemble, diagnostics = forecast( + rain, + timesteps=12, + n_ens_members=20, + kmperpixel=1.0, + timestep=5.0, + seed=42, + return_diagnostics=True, + ) + +The result has shape ``(20, 12, y, x)``. Lead index zero is the first forecast, +five minutes after the newest observation. ``timestep`` is in minutes and can +be fractional, for example 7.5 for 450-second observations. ``kmperpixel`` is +the isotropic pixel spacing. Both are required even when motion is supplied. +Input must be finite, nonnegative rain rates with consistent geometry and +cadence. Masked arrays and missing pixels are rejected. + +A fixed velocity field can be supplied as the second argument or as +``velocity=velocity``. Its shape is ``(2, y, x)`` with x/y components in pixels +per observation timestep. Otherwise dense Lucas-Kanade motion is estimated +from the latest two transformed observations. Statistical parameters and motion +remain fixed throughout each forecast. See :ref:`sphx_glr_auto_examples_plot_parametric_nowcast.py` +for a complete runnable synthetic example. + +Reference settings and regional overrides +---------------------------------------- + +These defaults preserve the evaluated ``nowcast_v2`` engine configuration from +``psteps_param_paper/config/db/config.json`` and the WRNZ acceptance configuration, +combined with the identified numerical implementation and generation safeguards. +They are a configurable regional reference, not universal calibration constants. +The contribution record in ``doc/proposals/parametric_engine.md`` identifies +source revisions and validation limits. + +Top-level settings: + +.. list-table:: + :header-rows: 1 + + * - Setting + - Default + - Meaning + * - ``n_cascade_levels`` + - 6 + - Normalized Gaussian cascades + * - ``rain_threshold`` + - 1.0 + - Occurrence threshold, mm/h + * - ``scale_break`` + - 20.0 + - Fixed spectral break, km + * - ``zerovalue`` + - threshold dBR - 0.1 + - Dry transformed value + * - ``taper_sigma`` + - 5.0 + - Boundary-mask Gaussian width, pixels + * - ``min_rain_fraction`` + - 0.025 + - Post-AR suppression, strictly below + +``estimation_kwargs`` accepts these overrides (unknown keys raise): + +=========================== ======================== ============================== +Key Default Meaning +=========================== ======================== ============================== +``lower_truncation`` 1.25 Latent Normal fit cutoff, dBR +``temporal_exponent`` 0.81 Timescale versus wavelength +``alpha`` 2.72 Pre-adjustment lag-pair power +``reference_levels`` ``(0,)`` Levels used for reference time +``reference_time`` None Optional time prior, minutes +``moment_distance_limit`` 0.5 Latent/ordinary selection +``beta_1_bounds`` (-3.9, just below -1.25) Large-scale slope bounds +``beta_2_min`` -4.0 Small-scale lower bound +``minimum_beta_gap`` 0.1 beta_2 <= beta_1 - gap +``correlation_time_bounds`` (60.0, 720.0) Reference-time bounds, minutes +``low_rain_fraction`` 0.05 Regional fallback below this +``fallback_beta_1`` -1.45 Low-coverage slope replacement +``fallback_beta_2`` -3.253958088759979 Regional median replacement +``fallback_reference_time`` 330.61752959848894 Regional median, minutes +=========================== ======================== ============================== + +Ordinary wet-pixel moments and fitted parent-Normal moments are returned +separately in diagnostics. The latent pair is selected only if finite, with +positive scale, and within the configured normalized Euclidean distance of +the ordinary pair. Otherwise the ordinary pair is used. Rain fraction and +marginal moments are not clipped to a climatology. Below the low-coverage +threshold, both slopes and reference time are replaced together before joint +bounds are applied. These regional fallback medians were saved in the source +``config/low_war_fallback.json`` from the Auckland archive; they can be replaced. + +The default reference time uses the largest cascade only, as in the evaluated +engine. The standalone estimator defaults to three levels, reflecting the later +selected paper analysis. To investigate that configuration, explicitly set +``reference_levels=(0, 1, 2)`` and ``temporal_exponent=0.7718612877`` together. +That later configuration has component support but does not inherit the forecast +validation of the evaluated default. No automatic fallback drops failed levels. +Timescales use the one-sided discrete AR(2) ACF sum including lag zero; they are +persistence summaries rather than forecast-skill horizons. Nominal wavelengths +come from the actual Gaussian filter. Scale-specific times at or below one +timestep resolve to white noise, with the limitation recorded in diagnostics. + +``noise_kwargs`` accepts ``spectral_bias=-0.5``, ``low_war_threshold=0.1``, +``max_attempts=5``, ``distance_threshold=1.0``, +``beta_variances=(0.067738, 0.076405)``, ``nbins=500``, and ``eps=0.001``. +The last two also control the post-AR marginal mapping. See +:doc:`calibrated_generator` for calibration and finite-grid limitations. + +``motion_kwargs`` overrides dense Lucas-Kanade settings. Mapping values such +as ``lk_kwargs`` or ``fd_kwargs`` update the corresponding reference submapping. +The reference has window (31,31), three pyramid levels, minimum eigenvalue +3e-5, Shi-Tomasi corners (400, quality 0.025, separation 5), IDW interpolation +(8 neighbors, power 1.25), outlier settings 3.5 and 20, and declustering scale 8. +It is estimated from two images, once, with no velocity perturbations. + +Dry and unavailable estimates +---------------------------- + +If the newest field has no rain at the occurrence threshold, the ensemble is +zero, including when earlier observations were wet. Motion/noise generation is +unnecessary and the diagnostic status is ``dry_latest_observation``. Subthreshold +rain counts as dry for this method. Earlier constant fields give zero normalized +anomalies rather than a division by zero. + +Positive rainfall requires at least two distinct wet-pixel values to estimate +its marginal. Failure raises a clear error. If spectral or temporal estimates +are unavailable outside the configured low-coverage fallback, the engine raises +instead of inventing estimates or reporting dry output. For example, partly dry +histories can lack a persistence estimate; users with an investigated temporal +prior can supply ``estimation_kwargs={"reference_time": 200.0}``. The raw failed +estimate remains visible, and bounds still apply to the supplied prior. + +The post-AR fraction cutoff is separate: a valid positive target below 0.025 +produces dry output by the reference policy. Set ``min_rain_fraction=0.0`` to +disable that suppression. The operational source's forecast-eligibility gate +at 0.03 is outside this computational engine. Missing data is always an error. + +State evolution and boundaries +------------------------------ + +Each member starts from the newest and previous observed cascade states. +One- and two-step semi-Lagrangian backward trajectories are cached once. The +two-step trajectory retains two unit integration steps. Histories are sampled +with linear interpolation and zero normalized anomalies outside the domain. +Normalized stochastic innovations are then added everywhere, including inflow. +There is no second output-advection pass or periodic wrap of rainfall. + +After the reference global normalization across cascade levels and space, +recomposition uses the innovation cascade's moments. Histogram-based marginal +mapping is applied to the recomposed field. The engine returns its inverse dBR +transform, then multiplies the transformed field by the smoothed one-step +domain mask and decomposes it again to form the next state. This nonlinear +feedback differs from native STEPS, which keeps its AR state separate from +output probability matching. Raw calibrated fields retain a rainfall marginal; +their cascade decomposition supplies normalized innovations. + +All calculations and results are float64. They precede the source workflow's +NetCDF packing and storage. Outputs have no regional 200 mm/h cap; finite +generated values are required. No input arrays or settings are modified. + +Randomness, diagnostics, and incremental output +---------------------------------------------- + +A local NumPy SeedSequence spawns independent member streams. With the same +seed and settings, the leading members agree when ensemble size increases. +Global NumPy random state and supplied SeedSequence objects are unchanged. +When seed is omitted, diagnostics record the entropy needed to repeat the run. +Numerical implementations, calibration retry decisions, and method settings +are part of reproducibility. + +``return_diagnostics=True`` returns ``(ensemble, diagnostics)`` with effective +settings, seed information, motion source/field, raw marginal and spectral +estimates, observed correlations/times, selected bounded parameters, safeguards, +AR coefficients, and calibration diagnostics indexed ``[member][lead]``. + +A callback receives one complete ``(member, y, x)`` array in mm/h per lead. +Its copy can be modified without affecting state or retained output. Set +``return_output=False`` to return None while streaming; this retains cascade +histories and current fields rather than the full forecast horizon. Diagnostics +can still be requested. Callback exceptions propagate to the caller. + +This first engine supports integer lead counts at the observation cadence, +fixed motion and parameters, complete grids, and AR(2). Irregular output times, +motion perturbations, and lead-dependent parameter blending remain extensions. diff --git a/examples/plot_parametric_nowcast.py b/examples/plot_parametric_nowcast.py new file mode 100644 index 00000000..70a817cb --- /dev/null +++ b/examples/plot_parametric_nowcast.py @@ -0,0 +1,66 @@ +""" +Parametric nowcast from three rain-rate fields +============================================ + +This self-contained example builds three synthetic observations, then lets the +parametric engine estimate motion and parameters and generate an ensemble. +It needs no radar archive, database, or labelled-array dependency. +""" + +import matplotlib.pyplot as plt +import numpy as np + +from pysteps import nowcasts +from pysteps.noise.parametric import generate_stochastic_field + +# Construct an illustrative rain-rate field and a translating observation +# history. In an application, pass three measured rain-rate arrays instead. +transformed = generate_stochastic_field( + (128, 128), + dict(beta_1=-2.4, beta_2=-3.1, rain_fraction=0.2, mean_db=6.0, stdev_db=3.0), + kmperpixel=1.0, + scale_break=20.0, + threshold=0.0, + seed=9, +) +rain = 10 ** (transformed / 10) +rain[rain < 1.0] = 0 +observations = np.stack( + [ + np.roll(rain, -2, axis=1), + np.roll(rain, -1, axis=1), + rain, + ] +) + +############################################################################### +# Forecast directly in mm/h. Motion is estimated internally from the latest +# two fields; no transformed input or stored cascade/parameter state is needed. +forecast = nowcasts.get_method("parametric") +ensemble, diagnostics = forecast( + observations, + timesteps=6, + n_ens_members=4, + kmperpixel=1.0, + timestep=5.0, + seed=42, + return_diagnostics=True, +) +print("Output shape:", ensemble.shape) +print("Selected generation parameters:", diagnostics["parameters"]) +print("Moment source:", diagnostics["safeguards"]["moment_source"]) + +############################################################################### +# Compare the newest observation with a member and the ensemble mean at 30 min. +fig, axes = plt.subplots(1, 3, figsize=(12, 4), constrained_layout=True) +for ax, field, title in zip( + axes, + [observations[-1], ensemble[0, -1], ensemble[:, -1].mean(axis=0)], + ["Newest observation", "Member 1, +30 min", "Ensemble mean, +30 min"], +): + image = ax.imshow(field, origin="lower", vmin=0, vmax=20, cmap="Blues") + ax.set_title(title) + ax.set_xlabel("x (km)") + ax.set_ylabel("y (km)") +fig.colorbar(image, ax=axes, label="Rain rate (mm/h)", shrink=0.75) +plt.show() diff --git a/pysteps/noise/__init__.py b/pysteps/noise/__init__.py index 7be00590..0e11951d 100644 --- a/pysteps/noise/__init__.py +++ b/pysteps/noise/__init__.py @@ -5,4 +5,4 @@ """ from .interface import get_method -from . import utils, motion, fftgenerators +from . import utils, motion, fftgenerators, parametric diff --git a/pysteps/noise/parametric.py b/pysteps/noise/parametric.py new file mode 100644 index 00000000..f8cf686a --- /dev/null +++ b/pysteps/noise/parametric.py @@ -0,0 +1,400 @@ +""" +pysteps.noise.parametric +======================== + +Statistically parameterized rainfall fields with inverse spectral calibration. + +.. autosummary:: + :toctree: ../generated/ + + power_law_filter + generate_stochastic_field + generate_calibrated_field +""" + +from collections.abc import Mapping + +import numpy as np + +from pysteps.postprocessing.probmatching import match_rainfall_distribution +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic + +GENERATOR_ID = "inverse_calibrated_v2" +SPECTRAL_BIAS = -0.5 +LOW_WAR_THRESHOLD = 0.1 +MAX_CALIBRATION_ATTEMPTS = 5 +SPECTRAL_DISTANCE_THRESHOLD = 1.0 +BETA_VARIANCES = (0.067738, 0.076405) + + +def _finite(name, value): + if not np.isscalar(value) or np.iscomplexobj(value) or not np.isfinite(value): + raise ValueError(f"{name} must be a finite real scalar") + return float(value) + + +def _shape(shape): + try: + shape = tuple(shape) + except TypeError: + raise ValueError( + "shape must contain two positive integer dimensions (y, x)" + ) from None + if len(shape) != 2 or any( + not isinstance(n, (int, np.integer)) or n < 1 for n in shape + ): + raise ValueError("shape must contain two positive integer dimensions (y, x)") + return tuple(int(n) for n in shape) + + +def _parameters(parameters): + if not isinstance(parameters, Mapping): + raise ValueError("parameters must be a mapping") + names = ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db") + if any(name not in parameters for name in names): + raise ValueError(f"parameters must contain {', '.join(names)}") + values = {name: _finite(name, parameters[name]) for name in names} + if not 0 <= values["rain_fraction"] <= 1: + raise ValueError("rain_fraction must lie in [0, 1]") + if values["stdev_db"] <= 0: + raise ValueError("stdev_db must be positive") + return values + + +def _random_generator(rng, seed): + if rng is not None: + if seed is not None: + raise ValueError("supply rng or seed, not both") + if not isinstance(rng, (np.random.Generator, np.random.RandomState)): + raise ValueError("rng must be a NumPy Generator or RandomState") + return rng + return np.random.default_rng(seed) + + +def power_law_filter(shape, beta_1, beta_2=None, kmperpixel=1.0, scale_break=None): + """Construct a full-FFT amplitude filter from signed power-spectrum slopes. + + Parameters + ---------- + shape: tuple of int + Output shape (y, x), each dimension positive. + beta_1, beta_2: float + Signed power slopes, power proportional to frequency**beta. The second + slope is optional; provide beta_2 and scale_break together. + kmperpixel: float, optional + Positive isotropic pixel spacing in km (default 1). + scale_break: float, optional + Positive break wavelength in km. beta_1 applies below its reciprocal + frequency, beta_2 at and above it, with continuity at the break. + + Returns + ------- + filter: ndarray + Finite full-FFT amplitude filter of shape (y, x), using half the power + exponents. DC amplitude equals one. The reference frequency is the + smallest nonzero frequency along the longer grid dimension. + """ + shape = _shape(shape) + beta_1 = _finite("beta_1", beta_1) + spacing = _finite("kmperpixel", kmperpixel) + if spacing <= 0: + raise ValueError("kmperpixel must be positive") + if (beta_2 is None) != (scale_break is None): + raise ValueError("provide beta_2 and scale_break together") + fy = np.fft.fftfreq(shape[0], d=spacing) + fx = np.fft.fftfreq(shape[1], d=spacing) + radius = np.sqrt(fy[:, None] ** 2 + fx[None, :] ** 2) + reference = 1 / (max(shape) * spacing) + result = np.ones(shape) + with np.errstate(divide="ignore", invalid="ignore", over="ignore"): + if beta_2 is None: + selected = radius > 0 + result[selected] = (radius[selected] / reference) ** (beta_1 / 2) + else: + beta_2 = _finite("beta_2", beta_2) + scale_break = _finite("scale_break", scale_break) + if scale_break <= 0: + raise ValueError("scale_break must be positive") + breakpoint = 1 / scale_break + low = radius < breakpoint + result[low] = (radius[low] / reference) ** (beta_1 / 2) + result[~low] = (breakpoint / reference) ** (beta_1 / 2) * ( + radius[~low] / breakpoint + ) ** (beta_2 / 2) + result[radius == 0] = 1.0 + if not np.isfinite(result).all(): + raise ValueError("parameters produce a nonfinite spectral filter") + return result + + +def _field_from_filter( + spectrum_filter, parameters, threshold, rng, zerovalue, nbins, eps +): + white_noise = rng.uniform(low=0, high=1, size=spectrum_filter.shape) + filtered = np.fft.ifft2( + np.fft.fft2(white_noise, spectrum_filter.shape) * spectrum_filter + ).real + return match_rainfall_distribution( + filtered, + parameters["rain_fraction"], + parameters["mean_db"], + parameters["stdev_db"], + threshold, + zerovalue=zerovalue, + nbins=nbins, + eps=eps, + ) + + +def generate_stochastic_field( + shape, + parameters, + kmperpixel, + scale_break, + threshold, + *, + rng=None, + seed=None, + zerovalue=None, + nbins=500, + eps=0.001, +): + """Generate a rainfall-marginal field without inverse spectral correction. + + Parameters + ---------- + shape: tuple of int + Output array shape (y, x). + parameters: mapping + Finite beta_1, beta_2, rain_fraction, mean_db, and positive stdev_db. + The last two are caller-selected parent-Normal generation moments. + The mapping is not modified; no regional QA or parameter clipping occurs. + kmperpixel, scale_break: float + Positive pixel spacing and spectral break wavelength, both in km. + threshold: float + Finite rain/no-rain threshold in transformed units (dBR for the reference + method). The returned field has the same transformed units. + rng: numpy.random.Generator or RandomState, optional + Caller-owned random stream; advanced in place. + seed: int or SeedSequence, optional + Seed for a local default_rng when rng is absent. Supply only one of them. + zerovalue: float, optional + Finite dry value below threshold, default threshold-0.1. + nbins, eps: int, float, optional + Marginal-mapping bins and quantile clip (defaults 500 and 0.001). + + Returns + ------- + field: ndarray + Float64 field of shape (y, x). Independent U(0,1) inputs are spectrally + filtered, then mapped to the requested occurrence and Normal marginal. + Dry anomalies remain continuous. This field is not globally standardized + or converted to linear rainfall. Occurrence and marginal matching are + approximate. An explicit zero fraction returns a constant dry field. + """ + parameters = _parameters(parameters) + threshold = _finite("threshold", threshold) + spectrum_filter = power_law_filter( + shape, parameters["beta_1"], parameters["beta_2"], kmperpixel, scale_break + ) + rng = _random_generator(rng, seed) + if parameters["rain_fraction"] == 0: + # Validate mapping settings without consuming a random draw. + return match_rainfall_distribution( + np.zeros(spectrum_filter.shape), + 0, + parameters["mean_db"], + parameters["stdev_db"], + threshold, + zerovalue=zerovalue, + nbins=nbins, + eps=eps, + ) + return _field_from_filter( + spectrum_filter, parameters, threshold, rng, zerovalue, nbins, eps + ) + + +def _estimate_beta(field, threshold, zerovalue, scale_break_pixels): + truncated = field.copy() + truncated[truncated <= threshold] = zerovalue + try: + frequency, power = rapsd_logarithmic(truncated) + result = fit_power_law_spectrum(frequency, power, scale_break_pixels) + except (RuntimeError, TypeError, ValueError, np.linalg.LinAlgError): + return np.nan, np.nan + return float(result["beta_1"]), float(result["beta_2"]) + + +def generate_calibrated_field( + shape, + parameters, + kmperpixel, + scale_break, + threshold, + *, + rng=None, + seed=None, + zerovalue=None, + spectral_bias=SPECTRAL_BIAS, + low_war_threshold=LOW_WAR_THRESHOLD, + max_attempts=MAX_CALIBRATION_ATTEMPTS, + distance_threshold=SPECTRAL_DISTANCE_THRESHOLD, + beta_variances=BETA_VARIANCES, + nbins=500, + eps=0.001, + return_diagnostics=False, +): + """Generate a field with inverse calibration and assessed realised spectral slopes. + + Parameters + ---------- + shape, parameters, kmperpixel, scale_break, threshold: + As in :func:`generate_stochastic_field`. Each dimension must contain at + least four pixels for spectral assessment. Parameters are not modified. + rng, seed, zerovalue, nbins, eps: + As in :func:`generate_stochastic_field`. + spectral_bias: float, optional + Additive correction to both input power slopes (default -0.5). Location + and scale receive no inverse correction in this v2 method. + low_war_threshold: float, optional + Fractions at or below this value permit retries (default 0.1). + max_attempts: int, optional + Maximum attempts for low fractions (default 5). Higher fractions use one. + distance_threshold: float, optional + Accept when sqrt(sum((estimated_beta-target_beta)**2/beta_variances)) is + at or below this nonnegative tolerance (default 1). + beta_variances: array_like + Two positive finite calibration error variances. Defaults are + (0.067738, 0.076405); these weights are empirical, not universal errors. + return_diagnostics: bool, optional + Also return selected spectral estimates, distance, attempted/selected + candidate indices, tolerance status, targets, effective calibration + settings, and per-candidate estimates and distances. + + Returns + ------- + field: ndarray + Selected float64 rainfall-marginal field, not standardized. Slopes are + assessed on a copy with values <= threshold replaced by zerovalue. + diagnostics: dict, optional + First in-tolerance candidate is selected. Otherwise use the closest + finite-distance candidate, retaining the first tie. If all spectra are + unavailable, use the last candidate and report unavailable_spectrum. + + Notes + ----- + Preserves ``inverse_calibrated_v2`` defaults and selection policy. Calibration + need not meet its tolerance; the outcome is explicit. A confirmed zero target + returns dry output with zero attempts and no spectral-success claim. Invalid + parameters or an unavailable positive-fraction marginal mapping raise errors. + This function does not perform operational moment selection or slope clipping. + """ + parameters = _parameters(parameters) + shape = _shape(shape) + if min(shape) < 4: + raise ValueError("calibrated shape must have at least four pixels per axis") + threshold = _finite("threshold", threshold) + spectral_bias = _finite("spectral_bias", spectral_bias) + low_war_threshold = _finite("low_war_threshold", low_war_threshold) + distance_threshold = _finite("distance_threshold", distance_threshold) + if not 0 <= low_war_threshold <= 1 or distance_threshold < 0: + raise ValueError( + "low_war_threshold must lie in [0,1] and distance_threshold must be nonnegative" + ) + if not isinstance(max_attempts, (int, np.integer)) or max_attempts < 1: + raise ValueError("max_attempts must be a positive integer") + if np.iscomplexobj(beta_variances): + raise ValueError("beta_variances must contain real values") + variances = np.asarray(beta_variances, dtype=float) + if ( + variances.shape != (2,) + or not np.isfinite(variances).all() + or np.any(variances <= 0) + ): + raise ValueError("beta_variances must contain two positive finite values") + zerovalue = ( + threshold - 0.1 if zerovalue is None else _finite("zerovalue", zerovalue) + ) + calibrated = dict( + parameters, + beta_1=parameters["beta_1"] + spectral_bias, + beta_2=parameters["beta_2"] + spectral_bias, + ) + spectrum_filter = power_law_filter( + shape, calibrated["beta_1"], calibrated["beta_2"], kmperpixel, scale_break + ) + rng = _random_generator(rng, seed) + settings = dict( + kmperpixel=float(kmperpixel), + scale_break=float(scale_break), + threshold=threshold, + zerovalue=zerovalue, + spectral_bias=spectral_bias, + low_war_threshold=low_war_threshold, + max_attempts=int(max_attempts), + distance_threshold=distance_threshold, + beta_variances=variances.copy(), + nbins=nbins, + eps=eps, + ) + estimates, distances = [], [] + + def output(candidate, status, tolerance_achieved): + field, beta_1, beta_2, distance, selected = candidate + diagnostics = dict( + generator=GENERATOR_ID, + status=status, + estimated_beta_1=beta_1, + estimated_beta_2=beta_2, + distance=distance, + attempts=len(distances), + selected_attempt=selected, + tolerance_achieved=tolerance_achieved, + target_parameters=parameters.copy(), + calibrated_parameters=calibrated.copy(), + settings=settings, + candidate_estimates=np.asarray(estimates).reshape(-1, 2), + candidate_distances=np.asarray(distances), + ) + return (field, diagnostics) if return_diagnostics else field + + if parameters["rain_fraction"] == 0: + field = match_rainfall_distribution( + np.zeros(shape), + 0, + parameters["mean_db"], + parameters["stdev_db"], + threshold, + zerovalue=zerovalue, + nbins=nbins, + eps=eps, + ) + return output((field, np.nan, np.nan, np.nan, 0), "dry", False) + best = last = None + limit = max_attempts if parameters["rain_fraction"] <= low_war_threshold else 1 + for attempt in range(1, limit + 1): + field = _field_from_filter( + spectrum_filter, calibrated, threshold, rng, zerovalue, nbins, eps + ) + beta_1, beta_2 = _estimate_beta( + field, threshold, zerovalue, scale_break / kmperpixel + ) + error = np.array([beta_1 - parameters["beta_1"], beta_2 - parameters["beta_2"]]) + distance = ( + float(np.sqrt(np.sum(error**2 / variances))) + if np.isfinite(error).all() + else np.inf + ) + estimates.append([beta_1, beta_2]) + distances.append(distance) + last = (field, beta_1, beta_2, distance, attempt) + if distance <= distance_threshold: + return output(last, "tolerance_achieved", True) + if np.isfinite(distance) and (best is None or distance < best[3]): + best = last + return output( + best if best is not None else last, + "best_available" if best is not None else "unavailable_spectrum", + False, + ) diff --git a/pysteps/nowcasts/_cascade_advection.py b/pysteps/nowcasts/_cascade_advection.py new file mode 100644 index 00000000..eb285c1e --- /dev/null +++ b/pysteps/nowcasts/_cascade_advection.py @@ -0,0 +1,41 @@ +"""Fixed-flow trajectory caching for Eulerian cascade evolution. + +Zero outside the domain represents a zero normalized anomaly. Innovations +are added after advection, including at inflow pixels. +""" + +import numpy as np +from scipy.ndimage import map_coordinates + +from pysteps.extrapolation.semilagrangian import extrapolate + + +def prepare_advection_coordinates(velocity, ar_order=2): + """Cache each lag using integer unit integration, in scipy (y, x) order.""" + velocity = np.asarray(velocity, dtype=float) + if ( + velocity.ndim != 3 + or velocity.shape[0] != 2 + or min(velocity.shape[1:]) < 2 + or not np.isfinite(velocity).all() + ): + raise ValueError("velocity must be finite with shape (2, y, x)") + if ar_order not in (1, 2): + raise ValueError("ar_order must be 1 or 2") + yy, xx = np.indices(velocity.shape[1:]) + xy = np.stack([xx, yy]) + coordinates = {} + for lag in range(1, ar_order + 1): + _, displacement = extrapolate(None, velocity, lag, return_displacement=True) + coordinates[lag] = (xy + displacement)[::-1].copy() + coordinates[lag].setflags(write=False) + return coordinates + + +def advect_field(field, coordinates): + """Sample a finite 2D anomaly with the reference constant-zero boundary.""" + if not np.isfinite(field).all(): + raise ValueError("field contains non-finite values") + return map_coordinates( + field, coordinates, order=1, mode="constant", cval=0.0, prefilter=False + ) diff --git a/pysteps/nowcasts/interface.py b/pysteps/nowcasts/interface.py index 3f87dbf6..7e72b3c6 100644 --- a/pysteps/nowcasts/interface.py +++ b/pysteps/nowcasts/interface.py @@ -24,6 +24,11 @@ four-dimensional array of shape (num_ensemble_members,num_timesteps,m,n). The time step of the output is taken from the inputs. +The ``parametric`` method additionally estimates motion when velocity is omitted. +It requires exactly three complete rain-rate observations in mm/h, pixel size +in km, and a timestep in minutes; its output is also in mm/h. Its first release +accepts integer timesteps only. See its method-specific forecast documentation. + .. autosummary:: :toctree: ../generated/ @@ -37,6 +42,7 @@ extrapolation, lagrangian_probability, linda, + parametric, sprog, sseps, steps, @@ -51,6 +57,7 @@ _nowcast_methods["lagrangian"] = extrapolation.forecast _nowcast_methods["lagrangian_probability"] = lagrangian_probability.forecast _nowcast_methods["linda"] = linda.forecast +_nowcast_methods["parametric"] = parametric.forecast _nowcast_methods["probability"] = lagrangian_probability.forecast _nowcast_methods["sprog"] = sprog.forecast _nowcast_methods["sseps"] = sseps.forecast @@ -81,6 +88,9 @@ def get_method(name): +-----------------+-------------------------------------------------------+ | linda | the LINDA method developed in :cite:`PCN2021` | +-----------------+-------------------------------------------------------+ + | parametric | three rain-rate observations to a parametric ensemble | + | | with internal parameter and motion estimation | + +-----------------+-------------------------------------------------------+ | lagrangian\_ | this approach computes local lagrangian probability | | probability | forecasts of threshold exceedences | +-----------------+-------------------------------------------------------+ diff --git a/pysteps/nowcasts/parametric.py b/pysteps/nowcasts/parametric.py new file mode 100644 index 00000000..dc23e653 --- /dev/null +++ b/pysteps/nowcasts/parametric.py @@ -0,0 +1,633 @@ +""" +pysteps.nowcasts.parametric +=========================== + +Three-observation parametric ensemble nowcasting, derived from pysteps_param. + +.. autosummary:: + :toctree: ../generated/ + + forecast +""" + +from collections.abc import Mapping +from copy import deepcopy + +import numpy as np +from scipy.ndimage import gaussian_filter + +from pysteps import motion +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.cascade.decomposition import decomposition_fft, recompose_fft +from pysteps.noise.parametric import generate_calibrated_field, GENERATOR_ID +from pysteps.postprocessing.probmatching import match_rainfall_distribution +from pysteps.timeseries.autoregression import estimate_ar_params_yw +from pysteps.timeseries.parametric import ( + ar2_correlation_time, + estimate_reference_correlation_time, + lagrangian_autocorrelation, + model_cascade_correlations, +) +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic +from pysteps.utils.statistics import compute_field_statistics +from pysteps.utils.transformation import dB_transform + +from ._cascade_advection import advect_field, prepare_advection_coordinates + +# Evaluated nowcast_v2 settings, not the later plotting-only timescale fit. +_ESTIMATION_DEFAULTS = dict( + lower_truncation=1.25, + temporal_exponent=0.81, + alpha=2.72, + reference_levels=(0,), + reference_time=None, + moment_distance_limit=0.5, + beta_1_bounds=(-3.9, float(np.nextafter(-1.25, -np.inf))), + beta_2_min=-4.0, + minimum_beta_gap=0.1, + correlation_time_bounds=(60.0, 720.0), + low_rain_fraction=0.05, + fallback_beta_1=-1.45, + fallback_beta_2=-3.253958088759979, + fallback_reference_time=330.61752959848894, +) +_MOTION_DEFAULTS = dict( + lk_kwargs=dict(winsize=(31, 31), nr_levels=3, min_eig_thr=3e-5), + fd_method="shitomasi", + fd_kwargs=dict(max_corners=400, quality_level=0.025, min_distance=5), + interp_method="idwinterp2d", + interp_kwargs=dict(k=8, power=1.25), + nr_std_outlier=3.5, + k_outlier=20, + decl_scale=8, +) +_NOISE_DEFAULTS = dict( + spectral_bias=-0.5, + low_war_threshold=0.1, + max_attempts=5, + distance_threshold=1.0, + beta_variances=(0.067738, 0.076405), + nbins=500, + eps=0.001, +) + + +def _finite(name, value): + if not np.isscalar(value) or np.iscomplexobj(value): + raise ValueError(f"{name} must be a finite real scalar") + try: + valid = np.isfinite(value) + except TypeError: + valid = False + if not valid: + raise ValueError(f"{name} must be a finite real scalar") + return float(value) + + +def _positive(name, value): + value = _finite(name, value) + if value <= 0: + raise ValueError(f"{name} must be positive") + return value + + +def _integer(name, value, minimum=1): + if ( + isinstance(value, (bool, np.bool_)) + or not isinstance(value, (int, np.integer)) + or value < minimum + ): + raise ValueError(f"{name} must be an integer >= {minimum}") + return int(value) + + +def _options(name, supplied, defaults): + if supplied is None: + return deepcopy(defaults) + if not isinstance(supplied, Mapping): + raise ValueError(f"{name} must be a mapping") + unknown = set(supplied) - set(defaults) + if unknown: + raise ValueError(f"Unknown {name}: {sorted(unknown)}") + result = deepcopy(defaults) + result.update(deepcopy(dict(supplied))) + return result + + +def _estimation_settings(supplied, threshold, n_levels): + settings = _options("estimation_kwargs", supplied, _ESTIMATION_DEFAULTS) + for key in settings: + if key not in ( + "reference_levels", + "reference_time", + "beta_1_bounds", + "correlation_time_bounds", + ): + settings[key] = _finite(key, settings[key]) + for key in ("alpha", "minimum_beta_gap", "fallback_reference_time"): + _positive(key, settings[key]) + if settings["moment_distance_limit"] < 0: + raise ValueError("moment_distance_limit must be nonnegative") + if not 0 <= settings["low_rain_fraction"] <= 1: + raise ValueError("low_rain_fraction must lie in [0, 1]") + if settings["lower_truncation"] < threshold: + raise ValueError("lower_truncation must be at or above the dBR threshold") + for key in ("beta_1_bounds", "correlation_time_bounds"): + bounds = np.asarray(settings[key], float) + if ( + bounds.shape != (2,) + or not np.isfinite(bounds).all() + or bounds[0] > bounds[1] + ): + raise ValueError(f"{key} must be two finite increasing bounds") + settings[key] = tuple(float(v) for v in bounds) + if settings["correlation_time_bounds"][0] <= 0: + raise ValueError("correlation_time_bounds must be positive") + if ( + settings["beta_2_min"] + > settings["beta_1_bounds"][0] - settings["minimum_beta_gap"] + ): + raise ValueError("beta_2_min and beta_1_bounds cannot satisfy minimum_beta_gap") + levels = np.asarray(settings["reference_levels"]) + if ( + levels.ndim != 1 + or not levels.size + or not np.issubdtype(levels.dtype, np.integer) + or np.any(levels < 0) + or np.any(levels >= n_levels) + or np.unique(levels).size != levels.size + ): + raise ValueError("reference_levels must contain distinct valid level indices") + settings["reference_levels"] = tuple(int(v) for v in levels) + if settings["reference_time"] is not None: + settings["reference_time"] = _positive( + "reference_time", settings["reference_time"] + ) + return settings + + +def _noise_settings(supplied): + settings = _options("noise_kwargs", supplied, _NOISE_DEFAULTS) + for key in ("spectral_bias", "low_war_threshold", "distance_threshold", "eps"): + settings[key] = _finite(key, settings[key]) + if ( + not 0 <= settings["low_war_threshold"] <= 1 + or settings["distance_threshold"] < 0 + ): + raise ValueError( + "noise fractions must lie in [0, 1] and distance must be nonnegative" + ) + if not 0 < settings["eps"] < 0.5: + raise ValueError("noise eps must lie in (0, 0.5)") + settings["max_attempts"] = _integer("max_attempts", settings["max_attempts"]) + settings["nbins"] = _integer("nbins", settings["nbins"], 2) + variances = np.asarray(settings["beta_variances"], float) + if ( + variances.shape != (2,) + or not np.isfinite(variances).all() + or np.any(variances <= 0) + ): + raise ValueError("beta_variances must contain two positive finite values") + settings["beta_variances"] = tuple(float(v) for v in variances) + return settings + + +def _decompose(field, bp_filter): + """Reference normalization, with zero anomalies for exactly constant levels.""" + result = decomposition_fft(field, bp_filter, compute_stats=True, normalize=False) + levels = result["cascade_levels"] + if np.ptp(field) == 0: + levels.fill(0) + result["stds"] = [0.0] * len(levels) + result["normalized"] = True + return result + for k, (mean, stdev) in enumerate(zip(result["means"], result["stds"])): + levels[k] = (levels[k] - mean) / stdev if stdev > 0 else 0 + result["normalized"] = True + return result + + +def _generation_parameters(statistics, spectrum, reference_time, settings): + """Select moments, replace low-coverage triplet, then apply joint bounds.""" + mu, sigma = statistics["ordinary_mean"], statistics["ordinary_stdev"] + if not np.isfinite(mu) or not np.isfinite(sigma) or sigma <= 0: + raise ValueError( + "Cannot estimate rainfall marginal: newest field needs at least two " + "distinct raining values; constant/single-pixel rain is not dry" + ) + latent = np.array([statistics["latent_mean"], statistics["latent_stdev"]]) + valid = np.isfinite(latent).all() and latent[1] > 0 + distance = ( + float(np.hypot(latent[0] - mu, latent[1] - sigma) / sigma) if valid else None + ) + use_latent = valid and distance <= settings["moment_distance_limit"] + selected = dict( + rain_fraction=statistics["rain_fraction"], + mean_db=float(latent[0] if use_latent else mu), + stdev_db=float(latent[1] if use_latent else sigma), + beta_1=spectrum["beta_1"], + beta_2=spectrum["beta_2"], + reference_time=reference_time, + ) + original = selected.copy() + low_war = selected["rain_fraction"] < settings["low_rain_fraction"] + if low_war: + selected.update( + beta_1=settings["fallback_beta_1"], + beta_2=settings["fallback_beta_2"], + reference_time=settings["fallback_reference_time"], + ) + if not all( + np.isfinite(selected[k]) for k in ("beta_1", "beta_2", "reference_time") + ): + raise ValueError( + "Unavailable spectral/persistence estimates outside the low-rainfall " + "fallback; inspect observations, scale_break, or reference_time" + ) + if selected["reference_time"] <= 0: + raise ValueError("Estimated reference_time must be positive") + before_bounds = selected.copy() + selected["beta_1"] = float(np.clip(selected["beta_1"], *settings["beta_1_bounds"])) + selected["beta_2"] = float( + np.clip( + selected["beta_2"], + settings["beta_2_min"], + selected["beta_1"] - settings["minimum_beta_gap"], + ) + ) + selected["reference_time"] = float( + np.clip(selected["reference_time"], *settings["correlation_time_bounds"]) + ) + audit = dict( + moment_source="latent" if use_latent else "ordinary", + moment_distance=distance, + moment_reason=( + "within_limit" + if use_latent + else ("distance" if valid else "invalid_latent") + ), + low_rain_fraction_fallback=low_war, + before_safeguards=original, + before_bounds=before_bounds, + bounded_parameters=[k for k in selected if selected[k] != before_bounds[k]], + ) + return selected, audit + + +def _advance( + histories, + parameters, + phi, + bp_filter, + coordinates, + rng, + threshold, + zerovalue, + kmperpixel, + scale_break, + noise_settings, + min_rain_fraction, +): + noise, diagnostics = generate_calibrated_field( + bp_filter["shape"], + parameters, + kmperpixel, + scale_break, + threshold, + rng=rng, + zerovalue=zerovalue, + return_diagnostics=True, + **noise_settings, + ) + innovation = _decompose(noise, bp_filter) + levels = np.empty_like(innovation["cascade_levels"]) + for k in range(len(levels)): + levels[k] = ( + phi[k, 0] * advect_field(histories[0]["cascade_levels"][k], coordinates[1]) + + phi[k, 1] + * advect_field(histories[1]["cascade_levels"][k], coordinates[2]) + + phi[k, 2] * innovation["cascade_levels"][k] + ) + # Preserve source global normalization over levels AND space. + stdev = np.std(levels) + if stdev > 0.1: + levels = (levels - np.mean(levels)) / stdev + updated = dict( + domain="spatial", + normalized=True, + compact_output=False, + cascade_levels=levels, + means=innovation["means"], + stds=innovation["stds"], + ) + field = match_rainfall_distribution( + recompose_fft(updated), + parameters["rain_fraction"], + parameters["mean_db"], + parameters["stdev_db"], + threshold, + zerovalue=zerovalue, + min_rain_fraction=min_rain_fraction, + nbins=noise_settings["nbins"], + eps=noise_settings["eps"], + ) + return field, diagnostics + + +def forecast( + precip, + velocity=None, + timesteps=1, + *, + n_ens_members=24, + kmperpixel=None, + timestep=None, + n_cascade_levels=6, + rain_threshold=1.0, + scale_break=20.0, + zerovalue=None, + taper_sigma=5.0, + min_rain_fraction=0.025, + seed=None, + motion_kwargs=None, + estimation_kwargs=None, + noise_kwargs=None, + callback=None, + return_output=True, + return_diagnostics=False, +): + """Generate a parametric nowcast ensemble from three rain-rate observations. + + Parameters + ---------- + precip: numpy.ndarray + Floating-point, finite nonnegative rain rates in mm/h, shape (3, y, x), + ordered oldest first at a regular interval. Grids must be complete. + velocity: numpy.ndarray, optional + Fixed x/y motion, shape (2, y, x), in pixels per observation timestep. + If omitted, estimate Lucas-Kanade motion from the latest two dBR fields. + timesteps: int, optional + Positive number of leads at the observation interval. Lead zero omitted. + n_ens_members, n_cascade_levels: int, optional + Ensemble size (24) and Gaussian cascade levels (6, minimum 3). + kmperpixel, timestep: float + Required positive isotropic pixel size in km and interval in minutes. + Fractional minutes are preserved. + rain_threshold, scale_break: float, optional + Rain occurrence threshold in mm/h (1) and spectral break in km (20). + zerovalue: float, optional + Dry dBR value below 10*log10(rain_threshold), default threshold_dbr-0.1. + taper_sigma: float, optional + Gaussian boundary-mask smoothing in pixels (5). Applied to transformed + forecasts before feedback decomposition, after producing the output. + min_rain_fraction: float, optional + Post-AR dry-output cutoff (0.025), strictly applied below this fraction. + Positive low fractions are still distinguished from missing estimates. + seed: int or numpy.random.SeedSequence, optional + Seed for independent member streams. Global RNG and supplied SeedSequence + are not mutated. Prefix members agree when ensemble size is increased. + motion_kwargs: dict, optional + Overrides of the reference dense Lucas-Kanade configuration. Ignored + when velocity is supplied; diagnostics record that motion source. + estimation_kwargs: dict, optional + Override marginal fitting, temporal scaling, moment selection, bounds, + and regional low-coverage fallbacks. See the parametric-nowcast user + guide for all keys and defaults. Reference time is in minutes. + noise_kwargs: dict, optional + Calibration controls for generate_calibrated_field; nbins and eps also + control post-AR marginal mapping. Randomness is controlled by seed. + callback: callable, optional + Called once per lead with a fresh (member, y, x) rain-rate array. + return_output, return_diagnostics: bool, optional + Return full ensemble (default True), and optional diagnostics (False). + With return_output=False, callback receives output without retaining + the full forecast. Errors from a callback propagate. + + Returns + ------- + ensemble: ndarray or None + Float64 mm/h array (member, lead, y, x), or None if return_output=False. + diagnostics: dict, optional + When requested, return (ensemble, diagnostics): raw estimates, selected + and bounded generation parameters, settings, motion, seed information, + and per-member/per-lead calibration results. + + Notes + ----- + Fixed statistical parameters and motion are used throughout the forecast. + Eulerian histories are advected by their respective one/two-step maps before + adding normalized innovations. Marginal correction, boundary taper, and + re-decomposition feed back into the state; this is not native STEPS with a + different noise option. Zero normalized anomalies at inflow are supplemented + by innovations. A newest field with no rain at the occurrence threshold + gives a dry ensemble even if older fields were wet. Earlier dry fields use + zero anomalies. Unavailable positive-rain moments raise rather than becoming + dry forecasts. No database, xarray, or additional runtime package is needed. + """ + if ( + not isinstance(precip, np.ndarray) + or np.ma.isMaskedArray(precip) + or not np.issubdtype(precip.dtype, np.floating) + or precip.ndim != 3 + or precip.shape[0] != 3 + or min(precip.shape[1:]) < 2 + or max(precip.shape[1:]) < 3 + ): + raise ValueError("precip must be a floating NumPy array with shape (3, y, x)") + if not np.isfinite(precip).all() or np.any(precip < 0): + raise ValueError("precip must contain only finite nonnegative rain rates") + rain = np.array(precip, dtype=float, copy=True) + shape = rain.shape[1:] + timesteps = _integer("timesteps", timesteps) + n_ens_members = _integer("n_ens_members", n_ens_members) + n_cascade_levels = _integer("n_cascade_levels", n_cascade_levels, 3) + kmperpixel = _positive("kmperpixel", kmperpixel) + timestep = _positive("timestep", timestep) + rain_threshold = _positive("rain_threshold", rain_threshold) + scale_break = _positive("scale_break", scale_break) + threshold = float(10 * np.log10(rain_threshold)) + zerovalue = ( + threshold - 0.1 if zerovalue is None else _finite("zerovalue", zerovalue) + ) + if zerovalue >= threshold: + raise ValueError("zerovalue must be below the dBR threshold") + taper_sigma = _finite("taper_sigma", taper_sigma) + min_rain_fraction = _finite("min_rain_fraction", min_rain_fraction) + if taper_sigma < 0 or not 0 <= min_rain_fraction <= 1: + raise ValueError("taper_sigma must be nonnegative; min_rain_fraction in [0, 1]") + if callback is not None and not callable(callback): + raise ValueError("callback must be callable") + for name, flag in [ + ("return_output", return_output), + ("return_diagnostics", return_diagnostics), + ]: + if not isinstance(flag, (bool, np.bool_)): + raise ValueError(f"{name} must be boolean") + estimation = _estimation_settings(estimation_kwargs, threshold, n_cascade_levels) + noise_settings = _noise_settings(noise_kwargs) + if motion_kwargs is not None and not isinstance(motion_kwargs, Mapping): + raise ValueError("motion_kwargs must be a mapping") + motion_settings = deepcopy(_MOTION_DEFAULTS) + for key, value in dict(motion_kwargs or {}).items(): + if isinstance(value, Mapping) and isinstance(motion_settings.get(key), Mapping): + motion_settings[key].update(deepcopy(dict(value))) + else: + motion_settings[key] = deepcopy(value) + if velocity is not None: + if np.iscomplexobj(velocity) or np.ma.isMaskedArray(velocity): + raise ValueError("velocity must be a finite real array") + velocity = np.array(velocity, dtype=float, copy=True) + if velocity.shape != (2, *shape) or not np.isfinite(velocity).all(): + raise ValueError("velocity must be finite with shape (2, y, x)") + if isinstance(seed, np.random.SeedSequence): + root_seed = np.random.SeedSequence( + seed.entropy, spawn_key=seed.spawn_key, pool_size=seed.pool_size + ) + else: + if seed is not None: + _integer("seed", seed, 0) + root_seed = np.random.SeedSequence(seed) + member_seeds = root_seed.spawn(n_ens_members) + streams = [np.random.default_rng(s) for s in member_seeds] + settings = dict( + timestep_minutes=timestep, + kmperpixel=kmperpixel, + n_cascade_levels=n_cascade_levels, + n_ens_members=n_ens_members, + timesteps=timesteps, + rain_threshold=rain_threshold, + scale_break_km=scale_break, + threshold_dbr=threshold, + zerovalue_dbr=zerovalue, + taper_sigma_pixels=taper_sigma, + min_rain_fraction=min_rain_fraction, + estimation=estimation, + noise=noise_settings, + motion=motion_settings, + ) + diagnostics = dict( + method="parametric", + generator=GENERATOR_ID, + settings=deepcopy(settings), + motion_source="supplied" if velocity is not None else "lucaskanade", + seed=dict( + entropy=root_seed.entropy, + spawn_key=root_seed.spawn_key, + pool_size=root_seed.pool_size, + member_spawn_keys=[s.spawn_key for s in member_seeds], + ), + generation=[[] for _ in range(n_ens_members)] if return_diagnostics else None, + ) + transformed, _ = dB_transform(rain, threshold=rain_threshold, zerovalue=zerovalue) + statistics = compute_field_statistics( + transformed[-1], threshold, estimation["lower_truncation"] + ) + diagnostics["statistics"] = statistics + output = np.empty((n_ens_members, timesteps, *shape)) if return_output else None + if statistics["rain_fraction"] == 0: + diagnostics.update( + status="dry_latest_observation", velocity=velocity, parameters=None + ) + if velocity is None: + diagnostics["motion_source"] = "not_required_for_dry_input" + for lead in range(timesteps): + fields = np.zeros((n_ens_members, *shape)) + if output is not None: + output[:, lead] = fields + if callback is not None: + callback(fields) + return (output, diagnostics) if return_diagnostics else output + if velocity is None: + velocity = np.asarray( + motion.get_method("LK")(transformed[-2:], **motion_settings), dtype=float + ) + if velocity.shape != (2, *shape) or not np.isfinite(velocity).all(): + raise ValueError("Motion estimator returned invalid velocity") + diagnostics["velocity"] = velocity.copy() + bp_filter = filter_gaussian(shape, n_cascade_levels) + observed = [_decompose(field, bp_filter) for field in transformed] + wavelengths = kmperpixel / bp_filter["central_freqs"] + correlations = lagrangian_autocorrelation( + np.stack([d["cascade_levels"] for d in observed]), velocity + ) + times = ar2_correlation_time(correlations[:, 0], correlations[:, 1], timestep) + estimated_time = estimate_reference_correlation_time( + times, + wavelengths, + estimation["temporal_exponent"], + levels=estimation["reference_levels"], + ) + reference_time = ( + estimated_time + if estimation["reference_time"] is None + else estimation["reference_time"] + ) + frequency, power = rapsd_logarithmic(transformed[-1]) + spectrum = fit_power_law_spectrum(frequency, power, scale_break / kmperpixel) + parameters, safeguards = _generation_parameters( + statistics, spectrum, reference_time, estimation + ) + model_lags, temporal_diagnostics = model_cascade_correlations( + parameters["reference_time"], + wavelengths, + estimation["temporal_exponent"], + estimation["alpha"], + timestep, + return_diagnostics=True, + ) + phi = np.array([estimate_ar_params_yw(pair) for pair in model_lags]) + diagnostics.update( + status="ok", + spectrum=spectrum, + observed_correlations=correlations, + observed_correlation_times_minutes=times, + estimated_reference_time_minutes=estimated_time, + wavelengths_km=wavelengths, + parameters=parameters.copy(), + safeguards=safeguards, + temporal_model=temporal_diagnostics, + model_correlations=model_lags, + ar_coefficients=phi, + constant_observed_levels=[ + np.flatnonzero(np.asarray(d["stds"]) == 0) for d in observed + ], + ) + coordinates = prepare_advection_coordinates(velocity) + taper = gaussian_filter(advect_field(np.ones(shape), coordinates[1]), taper_sigma) + # Kernels never mutate histories. Members start from the same observations + # and replace their own list entries after each update. + histories = [[observed[-1], observed[-2]] for _ in range(n_ens_members)] + for lead in range(timesteps): + fields = np.empty((n_ens_members, *shape)) + for member in range(n_ens_members): + field, generation = _advance( + histories[member], + parameters, + phi, + bp_filter, + coordinates, + streams[member], + threshold, + zerovalue, + kmperpixel, + scale_break, + noise_settings, + min_rain_fraction, + ) + with np.errstate(over="ignore"): + physical = 10 ** (field / 10) + physical[physical < rain_threshold] = 0 + if not np.isfinite(physical).all(): + raise ValueError("Generated rainfall contains nonfinite values") + fields[member] = physical + histories[member] = [ + _decompose(field * taper, bp_filter), + histories[member][0], + ] + if return_diagnostics: + diagnostics["generation"][member].append(generation) + if output is not None: + output[:, lead] = fields + if callback is not None: + callback(fields.copy()) + return (output, diagnostics) if return_diagnostics else output diff --git a/pysteps/postprocessing/probmatching.py b/pysteps/postprocessing/probmatching.py index ba493c51..3a020fb1 100644 --- a/pysteps/postprocessing/probmatching.py +++ b/pysteps/postprocessing/probmatching.py @@ -14,11 +14,130 @@ pmm_compute shift_scale resample_distributions + match_rainfall_distribution """ import numpy as np from scipy import interpolate as sip from scipy import optimize as sop +from scipy import stats as sstats + + +def match_rainfall_distribution( + field, + rain_fraction, + mean, + stdev, + threshold, + *, + zerovalue=None, + min_rain_fraction=0.0, + nbins=500, + eps=0.001, +): + """Map a finite field to a prescribed occurrence and Normal rainfall marginal. + + Parameters + ---------- + field: array_like + Nonempty, finite real 2D field, converted to float64 without modification. + rain_fraction: float + Requested wet fraction in [0, 1]. A histogram cutoff selects the wet + pixels before marginal mapping; the final wet fraction is approximate. + mean, stdev: float + Finite parent-Normal location and positive scale in transformed units. + These are caller-selected generation moments, not necessarily ordinary + retained wet-pixel moments. No moment selection is performed here. + threshold: float + Rain/no-rain threshold in the same transformed units. + zerovalue: float, optional + Finite dry value below threshold, default threshold-0.1. Used for an + explicit dry result; positive-fraction mapping retains continuous dry + anomalies below threshold, as in the source generator. + min_rain_fraction: float, optional + Explicit dry-output policy for fractions below this value. Default 0 + suppresses no positive fraction. The source post-AR adjustment uses 0.025. + nbins: int, optional + Number of histogram bins (default 500). + eps: float, optional + Normal-quantile probability clip in (0, 0.5), default 0.001. + + Returns + ------- + mapped: ndarray + Field of the same shape and transformed units. Zero requested fraction + or a fraction below min_rain_fraction returns the configured dry value. + + Notes + ----- + Preserves the source ``normalize_stoch_field`` mapping: find the last bin + whose cumulative probability is <= 1-rain_fraction, shift its upper edge + to threshold, and map the strictly-above-threshold sample's binned CDF to + clipped Normal quantiles using bin-centre linear interpolation. Wet mapped + values can cross back below threshold, and spectra can change. Neither + occurrence nor marginal moments are exact guarantees. Unavailable or + degenerate positive-fraction mappings raise ValueError, not a dry fallback. + """ + if np.iscomplexobj(field): + raise ValueError("field must contain real values") + field = np.asarray(field, dtype=float) + if field.ndim != 2 or field.size == 0 or not np.isfinite(field).all(): + raise ValueError("field must be a nonempty finite two-dimensional array") + for name, value in [ + ("rain_fraction", rain_fraction), + ("mean", mean), + ("stdev", stdev), + ("threshold", threshold), + ("min_rain_fraction", min_rain_fraction), + ("eps", eps), + ]: + if not np.isscalar(value) or np.iscomplexobj(value) or not np.isfinite(value): + raise ValueError(f"{name} must be finite") + if not 0 <= rain_fraction <= 1 or not 0 <= min_rain_fraction <= 1: + raise ValueError("rain fractions must lie in [0, 1]") + if stdev <= 0: + raise ValueError("stdev must be positive") + if not isinstance(nbins, (int, np.integer)) or nbins < 2: + raise ValueError("nbins must be an integer of at least two") + if not 0 < eps < 0.5: + raise ValueError("eps must lie in (0, 0.5)") + zerovalue = threshold - 0.1 if zerovalue is None else zerovalue + if ( + not np.isscalar(zerovalue) + or np.iscomplexobj(zerovalue) + or not np.isfinite(zerovalue) + or zerovalue >= threshold + ): + raise ValueError("zerovalue must be finite and below threshold") + if rain_fraction == 0 or rain_fraction < min_rain_fraction: + return np.full_like(field, zerovalue) + + distribution = sstats.cumfreq(field, numbins=nbins) + edges = distribution.lowerlimit + np.arange(nbins + 1) * distribution.binsize + cdf = distribution.cumcount / distribution.cumcount[-1] + below = np.flatnonzero(cdf <= 1 - rain_fraction) + rain_bin = int(below[-1]) if below.size else 0 + mapped = field + (threshold - edges[rain_bin + 1]) + wet = mapped > threshold + sample = mapped[wet] + if sample.size < 2 or np.ptp(sample) == 0: + raise ValueError("mapping produced too few distinct raining pixels") + distribution = sstats.cumfreq(sample, numbins=nbins) + edges = distribution.lowerlimit + np.arange(nbins + 1) * distribution.binsize + centres = 0.5 * (edges[:-1] + edges[1:]) + cdf = distribution.cumcount / distribution.cumcount[-1] + quantiles = sstats.norm.ppf(np.clip(cdf, eps, 1 - eps), loc=mean, scale=stdev) + transform = sip.interp1d( + centres, + quantiles, + kind="linear", + bounds_error=False, + fill_value=(quantiles[0], quantiles[-1]), + ) + mapped[wet] = transform(sample) + if not np.isfinite(mapped).all(): + raise ValueError("marginal mapping produced nonfinite values") + return mapped def compute_empirical_cdf(bin_edges, hist): diff --git a/pysteps/tests/data/calibrated_generator_reference.json b/pysteps/tests/data/calibrated_generator_reference.json new file mode 100644 index 00000000..34cf2dc2 --- /dev/null +++ b/pysteps/tests/data/calibrated_generator_reference.json @@ -0,0 +1,100 @@ +{ + "provenance": { + "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", + "source_sha256": { + "rain_stats_compute.py": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", + "stochastic_generator.py": "94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343" + }, + "numpy_version": "2.5.1", + "scipy_version": "1.18.0" + }, + "cases": [ + { + "shape": [ + 128, + 96 + ], + "parameters": { + "beta_1": -2.4, + "beta_2": -3.1, + "rain_fraction": 0.08, + "mean_db": 6.0, + "stdev_db": 3.5 + }, + "seed": 4, + "kmperpixel": 1.0, + "scale_break": 20.0, + "threshold": 0.0, + "settings": {}, + "diagnostics": { + "estimated_beta_1": -2.699411978542842, + "estimated_beta_2": -2.968097079810876, + "distance": 1.2454548636920237, + "attempts": 5, + "selected_attempt": 3, + "tolerance_achieved": false + } + }, + { + "shape": [ + 128, + 96 + ], + "parameters": { + "beta_1": -2.4, + "beta_2": -3.1, + "rain_fraction": 0.3, + "mean_db": 6.0, + "stdev_db": 3.5 + }, + "seed": 17, + "kmperpixel": 1.0, + "scale_break": 20.0, + "threshold": 0.0, + "settings": {}, + "diagnostics": { + "estimated_beta_1": -2.7242621897368915, + "estimated_beta_2": -3.2148751587431503, + "distance": 1.3133773650788025, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false + } + }, + { + "shape": [ + 95, + 127 + ], + "parameters": { + "beta_1": -2.4, + "beta_2": -3.1, + "rain_fraction": 0.05, + "mean_db": 6.0, + "stdev_db": 3.5 + }, + "seed": 11, + "kmperpixel": 1.0, + "scale_break": 20.0, + "threshold": 0.0, + "settings": { + "distance_threshold": 0.01 + }, + "diagnostics": { + "estimated_beta_1": -2.3629217611415716, + "estimated_beta_2": -2.8660387998408443, + "distance": 0.8583197619445808, + "attempts": 5, + "selected_attempt": 3, + "tolerance_achieved": false + } + } + ], + "post_ar_parameters": { + "rain_fraction": 0.35, + "mean": 6.5, + "stdev": 1.2, + "threshold": 0.0, + "zerovalue": -0.1 + } +} diff --git a/pysteps/tests/data/calibrated_generator_reference.npz b/pysteps/tests/data/calibrated_generator_reference.npz new file mode 100644 index 0000000000000000000000000000000000000000..904328575547302603628a722aac86025aa96b6f GIT binary patch literal 583668 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
+HahraR*3ftiaDd6~1e^VG`_a_)_XBS4X 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 z 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 z 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%0Dn%;C`maot&zbTur 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 rd^rAl-sRhBFJPYDGJc>Y~1R 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

Z@Z&l$@c$yk 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 literal 0 HcmV?d00001 diff --git a/pysteps/tests/test_noise_parametric.py b/pysteps/tests/test_noise_parametric.py new file mode 100644 index 00000000..e4fc5e5c --- /dev/null +++ b/pysteps/tests/test_noise_parametric.py @@ -0,0 +1,354 @@ +"""Calibrated generator source parity, distribution mapping, and selection tests.""" + +import copy +import json +from pathlib import Path + +import numpy as np +import pytest +from numpy.testing import assert_allclose, assert_array_equal + +from pysteps.noise import parametric +from pysteps.postprocessing.probmatching import match_rainfall_distribution + + +def _parameters(rain_fraction=0.08): + return dict( + beta_1=-2.4, beta_2=-3.1, rain_fraction=rain_fraction, mean_db=6.0, stdev_db=3.5 + ) + + +@pytest.fixture(scope="module") +def reference(): + root = Path(__file__).parent / "data/calibrated_generator_reference" + metadata = json.loads(root.with_suffix(".json").read_text()) + with np.load(root.with_suffix(".npz")) as stored: + arrays = {name: stored[name] for name in stored.files} + return metadata, arrays + + +def test_full_field_parity_with_original_generator(reference): + metadata, arrays = reference + for index, case in enumerate(metadata["cases"]): + field, diagnostics = parametric.generate_calibrated_field( + case["shape"], + case["parameters"], + case["kmperpixel"], + case["scale_break"], + case["threshold"], + seed=case["seed"], + return_diagnostics=True, + **case["settings"], + ) + assert_allclose(field, arrays[f"calibrated_{index}"], atol=1e-10, rtol=1e-12) + for name in ("attempts", "selected_attempt", "tolerance_achieved"): + assert diagnostics[name] == case["diagnostics"][name] + for name in ("estimated_beta_1", "estimated_beta_2", "distance"): + assert diagnostics[name] == pytest.approx( + case["diagnostics"][name], abs=3e-5 + ) + raw = parametric.generate_stochastic_field( + case["shape"], + case["parameters"], + case["kmperpixel"], + case["scale_break"], + case["threshold"], + seed=case["seed"], + ) + assert_allclose(raw, arrays[f"uncalibrated_{index}"], atol=1e-10, rtol=1e-12) + + +def test_shared_mapping_matches_post_ar_source(reference): + metadata, arrays = reference + original = arrays["post_ar_input"].copy() + result = match_rainfall_distribution( + original, **metadata["post_ar_parameters"], min_rain_fraction=0.025 + ) + assert_allclose(result, arrays["post_ar_output"], atol=1e-12, rtol=1e-12) + assert_array_equal(original, arrays["post_ar_input"]) + + +@pytest.mark.parametrize("shape", [(64, 48), (48, 64), (63, 37)]) +def test_filter_uses_half_power_exponents_and_physical_units(shape): + spacing = 0.5 + beta_1, beta_2, wavelength = -2.0, -3.5, 10.0 + filt = parametric.power_law_filter(shape, beta_1, beta_2, spacing, wavelength) + fy = np.fft.fftfreq(shape[0], spacing) + fx = np.fft.fftfreq(shape[1], spacing) + radius = np.hypot(fy[:, None], fx[None, :]) + reference = 1 / (max(shape) * spacing) + for y, x in [(0, 1), (1, 0), (shape[0] // 2, shape[1] // 2)]: + f = radius[y, x] + expected_power = ( + (f / reference) ** beta_1 + if f < 1 / wavelength + else ((1 / wavelength / reference) ** beta_1 * (f * wavelength) ** beta_2) + ) + assert filt[y, x] ** 2 == pytest.approx(expected_power, rel=1e-13) + assert filt[0, 0] == 1 and np.isfinite(filt).all() + # Scaling pixel spacing and break together leaves dimensionless responses. + assert_allclose( + filt, + parametric.power_law_filter(shape, beta_1, beta_2, spacing * 2, wavelength * 2), + ) + assert_allclose( + filt.T, + parametric.power_law_filter(shape[::-1], beta_1, beta_2, spacing, wavelength), + ) + + +@pytest.mark.parametrize("threshold", [-2, 0, 2]) +def test_mapping_upper_bin_edge_and_threshold_translation(threshold): + data = np.arange(100, dtype=float).reshape(10, 10) + result = match_rainfall_distribution(data, 0.3, 20, 1, threshold, nbins=100) + assert_array_equal(result > threshold, data >= 70) + base = match_rainfall_distribution(data, 0.3, 20, 1, 0, nbins=100) + assert_allclose(result[data < 70], base[data < 70] + threshold) + assert_allclose(result[data >= 70], base[data >= 70]) + assert_array_equal(data, np.arange(100).reshape(10, 10)) + + +def test_marginal_quantile_bounds_and_approximate_statistics(): + parameters = dict(_parameters(0.3), mean_db=6, stdev_db=1) + field = parametric.generate_stochastic_field( + (128, 128), parameters, 1, 20, 0, seed=123 + ) + wet = field[field > 0] + assert abs(wet.size / field.size - 0.3) < 0.02 + assert abs(wet.mean() - 6) < 0.1 and abs(wet.std() - 1) < 0.1 + from scipy.stats import norm + + assert wet.min() >= norm.ppf(0.001, loc=6, scale=1) + assert wet.max() <= norm.ppf(0.999, loc=6, scale=1) + assert field.std() != pytest.approx(1) # Raw marginal field is not standardized. + + +def test_first_acceptable_candidate_and_weighted_distance(monkeypatch): + estimates = iter([(-1.5, -2.2), (-2.3, -3), (-2.4, -3.1)]) + monkeypatch.setattr(parametric, "_estimate_beta", lambda *_: next(estimates)) + _, diagnostics = parametric.generate_calibrated_field( + (32, 32), _parameters(0.1), 1, 20, 0, seed=4, return_diagnostics=True + ) + assert diagnostics["attempts"] == diagnostics["selected_attempt"] == 2 + assert diagnostics["tolerance_achieved"] + assert diagnostics["distance"] == pytest.approx( + np.sqrt(0.1**2 / 0.067738 + 0.1**2 / 0.076405) + ) + + +def test_distance_tolerance_is_inclusive(monkeypatch): + parameters = dict(_parameters(0.1), beta_1=-2, beta_2=-3) + monkeypatch.setattr(parametric, "_estimate_beta", lambda *_: (-1, -3)) + _, diagnostics = parametric.generate_calibrated_field( + (32, 32), + parameters, + 1, + 20, + 0, + seed=4, + beta_variances=(1, 1), + distance_threshold=1, + return_diagnostics=True, + ) + assert diagnostics["distance"] == 1 and diagnostics["tolerance_achieved"] + assert diagnostics["attempts"] == 1 + + +def test_best_candidate_after_attempt_limit(monkeypatch): + estimates = iter([(-1, -2), (-2, -2.8), (-2.2, -2.9), (-1.8, -2.5), (-1.2, -2.1)]) + monkeypatch.setattr(parametric, "_estimate_beta", lambda *_: next(estimates)) + _, diagnostics = parametric.generate_calibrated_field( + (32, 32), + _parameters(0.05), + 1, + 20, + 0, + seed=4, + distance_threshold=0.01, + return_diagnostics=True, + ) + assert ( + not diagnostics["tolerance_achieved"] + and diagnostics["status"] == "best_available" + ) + assert diagnostics["attempts"] == 5 and diagnostics["selected_attempt"] == 3 + assert diagnostics["candidate_estimates"].shape == (5, 2) + + +def test_best_candidate_retains_first_tie(monkeypatch): + monkeypatch.setattr(parametric, "_estimate_beta", lambda *_: (-2, -3)) + _, diagnostics = parametric.generate_calibrated_field( + (32, 32), + _parameters(), + 1, + 20, + 0, + seed=4, + distance_threshold=0, + return_diagnostics=True, + ) + assert diagnostics["attempts"] == 5 and diagnostics["selected_attempt"] == 1 + + +def test_all_unavailable_spectra_select_last_candidate(monkeypatch): + monkeypatch.setattr(parametric, "_estimate_beta", lambda *_: (np.nan, np.nan)) + field, diagnostics = parametric.generate_calibrated_field( + (32, 32), _parameters(), 1, 20, 0, seed=4, return_diagnostics=True + ) + assert np.isfinite(field).all() and np.isinf(diagnostics["distance"]) + assert diagnostics["attempts"] == diagnostics["selected_attempt"] == 5 + assert ( + diagnostics["status"] == "unavailable_spectrum" + and not diagnostics["tolerance_achieved"] + ) + + +def test_numerical_spectrum_failure_is_reported(monkeypatch): + def unavailable(*_): + raise ValueError("unavailable numerical spectrum") + + monkeypatch.setattr(parametric, "rapsd_logarithmic", unavailable) + field, diagnostics = parametric.generate_calibrated_field( + (32, 32), _parameters(0.3), 1, 20, 0, seed=4, return_diagnostics=True + ) + assert np.isfinite(field).all() and diagnostics["status"] == "unavailable_spectrum" + + +def test_higher_fraction_uses_one_candidate(monkeypatch): + monkeypatch.setattr(parametric, "_estimate_beta", lambda *_: (-1, -2)) + _, diagnostics = parametric.generate_calibrated_field( + (32, 32), + _parameters(0.1001), + 1, + 20, + 0, + seed=4, + distance_threshold=0.01, + return_diagnostics=True, + ) + assert diagnostics["attempts"] == 1 + + +def test_parameters_seed_stream_and_calibration_controls(): + parameters = _parameters(0.3) + original = copy.deepcopy(parameters) + global_state = np.random.get_state() + first, diagnostics = parametric.generate_calibrated_field( + (64, 96), parameters, 1, 20, 0, seed=0, return_diagnostics=True + ) + assert_array_equal( + first, + parametric.generate_calibrated_field((64, 96), parameters, 1, 20, 0, seed=0), + ) + after = np.random.get_state() + assert global_state[0] == after[0] and global_state[2:] == after[2:] + assert_array_equal(global_state[1], after[1]) + assert parameters == original + assert diagnostics["calibrated_parameters"]["mean_db"] == parameters["mean_db"] + assert diagnostics["calibrated_parameters"]["stdev_db"] == parameters["stdev_db"] + assert diagnostics["calibrated_parameters"]["beta_1"] == parameters["beta_1"] - 0.5 + rng = np.random.default_rng(7) + a = parametric.generate_stochastic_field((64, 96), parameters, 1, 20, 0, rng=rng) + b = parametric.generate_stochastic_field((64, 96), parameters, 1, 20, 0, rng=rng) + assert not np.array_equal(a, b) + custom, custom_diagnostics = parametric.generate_calibrated_field( + (64, 96), + parameters, + 1, + 20, + 0, + seed=0, + spectral_bias=-0.2, + return_diagnostics=True, + ) + assert ( + custom_diagnostics["calibrated_parameters"]["beta_1"] + == parameters["beta_1"] - 0.2 + ) + assert not np.array_equal(first, custom) + + +def test_dry_target_does_not_consume_random_draw(): + rng = np.random.default_rng(7) + before = copy.deepcopy(rng.bit_generator.state) + field, diagnostics = parametric.generate_calibrated_field( + (32, 32), + _parameters(0), + 1, + 20, + 0, + rng=rng, + zerovalue=-1, + return_diagnostics=True, + ) + assert_array_equal(field, -1) + assert diagnostics["status"] == "dry" and diagnostics["attempts"] == 0 + assert not diagnostics["tolerance_achieved"] and np.isnan(diagnostics["distance"]) + assert rng.bit_generator.state == before + + +def test_post_ar_dry_policy_is_explicit_and_invalid_data_is_not_dry(): + field = np.random.default_rng(2).normal(size=(64, 64)) + suppressed = match_rainfall_distribution( + field, 0.01, 6, 2, 0, min_rain_fraction=0.025 + ) + assert_array_equal(suppressed, -0.1) + generated = match_rainfall_distribution(field, 0.01, 6, 2, 0) + assert np.any(generated > 0) + with pytest.raises(ValueError): + match_rainfall_distribution(np.full((8, 8), np.nan), 0, 6, 2, 0) + with pytest.raises(ValueError): + match_rainfall_distribution(np.ones((8, 8)), 0.1, 6, 2, 0) + + +@pytest.mark.parametrize( + "changes", + [ + {"max_attempts": 0}, + {"max_attempts": 1.5}, + {"distance_threshold": -1}, + {"distance_threshold": np.inf}, + {"low_war_threshold": 2}, + {"beta_variances": [0, 1]}, + {"beta_variances": [1]}, + {"beta_variances": np.array([1 + 1j, 1])}, + {"nbins": 1}, + {"eps": 0.5}, + {"zerovalue": 0}, + {"seed": 0, "rng": np.random.default_rng(0)}, + ], +) +def test_invalid_settings_raise(changes): + with pytest.raises(ValueError): + parametric.generate_calibrated_field( + (32, 32), _parameters(), 1, 20, 0, **changes + ) + + +@pytest.mark.parametrize( + "changes", + [ + {"rain_fraction": -1}, + {"rain_fraction": 1.1}, + {"mean_db": np.nan}, + {"stdev_db": 0}, + {"beta_1": np.inf}, + {"beta_2": None}, + ], +) +def test_invalid_parameters_raise(changes): + with pytest.raises(ValueError): + parametric.generate_calibrated_field( + (32, 32), dict(_parameters(), **changes), 1, 20, 0 + ) + + +def test_degenerate_geometry_and_too_sparse_mapping_are_explicit(): + with pytest.raises(ValueError): + parametric.power_law_filter((16, 16), -2, -3, 1, None) + with pytest.raises(ValueError): + parametric.generate_calibrated_field((2, 8), _parameters(), 1, 20, 0) + with pytest.raises(ValueError): + parametric.generate_stochastic_field( + (16, 16), _parameters(1e-12), 1, 20, 0, seed=0 + ) diff --git a/pysteps/tests/test_nowcasts_parametric.py b/pysteps/tests/test_nowcasts_parametric.py new file mode 100644 index 00000000..2789def2 --- /dev/null +++ b/pysteps/tests/test_nowcasts_parametric.py @@ -0,0 +1,423 @@ +"""Forecast contracts and source numerical parity, without operational services.""" + +from copy import deepcopy +import json +from pathlib import Path + +import numpy as np +import pytest +from scipy.ndimage import gaussian_filter + +from pysteps import nowcasts +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.extrapolation.semilagrangian import extrapolate +from pysteps.nowcasts import parametric +from pysteps.nowcasts._cascade_advection import ( + advect_field, + prepare_advection_coordinates, +) +from pysteps.timeseries.autoregression import estimate_ar_params_yw + + +@pytest.fixture(scope="module") +def reference(): + folder = Path(__file__).parent / "data" + metadata = json.loads((folder / "parametric_nowcast_reference.json").read_text()) + with np.load(folder / "parametric_nowcast_reference.npz") as arrays: + yield metadata, {k: arrays[k].copy() for k in arrays.files} + + +def _call(reference, index=0, **kwargs): + metadata, arrays = reference + case = metadata["cases"][index] + defaults = dict( + n_ens_members=2, + kmperpixel=1, + timestep=case["config"]["timestep"] / 60, + seed=case["seed"], + ) + defaults.update(kwargs) + return parametric.forecast( + arrays[f"history_{index}"], arrays[f"velocity_{index}"], 3, **defaults + ) + + +@pytest.mark.parametrize("index", [0, 1]) +def test_forecast_source_preparation_and_coupled_output(reference, index): + metadata, arrays = reference + case = metadata["cases"][index] + result, diagnostics = _call(reference, index, return_diagnostics=True) + assert result.shape == (2, 3, 128, 96) + assert np.isfinite(result).all() and np.all(result >= 0) + assert np.all((result == 0) | (result >= 1)) + for name in ("beta_1", "beta_2", "mean_db", "stdev_db", "rain_fraction"): + assert diagnostics["parameters"][name] == pytest.approx( + case["parameters"][name], abs=1e-6 + ) + assert diagnostics["parameters"]["reference_time"] == pytest.approx( + case["parameters"]["corl_zero"], abs=1e-7 + ) + np.testing.assert_allclose( + diagnostics["model_correlations"][:, 0], case["parameters"]["lag_1"], atol=1e-12 + ) + np.testing.assert_allclose( + diagnostics["model_correlations"][:, 1], case["parameters"]["lag_2"], atol=1e-12 + ) + assert diagnostics["safeguards"]["moment_source"] == case["moment_source"] + assert diagnostics["safeguards"]["low_rain_fraction_fallback"] == bool(index) + # Equivalent spectral solvers differ in the last digits. Histogram mapping + # and its feedback can amplify that perturbation over multiple leads. + expected = arrays[f"forecast_{index}"] + assert np.max(abs(result - expected)) < 0.05 + assert np.sqrt(np.mean((result - expected) ** 2)) < 0.001 + assert np.count_nonzero((result > 0) != (expected > 0)) <= 2 + for member in range(2): + for lead in range(3): + actual = diagnostics["generation"][member][lead] + original = case["calibration"][member][lead] + for name in ("attempts", "selected_attempt", "tolerance_achieved"): + assert actual[name] == original[name] + + +@pytest.mark.parametrize("index", [0, 1]) +def test_source_evolution_with_identical_parameters(reference, index): + metadata, arrays = reference + case = metadata["cases"][index] + rain, velocity = arrays[f"history_{index}"], arrays[f"velocity_{index}"] + transformed = rain.copy() + wet = transformed >= 1 + transformed[wet] = 10 * np.log10(transformed[wet]) + transformed[~wet] = -0.1 + bp = filter_gaussian(rain.shape[1:], 6) + observed = [parametric._decompose(f, bp) for f in transformed] + coordinates = prepare_advection_coordinates(velocity) + taper = gaussian_filter(advect_field(np.ones(rain.shape[1:]), coordinates[1]), 5) + original = case["parameters"] + target = { + k: original[k] + for k in ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db") + } + phi = np.array( + [ + estimate_ar_params_yw(pair) + for pair in zip(original["lag_1"], original["lag_2"]) + ] + ) + actual = np.empty_like(arrays[f"forecast_{index}"]) + for member, child in enumerate(np.random.SeedSequence(case["seed"]).spawn(2)): + rng = np.random.default_rng(child) + histories = [observed[-1], observed[-2]] + for lead in range(3): + field, _ = parametric._advance( + histories, + target, + phi, + bp, + coordinates, + rng, + 0, + -0.1, + 1, + 20, + parametric._noise_settings(None), + 0.025, + ) + physical = 10 ** (field / 10) + physical[physical < 1] = 0 + actual[member, lead] = physical + histories = [parametric._decompose(field * taper, bp), histories[0]] + np.testing.assert_allclose( + actual, arrays[f"forecast_{index}"], atol=1e-8, rtol=1e-10 + ) + + +@pytest.mark.parametrize("kind", ["zero", "translation", "varying"]) +def test_cached_advection_exactly_matches_native(kind): + rng = np.random.default_rng(45) + shape = (47, 64) + field = rng.normal(size=shape) + velocity = np.zeros((2, *shape)) + if kind == "translation": + velocity[0], velocity[1] = 1.3, -0.4 + elif kind == "varying": + yy, xx = np.indices(shape) + velocity[0] = 0.1 + xx / 30 + velocity[1] = -0.2 + yy / 40 + coordinates = prepare_advection_coordinates(velocity) + for lag in (1, 2): + native = extrapolate(field, velocity, lag, outval=0)[lag - 1] + np.testing.assert_array_equal(advect_field(field, coordinates[lag]), native) + assert not coordinates[lag].flags.writeable + + +def test_streams_member_isolation_and_input_purity(reference): + metadata, arrays = reference + original = {k: v.copy() for k, v in arrays.items()} + seed = np.random.SeedSequence(72) + np.random.seed(23) + global_state = deepcopy(np.random.get_state()) + result = _call(reference, seed=seed) + again = _call(reference, seed=seed) + np.testing.assert_array_equal(result, again) + assert seed.n_children_spawned == 0 + current_state = np.random.get_state() + assert global_state[0] == current_state[0] + np.testing.assert_array_equal(global_state[1], current_state[1]) + assert global_state[2:] == current_state[2:] + prefix = _call(reference, n_ens_members=1, seed=seed) + np.testing.assert_array_equal(prefix[0], result[0]) + larger = _call(reference, n_ens_members=3, seed=seed) + np.testing.assert_array_equal(larger[:2], result) + assert not np.array_equal(result[0], result[1]) + assert not np.array_equal(result, _call(reference, seed=73)) + for k in arrays: + np.testing.assert_array_equal(arrays[k], original[k]) + + +def test_callback_streaming_matches_output_and_cannot_mutate_state(reference): + expected = _call(reference) + leads = [] + + def callback(fields): + leads.append(fields.copy()) + fields.fill(0) + + result = _call(reference, callback=callback) + np.testing.assert_array_equal(result, expected) + np.testing.assert_array_equal(np.stack(leads, axis=1), expected) + leads.clear() + result, diagnostics = _call( + reference, callback=callback, return_output=False, return_diagnostics=True + ) + assert result is None + np.testing.assert_array_equal(np.stack(leads, axis=1), expected) + assert len(diagnostics["generation"][0]) == 3 + + +def test_callback_failure_propagates(reference): + def callback(fields): + raise RuntimeError("writer failed") + + with pytest.raises(RuntimeError, match="writer failed"): + _call(reference, callback=callback) + + +def test_internal_motion_uses_latest_pair_and_reference_configuration( + reference, monkeypatch +): + _, arrays = reference + history = arrays["history_0"] + calls = [] + + def motion(images, **kwargs): + calls.append((images.copy(), kwargs)) + return arrays["velocity_0"].copy() + + monkeypatch.setattr(parametric.motion, "get_method", lambda name: motion) + overrides = dict(decl_scale=12) + output, diagnostics = parametric.forecast( + history, + timesteps=2, + n_ens_members=1, + kmperpixel=1, + timestep=5, + seed=4, + motion_kwargs=overrides, + return_diagnostics=True, + ) + assert output.shape == (1, 2, *history.shape[1:]) + assert len(calls) == 1 and calls[0][0].shape[0] == 2 + expected = history[-2:].copy() + wet = expected >= 1 + expected[wet] = 10 * np.log10(expected[wet]) + expected[~wet] = -0.1 + np.testing.assert_array_equal(calls[0][0], expected) + assert calls[0][1]["decl_scale"] == 12 + assert overrides == {"decl_scale": 12} + assert diagnostics["motion_source"] == "lucaskanade" + + +def test_bad_estimated_motion_rejected(reference, monkeypatch): + _, arrays = reference + monkeypatch.setattr( + parametric.motion, + "get_method", + lambda name: lambda *args, **kw: np.full((2, 128, 96), np.nan), + ) + with pytest.raises(ValueError, match="invalid velocity"): + parametric.forecast(arrays["history_0"], kmperpixel=1, timestep=5) + + +@pytest.mark.parametrize("prior_wet", [False, True]) +def test_dry_latest_history_skips_estimation_and_motion(prior_wet, monkeypatch): + rain = np.zeros((3, 64, 64)) + if prior_wet: + rain[0] = 10 + + def unexpected(*args, **kwargs): + raise AssertionError("Dry history should not require motion or noise") + + monkeypatch.setattr(parametric.motion, "get_method", unexpected) + monkeypatch.setattr(parametric, "generate_calibrated_field", unexpected) + leads = [] + output, diagnostics = parametric.forecast( + rain, + timesteps=2, + kmperpixel=1, + timestep=7.5, + n_ens_members=3, + callback=lambda f: leads.append(f.copy()), + return_diagnostics=True, + ) + assert output.shape == (3, 2, 64, 64) and not output.any() + assert len(leads) == 2 and not np.stack(leads).any() + assert diagnostics["status"] == "dry_latest_observation" + assert diagnostics["parameters"] is None + + +def test_partly_dry_history_requires_explicit_temporal_prior(reference): + _, arrays = reference + history = arrays["history_0"].copy() + history[:2] = 0 + kwargs = dict(kmperpixel=1, timestep=5, n_ens_members=1, seed=4) + with pytest.raises(ValueError, match="Unavailable spectral/persistence"): + parametric.forecast(history, arrays["velocity_0"], 2, **kwargs) + result, diagnostics = parametric.forecast( + history, + arrays["velocity_0"], + 2, + estimation_kwargs={"reference_time": 200}, + return_diagnostics=True, + **kwargs, + ) + assert np.isfinite(result).all() + assert diagnostics["parameters"]["reference_time"] == 200 + assert np.isnan(diagnostics["estimated_reference_time_minutes"]) + assert all( + len(levels) == 6 for levels in diagnostics["constant_observed_levels"][:2] + ) + + +def test_low_fraction_post_ar_suppression_is_explicit(reference): + _, arrays = reference + history = arrays["history_1"].copy() + wet_indices = np.flatnonzero(history[-1] >= 1) + history[-1].flat[wet_indices[200:]] = 0 + result, diagnostics = parametric.forecast( + history, + arrays["velocity_1"], + 2, + kmperpixel=1, + timestep=7.5, + n_ens_members=1, + seed=42, + return_diagnostics=True, + ) + assert not result.any() + assert 0 < diagnostics["statistics"]["rain_fraction"] < 0.025 + assert ( + diagnostics["status"] == "ok" + and diagnostics["safeguards"]["low_rain_fraction_fallback"] + ) + + +@pytest.mark.parametrize("kind", ["constant", "single_pixel"]) +def test_unestimable_wet_input_is_not_dry(kind): + rain = np.zeros((3, 64, 64)) + if kind == "constant": + rain[:] = 5 + else: + rain[-1, 32, 32] = 5 + with pytest.raises(ValueError, match="at least two distinct"): + parametric.forecast( + rain, np.zeros((2, 64, 64)), kmperpixel=1, timestep=5, n_ens_members=1 + ) + + +@pytest.mark.parametrize( + "kwargs", + [ + {"timesteps": [1, 2]}, + {"timesteps": 0}, + {"n_ens_members": True}, + {"n_ens_members": 0}, + {"timestep": None}, + {"timestep": 0}, + {"kmperpixel": None}, + {"kmperpixel": 0}, + {"rain_threshold": 0}, + {"scale_break": np.nan}, + {"zerovalue": 0}, + {"taper_sigma": -1}, + {"min_rain_fraction": 1.1}, + {"seed": -1}, + {"callback": 4}, + {"return_output": "yes"}, + {"n_cascade_levels": 2}, + {"estimation_kwargs": {"unknown": 1}}, + {"estimation_kwargs": {"alpha": 0}}, + {"estimation_kwargs": {"lower_truncation": -1}}, + {"estimation_kwargs": {"reference_levels": (6,)}}, + {"estimation_kwargs": {"reference_levels": (0, 0)}}, + {"estimation_kwargs": {"reference_time": 0}}, + {"noise_kwargs": {"seed": 1}}, + {"noise_kwargs": {"eps": 0}}, + {"noise_kwargs": {"beta_variances": (0, 1)}}, + {"motion_kwargs": 3}, + ], +) +def test_invalid_options_even_when_dry(kwargs): + options = dict(kmperpixel=1, timestep=5, n_ens_members=1) + options.update(kwargs) + with pytest.raises(ValueError): + parametric.forecast(np.zeros((3, 64, 64)), **options) + + +@pytest.mark.parametrize( + "kind", ["shape", "integer", "nan", "negative", "mask", "complex", "list"] +) +def test_invalid_observations(kind): + rain = np.zeros((3, 64, 64)) + if kind == "shape": + rain = rain[:2] + elif kind == "integer": + rain = rain.astype(int) + elif kind == "nan": + rain[0, 0, 0] = np.nan + elif kind == "negative": + rain[0, 0, 0] = -1 + elif kind == "mask": + rain = np.ma.array(rain, mask=False) + elif kind == "complex": + rain = rain.astype(complex) + elif kind == "list": + rain = rain.tolist() + with pytest.raises(ValueError): + parametric.forecast(rain, kmperpixel=1, timestep=5) + + +def test_method_registration(): + assert nowcasts.get_method("parametric") is parametric.forecast + + +def test_safeguards_moment_selection_and_joint_bounds(): + statistics = dict( + ordinary_mean=6, + ordinary_stdev=3, + latent_mean=20, + latent_stdev=3, + rain_fraction=0.3, + ) + spectrum = dict(beta_1=-1, beta_2=-1) + settings = parametric._estimation_settings(None, 0, 6) + parameters, audit = parametric._generation_parameters( + statistics, spectrum, 20, settings + ) + assert audit["moment_source"] == "ordinary" and audit["moment_reason"] == "distance" + assert parameters["reference_time"] == 60 + assert parameters["beta_1"] < -1.25 + assert parameters["beta_2"] <= parameters["beta_1"] - 0.1 + assert spectrum == dict(beta_1=-1, beta_2=-1) + statistics["latent_mean"] = np.nan + _, audit = parametric._generation_parameters(statistics, spectrum, 200, settings) + assert audit["moment_reason"] == "invalid_latent" diff --git a/pysteps/tests/test_parametric_estimators.py b/pysteps/tests/test_parametric_estimators.py new file mode 100644 index 00000000..f0c886c7 --- /dev/null +++ b/pysteps/tests/test_parametric_estimators.py @@ -0,0 +1,321 @@ +"""Independent numerical checks and frozen source-implementation comparisons.""" + +import json +from pathlib import Path + +import numpy as np +import pytest +from numpy.testing import assert_allclose, assert_array_equal +from scipy.stats import truncnorm + +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.timeseries.parametric import ( + ar2_correlation_time, + estimate_reference_correlation_time, + fit_ar2_correlations, + lagrangian_autocorrelation, + model_cascade_correlations, +) +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic +from pysteps.utils.statistics import ( + compute_field_statistics, + fit_lower_truncated_normal, +) + + +def _directional_field(shape): + y, x = np.indices(shape) + return np.random.default_rng(23).normal(size=shape) + 4 * np.cos( + 2 * np.pi * (5 * y / shape[0] + 3 * x / shape[1]) + ) + + +def _marginal_field(): + rng = np.random.default_rng(37) + field = rng.normal(4, 3, size=(128, 96)) + field[rng.uniform(size=field.shape) > 0.3] = -0.1 + return field + + +def _lag_history(): + rng = np.random.default_rng(45) + history = rng.normal(size=(3, 2, 24, 32)) + velocity = np.full((2, 24, 32), 0.25) + velocity[0] = 0.6 + return history, velocity + + +@pytest.fixture(scope="module") +def reference(): + return json.loads( + ( + Path(__file__).parent / "data/parametric_estimators_reference.json" + ).read_text() + ) + + +def test_source_marginal_parity(reference): + field = _marginal_field() + original = field.copy() + estimated = compute_field_statistics(field, 0, lower_truncation=1.25) + for name, expected in reference["marginal"].items(): + assert estimated[name] == pytest.approx(expected, rel=1e-12, abs=1e-12) + assert_array_equal(field, original) + + +def test_source_spectrum_parity(reference): + for case in reference["spectra"]: + frequency, power = rapsd_logarithmic(_directional_field(tuple(case["shape"]))) + assert_allclose(10 * np.log10(frequency), case["frequency_db"], atol=1e-12) + assert_allclose(10 * np.log10(power), case["power_db"], atol=1e-10) + for expected in case["models"]: + estimated = fit_power_law_spectrum( + frequency, power, expected["scale_break"] + ) + assert estimated["status"] == "ok" + for name in ("beta_1", "beta_2", "c1", "c2"): + if expected[name] is None: + assert estimated[name] is None + else: + assert estimated[name] == pytest.approx(expected[name], abs=3e-5) + + +def test_source_temporal_and_paper_reference_parity(reference): + history, velocity = _lag_history() + assert_allclose( + lagrangian_autocorrelation(history, velocity), reference["lags"], atol=1e-14 + ) + for case in reference["ar2_inverse"]: + pair = fit_ar2_correlations(case["time"], case["alpha"], 5) + assert_allclose(pair, case["pair"], atol=1e-12) + paper = reference["paper_reference"] + assert_allclose( + estimate_reference_correlation_time( + paper["times"], paper["wavelengths"], paper["exponent"] + ), + paper["reference_times"], + rtol=1e-14, + ) + + +@pytest.mark.parametrize("shape", [(128, 128), (127, 127), (96, 128), (95, 127)]) +def test_radial_mean_matches_independent_full_fft(shape): + field = _directional_field(shape) + original = field.copy() + normalized = (field - field.mean()) / field.std() + full_power = abs(np.fft.fft2(normalized)) ** 2 + y, x = np.meshgrid( + np.fft.fftfreq(shape[0]), np.fft.fftfreq(shape[1]), indexing="ij" + ) + radius = np.hypot(y, x) + n = min(shape) // 2 + limit = np.hypot( + abs(np.fft.fftfreq(shape[0])[: shape[0] // 2]).max(), + abs(np.fft.fftfreq(shape[1])[: shape[1] // 2]).max(), + ) + # Preserve the historical endpoint rounding: exactly-boundary Fourier modes + # can move bins if geomspace is substituted for the source logspace edges. + edges = np.logspace(np.log10(1 / n), np.log10(limit), n) + expected_frequency, expected_power = [], [] + for lo, hi in zip(edges[:-1], edges[1:]): + mask = (radius >= lo) & (radius < hi) + if mask.any(): + expected_frequency.append((lo + hi) / 2) + expected_power.append(full_power[mask].mean()) + frequency, power = rapsd_logarithmic(field) + assert_allclose(frequency, expected_frequency, atol=1e-14) + assert_allclose(power, expected_power, rtol=1e-12) + for changed in (field.T, field[:, ::-1], np.rot90(field)): + actual_frequency, actual_power = rapsd_logarithmic(changed) + assert_allclose(actual_frequency, frequency, atol=1e-14) + assert_allclose(actual_power, power, rtol=1e-12) + assert_array_equal(field, original) + + +def test_exact_continuous_power_law_recovery(): + frequency = np.geomspace(0.005, 0.5, 120) + breakpoint = 1 / 20 + power = np.where( + frequency <= breakpoint, frequency**-2, breakpoint**1.5 * frequency**-3.5 + ) + result = fit_power_law_spectrum(frequency, power, scale_break=20) + assert result["status"] == "ok" + assert_allclose([result["beta_1"], result["beta_2"]], [-2, -3.5], atol=1e-7) + assert result["rmse_db"] < 1e-7 + assert result["c1"] + result["beta_1"] * 10 * np.log10(breakpoint) == pytest.approx( + result["c2"] + result["beta_2"] * 10 * np.log10(breakpoint) + ) + + +def test_unavailable_spectral_fits_are_explicit(): + frequency, power = rapsd_logarithmic(np.full((32, 32), -0.1)) + assert frequency.size == power.size == 0 + assert ( + fit_power_law_spectrum(frequency, power, 20)["status"] == "insufficient_samples" + ) + frequency = np.geomspace(0.02, 0.2, 30) + assert ( + fit_power_law_spectrum(frequency, np.ones(30), 1000)["status"] + == "unidentified_model" + ) + assert ( + fit_power_law_spectrum(frequency, np.zeros(30))["status"] + == "insufficient_samples" + ) + + +def test_latent_normal_recovery_and_occurrence_cutoff(): + mean, scale, cutoff = 2.0, 3.0, 1.25 + probabilities = (np.arange(20000) + 0.5) / 20000 + sample = truncnorm.ppf( + probabilities, (cutoff - mean) / scale, np.inf, loc=mean, scale=scale + ) + fitted_mean, fitted_scale, diagnostics = fit_lower_truncated_normal( + sample, cutoff, True + ) + assert_allclose([fitted_mean, fitted_scale], [mean, scale], atol=0.003) + assert diagnostics["status"] == "ok" + field = np.array([[-0.1, 0, 0.5, 2], [1, 3, 4, 5]]) + a = compute_field_statistics(field, 0, lower_truncation=1.25) + b = compute_field_statistics(field, 0, lower_truncation=2) + assert a["rain_fraction"] == b["rain_fraction"] == 7 / 8 + assert a["ordinary_mean"] == b["ordinary_mean"] + assert a["nonzero_fit"]["n_samples"] == 4 + + +def test_dry_constant_and_bounded_marginal_fits(): + dry = compute_field_statistics(np.full((4, 4), -0.1), 0, lower_truncation=1.25) + assert dry["rain_fraction"] == 0 + assert np.isnan(dry["ordinary_mean"]) and np.isnan(dry["latent_mean"]) + assert dry["nonzero_fit"]["status"] == "insufficient_samples" + assert fit_lower_truncated_normal([2, 2], 1, True)[2]["status"] == "constant_sample" + _, _, bounded = fit_lower_truncated_normal([0.01, 0.01, 0.01, 100], 0, True) + assert bounded["status"] == "bounded_solution" and bounded["boundary_limited"] + + +def _summed_acf(rho1, rho2, timestep): + phi1, phi2 = np.linalg.solve([[1, rho1], [rho1, 1]], [rho1, rho2]) + previous, current, total = 1.0, rho1, 1.0 + rho1 + for _ in range(100000): + previous, current = current, phi1 * current + phi2 * previous + total += current + if abs(previous) + abs(current) < 1e-13: + return timestep * total + raise AssertionError("ACF recurrence did not converge") + + +@pytest.mark.parametrize( + "phi1,phi2", [(1.2, -0.3), (0.4, 0.3), (-0.6, -0.3), (0.9, 0), (0, 0)] +) +def test_forward_acf_sum(phi1, phi2): + rho1 = phi1 / (1 - phi2) + rho2 = phi1 * rho1 + phi2 + assert ar2_correlation_time(rho1, rho2, 5) == pytest.approx( + _summed_acf(rho1, rho2, 5), rel=1e-10 + ) + assert ar2_correlation_time(rho1, rho2, 300) == pytest.approx( + 60 * ar2_correlation_time(rho1, rho2, 5) + ) + + +@pytest.mark.parametrize("alpha", [0.8, 1.25, 2, 2.72, 4]) +@pytest.mark.parametrize("target", [5, 5.00001, 10, 60, 420, 720]) +def test_inverse_matches_independent_acf_sum(alpha, target): + pair, diagnostics = fit_ar2_correlations(target, alpha, 5, True) + assert _summed_acf(*pair, 5) == pytest.approx(target, rel=1e-7) + assert diagnostics["achieved_time"] == pytest.approx(target, rel=1e-7) + + +def test_reference_selection_and_nominal_scale_model(): + wavelength = 1 / filter_gaussian((256, 256), 6, d=1)["central_freqs"] + exponent = 0.7718612877449645 + observed = 120 * (wavelength / wavelength[0]) ** exponent + assert estimate_reference_correlation_time( + observed, wavelength, exponent + ) == pytest.approx(120) + altered = observed.copy() + altered[-1] = np.nan + assert estimate_reference_correlation_time( + altered, wavelength, exponent + ) == pytest.approx(120) + altered[1] = np.nan + assert np.isnan(estimate_reference_correlation_time(altered, wavelength, exponent)) + assert estimate_reference_correlation_time( + altered, wavelength, exponent, levels=(0,) + ) == pytest.approx(120) + pairs, diagnostics = model_cascade_correlations( + 60, wavelength, exponent, 2.72, 5, return_diagnostics=True + ) + assert_allclose( + diagnostics["achieved_times"], + np.maximum(diagnostics["requested_times"], 5), + rtol=1e-7, + ) + assert_array_equal(pairs[diagnostics["limited_below_timestep"]], 0) + # Changing the reference convention must not change the predicted level times. + rescaled = estimate_reference_correlation_time( + observed, wavelength, exponent, reference_wavelength=128 + ) + alternate = model_cascade_correlations( + rescaled, wavelength, exponent, 2.72, 5, reference_wavelength=128 + ) + original = model_cascade_correlations(120, wavelength, exponent, 2.72, 5) + assert_allclose(alternate, original, atol=1e-12) + flat = model_cascade_correlations(120, wavelength, 0, 2.72, 5) + assert_allclose(ar2_correlation_time(flat[:, 0], flat[:, 1], 5), 120) + + +def test_lagrangian_boundary_conventions_and_input_preservation(): + base = np.random.default_rng(7).normal(size=(32, 40)) + history = np.stack([base, np.roll(base, 1, axis=1), np.roll(base, 2, axis=1)])[ + :, None + ] + velocity = np.zeros((2, 32, 40)) + velocity[0] = 1 + original = history.copy() + assert_allclose( + lagrangian_autocorrelation(history, velocity, boundary="mask"), + [[1, 1]], + atol=1e-14, + ) + zero = lagrangian_autocorrelation(history, velocity) + assert (zero < 1).all() + assert_array_equal(history, original) + assert np.isnan(lagrangian_autocorrelation(np.ones_like(history), velocity)).all() + assert_allclose( + lagrangian_autocorrelation(history, np.zeros_like(velocity)), + [ + [ + np.corrcoef(history[-1, 0].ravel(), history[-2, 0].ravel())[0, 1], + np.corrcoef(history[-1, 0].ravel(), history[-3, 0].ravel())[0, 1], + ] + ], + ) + + +@pytest.mark.parametrize( + "operation", + [ + lambda: compute_field_statistics([[np.nan]], 0), + lambda: compute_field_statistics([[1j]], 0), + lambda: compute_field_statistics([[1]], 1, lower_truncation=0), + lambda: fit_lower_truncated_normal([1, 2], np.nan), + lambda: rapsd_logarithmic(np.zeros((2, 2))), + lambda: rapsd_logarithmic(np.full((8, 8), np.nan)), + lambda: rapsd_logarithmic(np.full((8, 8), 1j)), + lambda: fit_power_law_spectrum([0.2, 0.1], [1, 2]), + lambda: fit_ar2_correlations(4, 2.72, 5), + lambda: fit_ar2_correlations(1e20, 2.72, 5), + lambda: estimate_reference_correlation_time([1, 2], [10, 5], 0.8), + lambda: model_cascade_correlations(60, [10, 5], 0.8, 0, 5), + lambda: lagrangian_autocorrelation(np.zeros((2, 1, 8, 8)), np.zeros((2, 8, 8))), + ], +) +def test_invalid_inputs_raise(operation): + with pytest.raises(ValueError): + operation() + + +def test_invalid_ar_pairs_are_not_repaired(): + assert np.isnan(ar2_correlation_time([np.nan, 1, 0.5], [0, 0.5, 1.5], 5)).all() + assert np.isnan(ar2_correlation_time(0.5, 0.2, 0)) diff --git a/pysteps/timeseries/parametric.py b/pysteps/timeseries/parametric.py new file mode 100644 index 00000000..1afa0b3d --- /dev/null +++ b/pysteps/timeseries/parametric.py @@ -0,0 +1,363 @@ +""" +pysteps.timeseries.parametric +============================= + +Correlation estimators and temporal scaling for parametric nowcasting. + +.. autosummary:: + :toctree: ../generated/ + + lagrangian_autocorrelation + ar2_correlation_time + fit_ar2_correlations + estimate_reference_correlation_time + model_cascade_correlations +""" + +import numpy as np +from scipy.optimize import brentq + +from pysteps.extrapolation.semilagrangian import extrapolate + +CORRELATION_TIME_DEFINITION = "ar_acf_sum_including_zero_v2" + + +def _finite(name, value): + if not np.isscalar(value) or not np.isfinite(value): + raise ValueError(f"{name} must be finite") + + +def _positive(name, value): + _finite(name, value) + if value <= 0: + raise ValueError(f"{name} must be positive and finite") + + +def _wavelengths(values): + values = np.asarray(values, dtype=float) + if ( + values.ndim != 1 + or values.size == 0 + or not np.isfinite(values).all() + or np.any(values <= 0) + ): + raise ValueError("wavelengths must be a nonempty positive finite 1D array") + return values + + +def ar2_correlation_time(lag1, lag2, timestep, tol=1e-5): + """Calculate the one-sided stationary AR(2) discrete ACF sum including zero. + + Parameters + ---------- + lag1, lag2: float or array_like + Observed lag-1 and lag-2 correlations. Arrays are broadcast together. + timestep: float + Sampling interval. The returned times have the same unit. + tol: float, optional + Guard for singular or near-nonstationary pairs (default 1e-5). + + Returns + ------- + time: float or ndarray + timestep * sum(rho[k], k=0..infinity). Invalid, nonstationary, and + numerically singular pairs return NaN, without AR(1) substitution. + + Notes + ----- + The sum is timestep*(1+rho1-phi1)/(1-phi1-phi2), with Yule-Walker AR + coefficients. White noise has time equal to one timestep. This differs + from a trapezoidal ACF integral by timestep/2 and is not a skill horizon. + """ + _positive("tol", tol) + if tol >= 1: + raise ValueError("tol must be less than one") + lag1, lag2 = np.broadcast_arrays(np.asarray(lag1, float), np.asarray(lag2, float)) + result = np.full(lag1.shape, np.nan) + if not np.isscalar(timestep) or not np.isfinite(timestep) or timestep <= 0: + return float(result) if result.ndim == 0 else result + with np.errstate(divide="ignore", invalid="ignore"): + denominator = 1 - lag1**2 + phi1 = lag1 * (1 - lag2) / denominator + phi2 = (lag2 - lag1**2) / denominator + total_denominator = 1 - phi1 - phi2 + valid = ( + np.isfinite(lag1) + & np.isfinite(lag2) + & (np.abs(lag1) < 1 - tol) + & (np.abs(lag2) < 1 - tol) + & (np.abs(denominator) >= tol) + & (np.abs(phi2) < 1 - tol) + & (phi1 + phi2 < 1 - tol) + & (phi2 - phi1 < 1 - tol) + & (total_denominator > tol) + ) + np.copyto(result, timestep * (1 + lag1 - phi1) / total_denominator, where=valid) + return float(result) if result.ndim == 0 else result + + +def _adjust_nonperiodic_lag2(lag1, lag2): + # Rationalized equivalent of adjust_lag2_corrcoef2, stable also at lag1=0. + root = np.sqrt(1 - lag1**2) + lower = lag1**2 * (1 + 2 * root) / (1 + root) ** 2 + return float(max(lag2, 2 * lag1 * lag2 - 1, lower)) + + +def fit_ar2_correlations(correlation_time, alpha, timestep, return_diagnostics=False): + """Invert a discrete timescale in the nonnegative, nonperiodic AR(2) family. + + Parameters + ---------- + correlation_time, timestep: float + Positive target time and sampling interval in the same unit. A target + below timestep is unrepresentable and raises ValueError. + alpha: float + Positive exponent in rho2 = rho1**alpha before nonperiodic adjustment. + return_diagnostics: bool, optional + Also return requested and achieved times and their absolute difference. + + Returns + ------- + correlations: ndarray + The adjusted [rho1, rho2] used by the inversion. Exactly one timestep + returns [0, 0]. The exponent relation can change at the adjustment bound. + diagnostics: dict, optional + Definition, requested_time, achieved_time, and absolute_error. + """ + _positive("correlation_time", correlation_time) + _positive("alpha", alpha) + _positive("timestep", timestep) + if correlation_time < timestep: + raise ValueError("correlation_time is below the discrete minimum timestep") + + def pair(rho1): + return _adjust_nonperiodic_lag2(rho1, rho1**alpha) + + def residual(rho1): + return ar2_correlation_time(rho1, pair(rho1), timestep) - correlation_time + + rho1 = rho2 = 0.0 + if correlation_time > timestep: + lower, upper, candidate = 0.0, 1.0, 0.5 + bracket = None + for _ in range(80): + value = residual(candidate) + if np.isfinite(value): + if value >= 0: + bracket = (0.0, candidate) + break + lower = candidate + else: + upper = candidate + candidate = (lower + upper) / 2 + if candidate == lower or candidate == upper: + break + if bracket is None: + raise ValueError( + "correlation_time exceeds the numerically stable AR(2) range" + ) + rho1 = float(brentq(residual, *bracket, xtol=1e-14, rtol=1e-14)) + rho2 = pair(rho1) + achieved = ar2_correlation_time(rho1, rho2, timestep) + if not np.isfinite(achieved) or not np.isclose( + achieved, correlation_time, rtol=1e-7, atol=1e-8 + ): + raise ValueError("AR(2) inverse failed to achieve the requested timescale") + correlations = np.array([rho1, rho2]) + diagnostics = dict( + definition=CORRELATION_TIME_DEFINITION, + requested_time=float(correlation_time), + achieved_time=achieved, + absolute_error=float(abs(achieved - correlation_time)), + ) + return (correlations, diagnostics) if return_diagnostics else correlations + + +def estimate_reference_correlation_time( + times, wavelengths, exponent, levels=(0, 1, 2), reference_wavelength=None +): + """Estimate a reference time by geometric averaging selected scale-adjusted times. + + Parameters + ---------- + times: array_like + Observed correlation times with shape (..., level), in one time unit. + wavelengths: array_like + Positive nominal wavelengths, one per level, from the actual cascade + filter (reciprocals of its central frequencies in consistent units). + exponent: float + Fixed finite temporal scaling exponent, not estimated here. + levels: sequence of int, optional + Zero-based contributing levels; defaults to (0, 1, 2), the selected + paper method. (0,) reproduces the production largest-level convention. + reference_wavelength: float, optional + Reference length, default wavelengths[0]. + + Returns + ------- + time: float or ndarray + exp(mean(log(T_j)-exponent*log(wavelength_j/reference_wavelength))). + Every selected time must be positive and finite; otherwise NaN is + returned for that sample. No level dropping or implicit fallback occurs. + """ + wavelengths = _wavelengths(wavelengths) + times = np.asarray(times, dtype=float) + if times.ndim < 1 or times.shape[-1] != wavelengths.size: + raise ValueError("times' last dimension must match wavelengths") + _finite("exponent", exponent) + levels = np.asarray(levels) + if ( + levels.ndim != 1 + or levels.size == 0 + or not np.issubdtype(levels.dtype, np.integer) + or np.any(levels < 0) + or np.any(levels >= wavelengths.size) + or np.unique(levels).size != levels.size + ): + raise ValueError("levels must contain distinct valid integer indices") + reference = wavelengths[0] if reference_wavelength is None else reference_wavelength + _positive("reference_wavelength", reference) + selected = times[..., levels] + valid = (np.isfinite(selected) & (selected > 0)).all(axis=-1) + result = np.full(times.shape[:-1], np.nan) + with np.errstate(divide="ignore", invalid="ignore", over="ignore"): + values = np.exp( + np.mean( + np.log(selected) - exponent * np.log(wavelengths[levels] / reference), + axis=-1, + ) + ) + np.copyto(result, values, where=valid & np.isfinite(values)) + return float(result) if result.ndim == 0 else result + + +def model_cascade_correlations( + reference_time, + wavelengths, + exponent, + alpha, + timestep, + reference_wavelength=None, + return_diagnostics=False, +): + """Construct AR(2) lag pairs from a reference time and nominal wavelengths. + + Parameters + ---------- + reference_time, timestep: float + Positive reference time and sampling interval, in the same unit. + wavelengths: array_like + Positive nominal wavelengths, from the actual cascade filter. + exponent, alpha: float + Finite scale-time exponent and positive pre-adjustment lag-pair exponent. + reference_wavelength: float, optional + Reference length, default wavelengths[0]. + return_diagnostics: bool, optional + Also return requested and achieved times, and sub-timestep limit flags. + + Returns + ------- + correlations: ndarray + Shape (level, 2), containing adjusted lag-1/lag-2 pairs. Requested times + at or below timestep use white noise [0, 0], achieving timestep. + diagnostics: dict, optional + The chosen ACF definition and per-level requested/achieved times. Length + and time units are caller supplied; no implicit conversion is performed. + """ + wavelengths = _wavelengths(wavelengths) + for name, value in [ + ("reference_time", reference_time), + ("alpha", alpha), + ("timestep", timestep), + ]: + _positive(name, value) + _finite("exponent", exponent) + reference = wavelengths[0] if reference_wavelength is None else reference_wavelength + _positive("reference_wavelength", reference) + requested = reference_time * (wavelengths / reference) ** exponent + if not np.isfinite(requested).all() or np.any(requested <= 0): + raise ValueError("scale-adjusted times must be positive and finite") + correlations = np.array( + [ + fit_ar2_correlations(max(time, timestep), alpha, timestep) + for time in requested + ] + ) + diagnostics = dict( + definition=CORRELATION_TIME_DEFINITION, + timestep=float(timestep), + requested_times=requested, + achieved_times=ar2_correlation_time( + correlations[:, 0], correlations[:, 1], timestep + ), + limited_below_timestep=requested < timestep, + ) + return (correlations, diagnostics) if return_diagnostics else correlations + + +def lagrangian_autocorrelation( + cascades, velocity, ar_order=2, min_stdev=0.1, boundary="zero" +): + """Estimate cascade correlations by successive unit-step backward advection. + + Parameters + ---------- + cascades: array_like + Finite history with shape (time, level, y, x), oldest first, containing + at least ar_order+1 times. Only the latest required history is used. + velocity: array_like + Finite fixed flow with shape (2, y, x), in pixels per timestep. + ar_order: {1, 2}, optional + Number of observed lags to estimate (default 2). + min_stdev: float, optional + Both aligned fields must exceed this population standard deviation. + boundary: {'zero', 'mask'}, optional + 'zero' preserves the evaluated production estimator: fill out-of-domain + pixels with zero after each unit step and correlate the whole grid. + 'mask' preserves NaNs through advection and uses common finite support + across all lags. It changes the estimator and is an explicit alternative. + + Returns + ------- + correlations: ndarray + Shape (level, ar_order), in ascending lag order. Insufficient support or + variance produces NaN. There is no clipping or stationarity repair. + """ + cascades, velocity = np.asarray(cascades, float), np.asarray(velocity, float) + if not isinstance(ar_order, (int, np.integer)) or ar_order not in (1, 2): + raise ValueError("ar_order must be 1 or 2") + if ( + cascades.ndim != 4 + or cascades.shape[0] < ar_order + 1 + or any(size == 0 for size in cascades.shape) + ): + raise ValueError( + "cascades must have shape (time, level, y, x) with sufficient history" + ) + if velocity.shape != (2, *cascades.shape[-2:]): + raise ValueError("velocity must have shape (2, y, x) matching cascades") + if not np.isfinite(cascades).all() or not np.isfinite(velocity).all(): + raise ValueError("cascades and velocity must contain only finite values") + if boundary not in ("zero", "mask"): + raise ValueError("boundary must be 'zero' or 'mask'") + if not np.isfinite(min_stdev) or min_stdev < 0: + raise ValueError("min_stdev must be finite and nonnegative") + result = np.full((cascades.shape[1], ar_order), np.nan) + for level in range(cascades.shape[1]): + aligned = [cascades[-1, level]] + for lag in range(1, ar_order + 1): + field = cascades[-(lag + 1), level] + for _ in range(lag): + field = extrapolate(field, velocity, 1, allow_nonfinite_values=True)[0] + if boundary == "zero": + field = np.where(np.isfinite(field), field, 0) + aligned.append(field) + support = np.isfinite(np.stack(aligned)).all(axis=0) + if np.count_nonzero(support) < 2: + continue + latest = aligned[0][support] + for lag in range(1, ar_order + 1): + previous = aligned[lag][support] + if np.std(latest) > min_stdev and np.std(previous) > min_stdev: + result[level, lag - 1] = np.corrcoef(latest, previous)[0, 1] + return result diff --git a/pysteps/utils/__init__.py b/pysteps/utils/__init__.py index a1a37c39..b5543a6f 100644 --- a/pysteps/utils/__init__.py +++ b/pysteps/utils/__init__.py @@ -11,5 +11,6 @@ from .pca import * from .reprojection import * from .spectral import * +from .statistics import * from .tapering import * from .transformation import * diff --git a/pysteps/utils/spectral.py b/pysteps/utils/spectral.py index a304b4ab..d92c3c8b 100644 --- a/pysteps/utils/spectral.py +++ b/pysteps/utils/spectral.py @@ -11,6 +11,8 @@ corrcoef mean rapsd + rapsd_logarithmic + fit_power_law_spectrum remove_rain_norain_discontinuity std """ @@ -19,6 +21,178 @@ from . import arrays +def rapsd_logarithmic(field, min_stdev=0.1): + """Compute a standardized field's radial spectrum in logarithmic bins. + + Parameters + ---------- + field: array_like + Finite, real two-dimensional field in (y, x) order. Dimensions must + each contain at least four pixels. The input is not modified. + min_stdev: float, optional + Minimum population standard deviation required for estimation. + + Returns + ------- + frequency, power: ndarray + Bin-centre frequencies in cycles per pixel and mean power per Fourier + mode, both in linear units. Empty arrays indicate insufficient variance + or bin support. + + Notes + ----- + Preserves the ``pysteps_param.power_spectrum_1D`` binning convention: + min(shape)//2 logarithmic edges from 1/(min(shape)//2) to the historical + diagonal frequency limit. This differs from :func:`rapsd` and deliberately + omits the lowest frequencies. Empty bins are omitted. Power is the squared + unscaled FFT of a zero-mean, unit-variance field, not annulus-integrated + energy. Real-FFT multiplicities reproduce the full Fourier-plane mean. + """ + if np.iscomplexobj(field): + raise ValueError("field must contain real values") + field = np.asarray(field, dtype=float) + if field.ndim != 2 or min(field.shape) < 4: + raise ValueError( + "field must be two-dimensional with at least 4 pixels per axis" + ) + if not np.isfinite(field).all(): + raise ValueError("field must contain only finite values") + if not np.isfinite(min_stdev) or min_stdev < 0: + raise ValueError("min_stdev must be finite and nonnegative") + stdev = np.std(field) + if stdev == 0 or stdev < min_stdev: + return np.empty(0), np.empty(0) + normalized = (field - np.mean(field)) / stdev + power = np.abs(np.fft.rfft2(normalized)) ** 2 + rows, cols = field.shape + fx, fy = np.fft.rfftfreq(cols), np.fft.fftfreq(rows) + radius = np.hypot(fy[:, None], fx[None, :]) + weights = np.full(power.shape, 2.0) + weights[:, 0] = 1.0 + if cols % 2 == 0: + weights[:, -1] = 1.0 + n_bins = min(rows, cols) // 2 + maximum = np.hypot( + np.max(np.abs(np.fft.fftfreq(rows)[: rows // 2])), + np.max(np.abs(np.fft.fftfreq(cols)[: cols // 2])), + ) + edges = np.logspace(np.log10(1 / n_bins), np.log10(maximum), n_bins) + frequency, radial_power = [], [] + for lower, upper in zip(edges[:-1], edges[1:]): + selected = (radius >= lower) & (radius < upper) + if np.any(selected): + frequency.append(0.5 * (lower + upper)) + radial_power.append(np.average(power[selected], weights=weights[selected])) + return np.asarray(frequency), np.asarray(radial_power) + + +def fit_power_law_spectrum( + frequency, power, scale_break=None, skip_bins=2, max_frequency=10 ** (-0.4) +): + """Fit signed spectral slopes, optionally at a prescribed wavelength break. + + Parameters + ---------- + frequency, power: array_like + Equally sized one-dimensional arrays in linear units, with frequencies + positive and strictly increasing. Power must be finite and nonnegative. + scale_break: float, optional + Fixed break wavelength in pixels when frequencies are cycles per pixel. + For physical frequencies, use the corresponding length unit. None fits + one slope. The fit is continuous at frequency 1/scale_break. + skip_bins: int, optional + Number of initial bins excluded from regression (default 2). + max_frequency: float, optional + Exclusive upper fit limit. Defaults preserve the source estimator's + interval of 10*log10(frequency) < -4 for pixel frequencies. + + Returns + ------- + result: dict + ``beta_1``, ``beta_2``, ``c1``, ``c2``, and ``scale_break`` describe + 10*log10(power) = beta*10*log10(frequency) + c. Slopes are signed. + ``status``, ``n_samples``, and ``rmse_db`` describe the fit. Unavailable + coefficients are NaN; beta_2 and c2 are None for a single-slope model. + + Notes + ----- + Zero-power bins cannot enter log-space regression. Insufficient samples, + an unidentified break, or failed optimization are reported without inventing + parameter estimates. The model is linear in its coefficients and is solved + by unweighted least squares. No climatological clipping or calibration is + applied. + """ + frequency, power = np.asarray(frequency, float), np.asarray(power, float) + if frequency.ndim != 1 or power.shape != frequency.shape: + raise ValueError("frequency and power must be equally sized 1D arrays") + if ( + not np.isfinite(frequency).all() + or np.any(frequency <= 0) + or np.any(np.diff(frequency) <= 0) + or not np.isfinite(power).all() + or np.any(power < 0) + ): + raise ValueError( + "frequencies must increase and be positive; power must be nonnegative" + ) + if not isinstance(skip_bins, (int, np.integer)) or skip_bins < 0: + raise ValueError("skip_bins must be a nonnegative integer") + if not np.isfinite(max_frequency) or max_frequency <= 0: + raise ValueError("max_frequency must be positive and finite") + if scale_break is not None and (not np.isfinite(scale_break) or scale_break <= 0): + raise ValueError("scale_break must be positive and finite") + selected = (np.arange(frequency.size) >= skip_bins) & (frequency < max_frequency) + selected &= power > 0 + x, y = 10 * np.log10(frequency[selected]), 10 * np.log10(power[selected]) + result = dict( + beta_1=np.nan, + beta_2=np.nan if scale_break is not None else None, + c1=np.nan, + c2=np.nan if scale_break is not None else None, + scale_break=scale_break, + status="insufficient_samples", + n_samples=int(x.size), + rmse_db=np.nan, + ) + if scale_break is None: + + def model(x, beta, intercept): + return beta * x + intercept + + design = np.column_stack([x, np.ones(x.size)]) + else: + breakpoint = 10 * np.log10(1 / scale_break) + + def model(x, beta_1, beta_2, c1): + c2 = (beta_1 - beta_2) * breakpoint + c1 + return np.where(x <= breakpoint, beta_1 * x + c1, beta_2 * x + c2) + + design = np.column_stack( + [np.minimum(x, breakpoint), np.maximum(x - breakpoint, 0), np.ones(x.size)] + ) + if x.size < design.shape[1]: + return result + if np.linalg.matrix_rank(design) < design.shape[1]: + result["status"] = "unidentified_model" + return result + try: + fitted, _, _, _ = np.linalg.lstsq(design, y, rcond=None) + except np.linalg.LinAlgError as error: + result.update(status="linear_solver_failed", message=str(error)) + return result + if not np.isfinite(fitted).all(): + result["status"] = "linear_solver_failed" + return result + result.update(beta_1=float(fitted[0]), c1=float(fitted[-1]), status="ok") + if scale_break is not None: + result.update( + beta_2=float(fitted[1]), + c2=float((fitted[0] - fitted[1]) * breakpoint + fitted[2]), + ) + result["rmse_db"] = float(np.sqrt(np.mean((model(x, *fitted) - y) ** 2))) + return result + + def corrcoef(X, Y, shape, use_full_fft=False): """ Compute the correlation coefficient between two-dimensional arrays in diff --git a/pysteps/utils/statistics.py b/pysteps/utils/statistics.py new file mode 100644 index 00000000..3475be95 --- /dev/null +++ b/pysteps/utils/statistics.py @@ -0,0 +1,174 @@ +""" +pysteps.utils.statistics +======================== + +Marginal parameter estimators for transformed precipitation. + +.. autosummary:: + :toctree: ../generated/ + + fit_lower_truncated_normal + compute_field_statistics +""" + +import numpy as np +from scipy.optimize import minimize_scalar +from scipy.special import log_ndtr + +__all__ = ["fit_lower_truncated_normal", "compute_field_statistics"] + + +def fit_lower_truncated_normal(data, lower_truncation, return_diagnostics=False): + """Fit the latent parent Normal to samples above a lower cutoff. + + Parameters + ---------- + data: array_like + Observations in transformed units. Nonfinite and below-cutoff values + are excluded. The input is not modified. + lower_truncation: float + Finite lower fitting cutoff in the same units as data. + return_diagnostics: bool, optional + Also return a dictionary describing sample count and fitting status. + + Returns + ------- + location, scale: float + Parent-Normal location and scale, distinct from retained-sample moments. + Both are NaN for insufficient or constant samples or failed optimization. + diagnostics: dict, optional + Sample count, status, standardized cutoff, and boundary indicators. + + Notes + ----- + Profiles scale analytically for a = (cutoff-location)/scale and minimizes + the conditional negative log likelihood over -12 <= a <= 12, preserving + the source ``pysteps_param`` method. Gaussian tail normalization uses log_ndtr. + A finite bounded solution is not proof of an unrestricted finite MLE: + ``boundary_limited`` identifies proximity to the bound or shifted-sample + coefficient of variation >= 1. Its numerical solution is retained for + compatibility and reported explicitly. Pixels have equal marginal weights; + spatial dependence and parameter uncertainty are not estimated here. + """ + if not np.isfinite(lower_truncation): + raise ValueError("lower_truncation must be finite") + if np.iscomplexobj(data): + raise ValueError("data must contain real values") + sample = np.asarray(data, dtype=float) + sample = sample[np.isfinite(sample) & (sample >= lower_truncation)] + diagnostics = dict( + n_samples=int(sample.size), + status="insufficient_samples", + boundary_limited=False, + ) + + def output(location=np.nan, scale=np.nan): + result = (float(location), float(scale)) + return (*result, diagnostics) if return_diagnostics else result + + if sample.size < 2: + return output() + if np.ptp(sample) <= 0: + diagnostics["status"] = "constant_sample" + return output() + shifted = sample - lower_truncation + mean_shifted = float(np.mean(shifted)) + second_moment = float(np.mean(shifted * shifted)) + + def scale_for_cutoff(a): + return 0.5 * ( + a * mean_shifted + np.sqrt(a**2 * mean_shifted**2 + 4 * second_moment) + ) + + def objective(a): + scale = scale_for_cutoff(a) + if not np.isfinite(scale) or scale <= 0: + return np.inf + standardized_second_moment = ( + second_moment / scale**2 + 2 * a * mean_shifted / scale + a**2 + ) + return float(np.log(scale) + 0.5 * standardized_second_moment + log_ndtr(-a)) + + result = minimize_scalar( + objective, + bounds=(-12.0, 12.0), + method="bounded", + options={"xatol": 1e-8, "maxiter": 200}, + ) + a = float(result.x) + scale = float(scale_for_cutoff(a)) + location = float(lower_truncation - a * scale) + if not result.success or not np.isfinite([location, scale]).all() or scale <= 0: + diagnostics["status"] = "optimization_failed" + return output() + cv = float(np.std(shifted) / mean_shifted) + boundary = abs(a) >= 12 - 1e-5 or cv >= 1 + diagnostics.update( + status="bounded_solution" if boundary else "ok", + boundary_limited=bool(boundary), + standardized_cutoff=a, + shifted_sample_cv=cv, + ) + return output(location, scale) + + +def compute_field_statistics(data, threshold, lower_truncation=None): + """Estimate rainfall occurrence and ordinary and latent marginal parameters. + + Parameters + ---------- + data: array_like + Nonempty, finite two-dimensional field. Units may be transformed rainfall + (dBR for the reference parametric method). The input is not modified. + threshold: float + Finite rain-occurrence threshold in the same units as data. Values at + or above the threshold are wet. The denominator is the whole valid grid. + lower_truncation: float, optional + Lower cutoff for fitting a latent Normal, at or above threshold. None + skips fitting. This setting does not change rainfall occurrence. + + Returns + ------- + result: dict + Rain fraction, whole-field mean and standard deviation, ``ordinary_mean`` + and ``ordinary_stdev`` of wet pixels (population moments), ``latent_mean`` + and ``latent_stdev``, pixel counts, and ``nonzero_fit`` diagnostics. + Unavailable moments are NaN. Estimates are not clipped or replaced. + """ + if np.iscomplexobj(data): + raise ValueError("data must contain real values") + data = np.asarray(data, dtype=float) + if data.ndim != 2 or data.size == 0: + raise ValueError("data must be a nonempty two-dimensional field") + if not np.isfinite(data).all(): + raise ValueError("data must contain only finite values") + if not np.isfinite(threshold): + raise ValueError("threshold must be finite") + if lower_truncation is not None and ( + not np.isfinite(lower_truncation) or lower_truncation < threshold + ): + raise ValueError("lower_truncation must be finite and at or above threshold") + wet = data[data >= threshold] + mean, stdev = ( + (float(np.mean(wet)), float(np.std(wet))) if wet.size else (np.nan, np.nan) + ) + result = dict( + rain_fraction=float(wet.size / data.size), + mean=float(np.mean(data)), + stdev=float(np.std(data)), + ordinary_mean=mean, + ordinary_stdev=stdev, + latent_mean=np.nan, + latent_stdev=np.nan, + n_pixels=int(data.size), + n_wet=int(wet.size), + threshold=float(threshold), + nonzero_fit={"status": "not_requested", "lower_truncation": None}, + ) + if lower_truncation is not None: + location, scale, diagnostics = fit_lower_truncated_normal( + wet, lower_truncation, return_diagnostics=True + ) + diagnostics["lower_truncation"] = float(lower_truncation) + result.update(latent_mean=location, latent_stdev=scale, nonzero_fit=diagnostics) + return result From 523e70275a9d8e8a968230dcf885eb4e596930ee Mon Sep 17 00:00:00 2001 From: alanseed Date: Thu, 15 Oct 2026 08:42:17 +1100 Subject: [PATCH 02/17] examples added --- doc/proposals/calibrated_generator.md | 26 +- doc/proposals/parametric_engine.md | 15 +- doc/proposals/parametric_nowcasting.md | 2 + .../user_guide/calibrated_generator.rst | 12 +- doc/source/user_guide/parametric_nowcast.rst | 4 + examples/plot_calibrated_noise_generator.py | 271 ++++++++++++++++++ examples/plot_parametric_nowcast_swiss.py | 183 ++++++++++++ 7 files changed, 503 insertions(+), 10 deletions(-) create mode 100644 examples/plot_calibrated_noise_generator.py create mode 100644 examples/plot_parametric_nowcast_swiss.py diff --git a/doc/proposals/calibrated_generator.md b/doc/proposals/calibrated_generator.md index 85e663a7..a1f844c3 100644 --- a/doc/proposals/calibrated_generator.md +++ b/doc/proposals/calibrated_generator.md @@ -140,7 +140,25 @@ Sphinx documentation build was not run because Sphinx was unavailable. ## Documentation and next stage API docstrings and noise-module reference entries are provided, with executable -examples in `doc/source/user_guide/calibrated_generator.rst`. The next stage is -the three-observation parametric nowcast engine, assembling transformation, motion, -estimation, reference settings and parameter safeguards, calibrated innovations, -advection/AR updates, and marginal feedback within one forecast interface. +examples in `doc/source/user_guide/calibrated_generator.rst`. The assembled +three-observation engine is documented in [its implementation record](parametric_engine.md). + +The [companion gallery example](../../examples/plot_calibrated_noise_generator.py) +uses the same MeteoSwiss composite as `plot_noise_generators.py`: 11 July 2016, +21:00 UTC. It estimates ordinary/latent moments and prescribed-break slopes on +a complete 300 km domain at 2 km spacing, explicitly selects generation moments, +and generates three seeded calibrated fields. It compares observed and calibrated +rainfall exceedance curves, contrasts radial spectra with an uncorrected field, +then decomposes the raw +field into normalized innovations. The original standardized-noise example +remains unchanged. + +The companion ran on the actual sample data and all four figures rendered. +Seeds 42 and 44 met the default spectral tolerance (distances 0.634 and 0.817); +seed 43 returned `best_available` with distance 1.295, correctly reported as +outside tolerance. The run checked finite 150-by-150 fields, rain-rate units and +thresholding, seed reproducibility, monotone exceedance curves and their wet-area +denominators, and zero-mean/unit-standard-deviation cascade innovations. +The raw generated fields had standard deviations around 2.39 dBR, preserving +the distinction from standardized cascade noise. This illustrates the methods +on Swiss data rather than asserting a Swiss calibration or new forecast skill. diff --git a/doc/proposals/parametric_engine.md b/doc/proposals/parametric_engine.md index ac91a259..cb098da1 100644 --- a/doc/proposals/parametric_engine.md +++ b/doc/proposals/parametric_engine.md @@ -179,6 +179,13 @@ source forecasts, exact cached-advection parity for zero/constant/varying flow, fractional cadence, independent streams and input purity, streaming equivalence, dry/partly dry/sparse histories, safeguard choices, and invalid input/settings. The gallery example executed successfully with internal motion and estimation. +The additional [Swiss radar tutorial](../../examples/plot_parametric_nowcast_swiss.py) +also ran with the actual MeteoSwiss example observations at 11:50, 11:55, and +12:00 UTC on 31 January 2017. A complete 300 km LV03 subdomain, aggregated to +2 km, produced a finite `(20, 6, 150, 150)` ensemble and four rendered figures: +observation, ensemble mean, four members, and 1 mm/h exceedance probabilities. +Domain metadata and probability calculations were checked. This demonstrates +the interface on Swiss data; it does not retune or validate a Swiss calibration. The wheel built successfully; its engine, trajectory helper, and forecast fixtures matched the checkout by SHA-256. All 154 packaged engine, estimator, and generator tests passed from the extracted wheel outside the checkout. @@ -190,6 +197,10 @@ not claimed. ## Non-parametric contribution: assessment, not implementation +Decision, 15 October 2026: defer the non-parametric engine and discussion with +upstream maintainers until the parametric contribution is in pySTEPS. The +assessment below is retained as background; it is not current delivery work. + There is merit in a subsequent contribution, chiefly the **Eulerian stochastic inflow treatment** evaluated in the source comparisons. Native upstream STEPS already supports non-parametric spectral noise, observed cascade correlations, @@ -225,5 +236,5 @@ The design implication is modest: force a general framework before either method is accepted. The non-parametric engine and native STEPS evolution are unchanged by this -contribution. Its useful boundary extension can be reviewed as a separate -scientific contribution after this engine is accepted. +contribution. Its useful boundary extension is on hold and can be raised as a +separate scientific contribution after the parametric engine is upstream. diff --git a/doc/proposals/parametric_nowcasting.md b/doc/proposals/parametric_nowcasting.md index ede5eff4..231fc8c7 100644 --- a/doc/proposals/parametric_nowcasting.md +++ b/doc/proposals/parametric_nowcasting.md @@ -293,6 +293,8 @@ forecast engine and executable example are now implemented; see the [engine extraction and validation record](parametric_engine.md) for the public call, reference defaults, source comparisons, limitations, and a separate assessment of the non-parametric inflow extension. +The non-parametric engine is deferred until the parametric contribution is in +pySTEPS; it is not part of the current delivery work. Use this Markdown proposal as the scope and design record for upstream discussion. Link it from issue #472 or a subsequent contribution discussion when ready. diff --git a/doc/source/user_guide/calibrated_generator.rst b/doc/source/user_guide/calibrated_generator.rst index 92b36e20..a5b7403f 100644 --- a/doc/source/user_guide/calibrated_generator.rst +++ b/doc/source/user_guide/calibrated_generator.rst @@ -3,16 +3,20 @@ Calibrated stochastic rainfall fields The calibrated generator creates a NumPy rainfall-marginal field from an explicit statistical parameter mapping. It implements the ``inverse_calibrated_v2`` method -extracted from ``pysteps_param``. It is a component of the proposed parametric +extracted from ``pysteps_param``. It is a component of the parametric nowcast engine; ensemble state evolution and observation preparation are separate -stages. +stages. See :ref:`sphx_glr_auto_examples_plot_calibrated_noise_generator.py` +for a radar-data example using the new estimators, explicit moment 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 eventual engine will estimate and select these parameters from +interface; the parametric engine estimates and selects these parameters from the observations:: import numpy as np @@ -103,7 +107,7 @@ first, then obtains normalized cascade innovations through decomposition:: 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 will consume this helper directly. +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 diff --git a/doc/source/user_guide/parametric_nowcast.rst b/doc/source/user_guide/parametric_nowcast.rst index 9ba73979..baaa6820 100644 --- a/doc/source/user_guide/parametric_nowcast.rst +++ b/doc/source/user_guide/parametric_nowcast.rst @@ -35,6 +35,10 @@ per observation timestep. Otherwise dense Lucas-Kanade motion is estimated from the latest two transformed observations. Statistical parameters and motion remain fixed throughout each forecast. See :ref:`sphx_glr_auto_examples_plot_parametric_nowcast.py` for a complete runnable synthetic example. +For a radar-data walkthrough using the same Swiss sequence as the STEPS tutorial, +see :ref:`sphx_glr_auto_examples_plot_parametric_nowcast_swiss.py`. It shows +importing three observations, selecting a complete domain, generating the +ensemble, and plotting means, members, and exceedance probabilities. Reference settings and regional overrides ---------------------------------------- diff --git a/examples/plot_calibrated_noise_generator.py b/examples/plot_calibrated_noise_generator.py new file mode 100644 index 00000000..25d33bde --- /dev/null +++ b/examples/plot_calibrated_noise_generator.py @@ -0,0 +1,271 @@ +""" +Calibrated stochastic rainfall fields +===================================== + +This companion to :ref:`sphx_glr_auto_examples_plot_noise_generators.py` +uses the new marginal and +spectral estimators and calibrated generator on the same Swiss radar composite. +It compares generated rainfall distributions and radial spectra, then shows +how the raw fields become normalized cascade innovations. + +Download the `pysteps example data `_ +and configure the ``mch`` data source in ``pystepsrc``. MeteoSwiss provides the +radar observation used here. This example generates independent spatial fields; +it does not perform temporal nowcasting. +""" + +from pathlib import Path +from pprint import pprint + +import matplotlib.pyplot as plt +import numpy as np + +from pysteps import io, rcparams +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.cascade.decomposition import decomposition_fft +from pysteps.noise.parametric import ( + generate_calibrated_field, + generate_stochastic_field, +) +from pysteps.utils import conversion, dimension, transformation +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic +from pysteps.utils.statistics import compute_field_statistics +from pysteps.visualization import basemaps, plot_precip_field + +rain_threshold = 1.0 # mm/h +scale_break = 20.0 # km, prescribed rather than estimated +lower_truncation = 1.25 # dBR, separate from the occurrence threshold +seed = 42 +num_realizations = 3 + +############################################################################### +# Read the Swiss radar composite +# ------------------------------ +# +# Use the 11 July 2016, 21:00 UTC image from ``plot_noise_generators.py``. +# Select a complete 300 km subdomain in Swiss LV03 coordinates, then aggregate +# to 2 km. Missing radar coverage is not interpreted as dry rainfall. + +filename = ( + Path(rcparams.data_sources["mch"]["root_path"]) + / "20160711" + / "AQC161932100V_00005.801.gif" +) +rain, _, metadata = io.import_mch_gif( + str(filename), product="AQC", unit="mm", accutime=5.0 +) +rain, metadata = conversion.to_rainrate(rain, metadata) +extent = (450000, 750000, 0, 300000) +rain, metadata = dimension.clip_domain(rain, metadata, extent) +rain, metadata = dimension.aggregate_fields_space(rain, metadata, 2000) +if not np.isfinite(rain).all() or np.any(rain < 0): + raise ValueError("The example requires complete nonnegative rain-rate data") +kmperpixel = metadata["xpixelsize"] / 1000.0 +observed, transformed_metadata = transformation.dB_transform( + rain, + metadata, + threshold=rain_threshold, + zerovalue=10 * np.log10(rain_threshold) - 0.1, +) +threshold = transformed_metadata["threshold"] +zerovalue = transformed_metadata["zerovalue"] + +############################################################################### +# Estimate marginal parameters and prescribed-break spectral slopes +# ----------------------------------------------------------------- +# +# Ordinary moments describe retained wet pixels; latent moments describe the +# fitted parent Normal. The reference engine selects the latent pair when its +# normalized distance from the ordinary pair is at most 0.5, otherwise the +# ordinary pair. Make that caller decision explicit here: the generator itself +# does not select moments or apply regional parameter bounds. +# +# Radial frequencies are in cycles per pixel, so convert the 20 km break to +# pixels for fitting. Pass physical km units to the generator later. + +statistics = compute_field_statistics(observed, threshold, lower_truncation) +frequency, power = rapsd_logarithmic(observed) +spectrum = fit_power_law_spectrum(frequency, power, scale_break / kmperpixel) +if spectrum["status"] != "ok": + raise ValueError(f"Unavailable spectral estimate: {spectrum['status']}") +ordinary = np.array([statistics["ordinary_mean"], statistics["ordinary_stdev"]]) +latent = np.array([statistics["latent_mean"], statistics["latent_stdev"]]) +if not np.isfinite(ordinary).all() or ordinary[1] <= 0: + raise ValueError("Raining-pixel moments are unavailable") +valid_latent = np.isfinite(latent).all() and latent[1] > 0 +moment_distance = ( + np.linalg.norm(latent - ordinary) / ordinary[1] if valid_latent else np.inf +) +use_latent = valid_latent and moment_distance <= 0.5 +location, scale = latent if use_latent else ordinary +parameters = dict( + beta_1=spectrum["beta_1"], + beta_2=spectrum["beta_2"], + rain_fraction=statistics["rain_fraction"], + mean_db=float(location), + stdev_db=float(scale), +) +print("Ordinary wet-pixel mean/stdev (dBR):", ordinary) +print("Fitted parent-Normal mean/stdev (dBR):", latent) +print("Moment source:", "latent" if use_latent else "ordinary") +print("Generation targets:") +pprint(parameters) + +############################################################################### +# Generate calibrated rainfall fields +# ----------------------------------- +# +# The outputs are float64 fields in dBR retaining a rainfall marginal, including +# continuous dry anomalies. They are not globally standardized noise. Calibration +# applies the reference -0.5 correction to both slopes, assesses realized slopes +# on a thresholded copy, and reports whether its distance tolerance was met. +# A best-available field can be returned without meeting that tolerance. +# +# Use three reproducible local seeds and retain their diagnostics. Also generate +# an uncorrected field for the spectrum comparison. Its seed matches the first +# calibrated run's initial draw; retries can select a different candidate. + +fields, diagnostics = [], [] +for member in range(num_realizations): + field, info = generate_calibrated_field( + rain.shape, + parameters, + kmperpixel, + scale_break, + threshold, + seed=seed + member, + zerovalue=zerovalue, + return_diagnostics=True, + ) + fields.append(field) + diagnostics.append(info) + print( + f"Realization {member + 1}: {info['status']}, " + f"distance={info['distance']:.3f}, attempts={info['attempts']}, " + f"selected={info['selected_attempt']}" + ) +uncorrected = generate_stochastic_field( + rain.shape, + parameters, + kmperpixel, + scale_break, + threshold, + seed=seed, + zerovalue=zerovalue, +) + +# Convert copies for rainfall plots. Keep the raw dBR arrays for decomposition. +rain_fields = [ + transformation.dB_transform(f, metadata=transformed_metadata, inverse=True)[0] + for f in fields +] +observed_rain = transformation.dB_transform( + observed, metadata=transformed_metadata, inverse=True +)[0] +fig = plt.figure(figsize=(10, 8)) +for index, (field, title) in enumerate( + zip( + [observed_rain, *rain_fields], + ["Observation", "Realization 1", "Realization 2", "Realization 3"], + ) +): + ax = basemaps.plot_geography( + metadata["projection"], extent, scale="110m", subplot=(2, 2, index + 1) + ) + plot_precip_field(field, ax=ax, geodata=metadata, axis="off", title=title) +plt.tight_layout() +plt.show() + +############################################################################### +# Compare rainfall occurrence and intensity distributions +# ------------------------------------------------------- +# +# Unconditional exceedance curves include all pixels in their denominator. +# Their value at 1 mm/h measures realized wet fraction. Histogram mapping and +# threshold crossings mean that occurrence and marginal moments are approximate; +# fitted parent-Normal moments are not the observed conditional moments. +# The generator's Normal-quantile clipping also limits its extreme tails. + +colors = ["tab:blue", "tab:orange", "tab:green"] +plotted_fields = [observed_rain, *rain_fields] +levels = np.geomspace(rain_threshold, max(f.max() for f in plotted_fields), 100) +fig, ax = plt.subplots() +for index, (field, label) in enumerate( + zip(plotted_fields, ["Observed", "Calibrated 1", "Calibrated 2", "Calibrated 3"]) +): + ordered = np.sort(field.ravel()) + probability = ( + ordered.size - np.searchsorted(ordered, levels, side="left") + ) / ordered.size + ax.semilogx( + levels, + probability, + color="black" if index == 0 else colors[index - 1], + label=label, + ) +ax.set_xlabel("Rain-rate threshold (mm/h)") +ax.set_ylabel("Fraction of pixels at or above threshold") +ax.set_title("Rainfall occurrence and intensity") +ax.legend() +ax.grid(True, alpha=0.25) +plt.show() + +############################################################################### +# Compare the realized radial spectra +# ----------------------------------- +# +# Follow the calibration assessment: replace values <= the dBR threshold by +# the dry value on a copy before estimating each generated spectrum. The radial +# estimator standardizes its input internally; the plotted quantity is mean +# squared unscaled FFT amplitude per mode, not a physical spectral density. +# A radial spectrum does not preserve the observation's directional structure. + +fig, ax = plt.subplots() +ax.loglog(kmperpixel / frequency, power, color="black", label="Observed") +for index, (field, label) in enumerate( + zip( + [uncorrected, *fields], + ["Without correction", "Calibrated 1", "Calibrated 2", "Calibrated 3"], + ) +): + assessment = field.copy() + assessment[assessment <= threshold] = zerovalue + frequencies, powers = rapsd_logarithmic(assessment) + ax.loglog( + kmperpixel / frequencies, + powers, + color="gray" if index == 0 else colors[index - 1], + linestyle="--" if index == 0 else "-", + label=label, + ) +ax.axvline(scale_break, color="black", linestyle=":", label="Prescribed break") +ax.invert_xaxis() +ax.set_xlabel("Wavelength (km)") +ax.set_ylabel("Radial mean FFT power of standardized dBR") +ax.set_title("Spectral assessment after marginal mapping") +ax.legend() +ax.grid(True, alpha=0.25) +plt.show() + +############################################################################### +# Decompose the raw field into normalized cascade innovations +# ----------------------------------------------------------- +# +# The nowcast engine decomposes the raw field, retaining its continuous dry +# anomalies. Normalization happens at each cascade level, rather than treating +# the rainfall-marginal field as globally standardized perturbation noise. + +bandpass = filter_gaussian(fields[0].shape, 6) +cascade = decomposition_fft(fields[0], bandpass, compute_stats=True, normalize=True) +innovations = cascade["cascade_levels"] +print("Innovation means:", innovations.mean(axis=(1, 2))) +print("Innovation standard deviations:", innovations.std(axis=(1, 2))) +fig, axes = plt.subplots(1, 2, figsize=(9, 4), constrained_layout=True) +for ax, level in zip(axes, [0, 5]): + image = ax.imshow(innovations[level], cmap="RdBu_r", vmin=-3, vmax=3) + ax.set_title(f"Normalized cascade level {level + 1}") + ax.set_axis_off() +fig.colorbar(image, ax=axes, label="Standardized anomaly", shrink=0.8) +plt.show() + +# sphinx_gallery_thumbnail_number = 1 diff --git a/examples/plot_parametric_nowcast_swiss.py b/examples/plot_parametric_nowcast_swiss.py new file mode 100644 index 00000000..582dda44 --- /dev/null +++ b/examples/plot_parametric_nowcast_swiss.py @@ -0,0 +1,183 @@ +""" +Parametric nowcast with Swiss radar data +======================================== + +This tutorial uses the same Swiss radar sequence as the STEPS nowcast example. +Three rain-rate observations are passed directly to the parametric engine, +which performs transformation, motion estimation, parameter estimation, and +ensemble generation internally. + +Download the `pysteps example data `_ +and configure the ``mch`` data source in ``pystepsrc`` before running this example. +The radar observations are provided by MeteoSwiss. +""" + +from datetime import datetime +from pprint import pprint + +import matplotlib.pyplot as plt +import numpy as np + +from pysteps import io, nowcasts, rcparams +from pysteps.postprocessing.ensemblestats import excprob +from pysteps.utils import conversion, dimension +from pysteps.visualization import basemaps, plot_precip_field + +n_ens_members = 20 +n_leadtimes = 6 +seed = 24 +rain_threshold = 1.0 # mm/h: reference occurrence threshold of the engine +date = datetime(2017, 1, 31, 12, 0) + +############################################################################### +# Read three observations in rain-rate units +# ------------------------------------------ +# +# Load the observations at 11:50, 11:55, and 12:00 UTC using the usual pySTEPS +# data-source configuration. The imported accumulation is converted to mm/h. + +source = rcparams.data_sources["mch"] +timestep = source["timestep"] # minutes between observations +filenames = io.find_by_date( + date, + source["root_path"], + source["path_fmt"], + source["fn_pattern"], + source["fn_ext"], + timestep, + num_prev_files=2, +) +importer = io.get_method(source["importer"], "importer") +rain, _, metadata = io.read_timeseries(filenames, importer, **source["importer_kwargs"]) +rain, metadata = conversion.to_rainrate(rain, metadata) + +############################################################################### +# Prepare a complete spatial domain +# --------------------------------- +# +# The full Swiss composite has missing coverage outside the radar footprint. +# This first parametric engine requires complete finite grids. Select a 300 km +# square subdomain with complete coverage at all three times, in Swiss LV03 +# coordinates (metres). Clip at the native resolution, then aggregate to 2 km +# to limit computation and memory. Missing pixels are not replaced with zero. + +extent = (450000, 750000, 0, 300000) # left, right, bottom, top in metres +rain, metadata = dimension.clip_domain(rain, metadata, extent) +rain, metadata = dimension.aggregate_fields_space(rain, metadata, 2000) +if rain.shape[0] != 3 or not np.isfinite(rain).all() or np.any(rain < 0): + raise ValueError("The tutorial requires three complete nonnegative rain fields") +kmperpixel = metadata["xpixelsize"] / 1000.0 + + +# Use the same geographic metadata for observations and forecasts. A coarse +# basemap limits the auxiliary Natural Earth map data needed by the example. +def make_map(subplot=(1, 1, 1)): + """Create the geographic axes before plotting the precipitation raster.""" + return basemaps.plot_geography( + metadata["projection"], extent, scale="110m", subplot=subplot + ) + + +plt.figure() +plot_precip_field( + rain[-1], + ax=make_map(), + geodata=metadata, + axis="off", + title="Swiss radar observation, 31 January 2017, 12:00 UTC", +) +plt.show() + +############################################################################### +# Generate the parametric ensemble +# -------------------------------- +# +# Pass linear rain rates directly: no external dBR transform or motion estimate +# is needed. The returned ensemble is also in mm/h, with shape +# (member, lead, y, x). Its first lead is +5 minutes and its last is +30 minutes. +# Fixed motion and estimated statistical parameters are shared across leads; +# each member has its own reproducible random stream. +# +# This example uses the existing regional reference defaults, including the +# 1 mm/h occurrence threshold and 20 km spectral break. Applying them to this +# Swiss case illustrates the interface; it does not establish Swiss calibration +# or forecast skill. The STEPS tutorial uses a different occurrence threshold. + +forecast = nowcasts.get_method("parametric") +ensemble, diagnostics = forecast( + rain, + timesteps=n_leadtimes, + n_ens_members=n_ens_members, + kmperpixel=kmperpixel, + timestep=timestep, + rain_threshold=rain_threshold, + seed=seed, + return_diagnostics=True, +) +print("Ensemble shape:", ensemble.shape) +print("Generation parameters:") +pprint(diagnostics["parameters"]) +print("Marginal moment source:", diagnostics["safeguards"]["moment_source"]) +print("Persistence definition:", diagnostics["temporal_model"]["definition"]) + +############################################################################### +# Plot the ensemble mean +# ---------------------- +# +# Averaging realizations smooths rainfall features whose position or intensity +# differs between members. Individual members retain more spatial variability. + +lead_minutes = n_leadtimes * timestep +ensemble_mean = ensemble[:, -1].mean(axis=0) +plt.figure() +plot_precip_field( + ensemble_mean, + ax=make_map(), + geodata=metadata, + axis="off", + title=f"Parametric ensemble mean (+{lead_minutes:g} min)", +) +plt.show() + +############################################################################### +# Plot four ensemble members +# -------------------------- + +fig = plt.figure(figsize=(10, 8)) +for member in range(4): + ax = make_map((2, 2, member + 1)) + ax = plot_precip_field( + ensemble[member, -1], + ax=ax, + geodata=metadata, + colorbar=False, + axis="off", + ) + ax.set_title(f"Member {member + 1} (+{lead_minutes:g} min)") +plt.tight_layout() +plt.show() + +############################################################################### +# Derive exceedance probabilities +# ------------------------------- +# +# Use a probability threshold consistent with the engine's occurrence threshold. +# The ensemble fraction exceeding 1 mm/h is an estimated exceedance probability, +# not an independently calibrated probability product. + +probability_threshold = 1.0 # mm/h +probabilities = excprob(ensemble[:, -1], probability_threshold) +plt.figure() +plot_precip_field( + probabilities, + ax=make_map(), + geodata=metadata, + axis="off", + ptype="prob", + units="mm/h", + probthr=probability_threshold, + title=f"Probability of rain >= {probability_threshold:g} mm/h (+{lead_minutes:g} min)", +) +plt.show() + +# sphinx_gallery_thumbnail_number = 3 From 4dd2c0326ce65006f84581891b7eab71db9bedf6 Mon Sep 17 00:00:00 2001 From: alanseed Date: Thu, 15 Oct 2026 08:55:00 +1100 Subject: [PATCH 03/17] Update estimator guide for implemented parametric engine --- doc/source/user_guide/parametric_estimators.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/source/user_guide/parametric_estimators.rst b/doc/source/user_guide/parametric_estimators.rst index d5fcac5b..f4b2d643 100644 --- a/doc/source/user_guide/parametric_estimators.rst +++ b/doc/source/user_guide/parametric_estimators.rst @@ -2,9 +2,9 @@ Parametric rainfall estimators ============================== The parametric estimators provide NumPy-based spatial statistics and temporal -model utilities extracted from ``pysteps_param``. They are the first components -of a proposed parametric nowcast engine; they do not yet implement the public -three-observation forecast endpoint. +model utilities extracted from ``pysteps_param``. They support the +three-observation forecast endpoint described in :doc:`parametric_nowcast` +and can also be used independently. Spatial estimation ------------------ @@ -114,7 +114,8 @@ the external repositories nor a database. The reference-generation script in ``pysteps/tests/data/generate_parametric_reference.py`` documents how to refresh the fixture using the source project's environment. -The defaults used to configure the eventual engine will be associated with the -evaluated paper and case-study configuration. These utilities leave regional -calibration settings and operational parameter-repair policies explicit. The -scope and extraction record are maintained under ``doc/proposals/``. +The engine defaults are traced to the evaluated paper and case-study +configuration in ``doc/proposals/parametric_engine.md``. These utilities leave +regional calibration settings and operational parameter-repair policies +explicit. The scope and extraction record are maintained under +``doc/proposals/``. From e3d1a02f8eed780502c2cfb2f10ee81c66f7d7fc Mon Sep 17 00:00:00 2001 From: alanseed Date: Thu, 15 Oct 2026 09:17:24 +1100 Subject: [PATCH 04/17] Fix parametric documentation headings --- doc/source/user_guide/calibrated_generator.rst | 4 ++-- doc/source/user_guide/parametric_nowcast.rst | 6 +++--- examples/plot_parametric_nowcast.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/user_guide/calibrated_generator.rst b/doc/source/user_guide/calibrated_generator.rst index a5b7403f..0c410c35 100644 --- a/doc/source/user_guide/calibrated_generator.rst +++ b/doc/source/user_guide/calibrated_generator.rst @@ -77,7 +77,7 @@ quantile clip can all be overridden. No reference defaults were retuned during this port. 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 @@ -91,7 +91,7 @@ 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:: diff --git a/doc/source/user_guide/parametric_nowcast.rst b/doc/source/user_guide/parametric_nowcast.rst index baaa6820..fff85a2c 100644 --- a/doc/source/user_guide/parametric_nowcast.rst +++ b/doc/source/user_guide/parametric_nowcast.rst @@ -41,7 +41,7 @@ importing three observations, selecting a complete domain, generating the ensemble, and plotting means, members, and exceedance probabilities. Reference settings and regional overrides ----------------------------------------- +----------------------------------------- These defaults preserve the evaluated ``nowcast_v2`` engine configuration from ``psteps_param_paper/config/db/config.json`` and the WRNZ acceptance configuration, @@ -132,7 +132,7 @@ The reference has window (31,31), three pyramid levels, minimum eigenvalue It is estimated from two images, once, with no velocity perturbations. Dry and unavailable estimates ----------------------------- +----------------------------- If the newest field has no rain at the occurrence threshold, the ensemble is zero, including when earlier observations were wet. Motion/noise generation is @@ -177,7 +177,7 @@ NetCDF packing and storage. Outputs have no regional 200 mm/h cap; finite generated values are required. No input arrays or settings are modified. Randomness, diagnostics, and incremental output ----------------------------------------------- +----------------------------------------------- A local NumPy SeedSequence spawns independent member streams. With the same seed and settings, the leading members agree when ensemble size increases. diff --git a/examples/plot_parametric_nowcast.py b/examples/plot_parametric_nowcast.py index 70a817cb..c17c84c1 100644 --- a/examples/plot_parametric_nowcast.py +++ b/examples/plot_parametric_nowcast.py @@ -1,6 +1,6 @@ """ Parametric nowcast from three rain-rate fields -============================================ +============================================== This self-contained example builds three synthetic observations, then lets the parametric engine estimate motion and parameters and generate an ensemble. From 52bf915127c2fe039c6846dc1c2d6cd71498796c Mon Sep 17 00:00:00 2001 From: alanseed Date: Thu, 15 Oct 2026 10:04:55 +1100 Subject: [PATCH 05/17] Allow platform variation in fitted marginal statistics --- pysteps/tests/test_parametric_estimators.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pysteps/tests/test_parametric_estimators.py b/pysteps/tests/test_parametric_estimators.py index f0c886c7..30c3205b 100644 --- a/pysteps/tests/test_parametric_estimators.py +++ b/pysteps/tests/test_parametric_estimators.py @@ -59,7 +59,13 @@ def test_source_marginal_parity(reference): original = field.copy() estimated = compute_field_statistics(field, 0, lower_truncation=1.25) for name, expected in reference["marginal"].items(): - assert estimated[name] == pytest.approx(expected, rel=1e-12, abs=1e-12) + # The likelihood optimizer's latent moments vary slightly across + # numerical libraries and platforms; direct moments remain tighter. + if name in ("latent_mean", "latent_stdev"): + rel_tol, abs_tol = 1e-7, 1e-8 + else: + rel_tol, abs_tol = 1e-12, 1e-12 + assert estimated[name] == pytest.approx(expected, rel=rel_tol, abs=abs_tol) assert_array_equal(field, original) From c1c4da569c26891370c8507f26ab5cf427f757ef Mon Sep 17 00:00:00 2001 From: alanseed Date: Mon, 26 Oct 2026 16:00:25 +1100 Subject: [PATCH 06/17] parametric temporal fallback added --- pysteps/nowcasts/parametric.py | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pysteps/nowcasts/parametric.py b/pysteps/nowcasts/parametric.py index dc23e653..85c3aa4e 100644 --- a/pysteps/nowcasts/parametric.py +++ b/pysteps/nowcasts/parametric.py @@ -351,6 +351,7 @@ def forecast( seed=None, motion_kwargs=None, estimation_kwargs=None, + temporal_fallback=None, noise_kwargs=None, callback=None, return_output=True, @@ -393,6 +394,14 @@ def forecast( Override marginal fitting, temporal scaling, moment selection, bounds, and regional low-coverage fallbacks. See the parametric-nowcast user guide for all keys and defaults. Reference time is in minutes. + temporal_fallback: dict, optional + Configured temporal-only fallback with positive ``lzero`` (km) and + ``tzero`` (minutes). If any observed AR(2) pair or the estimated + reference time is invalid, use tzero*(wavelength/lzero)**temporal_exponent + to construct all model correlations. Complete input grids and valid + marginal/spatial estimates remain required. This mode disables the + legacy low-coverage spectral/persistence replacement. Cannot be combined + with an explicit estimation_kwargs reference_time override. noise_kwargs: dict, optional Calibration controls for generate_calibrated_field; nbins and eps also control post-AR marginal mapping. Randomness is controlled by seed. @@ -464,6 +473,14 @@ def forecast( if not isinstance(flag, (bool, np.bool_)): raise ValueError(f"{name} must be boolean") estimation = _estimation_settings(estimation_kwargs, threshold, n_cascade_levels) + if temporal_fallback is not None: + if not isinstance(temporal_fallback, Mapping) or set(temporal_fallback) != {"lzero", "tzero"}: + raise ValueError("temporal_fallback requires exactly lzero (km) and tzero (minutes)") + temporal_fallback = {k: _positive(k, v) for k, v in temporal_fallback.items()} + if estimation["reference_time"] is not None: + raise ValueError("temporal_fallback cannot be combined with reference_time override") + # Temporal fallback must never mask an unavailable spatial estimate. + estimation["low_rain_fraction"] = 0.0 noise_settings = _noise_settings(noise_kwargs) if motion_kwargs is not None and not isinstance(motion_kwargs, Mapping): raise ValueError("motion_kwargs must be a mapping") @@ -502,6 +519,7 @@ def forecast( taper_sigma_pixels=taper_sigma, min_rain_fraction=min_rain_fraction, estimation=estimation, + temporal_fallback=temporal_fallback, noise=noise_settings, motion=motion_settings, ) @@ -562,11 +580,34 @@ def forecast( if estimation["reference_time"] is None else estimation["reference_time"] ) + temporal_selection = dict(source="observed", reason=None) + if estimation["reference_time"] is not None: + temporal_selection["source"] = "override" + use_fallback = temporal_fallback is not None and ( + not np.isfinite(times).all() or np.any(times <= 0) + or not np.isfinite(estimated_time) or estimated_time <= 0 + ) + if use_fallback: + reference_time = temporal_fallback["tzero"] * ( + wavelengths[0] / temporal_fallback["lzero"] + ) ** estimation["temporal_exponent"] + temporal_selection.update( + source="configured_fallback", + reason="invalid_observed_ar2_or_reference_time", + configuration=deepcopy(temporal_fallback), + ) + temporal_selection["selected_reference_time_minutes"] = reference_time frequency, power = rapsd_logarithmic(transformed[-1]) spectrum = fit_power_law_spectrum(frequency, power, scale_break / kmperpixel) parameters, safeguards = _generation_parameters( statistics, spectrum, reference_time, estimation ) + if use_fallback: + # The explicit configured scale law is not clipped to regional bounds. + parameters["reference_time"] = reference_time + safeguards["bounded_parameters"] = [ + k for k in safeguards["bounded_parameters"] if k != "reference_time" + ] model_lags, temporal_diagnostics = model_cascade_correlations( parameters["reference_time"], wavelengths, @@ -586,6 +627,7 @@ def forecast( parameters=parameters.copy(), safeguards=safeguards, temporal_model=temporal_diagnostics, + temporal_selection=temporal_selection, model_correlations=model_lags, ar_coefficients=phi, constant_observed_levels=[ From de22a03dba0413723af8a69fba51352075f5716a Mon Sep 17 00:00:00 2001 From: alanseed Date: Mon, 26 Oct 2026 16:03:03 +1100 Subject: [PATCH 07/17] tests for parametric temporal fallback added --- pysteps/tests/test_nowcasts_parametric.py | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pysteps/tests/test_nowcasts_parametric.py b/pysteps/tests/test_nowcasts_parametric.py index 2789def2..9d72c989 100644 --- a/pysteps/tests/test_nowcasts_parametric.py +++ b/pysteps/tests/test_nowcasts_parametric.py @@ -421,3 +421,41 @@ def test_safeguards_moment_selection_and_joint_bounds(): statistics["latent_mean"] = np.nan _, audit = parametric._generation_parameters(statistics, spectrum, 200, settings) assert audit["moment_reason"] == "invalid_latent" + + +@pytest.mark.parametrize("invalid_pair", [[np.nan, np.nan], [1.0, 1.0], [0.2, -0.99]]) +def test_configured_temporal_fallback(reference, monkeypatch, invalid_pair): + def correlations(cascades, velocity): + return np.tile(invalid_pair, (cascades.shape[1], 1)) + monkeypatch.setattr(parametric, "lagrangian_autocorrelation", correlations) + result, diag = _call(reference, temporal_fallback={"lzero": 256, "tzero": 420}, + timestep=7.5, return_diagnostics=True) + expected_time = 420 * (diag["wavelengths_km"][0] / 256) ** 0.81 + assert diag["temporal_selection"]["source"] == "configured_fallback" + assert diag["parameters"]["reference_time"] == pytest.approx(expected_time) + np.testing.assert_allclose(diag["temporal_model"]["requested_times"], + 420 * (diag["wavelengths_km"] / 256) ** 0.81) + assert not diag["safeguards"]["low_rain_fraction_fallback"] + assert np.isfinite(result).all() + + +def test_configured_fallback_preserves_valid_observation(reference): + result, diag = _call(reference, temporal_fallback={"lzero": 256, "tzero": 420}, + return_diagnostics=True) + assert diag["temporal_selection"]["source"] == "observed" + original = _call(reference) + np.testing.assert_array_equal(result, original) + + +def test_temporal_fallback_does_not_replace_spatial_estimates(reference, monkeypatch): + monkeypatch.setattr(parametric, "fit_power_law_spectrum", + lambda *args: {"beta_1": np.nan, "beta_2": np.nan}) + with pytest.raises(ValueError, match="Unavailable spectral"): + _call(reference, temporal_fallback={"lzero": 256, "tzero": 420}) + + +@pytest.mark.parametrize("fallback", [{}, {"lzero": 0, "tzero": 420}, + {"lzero": 256, "tzero": float("nan")}]) +def test_invalid_temporal_fallback(reference, fallback): + with pytest.raises(ValueError): + _call(reference, temporal_fallback=fallback) From f9e9bfe4c86ee584bb1b317629c3e6227da78d15 Mon Sep 17 00:00:00 2001 From: alanseed Date: Tue, 27 Oct 2026 11:08:12 +1100 Subject: [PATCH 08/17] black reformatting completed --- pysteps/nowcasts/parametric.py | 27 ++++++++++++------ pysteps/tests/test_nowcasts_parametric.py | 34 ++++++++++++++++------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/pysteps/nowcasts/parametric.py b/pysteps/nowcasts/parametric.py index 85c3aa4e..00d497e4 100644 --- a/pysteps/nowcasts/parametric.py +++ b/pysteps/nowcasts/parametric.py @@ -474,11 +474,18 @@ def forecast( raise ValueError(f"{name} must be boolean") estimation = _estimation_settings(estimation_kwargs, threshold, n_cascade_levels) if temporal_fallback is not None: - if not isinstance(temporal_fallback, Mapping) or set(temporal_fallback) != {"lzero", "tzero"}: - raise ValueError("temporal_fallback requires exactly lzero (km) and tzero (minutes)") + if not isinstance(temporal_fallback, Mapping) or set(temporal_fallback) != { + "lzero", + "tzero", + }: + raise ValueError( + "temporal_fallback requires exactly lzero (km) and tzero (minutes)" + ) temporal_fallback = {k: _positive(k, v) for k, v in temporal_fallback.items()} if estimation["reference_time"] is not None: - raise ValueError("temporal_fallback cannot be combined with reference_time override") + raise ValueError( + "temporal_fallback cannot be combined with reference_time override" + ) # Temporal fallback must never mask an unavailable spatial estimate. estimation["low_rain_fraction"] = 0.0 noise_settings = _noise_settings(noise_kwargs) @@ -584,13 +591,17 @@ def forecast( if estimation["reference_time"] is not None: temporal_selection["source"] = "override" use_fallback = temporal_fallback is not None and ( - not np.isfinite(times).all() or np.any(times <= 0) - or not np.isfinite(estimated_time) or estimated_time <= 0 + not np.isfinite(times).all() + or np.any(times <= 0) + or not np.isfinite(estimated_time) + or estimated_time <= 0 ) if use_fallback: - reference_time = temporal_fallback["tzero"] * ( - wavelengths[0] / temporal_fallback["lzero"] - ) ** estimation["temporal_exponent"] + reference_time = ( + temporal_fallback["tzero"] + * (wavelengths[0] / temporal_fallback["lzero"]) + ** estimation["temporal_exponent"] + ) temporal_selection.update( source="configured_fallback", reason="invalid_observed_ar2_or_reference_time", diff --git a/pysteps/tests/test_nowcasts_parametric.py b/pysteps/tests/test_nowcasts_parametric.py index 9d72c989..b5baa342 100644 --- a/pysteps/tests/test_nowcasts_parametric.py +++ b/pysteps/tests/test_nowcasts_parametric.py @@ -427,35 +427,49 @@ def test_safeguards_moment_selection_and_joint_bounds(): def test_configured_temporal_fallback(reference, monkeypatch, invalid_pair): def correlations(cascades, velocity): return np.tile(invalid_pair, (cascades.shape[1], 1)) + monkeypatch.setattr(parametric, "lagrangian_autocorrelation", correlations) - result, diag = _call(reference, temporal_fallback={"lzero": 256, "tzero": 420}, - timestep=7.5, return_diagnostics=True) + result, diag = _call( + reference, + temporal_fallback={"lzero": 256, "tzero": 420}, + timestep=7.5, + return_diagnostics=True, + ) expected_time = 420 * (diag["wavelengths_km"][0] / 256) ** 0.81 assert diag["temporal_selection"]["source"] == "configured_fallback" assert diag["parameters"]["reference_time"] == pytest.approx(expected_time) - np.testing.assert_allclose(diag["temporal_model"]["requested_times"], - 420 * (diag["wavelengths_km"] / 256) ** 0.81) + np.testing.assert_allclose( + diag["temporal_model"]["requested_times"], + 420 * (diag["wavelengths_km"] / 256) ** 0.81, + ) assert not diag["safeguards"]["low_rain_fraction_fallback"] assert np.isfinite(result).all() def test_configured_fallback_preserves_valid_observation(reference): - result, diag = _call(reference, temporal_fallback={"lzero": 256, "tzero": 420}, - return_diagnostics=True) + result, diag = _call( + reference, + temporal_fallback={"lzero": 256, "tzero": 420}, + return_diagnostics=True, + ) assert diag["temporal_selection"]["source"] == "observed" original = _call(reference) np.testing.assert_array_equal(result, original) def test_temporal_fallback_does_not_replace_spatial_estimates(reference, monkeypatch): - monkeypatch.setattr(parametric, "fit_power_law_spectrum", - lambda *args: {"beta_1": np.nan, "beta_2": np.nan}) + monkeypatch.setattr( + parametric, + "fit_power_law_spectrum", + lambda *args: {"beta_1": np.nan, "beta_2": np.nan}, + ) with pytest.raises(ValueError, match="Unavailable spectral"): _call(reference, temporal_fallback={"lzero": 256, "tzero": 420}) -@pytest.mark.parametrize("fallback", [{}, {"lzero": 0, "tzero": 420}, - {"lzero": 256, "tzero": float("nan")}]) +@pytest.mark.parametrize( + "fallback", [{}, {"lzero": 0, "tzero": 420}, {"lzero": 256, "tzero": float("nan")}] +) def test_invalid_temporal_fallback(reference, fallback): with pytest.raises(ValueError): _call(reference, temporal_fallback=fallback) From 51b8945e07d7ee81e8dfdafdf3d846981f949e47 Mon Sep 17 00:00:00 2001 From: alanseed Date: Thu, 5 Nov 2026 10:18:14 +1100 Subject: [PATCH 09/17] pysteps_param xarray model --- doc/source/user_guide/parametric_nowcast.rst | 98 ++++-- examples/plot_parametric_nowcast.py | 43 ++- examples/plot_parametric_nowcast_swiss.py | 164 +++++++-- pysteps/nowcasts/interface.py | 39 +-- pysteps/nowcasts/parametric.py | 197 +++++++++-- .../data/compare_parametric_nowcast_paper.py | 39 ++- .../data/compare_parametric_paper_fields.py | 8 +- pysteps/tests/test_nowcasts_parametric.py | 319 +++++++++++++++++- 8 files changed, 777 insertions(+), 130 deletions(-) diff --git a/doc/source/user_guide/parametric_nowcast.rst b/doc/source/user_guide/parametric_nowcast.rst index fff85a2c..34c93d17 100644 --- a/doc/source/user_guide/parametric_nowcast.rst +++ b/doc/source/user_guide/parametric_nowcast.rst @@ -6,39 +6,93 @@ observations and returns an ensemble in mm/h. Transformation, motion estimation, cascade decomposition, statistical estimation, and forecast evolution happen inside the call. No intermediate files or database are needed. -For observations stored in ``rain`` as a floating NumPy array ``(3, y, x)``, -ordered oldest to newest:: +For observations stored in a v2 ``dataset`` with three time slices, ordered +oldest to newest:: from pysteps import nowcasts forecast = nowcasts.get_method("parametric") ensemble, diagnostics = forecast( - rain, + dataset, timesteps=12, n_ens_members=20, - kmperpixel=1.0, - timestep=5.0, seed=42, return_diagnostics=True, ) + rain = ensemble[ensemble.attrs["precip_var"]] -The result has shape ``(20, 12, y, x)``. Lead index zero is the first forecast, -five minutes after the newest observation. ``timestep`` is in minutes and can -be fractional, for example 7.5 for 450-second observations. ``kmperpixel`` is -the isotropic pixel spacing. Both are required even when motion is supplied. -Input must be finite, nonnegative rain rates with consistent geometry and -cadence. Masked arrays and missing pixels are rejected. - -A fixed velocity field can be supplied as the second argument or as -``velocity=velocity``. Its shape is ``(2, y, x)`` with x/y components in pixels -per observation timestep. Otherwise dense Lucas-Kanade motion is estimated -from the latest two transformed observations. Statistical parameters and motion -remain fixed throughout each forecast. See :ref:`sphx_glr_auto_examples_plot_parametric_nowcast.py` -for a complete runnable synthetic example. -For a radar-data walkthrough using the same Swiss sequence as the STEPS tutorial, -see :ref:`sphx_glr_auto_examples_plot_parametric_nowcast_swiss.py`. It shows -importing three observations, selecting a complete domain, generating the -ensemble, and plotting means, members, and exceedance probabilities. +The precipitation variable must have dimensions ``(time, y, x)``, in any order, +and its name must be stored in ``dataset.attrs["precip_var"]``. Its ``units`` +attribute must be ``mm/h`` and ``transform`` must be absent or ``None``. +Observations must be floating point, finite, and nonnegative. Missing pixels +are rejected, including when the latest observation is dry. + +The spatial coordinates must be regularly spaced with square pixels, ``units`` +of ``m`` or ``km``, and signed ``stepsize`` attributes matching the coordinates. +Descending y coordinates are supported without reversing the data. The three +observation timestamps must be increasing, regularly spaced datetime64 values. +``time.attrs["stepsize"]`` must give their interval in seconds. Pixel size and +cadence are derived from these coordinates. Optional ``kmperpixel`` (km) and +``timestep`` (minutes) overrides must agree with the dataset. In particular, +450-second observations retain their physical interval of 7.5 minutes. + +The result is an xarray dataset whose precipitation has dimensions +``(ens_number, time, y, x)``, with ensemble members numbered from one. It +preserves spatial coordinates and projection metadata and adds a scalar +``forecast_reference_time`` equal to the newest observation time. Forecast +valid times start one observation interval later. Outputs are thresholded +linear rain rates in mm/h, with the occurrence threshold and zero dry value +recorded in their attributes. Untransformed output omits the ``transform`` +attribute, allowing native xarray NetCDF serialization. The numerical engine +loads arrays eagerly. + +Fixed motion can be supplied in ``velocity_x`` and ``velocity_y`` dataset +variables, each with dimensions ``(y, x)`` in any order. Their values are x/y +pixel displacements per observation interval. Velocity units can be omitted, +as in the upstream motion methods, or recorded as ``pixels/timestep``. +Both components must be present together and finite. Otherwise dense +Lucas-Kanade motion is estimated from the latest two transformed observations. +Statistical parameters and motion remain fixed throughout each forecast. + +See :ref:`sphx_glr_auto_examples_plot_parametric_nowcast.py` for a runnable +synthetic dataset example. For the complete Swiss radar workflow, including +clipping, aggregation, plotting, and a lossless NetCDF round trip, see +:ref:`sphx_glr_auto_examples_plot_parametric_nowcast_swiss.py`. The common +dataset model is described in :py:mod:`pysteps.io.importers`. + +The Swiss example also writes ``parametric_nowcast_swiss_member0.gif`` into +``rcparams.outputs["path_outputs"]`` (the current directory by default). It +plays the three QPE observations followed by the six forecasts of member index +zero, which has ``ens_number=1``. All frames share a map and colour scale, and +titles identify the data source and valid time. QPE retains measured rain +rates, while forecasts apply the configured occurrence threshold. The frames +show the actual five-minute fields, with no temporal interpolation. + +NetCDF export +-------------- + +The returned dataset can be exported directly with xarray:: + + import xarray as xr + + precip_var = ensemble.attrs["precip_var"] + ensemble.to_netcdf( + "forecast.nc", + engine="netcdf4", + encoding={precip_var: {"zlib": True, "complevel": 1}}, + ) + with xr.open_dataset("forecast.nc", engine="netcdf4") as reopened: + xr.testing.assert_identical(ensemble, reopened.load()) + +This uses lossless compression and retains float64 rainfall, member identifiers, +valid times, forecast reference time, and available geographic metadata. +Projection metadata are preserved as supplied; this does not add missing CF +projection mappings. The older ``pysteps.io.exporters`` interface still expects +arrays and a separate metadata dictionary. + +Callbacks continue to receive one NumPy array per lead. Native NetCDF export +here writes the assembled dataset; incremental writing through the older +exporter remains a separate interface. Reference settings and regional overrides ----------------------------------------- diff --git a/examples/plot_parametric_nowcast.py b/examples/plot_parametric_nowcast.py index c17c84c1..0d24bac7 100644 --- a/examples/plot_parametric_nowcast.py +++ b/examples/plot_parametric_nowcast.py @@ -4,17 +4,18 @@ This self-contained example builds three synthetic observations, then lets the parametric engine estimate motion and parameters and generate an ensemble. -It needs no radar archive, database, or labelled-array dependency. +It needs no radar archive or database. """ import matplotlib.pyplot as plt import numpy as np +import xarray as xr from pysteps import nowcasts from pysteps.noise.parametric import generate_stochastic_field # Construct an illustrative rain-rate field and a translating observation -# history. In an application, pass three measured rain-rate arrays instead. +# history. In an application, use a dataset of measured rain-rate observations. transformed = generate_stochastic_field( (128, 128), dict(beta_1=-2.4, beta_2=-3.1, rain_fraction=0.2, mean_db=6.0, stdev_db=3.0), @@ -33,20 +34,44 @@ ] ) +# Use an illustrative local Cartesian grid in kilometres. Time stepsize is +# expressed in seconds; the forecast derives its physical cadence and spacing. +dataset = xr.Dataset( + { + "precip_intensity": ( + ("time", "y", "x"), + observations, + {"units": "mm/h", "transform": None}, + ) + }, + coords={ + "time": ( + "time", + np.array( + ["2026-01-01T00:00", "2026-01-01T00:05", "2026-01-01T00:10"], + dtype="datetime64[s]", + ), + {"stepsize": 300}, + ), + "x": ("x", np.arange(128) + 0.5, {"units": "km", "stepsize": 1.0}), + "y": ("y", np.arange(128) + 0.5, {"units": "km", "stepsize": 1.0}), + }, + attrs={"precip_var": "precip_intensity"}, +) + ############################################################################### # Forecast directly in mm/h. Motion is estimated internally from the latest # two fields; no transformed input or stored cascade/parameter state is needed. forecast = nowcasts.get_method("parametric") ensemble, diagnostics = forecast( - observations, + dataset, timesteps=6, n_ens_members=4, - kmperpixel=1.0, - timestep=5.0, seed=42, return_diagnostics=True, ) -print("Output shape:", ensemble.shape) +rain_forecast = ensemble[ensemble.attrs["precip_var"]] +print("Output dimensions:", rain_forecast.sizes) print("Selected generation parameters:", diagnostics["parameters"]) print("Moment source:", diagnostics["safeguards"]["moment_source"]) @@ -55,7 +80,11 @@ fig, axes = plt.subplots(1, 3, figsize=(12, 4), constrained_layout=True) for ax, field, title in zip( axes, - [observations[-1], ensemble[0, -1], ensemble[:, -1].mean(axis=0)], + [ + observations[-1], + rain_forecast.isel(ens_number=0, time=-1), + rain_forecast.isel(time=-1).mean("ens_number"), + ], ["Newest observation", "Member 1, +30 min", "Ensemble mean, +30 min"], ): image = ax.imshow(field, origin="lower", vmin=0, vmax=20, cmap="Blues") diff --git a/examples/plot_parametric_nowcast_swiss.py b/examples/plot_parametric_nowcast_swiss.py index 582dda44..4366b8fe 100644 --- a/examples/plot_parametric_nowcast_swiss.py +++ b/examples/plot_parametric_nowcast_swiss.py @@ -14,14 +14,18 @@ from datetime import datetime from pprint import pprint +from pathlib import Path +from tempfile import TemporaryDirectory import matplotlib.pyplot as plt +from matplotlib.animation import FuncAnimation, PillowWriter import numpy as np +import xarray as xr from pysteps import io, nowcasts, rcparams -from pysteps.postprocessing.ensemblestats import excprob from pysteps.utils import conversion, dimension from pysteps.visualization import basemaps, plot_precip_field +from pysteps.xarray_helpers import geodata_from_dataset n_ens_members = 20 n_leadtimes = 6 @@ -48,8 +52,9 @@ num_prev_files=2, ) importer = io.get_method(source["importer"], "importer") -rain, _, metadata = io.read_timeseries(filenames, importer, **source["importer_kwargs"]) -rain, metadata = conversion.to_rainrate(rain, metadata) +observations = io.read_timeseries(filenames, importer, **source["importer_kwargs"]) +observations = conversion.to_rainrate(observations) +precip_var = observations.attrs["precip_var"] ############################################################################### # Prepare a complete spatial domain @@ -62,11 +67,16 @@ # to limit computation and memory. Missing pixels are not replaced with zero. extent = (450000, 750000, 0, 300000) # left, right, bottom, top in metres -rain, metadata = dimension.clip_domain(rain, metadata, extent) -rain, metadata = dimension.aggregate_fields_space(rain, metadata, 2000) -if rain.shape[0] != 3 or not np.isfinite(rain).all() or np.any(rain < 0): +observations = dimension.clip_domain(observations, extent) +observations = dimension.aggregate_fields_space(observations, 2000) +rain = observations[precip_var] +if ( + observations.sizes["time"] != 3 + or not np.isfinite(rain.values).all() + or (rain.values < 0).any() +): raise ValueError("The tutorial requires three complete nonnegative rain fields") -kmperpixel = metadata["xpixelsize"] / 1000.0 +geodata = geodata_from_dataset(observations) # Use the same geographic metadata for observations and forecasts. A coarse @@ -74,15 +84,15 @@ def make_map(subplot=(1, 1, 1)): """Create the geographic axes before plotting the precipitation raster.""" return basemaps.plot_geography( - metadata["projection"], extent, scale="110m", subplot=subplot + observations.attrs["projection"], extent, scale="110m", subplot=subplot ) plt.figure() plot_precip_field( - rain[-1], + rain.isel(time=-1).values, ax=make_map(), - geodata=metadata, + geodata=geodata, axis="off", title="Swiss radar observation, 31 January 2017, 12:00 UTC", ) @@ -93,8 +103,9 @@ def make_map(subplot=(1, 1, 1)): # -------------------------------- # # Pass linear rain rates directly: no external dBR transform or motion estimate -# is needed. The returned ensemble is also in mm/h, with shape -# (member, lead, y, x). Its first lead is +5 minutes and its last is +30 minutes. +# is needed. The returned dataset contains rain rates in mm/h with dimensions +# (ens_number, time, y, x). Its first valid time is 12:05 and its last is 12:30. +# Pixel size and cadence are derived from the observation coordinates. # Fixed motion and estimated statistical parameters are shared across leads; # each member has its own reproducible random stream. # @@ -105,16 +116,15 @@ def make_map(subplot=(1, 1, 1)): forecast = nowcasts.get_method("parametric") ensemble, diagnostics = forecast( - rain, + observations, timesteps=n_leadtimes, n_ens_members=n_ens_members, - kmperpixel=kmperpixel, - timestep=timestep, rain_threshold=rain_threshold, seed=seed, return_diagnostics=True, ) -print("Ensemble shape:", ensemble.shape) +forecast_rain = ensemble[ensemble.attrs["precip_var"]] +print("Ensemble dimensions:", dict(forecast_rain.sizes)) print("Generation parameters:") pprint(diagnostics["parameters"]) print("Marginal moment source:", diagnostics["safeguards"]["moment_source"]) @@ -127,13 +137,16 @@ def make_map(subplot=(1, 1, 1)): # Averaging realizations smooths rainfall features whose position or intensity # differs between members. Individual members retain more spatial variability. -lead_minutes = n_leadtimes * timestep -ensemble_mean = ensemble[:, -1].mean(axis=0) +lead_minutes = float( + (ensemble.time.values[-1] - ensemble.forecast_reference_time.values) + / np.timedelta64(1, "m") +) +ensemble_mean = forecast_rain.isel(time=-1).mean("ens_number", skipna=False) plt.figure() plot_precip_field( - ensemble_mean, + ensemble_mean.values, ax=make_map(), - geodata=metadata, + geodata=geodata, axis="off", title=f"Parametric ensemble mean (+{lead_minutes:g} min)", ) @@ -147,9 +160,9 @@ def make_map(subplot=(1, 1, 1)): for member in range(4): ax = make_map((2, 2, member + 1)) ax = plot_precip_field( - ensemble[member, -1], + forecast_rain.isel(ens_number=member, time=-1).values, ax=ax, - geodata=metadata, + geodata=geodata, colorbar=False, axis="off", ) @@ -166,12 +179,14 @@ def make_map(subplot=(1, 1, 1)): # not an independently calibrated probability product. probability_threshold = 1.0 # mm/h -probabilities = excprob(ensemble[:, -1], probability_threshold) +probabilities = (forecast_rain.isel(time=-1) >= probability_threshold).mean( + "ens_number" +) plt.figure() plot_precip_field( - probabilities, + probabilities.values, ax=make_map(), - geodata=metadata, + geodata=geodata, axis="off", ptype="prob", units="mm/h", @@ -180,4 +195,103 @@ def make_map(subplot=(1, 1, 1)): ) plt.show() +############################################################################### +# Animate observations followed by the first ensemble member +# ---------------------------------------------------------- +# +# Keep the same grid and colour scale for the three QPE observations and six +# forecast fields. Index zero selects the first member, whose ens_number +# coordinate is one. The title distinguishes observations from forecasts and +# displays actual valid times. QPE retains the measured rain rates; forecasts +# apply the engine's 1 mm/h occurrence threshold. No intermediate fields are +# interpolated. Pause briefly at the last observation and the last forecast. + +animation_member_index = 0 +animation_fields = np.concatenate( + [rain.values, forecast_rain.isel(ens_number=animation_member_index).values] +) +animation_times = np.concatenate([observations.time.values, ensemble.time.values]) +animation_observations = observations.sizes["time"] +animation_member_number = int(ensemble.ens_number.values[animation_member_index]) +animation_figure = plt.figure(figsize=(7, 7)) +animation_axis = make_map() +plot_precip_field(animation_fields[0], ax=animation_axis, geodata=geodata, axis="off") +animation_raster = animation_axis.images[-1] +animation_figure.text( + 0.5, + 0.025, + f"Forecast occurrence threshold: {rain_threshold:g} mm/h. QPE retains measured rates.", + ha="center", + fontsize=9, +) + + +def update_animation(frame): + """Update rainfall and its timestamp while retaining geography and colours.""" + animation_raster.set_data(animation_fields[frame]) + valid_time = np.datetime_as_string(animation_times[frame], unit="m").replace( + "T", " " + ) + if frame < animation_observations: + label = "QPE observation" + else: + lead = float( + (animation_times[frame] - ensemble.forecast_reference_time.values) + / np.timedelta64(1, "m") + ) + label = ( + f"Forecast member index {animation_member_index} " + f"(ens_number={animation_member_number}), +{lead:g} min" + ) + animation_axis.set_title(f"{label}\n{valid_time} UTC") + return (animation_raster, animation_axis.title) + + +animation_frames = ( + list(range(animation_observations)) + + [animation_observations - 1] + + list(range(animation_observations, len(animation_times))) + + [len(animation_times) - 1] * 2 +) +animation = FuncAnimation( + animation_figure, + update_animation, + frames=animation_frames, + interval=800, + repeat=True, + cache_frame_data=False, +) +animation_path = ( + Path(rcparams.outputs["path_outputs"]).expanduser() + / "parametric_nowcast_swiss_member0.gif" +) +animation_path.parent.mkdir(parents=True, exist_ok=True) +animation.save(animation_path, writer=PillowWriter(fps=1.25), dpi=110) +plt.close(animation_figure) +print("QPE and first-member animation:", animation_path.resolve()) + +############################################################################### +# Export the complete forecast dataset +# ------------------------------------- +# +# The native xarray NetCDF writer preserves the ensemble and time coordinates, +# geographic information, and rainfall attributes. Lossless compression retains +# the float64 values exactly. A temporary file makes the example repeatable; +# replace output_path with a persistent location to keep the forecast. +# This preserves the available projection metadata without adding a CF grid +# mapping for projections that the upstream converter does not yet support. + +with TemporaryDirectory(prefix="pysteps-parametric-swiss-") as output_directory: + output_path = Path(output_directory) / "parametric_nowcast_swiss.nc" + ensemble.to_netcdf( + output_path, + engine="netcdf4", + encoding={precip_var: {"zlib": True, "complevel": 1}}, + ) + with xr.open_dataset(output_path, engine="netcdf4") as reopened: + xr.testing.assert_identical(ensemble, reopened.load()) + print( + "NetCDF round trip preserved all forecast values, coordinates, and attributes." + ) + # sphinx_gallery_thumbnail_number = 3 diff --git a/pysteps/nowcasts/interface.py b/pysteps/nowcasts/interface.py index 7e72b3c6..552befb5 100644 --- a/pysteps/nowcasts/interface.py +++ b/pysteps/nowcasts/interface.py @@ -5,29 +5,22 @@ Interface for the nowcasts module. It returns a callable function for computing nowcasts. -The methods in the nowcasts module implement the following interface: - - ``forecast(precip, velocity, timesteps, **keywords)`` - -where precip is a (m,n) array with input precipitation field to be advected and -velocity is a (2,m,n) array containing the x- and y-components of -the m x n advection field. -timesteps can be an integer or a list. An integer specifies the number of time -steps to forecast, where the output time step is taken from the inputs. -Irregular time steps can be given in a list. -The interface accepts optional keyword arguments specific to the given method. - -The output depends on the type of the method. -For deterministic methods, the output is a three-dimensional array of shape -(num_timesteps,m,n) containing a time series of nowcast precipitation fields. -For stochastic methods that produce an ensemble, the output is a -four-dimensional array of shape (num_ensemble_members,num_timesteps,m,n). -The time step of the output is taken from the inputs. - -The ``parametric`` method additionally estimates motion when velocity is omitted. -It requires exactly three complete rain-rate observations in mm/h, pixel size -in km, and a timestep in minutes; its output is also in mm/h. Its first release -accepts integer timesteps only. See its method-specific forecast documentation. +The v2 nowcasting interface takes an xarray dataset: + + ``forecast(dataset, timesteps, **keywords)`` + +The dataset contains precipitation, spatial coordinates, observation times, +projection metadata and, when supplied, velocity_x and velocity_y variables. +The global ``precip_var`` attribute identifies the precipitation variable. +The output dataset contains dimensions (time, y, x) for deterministic methods +or (ens_number, time, y, x) for ensemble methods. Method-specific forecast +functions document their input history and optional settings. + +The ``parametric`` method requires exactly three complete, untransformed +rain-rate observations in mm/h on a regular grid with square pixels. Pixel +size and cadence are derived from coordinates; the time stepsize is in seconds. +It estimates motion if velocity variables are absent and returns rain rates in +mm/h. Its first release accepts integer timesteps only. .. autosummary:: :toctree: ../generated/ diff --git a/pysteps/nowcasts/parametric.py b/pysteps/nowcasts/parametric.py index 00d497e4..c93285c7 100644 --- a/pysteps/nowcasts/parametric.py +++ b/pysteps/nowcasts/parametric.py @@ -14,6 +14,7 @@ from copy import deepcopy import numpy as np +import xarray as xr from scipy.ndimage import gaussian_filter from pysteps import motion @@ -31,6 +32,7 @@ from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic from pysteps.utils.statistics import compute_field_statistics from pysteps.utils.transformation import dB_transform +from pysteps.xarray_helpers import convert_output_to_xarray_dataset from ._cascade_advection import advect_field, prepare_advection_coordinates @@ -334,9 +336,120 @@ def _advance( return field, diagnostics +def _velocity_from_dataset(dataset): + """Read fixed pixel displacements without xarray coordinate alignment.""" + names = ("velocity_x", "velocity_y") + present = [name in dataset.data_vars for name in names] + if not any(present): + return None + if not all(present): + raise ValueError("Supply both velocity_x and velocity_y, or neither") + components = [] + for name in names: + field = dataset[name] + if set(field.dims) != {"y", "x"} or field.ndim != 2: + raise ValueError(f"{name} must have dimensions (y, x)") + if field.attrs.get("units") not in ( + None, + "pixels/timestep", + "pixels / timestep", + ): + raise ValueError(f"{name} must be in pixels per observation timestep") + values = field.transpose("y", "x").values + if not np.issubdtype(values.dtype, np.number) or np.iscomplexobj(values): + raise ValueError(f"{name} must contain finite real values") + if not np.isfinite(values).all(): + raise ValueError(f"{name} must contain finite real values") + components.append(values) + return np.array(components, dtype=float, copy=True) + + +def _prepare_dataset(dataset, kmperpixel, timestep): + """Validate the physical grid and copy observations into canonical order.""" + if not isinstance(dataset, xr.Dataset): + raise ValueError("dataset must be an xarray.Dataset") + precip_var = dataset.attrs.get("precip_var") + if not isinstance(precip_var, str) or precip_var not in dataset.data_vars: + raise ValueError( + "dataset.attrs['precip_var'] must name a precipitation variable" + ) + precip = dataset[precip_var] + if precip.ndim != 3 or set(precip.dims) != {"time", "y", "x"}: + raise ValueError("Precipitation must have dimensions (time, y, x)") + precip = precip.transpose("time", "y", "x") + if ( + precip.shape[0] != 3 + or min(precip.shape[1:]) < 2 + or max(precip.shape[1:]) < 3 + or not np.issubdtype(precip.dtype, np.floating) + ): + raise ValueError("Precipitation must be floating point with shape (3, y, x)") + if precip.attrs.get("units") != "mm/h" or precip.attrs.get("transform") is not None: + raise ValueError("Precipitation must be untransformed rain rates in mm/h") + rain = precip.values + if not np.isfinite(rain).all() or np.any(rain < 0): + raise ValueError( + "Precipitation must contain only finite nonnegative rain rates" + ) + + spacings = [] + for axis in ("x", "y"): + if axis not in dataset.coords or dataset[axis].dims != (axis,): + raise ValueError(f"A one-dimensional {axis} coordinate is required") + coord = dataset[axis] + if not np.issubdtype(coord.dtype, np.number) or np.iscomplexobj(coord): + raise ValueError(f"{axis} coordinates must be finite real numbers") + values = np.asarray(coord.values, dtype=float) + differences = np.diff(values) + step = differences[0] + if ( + not np.isfinite(values).all() + or step == 0 + or not np.allclose(differences, step, rtol=1e-7, atol=abs(step) * 1e-10) + ): + raise ValueError( + f"{axis} coordinates must be regularly spaced and monotonic" + ) + declared = _finite(f"{axis}.stepsize", coord.attrs.get("stepsize")) + if not np.isclose(declared, step, rtol=1e-7, atol=abs(step) * 1e-10): + raise ValueError(f"{axis}.stepsize disagrees with coordinate spacing") + units = coord.attrs.get("units") + if units not in ("m", "km"): + raise ValueError(f"{axis} coordinate units must be 'm' or 'km'") + spacings.append(abs(step) / (1000.0 if units == "m" else 1.0)) + if not np.isclose(*spacings, rtol=1e-7, atol=0): + raise ValueError("The parametric forecast requires square pixels") + + if "time" not in dataset.coords or dataset.time.dims != ("time",): + raise ValueError("A one-dimensional datetime time coordinate is required") + times = dataset.time.values + if not np.issubdtype(times.dtype, np.datetime64) or np.isnat(times).any(): + raise ValueError("time must contain valid datetime64 observation times") + intervals = np.diff(times) / np.timedelta64(1, "s") + seconds = intervals[0] + if seconds <= 0 or not np.allclose(intervals, seconds, rtol=1e-9, atol=1e-9): + raise ValueError("Observation times must be increasing and regularly spaced") + declared = _positive("time.stepsize", dataset.time.attrs.get("stepsize")) + if not np.isclose(declared, seconds, rtol=1e-9, atol=1e-9): + raise ValueError("time.stepsize must match observation spacing in seconds") + derived_timestep = float(seconds / 60.0) + for name, supplied, derived in ( + ("kmperpixel", kmperpixel, spacings[0]), + ("timestep", timestep, derived_timestep), + ): + if supplied is not None and not np.isclose( + _positive(name, supplied), derived, rtol=1e-7, atol=0 + ): + raise ValueError(f"{name} disagrees with dataset coordinates") + + velocity = _velocity_from_dataset(dataset) + prepared = dataset.copy(deep=True) + prepared[precip_var] = precip.copy(deep=True).astype(float) + return prepared, velocity, float(spacings[0]), derived_timestep + + def forecast( - precip, - velocity=None, + dataset, timesteps=1, *, n_ens_members=24, @@ -361,19 +474,24 @@ def forecast( Parameters ---------- - precip: numpy.ndarray - Floating-point, finite nonnegative rain rates in mm/h, shape (3, y, x), - ordered oldest first at a regular interval. Grids must be complete. - velocity: numpy.ndarray, optional - Fixed x/y motion, shape (2, y, x), in pixels per observation timestep. - If omitted, estimate Lucas-Kanade motion from the latest two dBR fields. + dataset: xarray.Dataset + Three complete, finite, nonnegative rain-rate observations in mm/h, + ordered oldest first. The precipitation variable is named by the global + ``precip_var`` attribute and has dimensions (time, y, x), in any order. + Time must be datetime64 and regularly spaced; its ``stepsize`` is in + seconds. Spatial coordinates must be regular with square pixels, units + 'm' or 'km', and signed ``stepsize`` attributes. Optional velocity_x and + velocity_y variables have dimensions (y, x) and contain displacements + in pixels per observation timestep. If absent, estimate Lucas-Kanade + motion from the latest two dBR fields. timesteps: int, optional Positive number of leads at the observation interval. Lead zero omitted. n_ens_members, n_cascade_levels: int, optional Ensemble size (24) and Gaussian cascade levels (6, minimum 3). - kmperpixel, timestep: float - Required positive isotropic pixel size in km and interval in minutes. - Fractional minutes are preserved. + kmperpixel, timestep: float, optional + Pixel size in km and interval in minutes, derived from coordinates by + default. Explicit values must agree with the dataset. Fractional + minutes are preserved. rain_threshold, scale_break: float, optional Rain occurrence threshold in mm/h (1) and spectral break in km (20). zerovalue: float, optional @@ -414,8 +532,11 @@ def forecast( Returns ------- - ensemble: ndarray or None - Float64 mm/h array (member, lead, y, x), or None if return_output=False. + ensemble: xarray.Dataset or None + Float64 mm/h precipitation with dimensions (ens_number, time, y, x), + retaining spatial coordinates and projection metadata. Valid times + start one observation interval after forecast_reference_time. + None if return_output=False. diagnostics: dict, optional When requested, return (ensemble, diagnostics): raw estimates, selected and bounded generation parameters, settings, motion, seed information, @@ -431,21 +552,14 @@ def forecast( by innovations. A newest field with no rain at the occurrence threshold gives a dry ensemble even if older fields were wet. Earlier dry fields use zero anomalies. Unavailable positive-rain moments raise rather than becoming - dry forecasts. No database, xarray, or additional runtime package is needed. + dry forecasts. Dataset data are loaded eagerly; the numerical engine uses + NumPy arrays. No database is needed. """ - if ( - not isinstance(precip, np.ndarray) - or np.ma.isMaskedArray(precip) - or not np.issubdtype(precip.dtype, np.floating) - or precip.ndim != 3 - or precip.shape[0] != 3 - or min(precip.shape[1:]) < 2 - or max(precip.shape[1:]) < 3 - ): - raise ValueError("precip must be a floating NumPy array with shape (3, y, x)") - if not np.isfinite(precip).all() or np.any(precip < 0): - raise ValueError("precip must contain only finite nonnegative rain rates") - rain = np.array(precip, dtype=float, copy=True) + dataset, velocity, kmperpixel, timestep = _prepare_dataset( + dataset, kmperpixel, timestep + ) + precip_var = dataset.attrs["precip_var"] + rain = dataset[precip_var].values shape = rain.shape[1:] timesteps = _integer("timesteps", timesteps) n_ens_members = _integer("n_ens_members", n_ens_members) @@ -543,7 +657,16 @@ def forecast( ), generation=[[] for _ in range(n_ens_members)] if return_diagnostics else None, ) - transformed, _ = dB_transform(rain, threshold=rain_threshold, zerovalue=zerovalue) + transformed_dataset = dB_transform( + dataset, threshold=rain_threshold, zerovalue=zerovalue + ) + transformed = transformed_dataset[precip_var].values + # Untransformed rates omit the transform attribute so native NetCDF export + # does not encounter a non-serializable None value. + dataset[precip_var].attrs.pop("transform", None) + dataset[precip_var].attrs.update( + units="mm/h", threshold=rain_threshold, zerovalue=0.0 + ) statistics = compute_field_statistics( transformed[-1], threshold, estimation["lower_truncation"] ) @@ -561,12 +684,22 @@ def forecast( output[:, lead] = fields if callback is not None: callback(fields) + if output is not None: + output = convert_output_to_xarray_dataset(dataset, timesteps, output) return (output, diagnostics) if return_diagnostics else output if velocity is None: - velocity = np.asarray( - motion.get_method("LK")(transformed[-2:], **motion_settings), dtype=float + motion_dataset = motion.get_method("LK")( + transformed_dataset.isel(time=slice(-2, None)), **motion_settings ) - if velocity.shape != (2, *shape) or not np.isfinite(velocity).all(): + try: + velocity = ( + _velocity_from_dataset(motion_dataset) + if isinstance(motion_dataset, xr.Dataset) + else None + ) + except ValueError as error: + raise ValueError("Motion estimator returned invalid velocity") from error + if velocity is None or velocity.shape != (2, *shape): raise ValueError("Motion estimator returned invalid velocity") diagnostics["velocity"] = velocity.copy() bp_filter = filter_gaussian(shape, n_cascade_levels) @@ -683,4 +816,6 @@ def forecast( output[:, lead] = fields if callback is not None: callback(fields.copy()) + if output is not None: + output = convert_output_to_xarray_dataset(dataset, timesteps, output) return (output, diagnostics) if return_diagnostics else output diff --git a/pysteps/tests/data/compare_parametric_nowcast_paper.py b/pysteps/tests/data/compare_parametric_nowcast_paper.py index c417213e..a0b990ea 100644 --- a/pysteps/tests/data/compare_parametric_nowcast_paper.py +++ b/pysteps/tests/data/compare_parametric_nowcast_paper.py @@ -12,6 +12,7 @@ import subprocess import numpy as np +import xarray as xr from scipy.ndimage import gaussian_filter from pysteps import motion @@ -143,9 +144,38 @@ def main(): with np.load(path) as saved: rain = saved["rain"][[1, 0, 2]].astype(float) metadata = json.loads((folder / "input_metadata.json").read_text()) + observation_times = np.array( + [metadata["rain"][i]["valid_time"] for i in (1, 0, 2)], + dtype="datetime64[ns]", + ) + # The archives contain raster values, not georeferencing. A local 1 km + # Cartesian grid retains their array orientation for numerical comparison. + dataset = xr.Dataset( + {"precip_intensity": (("time", "y", "x"), rain, {"units": "mm/h"})}, + coords={ + "time": ("time", observation_times, {"stepsize": 300}), + "x": ( + "x", + (np.arange(rain.shape[2]) + 0.5) * 1000, + {"units": "m", "stepsize": 1000}, + ), + "y": ( + "y", + (np.arange(rain.shape[1]) + 0.5) * 1000, + {"units": "m", "stepsize": 1000}, + ), + }, + attrs={"precip_var": "precip_intensity"}, + ) transformed = source["DBTransformer"](threshold=1).transform(rain) - velocity = motion.get_method("LK")( - transformed[-2:], **parametric._MOTION_DEFAULTS + motion_input = dataset.isel(time=slice(-2, None)).copy(deep=True) + motion_input["precip_intensity"].data = transformed[-2:] + motion_input["precip_intensity"].attrs["transform"] = "dB" + motion_output = motion.get_method("LK")( + motion_input, **parametric._MOTION_DEFAULTS + ) + velocity = np.stack( + [motion_output["velocity_x"].values, motion_output["velocity_y"].values] ) cfg = config(rain.shape[1:]) cfg["n_forecasts"] = args.leads @@ -165,8 +195,8 @@ def main(): rain, velocity, cfg, parameters, seed, 2, args.leads ) # Exercise the public call including its internal motion estimation. - coupled, diagnostics = parametric.forecast( - rain, + coupled_dataset, diagnostics = parametric.forecast( + dataset, timesteps=args.leads, n_ens_members=2, kmperpixel=1, @@ -174,6 +204,7 @@ def main(): seed=seed, return_diagnostics=True, ) + coupled = coupled_dataset[coupled_dataset.attrs["precip_var"]].values np.testing.assert_array_equal(velocity, diagnostics["velocity"]) for key in ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db"): np.testing.assert_allclose( diff --git a/pysteps/tests/data/compare_parametric_paper_fields.py b/pysteps/tests/data/compare_parametric_paper_fields.py index fe2e8320..a62c7966 100644 --- a/pysteps/tests/data/compare_parametric_paper_fields.py +++ b/pysteps/tests/data/compare_parametric_paper_fields.py @@ -58,9 +58,13 @@ def main(): for path in paths: with np.load(path) as saved: rain = saved["rain"] - transformed, metadata = dB_transform( - rain.astype(float), threshold=1.0, zerovalue=-0.1 + dataset = xr.Dataset( + {"precip_intensity": (("y", "x"), rain.astype(float), {"units": "mm/h"})}, + attrs={"precip_var": "precip_intensity"}, ) + transformed_dataset = dB_transform(dataset, threshold=1.0, zerovalue=-0.1) + transformed = transformed_dataset["precip_intensity"].values + metadata = transformed_dataset["precip_intensity"].attrs source_spectrum, source_model = namespace["power_spectrum_1D"](transformed, 15) source_statistics = namespace["compute_field_stats"]( transformed, metadata, fit_truncated=True, lower_truncation=1.25 diff --git a/pysteps/tests/test_nowcasts_parametric.py b/pysteps/tests/test_nowcasts_parametric.py index b5baa342..939d4592 100644 --- a/pysteps/tests/test_nowcasts_parametric.py +++ b/pysteps/tests/test_nowcasts_parametric.py @@ -6,6 +6,7 @@ import numpy as np import pytest +import xarray as xr from scipy.ndimage import gaussian_filter from pysteps import nowcasts @@ -19,6 +20,73 @@ from pysteps.timeseries.autoregression import estimate_ar_params_yw +def _dataset(rain, velocity=None, timestep=5.0, kmperpixel=1.0): + """Attach physical coordinates without changing the saved reference arrays.""" + dataset = xr.Dataset( + { + "precip_intensity": ( + ("time", "y", "x"), + rain.copy(), + { + "units": "mm/h", + "transform": None, + "threshold": 0.1, + "zerovalue": 0.0, + "accutime": timestep, + }, + ), + }, + coords={ + "time": ( + "time", + np.datetime64("2026-01-01T00:00:00", "ns") + + np.arange(rain.shape[0]) + * np.timedelta64(round(timestep * 60e9), "ns"), + {"stepsize": timestep * 60}, + ), + "x": ( + "x", + (np.arange(rain.shape[2]) + 0.5) * kmperpixel * 1000, + {"units": "m", "stepsize": kmperpixel * 1000}, + ), + "y": ( + "y", + (np.arange(rain.shape[1]) + 0.5) * kmperpixel * 1000, + {"units": "m", "stepsize": kmperpixel * 1000}, + ), + }, + attrs={ + "precip_var": "precip_intensity", + "projection": "+proj=stere +lat_0=90 +lon_0=0 +lat_ts=60 +datum=WGS84 +units=m", + "institution": "reference fixture", + }, + ) + if velocity is not None: + dataset["velocity_x"] = (("y", "x"), velocity[0].copy()) + dataset["velocity_y"] = (("y", "x"), velocity[1].copy()) + return dataset + + +def _forecast_array(rain, velocity=None, timesteps=1, **kwargs): + """Run original numerical assertions through the public dataset interface.""" + if not isinstance(rain, np.ndarray): + return parametric.forecast(rain, timesteps, **kwargs) + dataset = _dataset( + rain, + velocity, + timestep=kwargs.get("timestep", 5.0), + kmperpixel=kwargs.get("kmperpixel", 1.0), + ) + result = parametric.forecast(dataset, timesteps, **kwargs) + if isinstance(result, tuple): + output, diagnostics = result + return ( + output["precip_intensity"].values if output is not None else None, + diagnostics, + ) + return result["precip_intensity"].values if result is not None else None + + @pytest.fixture(scope="module") def reference(): folder = Path(__file__).parent / "data" @@ -37,7 +105,7 @@ def _call(reference, index=0, **kwargs): seed=case["seed"], ) defaults.update(kwargs) - return parametric.forecast( + return _forecast_array( arrays[f"history_{index}"], arrays[f"velocity_{index}"], 3, **defaults ) @@ -210,12 +278,14 @@ def test_internal_motion_uses_latest_pair_and_reference_configuration( calls = [] def motion(images, **kwargs): - calls.append((images.copy(), kwargs)) - return arrays["velocity_0"].copy() + calls.append((images.copy(deep=True), kwargs)) + images["velocity_x"] = (("y", "x"), arrays["velocity_0"][0].copy()) + images["velocity_y"] = (("y", "x"), arrays["velocity_0"][1].copy()) + return images monkeypatch.setattr(parametric.motion, "get_method", lambda name: motion) overrides = dict(decl_scale=12) - output, diagnostics = parametric.forecast( + output, diagnostics = _forecast_array( history, timesteps=2, n_ens_members=1, @@ -226,12 +296,13 @@ def motion(images, **kwargs): return_diagnostics=True, ) assert output.shape == (1, 2, *history.shape[1:]) - assert len(calls) == 1 and calls[0][0].shape[0] == 2 + assert len(calls) == 1 and calls[0][0].sizes["time"] == 2 expected = history[-2:].copy() wet = expected >= 1 expected[wet] = 10 * np.log10(expected[wet]) expected[~wet] = -0.1 - np.testing.assert_array_equal(calls[0][0], expected) + np.testing.assert_array_equal(calls[0][0]["precip_intensity"].values, expected) + assert calls[0][0]["precip_intensity"].attrs["transform"] == "dB" assert calls[0][1]["decl_scale"] == 12 assert overrides == {"decl_scale": 12} assert diagnostics["motion_source"] == "lucaskanade" @@ -245,7 +316,7 @@ def test_bad_estimated_motion_rejected(reference, monkeypatch): lambda name: lambda *args, **kw: np.full((2, 128, 96), np.nan), ) with pytest.raises(ValueError, match="invalid velocity"): - parametric.forecast(arrays["history_0"], kmperpixel=1, timestep=5) + _forecast_array(arrays["history_0"], kmperpixel=1, timestep=5) @pytest.mark.parametrize("prior_wet", [False, True]) @@ -260,7 +331,7 @@ def unexpected(*args, **kwargs): monkeypatch.setattr(parametric.motion, "get_method", unexpected) monkeypatch.setattr(parametric, "generate_calibrated_field", unexpected) leads = [] - output, diagnostics = parametric.forecast( + output, diagnostics = _forecast_array( rain, timesteps=2, kmperpixel=1, @@ -281,8 +352,8 @@ def test_partly_dry_history_requires_explicit_temporal_prior(reference): history[:2] = 0 kwargs = dict(kmperpixel=1, timestep=5, n_ens_members=1, seed=4) with pytest.raises(ValueError, match="Unavailable spectral/persistence"): - parametric.forecast(history, arrays["velocity_0"], 2, **kwargs) - result, diagnostics = parametric.forecast( + _forecast_array(history, arrays["velocity_0"], 2, **kwargs) + result, diagnostics = _forecast_array( history, arrays["velocity_0"], 2, @@ -303,7 +374,7 @@ def test_low_fraction_post_ar_suppression_is_explicit(reference): history = arrays["history_1"].copy() wet_indices = np.flatnonzero(history[-1] >= 1) history[-1].flat[wet_indices[200:]] = 0 - result, diagnostics = parametric.forecast( + result, diagnostics = _forecast_array( history, arrays["velocity_1"], 2, @@ -329,7 +400,7 @@ def test_unestimable_wet_input_is_not_dry(kind): else: rain[-1, 32, 32] = 5 with pytest.raises(ValueError, match="at least two distinct"): - parametric.forecast( + _forecast_array( rain, np.zeros((2, 64, 64)), kmperpixel=1, timestep=5, n_ens_members=1 ) @@ -341,9 +412,7 @@ def test_unestimable_wet_input_is_not_dry(kind): {"timesteps": 0}, {"n_ens_members": True}, {"n_ens_members": 0}, - {"timestep": None}, {"timestep": 0}, - {"kmperpixel": None}, {"kmperpixel": 0}, {"rain_threshold": 0}, {"scale_break": np.nan}, @@ -370,7 +439,7 @@ def test_invalid_options_even_when_dry(kwargs): options = dict(kmperpixel=1, timestep=5, n_ens_members=1) options.update(kwargs) with pytest.raises(ValueError): - parametric.forecast(np.zeros((3, 64, 64)), **options) + _forecast_array(np.zeros((3, 64, 64)), **options) @pytest.mark.parametrize( @@ -388,12 +457,13 @@ def test_invalid_observations(kind): rain[0, 0, 0] = -1 elif kind == "mask": rain = np.ma.array(rain, mask=False) + rain.mask[0, 0, 0] = True elif kind == "complex": rain = rain.astype(complex) elif kind == "list": rain = rain.tolist() with pytest.raises(ValueError): - parametric.forecast(rain, kmperpixel=1, timestep=5) + _forecast_array(rain, kmperpixel=1, timestep=5) def test_method_registration(): @@ -473,3 +543,220 @@ def test_temporal_fallback_does_not_replace_spatial_estimates(reference, monkeyp def test_invalid_temporal_fallback(reference, fallback): with pytest.raises(ValueError): _call(reference, temporal_fallback=fallback) + + +@pytest.mark.parametrize("index", [0, 1]) +def test_dataset_coordinates_metadata_and_input_purity(reference, monkeypatch, index): + metadata, arrays = reference + seconds = metadata["cases"][index]["config"]["timestep"] + dataset = _dataset( + arrays[f"history_{index}"], arrays[f"velocity_{index}"], timestep=seconds / 60 + ) + original = dataset.copy(deep=True) + + def unexpected(*args, **kwargs): + raise AssertionError("Supplied velocity must bypass motion estimation") + + monkeypatch.setattr(parametric.motion, "get_method", unexpected) + output, diagnostics = parametric.forecast( + dataset, + 3, + n_ens_members=2, + seed=metadata["cases"][index]["seed"], + return_diagnostics=True, + ) + xr.testing.assert_identical(dataset, original) + field = output[output.attrs["precip_var"]] + assert field.dims == ("ens_number", "time", "y", "x") + assert field.dtype == np.float64 + expected_attrs = {**dataset.precip_intensity.attrs, "threshold": 1.0} + expected_attrs.pop("transform") + assert field.attrs == expected_attrs + assert output.attrs == dataset.attrs + assert diagnostics["motion_source"] == "supplied" + assert diagnostics["settings"]["timestep_minutes"] == seconds / 60 + assert diagnostics["settings"]["kmperpixel"] == 1.0 + np.testing.assert_array_equal(output.ens_number, [1, 2]) + np.testing.assert_array_equal( + output.time.values, + dataset.time.values[-1] + np.arange(1, 4) * np.timedelta64(int(seconds), "s"), + ) + assert output.forecast_reference_time.values == dataset.time.values[-1] + xr.testing.assert_identical(output.x.variable, dataset.x.variable) + xr.testing.assert_identical(output.y.variable, dataset.y.variable) + assert output.time.attrs["stepsize"] == seconds + assert "velocity_x" not in output and "velocity_y" not in output + # Returned coordinates must not expose mutable aliases into the input. + output.x.attrs["units"] = "changed on output only" + xr.testing.assert_identical(dataset, original) + + +def test_dataset_dimension_order_and_coordinate_units(reference): + _, arrays = reference + dataset = _dataset(arrays["history_0"], arrays["velocity_0"]) + expected = parametric.forecast(dataset, 1, n_ens_members=1, seed=4) + for axis in ("x", "y"): + dataset[axis] = dataset[axis] / 1000 + dataset[axis].attrs.update(units="km", stepsize=1.0) + dataset = dataset.transpose("x", "time", "y") + actual = parametric.forecast(dataset, 1, n_ens_members=1, seed=4) + np.testing.assert_array_equal(actual.precip_intensity, expected.precip_intensity) + xr.testing.assert_identical(actual.x.variable, dataset.x.variable) + xr.testing.assert_identical(actual.y.variable, dataset.y.variable) + + +def test_descending_y_and_dry_forecast_metadata(): + dataset = _dataset(np.zeros((3, 8, 9)), timestep=7.5) + dataset = dataset.isel(y=slice(None, None, -1)) + dataset.y.attrs["stepsize"] = -1000.0 + output = parametric.forecast(dataset, 2, n_ens_members=1) + xr.testing.assert_identical(output.y.variable, dataset.y.variable) + assert not output.precip_intensity.values.any() + assert "transform" not in output.precip_intensity.attrs + np.testing.assert_array_equal( + np.diff(output.time.values) / np.timedelta64(1, "s"), [450] + ) + + +@pytest.mark.parametrize("overrides", [{"kmperpixel": 2}, {"timestep": 5}]) +def test_explicit_geometry_must_agree_with_dataset(overrides): + dataset = _dataset(np.zeros((3, 8, 9)), timestep=7.5) + with pytest.raises(ValueError, match="disagrees with dataset coordinates"): + parametric.forecast(dataset, **overrides) + + +@pytest.mark.parametrize( + "kind, message", + [ + ("array", "xarray.Dataset"), + ("precip_var", "precip_var"), + ("units", "untransformed rain rates"), + ("transform", "untransformed rain rates"), + ("integer", "floating point"), + ("missing", "finite nonnegative"), + ("negative", "finite nonnegative"), + ("coordinate_units", "coordinate units"), + ("irregular_x", "regularly spaced"), + ("square", "square pixels"), + ("spatial_stepsize", "stepsize disagrees"), + ("missing_coordinate", "coordinate is required"), + ("time_stepsize", "spacing in seconds"), + ("irregular_time", "increasing and regularly spaced"), + ("backwards_time", "increasing and regularly spaced"), + ("nat", "valid datetime64"), + ("numeric_time", "valid datetime64"), + ("partial_velocity", "both velocity_x and velocity_y"), + ("missing_velocity", "finite real"), + ("velocity_units", "pixels per observation timestep"), + ("velocity_time_dimension", r"dimensions \(y, x\)"), + ], +) +def test_dataset_rejects_invalid_physical_inputs_before_dry_shortcut(kind, message): + dataset = _dataset(np.zeros((3, 8, 9)), np.zeros((2, 8, 9))) + if kind == "array": + dataset = dataset.precip_intensity.values + elif kind == "precip_var": + dataset.attrs["precip_var"] = "absent" + elif kind in ("units", "transform"): + dataset.precip_intensity.attrs[kind] = "dB" if kind == "transform" else "mm" + elif kind == "integer": + dataset["precip_intensity"] = dataset.precip_intensity.astype(int) + elif kind in ("missing", "negative"): + dataset.precip_intensity.values[0, 0, 0] = np.nan if kind == "missing" else -1 + elif kind == "coordinate_units": + dataset.x.attrs["units"] = "degrees_east" + elif kind == "irregular_x": + values = dataset.x.values.copy() + values[2] += 100 + dataset = dataset.assign_coords(x=("x", values, dataset.x.attrs)) + elif kind == "square": + dataset["y"] = dataset.y * 2 + dataset.y.attrs.update(units="m", stepsize=2000) + elif kind == "spatial_stepsize": + dataset.y.attrs["stepsize"] = -1000 + elif kind == "missing_coordinate": + dataset = dataset.drop_vars("x") + elif kind == "time_stepsize": + dataset.time.attrs["stepsize"] = 5 # Minutes must not be read as seconds. + elif kind == "irregular_time": + values = dataset.time.values.copy() + values[1] += np.timedelta64(1, "s") + dataset = dataset.assign_coords(time=("time", values, dataset.time.attrs)) + elif kind == "backwards_time": + dataset = dataset.isel(time=slice(None, None, -1)) + elif kind == "nat": + values = dataset.time.values.copy() + values[0] = np.datetime64("NaT", "ns") + dataset = dataset.assign_coords(time=("time", values, dataset.time.attrs)) + elif kind == "numeric_time": + dataset = dataset.assign_coords(time=[0, 300, 600]) + elif kind == "partial_velocity": + dataset = dataset.drop_vars("velocity_y") + elif kind == "missing_velocity": + dataset.velocity_x.values[0, 0] = np.nan + elif kind == "velocity_units": + dataset.velocity_x.attrs["units"] = "m/s" + elif kind == "velocity_time_dimension": + dataset["velocity_x"] = (("time", "y", "x"), np.zeros((3, 8, 9))) + with pytest.raises(ValueError, match=message): + parametric.forecast(dataset, n_ens_members=1) + + +@pytest.mark.parametrize("case", [0, 1, "dry"]) +def test_forecast_native_netcdf_roundtrip(reference, tmp_path, case): + """Exercise actual importer metadata and lossless CF decoding at both cadences.""" + from datetime import datetime + + from pysteps.xarray_helpers import convert_input_to_xarray_dataset + + metadata, arrays = reference + index = 0 if case == "dry" else case + seconds = 450 if case == "dry" else metadata["cases"][index]["config"]["timestep"] + rain = arrays[f"history_{index}"].copy() + if case == "dry": + rain.fill(0) + height, width = rain.shape[-2:] + dataset = convert_input_to_xarray_dataset( + rain, + None, + { + "unit": "mm/h", + "threshold": 0.1, + "zerovalue": 0.0, + "accutime": seconds / 60, + "projection": _dataset(rain).attrs["projection"] + " +x_0=0 +y_0=0", + "institution": "reference fixture", + "x1": 0, + "x2": width * 1000, + "y1": 0, + "y2": height * 1000, + "xpixelsize": 1000, + "ypixelsize": 1000, + "cartesian_unit": "m", + "yorigin": "upper", + }, + startdate=datetime(2026, 1, 1), + timestep=seconds, + ) + dataset["velocity_x"] = (("y", "x"), arrays[f"velocity_{index}"][0]) + dataset["velocity_y"] = (("y", "x"), arrays[f"velocity_{index}"][1]) + original = dataset.copy(deep=True) + output = parametric.forecast(dataset, 3, n_ens_members=2, seed=4) + precip_var = output.attrs["precip_var"] + output_path = tmp_path / "forecast.nc" + output.to_netcdf( + output_path, + engine="netcdf4", + encoding={precip_var: {"zlib": True, "complevel": 1}}, + ) + with xr.open_dataset(output_path, engine="netcdf4") as reopened: + loaded = reopened.load() + xr.testing.assert_identical(output, loaded) + xr.testing.assert_identical(dataset, original) + np.testing.assert_array_equal(output[precip_var], loaded[precip_var]) + assert loaded[precip_var].dtype == np.float64 + assert loaded.time.attrs["stepsize"] == seconds + assert "transform" not in loaded[precip_var].attrs + assert "lat" in loaded.coords and "lon" in loaded.coords + assert loaded[precip_var].attrs["grid_mapping"] in loaded.coords + assert "velocity_x" not in loaded and "velocity_y" not in loaded From ff91f36023967569127b7ef429b69b54e561bdd0 Mon Sep 17 00:00:00 2001 From: Alan Seed <87471705+alanseed@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:03:26 +1100 Subject: [PATCH 10/17] Update doc/source/pysteps_reference/noise.rst Co-authored-by: Ruben Imhoff <31476760+RubenImhoff@users.noreply.github.com> --- doc/source/pysteps_reference/noise.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/pysteps_reference/noise.rst b/doc/source/pysteps_reference/noise.rst index bff02a21..368f1349 100644 --- a/doc/source/pysteps_reference/noise.rst +++ b/doc/source/pysteps_reference/noise.rst @@ -6,7 +6,6 @@ Implementation of deterministic and ensemble nowcasting methods. .. automodule:: pysteps.noise.interface -.. automodule:: pysteps.noise.fftgenerators .. automodule:: pysteps.noise.parametric .. automodule:: pysteps.noise.motion .. automodule:: pysteps.noise.utils From fad848e6b1a13c525373608a3172ecf6feaaa50b Mon Sep 17 00:00:00 2001 From: Alan Seed <87471705+alanseed@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:04:20 +1100 Subject: [PATCH 11/17] Update doc/source/pysteps_reference/noise.rst Co-authored-by: Ruben Imhoff <31476760+RubenImhoff@users.noreply.github.com> --- doc/source/pysteps_reference/noise.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/pysteps_reference/noise.rst b/doc/source/pysteps_reference/noise.rst index 368f1349..cf542c80 100644 --- a/doc/source/pysteps_reference/noise.rst +++ b/doc/source/pysteps_reference/noise.rst @@ -5,6 +5,7 @@ pysteps.noise Implementation of deterministic and ensemble nowcasting methods. +.. automodule:: pysteps.noise.fftgenerators .. automodule:: pysteps.noise.interface .. automodule:: pysteps.noise.parametric .. automodule:: pysteps.noise.motion From 53a847e77eae1511c27112bebfa182b31dcd6e03 Mon Sep 17 00:00:00 2001 From: Alan Seed <87471705+alanseed@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:05:09 +1100 Subject: [PATCH 12/17] Update doc/source/pysteps_reference/noise.rst Co-authored-by: Ruben Imhoff <31476760+RubenImhoff@users.noreply.github.com> --- doc/source/pysteps_reference/noise.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/pysteps_reference/noise.rst b/doc/source/pysteps_reference/noise.rst index cf542c80..d2ffab64 100644 --- a/doc/source/pysteps_reference/noise.rst +++ b/doc/source/pysteps_reference/noise.rst @@ -7,6 +7,5 @@ Implementation of deterministic and ensemble nowcasting methods. .. automodule:: pysteps.noise.fftgenerators .. automodule:: pysteps.noise.interface -.. automodule:: pysteps.noise.parametric .. automodule:: pysteps.noise.motion .. automodule:: pysteps.noise.utils From 0350dd05c02d23860cc32e3b7161c6ea647d0428 Mon Sep 17 00:00:00 2001 From: Alan Seed <87471705+alanseed@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:05:53 +1100 Subject: [PATCH 13/17] Update doc/source/pysteps_reference/noise.rst Co-authored-by: Ruben Imhoff <31476760+RubenImhoff@users.noreply.github.com> --- doc/source/pysteps_reference/noise.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/pysteps_reference/noise.rst b/doc/source/pysteps_reference/noise.rst index d2ffab64..e38f405e 100644 --- a/doc/source/pysteps_reference/noise.rst +++ b/doc/source/pysteps_reference/noise.rst @@ -8,4 +8,5 @@ Implementation of deterministic and ensemble nowcasting methods. .. automodule:: pysteps.noise.fftgenerators .. automodule:: pysteps.noise.interface .. automodule:: pysteps.noise.motion +.. automodule:: pysteps.noise.parametric .. automodule:: pysteps.noise.utils From bba6845a6ebd9b1b115741e6dbf603ceff9cc26b Mon Sep 17 00:00:00 2001 From: alanseed Date: Fri, 6 Nov 2026 08:49:34 +1100 Subject: [PATCH 14/17] improved comments --- MANIFEST.in | 2 +- doc/proposals/calibrated_generator.md | 164 -------- .../calibrated_generator_validation.json | 127 ------ doc/proposals/parametric_engine.md | 240 ----------- doc/proposals/parametric_estimators.md | 158 ------- .../parametric_estimators_validation.json | 64 --- .../parametric_nowcast_validation.json | 389 ------------------ doc/proposals/parametric_nowcasting.md | 321 --------------- doc/source/pysteps_reference/noise.rst | 2 +- doc/source/pysteps_reference/nowcasts.rst | 2 +- .../user_guide/calibrated_generator.rst | 53 +-- .../user_guide/parametric_estimators.rst | 60 ++- doc/source/user_guide/parametric_nowcast.rst | 93 +++-- examples/plot_calibrated_noise_generator.py | 129 +++--- examples/plot_parametric_nowcast.py | 8 +- examples/plot_parametric_nowcast_swiss.py | 46 +-- pysteps/noise/parametric.py | 76 +++- pysteps/nowcasts/_cascade_advection.py | 36 +- pysteps/nowcasts/interface.py | 7 +- pysteps/nowcasts/parametric.py | 117 +++++- pysteps/postprocessing/probmatching.py | 10 +- pysteps/tests/data/README.rst | 71 ++++ .../data/compare_calibrated_paper_fields.py | 11 +- pysteps/timeseries/parametric.py | 24 +- pysteps/utils/spectral.py | 23 +- pysteps/utils/statistics.py | 22 +- 26 files changed, 539 insertions(+), 1716 deletions(-) delete mode 100644 doc/proposals/calibrated_generator.md delete mode 100644 doc/proposals/calibrated_generator_validation.json delete mode 100644 doc/proposals/parametric_engine.md delete mode 100644 doc/proposals/parametric_estimators.md delete mode 100644 doc/proposals/parametric_estimators_validation.json delete mode 100644 doc/proposals/parametric_nowcast_validation.json delete mode 100644 doc/proposals/parametric_nowcasting.md create mode 100644 pysteps/tests/data/README.rst diff --git a/MANIFEST.in b/MANIFEST.in index 4d86691f..59519138 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,5 +4,5 @@ include pysteps/pystepsrc_schema.json include pysteps/io/mch_lut_8bit_Metranet_AZC_V104.txt include pysteps/io/mch_lut_8bit_Metranet_v103.txt recursive-include pysteps *.pyx -recursive-include pysteps/tests/data *.json *.npz +recursive-include pysteps/tests/data *.json *.npz *.rst include pyproject.toml diff --git a/doc/proposals/calibrated_generator.md b/doc/proposals/calibrated_generator.md deleted file mode 100644 index a1f844c3..00000000 --- a/doc/proposals/calibrated_generator.md +++ /dev/null @@ -1,164 +0,0 @@ -# Step 3: calibrated stochastic field generator - -Implemented on `parametric-nowcasting` under the -[agreed contribution scope](parametric_nowcasting.md), 14 October 2026. - -## Public components - -- `pysteps.noise.parametric.power_law_filter`: full-FFT amplitude filter with - signed power slopes, continuous prescribed break, and explicit physical units. -- `pysteps.noise.parametric.generate_stochastic_field`: source-compatible filtered - uniform-noise field and histogram marginal mapping, without inverse correction. -- `pysteps.noise.parametric.generate_calibrated_field`: inverse spectral correction, - candidate assessment and selection, and optional calibration diagnostics. -- `pysteps.postprocessing.probmatching.match_rainfall_distribution`: common - NumPy marginal mapper for generation and the later post-AR adjustment. - -The generator accepts shape `(y, x)`, a mapping containing the five active scalar -parameters (`beta_1`, `beta_2`, `rain_fraction`, `mean_db`, `stdev_db`), physical -pixel spacing and break wavelength in km, and a transformed rain threshold. It -returns a float64 NumPy field, or `(field, diagnostics)` when requested. Supplied -parameters are copied and remain unchanged. No database object, xarray container, -or new runtime dependency is required. - -The generator consumes caller-selected active moments; it does not automatically -choose latent or ordinary moments or apply regional generation QA. These policies -belong to the forthcoming engine. The raw field retains its rainfall marginal; -the engine obtains normalized innovations by decomposing it with `normalize=True`. -Existing native STEPS noise registrations retain their existing interface. - -## Reference algorithm and defaults - -The source is `pysteps_param/src/pysteps_param/stochastic_generator.py` at revision -`94bd4c1571637a8f246ac391470e1a820d6376a6`. Its SHA-256 is -`94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343`. -The source method identifier `inverse_calibrated_v2` is retained. - -| Setting | Preserved default | -| --- | --- | -| White-noise input | Independent `U(0,1)` samples | -| Additive spectral correction | `-0.5` on both power slopes | -| Marginal inverse correction | None on active Normal location/scale | -| Histogram bins | `500` | -| Normal quantile probability clip | `[0.001, 0.999]` | -| Default dry value for assessment | `threshold - 0.1` | -| Low-fraction retry boundary | `rain_fraction <= 0.1` | -| Maximum attempts at low fraction | `5` | -| Attempts at higher fraction | `1` | -| Spectral error variances | `(0.067738, 0.076405)` | -| Accepted weighted distance | `<= 1.0` | - -No calibration constants were retuned. The spectral bias, error weights, tolerance, -retry boundary/count, histogram bins, and quantile clip are configurable. - -The mapper preserves the source histogram cutoff's **upper edge**, followed by -strictly-above-threshold sample selection and bin-centre interpolation to clipped -Normal quantiles. Wet mapped values may cross back below the rain threshold, and -the returned dry anomalies remain continuous. Neither exact wet fraction nor -global standardization is claimed. - -Assess slopes on a copy whose values `<= threshold` are replaced by the configured -dry value. Select the first candidate meeting the distance tolerance; otherwise -select the smallest finite distance, preserving the first tie. If all assessments -fail, select the last candidate with an explicit `unavailable_spectrum` status, -infinite distance, and no success claim. The implementation retains only the -best/current candidate fields rather than all attempted grids. - -The shared marginal helper defaults to no suppression of positive fractions. -The source post-AR policy, which returns dry output for target fraction `< 0.025`, -is available through the explicit `min_rain_fraction` setting. The helper preserves -valid source mappings and rejects nonfinite/degenerate input instead of inventing -a dry field after an invalid calculation. - -An explicitly confirmed zero target is a documented interface extension: return -the dry value, consume no random draws, and report zero attempts without spectral -success. Invalid parameters and failed positive-fraction mappings remain errors. - -## Reproducibility and validation - -Randomness uses a local `default_rng(seed)` or a caller-owned NumPy Generator or -RandomState. Global NumPy random state is unchanged. Caller streams advance in -place; the engine must give each member an independent stream. Retry policy and -numerical implementation are part of reproducibility. - -The frozen [reference metadata](../../pysteps/tests/data/calibrated_generator_reference.json) -and [complete fields](../../pysteps/tests/data/calibrated_generator_reference.npz) -were generated from the original numerical functions, result class, and constants. -A minimal five-scalar dataclass supplies the attributes those functions require, -without operational metadata or QA. No database is accessed. The source spectral -estimator is used when creating expectations; the port uses the new estimators. - -Tests cover complete seeded fields, source diagnostics and selection decisions, -the post-AR mapping, independently calculated filter responses, physical-unit -invariance, marginal cutoff/quantile semantics, dry/error cases, and random streams. -The fixtures are explicitly included in the package manifest so installed tests -can find both these and the step-2 reference data. - -To regenerate using the source project's environment and this checkout available -on `PYTHONPATH`: - -```bash -python pysteps/tests/data/generate_calibrated_reference.py /path/to/pysteps_param -``` - -The saved paper cases `auckland_flood`, `isolated_convection`, and `maximum_war` -were compared at seeds `0` and `20260908`, with a 15 km break, 1 km pixels, -1 mm/h occurrence threshold, and a 1.25 dBR latent fitting cutoff. These checks -use **raw estimated targets**, without regional moment selection or spectral -repair, and therefore evaluate the primitives rather than production engine QA. - -For identical target parameters, all six generated fields matched the source -exactly in this run. With the complete ported estimator-to-generator chain, the -largest field difference was below `4e-6` dBR. This arises from small differences -between equivalent spectral regression solvers amplified by histogram -interpolation. Candidate counts, selected attempts, tolerance outcomes, and -rain-threshold classifications agreed in all six comparisons. The comparison -uses an explicit `1e-4` dBR absolute bound for this coupled check, separately from -the tight identical-target check. The -[validation report](calibrated_generator_validation.json) records source/input -identity and individual results. - -```bash -python pysteps/tests/data/compare_calibrated_paper_fields.py \ - /path/to/pysteps_param /path/to/psteps_param_paper \ - --output /tmp/calibrated_generator_validation.json -``` - -These are generator and estimator-chain checks, not temporal nowcast acceptance -or a forecast-skill comparison. Full ensemble evolution and generation-time -parameter safeguards remain work for the engine stage. - -Validation used Python 3.12, NumPy 2.5.1, and SciPy 1.18.0. The targeted suite -passed 161 tests covering the new components and existing probability matching, -noise, spectral, transformation, and autoregression behavior (11 warnings from -existing modules). A built wheel included all reference fixtures; its 100 -estimator/generator tests passed when run from the extracted wheel outside the -checkout. Both user-guide examples executed successfully. Python formatting and -`git diff --check` passed. This was not a full repository test run, and a full -Sphinx documentation build was not run because Sphinx was unavailable. - -## Documentation and next stage - -API docstrings and noise-module reference entries are provided, with executable -examples in `doc/source/user_guide/calibrated_generator.rst`. The assembled -three-observation engine is documented in [its implementation record](parametric_engine.md). - -The [companion gallery example](../../examples/plot_calibrated_noise_generator.py) -uses the same MeteoSwiss composite as `plot_noise_generators.py`: 11 July 2016, -21:00 UTC. It estimates ordinary/latent moments and prescribed-break slopes on -a complete 300 km domain at 2 km spacing, explicitly selects generation moments, -and generates three seeded calibrated fields. It compares observed and calibrated -rainfall exceedance curves, contrasts radial spectra with an uncorrected field, -then decomposes the raw -field into normalized innovations. The original standardized-noise example -remains unchanged. - -The companion ran on the actual sample data and all four figures rendered. -Seeds 42 and 44 met the default spectral tolerance (distances 0.634 and 0.817); -seed 43 returned `best_available` with distance 1.295, correctly reported as -outside tolerance. The run checked finite 150-by-150 fields, rain-rate units and -thresholding, seed reproducibility, monotone exceedance curves and their wet-area -denominators, and zero-mean/unit-standard-deviation cascade innovations. -The raw generated fields had standard deviations around 2.39 dBR, preserving -the distinction from standardized cascade noise. This illustrates the methods -on Swiss data rather than asserting a Swiss calibration or new forecast skill. diff --git a/doc/proposals/calibrated_generator_validation.json b/doc/proposals/calibrated_generator_validation.json deleted file mode 100644 index 6dae9ff8..00000000 --- a/doc/proposals/calibrated_generator_validation.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "provenance": { - "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", - "source_sha256": { - "rain_stats_compute.py": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", - "stochastic_generator.py": "94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343" - } - }, - "paper_revision": "335f7660775480d865d9771820a81923c461832a", - "seeds": [ - 0, - 20260908 - ], - "scale_break_km": 15.0, - "pixel_size_km": 1.0, - "threshold_mm_per_hour": 1.0, - "lower_truncation_dbr": 1.25, - "target_policy": "Raw estimator outputs; regional generation QA not applied", - "coupled_maximum_absolute_error_tolerance_dbr": 0.0001, - "cases": [ - { - "name": "auckland_flood", - "input_sha256": "f78b06cc1efeb8d9ee061426eac2f51ed82235e00f4941b1d47104d104f667bd", - "parameters": { - "beta_1": -2.273651863876293, - "beta_2": -3.662396072463196, - "rain_fraction": 0.5553436279296875, - "mean_db": 8.847101002812746, - "stdev_db": 3.8570109547579055 - }, - "comparisons": [ - { - "seed": 0, - "attempts": 1, - "selected_attempt": 1, - "tolerance_achieved": true, - "distance": 0.6383365056784039, - "identical_targets_maximum_field_difference": 0.0, - "coupled_estimators_maximum_field_difference": 2.5451289848987813e-06, - "coupled_distance_difference": 2.5999754238448247e-08, - "coupled_threshold_classification_changes": 0 - }, - { - "seed": 20260908, - "attempts": 1, - "selected_attempt": 1, - "tolerance_achieved": true, - "distance": 0.6095533515286298, - "identical_targets_maximum_field_difference": 0.0, - "coupled_estimators_maximum_field_difference": 2.0263810918130787e-06, - "coupled_distance_difference": 3.071912091368034e-08, - "coupled_threshold_classification_changes": 0 - } - ] - }, - { - "name": "isolated_convection", - "input_sha256": "89f2e91182e1d3e6ca25f72665c79bfbe21bb32b6e342820d009f00e5e4cd9cf", - "parameters": { - "beta_1": -2.6892617566424533, - "beta_2": -4.121223604980568, - "rain_fraction": 0.0120086669921875, - "mean_db": 7.35241634620303, - "stdev_db": 5.986137951026812 - }, - "comparisons": [ - { - "seed": 0, - "attempts": 5, - "selected_attempt": 1, - "tolerance_achieved": false, - "distance": 1.7682317797999607, - "identical_targets_maximum_field_difference": 0.0, - "coupled_estimators_maximum_field_difference": 3.0861900794931785e-06, - "coupled_distance_difference": 2.7100723221096246e-08, - "coupled_threshold_classification_changes": 0 - }, - { - "seed": 20260908, - "attempts": 5, - "selected_attempt": 5, - "tolerance_achieved": false, - "distance": 1.372658775569873, - "identical_targets_maximum_field_difference": 0.0, - "coupled_estimators_maximum_field_difference": 3.81796733961437e-06, - "coupled_distance_difference": 1.336142596564116e-08, - "coupled_threshold_classification_changes": 0 - } - ] - }, - { - "name": "maximum_war", - "input_sha256": "c4b4056eb831909d7743ed843cafe5d8e2d07ee51aaaae48baf5b13bcd2e228f", - "parameters": { - "beta_1": -2.0124000625157166, - "beta_2": -2.815792829116034, - "rain_fraction": 0.97393798828125, - "mean_db": 4.864743029213302, - "stdev_db": 2.3268029538539783 - }, - "comparisons": [ - { - "seed": 0, - "attempts": 1, - "selected_attempt": 1, - "tolerance_achieved": false, - "distance": 2.239759280240739, - "identical_targets_maximum_field_difference": 0.0, - "coupled_estimators_maximum_field_difference": 7.808604465253666e-07, - "coupled_distance_difference": 1.093659296458327e-08, - "coupled_threshold_classification_changes": 0 - }, - { - "seed": 20260908, - "attempts": 1, - "selected_attempt": 1, - "tolerance_achieved": false, - "distance": 1.8951416473971945, - "identical_targets_maximum_field_difference": 0.0, - "coupled_estimators_maximum_field_difference": 5.423179940677869e-07, - "coupled_distance_difference": 7.69973840242244e-09, - "coupled_threshold_classification_changes": 0 - } - ] - } - ] -} diff --git a/doc/proposals/parametric_engine.md b/doc/proposals/parametric_engine.md deleted file mode 100644 index cb098da1..00000000 --- a/doc/proposals/parametric_engine.md +++ /dev/null @@ -1,240 +0,0 @@ -# Step 4: three-observation parametric nowcast engine - -Implemented on `parametric-nowcasting`, 15 October 2026, under the -[agreed scope](parametric_nowcasting.md), using the accepted -[estimators](parametric_estimators.md) and -[calibrated stochastic generator](calibrated_generator.md). - -## Endpoint - -```python -from pysteps import nowcasts - -ensemble, diagnostics = nowcasts.get_method("parametric")( - rain, # floating ndarray (3, y, x), mm/h - timesteps=12, - n_ens_members=20, - kmperpixel=1.0, - timestep=5.0, # minutes, fractional values supported - seed=42, - return_diagnostics=True, -) -``` - -The result is a float64 NumPy array `(member, lead, y, x)` in mm/h, starting -one observation interval after the newest field. Supplied motion is optional; -otherwise it is estimated from the newest two transformed observations. -All preparation and estimation happen internally. Public registration is -`pysteps.nowcasts.get_method("parametric")`; it is separate from native STEPS's -existing `noise_method="parametric"` option. - -The [engine](../../pysteps/nowcasts/parametric.py), -[user guide](../source/user_guide/parametric_nowcast.rst), and -[runnable gallery example](../../examples/plot_parametric_nowcast.py) describe -the settings and diagnostics. NumPy, SciPy, and existing pySTEPS components are -sufficient; internal Lucas-Kanade motion uses the existing OpenCV dependency. -No new runtime packages are required. - -## Preserved scientific behavior - -The engine uses dBR observations, normalized Gaussian cascades, source-compatible -Lagrangian correlation estimates, the discrete AR(2) persistence definition, -and the accepted spectral/marginal estimators. It selects ordinary or fitted -latent generation moments, applies the regional low-coverage triplet fallback, -then enforces joint spectral and persistence bounds. Raw estimates, selections, -bounds, and effective settings remain separate in diagnostics. - -Each forecast advances Eulerian cascade histories by their respective one/two -timestep backward trajectories, adds normalized calibrated innovations, applies -the source global normalization across levels and space, recomposes with -innovation moments, and performs the reference marginal mapping. The resulting -rainfall is returned before boundary tapering. The transformed field is then -tapered and decomposed again for the next state. This feedback, and stochastic -inflow following zero-anomaly advection, are part of the method. - -Independent member streams are spawned from a local SeedSequence. Every member -starts from observed initial states; kernels do not mutate shared observations. -Leading members agree when ensemble size increases. The old source member-state -aliasing bug is absent in the identified source revision and is explicitly -covered by the port's single-member/prefix regression test. - -Lead-wise callbacks receive all members in mm/h. They can modify their copy -without affecting state. `return_output=False` avoids storing the full horizon. -Callback exceptions propagate. Diagnostics record member seeds and per-lead -generator decisions without persisting anything. - -## Reference configuration and deliberate interface changes - -The numerical source revision is `pysteps_param` -`94bd4c1571637a8f246ac391470e1a820d6376a6`. The paper configuration and exported -inputs come from `psteps_param_paper` -`335f7660775480d865d9771820a81923c461832a`. - -Defaults are traced to `config/db/config.json` in the paper repository and -`config/db/nowcast_v2_acceptance.json` in the cases repository: six levels, -1 mm/h occurrence threshold, 20 km break, exponent 0.81, lag-pair exponent 2.72, -and the largest-level persistence reference. The marginal fitting cutoff is -1.25 dBR. Optical flow uses the source `dense_lucaskanade_v1` settings on the -latest pair. Source `StepsParameters` supplies moment selection (distance 0.5), -joint slope constraints, and reference-time bounds of 60–720 minutes. - -The low-coverage fallback applies below fraction 0.05, replacing the slopes and -reference time together with `-1.45`, `-3.253958088759979`, and -`330.61752959848894` minutes. The last two are regional medians frozen in the -source `config/low_war_fallback.json`, with its original archive provenance. -All these controls are configurable in `estimation_kwargs`; generator controls -are configurable in `noise_kwargs`. This extraction does not retune them. - -The later selected paper analysis uses the top three levels and exponent -0.7718612877. The standalone reference estimator supports it, and the engine -can select it explicitly through `reference_levels` and `temporal_exponent`. -It is not silently combined with the earlier evaluated forecast defaults. - -Interface changes are explicit: - -- Three complete chronological rain-rate arrays replace database lookups. -- Confirmed newest dry/subthreshold rainfall produces a zero ensemble; older - wet observations do not override the newest dry condition. -- Earlier constant fields produce zero normalized anomalies, avoiding division - by zero or normalized roundoff artifacts. Unavailable temporal estimates - outside the low-coverage policy raise; an investigated `reference_time` prior - can be supplied. Unavailable positive-rain marginal moments also raise. -- The post-AR fraction cutoff of 0.025 is preserved and configurable. The - operational issue-time eligibility gate at 0.03 is left to applications. -- Results are raw float64 rain rates before NetCDF packing. The operational - 200 mm/h rejection cap is left to applications; finite output is required. -- Integer lead counts, AR(2), and fixed parameters/motion are implemented. - Storage, NWP blending, irregular lead times, and velocity perturbations remain - outside this first endpoint. - -## Source parity and limits - -[Frozen complete forecast arrays](../../pysteps/tests/data/parametric_nowcast_reference.npz) -and [metadata](../../pysteps/tests/data/parametric_nowcast_reference.json) cover -two deterministic synthetic histories, including positive low coverage and a -7.5-minute cadence. Expectations were generated using original transformation, -estimators, generation QA, calibrated generation, and `update_field` definitions. -Only operational imports are removed; numerical statements are unchanged. -The source CLI's feedback/member loop is assembled explicitly outside storage. -Tests do not need either external repository or a database. - -Regenerate in the source environment, with this checkout on `PYTHONPATH`: - -```bash -python pysteps/tests/data/generate_nowcast_reference.py /path/to/pysteps_param -``` - -The [paper-history validation report](parametric_nowcast_validation.json) -records six comparisons: three archived 256×256 histories, two seeds, two -members, and twelve five-minute leads. Two histories produce positive-rain -forecasts; the low-coverage history exercises the explicit post-AR dry policy. -The archive layout is `[current, previous, future1, ...]`; indices `[1, 0, 2]` -provide three actual chronological observations. The forecast reference time -is therefore five minutes later than the old experiment cycle. Motion is -re-estimated for this latest pair rather than reusing older archived motion. - -With identical source-selected parameters and lag pairs, **all six complete -one-hour forecasts matched exactly** in this run. This isolates state evolution, -advection, calibrated innovations, marginal feedback, and member initialization. -The public call also runs both motion and parameter estimation internally. Its -selected parameters and lag pairs agree at the asserted numerical precision, -but the equivalent source nonlinear spectral regression and ported linear -least-squares solver differ in their final digits. - -Histogram interpolation, wet-pixel membership, and repeated feedback amplify -those perturbations. Across the six coupled runs: - -| Quantity | Largest observed difference | -| --- | ---: | -| Whole forecast RMSE | 0.004162 mm/h | -| Individual pixel rain rate | 1.406 mm/h | -| Mean field rain rate | 0.000401 mm/h | -| Wet fraction per field | 0.0001374 | -| Conditional dBR mean | 0.006208 dBR | -| Conditional dBR standard deviation | 0.004302 dBR | -| Estimated spectral slope | 0.000688 | -| Realized lag-1/lag-2 cascade correlation | 0.000229 | - -The largest run had 25 changed rain classifications among 1,572,864 output -pixels; calibration candidate selections agreed in every comparison. These -results support preservation of the evaluated method on the retained cases, -while documenting why the complete estimator-to-forecast chain is not claimed -to be pixel-identical. They are port checks, not new skill scores, a replay of -all published experiment cycles, or evidence of universal regional performance. -The prior scientific evaluation remains associated with its original revision. - -Reproduce without database access: - -```bash -python pysteps/tests/data/compare_parametric_nowcast_paper.py \ - /path/to/pysteps_param /path/to/psteps_param_paper \ - --output /tmp/parametric_nowcast_validation.json -``` - -The targeted regression suite passed **226 tests** (9 excluded existing STEPS -skill/export cases, 18 warnings from existing modules). This includes 54 new -engine tests, accepted components, existing noise/probability matching/spectral/ -transformation/AR tests, and existing STEPS nowcast behavior. Tests cover full -source forecasts, exact cached-advection parity for zero/constant/varying flow, -fractional cadence, independent streams and input purity, streaming equivalence, -dry/partly dry/sparse histories, safeguard choices, and invalid input/settings. -The gallery example executed successfully with internal motion and estimation. -The additional [Swiss radar tutorial](../../examples/plot_parametric_nowcast_swiss.py) -also ran with the actual MeteoSwiss example observations at 11:50, 11:55, and -12:00 UTC on 31 January 2017. A complete 300 km LV03 subdomain, aggregated to -2 km, produced a finite `(20, 6, 150, 150)` ensemble and four rendered figures: -observation, ensemble mean, four members, and 1 mm/h exceedance probabilities. -Domain metadata and probability calculations were checked. This demonstrates -the interface on Swiss data; it does not retune or validate a Swiss calibration. -The wheel built successfully; its engine, trajectory helper, and forecast -fixtures matched the checkout by SHA-256. All 154 packaged engine, estimator, -and generator tests passed from the extracted wheel outside the checkout. -Python formatting and `git diff --check` passed. These checks used Python -3.12.13, NumPy 2.5.1, and SciPy 1.18.0; the source/reference records also capture -hashes of the existing pySTEPS kernels used by both implementations. -This is a targeted test run; a full repository test suite and Sphinx build are -not claimed. - -## Non-parametric contribution: assessment, not implementation - -Decision, 15 October 2026: defer the non-parametric engine and discussion with -upstream maintainers until the parametric contribution is in pySTEPS. The -assessment below is retained as background; it is not current delivery work. - -There is merit in a subsequent contribution, chiefly the **Eulerian stochastic -inflow treatment** evaluated in the source comparisons. Native upstream STEPS -already supports non-parametric spectral noise, observed cascade correlations, -and empirical CDF matching. Porting its entire operational wrapper would -duplicate existing functionality and bring database concerns into this scope. -See the [upstream STEPS forecast reference](https://pysteps.readthedocs.io/en/stable/generated/pysteps.nowcasts.steps.forecast.html). - -The source `pysteps_inflow.py` instead moves normalized histories before AR -updates, adds innovations throughout the Eulerian domain, and avoids a second -output-advection pass. That can sustain statistically generated rain at inflow -and enable the comparison used in the paper. It also changes interpolation and -boundary evolution; it should be evaluated and exposed as an explicit method -choice. Unlike the parametric engine, it retains native STEPS estimation and AR -state, with empirical output matching outside that state. - -The design implication is modest: - -1. Keep fixed-flow trajectory caching and zero-anomaly sampling independent of - rainfall parameter estimation and marginal choice. This engine does so in - `pysteps/nowcasts/_cascade_advection.py`, with parity tests of its numerical - behavior. The new helper remains private until a public use is justified. -2. Preserve separate state-update policies. Swapping parametric innovations for - non-parametric noise in this engine would retain the parametric marginal - feedback and would not reproduce the paper's non-parametric counterpart. -3. For a later upstream implementation, assess an explicit boundary/evolution - option within native STEPS that reuses its preparation and forecast kernels. - Avoid copying the source adapter's version-locked private method overrides; - it was validated specifically against pySTEPS 1.21.2, whereas this checkout - is 1.21.5. -4. A separate three-rain-field convenience interface could share validation, - transformation metadata, motion preparation, and streaming conventions if - useful to upstream. It need not change this parametric public interface or - force a general framework before either method is accepted. - -The non-parametric engine and native STEPS evolution are unchanged by this -contribution. Its useful boundary extension is on hold and can be raised as a -separate scientific contribution after the parametric engine is upstream. diff --git a/doc/proposals/parametric_estimators.md b/doc/proposals/parametric_estimators.md deleted file mode 100644 index 93dae1b3..00000000 --- a/doc/proposals/parametric_estimators.md +++ /dev/null @@ -1,158 +0,0 @@ -# Step 2: parameter estimators and temporal-model utilities - -Implemented on `parametric-nowcasting`, 14 October 2026, under the -[agreed contribution scope](parametric_nowcasting.md). The public forecast engine -will assemble these helpers internally; this stage provides the independently -callable numerical components, not the three-field forecast endpoint. - -## Public helpers - -| Module | Function | Purpose | -| --- | --- | --- | -| `pysteps.utils.statistics` | `compute_field_statistics` | Wet fraction, ordinary wet-pixel moments, and optional latent Normal fitting | -| `pysteps.utils.statistics` | `fit_lower_truncated_normal` | Profile-likelihood fit of the parent Normal from samples above a cutoff | -| `pysteps.utils.spectral` | `rapsd_logarithmic` | Source-compatible radial mean power and frequency arrays in logarithmic bins | -| `pysteps.utils.spectral` | `fit_power_law_spectrum` | Signed single-slope or continuous fixed-break spectral fit | -| `pysteps.timeseries.parametric` | `lagrangian_autocorrelation` | Lag-1/lag-2 estimates from supplied cascade histories and velocity | -| `pysteps.timeseries.parametric` | `ar2_correlation_time` | Stationary AR(2) discrete ACF sum including lag zero | -| `pysteps.timeseries.parametric` | `estimate_reference_correlation_time` | Geometric mean of selected scale-adjusted level times | -| `pysteps.timeseries.parametric` | `fit_ar2_correlations` | Invert a target time into an adjusted nonperiodic lag pair | -| `pysteps.timeseries.parametric` | `model_cascade_correlations` | Apply temporal scaling to nominal wavelengths, retaining resolution diagnostics | - -Interfaces use NumPy arrays and ordinary Python containers. NumPy and SciPy are -the only numerical dependencies added by these helpers, and both are already -pySTEPS dependencies. The extraction has no database, xarray, pandas, or statsmodels -requirement. Fixture regeneration and the comparison against external paper fields -use the source project's environment, including xarray, to evaluate its original -functions; these are developer checks, not dependencies of the port or CI tests. - -## Scientific conventions and intentional interface changes - -- Field statistics and radial spectra take finite, complete fields. Invalid - input is rejected. Dry and failed estimates remain explicit; estimates are - not clipped or replaced with regional climatology at this stage. -- Estimation converts real inputs to float64 and does not modify them. Source - parity is assessed on the same float64 fields; input-precision changes should - not be confused with a change to the fitting method. -- Rain occurrence includes samples equal to the occurrence threshold. The - denominator is the complete grid. Ordinary moments use population standard - deviations. Latent fitting has a separate cutoff and distinct result fields. -- The latent fit preserves the source profile likelihood and bounded standardized - cutoff interval `[-12, 12]`. Diagnostics identify insufficient/constant samples, - fitting failure, and boundary-limited finite solutions, without changing the - returned source-compatible location and scale. -- The spectrum is the radial mean of unscaled FFT power from a standardized - field. Real-FFT multiplicities reproduce the full-plane mean. Historical - logarithmic edges, including their endpoint rounding, are retained. This is - distinct from the existing `rapsd` binning/normalization convention. -- Spectrum arrays contain linear frequency and power. Conversion to the source - function's `10*log10` representation is explicit. The fit excludes two initial - bins and frequencies at or above `10**(-0.4)` cycles/pixel by default. The - breakpoint is a wavelength in units consistent with the supplied frequencies. -- A prescribed-break model is linear in its coefficients. The port solves the - same unweighted least-squares objective with `numpy.linalg.lstsq`, instead of - the source's nonlinear `curve_fit` call. Small optimizer/roundoff differences - are allowed in coefficient comparisons; the spectrum itself is unchanged. - Insufficient samples and rank-deficient models return explicit statuses. -- Lagrangian correlations retain successive unit-step advection and the - production `boundary="zero"` convention. An explicit `boundary="mask"` - alternative uses common finite support; it changes the estimates and is not - the reference default. Missing input observations are still rejected. -- AR correlation times use caller-supplied time units and the full lag-zero - contribution. Invalid/nonstationary pairs remain NaN. No AR(1) substitution is - applied. These are persistence summaries, not skill horizons. -- Reference-time estimation defaults to levels `(0, 1, 2)`, following the selected - paper method. Every selected time must be valid. Explicit `levels=(0,)` - reproduces the production CLI's largest-level reference. The source research - workflow selected the three-level method without changing the production CLI; - the port exposes the distinction rather than silently conflating them. -- Wavelengths come from the actual cascade filter's `central_freqs`. They remain - nominal filter labels. Scaling exponents and regional calibration settings are - supplied explicitly; this stage does not fit or retune them. -- Inverting a timescale uses the adjusted lag pair throughout. Requested cascade - times at or below one timestep resolve to white noise, achieving one timestep. - Diagnostics retain requested and achieved values and sub-timestep limit flags. - -## Source provenance - -Extraction was checked against: - -- `pysteps_param` revision `94bd4c1571637a8f246ac391470e1a820d6376a6`: - `src/pysteps_param/rain_stats_compute.py` and `shared_utils.py`. -- `psteps_param_paper` revision `335f7660775480d865d9771820a81923c461832a`: - `workflows/reference_timescale_experiment.py`, with the selected method described - in `docs/selected_correlation_scaling.md`. - -The frozen [numerical reference fixture](../../pysteps/tests/data/parametric_estimators_reference.json) -contains file SHA-256 hashes and numerical-library versions. Its expectations -were evaluated from the original numerical function definitions on deterministic -synthetic fields, rather than calculated by the port. Loading only those -definitions avoids package-level imports of operational/database modules. - -To regenerate in an environment containing the original project's dependencies, -with this pySTEPS checkout available on `PYTHONPATH`: - -```bash -python pysteps/tests/data/generate_parametric_reference.py \ - /path/to/pysteps_param /path/to/psteps_param_paper -``` - -Fixture updates should retain the source revision and explain any intended -numerical change. Normal test runs use the committed JSON and need neither source -repository nor a database. - -## Validation - -Recorded result: **90 targeted regression tests passed** under Python 3.12, -NumPy 2.5.1, and SciPy 1.18.0. Both documentation examples executed successfully. -The test run emitted nine warnings from existing transformation and -autoregression tests. This is a targeted suite, not a full pySTEPS test run or a -complete Sphinx documentation build. - -The targeted regression suite covers the new helpers and existing spectral, -transformation, and autoregression functions. It uses the normal pySTEPS FMI test -inputs for existing AR tests: - -```bash -PYSTEPSRC=/path/to/test/pystepsrc python -m pytest -q \ - pysteps/tests/test_parametric_estimators.py \ - pysteps/tests/test_utils_spectral.py \ - pysteps/tests/test_utils_transformation.py \ - pysteps/tests/test_timeseries_autoregression.py -``` - -Independent checks include full-complex-FFT radial means for even/odd square and -rectangular directional fields, orientation invariance, analytic piecewise power -laws, known truncated-Normal quantiles, explicit AR recurrence sums, reference -wavelength invariance, boundary treatments, and dry/invalid inputs. Source-parity -checks cover spectra, marginal estimates, advected correlations, timescale -inversion, and the selected paper reference calculation. - -The saved paper inputs `auckland_flood`, `isolated_convection`, and `maximum_war` -were compared at 1 km resolution, a 15 km spectral break, a 1 mm/h occurrence -threshold, and a 1.25 dBR latent fitting cutoff. Spectra matched exactly in this -run; marginal estimates matched to the asserted numerical precision. Maximum -spectral coefficient differences were below `3e-7`, arising from the equivalent -linear least-squares solution. The -[comparison report](parametric_estimators_validation.json) records input hashes, -source identity, individual differences, and fit statuses. - -The retained comparison script reproduces this check without database access: - -```bash -python pysteps/tests/data/compare_parametric_paper_fields.py \ - /path/to/pysteps_param /path/to/psteps_param_paper \ - --output /tmp/parametric_estimators_validation.json -``` - -These are estimator and temporal-model checks. They do not claim that the complete -forecast engine has been ported or that forecast skill has been re-evaluated. - -## Documentation and next stage - -API docstrings and reference entries are supplied, along with the executable -examples in `doc/source/user_guide/parametric_estimators.rst`. The next stage is -the calibrated stochastic field generator, which will consume these estimators -and return its calibration diagnostics. Forecast-state preparation, reference -configuration assembly, generation-time parameter safeguards, and the public -three-observation forecast call remain work for the engine stage. diff --git a/doc/proposals/parametric_estimators_validation.json b/doc/proposals/parametric_estimators_validation.json deleted file mode 100644 index 2852e2f9..00000000 --- a/doc/proposals/parametric_estimators_validation.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "source_sha256": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", - "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", - "threshold_mm_per_hour": 1.0, - "lower_truncation_dbr": 1.25, - "scale_break_km": 15.0, - "pixel_size_km": 1.0, - "cases": [ - { - "name": "auckland_flood", - "input_sha256": "f78b06cc1efeb8d9ee061426eac2f51ed82235e00f4941b1d47104d104f667bd", - "shape": [ - 256, - 256 - ], - "maximum_spectrum_error_db": 0.0, - "spectral_parameter_absolute_differences": { - "beta_1": 9.808779566355952e-09, - "beta_2": 5.952312864820897e-09, - "c1": 1.4661427982787245e-07, - "c2": 3.875054943591749e-08 - }, - "rain_fraction": 0.5553436279296875, - "marginal_fit_status": "ok", - "spectrum_fit_status": "ok" - }, - { - "name": "isolated_convection", - "input_sha256": "89f2e91182e1d3e6ca25f72665c79bfbe21bb32b6e342820d009f00e5e4cd9cf", - "shape": [ - 256, - 256 - ], - "maximum_spectrum_error_db": 0.0, - "spectral_parameter_absolute_differences": { - "beta_1": 6.97985180764249e-09, - "beta_2": 3.0828113395386936e-09, - "c1": 9.989584270897467e-08, - "c2": 1.8450258210123138e-08 - }, - "rain_fraction": 0.0120086669921875, - "marginal_fit_status": "ok", - "spectrum_fit_status": "ok" - }, - { - "name": "maximum_war", - "input_sha256": "c4b4056eb831909d7743ed843cafe5d8e2d07ee51aaaae48baf5b13bcd2e228f", - "shape": [ - 256, - 256 - ], - "maximum_spectrum_error_db": 0.0, - "spectral_parameter_absolute_differences": { - "beta_1": 1.841340635522215e-08, - "beta_2": 9.041391191288994e-09, - "c1": 2.6422485177590715e-07, - "c2": 5.8668618407864415e-08 - }, - "rain_fraction": 0.97393798828125, - "marginal_fit_status": "ok", - "spectrum_fit_status": "ok" - } - ] -} diff --git a/doc/proposals/parametric_nowcast_validation.json b/doc/proposals/parametric_nowcast_validation.json deleted file mode 100644 index 8a9d68d7..00000000 --- a/doc/proposals/parametric_nowcast_validation.json +++ /dev/null @@ -1,389 +0,0 @@ -{ - "provenance": { - "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", - "source_sha256": { - "rain_stats_compute.py": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", - "stochastic_generator.py": "94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343", - "cascade_utils.py": "77c362a23aa46b214f1d7f613fe0e747a5f6e7f861c17f85094c5b91db13d403", - "shared_utils.py": "4e7346cda389b99971246e4cf060adeec9c51cc9444704446a071ccc44a5288f", - "model_config.py": "3504c8492f8158cd84b155753cb205e0a7810cd04c37d35f253c9d25fc374128", - "transformer.py": "8693d646602538cd75332cd4367170b4387c9844102b011b7090dae033482703", - "steps_params.py": "265c31239ac8647fd599648e0f00dbdbc8c963e4a7bdaadfa89edb177c4c6c28", - "cli/nowcast.py": "d892c72f8daa608669fce0e185397a9664644b53663eeb1d5cc3708ceb7bec26", - "config/low_war_fallback.json": "bf0b3e0c9c12d4ce0f1cf64d3fe997a8e3364f285666071c24835c3141c45e87" - }, - "python_version": "3.12.13", - "numpy_version": "2.5.1", - "scipy_version": "1.18.0", - "native_pysteps_sha256": { - "cascade/decomposition.py": "2963be8c5399afa8c066ab6df93d199f8362f476a93d24add96a508aecb2b84e", - "cascade/bandpass_filters.py": "0527beb2aa67a620425479fa09fa18dbf8ef311109a1de72e9df6d436ee7403b", - "timeseries/autoregression.py": "08205f5de8d6dbe6fea0d3021c929d50990a6379098359a1aab6c4315808d851", - "extrapolation/semilagrangian.py": "5f870efe3b9152fefd417ecc61ffa188d8e9a306515f676b66890d9f1443e9c3" - } - }, - "paper_revision": "335f7660775480d865d9771820a81923c461832a", - "numpy_version": "2.5.1", - "scipy_version": "1.18.0", - "leads": 12, - "members": 2, - "seeds": [ - 0, - 20261015 - ], - "observation_policy": "Archive indices [1,0,2], oldest first; newest is original cycle +5 minutes", - "motion_policy": "Reference Lucas-Kanade settings, re-estimated from latest pair", - "config": { - "n_rows": 256, - "n_cols": 256, - "n_cascade_levels": 6, - "n_forecasts": 12, - "n_ensembles": 2, - "p_size": 1000.0, - "ht": 0.81, - "alpha": 2.72, - "timestep": 300.0, - "ar_order": 2, - "rain_threshold": 1.0, - "scale_break_km": 20.0, - "transform": "dbr" - }, - "comparisons": [ - { - "case": "control_1", - "seed": 0, - "input_sha256": "1a014857e4718864e14c1df0964168a2f1a31c2803d5ecb74e45add339c94b9c", - "observation_times": [ - "2024-02-26 07:05:00", - "2024-02-26 07:10:00", - "2024-02-26 07:15:00" - ], - "parameters": { - "rain_fraction": 0.1327667236328125, - "mean_db": 4.8145470424096395, - "stdev_db": 2.9715365932730777, - "beta_1": -2.0298196144411613, - "beta_2": -3.207739158955002, - "reference_time": 211.6473372524444 - }, - "source_parameters": { - "mean_db": 4.8145470424096395, - "stdev_db": 2.9715365932730777, - "rain_fraction": 0.1327667236328125, - "beta_1": -2.0298195905466616, - "beta_2": -3.207739166346171, - "corl_zero": 211.6473372524458, - "lag_1": [ - 0.9886705269210497, - 0.9665458039340769, - 0.9321040968079097, - 0.8544231076116594, - 0.5948506112677796, - 0.0 - ], - "lag_2": [ - 0.969483268768411, - 0.9116015105837252, - 0.8259299478064996, - 0.6518542775352314, - 0.2835793794265275, - 0.0 - ] - }, - "moment_source": "latent", - "low_coverage_fallback": false, - "identical_parameters_maximum_error_mm_per_hour": 0.0, - "coupled_maximum_error_mm_per_hour": 1.1143516455570275, - "coupled_rmse_mm_per_hour": 0.0038375352394328984, - "rain_classification_changes": 22, - "total_output_pixels": 1572864, - "candidate_selection_changes": 0, - "maximum_wet_fraction_difference": 0.0001373291015625, - "maximum_mean_rain_difference": 8.883720019947638e-05, - "maximum_wet_dbr_mean_difference": 0.006207566738429371, - "maximum_wet_dbr_stdev_difference": 0.004301183495847294, - "maximum_spectral_slope_difference": 0.0006874459001529054, - "maximum_lagrangian_correlation_difference": 0.00022803375540375592 - }, - { - "case": "control_1", - "seed": 20261015, - "input_sha256": "1a014857e4718864e14c1df0964168a2f1a31c2803d5ecb74e45add339c94b9c", - "observation_times": [ - "2024-02-26 07:05:00", - "2024-02-26 07:10:00", - "2024-02-26 07:15:00" - ], - "parameters": { - "rain_fraction": 0.1327667236328125, - "mean_db": 4.8145470424096395, - "stdev_db": 2.9715365932730777, - "beta_1": -2.0298196144411613, - "beta_2": -3.207739158955002, - "reference_time": 211.6473372524444 - }, - "source_parameters": { - "mean_db": 4.8145470424096395, - "stdev_db": 2.9715365932730777, - "rain_fraction": 0.1327667236328125, - "beta_1": -2.0298195905466616, - "beta_2": -3.207739166346171, - "corl_zero": 211.6473372524458, - "lag_1": [ - 0.9886705269210497, - 0.9665458039340769, - 0.9321040968079097, - 0.8544231076116594, - 0.5948506112677796, - 0.0 - ], - "lag_2": [ - 0.969483268768411, - 0.9116015105837252, - 0.8259299478064996, - 0.6518542775352314, - 0.2835793794265275, - 0.0 - ] - }, - "moment_source": "latent", - "low_coverage_fallback": false, - "identical_parameters_maximum_error_mm_per_hour": 0.0, - "coupled_maximum_error_mm_per_hour": 1.4053766415548472, - "coupled_rmse_mm_per_hour": 0.0041613734563649765, - "rain_classification_changes": 25, - "total_output_pixels": 1572864, - "candidate_selection_changes": 0, - "maximum_wet_fraction_difference": 0.0001373291015625, - "maximum_mean_rain_difference": 0.00040067873940896614, - "maximum_wet_dbr_mean_difference": 0.002161082378539625, - "maximum_wet_dbr_stdev_difference": 0.001632912831670108, - "maximum_spectral_slope_difference": 0.0005393250813998662, - "maximum_lagrangian_correlation_difference": 0.00011391220003056923 - }, - { - "case": "rain_fraction_low_2", - "seed": 0, - "input_sha256": "06cfc68af445b4f1c4433939a42bc7b3e33f26ef9e5b9cc96db6797a804e96dd", - "observation_times": [ - "2023-07-08 11:30:00", - "2023-07-08 11:35:00", - "2023-07-08 11:40:00" - ], - "parameters": { - "rain_fraction": 0.0113677978515625, - "mean_db": 1.9564918736104107, - "stdev_db": 1.693264993850012, - "beta_1": -1.45, - "beta_2": -3.253958088759979, - "reference_time": 330.61752959848894 - }, - "source_parameters": { - "mean_db": 1.9564918736104107, - "stdev_db": 1.693264993850012, - "rain_fraction": 0.0113677978515625, - "beta_1": -1.45, - "beta_2": -3.253958088759979, - "corl_zero": 330.61752959848894, - "lag_1": [ - 0.9927967740225752, - 0.9790070324134028, - 0.9582284221587539, - 0.9140934050829691, - 0.8057201684185906, - 0.24456150604323787 - ], - "lag_2": [ - 0.9805283882269249, - 0.9439248229189373, - 0.8904218562228328, - 0.7832391109096336, - 0.5593596606263346, - 0.04531524722317416 - ] - }, - "moment_source": "ordinary", - "low_coverage_fallback": true, - "identical_parameters_maximum_error_mm_per_hour": 0.0, - "coupled_maximum_error_mm_per_hour": 0.0, - "coupled_rmse_mm_per_hour": 0.0, - "rain_classification_changes": 0, - "total_output_pixels": 1572864, - "candidate_selection_changes": 0, - "maximum_wet_fraction_difference": 0.0, - "maximum_mean_rain_difference": 0.0, - "maximum_wet_dbr_mean_difference": null, - "maximum_wet_dbr_stdev_difference": null, - "maximum_spectral_slope_difference": null, - "maximum_lagrangian_correlation_difference": null - }, - { - "case": "rain_fraction_low_2", - "seed": 20261015, - "input_sha256": "06cfc68af445b4f1c4433939a42bc7b3e33f26ef9e5b9cc96db6797a804e96dd", - "observation_times": [ - "2023-07-08 11:30:00", - "2023-07-08 11:35:00", - "2023-07-08 11:40:00" - ], - "parameters": { - "rain_fraction": 0.0113677978515625, - "mean_db": 1.9564918736104107, - "stdev_db": 1.693264993850012, - "beta_1": -1.45, - "beta_2": -3.253958088759979, - "reference_time": 330.61752959848894 - }, - "source_parameters": { - "mean_db": 1.9564918736104107, - "stdev_db": 1.693264993850012, - "rain_fraction": 0.0113677978515625, - "beta_1": -1.45, - "beta_2": -3.253958088759979, - "corl_zero": 330.61752959848894, - "lag_1": [ - 0.9927967740225752, - 0.9790070324134028, - 0.9582284221587539, - 0.9140934050829691, - 0.8057201684185906, - 0.24456150604323787 - ], - "lag_2": [ - 0.9805283882269249, - 0.9439248229189373, - 0.8904218562228328, - 0.7832391109096336, - 0.5593596606263346, - 0.04531524722317416 - ] - }, - "moment_source": "ordinary", - "low_coverage_fallback": true, - "identical_parameters_maximum_error_mm_per_hour": 0.0, - "coupled_maximum_error_mm_per_hour": 0.0, - "coupled_rmse_mm_per_hour": 0.0, - "rain_classification_changes": 0, - "total_output_pixels": 1572864, - "candidate_selection_changes": 0, - "maximum_wet_fraction_difference": 0.0, - "maximum_mean_rain_difference": 0.0, - "maximum_wet_dbr_mean_difference": null, - "maximum_wet_dbr_stdev_difference": null, - "maximum_spectral_slope_difference": null, - "maximum_lagrangian_correlation_difference": null - }, - { - "case": "beta_1_low_2", - "seed": 0, - "input_sha256": "03ca980e074252c2d81881e11c5809b07d8f318e75e3b771b4584853ace19f5c", - "observation_times": [ - "2023-05-18 04:10:00", - "2023-05-18 04:15:00", - "2023-05-18 04:20:00" - ], - "parameters": { - "rain_fraction": 0.1277313232421875, - "mean_db": 4.680338144579789, - "stdev_db": 2.8004374842985467, - "beta_1": -3.3072923631770093, - "beta_2": -3.4072923631770093, - "reference_time": 106.34402803983085 - }, - "source_parameters": { - "mean_db": 4.680338144579789, - "stdev_db": 2.8004374842985467, - "rain_fraction": 0.1277313232421875, - "beta_1": -3.3072923670456933, - "beta_2": -3.4072923670456934, - "corl_zero": 106.3440280398298, - "lag_1": [ - 0.9770190561256513, - 0.9295443032634654, - 0.8483174997675328, - 0.5744147297713484, - 0.04464354973970493, - 0.0 - ], - "lag_2": [ - 0.9387203950980603, - 0.8197749713206718, - 0.6392620437195909, - 0.26310257812965865, - 0.0014952818008041493, - 0.0 - ] - }, - "moment_source": "ordinary", - "low_coverage_fallback": false, - "identical_parameters_maximum_error_mm_per_hour": 0.0, - "coupled_maximum_error_mm_per_hour": 0.12168902458015651, - "coupled_rmse_mm_per_hour": 0.00015763317986856312, - "rain_classification_changes": 0, - "total_output_pixels": 1572864, - "candidate_selection_changes": 0, - "maximum_wet_fraction_difference": 0.0, - "maximum_mean_rain_difference": 3.451076281324461e-05, - "maximum_wet_dbr_mean_difference": 0.00045008806622437447, - "maximum_wet_dbr_stdev_difference": 0.00025805494973374365, - "maximum_spectral_slope_difference": 0.00020641856839320738, - "maximum_lagrangian_correlation_difference": 7.662549719011924e-05 - }, - { - "case": "beta_1_low_2", - "seed": 20261015, - "input_sha256": "03ca980e074252c2d81881e11c5809b07d8f318e75e3b771b4584853ace19f5c", - "observation_times": [ - "2023-05-18 04:10:00", - "2023-05-18 04:15:00", - "2023-05-18 04:20:00" - ], - "parameters": { - "rain_fraction": 0.1277313232421875, - "mean_db": 4.680338144579789, - "stdev_db": 2.8004374842985467, - "beta_1": -3.3072923631770093, - "beta_2": -3.4072923631770093, - "reference_time": 106.34402803983085 - }, - "source_parameters": { - "mean_db": 4.680338144579789, - "stdev_db": 2.8004374842985467, - "rain_fraction": 0.1277313232421875, - "beta_1": -3.3072923670456933, - "beta_2": -3.4072923670456934, - "corl_zero": 106.3440280398298, - "lag_1": [ - 0.9770190561256513, - 0.9295443032634654, - 0.8483174997675328, - 0.5744147297713484, - 0.04464354973970493, - 0.0 - ], - "lag_2": [ - 0.9387203950980603, - 0.8197749713206718, - 0.6392620437195909, - 0.26310257812965865, - 0.0014952818008041493, - 0.0 - ] - }, - "moment_source": "ordinary", - "low_coverage_fallback": false, - "identical_parameters_maximum_error_mm_per_hour": 0.0, - "coupled_maximum_error_mm_per_hour": 1.0019848242084561, - "coupled_rmse_mm_per_hour": 0.001818660593679053, - "rain_classification_changes": 5, - "total_output_pixels": 1572864, - "candidate_selection_changes": 0, - "maximum_wet_fraction_difference": 4.57763671875e-05, - "maximum_mean_rain_difference": 0.0001150467184827253, - "maximum_wet_dbr_mean_difference": 0.0007748684326314148, - "maximum_wet_dbr_stdev_difference": 0.0005629812109284593, - "maximum_spectral_slope_difference": 0.0002413728122387937, - "maximum_lagrangian_correlation_difference": 6.59631984430975e-05 - } - ] -} diff --git a/doc/proposals/parametric_nowcasting.md b/doc/proposals/parametric_nowcasting.md deleted file mode 100644 index 231fc8c7..00000000 --- a/doc/proposals/parametric_nowcasting.md +++ /dev/null @@ -1,321 +0,0 @@ -# Scope proposal: parametric ensemble nowcasting - -Status: agreed development scope and broad public-interface outline for the -`parametric-nowcasting` branch; proposed for upstream discussion. This document -does not imply upstream approval. The forecast API described below is now -implemented in this branch. - -Date: 14 October 2026. - -## Objective and endpoint - -Contribute a self-contained parametric ensemble nowcasting method to pySTEPS, -derived from the scientific components of `pysteps_param`. - -The public endpoint is: - -**Three consecutive rainfall fields → internal preparation and parameter -estimation → a nowcast ensemble.** - -An ordinary caller supplies rainfall observations and forecast settings. The -engine performs transformation, motion estimation, cascade decomposition, -statistical parameter estimation, temporal modelling, and ensemble generation. -The caller does not need to prepare or persist intermediate artifacts. - -The contribution includes the scientific components needed to reach this -endpoint, with those components also available for independent use. Database -services and operational orchestration remain in projects that depend on -pySTEPS. This boundary follows the direction discussed in -[pySTEPS issue #472](https://github.com/pySTEPS/pysteps/issues/472). - -## Public forecast contract - -### Observations - -- Accept a floating-point NumPy `ndarray` with shape `(3, y, x)` containing rain - rates in `mm/h`. -- Order the fields from oldest to newest, at a regular observation interval. -- Require consistent grid geometry and units across all three fields. -- Treat the newest observation as the forecast reference time. -- Use the three fields for AR(2) initialization and lag-correlation estimation. - -The first implementation targets complete, finite input grids. Invalid or -missing observations produce a clear error rather than being silently converted -to dry rainfall. A confirmed dry input history is a valid physical case and must -have defined dry-output behaviour. Sparse rainfall and partially dry histories -require explicit, documented estimation safeguards. - -### Forecast settings - -The caller specifies the number of forecast timesteps, ensemble size, observation -timestep, and pixel size. The initial method advances at the observation timestep; -arbitrary output intervals and irregular lead times are subsequent extensions. -Units must be explicit and consistent with the surrounding pySTEPS interfaces. - -The method supplies documented defaults for rainfall thresholds, cascade -settings, statistical fitting, temporal scaling, and generator calibration. -Users can override these settings for their region and data. Random-number -control is part of the public interface. - -Motion is estimated internally by default. An optional externally supplied -velocity field, with shape `(2, y, x)` in pixels per observation timestep, -supports controlled comparisons and applications with an existing motion -estimate. Motion remains fixed through the initial forecast and is shared across -members. - -### Results - -Return rainfall in `mm/h` as a floating-point NumPy `ndarray` with shape -`(member, lead_time, y, x)`. The first forecast is one timestep after the newest -observation; the input observation is not included as forecast lead zero. - -The default return value is the ensemble. Optional diagnostics provide the -estimated parameters, effective settings, motion estimate, fitting and fallback -status, and relevant generation diagnostics. An incremental-output callback can -follow the existing pySTEPS forecast conventions and remain independent of any -storage backend. - -The precise function signature, keyword names, and diagnostic container are -implementation decisions to agree with upstream maintainers. These decisions -must preserve the three-field entry point and internal estimation. - -NumPy arrays are the computational input and output throughout the engine. -Timestep, pixel size, and method settings are supplied separately; the forecast -does not require labelled coordinates or an xarray object. Diagnostics can use -ordinary Python containers and NumPy arrays. Applications may wrap the resulting -ensemble in xarray for labelled coordinates, analysis, or storage. - -## Scientific method included in the contribution - -The engine assembles the following stages: - -1. Validate the rainfall history and forecast settings, and resolve the reference - defaults together with any user overrides. -2. Apply the configured rainfall transformation, retaining threshold and dry-value - metadata. The reference parametric method operates in dBR. -3. Estimate motion from the input history, unless the caller supplies velocity. -4. Decompose the observations into cascades and prepare the history needed by the - forecast recursion and Lagrangian correlation estimation. -5. Estimate rainfall occurrence, ordinary wet-pixel moments, latent marginal - parameters, and spectral slopes at the configured scale break. -6. Estimate cascade correlations and construct the reference persistence and - temporal scaling model using the method supported by the paper experiments. -7. Generate calibrated stochastic fields and decompose them into normalized - cascade innovations. -8. Advance each ensemble member using advection, the cascade AR model, and those - innovations; recompose and apply the reference marginal adjustment. -9. Prepare the adjusted field's cascade state for the next forecast step, - including the reference boundary taper, and return rainfall in physical units. - -The initial forecast holds the estimated statistical parameter set fixed through -lead time. Each member evolves independently from the same observed initial -history, with its own reproducible random stream. - -The port must retain the evaluated state-evolution method. In the existing -implementation, the marginal-adjusted field is tapered and decomposed again to -form the next state. This feedback is scientifically consequential and belongs -inside the engine. Adding only a noise option to native STEPS would not reproduce -the complete method. - -Generated stochastic rainfall fields retain their rainfall marginal; they are -not themselves guaranteed to have global mean zero and variance one. Cascade -decomposition supplies normalized innovations. Marginal adjustment and realised -spectral matching must be described with their finite-grid approximations and -diagnostics. - -## Reference defaults and scientific evidence - -The default configuration represents the method evaluated in the paper workflow -and WRNZ experiments. It provides a usable reference implementation and can be -modified by users who have investigated their region and data. - -Keep the following distinction explicit: - -| Estimated from the three observations | Configurable method settings and defaults | -| --- | --- | -| Rainfall occurrence and marginal parameters | Rain threshold, dry value, and marginal fitting cutoff | -| Spectral parameters | Scale break, spectral fitting conventions, and calibration corrections | -| Motion and observed cascade correlations | Motion-estimator settings, cascade definition, and temporal scaling settings | -| Reference persistence estimate | Estimation safeguards, constraints, and any regional fallback values | - -Ordinary retained wet-pixel moments and fitted latent Normal parameters are -different quantities and must remain distinguishable. Rain occurrence and -latent-distribution fitting may use different thresholds. Temporal correlation -times must retain an explicit definition and timestep convention; they are not -forecast-skill horizons. - -Numerical reference defaults will be transcribed from the identified evaluated -configuration during extraction. They must not be assembled from unrelated test -configurations or silently replaced with settings from another model revision. -Record the effective values and overrides in diagnostics. Regional empirical -fallbacks must be identified as such and be configurable. - -The evidence sources are: - -| Project | Role and starting points | -| --- | --- | -| `pysteps_param` | Numerical implementation, component tests, and method notes; especially `rain_stats_compute.py`, `stochastic_generator.py`, `shared_utils.py`, and `cli/nowcast.py` under `src/pysteps_param/` | -| `psteps_param_paper` | Paper methods, calibration investigations, reproducible figures, and scientific comparisons; start with `README.md`, `docs/`, and `workflows/` | -| `pysteps_param_wrnz_cases` | Regional experiments and forecast-output evaluation; start with `experiments/calibrated_field_generator_acceptance/` and `experiments/nowcast_v2_acceptance/` | - -The paper repository is locally named `psteps_param_paper` and was referred to as -`pysteps_param_paper` in the scope discussion. - -As of this proposal, the author reports that the manuscript is close to submission -to the *Quarterly Journal of the Royal Meteorological Society*. It is not yet a -published reference. The author also reports that subsequent output evaluation -following a minor bug finding supports continued use of the reference defaults. -The scope therefore does not require retuning those defaults before porting. - -During extraction, associate the reference configuration with exact source -revisions, experiment manifests, and relevant evaluation artifacts. Record the -bug and the implementation revision covered by the subsequent evaluation in that -evidence record. This proposal records the author's assessment; it does not claim -an independent rerun of that evaluation. Historical experiment results must retain -their original implementation and configuration identities. - -## Reusable components and integration - -The forecast function provides the normal user entry point. Its components remain -independently callable for scientific investigation and testing. - -| Component | Integration direction | -| --- | --- | -| Spectral estimation and prescribed-break fitting | Extend or reuse `pysteps.utils.spectral`, preserving documented numerical conventions | -| Marginal statistics and lower-truncated Normal fitting | Provide array-based statistical utilities used by the engine | -| Correlation times and temporal scaling | Integrate with `pysteps.timeseries` and reuse cascade-filter frequencies | -| Calibrated stochastic field generation | Add a distinct method under `pysteps.noise` with explicit target parameters and diagnostics | -| Marginal adjustment | Reuse or extend `pysteps.postprocessing.probmatching` where conventions agree | -| Forecast preparation and state evolution | Add a parametric method under `pysteps.nowcasts` and register it through the usual method interface | -| Scientific parameter representation | Use a small container separating estimated values, effective settings, and diagnostics from operational identities | - -These module placements are proposals, not a requirement to create a new package -hierarchy. Reuse existing primitives when doing so preserves the evaluated -method. Isolate kernels from database imports and avoid requiring pandas or -xarray solely to estimate parameters or generate an ensemble. - -The current `StepsParameters` object combines scientific values, database -metadata, and operational QA. Extract the scientific representation and the -method safeguards needed by the forecast. Storage identities and operational -audit persistence stay with the application. - -## Dependencies - -The planned scientific core can use NumPy, SciPy, and existing pySTEPS primitives. -The source implementation's package dependencies do not all become dependencies -of the upstream method: extract the required numerical operations together with -their array-based interfaces. - -| Dependency | Expected role and current repository support | -| --- | --- | -| NumPy | Arrays, FFT operations, and random streams; already a core pySTEPS dependency | -| SciPy | Distribution functions, interpolation, optimization, and advection sampling; already a core pySTEPS dependency | -| OpenCV | Used through the existing Lucas–Kanade motion method; already listed in the pySTEPS requirements and Conda environments, with availability checked by the motion implementation | -| pyproj and netCDF4 | Relevant to separate geometry and I/O work; already listed in the pySTEPS requirements and Conda environments | - -xarray is used for result packaging in the source estimators and I/O. Return -NumPy arrays and ordinary parameter containers from the extracted estimators. -pandas and statsmodels support parameter-table processing and NWP smoothing in -the source workflow; they are not required by the initial fixed-parameter -nowcast engine. MongoDB dependencies remain with the operational application. - -No new required package is currently anticipated for the scoped engine. -Additional dependencies or higher minimum versions may be introduced if a -specific implementation need emerges. Document the reason, distinguish required -from optional capabilities, and keep the appropriate package metadata, -requirements files, Conda environments, documentation, and CI configuration -consistent. Verify the engine using the supported pySTEPS environment rather -than relying on packages present only in the source project's environment. - -## Supporting contributions and scope boundaries - -A transformation object may be useful as a wrapper around existing pySTEPS -transformation functions. The endpoint only requires consistent transformation -and metadata handling; a new public object hierarchy is optional. - -CF NetCDF projection handling and interoperability improvements can be separate -I/O contributions. Existing pySTEPS NetCDF support should be extended rather than -duplicated. A validated domain-geometry adapter and example JSON configuration -may also be useful. Neither feature is a prerequisite for passing arrays to the -forecast engine. Geometry adapters must respect pySTEPS raster-boundary and -pixel-centre conventions. - -The initial contribution excludes: - -- MongoDB, GridFS, field catalogues, and persistent forecast-cycle records. -- Database configuration loading and database-oriented command-line workflows. -- Archive selection, storm catalogues, scheduling, operational reporting, and - application-specific viewers. -- Radar/NWP parameter blending and lead-dependent statistical parameter inputs. -- Velocity perturbations and externally supplied forecast-state initialization. -- A new operational framework or mandatory database, NetCDF, or JSON input path. - -Parameter blending is a subsequent scientific extension once the standalone -forecast engine is established. External projects can retain complete operational -workflows while using the upstream engine for computation. - -## Validation and completion criteria - -The contribution is complete when a caller can pass three observations and -forecast settings to the public method and obtain a reproducible ensemble without -precomputing parameters or accessing a database. - -Validation should establish: - -- Component correctness for spectra, marginal fitting, temporal correlations, - advection, and stochastic generation, with independent references where useful. -- Correct input ordering, output shape, units, and forecast lead-time convention. -- Explicit behaviour for dry and sparse histories, invalid input, and failed or - constrained parameter estimates. -- Reproducibility with a fixed seed, independent member state and random streams, - and consistent results when using incremental output. -- Numerical parity with the identified reference implementation using matching - inputs, defaults, random streams, and state evolution. Document justified - differences rather than assuming exact equality across numerical backends. -- End-to-end preservation of the evaluated rainfall distributions, spectra, and - temporal behaviour, using suitable retained cases from the existing evidence. -- Compatibility with the pySTEPS dependency and supported-Python conventions, - together with the required upstream test checks. - -The existing scientific evidence supports the reference defaults. Port validation -establishes that the new implementation preserves that method; it does not impose -a requirement to discover a new calibration or demonstrate superiority in every -region. Material numerical changes require proportionate evaluation. - -## Delivery and documentation - -Step 2, the reusable parameter estimators and temporal-model utilities, is -implemented in this branch. See the [extraction and validation record](parametric_estimators.md) -for the public helpers, numerical conventions, source provenance, and checks. -Step 3, the calibrated generator and shared marginal mapping, is also implemented; -see its [extraction and validation record](calibrated_generator.md). The complete -forecast engine and executable example are now implemented; see the -[engine extraction and validation record](parametric_engine.md) for the -public call, reference defaults, source comparisons, limitations, and a -separate assessment of the non-parametric inflow extension. -The non-parametric engine is deferred until the parametric contribution is in -pySTEPS; it is not part of the current delivery work. - -Use this Markdown proposal as the scope and design record for upstream discussion. -Link it from issue #472 or a subsequent contribution discussion when ready. - -Deliver the work in reviewable stages: - -1. Identify the evaluated reference revision and configuration, and agree the - public interface and diagnostic conventions. -2. Extract the reusable estimators and temporal-model utilities. -3. Integrate calibrated stochastic generation and its diagnostics. -4. Assemble the three-field forecast engine, preserving reference state evolution - and adding reproducible random-number control. -5. Complete parity checks, an executable forecast example, and user documentation. - -The published pySTEPS documentation currently uses reStructuredText. During -implementation, add API docstrings and reference entries in that format, a gallery -example starting from three rainfall fields, documentation of each reference -default and its override, and manuscript or publication citations as available. -The example should run without the source projects' database infrastructure. - -Keep detailed experiment artifacts with their research projects and provide a -concise, revision-specific validation summary in the upstream contribution. -Update this scope document if implementation changes the agreed endpoint or -scientific behaviour. diff --git a/doc/source/pysteps_reference/noise.rst b/doc/source/pysteps_reference/noise.rst index e38f405e..b67aadb5 100644 --- a/doc/source/pysteps_reference/noise.rst +++ b/doc/source/pysteps_reference/noise.rst @@ -2,7 +2,7 @@ pysteps.noise ============= -Implementation of deterministic and ensemble nowcasting methods. +Methods for generating spatially correlated noise and velocity perturbations. .. automodule:: pysteps.noise.fftgenerators diff --git a/doc/source/pysteps_reference/nowcasts.rst b/doc/source/pysteps_reference/nowcasts.rst index 5a4e2f3a..cb4287cf 100644 --- a/doc/source/pysteps_reference/nowcasts.rst +++ b/doc/source/pysteps_reference/nowcasts.rst @@ -8,8 +8,8 @@ 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 +.. automodule:: pysteps.nowcasts.linda .. automodule:: pysteps.nowcasts.parametric .. automodule:: pysteps.nowcasts.sprog .. automodule:: pysteps.nowcasts.sseps diff --git a/doc/source/user_guide/calibrated_generator.rst b/doc/source/user_guide/calibrated_generator.rst index 0c410c35..6ecddc00 100644 --- a/doc/source/user_guide/calibrated_generator.rst +++ b/doc/source/user_guide/calibrated_generator.rst @@ -1,12 +1,15 @@ Calibrated stochastic rainfall fields ===================================== -The calibrated generator creates a NumPy rainfall-marginal field from an explicit -statistical parameter mapping. It implements the ``inverse_calibrated_v2`` method -extracted from ``pysteps_param``. It is a component of the parametric -nowcast engine; ensemble state evolution and observation preparation are separate -stages. See :ref:`sphx_glr_auto_examples_plot_calibrated_noise_generator.py` -for a radar-data example using the new estimators, explicit moment selection, +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. @@ -49,12 +52,14 @@ 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. -Preserved reference behaviour ------------------------------ +Generation and spectral correction +---------------------------------- Independent ``U(0,1)`` samples are FFT-filtered using half the signed power slopes -as amplitude exponents. Both supplied slopes receive the default additive -correction -0.5; the location and scale receive no inverse correction. +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 @@ -65,7 +70,7 @@ 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 reference +``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 @@ -73,8 +78,8 @@ candidate is selected, retaining the first exact tie. If all spectral assessment 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. No reference defaults were retuned during -this port. +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 ---------------------------- @@ -111,22 +116,6 @@ 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 source post-AR dry policy is reproduced by explicitly passing -``min_rain_fraction=0.025``; that policy is distinct from stochastic generation. - -Validation ----------- - -Frozen complete fields and diagnostics compare the port with original functions -on seeded square/rectangular configurations. Tests also exercise filter exponents, -physical units, histogram cutoff semantics, random-stream independence, all -candidate-selection outcomes, and the source post-AR mapping. Fixtures include -source revisions and hashes and are included in wheel package data. - -Saved paper fields provide additional comparisons at two seeds. Identical target -parameters reproduce the original fields exactly in the recorded run. When the -ported estimators supply slightly different least-squares slopes, the complete -estimator-to-generator chain shows small numerical differences from histogram -interpolation. The revision-specific report under ``doc/proposals/`` records -those differences separately from identical-target generator parity. These checks -do not constitute a complete nowcast or forecast-skill evaluation. +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. diff --git a/doc/source/user_guide/parametric_estimators.rst b/doc/source/user_guide/parametric_estimators.rst index f4b2d643..192fee04 100644 --- a/doc/source/user_guide/parametric_estimators.rst +++ b/doc/source/user_guide/parametric_estimators.rst @@ -2,8 +2,7 @@ Parametric rainfall estimators ============================== The parametric estimators provide NumPy-based spatial statistics and temporal -model utilities extracted from ``pysteps_param``. They support the -three-observation forecast endpoint described in :doc:`parametric_nowcast` +model utilities. They support the three-observation forecast interface described in :doc:`parametric_nowcast` and can also be used independently. Spatial estimation @@ -11,10 +10,11 @@ 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. This example uses illustrative settings, not a complete reference -forecast configuration:: +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 @@ -22,7 +22,13 @@ forecast configuration:: 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 - transformed, metadata = dB_transform(rain, threshold=1.0, zerovalue=-0.1) + 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 ) @@ -40,8 +46,8 @@ 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 preserves the source method's logarithmic bin edges, -standardization, and unscaled FFT power convention. It differs from +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. @@ -63,21 +69,22 @@ 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"`` reproduces production zero filling -after each step. The explicit ``boundary="mask"`` alternative uses common -finite support across the aligned histories and changes the estimates; it is -not a silent replacement of the evaluated convention. +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. -The selected paper reference-time calculation uses the geometric mean of -scale-adjusted times from the first three levels. Every contributing time must -be positive and finite. Explicit ``levels=(0,)`` reproduces the production -largest-level reference convention. The scaling exponent is supplied separately; -the helper does not recalibrate it. Use nominal wavelengths from the actual +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 @@ -88,7 +95,7 @@ cascade filter:: 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.7718612877449645 + exponent = 0.8 # illustrative temporal scaling exponent reference_minutes = estimate_reference_correlation_time( observed_times_minutes, wavelengths_km, exponent ) @@ -103,19 +110,6 @@ nonperiodic lag-2 adjustment when inverting the target timescale. Requested time at or below one timestep use white-noise lag pairs, achieving one timestep. Diagnostics retain requested and achieved times and identify sub-timestep limits. -Validation and provenance -------------------------- - -The estimator tests compare against frozen numerical references evaluated from -the original source functions, together with independent full-FFT spectra, -known truncated-Normal samples, and explicit AR recurrence sums. Reference -metadata records source revisions and SHA-256 hashes. Tests require neither -the external repositories nor a database. The reference-generation script in -``pysteps/tests/data/generate_parametric_reference.py`` documents how to refresh -the fixture using the source project's environment. - -The engine defaults are traced to the evaluated paper and case-study -configuration in ``doc/proposals/parametric_engine.md``. These utilities leave -regional calibration settings and operational parameter-repair policies -explicit. The scope and extraction record are maintained under -``doc/proposals/``. +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. diff --git a/doc/source/user_guide/parametric_nowcast.rst b/doc/source/user_guide/parametric_nowcast.rst index 34c93d17..a00ceb30 100644 --- a/doc/source/user_guide/parametric_nowcast.rst +++ b/doc/source/user_guide/parametric_nowcast.rst @@ -4,7 +4,16 @@ Parametric ensemble nowcasting ``pysteps.nowcasts.parametric.forecast`` takes three consecutive rain-rate observations and returns an ensemble in mm/h. Transformation, motion estimation, cascade decomposition, statistical estimation, and forecast evolution happen -inside the call. No intermediate files or database are needed. +inside the call. Each ensemble member represents one possible evolution of the +rainfall field. + +The method describes rainfall using the fraction of rainy pixels, the +rain-intensity distribution, spectral slopes describing spatial structure, and +correlation times describing persistence at different spatial scales. It holds +these statistics and motion fixed while individual rainfall patterns evolve. +Rain intensity is modelled in dBR, defined as ``10*log10(R)`` for rain rate R in +mm/h. A cascade separates this transformed field into bands of spatial scales; +each band evolves with a second-order autoregressive, or AR(2), model. For observations stored in a v2 ``dataset`` with three time slices, ordered oldest to newest:: @@ -94,15 +103,14 @@ Callbacks continue to receive one NumPy array per lead. Native NetCDF export here writes the assembled dataset; incremental writing through the older exporter remains a separate interface. -Reference settings and regional overrides ------------------------------------------ +Settings and regional calibration +--------------------------------- -These defaults preserve the evaluated ``nowcast_v2`` engine configuration from -``psteps_param_paper/config/db/config.json`` and the WRNZ acceptance configuration, -combined with the identified numerical implementation and generation safeguards. -They are a configurable regional reference, not universal calibration constants. -The contribution record in ``doc/proposals/parametric_engine.md`` identifies -source revisions and validation limits. +The defaults provide a starting configuration. Rainfall thresholds, spectral +corrections, temporal scaling, and parameter bounds may need adjustment for the +region, spatial resolution, and observation interval. The low-rain-fraction +replacement values below were derived from Auckland rainfall data; they are +regional estimates rather than universal constants. Top-level settings: @@ -129,7 +137,7 @@ Top-level settings: - Boundary-mask Gaussian width, pixels * - ``min_rain_fraction`` - 0.025 - - Post-AR suppression, strictly below + - Return dry rainfall for target fractions strictly below this value ``estimation_kwargs`` accepts these overrides (unknown keys raise): @@ -158,15 +166,14 @@ positive scale, and within the configured normalized Euclidean distance of the ordinary pair. Otherwise the ordinary pair is used. Rain fraction and marginal moments are not clipped to a climatology. Below the low-coverage threshold, both slopes and reference time are replaced together before joint -bounds are applied. These regional fallback medians were saved in the source -``config/low_war_fallback.json`` from the Auckland archive; they can be replaced. - -The default reference time uses the largest cascade only, as in the evaluated -engine. The standalone estimator defaults to three levels, reflecting the later -selected paper analysis. To investigate that configuration, explicitly set -``reference_levels=(0, 1, 2)`` and ``temporal_exponent=0.7718612877`` together. -That later configuration has component support but does not inherit the forecast -validation of the evaluated default. No automatic fallback drops failed levels. +bounds are applied. The replacement small-scale slope and reference time are +medians from the Auckland archive; all three replacement values are configurable. + +The forecast estimates reference time from the largest cascade level by default. +The standalone estimator averages scale-adjusted times from three levels by +default. Set ``reference_levels=(0, 1, 2)`` to use that approach in a forecast, +and choose a temporal scaling exponent appropriate to the data. Every selected +level must have a valid estimate; failed levels are not dropped automatically. Timescales use the one-sided discrete AR(2) ACF sum including lag zero; they are persistence summaries rather than forecast-skill horizons. Nominal wavelengths come from the actual Gaussian filter. Scale-specific times at or below one @@ -179,8 +186,8 @@ The last two also control the post-AR marginal mapping. See :doc:`calibrated_generator` for calibration and finite-grid limitations. ``motion_kwargs`` overrides dense Lucas-Kanade settings. Mapping values such -as ``lk_kwargs`` or ``fd_kwargs`` update the corresponding reference submapping. -The reference has window (31,31), three pyramid levels, minimum eigenvalue +as ``lk_kwargs`` or ``fd_kwargs`` update the corresponding default submapping. +The defaults use window (31,31), three pyramid levels, minimum eigenvalue 3e-5, Shi-Tomasi corners (400, quality 0.025, separation 5), IDW interpolation (8 neighbors, power 1.25), outlier settings 3.5 and 20, and declustering scale 8. It is estimated from two images, once, with no velocity perturbations. @@ -203,9 +210,38 @@ prior can supply ``estimation_kwargs={"reference_time": 200.0}``. The raw failed estimate remains visible, and bounds still apply to the supplied prior. The post-AR fraction cutoff is separate: a valid positive target below 0.025 -produces dry output by the reference policy. Set ``min_rain_fraction=0.0`` to -disable that suppression. The operational source's forecast-eligibility gate -at 0.03 is outside this computational engine. Missing data is always an error. +produces dry output. Set ``min_rain_fraction=0.0`` to disable that suppression. +Missing data is always an error. + +Configured temporal fallback +---------------------------- + +A supplied temporal scaling model can replace unavailable persistence estimates:: + + ensemble, diagnostics = forecast( + dataset, + timesteps=12, + temporal_fallback={"lzero": 100.0, "tzero": 200.0}, + seed=42, + return_diagnostics=True, + ) + +These example values are illustrative. ``lzero`` is a positive reference +wavelength in km, and ``tzero`` is its positive correlation time in minutes. +If any observed AR(2) correlation time or the estimated reference time is invalid, +the method constructs correlation times for all levels from +``tzero * (wavelength / lzero)**temporal_exponent``. Requested times at or below +one observation interval resolve to white noise, as in the ordinary temporal +model. Diagnostics under ``temporal_selection`` record the selected source, +reason, and configuration. + +Supplying ``temporal_fallback`` disables the combined low-rain-fraction +replacement of both slopes and reference time, even when temporal fallback is +not activated. Spatial estimates must therefore be available. Missing input, +invalid motion, and unavailable marginal estimates remain errors. An activated +fallback uses its configured time without clipping to +``correlation_time_bounds``; spectral bounds still apply. This option cannot be +combined with an explicit ``estimation_kwargs["reference_time"]`` override. State evolution and boundaries ------------------------------ @@ -217,7 +253,7 @@ with linear interpolation and zero normalized anomalies outside the domain. Normalized stochastic innovations are then added everywhere, including inflow. There is no second output-advection pass or periodic wrap of rainfall. -After the reference global normalization across cascade levels and space, +After normalization across all cascade levels and spatial pixels together, recomposition uses the innovation cascade's moments. Histogram-based marginal mapping is applied to the recomposed field. The engine returns its inverse dBR transform, then multiplies the transformed field by the smoothed one-step @@ -226,9 +262,8 @@ feedback differs from native STEPS, which keeps its AR state separate from output probability matching. Raw calibrated fields retain a rainfall marginal; their cascade decomposition supplies normalized innovations. -All calculations and results are float64. They precede the source workflow's -NetCDF packing and storage. Outputs have no regional 200 mm/h cap; finite -generated values are required. No input arrays or settings are modified. +All calculations and results are float64, and generated values must be finite. +Input observations and settings are not modified. Randomness, diagnostics, and incremental output ----------------------------------------------- @@ -251,6 +286,6 @@ Its copy can be modified without affecting state or retained output. Set histories and current fields rather than the full forecast horizon. Diagnostics can still be requested. Callback exceptions propagate to the caller. -This first engine supports integer lead counts at the observation cadence, +The method supports integer lead counts at the observation cadence, fixed motion and parameters, complete grids, and AR(2). Irregular output times, motion perturbations, and lead-dependent parameter blending remain extensions. diff --git a/examples/plot_calibrated_noise_generator.py b/examples/plot_calibrated_noise_generator.py index 25d33bde..8bce1bbc 100644 --- a/examples/plot_calibrated_noise_generator.py +++ b/examples/plot_calibrated_noise_generator.py @@ -2,11 +2,14 @@ Calibrated stochastic rainfall fields ===================================== -This companion to :ref:`sphx_glr_auto_examples_plot_noise_generators.py` -uses the new marginal and -spectral estimators and calibrated generator on the same Swiss radar composite. -It compares generated rainfall distributions and radial spectra, then shows -how the raw fields become normalized cascade innovations. +This example creates random rainfall patterns with statistics similar to a +Swiss radar observation. It estimates the rainy area, rain-intensity +distribution, and spatial structure, then uses them to generate three fields. +The figures compare the generated fields with the observation. + +It uses the same radar image as +:ref:`sphx_glr_auto_examples_plot_noise_generators.py`. The final section shows +how to separate a generated field into spatial scales for use in a nowcast. Download the `pysteps example data `_ and configure the ``mch`` data source in ``pystepsrc``. MeteoSwiss provides the @@ -31,6 +34,7 @@ from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic from pysteps.utils.statistics import compute_field_statistics from pysteps.visualization import basemaps, plot_precip_field +from pysteps.xarray_helpers import geodata_from_dataset rain_threshold = 1.0 # mm/h scale_break = 20.0 # km, prescribed rather than estimated @@ -51,37 +55,41 @@ / "20160711" / "AQC161932100V_00005.801.gif" ) -rain, _, metadata = io.import_mch_gif( - str(filename), product="AQC", unit="mm", accutime=5.0 -) -rain, metadata = conversion.to_rainrate(rain, metadata) +dataset = io.import_mch_gif(str(filename), product="AQC", unit="mm", accutime=5.0) + +# Convert to mm/h, select the domain, and average to a 2 km grid. +dataset = conversion.to_rainrate(dataset) extent = (450000, 750000, 0, 300000) -rain, metadata = dimension.clip_domain(rain, metadata, extent) -rain, metadata = dimension.aggregate_fields_space(rain, metadata, 2000) +dataset = dimension.clip_domain(dataset, extent) +dataset = dimension.aggregate_fields_space(dataset, 2000) +precip_var = dataset.attrs["precip_var"] +rain = dataset[precip_var].values +geodata = geodata_from_dataset(dataset) if not np.isfinite(rain).all() or np.any(rain < 0): raise ValueError("The example requires complete nonnegative rain-rate data") -kmperpixel = metadata["xpixelsize"] / 1000.0 -observed, transformed_metadata = transformation.dB_transform( - rain, - metadata, +kmperpixel = abs(float(dataset.x.attrs["stepsize"])) / 1000.0 + +# Work in dBR = 10*log10(rain rate in mm/h). Extract arrays for the estimators. +transformed_dataset = transformation.dB_transform( + dataset, threshold=rain_threshold, zerovalue=10 * np.log10(rain_threshold) - 0.1, ) -threshold = transformed_metadata["threshold"] -zerovalue = transformed_metadata["zerovalue"] +observed = transformed_dataset[precip_var].values +threshold = transformed_dataset[precip_var].attrs["threshold"] +zerovalue = transformed_dataset[precip_var].attrs["zerovalue"] ############################################################################### -# Estimate marginal parameters and prescribed-break spectral slopes -# ----------------------------------------------------------------- +# Estimate rainfall statistics +# ---------------------------- # -# Ordinary moments describe retained wet pixels; latent moments describe the -# fitted parent Normal. The reference engine selects the latent pair when its -# normalized distance from the ordinary pair is at most 0.5, otherwise the -# ordinary pair. Make that caller decision explicit here: the generator itself -# does not select moments or apply regional parameter bounds. +# Calculate the fraction of rainy pixels and their mean and standard deviation. +# Also fit a Normal distribution that accounts for the lower fitting cutoff. +# Use its fitted parameters if they are close to the sample estimates; +# otherwise, use the sample estimates. This is the selection used by the nowcast. # -# Radial frequencies are in cycles per pixel, so convert the 20 km break to -# pixels for fitting. Pass physical km units to the generator later. +# Fit spectral slopes on either side of the prescribed 20 km break to describe +# spatial structure. Convert the break to pixels for this estimator. statistics = compute_field_statistics(observed, threshold, lower_truncation) frequency, power = rapsd_logarithmic(observed) @@ -92,6 +100,8 @@ latent = np.array([statistics["latent_mean"], statistics["latent_stdev"]]) if not np.isfinite(ordinary).all() or ordinary[1] <= 0: raise ValueError("Raining-pixel moments are unavailable") +# Measure the difference between parameter pairs in units of the sample +# standard deviation. The selection limit is configurable in the nowcast. valid_latent = np.isfinite(latent).all() and latent[1] > 0 moment_distance = ( np.linalg.norm(latent - ordinary) / ordinary[1] if valid_latent else np.inf @@ -115,15 +125,14 @@ # Generate calibrated rainfall fields # ----------------------------------- # -# The outputs are float64 fields in dBR retaining a rainfall marginal, including -# continuous dry anomalies. They are not globally standardized noise. Calibration -# applies the reference -0.5 correction to both slopes, assesses realized slopes -# on a thresholded copy, and reports whether its distance tolerance was met. -# A best-available field can be returned without meeting that tolerance. +# Generate three fields in dBR using separate seeds for repeatable results. +# Matching rain intensities changes spatial structure, so the generator adjusts +# the input spectral slopes and checks the resulting spectrum. Diagnostics +# report whether each field is close enough to its target slopes. # -# Use three reproducible local seeds and retain their diagnostics. Also generate -# an uncorrected field for the spectrum comparison. Its seed matches the first -# calibrated run's initial draw; retries can select a different candidate. +# Also generate a field without the correction to compare the spectra later. +# It uses the first calibrated run's initial seed, although retries may make +# that run select a different random field. fields, diagnostics = [], [] for member in range(num_realizations): @@ -154,14 +163,18 @@ zerovalue=zerovalue, ) -# Convert copies for rainfall plots. Keep the raw dBR arrays for decomposition. -rain_fields = [ - transformation.dB_transform(f, metadata=transformed_metadata, inverse=True)[0] - for f in fields -] -observed_rain = transformation.dB_transform( - observed, metadata=transformed_metadata, inverse=True -)[0] +# Convert to mm/h for plotting. Keep the original dBR arrays for decomposition. +rain_fields = [] +for field in fields: + generated = transformed_dataset.copy(deep=True) + generated[precip_var].data = field.copy() + generated = transformation.dB_transform(generated, inverse=True) + rain_fields.append(generated[precip_var].values) +observed_rain = transformation.dB_transform(transformed_dataset, inverse=True)[ + precip_var +].values + +# Plot the observation and generated rainfall fields. fig = plt.figure(figsize=(10, 8)) for index, (field, title) in enumerate( zip( @@ -170,9 +183,9 @@ ) ): ax = basemaps.plot_geography( - metadata["projection"], extent, scale="110m", subplot=(2, 2, index + 1) + dataset.attrs["projection"], extent, scale="110m", subplot=(2, 2, index + 1) ) - plot_precip_field(field, ax=ax, geodata=metadata, axis="off", title=title) + plot_precip_field(field, ax=ax, geodata=geodata, axis="off", title=title) plt.tight_layout() plt.show() @@ -180,11 +193,9 @@ # Compare rainfall occurrence and intensity distributions # ------------------------------------------------------- # -# Unconditional exceedance curves include all pixels in their denominator. -# Their value at 1 mm/h measures realized wet fraction. Histogram mapping and -# threshold crossings mean that occurrence and marginal moments are approximate; -# fitted parent-Normal moments are not the observed conditional moments. -# The generator's Normal-quantile clipping also limits its extreme tails. +# For each rain-rate threshold, plot the fraction of all pixels at or above it. +# At 1 mm/h, this is the rainy fraction. Distribution matching is approximate, +# so generated fractions and intensities can differ from their targets. colors = ["tab:blue", "tab:orange", "tab:green"] plotted_fields = [observed_rain, *rain_fields] @@ -214,11 +225,10 @@ # Compare the realized radial spectra # ----------------------------------- # -# Follow the calibration assessment: replace values <= the dBR threshold by -# the dry value on a copy before estimating each generated spectrum. The radial -# estimator standardizes its input internally; the plotted quantity is mean -# squared unscaled FFT amplitude per mode, not a physical spectral density. -# A radial spectrum does not preserve the observation's directional structure. +# Compare how variability is distributed across spatial scales. As in the +# calibration check, replace values at or below the rain threshold on a copy. +# The estimator subtracts the mean and divides by the standard deviation before +# averaging FFT power over directions; it does not describe rainfall orientation. fig, ax = plt.subplots() ax.loglog(kmperpixel / frequency, power, color="black", label="Observed") @@ -248,12 +258,13 @@ plt.show() ############################################################################### -# Decompose the raw field into normalized cascade innovations -# ----------------------------------------------------------- +# Separate the generated field into spatial scales +# ------------------------------------------------ # -# The nowcast engine decomposes the raw field, retaining its continuous dry -# anomalies. Normalization happens at each cascade level, rather than treating -# the rainfall-marginal field as globally standardized perturbation noise. +# A cascade splits the field into bands of spatial scales. Each band is then +# normalized to mean zero and standard deviation one. The nowcast uses these +# normalized bands as random innovations in its autoregressive update. +# Decompose the dBR field directly, retaining its values below the rain threshold. bandpass = filter_gaussian(fields[0].shape, 6) cascade = decomposition_fft(fields[0], bandpass, compute_stats=True, normalize=True) diff --git a/examples/plot_parametric_nowcast.py b/examples/plot_parametric_nowcast.py index 0d24bac7..21cb426d 100644 --- a/examples/plot_parametric_nowcast.py +++ b/examples/plot_parametric_nowcast.py @@ -4,7 +4,7 @@ This self-contained example builds three synthetic observations, then lets the parametric engine estimate motion and parameters and generate an ensemble. -It needs no radar archive or database. +It runs without downloading radar observations. """ import matplotlib.pyplot as plt @@ -24,8 +24,11 @@ threshold=0.0, seed=9, ) +# Convert the generated dBR values to rain rates for the forecast input. rain = 10 ** (transformed / 10) rain[rain < 1.0] = 0 +# Construct motion by shifting one pixel per interval. np.roll wraps at the +# array edge to build this artificial history; the forecast has open boundaries. observations = np.stack( [ np.roll(rain, -2, axis=1), @@ -61,7 +64,8 @@ ############################################################################### # Forecast directly in mm/h. Motion is estimated internally from the latest -# two fields; no transformed input or stored cascade/parameter state is needed. +# two fields. Each member starts from these observations and evolves with +# different random perturbations. forecast = nowcasts.get_method("parametric") ensemble, diagnostics = forecast( dataset, diff --git a/examples/plot_parametric_nowcast_swiss.py b/examples/plot_parametric_nowcast_swiss.py index 4366b8fe..77ab6892 100644 --- a/examples/plot_parametric_nowcast_swiss.py +++ b/examples/plot_parametric_nowcast_swiss.py @@ -27,10 +27,11 @@ from pysteps.visualization import basemaps, plot_precip_field from pysteps.xarray_helpers import geodata_from_dataset +# Set ensemble size, forecast length, and a seed for repeatable results. n_ens_members = 20 n_leadtimes = 6 seed = 24 -rain_threshold = 1.0 # mm/h: reference occurrence threshold of the engine +rain_threshold = 1.0 # mm/h date = datetime(2017, 1, 31, 12, 0) ############################################################################### @@ -61,7 +62,7 @@ # --------------------------------- # # The full Swiss composite has missing coverage outside the radar footprint. -# This first parametric engine requires complete finite grids. Select a 300 km +# The parametric method requires complete finite grids. Select a 300 km # square subdomain with complete coverage at all three times, in Swiss LV03 # coordinates (metres). Clip at the native resolution, then aggregate to 2 km # to limit computation and memory. Missing pixels are not replaced with zero. @@ -88,6 +89,7 @@ def make_map(subplot=(1, 1, 1)): ) +# Inspect the newest observation. plt.figure() plot_precip_field( rain.isel(time=-1).values, @@ -105,14 +107,14 @@ def make_map(subplot=(1, 1, 1)): # Pass linear rain rates directly: no external dBR transform or motion estimate # is needed. The returned dataset contains rain rates in mm/h with dimensions # (ens_number, time, y, x). Its first valid time is 12:05 and its last is 12:30. -# Pixel size and cadence are derived from the observation coordinates. -# Fixed motion and estimated statistical parameters are shared across leads; -# each member has its own reproducible random stream. +# Grid cell size and time step are derived from the observation coordinates. +# Motion and rainfall statistics are estimated once and held fixed. Each member's +# rainfall pattern evolves separately using its own random perturbations. +# Setting the seed makes these results reproducible. # -# This example uses the existing regional reference defaults, including the -# 1 mm/h occurrence threshold and 20 km spectral break. Applying them to this -# Swiss case illustrates the interface; it does not establish Swiss calibration -# or forecast skill. The STEPS tutorial uses a different occurrence threshold. +# The defaults include a 1 mm/h rain threshold and a 20 km spectral break. +# This example demonstrates the workflow; regional calibration and forecast +# skill require a separate assessment. forecast = nowcasts.get_method("parametric") ensemble, diagnostics = forecast( @@ -148,7 +150,7 @@ def make_map(subplot=(1, 1, 1)): ax=make_map(), geodata=geodata, axis="off", - title=f"Parametric ensemble mean (+{lead_minutes:g} min)", + title=f"Ensemble mean parametric nowcast (+{lead_minutes:g} min)", ) plt.show() @@ -174,9 +176,8 @@ def make_map(subplot=(1, 1, 1)): # Derive exceedance probabilities # ------------------------------- # -# Use a probability threshold consistent with the engine's occurrence threshold. -# The ensemble fraction exceeding 1 mm/h is an estimated exceedance probability, -# not an independently calibrated probability product. +# Estimate the probability of rain at or above 1 mm/h as the fraction of members +# meeting that threshold. Use the same threshold as the forecast. probability_threshold = 1.0 # mm/h probabilities = (forecast_rain.isel(time=-1) >= probability_threshold).mean( @@ -199,12 +200,9 @@ def make_map(subplot=(1, 1, 1)): # Animate observations followed by the first ensemble member # ---------------------------------------------------------- # -# Keep the same grid and colour scale for the three QPE observations and six -# forecast fields. Index zero selects the first member, whose ens_number -# coordinate is one. The title distinguishes observations from forecasts and -# displays actual valid times. QPE retains the measured rain rates; forecasts -# apply the engine's 1 mm/h occurrence threshold. No intermediate fields are -# interpolated. Pause briefly at the last observation and the last forecast. +# Show the three radar observations followed by six forecasts on the same map +# and colour scale. Array index zero selects ensemble member number one. +# Display the valid times and pause at the last observation and forecast. animation_member_index = 0 animation_fields = np.concatenate( @@ -274,12 +272,10 @@ def update_animation(frame): # Export the complete forecast dataset # ------------------------------------- # -# The native xarray NetCDF writer preserves the ensemble and time coordinates, -# geographic information, and rainfall attributes. Lossless compression retains -# the float64 values exactly. A temporary file makes the example repeatable; -# replace output_path with a persistent location to keep the forecast. -# This preserves the available projection metadata without adding a CF grid -# mapping for projections that the upstream converter does not yet support. +# Save the dataset with lossless compression and check that reopening preserves +# its values, coordinates, and attributes. Choose a persistent output path to +# keep the file. Projection metadata are retained as supplied; export does not +# add missing CF projection mappings. with TemporaryDirectory(prefix="pysteps-parametric-swiss-") as output_directory: output_path = Path(output_directory) / "parametric_nowcast_swiss.nc" diff --git a/pysteps/noise/parametric.py b/pysteps/noise/parametric.py index f8cf686a..2d142903 100644 --- a/pysteps/noise/parametric.py +++ b/pysteps/noise/parametric.py @@ -28,12 +28,14 @@ def _finite(name, value): + """Validate and return a finite real scalar.""" if not np.isscalar(value) or np.iscomplexobj(value) or not np.isfinite(value): raise ValueError(f"{name} must be a finite real scalar") return float(value) def _shape(shape): + """Validate and return two positive grid dimensions in (y, x) order.""" try: shape = tuple(shape) except TypeError: @@ -48,6 +50,7 @@ def _shape(shape): def _parameters(parameters): + """Copy and validate the five rainfall-generation parameters.""" if not isinstance(parameters, Mapping): raise ValueError("parameters must be a mapping") names = ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db") @@ -62,6 +65,7 @@ def _parameters(parameters): def _random_generator(rng, seed): + """Return the supplied random stream or create a local seeded stream.""" if rng is not None: if seed is not None: raise ValueError("supply rng or seed, not both") @@ -101,6 +105,7 @@ def power_law_filter(shape, beta_1, beta_2=None, kmperpixel=1.0, scale_break=Non raise ValueError("kmperpixel must be positive") if (beta_2 is None) != (scale_break is None): raise ValueError("provide beta_2 and scale_break together") + # Power is squared amplitude, so the filter uses half the power exponent. fy = np.fft.fftfreq(shape[0], d=spacing) fx = np.fft.fftfreq(shape[1], d=spacing) radius = np.sqrt(fy[:, None] ** 2 + fx[None, :] ** 2) @@ -130,6 +135,14 @@ def power_law_filter(shape, beta_1, beta_2=None, kmperpixel=1.0, scale_break=Non def _field_from_filter( spectrum_filter, parameters, threshold, rng, zerovalue, nbins, eps ): + """Generate a correlated field and match its rainfall distribution. + + Apply the full-FFT amplitude filter to uniform white noise, then map the + result to the supplied rain fraction and Normal intensity parameters. + Return a (y, x) array in the same transformed units as threshold; values + below the threshold remain continuous for later cascade decomposition. + """ + # Filtering introduces spatial correlation before intensity matching. white_noise = rng.uniform(low=0, high=1, size=spectrum_filter.shape) filtered = np.fft.ifft2( np.fft.fft2(white_noise, spectrum_filter.shape) * spectrum_filter @@ -159,7 +172,7 @@ def generate_stochastic_field( nbins=500, eps=0.001, ): - """Generate a rainfall-marginal field without inverse spectral correction. + """Generate a random rainfall field without spectral bias correction. Parameters ---------- @@ -168,12 +181,12 @@ def generate_stochastic_field( parameters: mapping Finite beta_1, beta_2, rain_fraction, mean_db, and positive stdev_db. The last two are caller-selected parent-Normal generation moments. - The mapping is not modified; no regional QA or parameter clipping occurs. + The mapping is not modified; no regional parameter selection or clipping occurs. kmperpixel, scale_break: float Positive pixel spacing and spectral break wavelength, both in km. threshold: float - Finite rain/no-rain threshold in transformed units (dBR for the reference - method). The returned field has the same transformed units. + Finite rain/no-rain threshold in transformed units (dBR when used for + parametric nowcasting). The returned field has the same transformed units. rng: numpy.random.Generator or RandomState, optional Caller-owned random stream; advanced in place. seed: int or SeedSequence, optional @@ -216,6 +229,12 @@ def generate_stochastic_field( def _estimate_beta(field, threshold, zerovalue, scale_break_pixels): + """Estimate two spectral slopes after replacing subthreshold values. + + Use a copy for assessment so the generated field retains its continuous + values below threshold. The break wavelength is in pixels. Return two + NaNs if the spectrum cannot be estimated or fitted. + """ truncated = field.copy() truncated[truncated <= threshold] = zerovalue try: @@ -245,18 +264,28 @@ def generate_calibrated_field( eps=0.001, return_diagnostics=False, ): - """Generate a field with inverse calibration and assessed realised spectral slopes. + """Generate a random rainfall field with spectral bias correction. Parameters ---------- - shape, parameters, kmperpixel, scale_break, threshold: - As in :func:`generate_stochastic_field`. Each dimension must contain at - least four pixels for spectral assessment. Parameters are not modified. - rng, seed, zerovalue, nbins, eps: - As in :func:`generate_stochastic_field`. + shape: tuple of int + Output shape (y, x), with at least four pixels along each axis. + parameters: mapping + Target beta_1, beta_2, rain_fraction, mean_db, and stdev_db, as in + :func:`generate_stochastic_field`. The mapping is not modified. + kmperpixel, scale_break: float + Positive pixel spacing and spectral break wavelength, both in km. + threshold: float + Rain/no-rain threshold in transformed units, also used for assessment. + rng: numpy.random.Generator or RandomState, optional + Caller-owned random stream, advanced in place. Supply rng or seed. + seed: int or SeedSequence, optional + Seed for a local random stream when rng is absent. + zerovalue: float, optional + Dry value below threshold, default threshold-0.1. spectral_bias: float, optional - Additive correction to both input power slopes (default -0.5). Location - and scale receive no inverse correction in this v2 method. + Additive correction to both input power slopes (default -0.5). The Normal + location and scale are used as supplied. low_war_threshold: float, optional Fractions at or below this value permit retries (default 0.1). max_attempts: int, optional @@ -264,9 +293,13 @@ def generate_calibrated_field( distance_threshold: float, optional Accept when sqrt(sum((estimated_beta-target_beta)**2/beta_variances)) is at or below this nonnegative tolerance (default 1). - beta_variances: array_like + beta_variances: array_like, optional Two positive finite calibration error variances. Defaults are (0.067738, 0.076405); these weights are empirical, not universal errors. + nbins: int, optional + Number of distribution-matching histogram bins (default 500). + eps: float, optional + Normal-quantile probability clip (default 0.001). return_diagnostics: bool, optional Also return selected spectral estimates, distance, attempted/selected candidate indices, tolerance status, targets, effective calibration @@ -284,11 +317,14 @@ def generate_calibrated_field( Notes ----- - Preserves ``inverse_calibrated_v2`` defaults and selection policy. Calibration - need not meet its tolerance; the outcome is explicit. A confirmed zero target - returns dry output with zero attempts and no spectral-success claim. Invalid - parameters or an unavailable positive-fraction marginal mapping raise errors. - This function does not perform operational moment selection or slope clipping. + Distribution matching can change a filtered field's spectrum. The additive + slope correction compensates for this effect empirically; its default and + the error variances may need calibration for other rainfall data. + Calibration need not meet its tolerance; diagnostics report the outcome. + A confirmed zero target returns dry output with zero attempts and no + spectral-success claim. Invalid parameters or an unavailable + positive-fraction marginal mapping raise errors. + This function does not perform moment selection or slope clipping. """ parameters = _parameters(parameters) shape = _shape(shape) @@ -316,6 +352,7 @@ def generate_calibrated_field( zerovalue = ( threshold - 0.1 if zerovalue is None else _finite("zerovalue", zerovalue) ) + # Adjust input slopes before mapping, then assess against the original targets. calibrated = dict( parameters, beta_1=parameters["beta_1"] + spectral_bias, @@ -371,6 +408,7 @@ def output(candidate, status, tolerance_achieved): eps=eps, ) return output((field, np.nan, np.nan, np.nan, 0), "dry", False) + # Retry sparse fields, whose realised spectra vary more between draws. best = last = None limit = max_attempts if parameters["rain_fraction"] <= low_war_threshold else 1 for attempt in range(1, limit + 1): @@ -393,6 +431,8 @@ def output(candidate, status, tolerance_achieved): return output(last, "tolerance_achieved", True) if np.isfinite(distance) and (best is None or distance < best[3]): best = last + # If tolerance is missed, keep the closest measurable candidate. If none + # can be assessed, return the last field with an unavailable-spectrum status. return output( best if best is not None else last, "best_available" if best is not None else "unavailable_spectrum", diff --git a/pysteps/nowcasts/_cascade_advection.py b/pysteps/nowcasts/_cascade_advection.py index eb285c1e..f54920a0 100644 --- a/pysteps/nowcasts/_cascade_advection.py +++ b/pysteps/nowcasts/_cascade_advection.py @@ -11,7 +11,23 @@ def prepare_advection_coordinates(velocity, ar_order=2): - """Cache each lag using integer unit integration, in scipy (y, x) order.""" + """Prepare backward trajectories for sampling previous cascade states. + + Parameters + ---------- + velocity: array_like + Fixed x and y displacements with shape (2, y, x), in pixels per + observation interval. All values must be finite. + ar_order: {1, 2}, optional + Number of previous states to advect (default 2). + + Returns + ------- + coordinates: dict + Read-only arrays of shape (2, y, x), keyed by integer lag. Components + are in (y, x) order for scipy.ndimage.map_coordinates. The two-step + trajectory uses two unit integration steps with the same velocity. + """ velocity = np.asarray(velocity, dtype=float) if ( velocity.ndim != 3 @@ -27,13 +43,29 @@ def prepare_advection_coordinates(velocity, ar_order=2): coordinates = {} for lag in range(1, ar_order + 1): _, displacement = extrapolate(None, velocity, lag, return_displacement=True) + # Extrapolation returns (x, y) displacements; SciPy samples in (y, x). coordinates[lag] = (xy + displacement)[::-1].copy() coordinates[lag].setflags(write=False) return coordinates def advect_field(field, coordinates): - """Sample a finite 2D anomaly with the reference constant-zero boundary.""" + """Advect a normalized cascade field using backward sampling coordinates. + + Parameters + ---------- + field: ndarray + Finite two-dimensional normalized cascade field in (y, x) order. + coordinates: ndarray + Sampling positions with shape (2, y, x), in (y, x) component order. + + Returns + ------- + advected: ndarray + Linearly interpolated field. Samples outside the grid are zero, + representing the cascade level's mean. Random innovations are added + separately during forecasting, including at these inflow pixels. + """ if not np.isfinite(field).all(): raise ValueError("field contains non-finite values") return map_coordinates( diff --git a/pysteps/nowcasts/interface.py b/pysteps/nowcasts/interface.py index 552befb5..0ba67745 100644 --- a/pysteps/nowcasts/interface.py +++ b/pysteps/nowcasts/interface.py @@ -12,15 +12,16 @@ The dataset contains precipitation, spatial coordinates, observation times, projection metadata and, when supplied, velocity_x and velocity_y variables. The global ``precip_var`` attribute identifies the precipitation variable. -The output dataset contains dimensions (time, y, x) for deterministic methods +The output is an xarray.Dataset with dimensions (time, y, x) for deterministic methods or (ens_number, time, y, x) for ensemble methods. Method-specific forecast functions document their input history and optional settings. The ``parametric`` method requires exactly three complete, untransformed rain-rate observations in mm/h on a regular grid with square pixels. Pixel -size and cadence are derived from coordinates; the time stepsize is in seconds. +size and time step are derived from coordinates; the time coordinate's +``stepsize`` attribute is in seconds. It estimates motion if velocity variables are absent and returns rain rates in -mm/h. Its first release accepts integer timesteps only. +mm/h. It accepts integer lead counts at the observation interval. .. autosummary:: :toctree: ../generated/ diff --git a/pysteps/nowcasts/parametric.py b/pysteps/nowcasts/parametric.py index c93285c7..b1655a0d 100644 --- a/pysteps/nowcasts/parametric.py +++ b/pysteps/nowcasts/parametric.py @@ -2,7 +2,7 @@ pysteps.nowcasts.parametric =========================== -Three-observation parametric ensemble nowcasting, derived from pysteps_param. +Generate parametric ensemble nowcasts from three rainfall observations. .. autosummary:: :toctree: ../generated/ @@ -36,7 +36,8 @@ from ._cascade_advection import advect_field, prepare_advection_coordinates -# Evaluated nowcast_v2 settings, not the later plotting-only timescale fit. +# Configurable estimation defaults. Low-rain-fraction replacements are regional +# values from Auckland rainfall data; see the parametric nowcasting user guide. _ESTIMATION_DEFAULTS = dict( lower_truncation=1.25, temporal_exponent=0.81, @@ -75,6 +76,7 @@ def _finite(name, value): + """Validate and return a finite real scalar.""" if not np.isscalar(value) or np.iscomplexobj(value): raise ValueError(f"{name} must be a finite real scalar") try: @@ -87,6 +89,7 @@ def _finite(name, value): def _positive(name, value): + """Validate and return a positive real scalar.""" value = _finite(name, value) if value <= 0: raise ValueError(f"{name} must be positive") @@ -94,6 +97,7 @@ def _positive(name, value): def _integer(name, value, minimum=1): + """Validate an integer at or above the specified minimum.""" if ( isinstance(value, (bool, np.bool_)) or not isinstance(value, (int, np.integer)) @@ -104,6 +108,7 @@ def _integer(name, value, minimum=1): def _options(name, supplied, defaults): + """Copy defaults and apply overrides, rejecting unknown setting names.""" if supplied is None: return deepcopy(defaults) if not isinstance(supplied, Mapping): @@ -117,6 +122,7 @@ def _options(name, supplied, defaults): def _estimation_settings(supplied, threshold, n_levels): + """Validate estimation settings against the threshold and cascade count.""" settings = _options("estimation_kwargs", supplied, _ESTIMATION_DEFAULTS) for key in settings: if key not in ( @@ -169,6 +175,7 @@ def _estimation_settings(supplied, threshold, n_levels): def _noise_settings(supplied): + """Validate spectral-calibration and distribution-matching settings.""" settings = _options("noise_kwargs", supplied, _NOISE_DEFAULTS) for key in ("spectral_bias", "low_war_threshold", "distance_threshold", "eps"): settings[key] = _finite(key, settings[key]) @@ -195,7 +202,7 @@ def _noise_settings(supplied): def _decompose(field, bp_filter): - """Reference normalization, with zero anomalies for exactly constant levels.""" + """Normalize each cascade level, using zero anomalies for constant levels.""" result = decomposition_fft(field, bp_filter, compute_stats=True, normalize=False) levels = result["cascade_levels"] if np.ptp(field) == 0: @@ -210,7 +217,13 @@ def _decompose(field, bp_filter): def _generation_parameters(statistics, spectrum, reference_time, settings): - """Select moments, replace low-coverage triplet, then apply joint bounds.""" + """Select intensity parameters and constrain slopes and persistence. + + Prefer fitted Normal parameters when close to the wet-pixel sample moments. + For low rain fractions, replace both spectral slopes and reference time + together. Apply the slope and time bounds afterwards, retaining the raw + estimates and each selection in the returned audit dictionary. + """ mu, sigma = statistics["ordinary_mean"], statistics["ordinary_stdev"] if not np.isfinite(mu) or not np.isfinite(sigma) or sigma <= 0: raise ValueError( @@ -290,6 +303,42 @@ def _advance( noise_settings, min_rain_fraction, ): + """Advance one member by one observation interval. + + Parameters + ---------- + histories: sequence of dict + Two normalized cascade decompositions, newest first. Each contains + cascade_levels with shape (level, y, x). Histories are not modified. + parameters: mapping + Selected rain fraction, Normal intensity parameters, and spectral slopes. + phi: ndarray + Array of shape (level, 3): lag-1 coefficient, lag-2 coefficient, and + innovation coefficient returned by estimate_ar_params_yw. + bp_filter: dict + Gaussian cascade filter used for both histories and random fields. + coordinates: dict + Backward sampling coordinates for lags 1 and 2, in (y, x) order. + rng: numpy.random.Generator + Member-specific random stream, advanced in place. + threshold, zerovalue: float + Rain occurrence threshold and dry value in dBR. + kmperpixel, scale_break: float + Pixel spacing and spectral break wavelength in km. + noise_settings: dict + Calibration and histogram-mapping options. + min_rain_fraction: float + Target fraction below which distribution matching returns dry output. + + Returns + ------- + field: ndarray + Forecast field in dBR with shape (y, x), before boundary tapering. + diagnostics: dict + Calibration results for the random field used in this update. + """ + # Generate rainfall variability, then normalize its cascade levels to obtain + # the innovations used in the autoregressive update. noise, diagnostics = generate_calibrated_field( bp_filter["shape"], parameters, @@ -303,6 +352,8 @@ def _advance( ) innovation = _decompose(noise, bp_filter) levels = np.empty_like(innovation["cascade_levels"]) + # Bring both previous states to the forecast time before the AR(2) update. + # Zero anomalies outside the grid allow innovations to populate inflow. for k in range(len(levels)): levels[k] = ( phi[k, 0] * advect_field(histories[0]["cascade_levels"][k], coordinates[1]) @@ -310,7 +361,8 @@ def _advance( * advect_field(histories[1]["cascade_levels"][k], coordinates[2]) + phi[k, 2] * innovation["cascade_levels"][k] ) - # Preserve source global normalization over levels AND space. + # Normalize over all levels and spatial pixels together. This couples the + # levels and differs from normalizing each updated level separately. stdev = np.std(levels) if stdev > 0.1: levels = (levels - np.mean(levels)) / stdev @@ -322,6 +374,8 @@ def _advance( means=innovation["means"], stds=innovation["stds"], ) + # Recompose with the innovation moments and restore the target rainfall + # distribution. The caller feeds this adjusted field into the next update. field = match_rainfall_distribution( recompose_fft(updated), parameters["rain_fraction"], @@ -337,7 +391,13 @@ def _advance( def _velocity_from_dataset(dataset): - """Read fixed pixel displacements without xarray coordinate alignment.""" + """Read fixed x/y displacements as an array with shape (2, y, x). + + Return None if both velocity variables are absent. Otherwise require both + components, finite values, and dimensions (y, x) in either order. Values + are displacements in pixels per observation interval. Extract arrays before + stacking to avoid automatic xarray coordinate alignment. + """ names = ("velocity_x", "velocity_y") present = [name in dataset.data_vars for name in names] if not any(present): @@ -365,7 +425,14 @@ def _velocity_from_dataset(dataset): def _prepare_dataset(dataset, kmperpixel, timestep): - """Validate the physical grid and copy observations into canonical order.""" + """Validate the grid and copy observations into (time, y, x) order. + + Require three finite rain-rate fields, regularly spaced observation times, + and a regular grid with square pixels. Coordinate stepsize attributes must + match the actual spacing. Return the copied dataset, optional velocity, + pixel size in km, and observation interval in minutes. Explicit pixel-size + or interval overrides must agree with the coordinates. + """ if not isinstance(dataset, xr.Dataset): raise ValueError("dataset must be an xarray.Dataset") precip_var = dataset.attrs.get("precip_var") @@ -506,20 +573,21 @@ def forecast( Seed for independent member streams. Global RNG and supplied SeedSequence are not mutated. Prefix members agree when ensemble size is increased. motion_kwargs: dict, optional - Overrides of the reference dense Lucas-Kanade configuration. Ignored + Overrides of the default dense Lucas-Kanade configuration. Ignored when velocity is supplied; diagnostics record that motion source. estimation_kwargs: dict, optional Override marginal fitting, temporal scaling, moment selection, bounds, - and regional low-coverage fallbacks. See the parametric-nowcast user - guide for all keys and defaults. Reference time is in minutes. + and regional low-coverage fallbacks. See + :doc:`/user_guide/parametric_nowcast` for all keys and defaults. + Reference time is in minutes. temporal_fallback: dict, optional Configured temporal-only fallback with positive ``lzero`` (km) and ``tzero`` (minutes). If any observed AR(2) pair or the estimated reference time is invalid, use tzero*(wavelength/lzero)**temporal_exponent to construct all model correlations. Complete input grids and valid marginal/spatial estimates remain required. This mode disables the - legacy low-coverage spectral/persistence replacement. Cannot be combined - with an explicit estimation_kwargs reference_time override. + combined low-rain-fraction slope and reference-time replacement. Cannot + be combined with an explicit estimation_kwargs reference_time override. noise_kwargs: dict, optional Calibration controls for generate_calibrated_field; nbins and eps also control post-AR marginal mapping. Randomness is controlled by seed. @@ -545,16 +613,18 @@ def forecast( Notes ----- Fixed statistical parameters and motion are used throughout the forecast. - Eulerian histories are advected by their respective one/two-step maps before - adding normalized innovations. Marginal correction, boundary taper, and - re-decomposition feed back into the state; this is not native STEPS with a - different noise option. Zero normalized anomalies at inflow are supplemented - by innovations. A newest field with no rain at the occurrence threshold - gives a dry ensemble even if older fields were wet. Earlier dry fields use + The two previous cascade states are advected to the forecast time before + adding normalized random innovations. Distribution matching, boundary taper, + and re-decomposition feed back into the next state. In STEPS, output + probability matching is separate from the autoregressive state. Zero + normalized anomalies at inflow are supplemented by innovations. A newest + field with no rain at the occurrence threshold gives a dry ensemble even + if older fields were wet. Earlier dry fields use zero anomalies. Unavailable positive-rain moments raise rather than becoming dry forecasts. Dataset data are loaded eagerly; the numerical engine uses - NumPy arrays. No database is needed. + NumPy arrays. """ + # Resolve grid geometry and cadence before working with NumPy arrays. dataset, velocity, kmperpixel, timestep = _prepare_dataset( dataset, kmperpixel, timestep ) @@ -617,6 +687,8 @@ def forecast( velocity = np.array(velocity, dtype=float, copy=True) if velocity.shape != (2, *shape) or not np.isfinite(velocity).all(): raise ValueError("velocity must be finite with shape (2, y, x)") + # Give each member a separate stream; increasing ensemble size leaves the + # existing member streams unchanged. Copy a supplied SeedSequence first. if isinstance(seed, np.random.SeedSequence): root_seed = np.random.SeedSequence( seed.entropy, spawn_key=seed.spawn_key, pool_size=seed.pool_size @@ -667,6 +739,8 @@ def forecast( dataset[precip_var].attrs.update( units="mm/h", threshold=rain_threshold, zerovalue=0.0 ) + # Estimate occurrence and intensity from the newest observation. A dry + # newest field has a defined result without estimating motion or spectra. statistics = compute_field_statistics( transformed[-1], threshold, estimation["lower_truncation"] ) @@ -702,6 +776,7 @@ def forecast( if velocity is None or velocity.shape != (2, *shape): raise ValueError("Motion estimator returned invalid velocity") diagnostics["velocity"] = velocity.copy() + # Estimate scale-dependent persistence from the three aligned observations. bp_filter = filter_gaussian(shape, n_cascade_levels) observed = [_decompose(field, bp_filter) for field in transformed] wavelengths = kmperpixel / bp_filter["central_freqs"] @@ -741,6 +816,7 @@ def forecast( configuration=deepcopy(temporal_fallback), ) temporal_selection["selected_reference_time_minutes"] = reference_time + # Fit spatial structure and select the parameters held fixed for all leads. frequency, power = rapsd_logarithmic(transformed[-1]) spectrum = fit_power_law_spectrum(frequency, power, scale_break / kmperpixel) parameters, safeguards = _generation_parameters( @@ -760,6 +836,7 @@ def forecast( timestep, return_diagnostics=True, ) + # Convert model lag correlations to AR coefficients and innovation weights. phi = np.array([estimate_ar_params_yw(pair) for pair in model_lags]) diagnostics.update( status="ok", @@ -805,6 +882,8 @@ def forecast( physical[physical < rain_threshold] = 0 if not np.isfinite(physical).all(): raise ValueError("Generated rainfall contains nonfinite values") + # Return physical rainfall before tapering. Only the feedback state + # is tapered and decomposed for the next forecast step. fields[member] = physical histories[member] = [ _decompose(field * taper, bp_filter), diff --git a/pysteps/postprocessing/probmatching.py b/pysteps/postprocessing/probmatching.py index 3a020fb1..457b2a45 100644 --- a/pysteps/postprocessing/probmatching.py +++ b/pysteps/postprocessing/probmatching.py @@ -53,10 +53,10 @@ def match_rainfall_distribution( zerovalue: float, optional Finite dry value below threshold, default threshold-0.1. Used for an explicit dry result; positive-fraction mapping retains continuous dry - anomalies below threshold, as in the source generator. + values below threshold for subsequent cascade decomposition. min_rain_fraction: float, optional Explicit dry-output policy for fractions below this value. Default 0 - suppresses no positive fraction. The source post-AR adjustment uses 0.025. + suppresses no positive fraction. The parametric forecast uses 0.025. nbins: int, optional Number of histogram bins (default 500). eps: float, optional @@ -70,7 +70,7 @@ def match_rainfall_distribution( Notes ----- - Preserves the source ``normalize_stoch_field`` mapping: find the last bin + Find the last bin whose cumulative probability is <= 1-rain_fraction, shift its upper edge to threshold, and map the strictly-above-threshold sample's binned CDF to clipped Normal quantiles using bin-centre linear interpolation. Wet mapped @@ -112,6 +112,8 @@ def match_rainfall_distribution( if rain_fraction == 0 or rain_fraction < min_rain_fraction: return np.full_like(field, zerovalue) + # Shift a histogram cutoff to the occurrence threshold to select wet pixels. + # Values below it remain continuous rather than being set to zerovalue. distribution = sstats.cumfreq(field, numbins=nbins) edges = distribution.lowerlimit + np.arange(nbins + 1) * distribution.binsize cdf = distribution.cumcount / distribution.cumcount[-1] @@ -122,6 +124,8 @@ def match_rainfall_distribution( sample = mapped[wet] if sample.size < 2 or np.ptp(sample) == 0: raise ValueError("mapping produced too few distinct raining pixels") + # Map the wet sample through its conditional CDF to Normal quantiles. + # Clipping avoids infinite quantiles; mapped values may cross the threshold. distribution = sstats.cumfreq(sample, numbins=nbins) edges = distribution.lowerlimit + np.arange(nbins + 1) * distribution.binsize centres = 0.5 * (edges[:-1] + edges[1:]) diff --git a/pysteps/tests/data/README.rst b/pysteps/tests/data/README.rst new file mode 100644 index 00000000..ab8341b3 --- /dev/null +++ b/pysteps/tests/data/README.rst @@ -0,0 +1,71 @@ +Parametric regression data +========================== + +The parametric tests combine independent numerical checks with frozen results +from the original WRNZ implementation. Normal test runs read the committed JSON +and NPZ files; they need neither the external repositories nor a database. + +The JSON ``provenance`` entries identify the original revisions, file SHA-256 +hashes, and numerical-library versions. Case entries record parameters, settings, +and seeds. The NPZ files contain complete expected fields and forecast histories. +Retain these identities when updating fixtures, and explain intended numerical +changes rather than regenerating expected values with the implementation under +test. + +Fixture generation +------------------ + +Run these commands from the pySTEPS repository root in an environment containing +the original project's dependencies, with this checkout on ``PYTHONPATH``:: + + python pysteps/tests/data/generate_parametric_reference.py \ + /path/to/pysteps_param /path/to/psteps_param_paper + python pysteps/tests/data/generate_calibrated_reference.py \ + /path/to/pysteps_param + python pysteps/tests/data/generate_nowcast_reference.py \ + /path/to/pysteps_param + +These commands overwrite the corresponding ``*_reference.json`` and +``*_reference.npz`` files beside the scripts. They extract original numerical +definitions without importing the operational database components. Original +function names and repository paths are intentional here: they establish where +the expected results came from. + +The committed references identify ``pysteps_param`` revision +``94bd4c1571637a8f246ac391470e1a820d6376a6`` and, where used, +``psteps_param_paper`` revision ``335f7660775480d865d9771820a81923c461832a``. +The generator and forecast metadata also identify settings and kernels used in +the comparisons. Compatibility with later source revisions is not assumed. + +Comparisons with saved rainfall cases +------------------------------------- + +The comparison scripts require the external paper repository's saved inputs. +They read those inputs and write a JSON report to the requested output path:: + + python pysteps/tests/data/compare_parametric_paper_fields.py \ + /path/to/pysteps_param /path/to/psteps_param_paper \ + --output /tmp/parametric_estimators_validation.json + python pysteps/tests/data/compare_calibrated_paper_fields.py \ + /path/to/pysteps_param /path/to/psteps_param_paper \ + --output /tmp/calibrated_generator_validation.json + python pysteps/tests/data/compare_parametric_nowcast_paper.py \ + /path/to/pysteps_param /path/to/psteps_param_paper \ + --leads 3 --output /tmp/parametric_nowcast_validation.json + +The estimator and generator scripts use ``figs/individual_field_fits/*.npz``. +The forecast script uses the saved case histories under ``figs/extreme_parameter_experiment``; +its module docstring explains the chronological selection of observations. +Reports include source identity, input hashes, settings, and numerical +differences. An existing report at the output path is overwritten. + +Compare identical-parameter evolution separately from the complete chain of +estimation and generation. Equivalent spectral regression solvers can produce +small coefficient differences that histogram interpolation and forecast +feedback amplify. Test comments and assertions explain the comparison +tolerances. These checks establish numerical agreement for the specified cases; +they do not measure forecast skill or establish calibration in another region. + +The current pySTEPS API and independent numerical tests define the maintained +behaviour. A deliberate method change may differ from the WRNZ references, but +must explain the difference and its validation. diff --git a/pysteps/tests/data/compare_calibrated_paper_fields.py b/pysteps/tests/data/compare_calibrated_paper_fields.py index c6aec8a2..0ddd2614 100644 --- a/pysteps/tests/data/compare_calibrated_paper_fields.py +++ b/pysteps/tests/data/compare_calibrated_paper_fields.py @@ -2,7 +2,7 @@ Requires the source project's dependencies, including xarray. Inputs and outputs are local files; no database is accessed. Raw estimates are passed to both -generators without regional generation QA, which belongs to the engine stage. +generators without the regional parameter selection used by the forecast. """ import argparse @@ -12,6 +12,7 @@ import subprocess import numpy as np +import xarray as xr from scipy.optimize import minimize_scalar from scipy.special import log_ndtr @@ -56,7 +57,13 @@ def main(): for path in paths: with np.load(path) as stored: rain = stored["rain"].astype(float) - transformed, metadata = dB_transform(rain, threshold=1.0, zerovalue=-0.1) + 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 _, spectrum = source["power_spectrum_1D"](transformed, 15) marginal = source["compute_field_stats"]( transformed, metadata, fit_truncated=True, lower_truncation=1.25 diff --git a/pysteps/timeseries/parametric.py b/pysteps/timeseries/parametric.py index 1afa0b3d..21cfa3f3 100644 --- a/pysteps/timeseries/parametric.py +++ b/pysteps/timeseries/parametric.py @@ -23,17 +23,20 @@ def _finite(name, value): + """Require a finite scalar.""" if not np.isscalar(value) or not np.isfinite(value): raise ValueError(f"{name} must be finite") def _positive(name, value): + """Require a positive finite scalar.""" _finite(name, value) if value <= 0: raise ValueError(f"{name} must be positive and finite") def _wavelengths(values): + """Return a nonempty one-dimensional array of positive wavelengths.""" values = np.asarray(values, dtype=float) if ( values.ndim != 1 @@ -81,6 +84,8 @@ def ar2_correlation_time(lag1, lag2, timestep, tol=1e-5): phi1 = lag1 * (1 - lag2) / denominator phi2 = (lag2 - lag1**2) / denominator total_denominator = 1 - phi1 - phi2 + # Require stationary AR coefficients and denominators separated from + # zero; near-unit roots make the infinite correlation sum unstable. valid = ( np.isfinite(lag1) & np.isfinite(lag2) @@ -97,7 +102,9 @@ def ar2_correlation_time(lag1, lag2, timestep, tol=1e-5): def _adjust_nonperiodic_lag2(lag1, lag2): - # Rationalized equivalent of adjust_lag2_corrcoef2, stable also at lag1=0. + """Raise the lag-2 correlation to the nonperiodic AR(2) lower bound.""" + # Algebraically equivalent to adjust_lag2_corrcoef2, but avoids division + # by lag1**2 and cancellation near zero. root = np.sqrt(1 - lag1**2) lower = lag1**2 * (1 + 2 * root) / (1 + root) ** 2 return float(max(lag2, 2 * lag1 * lag2 - 1, lower)) @@ -138,6 +145,8 @@ def residual(rho1): rho1 = rho2 = 0.0 if correlation_time > timestep: + # Find a finite sign-changing bracket below the stationarity limit. + # Nonfinite residuals lower the upper search bound. lower, upper, candidate = 0.0, 1.0, 0.5 bracket = None for _ in range(80): @@ -188,8 +197,8 @@ def estimate_reference_correlation_time( exponent: float Fixed finite temporal scaling exponent, not estimated here. levels: sequence of int, optional - Zero-based contributing levels; defaults to (0, 1, 2), the selected - paper method. (0,) reproduces the production largest-level convention. + Zero-based contributing levels; defaults to (0, 1, 2). Use (0,) to + estimate the reference time from the largest cascade level alone. reference_wavelength: float, optional Reference length, default wavelengths[0]. @@ -312,10 +321,10 @@ def lagrangian_autocorrelation( min_stdev: float, optional Both aligned fields must exceed this population standard deviation. boundary: {'zero', 'mask'}, optional - 'zero' preserves the evaluated production estimator: fill out-of-domain - pixels with zero after each unit step and correlate the whole grid. - 'mask' preserves NaNs through advection and uses common finite support - across all lags. It changes the estimator and is an explicit alternative. + 'zero' fills out-of-domain pixels with zero after each unit step and + correlates the whole grid. For normalized cascades, zero represents + the level mean. 'mask' preserves NaNs through advection and correlates + only pixels that are finite across all aligned lags. Returns ------- @@ -345,6 +354,7 @@ def lagrangian_autocorrelation( result = np.full((cascades.shape[1], ar_order), np.nan) for level in range(cascades.shape[1]): aligned = [cascades[-1, level]] + # Apply unit steps successively; a two-step field is interpolated twice. for lag in range(1, ar_order + 1): field = cascades[-(lag + 1), level] for _ in range(lag): diff --git a/pysteps/utils/spectral.py b/pysteps/utils/spectral.py index d92c3c8b..7803e969 100644 --- a/pysteps/utils/spectral.py +++ b/pysteps/utils/spectral.py @@ -41,12 +41,13 @@ def rapsd_logarithmic(field, min_stdev=0.1): Notes ----- - Preserves the ``pysteps_param.power_spectrum_1D`` binning convention: - min(shape)//2 logarithmic edges from 1/(min(shape)//2) to the historical - diagonal frequency limit. This differs from :func:`rapsd` and deliberately - omits the lowest frequencies. Empty bins are omitted. Power is the squared - unscaled FFT of a zero-mean, unit-variance field, not annulus-integrated - energy. Real-FFT multiplicities reproduce the full Fourier-plane mean. + Use min(shape)//2 logarithmic edges, starting at 1/(min(shape)//2) + cycles per pixel. The upper edge is the diagonal radius formed from the + largest absolute frequencies in the first half of each FFT frequency array. + This differs from :func:`rapsd` and omits the lowest frequencies. Empty bins + are omitted. Power is the squared unscaled FFT of a zero-mean, unit-variance + field, not annulus-integrated energy. Real-FFT multiplicities reproduce the + full Fourier-plane mean. """ if np.iscomplexobj(field): raise ValueError("field must contain real values") @@ -67,6 +68,8 @@ def rapsd_logarithmic(field, min_stdev=0.1): rows, cols = field.shape fx, fy = np.fft.rfftfreq(cols), np.fft.fftfreq(rows) radius = np.hypot(fy[:, None], fx[None, :]) + # The real FFT omits conjugate modes. Count them twice, except in the + # zero-frequency and (for even widths) Nyquist columns. weights = np.full(power.shape, 2.0) weights[:, 0] = 1.0 if cols % 2 == 0: @@ -76,6 +79,8 @@ def rapsd_logarithmic(field, min_stdev=0.1): np.max(np.abs(np.fft.fftfreq(rows)[: rows // 2])), np.max(np.abs(np.fft.fftfreq(cols)[: cols // 2])), ) + # Keep logarithmic endpoints explicit: roundoff can move a Fourier mode + # between bins when it falls exactly on an edge. edges = np.logspace(np.log10(1 / n_bins), np.log10(maximum), n_bins) frequency, radial_power = [], [] for lower, upper in zip(edges[:-1], edges[1:]): @@ -103,8 +108,8 @@ def fit_power_law_spectrum( skip_bins: int, optional Number of initial bins excluded from regression (default 2). max_frequency: float, optional - Exclusive upper fit limit. Defaults preserve the source estimator's - interval of 10*log10(frequency) < -4 for pixel frequencies. + Exclusive upper fit limit, default 10**(-0.4) cycles per pixel. + Set this in matching units if supplying physical frequencies. Returns ------- @@ -167,6 +172,8 @@ def model(x, beta_1, beta_2, c1): c2 = (beta_1 - beta_2) * breakpoint + c1 return np.where(x <= breakpoint, beta_1 * x + c1, beta_2 * x + c2) + # This basis fits both slopes and a shared intercept while enforcing + # continuity at the fixed breakpoint. design = np.column_stack( [np.minimum(x, breakpoint), np.maximum(x - breakpoint, 0), np.ones(x.size)] ) diff --git a/pysteps/utils/statistics.py b/pysteps/utils/statistics.py index 3475be95..8f511b86 100644 --- a/pysteps/utils/statistics.py +++ b/pysteps/utils/statistics.py @@ -19,7 +19,7 @@ def fit_lower_truncated_normal(data, lower_truncation, return_diagnostics=False): - """Fit the latent parent Normal to samples above a lower cutoff. + """Fit a parent Normal distribution to samples at or above a lower cutoff. Parameters ---------- @@ -41,14 +41,15 @@ def fit_lower_truncated_normal(data, lower_truncation, return_diagnostics=False) Notes ----- - Profiles scale analytically for a = (cutoff-location)/scale and minimizes - the conditional negative log likelihood over -12 <= a <= 12, preserving - the source ``pysteps_param`` method. Gaussian tail normalization uses log_ndtr. + Profile the scale analytically for a = (cutoff-location)/scale and minimize + the conditional negative log likelihood over -12 <= a <= 12. Use log_ndtr + to evaluate the retained Normal tail probability without underflow. A finite bounded solution is not proof of an unrestricted finite MLE: ``boundary_limited`` identifies proximity to the bound or shifted-sample - coefficient of variation >= 1. Its numerical solution is retained for - compatibility and reported explicitly. Pixels have equal marginal weights; - spatial dependence and parameter uncertainty are not estimated here. + coefficient of variation >= 1. The bounded numerical solution is returned + with this diagnostic so callers can decide whether to use it. Pixels have + equal marginal weights; spatial dependence and parameter uncertainty are + not estimated here. """ if not np.isfinite(lower_truncation): raise ValueError("lower_truncation must be finite") @@ -71,16 +72,20 @@ def output(location=np.nan, scale=np.nan): if np.ptp(sample) <= 0: diagnostics["status"] = "constant_sample" return output() + # Express retained observations relative to the cutoff. For a fixed + # standardized cutoff a, the likelihood gives scale analytically. shifted = sample - lower_truncation mean_shifted = float(np.mean(shifted)) second_moment = float(np.mean(shifted * shifted)) def scale_for_cutoff(a): + """Return the positive profile-likelihood scale for a fixed cutoff a.""" return 0.5 * ( a * mean_shifted + np.sqrt(a**2 * mean_shifted**2 + 4 * second_moment) ) def objective(a): + """Evaluate conditional negative log likelihood per retained sample.""" scale = scale_for_cutoff(a) if not np.isfinite(scale) or scale <= 0: return np.inf @@ -89,6 +94,7 @@ def objective(a): ) return float(np.log(scale) + 0.5 * standardized_second_moment + log_ndtr(-a)) + # A bounded scalar search avoids fitting location and scale independently. result = minimize_scalar( objective, bounds=(-12.0, 12.0), @@ -119,7 +125,7 @@ def compute_field_statistics(data, threshold, lower_truncation=None): ---------- data: array_like Nonempty, finite two-dimensional field. Units may be transformed rainfall - (dBR for the reference parametric method). The input is not modified. + (dBR for parametric nowcasting). The input is not modified. threshold: float Finite rain-occurrence threshold in the same units as data. Values at or above the threshold are wet. The denominator is the whole valid grid. From 02c52c6976b2b2013c9a710af73d731a7fd2aff0 Mon Sep 17 00:00:00 2001 From: alanseed Date: Fri, 6 Nov 2026 09:45:04 +1100 Subject: [PATCH 15/17] simulation method added --- doc/source/index.rst | 2 +- doc/source/user_guide/parametric_nowcast.rst | 107 +++- examples/plot_parametric_nowcast.py | 99 ---- examples/plot_parametric_simulation.py | 424 ++++++++++++++ pysteps/nowcasts/parametric.py | 566 +++++++++++++++---- pysteps/tests/test_simulation_parametric.py | 285 ++++++++++ 6 files changed, 1270 insertions(+), 213 deletions(-) delete mode 100644 examples/plot_parametric_nowcast.py create mode 100644 examples/plot_parametric_simulation.py create mode 100644 pysteps/tests/test_simulation_parametric.py diff --git a/doc/source/index.rst b/doc/source/index.rst index 349f64ad..c31b6555 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -36,7 +36,7 @@ __ https://github.com/pySTEPS/pysteps API Reference Parametric rainfall estimators Calibrated stochastic rainfall fields - Parametric ensemble nowcasting + Parametric nowcasting and simulation Example data Configuration file (pystepsrc) Machine learning applications diff --git a/doc/source/user_guide/parametric_nowcast.rst b/doc/source/user_guide/parametric_nowcast.rst index a00ceb30..d5647dc0 100644 --- a/doc/source/user_guide/parametric_nowcast.rst +++ b/doc/source/user_guide/parametric_nowcast.rst @@ -1,11 +1,13 @@ -Parametric ensemble nowcasting -============================== +Parametric nowcasting and simulation +==================================== ``pysteps.nowcasts.parametric.forecast`` takes three consecutive rain-rate observations and returns an ensemble in mm/h. Transformation, motion estimation, cascade decomposition, statistical estimation, and forecast evolution happen inside the call. Each ensemble member represents one possible evolution of the -rainfall field. +rainfall field. For simulations with prescribed rainfall statistics and motion, +use :func:`pysteps.nowcasts.parametric.simulate` instead. Both interfaces share +the same numerical evolution. The method describes rainfall using the fraction of rainy pixels, the rain-intensity distribution, spectral slopes describing spatial structure, and @@ -15,6 +17,9 @@ Rain intensity is modelled in dBR, defined as ``10*log10(R)`` for rain rate R in mm/h. A cascade separates this transformed field into bands of spatial scales; each band evolves with a second-order autoregressive, or AR(2), model. +Nowcasting from observations +---------------------------- + For observations stored in a v2 ``dataset`` with three time slices, ordered oldest to newest:: @@ -63,9 +68,8 @@ Both components must be present together and finite. Otherwise dense Lucas-Kanade motion is estimated from the latest two transformed observations. Statistical parameters and motion remain fixed throughout each forecast. -See :ref:`sphx_glr_auto_examples_plot_parametric_nowcast.py` for a runnable -synthetic dataset example. For the complete Swiss radar workflow, including -clipping, aggregation, plotting, and a lossless NetCDF round trip, see +For the complete Swiss radar nowcasting workflow, including clipping, +aggregation, plotting, and a lossless NetCDF round trip, see :ref:`sphx_glr_auto_examples_plot_parametric_nowcast_swiss.py`. The common dataset model is described in :py:mod:`pysteps.io.importers`. @@ -243,10 +247,99 @@ fallback uses its configured time without clipping to ``correlation_time_bounds``; spectral bounds still apply. This option cannot be combined with an explicit ``estimation_kwargs["reference_time"]`` override. +Simulation with prescribed parameters +------------------------------------- + +:func:`pysteps.nowcasts.parametric.simulate` uses supplied rainfall parameters +and motion to generate stochastic time series. The initial fields provide the +cascade states; they are not used to estimate the generation parameters. +This is useful for controlled experiments with specified rainfall statistics. +See :ref:`sphx_glr_auto_examples_plot_parametric_simulation.py` for a complete +example that constructs its initial history and a uniform advection field. + +Supply the same three-field dataset used by ``forecast``, including both +``velocity_x`` and ``velocity_y`` variables. Their values are displacements in +pixels per observation interval. Motion is required even for a dry target; +use zero-valued components for no advection. The latest two fields initialize +the AR(2) state. The oldest field is accepted and validated for consistency +with the forecast input interface. + +For an initial ``dataset`` prepared in this way:: + + from pysteps.nowcasts.parametric import simulate + + parameters = { + "beta_1": -2.4, + "beta_2": -3.1, + "rain_fraction": 0.2, + "mean_db": 6.0, + "stdev_db": 3.0, + } + ensemble, diagnostics = simulate( + dataset, + timesteps=12, + parameters=parameters, + reference_time=400.0, # minutes + reference_wavelength=256.0, # km + temporal_exponent=0.81, + alpha=2.72, + n_ens_members=1, + seed=42, + return_diagnostics=True, + ) + +The five rainfall parameters are finite real scalars. ``rain_fraction`` must +lie in [0, 1] and ``stdev_db`` must be positive. The two spectral slopes are +signed; ``mean_db`` and ``stdev_db`` are Normal location and scale in dBR. +These are generation targets, distinct from the initial fields' sample moments. +The mapping must contain exactly these five keys. Values are held fixed without +moment selection, regional replacements, or clipping to estimation bounds. +Spectral calibration still uses the configurable controls described in +:doc:`calibrated_generator`. + +Reference time and reference wavelength are required, positive inputs. In this +example, correlation time is 400 minutes at 256 km, and each cascade level uses + +.. math:: + + T_j = 400 \left(\frac{\lambda_j}{256}\right)^{0.81}, + +where :math:`\lambda_j` is its nominal wavelength in km. The defaults are +``temporal_exponent=0.81`` and ``alpha=2.72``. The helper +:func:`pysteps.timeseries.parametric.model_cascade_correlations` finds lag-1 and +lag-2 correlations for these times. The relation +``rho2 = rho1**alpha`` is adjusted where needed for a nonperiodic AR(2) model; +the correlations are then converted to AR coefficients and innovation weights. +These quantities are reported separately in diagnostics. + +Correlation time is the one-sided discrete AR(2) autocorrelation sum including +lag zero. It is not an e-folding time. Requested times at or below the observation +interval use white noise, with the achieved times recorded. Distribution +matching and boundary feedback can change the correlations measured from the +resulting rainfall fields. + +A zero target rain fraction produces dry output even from wet initial fields. +A positive target can generate rain from dry initial fields. Unlike ``forecast``, +``simulate`` defaults to ``min_rain_fraction=0.0`` so positive target fractions +are not suppressed. Missing inputs and failed positive-fraction distribution +mappings remain errors. + +Shifted copies of one stochastic field can provide the initial history, as in +the gallery example. They describe pure translation, so the initial history +is not stationary under the prescribed temporal model. Interpretation of +simulated statistics should account for this initial transient. + +The returned dataset, member numbering, random streams, callbacks, and output +retention follow the forecast conventions below. For compatibility, the output +``forecast_reference_time`` coordinate identifies the newest initial field. +With diagnostics requested, ``parameter_source`` is ``supplied`` and +``model_correlations`` and ``ar_coefficients`` identify the model driving the +simulation. No observation-based fitting diagnostics are produced. + State evolution and boundaries ------------------------------ -Each member starts from the newest and previous observed cascade states. +Each member starts from the newest and previous initial cascade states. One- and two-step semi-Lagrangian backward trajectories are cached once. The two-step trajectory retains two unit integration steps. Histories are sampled with linear interpolation and zero normalized anomalies outside the domain. diff --git a/examples/plot_parametric_nowcast.py b/examples/plot_parametric_nowcast.py deleted file mode 100644 index 21cb426d..00000000 --- a/examples/plot_parametric_nowcast.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -Parametric nowcast from three rain-rate fields -============================================== - -This self-contained example builds three synthetic observations, then lets the -parametric engine estimate motion and parameters and generate an ensemble. -It runs without downloading radar observations. -""" - -import matplotlib.pyplot as plt -import numpy as np -import xarray as xr - -from pysteps import nowcasts -from pysteps.noise.parametric import generate_stochastic_field - -# Construct an illustrative rain-rate field and a translating observation -# history. In an application, use a dataset of measured rain-rate observations. -transformed = generate_stochastic_field( - (128, 128), - dict(beta_1=-2.4, beta_2=-3.1, rain_fraction=0.2, mean_db=6.0, stdev_db=3.0), - kmperpixel=1.0, - scale_break=20.0, - threshold=0.0, - seed=9, -) -# Convert the generated dBR values to rain rates for the forecast input. -rain = 10 ** (transformed / 10) -rain[rain < 1.0] = 0 -# Construct motion by shifting one pixel per interval. np.roll wraps at the -# array edge to build this artificial history; the forecast has open boundaries. -observations = np.stack( - [ - np.roll(rain, -2, axis=1), - np.roll(rain, -1, axis=1), - rain, - ] -) - -# Use an illustrative local Cartesian grid in kilometres. Time stepsize is -# expressed in seconds; the forecast derives its physical cadence and spacing. -dataset = xr.Dataset( - { - "precip_intensity": ( - ("time", "y", "x"), - observations, - {"units": "mm/h", "transform": None}, - ) - }, - coords={ - "time": ( - "time", - np.array( - ["2026-01-01T00:00", "2026-01-01T00:05", "2026-01-01T00:10"], - dtype="datetime64[s]", - ), - {"stepsize": 300}, - ), - "x": ("x", np.arange(128) + 0.5, {"units": "km", "stepsize": 1.0}), - "y": ("y", np.arange(128) + 0.5, {"units": "km", "stepsize": 1.0}), - }, - attrs={"precip_var": "precip_intensity"}, -) - -############################################################################### -# Forecast directly in mm/h. Motion is estimated internally from the latest -# two fields. Each member starts from these observations and evolves with -# different random perturbations. -forecast = nowcasts.get_method("parametric") -ensemble, diagnostics = forecast( - dataset, - timesteps=6, - n_ens_members=4, - seed=42, - return_diagnostics=True, -) -rain_forecast = ensemble[ensemble.attrs["precip_var"]] -print("Output dimensions:", rain_forecast.sizes) -print("Selected generation parameters:", diagnostics["parameters"]) -print("Moment source:", diagnostics["safeguards"]["moment_source"]) - -############################################################################### -# Compare the newest observation with a member and the ensemble mean at 30 min. -fig, axes = plt.subplots(1, 3, figsize=(12, 4), constrained_layout=True) -for ax, field, title in zip( - axes, - [ - observations[-1], - rain_forecast.isel(ens_number=0, time=-1), - rain_forecast.isel(time=-1).mean("ens_number"), - ], - ["Newest observation", "Member 1, +30 min", "Ensemble mean, +30 min"], -): - image = ax.imshow(field, origin="lower", vmin=0, vmax=20, cmap="Blues") - ax.set_title(title) - ax.set_xlabel("x (km)") - ax.set_ylabel("y (km)") -fig.colorbar(image, ax=axes, label="Rain rate (mm/h)", shrink=0.75) -plt.show() diff --git a/examples/plot_parametric_simulation.py b/examples/plot_parametric_simulation.py new file mode 100644 index 00000000..7e573e97 --- /dev/null +++ b/examples/plot_parametric_simulation.py @@ -0,0 +1,424 @@ +""" +Simulate a time series of rainfall fields +========================================= + +Generate rainfall patterns that move and change with time using prescribed +rainfall statistics. This example sets the rain fraction, intensity distribution, +spatial structure, and temporal persistence explicitly. It runs without radar +observations. + +The correlation time is 400 minutes at a wavelength of 256 km. Smaller rainfall +features have shorter correlation times and change more quickly. A uniform +motion field moves the rain one pixel east every five minutes. +""" + +import matplotlib.pyplot as plt +import numpy as np +import xarray as xr + +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.cascade.decomposition import decomposition_fft +from pysteps.noise.parametric import generate_calibrated_field +from pysteps.nowcasts.parametric import simulate +from pysteps.timeseries.parametric import ( + ar2_correlation_time, + estimate_reference_correlation_time, + lagrangian_autocorrelation, + model_cascade_correlations, +) +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic +from pysteps.utils.statistics import compute_field_statistics +from pysteps.utils.transformation import dB_transform + +############################################################################### +# Choose rainfall and temporal parameters +# --------------------------------------- +# +# Describe the rainy fraction and intensity distribution in dBR, where +# dBR = 10*log10(rain rate in mm/h). The two spectral slopes describe spatial +# variability on either side of the 20 km break. These parameters stay fixed; +# the positions and intensities of individual rainfall features evolve. + +shape = (128, 128) +kmperpixel = 1.0 +timestep = 5.0 # minutes +n_cascade_levels = 6 +rain_threshold = 1.0 # mm/h +scale_break = 20.0 # km +parameters = dict( + beta_1=-2.4, + beta_2=-3.1, + rain_fraction=0.2, + mean_db=6.0, + stdev_db=3.0, +) +reference_time = 400.0 # minutes +reference_wavelength = 256.0 # km +temporal_exponent = 0.81 +alpha = 2.72 + +############################################################################### +# Calculate lag correlations at each spatial scale +# ------------------------------------------------ +# +# Separate the field into six bands of spatial scales, called cascade levels. +# Assign a correlation time to each level using +# T = 400 * (wavelength / 256)**0.81, with wavelengths in km and times in minutes. +# The temporal helper finds lag-1 and lag-2 correlations for each time using +# rho2 = rho1**alpha, adjusted where necessary for a nonperiodic AR(2) process. +# Correlation time here is a discrete autocorrelation sum including lag zero. + +bandpass = filter_gaussian(shape, n_cascade_levels) +wavelengths = kmperpixel / bandpass["central_freqs"] +lag_correlations, temporal_diagnostics = model_cascade_correlations( + reference_time, + wavelengths, + temporal_exponent, + alpha, + timestep, + reference_wavelength=reference_wavelength, + return_diagnostics=True, +) +print("Prescribed temporal model (wavelength in km, time in minutes):") +print(f"{'Wavelength':>12} {'Time':>12} {'Lag-1':>12} {'Lag-2':>12}") +for row in np.column_stack( + [wavelengths, temporal_diagnostics["requested_times"], lag_correlations] +): + print(" ".join(f"{value:12.4f}" for value in row)) + +############################################################################### +# Construct an initial history and uniform motion +# ----------------------------------------------- +# +# Generate one random rainfall field, then create two earlier fields by shifting +# it west. np.roll wraps the array edges to construct this artificial history. +# The simulation itself uses open boundaries, with random variability added at +# inflow. Shifted copies describe pure translation; the initial history is not +# a stationary realization of the prescribed temporal model. + +transformed = generate_calibrated_field( + shape, + parameters, + kmperpixel, + scale_break, + threshold=10 * np.log10(rain_threshold), + seed=9, +) +rain = 10 ** (transformed / 10) +rain[rain < rain_threshold] = 0 +initial_fields = np.stack([np.roll(rain, -2, axis=1), np.roll(rain, -1, axis=1), rain]) + +# Attach physical coordinates and the supplied velocity. Rainfall is in mm/h; +# velocity is in pixels per interval, and the time stepsize attribute is seconds. +dataset = xr.Dataset( + { + "rain": (("time", "y", "x"), initial_fields, {"units": "mm/h"}), + "velocity_x": (("y", "x"), np.ones(shape), {"units": "pixels/timestep"}), + "velocity_y": (("y", "x"), np.zeros(shape), {"units": "pixels/timestep"}), + }, + coords={ + "time": ( + "time", + np.array( + ["2026-01-01T00:00", "2026-01-01T00:05", "2026-01-01T00:10"], + dtype="datetime64[s]", + ), + {"stepsize": timestep * 60}, + ), + "x": ( + "x", + (np.arange(shape[1]) + 0.5) * kmperpixel, + {"units": "km", "stepsize": kmperpixel}, + ), + "y": ( + "y", + (np.arange(shape[0]) + 0.5) * kmperpixel, + {"units": "km", "stepsize": kmperpixel}, + ), + }, + attrs={"precip_var": "rain"}, +) + +############################################################################### +# Simulate one hour of rainfall +# ----------------------------- +# +# simulate() computes the same lag correlations shown above, then converts them +# to AR coefficients and innovation weights. At each step, it advects the two +# previous cascade states and combines them with a new random field. Rainfall +# distribution matching and boundary tapering feed into the next state. +# The supplied parameters and velocity are used throughout, without estimation. + +ensemble, diagnostics = simulate( + dataset, + timesteps=12, + parameters=parameters, + reference_time=reference_time, + reference_wavelength=reference_wavelength, + temporal_exponent=temporal_exponent, + alpha=alpha, + n_cascade_levels=n_cascade_levels, + rain_threshold=rain_threshold, + scale_break=scale_break, + n_ens_members=1, + seed=42, + return_diagnostics=True, +) +simulated_rain = ensemble[ensemble.attrs["precip_var"]].isel(ens_number=0) + +############################################################################### +# Recover parameters from the simulated rainfall +# ---------------------------------------------- +# +# Estimate statistics from all twelve generated fields of the first realization. +# Exclude the initial field and its shifted copies. Use the returned rain rates, +# transformed back to dBR, rather than the internal cascade states or generation +# diagnostics. Dry output pixels receive the same transformed dry value as the +# initial data. +# +# Estimate the fraction of rainy pixels and fit the parent Normal distribution +# above 1.25 dBR. Its fitted location and scale can be compared with mean_db and +# stdev_db. Ordinary wet-pixel moments describe the truncated sample and are +# different quantities. Fit both spectral slopes with the prescribed break. + +threshold_db = 10 * np.log10(rain_threshold) +lower_truncation = 1.25 # dBR, fitting cutoff distinct from the rain threshold +transformed_ensemble = dB_transform( + ensemble, threshold=rain_threshold, zerovalue=threshold_db - 0.1 +) +validation_fields = ( + transformed_ensemble[ensemble.attrs["precip_var"]].isel(ens_number=0).values +) +field_estimates = [] +marginal_statuses = [] +spectral_statuses = [] +cascades = [] +for field in validation_fields: + statistics = compute_field_statistics(field, threshold_db, lower_truncation) + frequency, power = rapsd_logarithmic(field) + spectrum = fit_power_law_spectrum( + frequency, power, scale_break=scale_break / kmperpixel + ) + field_estimates.append( + [ + statistics["rain_fraction"], + statistics["latent_mean"], + statistics["latent_stdev"], + spectrum["beta_1"], + spectrum["beta_2"], + ] + ) + marginal_statuses.append(statistics["nonzero_fit"]["status"]) + spectral_statuses.append(spectrum["status"]) + cascades.append( + decomposition_fft(field, bandpass, compute_stats=True, normalize=True)[ + "cascade_levels" + ] + ) +field_estimates = np.asarray(field_estimates) +cascades = np.stack(cascades) + +# Show variability between fields alongside the target. These fields are +# temporally correlated: their standard deviation is not a confidence interval. +# Retain unavailable estimates as NaN and report finite counts; do not replace +# them with the targets or silently discard failed fits. +labels = ( + "Rain fraction", + "Normal location (dBR)", + "Normal scale (dBR)", + "Large-scale slope", + "Small-scale slope", +) +targets = [ + parameters[name] + for name in ("rain_fraction", "mean_db", "stdev_db", "beta_1", "beta_2") +] +print(f"\nParameter recovery from {len(validation_fields)} fields of one realization:") +print( + f"{'Parameter':<23} {'Target':>12} {'Mean':>12} {'SD':>12} {'Finite / total':>15}" +) +for label, target, values in zip(labels, targets, field_estimates.T): + count = np.count_nonzero(np.isfinite(values)) + print( + f"{label:<23} {target:12.4f} {np.mean(values):12.4f} " + f"{np.std(values):12.4f} {str(count) + '/' + str(len(values)):>15}" + ) +for name, statuses in [ + ("Marginal", marginal_statuses), + ("Spectral", spectral_statuses), +]: + for status in sorted(set(statuses)): + print(f"{name} fit status {status}: {statuses.count(status)}/{len(statuses)}") + +############################################################################### +# Assess noisy spectral estimates with Monte Carlo sampling +# --------------------------------------------------------- +# +# A spectral slope fitted to one field can be noisy, especially at large scales +# represented by few Fourier modes. Use a Monte Carlo comparison to assess the +# spectral targets: generate independent initial fields and simulate independent +# one-hour realizations with the same parameters. +# +# Average the fitted slopes over the twelve fields within each run, then report +# the mean, standard deviation, and 5th/95th percentiles across independent runs. +# This treats each run as one replicate; its correlated frames are not separate +# independent samples. The percentile range describes the spread of run means, +# not a confidence interval for the overall mean. Calibration and nonlinear +# evolution can still leave systematic differences from the target slopes. + +n_monte_carlo = 32 +monte_carlo_slopes = [] +for run_seed in np.random.SeedSequence(2026).spawn(n_monte_carlo): + initial_seed, evolution_seed = run_seed.spawn(2) + mc_transformed = generate_calibrated_field( + shape, parameters, kmperpixel, scale_break, threshold_db, seed=initial_seed + ) + mc_rain = 10 ** (mc_transformed / 10) + mc_rain[mc_rain < rain_threshold] = 0 + mc_history = dataset.copy(deep=True) + mc_history[dataset.attrs["precip_var"]].data = np.stack( + [np.roll(mc_rain, -2, axis=1), np.roll(mc_rain, -1, axis=1), mc_rain] + ) + mc_ensemble = simulate( + mc_history, + timesteps=len(validation_fields), + parameters=parameters, + reference_time=reference_time, + reference_wavelength=reference_wavelength, + temporal_exponent=temporal_exponent, + alpha=alpha, + n_cascade_levels=n_cascade_levels, + rain_threshold=rain_threshold, + scale_break=scale_break, + n_ens_members=1, + seed=evolution_seed, + ) + mc_fields = ( + dB_transform( + mc_ensemble, threshold=rain_threshold, zerovalue=threshold_db - 0.1 + )[ensemble.attrs["precip_var"]] + .isel(ens_number=0) + .values + ) + mc_slopes = [] + for mc_field in mc_fields: + frequency, power = rapsd_logarithmic(mc_field) + spectrum = fit_power_law_spectrum( + frequency, power, scale_break=scale_break / kmperpixel + ) + mc_slopes.append([spectrum["beta_1"], spectrum["beta_2"]]) + # A failed field fit leaves its run mean unavailable; do not silently drop it. + monte_carlo_slopes.append(np.mean(mc_slopes, axis=0)) +monte_carlo_slopes = np.asarray(monte_carlo_slopes) +print(f"\nMonte Carlo spectral recovery: {n_monte_carlo} independent runs") +print( + f"{'Parameter':<23} {'Target':>12} {'MC mean':>12} {'MC SD':>12} " + f"{'P05':>12} {'P95':>12} {'Finite runs':>12}" +) +for label, name, values in zip( + ["Large-scale slope", "Small-scale slope"], + ["beta_1", "beta_2"], + monte_carlo_slopes.T, +): + lower, upper = np.percentile(values, [5, 95]) + count = np.count_nonzero(np.isfinite(values)) + print( + f"{label:<23} {parameters[name]:12.4f} {np.mean(values):12.4f} " + f"{np.std(values):12.4f} {lower:12.4f} {upper:12.4f} " + f"{str(count) + '/' + str(n_monte_carlo):>12}" + ) + +############################################################################### +# Compare prescribed and estimated temporal correlations +# ------------------------------------------------------ +# +# Align each overlapping three-field history with the known velocity and +# estimate its spatial lag-1 and lag-2 correlations at every cascade level. +# Use the same zero-anomaly boundary convention as the forecast estimator. +# Average the lag pairs, then convert that average to an implied AR(2) time. +# These are estimates from the displayed realization, separate from the Monte +# Carlo spectral check. One hour is short relative to the 400-minute reference +# time, and shifted initial fields introduce a transient. Distribution matching +# and boundary feedback can also change the measured correlations. + +velocity = np.stack([dataset.velocity_x.values, dataset.velocity_y.values]) +measured_correlations = np.stack( + [ + lagrangian_autocorrelation(cascades[end - 2 : end + 1], velocity) + for end in range(2, len(cascades)) + ] +) +average_correlations = np.mean(measured_correlations, axis=0) +estimated_times = ar2_correlation_time( + average_correlations[:, 0], average_correlations[:, 1], timestep +) +print( + f"\nTemporal recovery from {len(measured_correlations)} overlapping histories " + "(km and minutes):" +) +print( + f"{'Wavelength':>12} {'Model T':>12} {'Implied T':>12} " + f"{'Model lag-1':>12} {'Est. lag-1':>12} " + f"{'Model lag-2':>12} {'Est. lag-2':>12}" +) +for row in np.column_stack( + [ + wavelengths, + temporal_diagnostics["achieved_times"], + estimated_times, + lag_correlations[:, 0], + average_correlations[:, 0], + lag_correlations[:, 1], + average_correlations[:, 1], + ] +): + print(" ".join(f"{value:12.4f}" for value in row)) + +# Rescale the first three level times to 256 km with the prescribed exponent. +# All three must be valid; the estimator returns NaN rather than dropping one. +# The spectral break, temporal exponent, and alpha are held fixed in this check, +# not independently recovered from the simulation. +estimated_reference_time = estimate_reference_correlation_time( + estimated_times, + wavelengths, + temporal_exponent, + levels=(0, 1, 2), + reference_wavelength=reference_wavelength, +) +print( + f"\n{'Reference scale (km)':<23} {'Target time (min)':>18} {'Implied time (min)':>19}" +) +print( + f"{reference_wavelength:<23.4f} {reference_time:18.4f} {estimated_reference_time:19.4f}" +) + +# The implied times are diagnostic extrapolations from two lags, rather than +# direct measurements of long correlation times. Longer runs and a separate +# Monte Carlo temporal assessment are needed to assess long-run persistence. + +############################################################################### +# Show the evolution of one realization +# ------------------------------------- +# +# Plot the initial field and five times from the same realization. Use a common +# colour scale to see the translation and changes in rainfall structure. + +steps = (0, 1, 3, 6, 9, 12) +fig, axes = plt.subplots( + 2, 3, figsize=(11, 7), sharex=True, sharey=True, constrained_layout=True +) +for ax, step in zip(axes.flat, steps): + field = rain if step == 0 else simulated_rain.isel(time=step - 1).values + image = ax.imshow( + field, + origin="lower", + vmin=0, + vmax=20, + cmap="Blues", + extent=(0, shape[1] * kmperpixel, 0, shape[0] * kmperpixel), + ) + ax.set_title("Initial field" if step == 0 else f"+{step * timestep:g} min") + ax.set_xlabel("x (km)") + ax.set_ylabel("y (km)") +fig.colorbar(image, ax=axes, label="Rain rate (mm/h)", shrink=0.8) +plt.show() diff --git a/pysteps/nowcasts/parametric.py b/pysteps/nowcasts/parametric.py index b1655a0d..238f6220 100644 --- a/pysteps/nowcasts/parametric.py +++ b/pysteps/nowcasts/parametric.py @@ -2,12 +2,13 @@ pysteps.nowcasts.parametric =========================== -Generate parametric ensemble nowcasts from three rainfall observations. +Generate rainfall time series from estimated or prescribed parameters. .. autosummary:: :toctree: ../generated/ forecast + simulate """ from collections.abc import Mapping @@ -515,6 +516,165 @@ def _prepare_dataset(dataset, kmperpixel, timestep): return prepared, velocity, float(spacings[0]), derived_timestep +def _random_streams(seed, n_ens_members): + """Create independent member streams and record their seed identities.""" + # Give each member a separate stream; increasing ensemble size leaves the + # existing member streams unchanged. Copy a supplied SeedSequence first. + if isinstance(seed, np.random.SeedSequence): + root_seed = np.random.SeedSequence( + seed.entropy, spawn_key=seed.spawn_key, pool_size=seed.pool_size + ) + else: + if seed is not None: + _integer("seed", seed, 0) + root_seed = np.random.SeedSequence(seed) + member_seeds = root_seed.spawn(n_ens_members) + streams = [np.random.default_rng(s) for s in member_seeds] + information = dict( + entropy=root_seed.entropy, + spawn_key=root_seed.spawn_key, + pool_size=root_seed.pool_size, + member_spawn_keys=[s.spawn_key for s in member_seeds], + ) + return streams, information + + +def _evolution_settings( + timesteps, + n_ens_members, + n_cascade_levels, + rain_threshold, + scale_break, + zerovalue, + taper_sigma, + min_rain_fraction, + callback, + return_output, + return_diagnostics, +): + """Validate shared evolution controls and return settings with explicit units.""" + timesteps = _integer("timesteps", timesteps) + n_ens_members = _integer("n_ens_members", n_ens_members) + n_cascade_levels = _integer("n_cascade_levels", n_cascade_levels, 3) + rain_threshold = _positive("rain_threshold", rain_threshold) + scale_break = _positive("scale_break", scale_break) + threshold = float(10 * np.log10(rain_threshold)) + zerovalue = ( + threshold - 0.1 if zerovalue is None else _finite("zerovalue", zerovalue) + ) + if zerovalue >= threshold: + raise ValueError("zerovalue must be below the dBR threshold") + taper_sigma = _finite("taper_sigma", taper_sigma) + min_rain_fraction = _finite("min_rain_fraction", min_rain_fraction) + if taper_sigma < 0 or not 0 <= min_rain_fraction <= 1: + raise ValueError("taper_sigma must be nonnegative; min_rain_fraction in [0, 1]") + if callback is not None and not callable(callback): + raise ValueError("callback must be callable") + for name, flag in [ + ("return_output", return_output), + ("return_diagnostics", return_diagnostics), + ]: + if not isinstance(flag, (bool, np.bool_)): + raise ValueError(f"{name} must be boolean") + return dict( + timesteps=timesteps, + n_ens_members=n_ens_members, + n_cascade_levels=n_cascade_levels, + rain_threshold=rain_threshold, + scale_break_km=scale_break, + threshold_dbr=threshold, + zerovalue_dbr=zerovalue, + taper_sigma_pixels=taper_sigma, + min_rain_fraction=min_rain_fraction, + ) + + +def _run_ensemble( + dataset, + observed, + velocity, + parameters, + phi, + bp_filter, + streams, + settings, + diagnostics, + callback, + return_output, + return_diagnostics, +): + """Evolve initialized members and collect or stream rain-rate fields. + + Both forecast and simulate use this loop. The observed sequence contains + normalized cascade decompositions, oldest first. Settings contain validated + physical units and calibration controls. A missing parameter set or zero target rain + fraction returns dry fields without drawing noise or constructing trajectories. + """ + shape = dataset[dataset.attrs["precip_var"]].shape[1:] + n_ens_members = len(streams) + timesteps = settings["timesteps"] + threshold = settings["threshold_dbr"] + zerovalue = settings["zerovalue_dbr"] + rain_threshold = settings["rain_threshold"] + kmperpixel = settings["kmperpixel"] + scale_break = settings["scale_break_km"] + noise_settings = settings["noise"] + min_rain_fraction = settings["min_rain_fraction"] + dry = parameters is None or parameters["rain_fraction"] == 0 + output = np.empty((n_ens_members, timesteps, *shape)) if return_output else None + if not dry: + coordinates = prepare_advection_coordinates(velocity) + taper = gaussian_filter( + advect_field(np.ones(shape), coordinates[1]), settings["taper_sigma_pixels"] + ) + # Histories share read-only observations until each member replaces its + # own list entries. The numerical kernels never mutate these observations. + histories = [[observed[-1], observed[-2]] for _ in range(n_ens_members)] + members = range(n_ens_members) if not dry else () + for lead in range(timesteps): + fields = ( + np.zeros((n_ens_members, *shape)) + if dry + else np.empty((n_ens_members, *shape)) + ) + for member in members: + field, generation = _advance( + histories[member], + parameters, + phi, + bp_filter, + coordinates, + streams[member], + threshold, + zerovalue, + kmperpixel, + scale_break, + noise_settings, + min_rain_fraction, + ) + with np.errstate(over="ignore"): + physical = 10 ** (field / 10) + physical[physical < rain_threshold] = 0 + if not np.isfinite(physical).all(): + raise ValueError("Generated rainfall contains nonfinite values") + # Return physical rainfall before tapering. Only the feedback state + # is tapered and decomposed for the next forecast step. + fields[member] = physical + histories[member] = [ + _decompose(field * taper, bp_filter), + histories[member][0], + ] + if return_diagnostics: + diagnostics["generation"][member].append(generation) + if output is not None: + output[:, lead] = fields + if callback is not None: + callback(fields.copy()) + if output is not None: + output = convert_output_to_xarray_dataset(dataset, timesteps, output) + return (output, diagnostics) if return_diagnostics else output + + def forecast( dataset, timesteps=1, @@ -610,6 +770,10 @@ def forecast( and bounded generation parameters, settings, motion, seed information, and per-member/per-lead calibration results. + See also + -------- + simulate + Notes ----- Fixed statistical parameters and motion are used throughout the forecast. @@ -631,31 +795,25 @@ def forecast( precip_var = dataset.attrs["precip_var"] rain = dataset[precip_var].values shape = rain.shape[1:] - timesteps = _integer("timesteps", timesteps) - n_ens_members = _integer("n_ens_members", n_ens_members) - n_cascade_levels = _integer("n_cascade_levels", n_cascade_levels, 3) - kmperpixel = _positive("kmperpixel", kmperpixel) - timestep = _positive("timestep", timestep) - rain_threshold = _positive("rain_threshold", rain_threshold) - scale_break = _positive("scale_break", scale_break) - threshold = float(10 * np.log10(rain_threshold)) - zerovalue = ( - threshold - 0.1 if zerovalue is None else _finite("zerovalue", zerovalue) + settings = _evolution_settings( + timesteps, + n_ens_members, + n_cascade_levels, + rain_threshold, + scale_break, + zerovalue, + taper_sigma, + min_rain_fraction, + callback, + return_output, + return_diagnostics, ) - if zerovalue >= threshold: - raise ValueError("zerovalue must be below the dBR threshold") - taper_sigma = _finite("taper_sigma", taper_sigma) - min_rain_fraction = _finite("min_rain_fraction", min_rain_fraction) - if taper_sigma < 0 or not 0 <= min_rain_fraction <= 1: - raise ValueError("taper_sigma must be nonnegative; min_rain_fraction in [0, 1]") - if callback is not None and not callable(callback): - raise ValueError("callback must be callable") - for name, flag in [ - ("return_output", return_output), - ("return_diagnostics", return_diagnostics), - ]: - if not isinstance(flag, (bool, np.bool_)): - raise ValueError(f"{name} must be boolean") + timesteps = settings["timesteps"] + n_ens_members = settings["n_ens_members"] + n_cascade_levels = settings["n_cascade_levels"] + rain_threshold = settings["rain_threshold"] + threshold = settings["threshold_dbr"] + zerovalue = settings["zerovalue_dbr"] estimation = _estimation_settings(estimation_kwargs, threshold, n_cascade_levels) if temporal_fallback is not None: if not isinstance(temporal_fallback, Mapping) or set(temporal_fallback) != { @@ -687,30 +845,10 @@ def forecast( velocity = np.array(velocity, dtype=float, copy=True) if velocity.shape != (2, *shape) or not np.isfinite(velocity).all(): raise ValueError("velocity must be finite with shape (2, y, x)") - # Give each member a separate stream; increasing ensemble size leaves the - # existing member streams unchanged. Copy a supplied SeedSequence first. - if isinstance(seed, np.random.SeedSequence): - root_seed = np.random.SeedSequence( - seed.entropy, spawn_key=seed.spawn_key, pool_size=seed.pool_size - ) - else: - if seed is not None: - _integer("seed", seed, 0) - root_seed = np.random.SeedSequence(seed) - member_seeds = root_seed.spawn(n_ens_members) - streams = [np.random.default_rng(s) for s in member_seeds] - settings = dict( + streams, seed_information = _random_streams(seed, n_ens_members) + settings.update( timestep_minutes=timestep, kmperpixel=kmperpixel, - n_cascade_levels=n_cascade_levels, - n_ens_members=n_ens_members, - timesteps=timesteps, - rain_threshold=rain_threshold, - scale_break_km=scale_break, - threshold_dbr=threshold, - zerovalue_dbr=zerovalue, - taper_sigma_pixels=taper_sigma, - min_rain_fraction=min_rain_fraction, estimation=estimation, temporal_fallback=temporal_fallback, noise=noise_settings, @@ -721,12 +859,7 @@ def forecast( generator=GENERATOR_ID, settings=deepcopy(settings), motion_source="supplied" if velocity is not None else "lucaskanade", - seed=dict( - entropy=root_seed.entropy, - spawn_key=root_seed.spawn_key, - pool_size=root_seed.pool_size, - member_spawn_keys=[s.spawn_key for s in member_seeds], - ), + seed=seed_information, generation=[[] for _ in range(n_ens_members)] if return_diagnostics else None, ) transformed_dataset = dB_transform( @@ -745,22 +878,26 @@ def forecast( transformed[-1], threshold, estimation["lower_truncation"] ) diagnostics["statistics"] = statistics - output = np.empty((n_ens_members, timesteps, *shape)) if return_output else None if statistics["rain_fraction"] == 0: diagnostics.update( status="dry_latest_observation", velocity=velocity, parameters=None ) if velocity is None: diagnostics["motion_source"] = "not_required_for_dry_input" - for lead in range(timesteps): - fields = np.zeros((n_ens_members, *shape)) - if output is not None: - output[:, lead] = fields - if callback is not None: - callback(fields) - if output is not None: - output = convert_output_to_xarray_dataset(dataset, timesteps, output) - return (output, diagnostics) if return_diagnostics else output + return _run_ensemble( + dataset, + None, + velocity, + None, + None, + None, + streams, + settings, + diagnostics, + callback, + return_output, + return_diagnostics, + ) if velocity is None: motion_dataset = motion.get_method("LK")( transformed_dataset.isel(time=slice(-2, None)), **motion_settings @@ -855,46 +992,263 @@ def forecast( np.flatnonzero(np.asarray(d["stds"]) == 0) for d in observed ], ) - coordinates = prepare_advection_coordinates(velocity) - taper = gaussian_filter(advect_field(np.ones(shape), coordinates[1]), taper_sigma) - # Kernels never mutate histories. Members start from the same observations - # and replace their own list entries after each update. - histories = [[observed[-1], observed[-2]] for _ in range(n_ens_members)] - for lead in range(timesteps): - fields = np.empty((n_ens_members, *shape)) - for member in range(n_ens_members): - field, generation = _advance( - histories[member], - parameters, - phi, - bp_filter, - coordinates, - streams[member], - threshold, - zerovalue, - kmperpixel, - scale_break, - noise_settings, - min_rain_fraction, - ) - with np.errstate(over="ignore"): - physical = 10 ** (field / 10) - physical[physical < rain_threshold] = 0 - if not np.isfinite(physical).all(): - raise ValueError("Generated rainfall contains nonfinite values") - # Return physical rainfall before tapering. Only the feedback state - # is tapered and decomposed for the next forecast step. - fields[member] = physical - histories[member] = [ - _decompose(field * taper, bp_filter), - histories[member][0], - ] - if return_diagnostics: - diagnostics["generation"][member].append(generation) - if output is not None: - output[:, lead] = fields - if callback is not None: - callback(fields.copy()) - if output is not None: - output = convert_output_to_xarray_dataset(dataset, timesteps, output) - return (output, diagnostics) if return_diagnostics else output + return _run_ensemble( + dataset, + observed, + velocity, + parameters, + phi, + bp_filter, + streams, + settings, + diagnostics, + callback, + return_output, + return_diagnostics, + ) + + +def simulate( + dataset, + timesteps=1, + *, + parameters, + reference_time, + reference_wavelength, + temporal_exponent=0.81, + alpha=2.72, + n_ens_members=24, + kmperpixel=None, + timestep=None, + n_cascade_levels=6, + rain_threshold=1.0, + scale_break=20.0, + zerovalue=None, + taper_sigma=5.0, + min_rain_fraction=0.0, + seed=None, + noise_kwargs=None, + callback=None, + return_output=True, + return_diagnostics=False, +): + """Simulate rainfall time series using prescribed statistics and motion. + + Parameters + ---------- + dataset: xarray.Dataset + Three complete, finite, nonnegative rain-rate fields in mm/h, ordered + oldest first, on a regular grid with square pixels and at least four + pixels along each spatial dimension. Use the dataset + conventions described in :func:`forecast`: a global precip_var attribute, + precipitation dimensions (time, y, x) in any order, spatial units 'm' or + 'km', and coordinate stepsize attributes matching the grid and time + interval. Time stepsize is in seconds. Supply both velocity_x and + velocity_y with dimensions (y, x), in either order, and displacements + in pixels per interval. Motion is not estimated. The latest two fields + initialize the AR(2) state; the oldest is retained for consistency with + the forecast input interface. + timesteps: int, optional + Positive number of intervals to simulate (default 1). The first output + is one observation interval after the newest initial field. + parameters: mapping + Exactly five finite real values: beta_1 and beta_2 are signed spectral + slopes; rain_fraction lies in [0, 1]; mean_db and positive stdev_db are + the Normal location and scale in dBR. These describe the generation + targets, not necessarily the initial fields' sample statistics. Values + are copied and held fixed, without estimation, clipping, or regional + replacements. + reference_time: float + Positive correlation time in minutes at reference_wavelength. This is + the one-sided discrete AR(2) autocorrelation sum including lag zero, + not an e-folding time or a forecast-skill horizon. + reference_wavelength: float + Positive reference wavelength in km. It need not equal a cascade level's + nominal wavelength or the domain size. + temporal_exponent: float, optional + Finite exponent in T = reference_time * + (wavelength/reference_wavelength)**temporal_exponent (default 0.81). + alpha: float, optional + Positive exponent in rho2 = rho1**alpha before the nonperiodic AR(2) + adjustment (default 2.72). Lag correlations are converted to AR + coefficients and innovation weights separately. + n_ens_members: int, optional + Number of independent realizations (default 24). + kmperpixel, timestep: float, optional + Pixel spacing in km and interval in minutes. Derived from coordinates + by default; explicit values must agree. Fractional minutes are retained. + n_cascade_levels: int, optional + Number of Gaussian cascade levels (default 6, minimum 3). + rain_threshold: float, optional + Rain occurrence threshold in mm/h (default 1). + scale_break: float, optional + Prescribed spectral break wavelength in km (default 20). + zerovalue: float, optional + Dry dBR value below the transformed rain threshold. The default is + 10*log10(rain_threshold)-0.1. + taper_sigma: float, optional + Gaussian boundary-mask smoothing width in pixels (default 5). Tapering + affects the feedback state after the output field has been produced. + min_rain_fraction: float, optional + Return dry output for target fractions strictly below this value. + Default 0 preserves positive target fractions; forecast defaults to + 0.025. Failed positive-fraction mappings raise an error. + seed: int or numpy.random.SeedSequence, optional + Seed for separate member streams. Global random state and a supplied + SeedSequence are unchanged. Increasing the ensemble size preserves the + leading members for identical settings. + noise_kwargs: dict, optional + Overrides for spectral calibration and distribution matching, as in + :func:`forecast`. Calibration may miss its tolerance; diagnostics + report the selected candidate and its spectral error. + callback: callable, optional + Called once per interval with a fresh (member, y, x) array in mm/h. + Modifying it does not affect the state or retained output. Exceptions + propagate to the caller. + return_output: bool, optional + Retain and return the full time series (default True). Set False to + stream through callback without retaining the full output. + return_diagnostics: bool, optional + Also return diagnostics (default False). + + Returns + ------- + ensemble: xarray.Dataset or None + Float64 rain rates with dimensions (ens_number, time, y, x), preserving + spatial coordinates and projection metadata. Members are numbered from + one. The forecast_reference_time coordinate identifies the newest + initial field, following the forecast output convention. None if + return_output=False. + diagnostics: dict, optional + When requested, return (ensemble, diagnostics). Record the prescribed + parameters, effective settings, seed identities, supplied velocity, + nominal wavelengths, requested and achieved correlation times, model + lag correlations, AR coefficients, and calibration results indexed + [member][interval]. + + See also + -------- + forecast, pysteps.timeseries.parametric.model_cascade_correlations + + Notes + ----- + Simulation and forecasting share the same advection, stochastic AR(2) + updates, distribution matching, and boundary feedback. Parameters and motion + stay fixed while each member's rainfall pattern evolves. Matching of rain + fraction, intensity distribution, and spectra is approximate on finite + grids. Distribution matching and boundary feedback can change correlations + measured from the resulting rainfall. Requested level times at or below one + interval resolve to white noise, with achieved times recorded in diagnostics. + + A zero target rain fraction produces dry output even from wet initial + fields. Conversely, a positive target can generate rain from dry initial + fields. Missing initial values remain errors. Shifted copies of one field + can initialize a simulation, but they represent pure translation; they do + not establish a stationary history for the prescribed temporal model. + """ + dataset, velocity, kmperpixel, timestep = _prepare_dataset( + dataset, kmperpixel, timestep + ) + if velocity is None: + raise ValueError("simulate requires both velocity_x and velocity_y") + precip_var = dataset.attrs["precip_var"] + shape = dataset[precip_var].shape[1:] + if min(shape) < 4: + raise ValueError("Simulation requires at least four pixels per axis") + settings = _evolution_settings( + timesteps, + n_ens_members, + n_cascade_levels, + rain_threshold, + scale_break, + zerovalue, + taper_sigma, + min_rain_fraction, + callback, + return_output, + return_diagnostics, + ) + if not isinstance(parameters, Mapping): + raise ValueError("parameters must be a mapping") + names = ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db") + if set(parameters) != set(names): + raise ValueError(f"parameters must contain exactly {', '.join(names)}") + parameters = {name: _finite(name, parameters[name]) for name in names} + if not 0 <= parameters["rain_fraction"] <= 1: + raise ValueError("rain_fraction must lie in [0, 1]") + _positive("stdev_db", parameters["stdev_db"]) + reference_time = _positive("reference_time", reference_time) + reference_wavelength = _positive("reference_wavelength", reference_wavelength) + temporal_exponent = _finite("temporal_exponent", temporal_exponent) + alpha = _positive("alpha", alpha) + noise_settings = _noise_settings(noise_kwargs) + streams, seed_information = _random_streams(seed, settings["n_ens_members"]) + settings.update( + timestep_minutes=timestep, + kmperpixel=kmperpixel, + reference_time_minutes=reference_time, + reference_wavelength_km=reference_wavelength, + temporal_exponent=temporal_exponent, + alpha=alpha, + noise=noise_settings, + ) + + # Construct temporal correlations from the prescribed scale law, using the + # actual filter wavelengths. No correlations are estimated from the history. + bp_filter = filter_gaussian(shape, settings["n_cascade_levels"]) + wavelengths = kmperpixel / bp_filter["central_freqs"] + model_lags, temporal_diagnostics = model_cascade_correlations( + reference_time, + wavelengths, + temporal_exponent, + alpha, + timestep, + reference_wavelength=reference_wavelength, + return_diagnostics=True, + ) + phi = np.array([estimate_ar_params_yw(pair) for pair in model_lags]) + + # Initial rainfall supplies cascade states only. It does not alter the + # supplied intensity, occurrence, spectral, or persistence parameters. + transformed_dataset = dB_transform( + dataset, + threshold=settings["rain_threshold"], + zerovalue=settings["zerovalue_dbr"], + ) + observed = [ + _decompose(field, bp_filter) for field in transformed_dataset[precip_var].values + ] + dataset[precip_var].attrs.pop("transform", None) + dataset[precip_var].attrs.update( + units="mm/h", threshold=settings["rain_threshold"], zerovalue=0.0 + ) + diagnostics = dict( + method="parametric_simulation", + generator=GENERATOR_ID, + status="dry_target" if parameters["rain_fraction"] == 0 else "ok", + settings=deepcopy(settings), + parameter_source="supplied", + parameters=parameters.copy(), + motion_source="supplied", + velocity=velocity.copy(), + seed=seed_information, + wavelengths_km=wavelengths, + temporal_model=temporal_diagnostics, + model_correlations=model_lags, + ar_coefficients=phi, + generation=[[] for _ in streams] if return_diagnostics else None, + ) + return _run_ensemble( + dataset, + observed, + velocity, + parameters, + phi, + bp_filter, + streams, + settings, + diagnostics, + callback, + return_output, + return_diagnostics, + ) diff --git a/pysteps/tests/test_simulation_parametric.py b/pysteps/tests/test_simulation_parametric.py new file mode 100644 index 00000000..fabb958c --- /dev/null +++ b/pysteps/tests/test_simulation_parametric.py @@ -0,0 +1,285 @@ +"""Check prescribed rainfall simulation and its shared forecast evolution.""" + +from copy import deepcopy +import json +from pathlib import Path + +import numpy as np +import pytest +import xarray as xr + +from pysteps.nowcasts import parametric + + +@pytest.fixture +def initial(): + """Build a translating history with known motion and a fractional cadence.""" + rng = np.random.default_rng(17) + rain = rng.lognormal(1.0, 0.8, (32, 40)) + rain[rng.random(rain.shape) > 0.3] = 0 + return xr.Dataset( + { + "rain": ( + ("time", "y", "x"), + np.stack([np.roll(rain, -2, 1), np.roll(rain, -1, 1), rain]), + {"units": "mm/h", "transform": None}, + ), + "velocity_x": ( + ("y", "x"), + np.ones(rain.shape), + {"units": "pixels/timestep"}, + ), + "velocity_y": (("y", "x"), np.zeros(rain.shape)), + }, + coords={ + "time": ( + "time", + np.datetime64("2026-01-01T00:00:00") + + np.arange(3) * np.timedelta64(450, "s"), + {"stepsize": 450}, + ), + "x": ("x", (np.arange(40) + 0.5) * 1000, {"units": "m", "stepsize": 1000}), + "y": ( + "y", + (np.arange(32)[::-1] + 0.5) * 1000, + {"units": "m", "stepsize": -1000}, + ), + }, + attrs={"precip_var": "rain", "projection": "+proj=longlat +datum=WGS84"}, + ) + + +@pytest.fixture +def targets(): + """Return generation targets distinct from the history's sample statistics.""" + return dict(beta_1=-2.4, beta_2=-3.1, rain_fraction=0.2, mean_db=6.0, stdev_db=3.0) + + +def _simulate(initial, targets, **kwargs): + """Supply example settings while allowing individual contract checks.""" + settings = dict( + parameters=targets, + reference_time=400.0, + reference_wavelength=256.0, + timesteps=3, + n_ens_members=2, + seed=42, + return_diagnostics=True, + ) + settings.update(kwargs) + return parametric.simulate(initial, **settings) + + +def test_prescribed_scale_law_and_output_coordinates(initial, targets): + output, diagnostics = _simulate(initial.transpose("x", "time", "y"), targets) + expected = 400 * (diagnostics["wavelengths_km"] / 256) ** 0.81 + np.testing.assert_allclose( + diagnostics["temporal_model"]["requested_times"], expected + ) + assert diagnostics["settings"]["timestep_minutes"] == 7.5 + assert diagnostics["parameters"] == targets + assert diagnostics["parameter_source"] == "supplied" + assert output.rain.dims == ("ens_number", "time", "y", "x") + assert output.rain.shape == (2, 3, 32, 40) + assert np.isfinite(output.rain).all() and (output.rain >= 0).all() + assert np.all((output.rain == 0) | (output.rain >= 1)) + np.testing.assert_array_equal(output.x, initial.x) + np.testing.assert_array_equal(output.y, initial.y) + np.testing.assert_array_equal(output.ens_number, [1, 2]) + np.testing.assert_array_equal( + output.time, + initial.time.values[-1] + np.arange(1, 4) * np.timedelta64(450, "s"), + ) + assert output.forecast_reference_time == initial.time.values[-1] + assert output.attrs["projection"] == initial.attrs["projection"] + assert "transform" not in output.rain.attrs + + # Sum the AR recurrence independently to check the achieved discrete times. + phi = diagnostics["ar_coefficients"] + previous = np.ones(len(phi)) + current = diagnostics["model_correlations"][:, 0].copy() + total = previous + current + for _ in range(10000): + previous, current = current, phi[:, 0] * current + phi[:, 1] * previous + total += current + np.testing.assert_allclose(total * 7.5, np.maximum(expected, 7.5), atol=1e-7) + + +def test_simulation_does_not_estimate_or_replace_parameters( + initial, targets, monkeypatch +): + def forbidden(*args, **kwargs): + raise AssertionError("Simulation called an observation-based estimator") + + for name in [ + "compute_field_statistics", + "fit_power_law_spectrum", + "rapsd_logarithmic", + "lagrangian_autocorrelation", + "_generation_parameters", + ]: + monkeypatch.setattr(parametric, name, forbidden) + monkeypatch.setattr(parametric.motion, "get_method", forbidden) + initial.rain.values.fill(0) + # These valid supplied slopes violate the forecast's regional selection bounds. + targets.update(beta_1=-1.0, beta_2=-0.7, rain_fraction=0.04) + output, diagnostics = _simulate(initial, targets, reference_time=1200) + assert diagnostics["parameters"] == targets + assert diagnostics["settings"]["reference_time_minutes"] == 1200 + assert diagnostics["settings"]["min_rain_fraction"] == 0 + assert (output.rain > 0).any() + + +def test_simulation_matches_forecast_with_identical_parameters(): + folder = Path(__file__).parent / "data" + case = json.loads((folder / "parametric_nowcast_reference.json").read_text())[ + "cases" + ][0] + with np.load(folder / "parametric_nowcast_reference.npz") as data: + rain, velocity = data["history_0"], data["velocity_0"] + history = xr.Dataset( + { + "rain": (("time", "y", "x"), rain, {"units": "mm/h"}), + "velocity_x": (("y", "x"), velocity[0]), + "velocity_y": (("y", "x"), velocity[1]), + }, + coords={ + "time": ( + "time", + np.datetime64("2026-01-01") + np.arange(3) * np.timedelta64(5, "m"), + {"stepsize": 300}, + ), + "x": ("x", np.arange(rain.shape[2]), {"units": "km", "stepsize": 1}), + "y": ("y", np.arange(rain.shape[1]), {"units": "km", "stepsize": 1}), + }, + attrs={"precip_var": "rain"}, + ) + forecast, diagnostics = parametric.forecast( + history, + timesteps=3, + n_ens_members=2, + seed=case["seed"], + return_diagnostics=True, + ) + targets = { + name: diagnostics["parameters"][name] + for name in ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db") + } + simulation, supplied = _simulate( + history, + targets, + seed=case["seed"], + min_rain_fraction=0.025, + reference_time=diagnostics["parameters"]["reference_time"], + reference_wavelength=diagnostics["wavelengths_km"][0], + ) + xr.testing.assert_identical(simulation, forecast) + np.testing.assert_array_equal( + supplied["ar_coefficients"], diagnostics["ar_coefficients"] + ) + + +def test_streaming_purity_and_member_streams(initial, targets): + saved = initial.copy(deep=True) + supplied_targets = targets.copy() + noise_settings = dict(max_attempts=2) + seed = np.random.SeedSequence(42) + seed.spawn(2) + seed_state = deepcopy(seed.state) + global_state = np.random.get_state() + output, diagnostics = _simulate( + initial, targets, seed=seed, noise_kwargs=noise_settings + ) + streamed = [] + + def callback(field): + streamed.append(field.copy()) + field.fill(-1) + + result, streamed_diagnostics = _simulate( + initial, + targets, + seed=seed, + noise_kwargs=noise_settings, + callback=callback, + return_output=False, + ) + assert result is None + np.testing.assert_array_equal(np.stack(streamed, axis=1), output.rain) + assert streamed_diagnostics["seed"] == diagnostics["seed"] + larger, _ = _simulate( + initial, targets, seed=seed, n_ens_members=3, noise_kwargs=noise_settings + ) + np.testing.assert_array_equal(larger.rain[:2], output.rain) + xr.testing.assert_identical(initial, saved) + assert targets == supplied_targets + assert noise_settings == dict(max_attempts=2) + assert seed.state == seed_state + np.testing.assert_equal(np.random.get_state(), global_state) + + +def test_dry_target_validates_input_and_skips_generation(initial, targets, monkeypatch): + targets["rain_fraction"] = 0 + + def forbidden(*args, **kwargs): + raise AssertionError("A dry target consumed stochastic generation") + + monkeypatch.setattr(parametric, "generate_calibrated_field", forbidden) + output, diagnostics = _simulate(initial, targets) + assert (output.rain == 0).all() + assert diagnostics["status"] == "dry_target" + assert diagnostics["generation"] == [[], []] + initial.rain.values[0, 0, 0] = np.nan + with pytest.raises(ValueError, match="finite"): + _simulate(initial, targets) + + +@pytest.mark.parametrize( + "name,value", + [ + ("reference_time", 0), + ("reference_time", np.inf), + ("reference_wavelength", -1), + ("alpha", 0), + ("temporal_exponent", np.nan), + ("timesteps", 0), + ("n_ens_members", 0), + ("callback", 1), + ("return_output", 1), + ], +) +def test_invalid_simulation_settings(initial, targets, name, value): + with pytest.raises(ValueError): + _simulate(initial, targets, **{name: value}) + + +@pytest.mark.parametrize( + "change", + [ + dict(stdev_db=0), + dict(rain_fraction=-0.1), + dict(rain_fraction=1.1), + dict(mean_db=np.nan), + dict(beta_1=1j), + dict(unknown=1), + ], +) +def test_invalid_simulation_targets(initial, targets, change): + targets.update(change) + with pytest.raises(ValueError): + _simulate(initial, targets) + + +def test_simulation_requires_motion_and_complete_parameter_mapping(initial, targets): + with pytest.raises(ValueError, match="requires both"): + _simulate(initial.drop_vars(["velocity_x", "velocity_y"]), targets) + targets.pop("stdev_db") + with pytest.raises(ValueError, match="exactly"): + _simulate(initial, targets) + + +def test_subinterval_times_resolve_to_white_noise(initial, targets): + _, diagnostics = _simulate(initial, targets, reference_time=1.0) + assert diagnostics["temporal_model"]["limited_below_timestep"].all() + np.testing.assert_array_equal(diagnostics["model_correlations"], 0) + np.testing.assert_array_equal(diagnostics["temporal_model"]["achieved_times"], 7.5) From 0cbc9a8ae205d8f45cf9fea3d9afdeb1cdd38652 Mon Sep 17 00:00:00 2001 From: alanseed Date: Fri, 6 Nov 2026 10:02:44 +1100 Subject: [PATCH 16/17] end-to-end test added --- pysteps/tests/data/README.rst | 55 ++++- .../data/parametric_simulation_reference.json | 205 +++++++++++++++++ pysteps/tests/test_simulation_parametric.py | 217 ++++++++++++++++++ 3 files changed, 471 insertions(+), 6 deletions(-) create mode 100644 pysteps/tests/data/parametric_simulation_reference.json diff --git a/pysteps/tests/data/README.rst b/pysteps/tests/data/README.rst index ab8341b3..a562df0f 100644 --- a/pysteps/tests/data/README.rst +++ b/pysteps/tests/data/README.rst @@ -4,13 +4,16 @@ Parametric regression data The parametric tests combine independent numerical checks with frozen results from the original WRNZ implementation. Normal test runs read the committed JSON and NPZ files; they need neither the external repositories nor a database. +The simulation recovery reference described below records a separate pySTEPS +end-to-end regression case. -The JSON ``provenance`` entries identify the original revisions, file SHA-256 -hashes, and numerical-library versions. Case entries record parameters, settings, -and seeds. The NPZ files contain complete expected fields and forecast histories. -Retain these identities when updating fixtures, and explain intended numerical -changes rather than regenerating expected values with the implementation under -test. +The source-comparison JSON ``provenance`` entries identify the original revisions, +file SHA-256 hashes, and numerical-library versions. Case entries record parameters, +settings, and seeds. The NPZ files contain complete expected fields and histories. +Retain these identities when updating fixtures and explain intended numerical +changes. For comparisons with the original implementation, expected values must +continue to come from that implementation rather than being replaced with +pySTEPS results. Fixture generation ------------------ @@ -69,3 +72,43 @@ they do not measure forecast skill or establish calibration in another region. The current pySTEPS API and independent numerical tests define the maintained behaviour. A deliberate method change may differ from the WRNZ references, but must explain the difference and its validation. + +Simulation parameter recovery +----------------------------- + +``parametric_simulation_reference.json`` records the deterministic example in +``examples/plot_parametric_simulation.py``: a 128 by 128 grid at 1 km, a generated +initial field with seed 9, two translated copies, uniform motion of one pixel +per interval, and one member with seed 42 over twelve five-minute steps. +The reference time is 400 minutes at 256 km. The JSON records all inputs, the +source revision and example hash, numerical-library versions, and expected +recovered statistics. This reference was generated from the pySTEPS example; +it is not an additional comparison with WRNZ. + +Run the complete case with:: + + python -m pytest -q pysteps/tests/test_simulation_parametric.py::test_simulation_example_end_to_end_parameter_recovery + +The test generates the history, calls the public ``simulate`` function, checks +the output coordinates and units, and estimates occurrence, fitted Normal +parameters, spectral slopes, and Lagrangian correlations from the returned +rainfall. All twelve fields and all ten overlapping three-field histories are +included. It does not run plotting or the example's 32-run Monte Carlo check. + +The translated initialization creates a temporal transient. Expected implied +correlation times therefore describe this fixed setup, including its bias; +the recovered reference time is approximately 1001.54 minutes. The test does +not require recovery of the prescribed 400 minutes and does not define a +universal acceptable bias or replace the paper's scientific evaluation. + +Regression tolerances allow two pixels in occurrence, small numerical changes +in fitted moments and slopes, and 1e-6 absolute error in mean lag correlations. +Implied times have a relative tolerance of 0.1%, since the conversion is sensitive +near unit correlation. These are fixed-seed numerical tolerances, not Monte Carlo +acceptance bounds. The test gives the exact tolerances for each quantity. + +A deliberate change to the generation or estimation method should compare +its results with this baseline before updating the expected statistics. +Reproduce the generation and recovery steps documented in the example with +the stored case settings; review changes, then update the JSON and its +provenance explicitly. Normal tests only read the reference file. diff --git a/pysteps/tests/data/parametric_simulation_reference.json b/pysteps/tests/data/parametric_simulation_reference.json new file mode 100644 index 00000000..a2e69c7b --- /dev/null +++ b/pysteps/tests/data/parametric_simulation_reference.json @@ -0,0 +1,205 @@ +{ + "provenance": { + "description": "Fixed-seed pySTEPS simulation and parameter-recovery regression baseline. The translated initial history retains a persistence transient; recovered times are not required to equal the prescribed times.", + "pysteps_revision": "02c52c6976b2b2013c9a710af73d731a7fd2aff0", + "example_sha256": "95bdc5835cf810b09a93e9b03531ce1820dded3c0d548e66f91c88de999a2931", + "python_version": "3.13.18", + "numpy_version": "2.5.4", + "scipy_version": "1.18.1" + }, + "case": { + "shape": [ + 128, + 128 + ], + "kmperpixel": 1.0, + "timestep_minutes": 5.0, + "n_cascade_levels": 6, + "rain_threshold": 1.0, + "scale_break_km": 20.0, + "parameters": { + "beta_1": -2.4, + "beta_2": -3.1, + "rain_fraction": 0.2, + "mean_db": 6.0, + "stdev_db": 3.0 + }, + "reference_time_minutes": 400.0, + "reference_wavelength_km": 256.0, + "temporal_exponent": 0.81, + "alpha": 2.72, + "initial_seed": 9, + "evolution_seed": 42, + "n_timesteps": 12, + "n_ens_members": 1, + "velocity_pixels_per_timestep": [ + 1.0, + 0.0 + ], + "lower_truncation_dbr": 1.25 + }, + "expected": { + "field_parameter_names": [ + "rain_fraction", + "latent_mean", + "latent_stdev", + "beta_1", + "beta_2" + ], + "field_parameters": [ + [ + 0.19671630859375, + 5.986249096139553, + 3.012794880676619, + -2.2557237980868403, + -3.1141331601444824 + ], + [ + 0.19573974609375, + 6.016000339472105, + 2.9937188072117156, + -2.3026626012007245, + -3.2753025735088093 + ], + [ + 0.19732666015625, + 6.002121903317441, + 3.0059439970753967, + -2.197762094766152, + -3.1665853591479802 + ], + [ + 0.196533203125, + 6.01078589534421, + 3.000267695191747, + -1.9024561361129966, + -3.3591817187947495 + ], + [ + 0.19647216796875, + 6.007605236995318, + 3.001684868552406, + -1.381710667204899, + -3.128221868555097 + ], + [ + 0.19671630859375, + 5.995297095394097, + 3.009845705291915, + -1.4967524364344298, + -3.0656365574002926 + ], + [ + 0.19677734375, + 6.01321965613408, + 2.9974893524016766, + -1.8162638082199871, + -3.189509935219112 + ], + [ + 0.197021484375, + 6.006500846058137, + 3.0011801577347486, + -2.209983980362383, + -3.27064696478013 + ], + [ + 0.196533203125, + 6.022383951062275, + 2.9906279041013786, + -2.0206483070286563, + -3.03297340660577 + ], + [ + 0.197021484375, + 6.019270015751998, + 2.9913060498988484, + -2.1704243610864493, + -3.174610558183172 + ], + [ + 0.196044921875, + 5.992851718774125, + 3.0109195870655547, + -2.2807586082454363, + -3.1638150624423744 + ], + [ + 0.19610595703125, + 6.007345349393529, + 3.0015274954276343, + -1.9180022593139838, + -3.1622929516961777 + ] + ], + "wavelengths_km": [ + 128.0, + 42.666666666666664, + 21.333333333333332, + 10.666666666666666, + 5.333333333333333, + 2.0 + ], + "model_correlations": [ + [ + 0.9895046127483391, + 0.9717095674103806 + ], + [ + 0.9737829005847584, + 0.9302871714238284 + ], + [ + 0.9524697863763386, + 0.8759418701868156 + ], + [ + 0.9114361898321935, + 0.7770616058765629 + ], + [ + 0.8260762701358226, + 0.5946957013548607 + ], + [ + 0.40098044282778983, + 0.12403262116727216 + ] + ], + "mean_observed_correlations": [ + [ + 0.9956493182859601, + 0.989635285906074 + ], + [ + 0.9855593938093655, + 0.9649218639462054 + ], + [ + 0.9720016927658678, + 0.9301018111857937 + ], + [ + 0.9429181836364829, + 0.8560155629373574 + ], + [ + 0.8550468554132097, + 0.6631306333051005 + ], + [ + 0.5520161033975197, + 0.27420614707810975 + ] + ], + "implied_correlation_times_minutes": [ + 776.992666271944, + 220.70619946046185, + 104.59312856023948, + 48.49459426084739, + 21.58225649541986, + 10.432811132441214 + ], + "implied_reference_time_minutes": 1001.5425576214981 + } +} diff --git a/pysteps/tests/test_simulation_parametric.py b/pysteps/tests/test_simulation_parametric.py index fabb958c..b4e293bb 100644 --- a/pysteps/tests/test_simulation_parametric.py +++ b/pysteps/tests/test_simulation_parametric.py @@ -8,7 +8,18 @@ import pytest import xarray as xr +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.cascade.decomposition import decomposition_fft +from pysteps.noise.parametric import generate_calibrated_field from pysteps.nowcasts import parametric +from pysteps.timeseries.parametric import ( + ar2_correlation_time, + estimate_reference_correlation_time, + lagrangian_autocorrelation, +) +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic +from pysteps.utils.statistics import compute_field_statistics +from pysteps.utils.transformation import dB_transform @pytest.fixture @@ -283,3 +294,209 @@ def test_subinterval_times_resolve_to_white_noise(initial, targets): assert diagnostics["temporal_model"]["limited_below_timestep"].all() np.testing.assert_array_equal(diagnostics["model_correlations"], 0) np.testing.assert_array_equal(diagnostics["temporal_model"]["achieved_times"], 7.5) + + +def test_simulation_example_end_to_end_parameter_recovery(): + """Regress the fixed-seed gallery workflow through output parameter recovery.""" + reference = json.loads( + ( + Path(__file__).parent / "data/parametric_simulation_reference.json" + ).read_text() + ) + case, expected = reference["case"], reference["expected"] + shape = tuple(case["shape"]) + threshold_db = 10 * np.log10(case["rain_threshold"]) + interval = np.timedelta64(round(case["timestep_minutes"] * 60), "s") + + # Start from a generated field and its translated copies, as in the example. + # The test uses public functions without importing plotting code or running + # the example's Monte Carlo experiment. + transformed = generate_calibrated_field( + shape, + case["parameters"], + case["kmperpixel"], + case["scale_break_km"], + threshold_db, + seed=case["initial_seed"], + ) + rain = 10 ** (transformed / 10) + rain[rain < case["rain_threshold"]] = 0 + velocity = np.array( + [ + np.full(shape, component) + for component in case["velocity_pixels_per_timestep"] + ] + ) + history = xr.Dataset( + { + "rain": ( + ("time", "y", "x"), + np.stack([np.roll(rain, -2, axis=1), np.roll(rain, -1, axis=1), rain]), + {"units": "mm/h"}, + ), + "velocity_x": (("y", "x"), velocity[0], {"units": "pixels/timestep"}), + "velocity_y": (("y", "x"), velocity[1], {"units": "pixels/timestep"}), + }, + coords={ + "time": ( + "time", + np.datetime64("2026-01-01T00:00:00") + np.arange(3) * interval, + {"stepsize": case["timestep_minutes"] * 60}, + ), + "x": ( + "x", + (np.arange(shape[1]) + 0.5) * case["kmperpixel"], + {"units": "km", "stepsize": case["kmperpixel"]}, + ), + "y": ( + "y", + (np.arange(shape[0]) + 0.5) * case["kmperpixel"], + {"units": "km", "stepsize": case["kmperpixel"]}, + ), + }, + attrs={"precip_var": "rain"}, + ) + output, diagnostics = parametric.simulate( + history, + timesteps=case["n_timesteps"], + parameters=case["parameters"], + reference_time=case["reference_time_minutes"], + reference_wavelength=case["reference_wavelength_km"], + temporal_exponent=case["temporal_exponent"], + alpha=case["alpha"], + n_cascade_levels=case["n_cascade_levels"], + rain_threshold=case["rain_threshold"], + scale_break=case["scale_break_km"], + n_ens_members=case["n_ens_members"], + seed=case["evolution_seed"], + return_diagnostics=True, + ) + assert output.rain.dims == ("ens_number", "time", "y", "x") + assert output.rain.shape == (case["n_ens_members"], case["n_timesteps"], *shape) + assert output.rain.dtype == np.float64 + assert np.isfinite(output.rain).all() and (output.rain >= 0).all() + assert np.all((output.rain == 0) | (output.rain >= case["rain_threshold"])) + assert output.rain.attrs["units"] == "mm/h" + assert output.rain.attrs["threshold"] == case["rain_threshold"] + assert output.rain.attrs["zerovalue"] == 0 + assert "transform" not in output.rain.attrs + xr.testing.assert_identical(output.x.variable, history.x.variable) + xr.testing.assert_identical(output.y.variable, history.y.variable) + np.testing.assert_array_equal(output.ens_number, [1]) + np.testing.assert_array_equal( + output.time, + history.time.values[-1] + np.arange(1, case["n_timesteps"] + 1) * interval, + ) + assert output.time.attrs["stepsize"] == case["timestep_minutes"] * 60 + assert output.forecast_reference_time == history.time.values[-1] + assert diagnostics["parameters"] == case["parameters"] + + bandpass = filter_gaussian(shape, case["n_cascade_levels"]) + wavelengths = case["kmperpixel"] / bandpass["central_freqs"] + np.testing.assert_allclose(wavelengths, expected["wavelengths_km"], rtol=1e-12) + np.testing.assert_allclose( + diagnostics["temporal_model"]["requested_times"], + case["reference_time_minutes"] + * (wavelengths / case["reference_wavelength_km"]) ** case["temporal_exponent"], + rtol=1e-12, + ) + np.testing.assert_allclose( + diagnostics["model_correlations"], + expected["model_correlations"], + atol=1e-10, + rtol=1e-10, + ) + + # Recover statistics from every returned field, rather than accepting the + # supplied parameters in diagnostics as evidence of the generated behaviour. + fields = ( + dB_transform( + output, + threshold=case["rain_threshold"], + zerovalue=threshold_db - 0.1, + ) + .rain.isel(ens_number=0) + .values + ) + recovered = [] + cascades = [] + for field in fields: + statistics = compute_field_statistics( + field, threshold_db, case["lower_truncation_dbr"] + ) + frequency, power = rapsd_logarithmic(field) + spectrum = fit_power_law_spectrum( + frequency, + power, + scale_break=case["scale_break_km"] / case["kmperpixel"], + ) + assert statistics["nonzero_fit"]["status"] == "ok" + assert spectrum["status"] == "ok" + recovered.append( + [ + statistics["rain_fraction"], + statistics["latent_mean"], + statistics["latent_stdev"], + spectrum["beta_1"], + spectrum["beta_2"], + ] + ) + cascades.append( + decomposition_fft( + field, + bandpass, + compute_stats=True, + normalize=True, + )["cascade_levels"] + ) + recovered = np.asarray(recovered) + assert np.isfinite(recovered).all() + # Fixed seeds remove Monte Carlo sampling variability from this comparison. + # Allow two threshold-crossing pixels and small floating-point differences + # in fitted moments/slopes; these are numerical regression tolerances. + tolerances = [2 / np.prod(shape), 1e-4, 1e-4, 1e-4, 1e-4] + for column, (name, tolerance) in enumerate( + zip(expected["field_parameter_names"], tolerances) + ): + np.testing.assert_allclose( + recovered[:, column], + np.asarray(expected["field_parameters"])[:, column], + rtol=1e-5, + atol=tolerance, + err_msg=name, + ) + + # Include only simulated fields: the artificial initialization is excluded + # from the ten overlapping three-field histories, but its transient persists. + cascades = np.stack(cascades) + observed = np.mean( + [ + lagrangian_autocorrelation(cascades[end - 2 : end + 1], velocity) + for end in range(2, len(cascades)) + ], + axis=0, + ) + assert np.isfinite(observed).all() + np.testing.assert_allclose( + observed, expected["mean_observed_correlations"], atol=1e-6, rtol=0 + ) + times = ar2_correlation_time( + observed[:, 0], observed[:, 1], case["timestep_minutes"] + ) + reference_time = estimate_reference_correlation_time( + times, + wavelengths, + case["temporal_exponent"], + levels=(0, 1, 2), + reference_wavelength=case["reference_wavelength_km"], + ) + # The reference describes this initialization and one-hour run, not stationary + # recovery of the prescribed 400 minutes. Do not make a bias factor a model + # requirement. High correlations amplify roundoff in the implied times, so + # check the lag pairs tightly and allow 0.1% on their nonlinear conversion. + np.testing.assert_allclose( + times, expected["implied_correlation_times_minutes"], rtol=1e-3, atol=1e-6 + ) + assert reference_time == pytest.approx( + expected["implied_reference_time_minutes"], rel=1e-3 + ) From 72929d8647c431c69f59bc2399803ccfe9a325a5 Mon Sep 17 00:00:00 2001 From: alanseed Date: Fri, 6 Nov 2026 10:17:04 +1100 Subject: [PATCH 17/17] added defaults as explicit keyword arguments to forecast() --- doc/source/user_guide/parametric_nowcast.rst | 6 +- pysteps/nowcasts/parametric.py | 102 ++++++++++++++----- pysteps/tests/test_nowcasts_parametric.py | 55 ++++++++-- 3 files changed, 124 insertions(+), 39 deletions(-) diff --git a/doc/source/user_guide/parametric_nowcast.rst b/doc/source/user_guide/parametric_nowcast.rst index d5647dc0..735493b4 100644 --- a/doc/source/user_guide/parametric_nowcast.rst +++ b/doc/source/user_guide/parametric_nowcast.rst @@ -143,7 +143,7 @@ Top-level settings: - 0.025 - Return dry rainfall for target fractions strictly below this value -``estimation_kwargs`` accepts these overrides (unknown keys raise): +Estimation settings are keyword arguments to ``forecast()``: =========================== ======================== ============================== Key Default Meaning @@ -210,7 +210,7 @@ its marginal. Failure raises a clear error. If spectral or temporal estimates are unavailable outside the configured low-coverage fallback, the engine raises instead of inventing estimates or reporting dry output. For example, partly dry histories can lack a persistence estimate; users with an investigated temporal -prior can supply ``estimation_kwargs={"reference_time": 200.0}``. The raw failed +prior can supply ``reference_time=200.0``. The raw failed estimate remains visible, and bounds still apply to the supplied prior. The post-AR fraction cutoff is separate: a valid positive target below 0.025 @@ -245,7 +245,7 @@ not activated. Spatial estimates must therefore be available. Missing input, invalid motion, and unavailable marginal estimates remain errors. An activated fallback uses its configured time without clipping to ``correlation_time_bounds``; spectral bounds still apply. This option cannot be -combined with an explicit ``estimation_kwargs["reference_time"]`` override. +combined with an explicit ``reference_time`` override. Simulation with prescribed parameters ------------------------------------- diff --git a/pysteps/nowcasts/parametric.py b/pysteps/nowcasts/parametric.py index 238f6220..4cffc355 100644 --- a/pysteps/nowcasts/parametric.py +++ b/pysteps/nowcasts/parametric.py @@ -37,24 +37,6 @@ from ._cascade_advection import advect_field, prepare_advection_coordinates -# Configurable estimation defaults. Low-rain-fraction replacements are regional -# values from Auckland rainfall data; see the parametric nowcasting user guide. -_ESTIMATION_DEFAULTS = dict( - lower_truncation=1.25, - temporal_exponent=0.81, - alpha=2.72, - reference_levels=(0,), - reference_time=None, - moment_distance_limit=0.5, - beta_1_bounds=(-3.9, float(np.nextafter(-1.25, -np.inf))), - beta_2_min=-4.0, - minimum_beta_gap=0.1, - correlation_time_bounds=(60.0, 720.0), - low_rain_fraction=0.05, - fallback_beta_1=-1.45, - fallback_beta_2=-3.253958088759979, - fallback_reference_time=330.61752959848894, -) _MOTION_DEFAULTS = dict( lk_kwargs=dict(winsize=(31, 31), nr_levels=3, min_eig_thr=3e-5), fd_method="shitomasi", @@ -122,9 +104,9 @@ def _options(name, supplied, defaults): return result -def _estimation_settings(supplied, threshold, n_levels): +def _estimation_settings(settings, threshold, n_levels): """Validate estimation settings against the threshold and cascade count.""" - settings = _options("estimation_kwargs", supplied, _ESTIMATION_DEFAULTS) + settings = deepcopy(settings) for key in settings: if key not in ( "reference_levels", @@ -690,7 +672,20 @@ def forecast( min_rain_fraction=0.025, seed=None, motion_kwargs=None, - estimation_kwargs=None, + lower_truncation=1.25, + temporal_exponent=0.81, + alpha=2.72, + reference_levels=(0,), + reference_time=None, + moment_distance_limit=0.5, + beta_1_bounds=(-3.9, float(np.nextafter(-1.25, -np.inf))), + beta_2_min=-4.0, + minimum_beta_gap=0.1, + correlation_time_bounds=(60.0, 720.0), + low_rain_fraction=0.05, + fallback_beta_1=-1.45, + fallback_beta_2=-3.253958088759979, + fallback_reference_time=330.61752959848894, temporal_fallback=None, noise_kwargs=None, callback=None, @@ -735,11 +730,43 @@ def forecast( motion_kwargs: dict, optional Overrides of the default dense Lucas-Kanade configuration. Ignored when velocity is supplied; diagnostics record that motion source. - estimation_kwargs: dict, optional - Override marginal fitting, temporal scaling, moment selection, bounds, - and regional low-coverage fallbacks. See - :doc:`/user_guide/parametric_nowcast` for all keys and defaults. - Reference time is in minutes. + lower_truncation: float, optional + Lower cutoff for fitting the parent Normal distribution, in dBR (1.25). + Must be at or above the transformed rain occurrence threshold. + temporal_exponent: float, optional + Exponent relating correlation time to cascade wavelength (0.81). + alpha: float, optional + Power controlling the model lag-1 and lag-2 correlations (2.72). + reference_levels: tuple of int, optional + Zero-based cascade levels used to estimate reference time, default (0,). + Every selected level must yield a valid estimate. + reference_time: float, optional + Reference correlation time in minutes. By default, estimate it from the + observations. A supplied value replaces that estimate before applying + low-coverage replacements and correlation-time bounds. + moment_distance_limit: float, optional + Maximum Euclidean distance between fitted and sample intensity moments, + normalized by the sample standard deviation (0.5). Use the fitted + moments only when valid and within this limit. + beta_1_bounds: tuple of float, optional + Lower and upper bounds for the large-scale spectral slope, default + (-3.9, just below -1.25). + beta_2_min: float, optional + Lower bound for the small-scale spectral slope (-4.0). + minimum_beta_gap: float, optional + Minimum separation enforcing beta_2 <= beta_1 - gap (0.1). + correlation_time_bounds: tuple of float, optional + Lower and upper reference-time bounds in minutes, default (60, 720). + low_rain_fraction: float, optional + Rain fraction below which both slopes and reference time are replaced + by the fallback values (0.05). Set to zero to disable replacement. + fallback_beta_1, fallback_beta_2: float, optional + Low-coverage spectral slope replacements, default -1.45 and + -3.253958088759979. The small-scale value is an Auckland rainfall + archive median; see :doc:`/user_guide/parametric_nowcast`. + fallback_reference_time: float, optional + Low-coverage reference-time replacement in minutes + (330.61752959848894), the Auckland rainfall archive median. temporal_fallback: dict, optional Configured temporal-only fallback with positive ``lzero`` (km) and ``tzero`` (minutes). If any observed AR(2) pair or the estimated @@ -747,7 +774,7 @@ def forecast( to construct all model correlations. Complete input grids and valid marginal/spatial estimates remain required. This mode disables the combined low-rain-fraction slope and reference-time replacement. Cannot - be combined with an explicit estimation_kwargs reference_time override. + be combined with an explicit reference_time override. noise_kwargs: dict, optional Calibration controls for generate_calibrated_field; nbins and eps also control post-AR marginal mapping. Randomness is controlled by seed. @@ -814,7 +841,26 @@ def forecast( rain_threshold = settings["rain_threshold"] threshold = settings["threshold_dbr"] zerovalue = settings["zerovalue_dbr"] - estimation = _estimation_settings(estimation_kwargs, threshold, n_cascade_levels) + estimation = _estimation_settings( + dict( + lower_truncation=lower_truncation, + temporal_exponent=temporal_exponent, + alpha=alpha, + reference_levels=reference_levels, + reference_time=reference_time, + moment_distance_limit=moment_distance_limit, + beta_1_bounds=beta_1_bounds, + beta_2_min=beta_2_min, + minimum_beta_gap=minimum_beta_gap, + correlation_time_bounds=correlation_time_bounds, + low_rain_fraction=low_rain_fraction, + fallback_beta_1=fallback_beta_1, + fallback_beta_2=fallback_beta_2, + fallback_reference_time=fallback_reference_time, + ), + threshold, + n_cascade_levels, + ) if temporal_fallback is not None: if not isinstance(temporal_fallback, Mapping) or set(temporal_fallback) != { "lzero", diff --git a/pysteps/tests/test_nowcasts_parametric.py b/pysteps/tests/test_nowcasts_parametric.py index 939d4592..3dae377d 100644 --- a/pysteps/tests/test_nowcasts_parametric.py +++ b/pysteps/tests/test_nowcasts_parametric.py @@ -357,7 +357,7 @@ def test_partly_dry_history_requires_explicit_temporal_prior(reference): history, arrays["velocity_0"], 2, - estimation_kwargs={"reference_time": 200}, + reference_time=200, return_diagnostics=True, **kwargs, ) @@ -423,12 +423,11 @@ def test_unestimable_wet_input_is_not_dry(kind): {"callback": 4}, {"return_output": "yes"}, {"n_cascade_levels": 2}, - {"estimation_kwargs": {"unknown": 1}}, - {"estimation_kwargs": {"alpha": 0}}, - {"estimation_kwargs": {"lower_truncation": -1}}, - {"estimation_kwargs": {"reference_levels": (6,)}}, - {"estimation_kwargs": {"reference_levels": (0, 0)}}, - {"estimation_kwargs": {"reference_time": 0}}, + {"alpha": 0}, + {"lower_truncation": -1}, + {"reference_levels": (6,)}, + {"reference_levels": (0, 0)}, + {"reference_time": 0}, {"noise_kwargs": {"seed": 1}}, {"noise_kwargs": {"eps": 0}}, {"noise_kwargs": {"beta_variances": (0, 1)}}, @@ -470,6 +469,43 @@ def test_method_registration(): assert nowcasts.get_method("parametric") is parametric.forecast +def test_explicit_estimation_settings_are_retained(): + options = dict( + lower_truncation=1.5, + temporal_exponent=0.9, + alpha=2.5, + reference_levels=(0, 1, 2), + reference_time=250, + moment_distance_limit=0.6, + beta_1_bounds=(-3.8, -1.3), + beta_2_min=-4.2, + minimum_beta_gap=0.2, + correlation_time_bounds=(50, 800), + low_rain_fraction=0.04, + fallback_beta_1=-1.5, + fallback_beta_2=-3.2, + fallback_reference_time=320, + ) + original = deepcopy(options) + _, diagnostics = _forecast_array( + np.zeros((3, 8, 8)), + n_ens_members=1, + return_diagnostics=True, + **options, + ) + assert diagnostics["settings"]["estimation"] == options + assert options == original + + +def test_reference_time_conflicts_with_temporal_fallback(reference): + with pytest.raises(ValueError, match="cannot be combined with reference_time"): + _call( + reference, + reference_time=200, + temporal_fallback={"lzero": 256, "tzero": 420}, + ) + + def test_safeguards_moment_selection_and_joint_bounds(): statistics = dict( ordinary_mean=6, @@ -479,7 +515,10 @@ def test_safeguards_moment_selection_and_joint_bounds(): rain_fraction=0.3, ) spectrum = dict(beta_1=-1, beta_2=-1) - settings = parametric._estimation_settings(None, 0, 6) + _, diagnostics = _forecast_array( + np.zeros((3, 8, 8)), n_ens_members=1, return_diagnostics=True + ) + settings = diagnostics["settings"]["estimation"] parameters, audit = parametric._generation_parameters( statistics, spectrum, 20, settings )

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 a|F`YDrWy+BKNR$rg*J!) literal 0 HcmV?d00001 diff --git a/pysteps/tests/data/compare_calibrated_paper_fields.py b/pysteps/tests/data/compare_calibrated_paper_fields.py new file mode 100644 index 00000000..c6aec8a2 --- /dev/null +++ b/pysteps/tests/data/compare_calibrated_paper_fields.py @@ -0,0 +1,166 @@ +"""Check original and ported generator outputs on saved paper rainfall targets. + +Requires the source project's dependencies, including xarray. Inputs and outputs +are local files; no database is accessed. Raw estimates are passed to both +generators without regional generation QA, which belongs to the engine stage. +""" + +import argparse +import hashlib +import json +from pathlib import Path +import subprocess + +import numpy as np +from scipy.optimize import minimize_scalar +from scipy.special import log_ndtr + +from pysteps.noise.parametric import generate_calibrated_field +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic +from pysteps.utils.statistics import compute_field_statistics +from pysteps.utils.transformation import dB_transform +from generate_calibrated_reference import source_generator +from generate_parametric_reference import _load_functions + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("parameter_repo", type=Path) + parser.add_argument("paper_repo", type=Path) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + source, provenance = source_generator(args.parameter_repo) + source.update(minimize_scalar=minimize_scalar, log_ndtr=log_ndtr) + _load_functions( + args.parameter_repo / "src/pysteps_param/rain_stats_compute.py", + {"fit_lower_truncated_normal", "compute_field_stats"}, + source, + ) + report = dict( + provenance=provenance, + paper_revision=subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=args.paper_repo, text=True + ).strip(), + seeds=[0, 20260908], + scale_break_km=15.0, + pixel_size_km=1.0, + threshold_mm_per_hour=1.0, + lower_truncation_dbr=1.25, + target_policy="Raw estimator outputs; regional generation QA not applied", + coupled_maximum_absolute_error_tolerance_dbr=1e-4, + cases=[], + ) + paths = sorted((args.paper_repo / "figs/individual_field_fits").glob("*.npz")) + if not paths: + raise ValueError("No saved paper fields found") + for path in paths: + with np.load(path) as stored: + rain = stored["rain"].astype(float) + transformed, metadata = dB_transform(rain, threshold=1.0, zerovalue=-0.1) + _, spectrum = source["power_spectrum_1D"](transformed, 15) + marginal = source["compute_field_stats"]( + transformed, metadata, fit_truncated=True, lower_truncation=1.25 + ) + parameters = dict( + beta_1=spectrum["beta_1"], + beta_2=spectrum["beta_2"], + rain_fraction=marginal["nonzero_fraction"], + mean_db=marginal["latent_mean"], + stdev_db=marginal["latent_stdev"], + ) + frequency, power = rapsd_logarithmic(transformed) + port_spectrum = fit_power_law_spectrum(frequency, power, 15) + port_marginal = compute_field_statistics( + transformed, metadata["threshold"], 1.25 + ) + port_parameters = dict( + beta_1=port_spectrum["beta_1"], + beta_2=port_spectrum["beta_2"], + rain_fraction=port_marginal["rain_fraction"], + mean_db=port_marginal["latent_mean"], + stdev_db=port_marginal["latent_stdev"], + ) + case = dict( + name=path.stem, + input_sha256=hashlib.sha256(path.read_bytes()).hexdigest(), + parameters=parameters, + comparisons=[], + ) + for seed in report["seeds"]: + original = source["gen_calibrated_stoch_field"]( + source["StepsParameters"](**parameters), + rain.shape[1], + rain.shape[0], + 1, + 15, + metadata["threshold"], + rng=np.random.default_rng(seed), + zerovalue=metadata["zerovalue"], + ) + fixed, fixed_diagnostics = generate_calibrated_field( + rain.shape, + parameters, + 1, + 15, + metadata["threshold"], + seed=seed, + zerovalue=metadata["zerovalue"], + return_diagnostics=True, + ) + coupled, coupled_diagnostics = generate_calibrated_field( + rain.shape, + port_parameters, + 1, + 15, + metadata["threshold"], + seed=seed, + zerovalue=metadata["zerovalue"], + return_diagnostics=True, + ) + np.testing.assert_allclose(fixed, original.field, atol=1e-10, rtol=1e-12) + # Equivalent spectral regression solvers produce slightly different + # slopes. Histogram interpolation can amplify those differences. + # Check a 1e-4 dBR absolute limit (about 2.3e-5 relative rain rate) + # separately from the tight identical-target generator comparison. + np.testing.assert_allclose(coupled, original.field, atol=1e-4, rtol=0) + for diagnostics in (fixed_diagnostics, coupled_diagnostics): + for name in ("attempts", "selected_attempt", "tolerance_achieved"): + assert diagnostics[name] == getattr(original, name) + for name in ("estimated_beta_1", "estimated_beta_2", "distance"): + np.testing.assert_allclose( + diagnostics[name], getattr(original, name), atol=3e-5, rtol=1e-7 + ) + case["comparisons"].append( + dict( + seed=seed, + attempts=original.attempts, + selected_attempt=original.selected_attempt, + tolerance_achieved=original.tolerance_achieved, + distance=original.distance, + identical_targets_maximum_field_difference=float( + np.max(abs(fixed - original.field)) + ), + coupled_estimators_maximum_field_difference=float( + np.max(abs(coupled - original.field)) + ), + coupled_distance_difference=float( + abs(coupled_diagnostics["distance"] - original.distance) + ), + coupled_threshold_classification_changes=int( + np.count_nonzero( + (coupled > metadata["threshold"]) + != (original.field > metadata["threshold"]) + ) + ), + ) + ) + report["cases"].append(case) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + print( + f"Compared {len(paths)} paper cases at {len(report['seeds'])} seeds; report: {args.output}" + ) + + +if __name__ == "__main__": + main() diff --git a/pysteps/tests/data/compare_parametric_nowcast_paper.py b/pysteps/tests/data/compare_parametric_nowcast_paper.py new file mode 100644 index 00000000..c417213e --- /dev/null +++ b/pysteps/tests/data/compare_parametric_nowcast_paper.py @@ -0,0 +1,252 @@ +"""Compare complete forecasts on frozen three-observation paper histories. + +The archives store [current, previous, future1, ...]; select [1, 0, 2] as +chronological observations and estimate motion anew for the latest pair. +Nothing is read from a database or written to the research repository. +""" + +import argparse +import hashlib +import json +from pathlib import Path +import subprocess + +import numpy as np +from scipy.ndimage import gaussian_filter + +from pysteps import motion +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.nowcasts import parametric +from pysteps.nowcasts._cascade_advection import ( + advect_field, + prepare_advection_coordinates, +) +from pysteps.timeseries.autoregression import estimate_ar_params_yw +from pysteps.timeseries.parametric import lagrangian_autocorrelation +from pysteps.utils.spectral import rapsd_logarithmic, fit_power_law_spectrum + +from generate_nowcast_reference import source_engine, config, prepare, evolve + + +def fixed_parameter_forecast(rain, velocity, cfg, parameters, seed, n_members, n_leads): + """Isolate evolution using the source's selected parameters and lag pairs.""" + bp = filter_gaussian(rain.shape[1:], 6) + transformed = 10 * np.log10(np.maximum(rain, 1)) + transformed[rain < 1] = -0.1 + observed = [parametric._decompose(f, bp) for f in transformed] + coordinates = prepare_advection_coordinates(velocity) + taper = gaussian_filter(advect_field(np.ones(rain.shape[1:]), coordinates[1]), 5) + target = { + k: getattr(parameters, k) + for k in ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db") + } + phi = np.array( + [ + estimate_ar_params_yw(pair) + for pair in zip(parameters.lag_1, parameters.lag_2) + ] + ) + output = np.empty((n_members, n_leads, *rain.shape[1:])) + for member, child in enumerate(np.random.SeedSequence(seed).spawn(n_members)): + rng = np.random.default_rng(child) + histories = [observed[-1], observed[-2]] + for lead in range(n_leads): + field, _ = parametric._advance( + histories, + target, + phi, + bp, + coordinates, + rng, + 0, + -0.1, + 1, + 20, + parametric._noise_settings(None), + 0.025, + ) + physical = 10 ** (field / 10) + physical[physical < 1] = 0 + output[member, lead] = physical + histories = [parametric._decompose(field * taper, bp), histories[0]] + return output + + +def metrics(forecast, observations, velocity): + bp = filter_gaussian(forecast.shape[-2:], 6) + result = [] + for member in forecast: + history = [] + for field in observations[-2:]: + db = 10 * np.log10(np.maximum(field, 1)) + db[field < 1] = -0.1 + history.append(parametric._decompose(db, bp)["cascade_levels"]) + for field in member: + wet = field >= 1 + db = 10 * np.log10(np.maximum(field, 1)) + db[~wet] = -0.1 + frequency, power = rapsd_logarithmic(db) + slopes = fit_power_law_spectrum(frequency, power, 20) + history.append(parametric._decompose(db, bp)["cascade_levels"]) + correlations = lagrangian_autocorrelation(np.stack(history[-3:]), velocity) + result.append( + [ + float(wet.mean()), + float(field.mean()), + float(db[wet].mean()) if wet.any() else np.nan, + float(db[wet].std()) if wet.any() else np.nan, + slopes["beta_1"], + slopes["beta_2"], + *correlations.ravel(), + ] + ) + return np.asarray(result) + + +def finite_max(values): + values = np.asarray(values) + return ( + float(np.max(values[np.isfinite(values)])) + if np.isfinite(values).any() + else None + ) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("parameter_repo", type=Path) + parser.add_argument("paper_repo", type=Path) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--leads", type=int, default=12) + args = parser.parse_args() + source, provenance = source_engine(args.parameter_repo) + import scipy + + report = dict( + provenance=provenance, + paper_revision=subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=args.paper_repo, text=True + ).strip(), + numpy_version=np.__version__, + scipy_version=scipy.__version__, + leads=args.leads, + members=2, + seeds=[0, 20261015], + observation_policy="Archive indices [1,0,2], oldest first; newest is original cycle +5 minutes", + motion_policy="Reference Lucas-Kanade settings, re-estimated from latest pair", + config=dict(config((256, 256)), n_forecasts=args.leads), + comparisons=[], + ) + for name in ("control_1", "rain_fraction_low_2", "beta_1_low_2"): + folder = args.paper_repo / "figs/extreme_parameter_experiment" / name + path = folder / "inputs.npz" + with np.load(path) as saved: + rain = saved["rain"][[1, 0, 2]].astype(float) + metadata = json.loads((folder / "input_metadata.json").read_text()) + transformed = source["DBTransformer"](threshold=1).transform(rain) + velocity = motion.get_method("LK")( + transformed[-2:], **parametric._MOTION_DEFAULTS + ) + cfg = config(rain.shape[1:]) + cfg["n_forecasts"] = args.leads + parameters, _, _, _ = prepare(source, rain, velocity, cfg) + for seed in report["seeds"]: + original, calibration = evolve( + source, + rain, + velocity, + cfg, + parameters, + seed, + n_members=2, + n_leads=args.leads, + ) + fixed = fixed_parameter_forecast( + rain, velocity, cfg, parameters, seed, 2, args.leads + ) + # Exercise the public call including its internal motion estimation. + coupled, diagnostics = parametric.forecast( + rain, + timesteps=args.leads, + n_ens_members=2, + kmperpixel=1, + timestep=5, + seed=seed, + return_diagnostics=True, + ) + np.testing.assert_array_equal(velocity, diagnostics["velocity"]) + for key in ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db"): + np.testing.assert_allclose( + diagnostics["parameters"][key], + getattr(parameters, key), + atol=1e-6, + rtol=0, + ) + np.testing.assert_allclose( + diagnostics["parameters"]["reference_time"], + parameters.corl_zero, + atol=1e-7, + rtol=0, + ) + np.testing.assert_allclose( + diagnostics["model_correlations"], + np.array([parameters.lag_1, parameters.lag_2]).T, + atol=1e-12, + rtol=0, + ) + np.testing.assert_allclose(fixed, original, atol=1e-8, rtol=1e-10) + difference = abs(coupled - original) + rms = float(np.sqrt(np.mean((coupled - original) ** 2))) + original_metrics = metrics(original, rain, velocity) + coupled_metrics = metrics(coupled, rain, velocity) + deltas = abs(coupled_metrics - original_metrics) + assert np.array_equal( + np.isfinite(original_metrics), np.isfinite(coupled_metrics) + ) + assert np.isfinite(coupled).all() and np.all(coupled >= 0) + selection_changes = sum( + diagnostics["generation"][m][lead]["selected_attempt"] + != calibration[m][lead]["selected_attempt"] + for m in range(2) + for lead in range(args.leads) + ) + report["comparisons"].append( + dict( + case=name, + seed=seed, + input_sha256=hashlib.sha256(path.read_bytes()).hexdigest(), + observation_times=[ + metadata["rain"][i]["valid_time"] for i in (1, 0, 2) + ], + parameters=diagnostics["parameters"], + source_parameters=parameters.active_parameters(), + moment_source=diagnostics["safeguards"]["moment_source"], + low_coverage_fallback=diagnostics["safeguards"][ + "low_rain_fraction_fallback" + ], + identical_parameters_maximum_error_mm_per_hour=float( + abs(fixed - original).max() + ), + coupled_maximum_error_mm_per_hour=float(difference.max()), + coupled_rmse_mm_per_hour=rms, + rain_classification_changes=int( + np.count_nonzero((coupled >= 1) != (original >= 1)) + ), + total_output_pixels=int(coupled.size), + candidate_selection_changes=selection_changes, + maximum_wet_fraction_difference=finite_max(deltas[:, 0]), + maximum_mean_rain_difference=finite_max(deltas[:, 1]), + maximum_wet_dbr_mean_difference=finite_max(deltas[:, 2]), + maximum_wet_dbr_stdev_difference=finite_max(deltas[:, 3]), + maximum_spectral_slope_difference=finite_max(deltas[:, 4:6]), + maximum_lagrangian_correlation_difference=finite_max(deltas[:, 6:]), + ) + ) + print( + f"{name}, seed {seed}: fixed max {abs(fixed-original).max():.3g}, coupled max {difference.max():.3g}, RMSE {rms:.3g}" + ) + args.output.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + + +if __name__ == "__main__": + main() diff --git a/pysteps/tests/data/compare_parametric_paper_fields.py b/pysteps/tests/data/compare_parametric_paper_fields.py new file mode 100644 index 00000000..fe2e8320 --- /dev/null +++ b/pysteps/tests/data/compare_parametric_paper_fields.py @@ -0,0 +1,111 @@ +"""Compare source and ported estimators on saved paper rainfall fields. + +Requires the source project's environment, including xarray, and the saved +figs/individual_field_fits/*.npz inputs. Does not connect to a database. +""" + +import argparse +import hashlib +import json +from pathlib import Path +import subprocess + +import numpy as np +from scipy.optimize import curve_fit, minimize_scalar +from scipy.special import log_ndtr + +from pysteps.utils.spectral import fit_power_law_spectrum, rapsd_logarithmic +from pysteps.utils.statistics import compute_field_statistics +from pysteps.utils.transformation import dB_transform +from generate_parametric_reference import _load_functions + + +def main(): + import xarray as xr + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("parameter_repo", type=Path) + parser.add_argument("paper_repo", type=Path) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + namespace = dict( + np=np, + xr=xr, + curve_fit=curve_fit, + minimize_scalar=minimize_scalar, + log_ndtr=log_ndtr, + ) + source = args.parameter_repo / "src/pysteps_param/rain_stats_compute.py" + source_hash = _load_functions( + source, + {"power_spectrum_1D", "compute_field_stats", "fit_lower_truncated_normal"}, + namespace, + ) + report = dict( + source_sha256=source_hash, + source_revision=subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=args.parameter_repo, text=True + ).strip(), + threshold_mm_per_hour=1.0, + lower_truncation_dbr=1.25, + scale_break_km=15.0, + pixel_size_km=1.0, + cases=[], + ) + paths = sorted((args.paper_repo / "figs/individual_field_fits").glob("*.npz")) + if not paths: + raise ValueError("No saved paper fields found") + for path in paths: + with np.load(path) as saved: + rain = saved["rain"] + transformed, metadata = dB_transform( + rain.astype(float), threshold=1.0, zerovalue=-0.1 + ) + source_spectrum, source_model = namespace["power_spectrum_1D"](transformed, 15) + source_statistics = namespace["compute_field_stats"]( + transformed, metadata, fit_truncated=True, lower_truncation=1.25 + ) + frequency, power = rapsd_logarithmic(transformed) + model = fit_power_law_spectrum(frequency, power, 15) + statistics = compute_field_statistics(transformed, metadata["threshold"], 1.25) + np.testing.assert_allclose( + 10 * np.log10(frequency), source_spectrum.psd_bins.values, atol=1e-12 + ) + np.testing.assert_allclose( + 10 * np.log10(power), source_spectrum.psd.values, atol=1e-10 + ) + differences = { + name: abs(model[name] - source_model[name]) + for name in ("beta_1", "beta_2", "c1", "c2") + } + assert max(differences.values()) < 3e-5 + for name in ("ordinary_mean", "ordinary_stdev", "latent_mean", "latent_stdev"): + np.testing.assert_allclose( + statistics[name], source_statistics[name], rtol=1e-12 + ) + np.testing.assert_allclose( + statistics["rain_fraction"], + source_statistics["nonzero_fraction"], + rtol=1e-12, + ) + report["cases"].append( + dict( + name=path.stem, + input_sha256=hashlib.sha256(path.read_bytes()).hexdigest(), + shape=list(rain.shape), + maximum_spectrum_error_db=float( + np.max(abs(10 * np.log10(power) - source_spectrum.psd.values)) + ), + spectral_parameter_absolute_differences=differences, + rain_fraction=statistics["rain_fraction"], + marginal_fit_status=statistics["nonzero_fit"]["status"], + spectrum_fit_status=model["status"], + ) + ) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + print(f"Compared {len(paths)} saved fields; report: {args.output}") + + +if __name__ == "__main__": + main() diff --git a/pysteps/tests/data/generate_calibrated_reference.py b/pysteps/tests/data/generate_calibrated_reference.py new file mode 100644 index 00000000..d047085f --- /dev/null +++ b/pysteps/tests/data/generate_calibrated_reference.py @@ -0,0 +1,159 @@ +"""Freeze original generator outputs on synthetic targets without database access. + +Run with the source project's dependencies (including xarray): +python generate_calibrated_reference.py /path/to/pysteps_param + +Normal tests read only the JSON and NPZ fixtures, not the source repository. +""" + +import argparse +import ast +from dataclasses import dataclass, make_dataclass, replace +import hashlib +import json +from pathlib import Path +import subprocess +from typing import Optional + +import numpy as np +from scipy import interpolate, stats +from scipy.optimize import curve_fit + +from generate_parametric_reference import _load_functions + + +def source_generator(parameter_repo): + """Load the actual source functions, result class, and constants. + + Supply a five-scalar dataclass in place of the operational StepsParameters. + The numerical functions access only these five attributes; replace() retains + its original semantics. No QA selection or database identity is introduced. + """ + import xarray as xr + + source = parameter_repo / "src/pysteps_param" + parameters = make_dataclass( + "ScalarParameters", + [ + (name, float) + for name in ("beta_1", "beta_2", "rain_fraction", "mean_db", "stdev_db") + ], + ) + namespace = dict( + __name__=__name__, + np=np, + xr=xr, + stats=stats, + interpolate=interpolate, + curve_fit=curve_fit, + dataclass=dataclass, + replace=replace, + Optional=Optional, + StepsParameters=parameters, + ) + stats_hash = _load_functions( + source / "rain_stats_compute.py", {"power_spectrum_1D"}, namespace + ) + path = source / "stochastic_generator.py" + original = path.read_text() + nodes = [ + node + for node in ast.parse(original).body + if isinstance(node, ast.FunctionDef) + or isinstance(node, ast.ClassDef) + and node.name == "CalibratedStochasticField" + or isinstance(node, ast.Assign) + and all( + isinstance(target, ast.Name) and target.id.isupper() + for target in node.targets + ) + ] + exec(compile(ast.Module(body=nodes, type_ignores=[]), str(path), "exec"), namespace) + provenance = dict( + source_revision=subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=parameter_repo, text=True + ).strip(), + source_sha256={ + "rain_stats_compute.py": stats_hash, + "stochastic_generator.py": hashlib.sha256(original.encode()).hexdigest(), + }, + ) + return namespace, provenance + + +def main(): + import scipy + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("parameter_repo", type=Path) + args = parser.parse_args() + source, provenance = source_generator(args.parameter_repo) + provenance.update(numpy_version=np.__version__, scipy_version=scipy.__version__) + metadata = dict(provenance=provenance, cases=[]) + arrays = {} + cases = [ + ((128, 96), 0.08, 4, {}), + ((128, 96), 0.3, 17, {}), + ((95, 127), 0.05, 11, {"distance_threshold": 0.01}), + ] + for index, (shape, fraction, seed, settings) in enumerate(cases): + parameters = dict( + beta_1=-2.4, beta_2=-3.1, rain_fraction=fraction, mean_db=6.0, stdev_db=3.5 + ) + target = source["StepsParameters"](**parameters) + result = source["gen_calibrated_stoch_field"]( + target, + shape[1], + shape[0], + 1.0, + 20.0, + 0.0, + rng=np.random.default_rng(seed), + **settings, + ) + arrays[f"calibrated_{index}"] = result.field + arrays[f"uncalibrated_{index}"] = source["gen_stoch_field"]( + target, shape[1], shape[0], 1.0, 20.0, 0.0, rng=np.random.default_rng(seed) + ) + metadata["cases"].append( + dict( + shape=shape, + parameters=parameters, + seed=seed, + kmperpixel=1.0, + scale_break=20.0, + threshold=0.0, + settings=settings, + diagnostics={ + name: getattr(result, name) + for name in ( + "estimated_beta_1", + "estimated_beta_2", + "distance", + "attempts", + "selected_attempt", + "tolerance_achieved", + ) + }, + ) + ) + target = source["StepsParameters"]( + beta_1=-2.0, beta_2=-3.0, rain_fraction=0.35, mean_db=6.5, stdev_db=1.2 + ) + arrays["post_ar_input"] = np.random.default_rng(19).normal(size=(48, 64)) + arrays["post_ar_output"] = source["normalize_db_field"]( + arrays["post_ar_input"], target, 0.0, -0.1 + ) + metadata["post_ar_parameters"] = dict( + rain_fraction=0.35, mean=6.5, stdev=1.2, threshold=0.0, zerovalue=-0.1 + ) + prefix = Path(__file__).with_name("calibrated_generator_reference") + np.savez_compressed(prefix.with_suffix(".npz"), **arrays) + prefix.with_suffix(".json").write_text( + json.dumps(metadata, indent=2, allow_nan=False) + "\n" + ) + print(prefix) + + +if __name__ == "__main__": + main() diff --git a/pysteps/tests/data/generate_nowcast_reference.py b/pysteps/tests/data/generate_nowcast_reference.py new file mode 100644 index 00000000..3473b7cd --- /dev/null +++ b/pysteps/tests/data/generate_nowcast_reference.py @@ -0,0 +1,315 @@ +"""Freeze the original preparation, QA, and multi-lead forecast numerics. + +Run in the source project's environment: +python generate_nowcast_reference.py /path/to/pysteps_param + +No database is accessed. AST extraction loads original numerical definitions; +only a relative import inside QA is removed to resolve the extracted helpers. +Reference independent streams are supplied explicitly to the original generator. +""" + +import argparse +import ast +from copy import deepcopy +from dataclasses import dataclass, field +import hashlib +import inspect +import json +from pathlib import Path +from typing import Any, ClassVar, Mapping, TypedDict, cast +import warnings +import sys + +import numpy as np +from scipy.ndimage import gaussian_filter, map_coordinates +from scipy.optimize import minimize_scalar +from scipy.special import log_ndtr + +from pysteps import extrapolation +from pysteps.cascade.bandpass_filters import filter_gaussian +from pysteps.cascade.decomposition import decomposition_fft +from pysteps.timeseries.autoregression import ( + estimate_ar_params_yw, + adjust_lag2_corrcoef2, +) + +from generate_calibrated_reference import source_generator +from generate_parametric_reference import _load_functions + + +def source_engine(parameter_repo): + namespace, provenance = source_generator(parameter_repo) + source = parameter_repo / "src/pysteps_param" + namespace.update( + __file__=str(source / "steps_params.py"), + deepcopy=deepcopy, + copy=__import__("copy"), + np=np, + Path=Path, + json=json, + dataclass=dataclass, + field=field, + ClassVar=ClassVar, + Mapping=Mapping, + warnings=warnings, + Any=Any, + TypedDict=TypedDict, + cast=cast, + minimize_scalar=minimize_scalar, + log_ndtr=log_ndtr, + extrapolation=extrapolation, + map_coordinates=map_coordinates, + estimate_ar_params_yw=estimate_ar_params_yw, + adjust_lag2_corrcoef2=adjust_lag2_corrcoef2, + filter_gaussian=filter_gaussian, + decomposition_fft=decomposition_fft, + ) + from pysteps.cascade.decomposition import recompose_fft + + namespace["recompose_fft"] = recompose_fft + for filename, names in [ + ( + "rain_stats_compute.py", + { + "compute_field_stats", + "fit_lower_truncated_normal", + "correlation_length", + "lagr_auto_cor", + }, + ), + ("cascade_utils.py", {"calculate_wavelengths"}), + ( + "shared_utils.py", + { + "update_field", + "prepare_advection_coordinates", + "_adjust_model_lag2", + "fit_auto_cors", + "calc_model_auto_cors", + "model_correlation_diagnostics", + }, + ), + ]: + provenance["source_sha256"][filename] = _load_functions( + source / filename, names, namespace + ) + for filename, wanted in [ + ("model_config.py", None), + ("transformer.py", {"BaseTransformer", "DBTransformer"}), + ("steps_params.py", {"StepsParameters"}), + ]: + path = source / filename + tree = ast.parse(path.read_text()) + if wanted is not None: + tree.body = [ + node + for node in tree.body + if isinstance(node, ast.ClassDef) and node.name in wanted + ] + # QA's relative import resolves to the same original functions above. + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + node.body = [ + item + for item in node.body + if not isinstance(item, ast.ImportFrom) + ] + exec(compile(tree, str(path), "exec"), namespace) + provenance["source_sha256"][filename] = hashlib.sha256( + path.read_bytes() + ).hexdigest() + namespace["get_transformer"] = lambda name, threshold: namespace["DBTransformer"]( + threshold=threshold + ) + namespace["CORRELATION_TIME_DEFINITION"] = "ar_acf_sum_including_zero_v2" + provenance["source_sha256"]["cli/nowcast.py"] = hashlib.sha256( + (source / "cli/nowcast.py").read_bytes() + ).hexdigest() + provenance["source_sha256"]["config/low_war_fallback.json"] = hashlib.sha256( + (source / "config/low_war_fallback.json").read_bytes() + ).hexdigest() + import scipy + + provenance.update( + python_version=sys.version.split()[0], + numpy_version=np.__version__, + scipy_version=scipy.__version__, + native_pysteps_sha256={ + name: hashlib.sha256( + Path(inspect.getsourcefile(function)).read_bytes() + ).hexdigest() + for name, function in ( + ("cascade/decomposition.py", decomposition_fft), + ("cascade/bandpass_filters.py", filter_gaussian), + ("timeseries/autoregression.py", estimate_ar_params_yw), + ( + "extrapolation/semilagrangian.py", + extrapolation.get_method("semilagrangian"), + ), + ) + }, + ) + return namespace, provenance + + +def config(shape, timestep=5.0, kmperpixel=1.0, scale_break=20.0): + return dict( + n_rows=shape[0], + n_cols=shape[1], + n_cascade_levels=6, + n_forecasts=3, + n_ensembles=2, + p_size=kmperpixel * 1000, + ht=0.81, + alpha=2.72, + timestep=timestep * 60, + ar_order=2, + rain_threshold=1.0, + scale_break_km=scale_break, + transform="dbr", + ) + + +def prepare(source, rain, velocity, cfg): + transformer = source["DBTransformer"](threshold=cfg["rain_threshold"]) + transformed = transformer.transform(rain) + bp_filter = filter_gaussian(rain.shape[1:], cfg["n_cascade_levels"]) + cascades = [ + decomposition_fft(f, bp_filter, compute_stats=True, normalize=True) + for f in transformed + ] + marginal = source["compute_field_stats"]( + transformed[-1], transformer.metadata, fit_truncated=True, lower_truncation=1.25 + ) + _, spectrum = source["power_spectrum_1D"]( + transformed[-1], cfg["scale_break_km"] / (cfg["p_size"] / 1000) + ) + correlations = source["lagr_auto_cor"]( + np.stack([c["cascade_levels"] for c in cascades]), velocity + ) + reference_time = source["correlation_length"]( + *correlations[0], cfg["timestep"] / 60 + ) + parameters = source["StepsParameters"]( + metadata={}, + ordinary_mean_db=marginal["ordinary_mean"], + ordinary_stdev_db=marginal["ordinary_stdev"], + latent_mean_db=marginal["latent_mean"], + latent_stdev_db=marginal["latent_stdev"], + rain_fraction=marginal["nonzero_fraction"], + beta_1=spectrum["beta_1"], + beta_2=spectrum["beta_2"], + corl_zero=reference_time, + ).apply_qa(cfg) + return parameters, cascades, bp_filter, transformer + + +def evolve(source, rain, velocity, cfg, parameters, seed, n_members=2, n_leads=3): + bp_filter = filter_gaussian(rain.shape[1:], cfg["n_cascade_levels"]) + transformer = source["DBTransformer"](threshold=cfg["rain_threshold"]) + init = [ + decomposition_fft( + transformer.transform(f), bp_filter, compute_stats=True, normalize=True + ) + for f in rain[-1:-3:-1] + ] + coordinates = source["prepare_advection_coordinates"](velocity) + taper = gaussian_filter( + extrapolation.get_method("semilagrangian")( + np.ones(rain.shape[1:]), velocity, 1, outval=0 + )[0], + 5.0, + ) + seeds = np.random.SeedSequence(seed).spawn(n_members) + original_generator = source["gen_calibrated_stoch_field"] + output = np.empty((n_members, n_leads, *rain.shape[1:])) + calibration = [[] for _ in range(n_members)] + try: + for member in range(n_members): + histories = deepcopy(init) + rng = np.random.default_rng(seeds[member]) + + def seeded_generator(*args, **kwargs): + result = original_generator(*args, **kwargs, rng=rng) + calibration[member].append( + {k: v for k, v in vars(result).items() if k != "field"} + ) + return result + + source["gen_calibrated_stoch_field"] = seeded_generator + for lead in range(n_leads): + field = source["update_field"]( + histories, + velocity, + parameters, + bp_filter, + cfg, + advection_coordinates=coordinates, + ) + output[member, lead] = transformer.inverse_transform(field) + histories = [ + decomposition_fft( + field * taper, bp_filter, compute_stats=True, normalize=True + ), + histories[0], + ] + finally: + source["gen_calibrated_stoch_field"] = original_generator + return output, calibration + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("parameter_repo", type=Path) + args = parser.parse_args() + source, provenance = source_engine(args.parameter_repo) + arrays, cases = {}, [] + for index, fraction in enumerate([0.2, 0.04]): + shape = (128, 96) + cfg = config(shape, timestep=7.5 if index else 5.0) + target = source["StepsParameters"]( + metadata={}, + beta_1=-2.4, + beta_2=-3.1, + rain_fraction=fraction, + mean_db=6, + stdev_db=3, + ) + dbr = source["gen_stoch_field"]( + target, shape[1], shape[0], 1, 20, 0, rng=np.random.default_rng(9 + index) + ) + rain = 10 ** (dbr / 10) + rain[rain < 1] = 0 + history = np.stack([np.roll(rain, -2, axis=1), np.roll(rain, -1, axis=1), rain]) + velocity = np.zeros((2, *shape)) + velocity[0] = 1 + parameters, _, _, _ = prepare(source, history, velocity, cfg) + seed = 20261015 + index + output, calibration = evolve(source, history, velocity, cfg, parameters, seed) + arrays.update( + { + f"history_{index}": history, + f"velocity_{index}": velocity, + f"forecast_{index}": output, + } + ) + cases.append( + dict( + config=cfg, + seed=seed, + parameters=parameters.active_parameters(), + moment_source=parameters.qa["moment_source"], + calibration=calibration, + ) + ) + folder = Path(__file__).parent + np.savez_compressed(folder / "parametric_nowcast_reference.npz", **arrays) + (folder / "parametric_nowcast_reference.json").write_text( + json.dumps(dict(provenance=provenance, cases=cases), indent=2, allow_nan=False) + + "\n" + ) + print(f"Wrote {len(cases)} source forecast cases") + + +if __name__ == "__main__": + main() diff --git a/pysteps/tests/data/generate_parametric_reference.py b/pysteps/tests/data/generate_parametric_reference.py new file mode 100644 index 00000000..b1440919 --- /dev/null +++ b/pysteps/tests/data/generate_parametric_reference.py @@ -0,0 +1,164 @@ +"""Regenerate numerical references from the two original source repositories. + +Run in the source project's environment (including xarray): +python generate_parametric_reference.py /path/to/pysteps_param /path/to/psteps_param_paper + +Tests read only the generated JSON and do not need either external repository. +""" + +import argparse +import ast +import hashlib +import json +from pathlib import Path +import subprocess + +import numpy as np +from scipy.optimize import brentq, curve_fit, minimize_scalar +from scipy.special import log_ndtr + +from pysteps import extrapolation +from pysteps.cascade.bandpass_filters import filter_gaussian + + +def _load_functions(path, names, namespace): + # Load the original numerical definitions without executing package-level + # imports of database or operational modules. + source = path.read_text() + definitions = [ + node + for node in ast.parse(source).body + if isinstance(node, ast.FunctionDef) and node.name in names + ] + if {node.name for node in definitions} != set(names): + raise ValueError(f"Missing source functions in {path}") + exec( + compile(ast.Module(body=definitions, type_ignores=[]), str(path), "exec"), + namespace, + ) + return hashlib.sha256(source.encode()).hexdigest() + + +def main(): + import xarray as xr + import scipy + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("parameter_repo", type=Path) + parser.add_argument("paper_repo", type=Path) + args = parser.parse_args() + source = args.parameter_repo / "src/pysteps_param" + namespace = dict( + np=np, + xr=xr, + curve_fit=curve_fit, + minimize_scalar=minimize_scalar, + log_ndtr=log_ndtr, + brentq=brentq, + extrapolation=extrapolation, + ) + hashes = {} + hashes["pysteps_param/src/pysteps_param/rain_stats_compute.py"] = _load_functions( + source / "rain_stats_compute.py", + { + "power_spectrum_1D", + "fit_lower_truncated_normal", + "compute_field_stats", + "correlation_length", + "lagr_auto_cor", + }, + namespace, + ) + hashes["pysteps_param/src/pysteps_param/shared_utils.py"] = _load_functions( + source / "shared_utils.py", {"fit_auto_cors", "_adjust_model_lag2"}, namespace + ) + hashes["psteps_param_paper/workflows/reference_timescale_experiment.py"] = ( + _load_functions( + args.paper_repo / "workflows/reference_timescale_experiment.py", + {"reference_log"}, + namespace, + ) + ) + result = dict( + provenance=dict( + source_revisions={ + name: subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=root, text=True + ).strip() + for name, root in [ + ("pysteps_param", args.parameter_repo), + ("psteps_param_paper", args.paper_repo), + ] + }, + source_sha256=hashes, + numpy_version=np.__version__, + scipy_version=scipy.__version__, + description="Numerical references evaluated from original function definitions on deterministic synthetic inputs; no database access.", + ), + spectra=[], + ar2_inverse=[], + ) + for shape in [(128, 128), (95, 127)]: + y, x = np.indices(shape) + field = np.random.default_rng(23).normal(size=shape) + 4 * np.cos( + 2 * np.pi * (5 * y / shape[0] + 3 * x / shape[1]) + ) + models = [] + for scale_break in (None, 20): + spectrum, model = namespace["power_spectrum_1D"](field, scale_break) + models.append(model) + result["spectra"].append( + dict( + shape=shape, + frequency_db=spectrum.psd_bins.values.tolist(), + power_db=spectrum.psd.values.tolist(), + models=models, + ) + ) + rng = np.random.default_rng(37) + field = rng.normal(4, 3, size=(128, 96)) + field[rng.uniform(size=field.shape) > 0.3] = -0.1 + stats = namespace["compute_field_stats"]( + field, + {"threshold": 0, "zerovalue": -0.1, "transform": "dB"}, + fit_truncated=True, + lower_truncation=1.25, + ) + result["marginal"] = { + name: stats[name] + for name in [ + "ordinary_mean", + "ordinary_stdev", + "latent_mean", + "latent_stdev", + "mean", + "stdev", + ] + } + result["marginal"]["rain_fraction"] = stats["nonzero_fraction"] + history = np.random.default_rng(45).normal(size=(3, 2, 24, 32)) + velocity = np.full((2, 24, 32), 0.25) + velocity[0] = 0.6 + result["lags"] = namespace["lagr_auto_cor"](history, velocity).tolist() + for alpha in (0.8, 2.72, 4): + for time in (5, 10, 60, 420): + r1, r2, _ = namespace["fit_auto_cors"](time, alpha, 5) + result["ar2_inverse"].append(dict(time=time, alpha=alpha, pair=[r1, r2])) + wavelengths = 1 / filter_gaussian((256, 256), 6, d=1)["central_freqs"] + times = np.array([[60, 42, 25, 10, 8, 6], [120, 70, 31, 20, 10, 5]]) + exponent = 0.7718612877449645 + result["paper_reference"] = dict( + wavelengths=wavelengths.tolist(), + times=times.tolist(), + exponent=exponent, + reference_times=np.exp( + namespace["reference_log"](times, wavelengths, exponent) + ).tolist(), + ) + path = Path(__file__).with_name("parametric_estimators_reference.json") + path.write_text(json.dumps(result, indent=2, allow_nan=False) + "\n") + print(path) + + +if __name__ == "__main__": + main() diff --git a/pysteps/tests/data/parametric_estimators_reference.json b/pysteps/tests/data/parametric_estimators_reference.json new file mode 100644 index 00000000..be59727d --- /dev/null +++ b/pysteps/tests/data/parametric_estimators_reference.json @@ -0,0 +1,420 @@ +{ + "provenance": { + "source_revisions": { + "pysteps_param": "94bd4c1571637a8f246ac391470e1a820d6376a6", + "psteps_param_paper": "335f7660775480d865d9771820a81923c461832a" + }, + "source_sha256": { + "pysteps_param/src/pysteps_param/rain_stats_compute.py": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", + "pysteps_param/src/pysteps_param/shared_utils.py": "4e7346cda389b99971246e4cf060adeec9c51cc9444704446a071ccc44a5288f", + "psteps_param_paper/workflows/reference_timescale_experiment.py": "f93769bb6b4f94701b682c2947bfec661d0fa089fafa0ad2132d4744e7d6cee5" + }, + "numpy_version": "2.5.1", + "scipy_version": "1.18.0", + "description": "Numerical references evaluated from original function definitions on deterministic synthetic inputs; no database access." + }, + "spectra": [ + { + "shape": [ + 128, + 128 + ], + "frequency_db": [ + -17.667251038327528, + -16.62037767221858, + -16.358659330691346, + -16.096940989164107, + -15.573504306109635, + -15.050067623055163, + -14.788349281527926, + -14.52663094000069, + -14.003194256946216, + -13.74147591541898, + -13.479757573891744, + -13.218039232364507, + -12.956320890837269, + -12.694602549310032, + -12.432884207782795, + -12.17116586625556, + -11.909447524728321, + -11.647729183201086, + -11.386010841673848, + -11.12429250014661, + -10.862574158619376, + -10.600855817092139, + -10.3391374755649, + -10.077419134037664, + -9.815700792510428, + -9.553982450983193, + -9.292264109455955, + -9.03054576792872, + -8.768827426401483, + -8.507109084874246, + -8.24539074334701, + -7.983672401819772, + -7.721954060292536, + -7.460235718765299, + -7.198517377238063, + -6.936799035710824, + -6.675080694183588, + -6.413362352656352, + -6.151644011129115, + -5.889925669601878, + -5.628207328074641, + -5.366488986547405, + -5.104770645020168, + -4.843052303492932, + -4.581333961965695, + -4.319615620438459, + -4.057897278911222, + -3.796178937383985, + -3.5344605958567494, + -3.2727422543295113, + -3.011023912802275, + -2.7493055712750385, + -2.4875872297478026, + -2.225868888220565, + -1.9641505466933276, + -1.702432205166092 + ], + "power_db": [ + 32.21404823693171, + 32.89115806726739, + 31.577373883302656, + 28.82632685065227, + 32.53204791407693, + 18.227053814532425, + 32.34731566908866, + 32.59450536851558, + 34.32142466901202, + 32.22744056651604, + 72.98771267445036, + 30.565395736421802, + 34.95378940097872, + 29.793454900506084, + 30.17849205522203, + 30.86793520242505, + 33.25565618937198, + 32.28764048250997, + 32.474320741622556, + 34.88645980840497, + 32.45018941268981, + 33.86022169320155, + 34.03311950786575, + 31.828145357820013, + 32.2523420785126, + 31.985915536416858, + 33.33305235105088, + 33.17467990441208, + 32.26714354192855, + 31.98576568056385, + 33.20549716177654, + 33.1596442083763, + 32.81630883012787, + 32.45988258537234, + 32.84419641634696, + 32.56861411042712, + 32.71016110087893, + 32.67319800011376, + 32.67739642508208, + 32.97680110663359, + 32.82724636105732, + 32.345353412324805, + 32.5310842412989, + 32.897376674033836, + 32.27736692836629, + 32.67337497580174, + 32.38010211913294, + 32.533425911263386, + 32.38907888191223, + 32.34062182235669, + 32.384596188693415, + 32.47910959397415, + 32.85629115161199, + 32.712122674743746, + 32.84431812332657, + 33.18497928300023 + ], + "models": [ + { + "beta_1": -0.05268062227348369, + "beta_2": null, + "c1": 32.54838859499382, + "c2": null, + "scale_break": null + }, + { + "beta_1": 1.750219739903674, + "beta_2": -0.40906661474982897, + "c1": 57.923321834892, + "c2": 29.830358668570593, + "scale_break": 20.0 + } + ] + }, + { + "shape": [ + 95, + 127 + ], + "frequency_db": [ + -16.553811597148872, + -16.225674374749133, + -15.89753715234939, + -14.913125485150168, + -14.584988262750427, + -14.256851040350684, + -13.928713817950946, + -13.600576595551207, + -13.272439373151464, + -12.944302150751723, + -12.616164928351983, + -12.28802770595224, + -11.959890483552499, + -11.63175326115276, + -11.303616038753017, + -10.975478816353279, + -10.647341593953536, + -10.319204371553797, + -9.991067149154055, + -9.662929926754313, + -9.334792704354573, + -9.00665548195483, + -8.67851825955509, + -8.350381037155351, + -8.02224381475561, + -7.694106592355868, + -7.365969369956128, + -7.0378321475563865, + -6.709694925156645, + -6.381557702756905, + -6.053420480357165, + -5.725283257957425, + -5.397146035557682, + -5.069008813157943, + -4.740871590758201, + -4.41273436835846, + -4.084597145958718, + -3.756459923558978, + -3.428322701159238, + -3.1001854787594976, + -2.7720482563597564, + -2.4439110339600143, + -2.115773811560275, + -1.7876365891605324 + ], + "power_db": [ + 29.596752108332932, + 28.599293553596823, + 33.77561208788202, + 31.812027504318486, + 29.098075789303095, + 30.40169398250597, + 29.327640206538494, + 27.363420838411884, + 31.733827101431466, + 31.984511553230636, + 32.370948559104335, + 69.0752136877862, + 31.664778170206, + 30.87052803355375, + 30.2926202517147, + 32.23407752743059, + 31.10302473421841, + 31.76113827303327, + 31.237543669781328, + 32.21429105170373, + 32.443496881228896, + 32.101719729101546, + 31.952552126310287, + 31.450292614378963, + 31.70056341231345, + 30.785298020765595, + 31.38208548693949, + 30.76054457607694, + 30.415209154698513, + 31.404183893420626, + 31.685923903583767, + 31.664054109928223, + 31.64058740674395, + 31.220371947476643, + 31.384272922719155, + 30.951877101954558, + 31.203472798749754, + 31.306650889532584, + 31.07704516767248, + 31.3949229114742, + 31.41658267109576, + 31.19917051893485, + 31.11708521309568, + 30.934879607893023 + ], + "models": [ + { + "beta_1": -0.21547126372428127, + "beta_2": null, + "c1": 30.27008964716364, + "c2": null, + "scale_break": null + }, + { + "beta_1": 1.8738479155680257, + "beta_2": -0.5102178985934316, + "c1": 59.072933167953764, + "c2": 28.0555218093425, + "scale_break": 20.0 + } + ] + } + ], + "ar2_inverse": [ + { + "time": 5, + "alpha": 0.8, + "pair": [ + 0.0, + 0.0 + ] + }, + { + "time": 10, + "alpha": 0.8, + "pair": [ + 0.25029703934242475, + 0.3301904970651707 + ] + }, + { + "time": 60, + "alpha": 0.8, + "pair": [ + 0.7516432139329509, + 0.7958100010789851 + ] + }, + { + "time": 420, + "alpha": 0.8, + "pair": [ + 0.9547653195683252, + 0.9636455237978131 + ] + }, + { + "time": 5, + "alpha": 2.72, + "pair": [ + 0.0, + 0.0 + ] + }, + { + "time": 10, + "alpha": 2.72, + "pair": [ + 0.5700065264871803, + 0.25880799846894853 + ] + }, + { + "time": 60, + "alpha": 2.72, + "pair": [ + 0.9580235217567927, + 0.889904060320336 + ] + }, + { + "time": 420, + "alpha": 2.72, + "pair": [ + 0.9943443348521103, + 0.9846913121295453 + ] + }, + { + "time": 5, + "alpha": 4, + "pair": [ + 0.0, + 0.0 + ] + }, + { + "time": 10, + "alpha": 4, + "pair": [ + 0.5700065264871803, + 0.25880799846894853 + ] + }, + { + "time": 60, + "alpha": 4, + "pair": [ + 0.9852101493475673, + 0.9498681838104814 + ] + }, + { + "time": 420, + "alpha": 4, + "pair": [ + 0.9997132719080527, + 0.9988800690605281 + ] + } + ], + "marginal": { + "ordinary_mean": 4.5544651679144215, + "ordinary_stdev": 2.544133913407154, + "latent_mean": 4.02706361980481, + "latent_stdev": 2.9781258381020006, + "mean": 1.139150098359972, + "stdev": 2.5022682644201866, + "rain_fraction": 0.2740885416666667 + }, + "lags": [ + [ + 0.007973669863881415, + 0.01433718006760386 + ], + [ + -0.005838299935575144, + 0.005083562758421983 + ] + ], + "paper_reference": { + "wavelengths": [ + 256.0, + 70.28517926862416, + 31.308488057340607, + 13.946345938598434, + 6.212390859719535, + 2.0 + ], + "times": [ + [ + 60, + 42, + 25, + 10, + 8, + 6 + ], + [ + 120, + 70, + 31, + 20, + 10, + 5 + ] + ], + "exponent": 0.7718612877449645, + "reference_times": [ + 95.28171104914436, + 152.91251252988084 + ] + } +} diff --git a/pysteps/tests/data/parametric_nowcast_reference.json b/pysteps/tests/data/parametric_nowcast_reference.json new file mode 100644 index 00000000..ef566ae0 --- /dev/null +++ b/pysteps/tests/data/parametric_nowcast_reference.json @@ -0,0 +1,221 @@ +{ + "provenance": { + "source_revision": "94bd4c1571637a8f246ac391470e1a820d6376a6", + "source_sha256": { + "rain_stats_compute.py": "2fd6f0b622e88e143d8128f80cb5d0920f0111c1021c6c448222c90bff92c889", + "stochastic_generator.py": "94c491e4fd90da0c733ce65b48227b6b73dc31a8fc6a23a44e211ad36b3f9343", + "cascade_utils.py": "77c362a23aa46b214f1d7f613fe0e747a5f6e7f861c17f85094c5b91db13d403", + "shared_utils.py": "4e7346cda389b99971246e4cf060adeec9c51cc9444704446a071ccc44a5288f", + "model_config.py": "3504c8492f8158cd84b155753cb205e0a7810cd04c37d35f253c9d25fc374128", + "transformer.py": "8693d646602538cd75332cd4367170b4387c9844102b011b7090dae033482703", + "steps_params.py": "265c31239ac8647fd599648e0f00dbdbc8c963e4a7bdaadfa89edb177c4c6c28", + "cli/nowcast.py": "d892c72f8daa608669fce0e185397a9664644b53663eeb1d5cc3708ceb7bec26", + "config/low_war_fallback.json": "bf0b3e0c9c12d4ce0f1cf64d3fe997a8e3364f285666071c24835c3141c45e87" + }, + "python_version": "3.12.13", + "numpy_version": "2.5.1", + "scipy_version": "1.18.0", + "native_pysteps_sha256": { + "cascade/decomposition.py": "2963be8c5399afa8c066ab6df93d199f8362f476a93d24add96a508aecb2b84e", + "cascade/bandpass_filters.py": "0527beb2aa67a620425479fa09fa18dbf8ef311109a1de72e9df6d436ee7403b", + "timeseries/autoregression.py": "08205f5de8d6dbe6fea0d3021c929d50990a6379098359a1aab6c4315808d851", + "extrapolation/semilagrangian.py": "5f870efe3b9152fefd417ecc61ffa188d8e9a306515f676b66890d9f1443e9c3" + } + }, + "cases": [ + { + "config": { + "n_rows": 128, + "n_cols": 96, + "n_cascade_levels": 6, + "n_forecasts": 3, + "n_ensembles": 2, + "p_size": 1000.0, + "ht": 0.81, + "alpha": 2.72, + "timestep": 300.0, + "ar_order": 2, + "rain_threshold": 1.0, + "scale_break_km": 20.0, + "transform": "dbr" + }, + "seed": 20261015, + "parameters": { + "mean_db": 6.0028472842915725, + "stdev_db": 3.0012414749875695, + "rain_fraction": 0.19710286458333334, + "beta_1": -2.304747110331049, + "beta_2": -2.5631814182884716, + "corl_zero": 456.36535657886674, + "lag_1": [ + 0.9947989543385891, + 0.9871752674307953, + 0.9771449287175584, + 0.9587433096366739, + 0.9237438162041633, + 0.7935185195767728 + ], + "lag_2": [ + 0.9859163541951547, + 0.9655002791814661, + 0.9390493841270735, + 0.89172384927532, + 0.8059353402996958, + 0.5395489647808457 + ] + }, + "moment_source": "latent", + "calibration": [ + [ + { + "estimated_beta_1": -2.97392072940944, + "estimated_beta_2": -2.483368847914942, + "distance": 2.58728403725768, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -1.817123223192054, + "estimated_beta_2": -2.6302184889764453, + "distance": 1.8891966298004956, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -2.9519417030723605, + "estimated_beta_2": -2.4530007388125172, + "distance": 2.518418223677037, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false + } + ], + [ + { + "estimated_beta_1": -1.7058699586645432, + "estimated_beta_2": -2.9044931766578634, + "distance": 2.6113997297501057, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -2.8656737433833874, + "estimated_beta_2": -2.5431414850604748, + "distance": 2.1564304711388225, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -2.8926687128107043, + "estimated_beta_2": -2.625601365428789, + "distance": 2.270191677685425, + "attempts": 1, + "selected_attempt": 1, + "tolerance_achieved": false + } + ] + ] + }, + { + "config": { + "n_rows": 128, + "n_cols": 96, + "n_cascade_levels": 6, + "n_forecasts": 3, + "n_ensembles": 2, + "p_size": 1000.0, + "ht": 0.81, + "alpha": 2.72, + "timestep": 450.0, + "ar_order": 2, + "rain_threshold": 1.0, + "scale_break_km": 20.0, + "transform": "dbr" + }, + "seed": 20261016, + "parameters": { + "mean_db": 6.0135757197788715, + "stdev_db": 3.0093428695820377, + "rain_fraction": 0.03955078125, + "beta_1": -1.45, + "beta_2": -3.253958088759979, + "corl_zero": 330.61752959848894, + "lag_1": [ + 0.9891292643911687, + 0.9728202376077736, + 0.9506635983379176, + 0.9078567874780119, + 0.816699134701302, + 0.37407983990030463 + ], + "lag_2": [ + 0.9707073074247313, + 0.9277878117174473, + 0.8714311340224543, + 0.7687890306712478, + 0.5776929978968777, + 0.10753778774958642 + ] + }, + "moment_source": "latent", + "calibration": [ + [ + { + "estimated_beta_1": -1.523354904476627, + "estimated_beta_2": -2.8895547119152427, + "distance": 1.3481136783625316, + "attempts": 5, + "selected_attempt": 3, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -1.5548026590261312, + "estimated_beta_2": -2.975659648574481, + "distance": 1.0843549412999052, + "attempts": 5, + "selected_attempt": 3, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -0.6578986279771498, + "estimated_beta_2": -2.7596856788529567, + "distance": 3.5298757819809303, + "attempts": 5, + "selected_attempt": 2, + "tolerance_achieved": false + } + ], + [ + { + "estimated_beta_1": -1.2562055284061402, + "estimated_beta_2": -2.827728216458951, + "distance": 1.7123619898017537, + "attempts": 5, + "selected_attempt": 1, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -2.054098896544064, + "estimated_beta_2": -2.944043379979809, + "distance": 2.577699519586813, + "attempts": 5, + "selected_attempt": 1, + "tolerance_achieved": false + }, + { + "estimated_beta_1": -1.1774689342257276, + "estimated_beta_2": -2.769690712172061, + "distance": 2.0410397132918026, + "attempts": 5, + "selected_attempt": 1, + "tolerance_achieved": false + } + ] + ] + } + ] +} diff --git a/pysteps/tests/data/parametric_nowcast_reference.npz b/pysteps/tests/data/parametric_nowcast_reference.npz new file mode 100644 index 0000000000000000000000000000000000000000..e2390ffa9c02cfb3a8b73f7d84b506adaf9ca834 GIT binary patch literal 216094 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 z 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