Skip to content

Commit 2e24248

Browse files
committed
Fix formatting
1 parent 3948d5d commit 2e24248

1 file changed

Lines changed: 140 additions & 166 deletions

File tree

pysteps/tests/test_blending_steps.py

Lines changed: 140 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,35 @@
7777

7878
# fmt:on
7979

80+
81+
def run_and_assert_forecast(
82+
precip, forecast_kwargs, expected_n_ens_members, n_timesteps, converter, metadata
83+
):
84+
"""Run a blended nowcast and assert the output has the expected shape."""
85+
precip_forecast = blending.steps.forecast(precip=precip, **forecast_kwargs)
86+
87+
assert precip_forecast.ndim == 4, "Wrong amount of dimensions in forecast output"
88+
assert (
89+
precip_forecast.shape[0] == expected_n_ens_members
90+
), "Wrong amount of output ensemble members in forecast output"
91+
assert (
92+
precip_forecast.shape[1] == n_timesteps
93+
), "Wrong amount of output time steps in forecast output"
94+
95+
# Transform the data back into mm/h
96+
precip_forecast, _ = converter(precip_forecast, metadata)
97+
98+
assert (
99+
precip_forecast.ndim == 4
100+
), "Wrong amount of dimensions in converted forecast output"
101+
assert (
102+
precip_forecast.shape[0] == expected_n_ens_members
103+
), "Wrong amount of output ensemble members in converted forecast output"
104+
assert (
105+
precip_forecast.shape[1] == n_timesteps
106+
), "Wrong amount of output time steps in converted forecast output"
107+
108+
80109
steps_arg_names = (
81110
"n_models",
82111
"timesteps",
@@ -357,10 +386,9 @@ def test_steps_blending(
357386
assert nwp_velocity.ndim == 5, "nwp_velocity must be a five-dimensional array"
358387

359388
###
360-
# The blending
389+
# Shared forecast kwargs
361390
###
362-
precip_forecast = blending.steps.forecast(
363-
precip=radar_precip,
391+
forecast_kwargs = dict(
364392
precip_models=nwp_precip_decomp,
365393
velocity=radar_velocity,
366394
velocity_models=nwp_velocity,
@@ -402,179 +430,125 @@ def test_steps_blending(
402430
measure_time=False,
403431
)
404432

405-
assert precip_forecast.ndim == 4, "Wrong amount of dimensions in forecast output"
406-
407-
assert (
408-
precip_forecast.shape[0] == expected_n_ens_members
409-
), "Wrong amount of output ensemble members in forecast output"
410-
411-
assert (
412-
precip_forecast.shape[1] == n_timesteps
413-
), "Wrong amount of output time steps in forecast output"
414-
415-
# Transform the data back into mm/h
416-
precip_forecast, _ = converter(precip_forecast, metadata)
417-
418-
assert (
419-
precip_forecast.ndim == 4
420-
), "Wrong amount of dimensions in converted forecast output"
421-
422-
assert (
423-
precip_forecast.shape[0] == expected_n_ens_members
424-
), "Wrong amount of output ensemble members in converted forecast output"
425-
426-
assert (
427-
precip_forecast.shape[1] == n_timesteps
428-
), "Wrong amount of output time steps in converted forecast output"
429-
430-
### Add another test for observations that are 0 at last time step and non-zero before that
431-
radar_precip_zero_latest = radar_precip.copy()
432-
radar_precip_zero_latest[-1] = metadata["zerovalue"]
433-
434433
###
435434
# The blending
436435
###
437-
precip_forecast = blending.steps.forecast(
438-
precip=radar_precip_zero_latest,
439-
precip_models=nwp_precip_decomp,
440-
velocity=radar_velocity,
441-
velocity_models=nwp_velocity,
442-
timesteps=timesteps,
443-
timestep=5.0,
444-
issuetime=datetime.datetime.strptime("202112012355", "%Y%m%d%H%M"),
445-
n_ens_members=n_ens_members,
446-
n_cascade_levels=n_cascade_levels,
447-
blend_nwp_members=blend_nwp_members,
448-
precip_thr=metadata["threshold"],
449-
kmperpixel=1.0,
450-
extrap_method="semilagrangian",
451-
decomp_method="fft",
452-
bandpass_filter_method="gaussian",
453-
noise_method="nonparametric",
454-
noise_stddev_adj="auto",
455-
ar_order=2,
456-
vel_pert_method=vel_pert_method,
457-
weights_method=weights_method,
458-
timestep_start_full_nwp_weight=timestep_start_full_nwp_weight,
459-
conditional=False,
460-
probmatching_method=probmatching_method,
461-
mask_method=mask_method,
462-
resample_distribution=resample_distribution,
463-
smooth_radar_mask_range=smooth_radar_mask_range,
464-
callback=None,
465-
return_output=True,
466-
seed=None,
467-
num_workers=1,
468-
fft_method="numpy",
469-
domain="spatial",
470-
outdir_path_skill=outdir_path_skill,
471-
extrap_kwargs=None,
472-
filter_kwargs=None,
473-
noise_kwargs=None,
474-
vel_pert_kwargs=None,
475-
clim_kwargs=clim_kwargs,
476-
mask_kwargs=mask_kwargs,
477-
measure_time=False,
436+
# Test with full radar data
437+
run_and_assert_forecast(
438+
radar_precip,
439+
forecast_kwargs,
440+
expected_n_ens_members,
441+
n_timesteps,
442+
converter,
443+
metadata,
478444
)
479445

480-
assert precip_forecast.ndim == 4, "Wrong amount of dimensions in forecast output"
481-
482-
assert (
483-
precip_forecast.shape[0] == expected_n_ens_members
484-
), "Wrong amount of output ensemble members in forecast output"
485446

486-
assert (
487-
precip_forecast.shape[1] == n_timesteps
488-
), "Wrong amount of output time steps in forecast output"
489-
490-
# Transform the data back into mm/h
491-
precip_forecast, _ = converter(precip_forecast, metadata)
492-
493-
assert (
494-
precip_forecast.ndim == 4
495-
), "Wrong amount of dimensions in converted forecast output"
496-
497-
assert (
498-
precip_forecast.shape[0] == expected_n_ens_members
499-
), "Wrong amount of output ensemble members in converted forecast output"
500-
501-
assert (
502-
precip_forecast.shape[1] == n_timesteps
503-
), "Wrong amount of output time steps in converted forecast output"
504-
505-
### Add the case where the precip observation is non-zero for the latest time step but 0 before
506-
radar_precip_zero_previous = radar_precip.copy()
507-
radar_precip_zero_previous[:-1] = metadata["zerovalue"]
447+
@pytest.mark.parametrize("ar_order", [1, 2])
448+
def test_steps_blending_partial_zero_radar(ar_order):
449+
"""Test that a forecast succeeds when only the latest radar frame has
450+
precipitation (initiating cell corner case that produces NaN autocorrelations
451+
for the earlier, all-zero cascade levels)."""
452+
pytest.importorskip("cv2")
508453

509-
###
510-
# The blending
511-
###
512-
precip_forecast = blending.steps.forecast(
513-
precip=radar_precip_zero_previous,
514-
precip_models=nwp_precip_decomp,
515-
velocity=radar_velocity,
516-
velocity_models=nwp_velocity,
517-
timesteps=timesteps,
518-
timestep=5.0,
519-
issuetime=datetime.datetime.strptime("202112012355", "%Y%m%d%H%M"),
520-
n_ens_members=n_ens_members,
521-
n_cascade_levels=n_cascade_levels,
522-
blend_nwp_members=blend_nwp_members,
523-
precip_thr=metadata["threshold"],
524-
kmperpixel=1.0,
525-
extrap_method="semilagrangian",
526-
decomp_method="fft",
527-
bandpass_filter_method="gaussian",
528-
noise_method="nonparametric",
529-
noise_stddev_adj="auto",
530-
ar_order=2,
531-
vel_pert_method=vel_pert_method,
532-
weights_method=weights_method,
533-
timestep_start_full_nwp_weight=timestep_start_full_nwp_weight,
534-
conditional=False,
535-
probmatching_method=probmatching_method,
536-
mask_method=mask_method,
537-
resample_distribution=resample_distribution,
538-
smooth_radar_mask_range=smooth_radar_mask_range,
539-
callback=None,
540-
return_output=True,
541-
seed=None,
542-
num_workers=1,
543-
fft_method="numpy",
544-
domain="spatial",
545-
outdir_path_skill=outdir_path_skill,
546-
extrap_kwargs=None,
547-
filter_kwargs=None,
548-
noise_kwargs=None,
549-
vel_pert_kwargs=None,
550-
clim_kwargs=clim_kwargs,
551-
mask_kwargs=mask_kwargs,
552-
measure_time=False,
454+
n_timesteps = 3
455+
metadata = dict(
456+
unit="mm",
457+
transformation="dB",
458+
accutime=5.0,
459+
transform="dB",
460+
zerovalue=0.0,
461+
threshold=0.01,
462+
zr_a=200.0,
463+
zr_b=1.6,
553464
)
554465

555-
assert precip_forecast.ndim == 4, "Wrong amount of dimensions in forecast output"
556-
557-
assert (
558-
precip_forecast.shape[0] == expected_n_ens_members
559-
), "Wrong amount of output ensemble members in forecast output"
560-
561-
assert (
562-
precip_forecast.shape[1] == n_timesteps
563-
), "Wrong amount of output time steps in forecast output"
564-
565-
# Transform the data back into mm/h
566-
precip_forecast, _ = converter(precip_forecast, metadata)
567-
568-
assert (
569-
precip_forecast.ndim == 4
570-
), "Wrong amount of dimensions in converted forecast output"
466+
# Build minimal NWP data (1 model, 4 time steps)
467+
nwp_precip = np.zeros((1, n_timesteps + 1, 200, 200))
468+
for i in range(nwp_precip.shape[1]):
469+
nwp_precip[0, i, 30:185, 32 + i] = 1.0
470+
nwp_precip[0, i, 30:185, 33 + i] = 5.0
471+
nwp_precip[0, i, 30:185, 34 + i] = 5.0
472+
nwp_precip[0, i, 30:185, 35 + i] = 4.5
571473

572-
assert (
573-
precip_forecast.shape[0] == expected_n_ens_members
574-
), "Wrong amount of output ensemble members in converted forecast output"
474+
# Build radar data: only the latest (most recent) frame has precipitation
475+
radar_precip = np.zeros((3, 200, 200))
476+
radar_precip[2, 30:155, 32] = 1.0
477+
radar_precip[2, 30:155, 33] = 5.0
478+
radar_precip[2, 30:155, 34] = 5.0
479+
radar_precip[2, 30:155, 35] = 4.5
575480

576-
assert (
577-
precip_forecast.shape[1] == n_timesteps
578-
), "Wrong amount of output time steps in converted forecast output"
481+
# Threshold, convert and transform
482+
radar_precip[radar_precip < metadata["threshold"]] = 0.0
483+
nwp_precip[nwp_precip < metadata["threshold"]] = 0.0
484+
converter = pysteps.utils.get_method("mm/h")
485+
radar_precip, _ = converter(radar_precip, metadata)
486+
nwp_precip, metadata = converter(nwp_precip, metadata)
487+
transformer = pysteps.utils.get_method(metadata["transformation"])
488+
radar_precip, _ = transformer(radar_precip, metadata)
489+
nwp_precip, metadata = transformer(nwp_precip, metadata)
490+
radar_precip[~np.isfinite(radar_precip)] = metadata["zerovalue"]
491+
nwp_precip[~np.isfinite(nwp_precip)] = metadata["zerovalue"]
579492

493+
# Decompose NWP
494+
n_cascade_levels = 6
495+
decomp_method, _ = cascade.get_method("fft")
496+
bp_filter = cascade.get_method("gaussian")(radar_precip.shape[1:], n_cascade_levels)
497+
nwp_precip_decomp = nwp_precip.copy()
580498

499+
# Velocity fields
500+
oflow_method = pysteps.motion.get_method("lucaskanade")
501+
radar_velocity = oflow_method(radar_precip)
502+
_V_NWP = [
503+
oflow_method(nwp_precip[0, t - 1 : t + 1, :])
504+
for t in range(1, nwp_precip.shape[1])
505+
]
506+
nwp_velocity = np.stack([np.insert(_V_NWP, 0, _V_NWP[0], axis=0)])
507+
508+
run_and_assert_forecast(
509+
radar_precip,
510+
dict(
511+
precip_models=nwp_precip_decomp,
512+
velocity=radar_velocity,
513+
velocity_models=nwp_velocity,
514+
timesteps=n_timesteps,
515+
timestep=5.0,
516+
issuetime=datetime.datetime.strptime("202112012355", "%Y%m%d%H%M"),
517+
n_ens_members=2,
518+
n_cascade_levels=n_cascade_levels,
519+
blend_nwp_members=False,
520+
precip_thr=metadata["threshold"],
521+
kmperpixel=1.0,
522+
extrap_method="semilagrangian",
523+
decomp_method="fft",
524+
bandpass_filter_method="gaussian",
525+
noise_method="nonparametric",
526+
noise_stddev_adj="auto",
527+
ar_order=ar_order,
528+
vel_pert_method=None,
529+
weights_method="bps",
530+
conditional=False,
531+
probmatching_method=None,
532+
mask_method="incremental",
533+
resample_distribution=False,
534+
smooth_radar_mask_range=0,
535+
callback=None,
536+
return_output=True,
537+
seed=42,
538+
num_workers=1,
539+
fft_method="numpy",
540+
domain="spatial",
541+
outdir_path_skill="./tmp/",
542+
extrap_kwargs=None,
543+
filter_kwargs=None,
544+
noise_kwargs=None,
545+
vel_pert_kwargs=None,
546+
clim_kwargs=None,
547+
mask_kwargs=None,
548+
measure_time=False,
549+
),
550+
expected_n_ens_members=2,
551+
n_timesteps=n_timesteps,
552+
converter=converter,
553+
metadata=metadata,
554+
)

0 commit comments

Comments
 (0)