Skip to content

Commit 4ce2008

Browse files
authored
fix: Advection of domain mask for smooth blending at domain edges in reduced-space EnKF (#533)
feat: add smooth_radar_mask functionality from steps blending to pca_ens_kalman_filter blending --------- Co-authored-by: Ruben Imhoff Co-authored-by: mats-knm
1 parent 997bcd1 commit 4ce2008

File tree

2 files changed

+47
-20
lines changed

2 files changed

+47
-20
lines changed

pysteps/blending/pca_ens_kalman_filter.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ class EnKFCombinationConfig:
176176
:py:mod:`pysteps.blending.ens_kalman_filter_methods`.
177177
measure_time: bool
178178
If set to True, measure, print and return the computation time.
179+
verbose_output: bool
180+
If set to True, return additionally the background ensemble of the EnKF for further statistics.
179181
callback: function, optional
180182
Optional function that is called after computation of each time step of
181183
the nowcast. The function takes one argument: a three-dimensional array
@@ -214,6 +216,7 @@ class EnKFCombinationConfig:
214216
noise_kwargs: dict[str, Any] = field(default_factory=dict)
215217
combination_kwargs: dict[str, Any] = field(default_factory=dict)
216218
measure_time: bool = False
219+
verbose_output: bool = False
217220
callback: Any | None = None
218221
return_output: bool = True
219222
n_noise_fields: int = 30
@@ -609,6 +612,7 @@ def __init__(
609612
self.nwc_prediction_btf = self.nwc_prediction.copy()
610613

611614
self.final_combined_forecast = []
615+
self.background_ensemble = {}
612616

613617
return
614618

@@ -785,7 +789,10 @@ def __advect(self):
785789
displacement_previous=self.__previous_displacement,
786790
**self.__forecast_state.params.extrapolation_kwargs,
787791
)
788-
if self.__forecast_state.config.smooth_radar_mask_range > 0:
792+
if (
793+
self.__forecast_state.config.smooth_radar_mask_range > 0
794+
and self.__ens_member == 0
795+
):
789796
self.__forecast_state.params.domain_mask = (
790797
self.__forecast_state.params.extrapolation_method(
791798
self.__forecast_state.params.domain_mask,
@@ -1082,6 +1089,8 @@ def compute_forecast(self):
10821089
self.__fc_init,
10831090
self.__mainloop_time,
10841091
)
1092+
if self.__config.verbose_output:
1093+
return self.FS.final_combined_forecast, self.FS.background_ensemble
10851094
return self.FS.final_combined_forecast
10861095

10871096
# Else, return None
@@ -1406,7 +1415,7 @@ def __integrated_nowcast_main_loop(self):
14061415
print(f"Full NWP weight is reached for lead time + {fc_leadtime} min")
14071416
if is_correction_timestep:
14081417
t_corr = np.where(
1409-
self.__correction_leadtimes == self.__forecast_leadtimes[t - 1]
1418+
self.__correction_leadtimes == self.__forecast_leadtimes[t]
14101419
)[0][0]
14111420
self.FS.nwc_prediction = self.__nwp_precip[:, t_corr]
14121421

@@ -1476,6 +1485,11 @@ def __forecast_loop(self, t, is_correction_timestep, is_nowcasting_timestep):
14761485
self.__correction_leadtimes == self.__forecast_leadtimes[t - 1]
14771486
)[0][0]
14781487

1488+
if self.__config.verbose_output:
1489+
self.FS.background_ensemble[self.__correction_leadtimes[t_corr]] = (
1490+
self.FS.nwc_prediction.copy()
1491+
)
1492+
14791493
self.FS.nwc_prediction, self.FS.fc_resampled = (
14801494
self.KalmanFilterModel.correct_step(
14811495
self.FS.nwc_prediction,
@@ -1571,6 +1585,7 @@ def forecast(
15711585
noise_kwargs=None,
15721586
combination_kwargs=None,
15731587
measure_time=False,
1588+
verbose_output=False,
15741589
):
15751590
"""
15761591
Generate a combined nowcast ensemble by using the reduced-space ensemble Kalman
@@ -1700,7 +1715,9 @@ def forecast(
17001715
ensemble Kalman filter method. See the documentation of
17011716
:py:mod:`pysteps.blending.ens_kalman_filter_methods`.
17021717
measure_time: bool
1703-
If set to True, measure, print and return the computation time.
1718+
If set to True, measure, print and return the computation time.
1719+
verbose_output: bool
1720+
If set to True, return additionally the background ensemble of the EnKF for further statistics.
17041721
17051722
Returns
17061723
-------
@@ -1751,6 +1768,7 @@ def forecast(
17511768
noise_kwargs=noise_kwargs,
17521769
combination_kwargs=combination_kwargs,
17531770
measure_time=measure_time,
1771+
verbose_output=verbose_output,
17541772
callback=callback,
17551773
return_output=return_output,
17561774
n_noise_fields=30,

pysteps/tests/test_blending_pca_ens_kalman_filter.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,41 @@
1010
# fmt: off
1111
pca_enkf_arg_values = [
1212
# Standard setting
13-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
13+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
1414
# Smooth radar mask
15-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,20),
15+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,20,False),
1616
# Coarser NWP temporal resolution
17-
(20,30,0,-60,False,False,5,15,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
17+
(20,30,0,-60,False,False,5,15,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
1818
# Coarser Obs temporal resolution
19-
(20,30,0,-60,False,False,10,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
19+
(20,30,0,-60,False,False,10,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
2020
# Larger shift of the NWP init
21-
(20,30,0,-30,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
21+
(20,30,0,-30,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
2222
# Zero rain case in observation
23-
(20,30,0,-60,True,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
23+
(20,30,0,-60,True,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
2424
# Zero rain case in NWP
25-
(20,30,0,-60,False,True,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
25+
(20,30,0,-60,False,True,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
2626
# Zero rain in both
27-
(20,30,0,-60,True,True,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
27+
(20,30,0,-60,True,True,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
2828
# Accumulated sampling probability
29-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",True,False,0),
29+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",True,False,0,False),
3030
# Use full NWP weight
31-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,True,0),
31+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,True,0,False),
3232
# Both
33-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",True,True,0),
33+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",True,True,0,False),
3434
# Explained variance as sampling probability source
35-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"explained_var",False,False,0),
35+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"explained_var",False,False,0,False),
3636
# No combination
37-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",False,None,1.0,"ensemble",False,False,0),
37+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",False,None,1.0,"ensemble",False,False,0,False),
3838
# Standard deviation adjustment
39-
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,"auto",1.0,"ensemble",False,False,0),
39+
(20,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,"auto",1.0,"ensemble",False,False,0,False),
4040
# Other number of ensemble members
41-
(10,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
41+
(10,30,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
4242
# Other forecast length
43-
(20,35,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0),
43+
(20,35,0,-60,False,False,5,5,0.05,0.01,"ssft","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
4444
# Other noise method
45-
(20,30,0,-60,False,False,5,5,0.05,0.01,"nonparametric","masked_enkf",True,None,1.0,"ensemble",False,False,0),]
45+
(20,30,0,-60,False,False,5,5,0.05,0.01,"nonparametric","masked_enkf",True,None,1.0,"ensemble",False,False,0,False),
46+
# Verbose output
47+
(20,30,0,-60,False,False,5,5,0.05,0.01,"nonparametric","masked_enkf",True,None,1.0,"ensemble",False,False,0,True),]
4648
# fmt: on
4749

4850
pca_enkf_arg_names = (
@@ -65,6 +67,7 @@
6567
"use_accum_sampling_prob",
6668
"ensure_full_nwp_weight",
6769
"smooth_radar_mask_range",
70+
"verbose_output",
6871
)
6972

7073

@@ -89,6 +92,7 @@ def test_pca_enkf_combination(
8992
use_accum_sampling_prob,
9093
ensure_full_nwp_weight,
9194
smooth_radar_mask_range,
95+
verbose_output,
9296
):
9397
pytest.importorskip("sklearn")
9498

@@ -248,8 +252,13 @@ def test_pca_enkf_combination(
248252
noise_kwargs=None,
249253
combination_kwargs=combination_kwargs,
250254
measure_time=False,
255+
verbose_output=verbose_output,
251256
)
252257

258+
if verbose_output:
259+
assert len(combined_forecast) == 2, "Wrong amount of output data"
260+
combined_forecast = combined_forecast[0]
261+
253262
assert combined_forecast.ndim == 4, "Wrong amount of dimensions in forecast output"
254263
assert (
255264
combined_forecast.shape[0] == n_ens_members

0 commit comments

Comments
 (0)