Skip to content

Commit f9a560a

Browse files
jjokellas7yoewerclaude
authored
Refactor adaptive forgetting factor in PDAF_set_forget / PDAF_set_forget_local (#78)
Replaces the previous adaptive forgetting factor formula (`var_ens / (var_resid - var_obs)`) with a target-variance-based approach. The new algorithm inflates the ensemble only when its spread in observation space is below the observation error standard deviation, and caps the result at 1. The `forget` input parameter now acts as a **lower bound** (maximum inflation) in adaptive mode (`type_forget = 1`/`2`) rather than being unused. - `docs/users_guide/running_tsmp_pdaf/input_cmd.md`: added `type_forget` section; updated `forget` section to document fixed vs. adaptive mode. --------- Co-authored-by: Yorck Ewerdwalbesloh <s7yoewer@uni-bonn.de> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e4aa336 commit f9a560a

3 files changed

Lines changed: 104 additions & 17 deletions

File tree

docs/users_guide/running_tsmp_pdaf/input_cmd.md

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,30 +102,71 @@ For flexible time stepping, `delt_obs` must be one.
102102
- 2: 1 plus timing output
103103
- 3: 2 plus debug output
104104

105+
## type_forget ##
106+
107+
`type_forget` (integer) Type of forgetting factor. Default: `0`.
108+
109+
For SEIK / LSEIK / ETKF / LETKF / ESTKF / LESTKF:
110+
- `0`: fixed forgetting factor (value given by `forget`)
111+
- `1`: global adaptive forgetting factor (computed from ensemble
112+
spread and observation error variance; `forget` acts as lower bound)
113+
- `2`: local adaptive forgetting factor, for LSEIK / LETKF / LESTKF
114+
only (same adaptive logic applied per local analysis domain; `forget`
115+
acts as lower bound)
116+
117+
For NETF / LNETF / PF:
118+
- `0`: apply inflation on forecast ensemble
119+
- `2`: apply inflation on analysis ensemble
120+
105121
## forget ##
106122

107-
`forget` (real) forgetting factor for filter analysis
123+
`forget` (real) forgetting factor for filter analysis. Default: `1.0`.
108124

109-
Example: `-forget 0.98`.
125+
**Fixed mode (`type_forget = 0`):** `forget` is applied directly as the
126+
forgetting factor. Example: `-forget 0.98`.
110127

111-
General advise: Choose forgetting factor close to one. For values
112-
smaller than 0.95, effects like a splitting of the ensemble have been
113-
observed (compare Amezcua et al., Tellus A 2012, 64, 18039,
128+
General advice: choose `forget` close to one. For values smaller than
129+
0.95, effects like a splitting of the ensemble have been observed
130+
(compare Amezcua et al., Tellus A 2012, 64, 18039,
114131
<http://dx.doi.org/10.3402/tellusa.v64i0.18039>)
115132

116133
For EnKF / LEnKF, the forgetting factor leads to a spreading of the
117-
ensemble through manipulating ensemble member by
134+
ensemble through manipulating each ensemble member by
118135

119-
\begin{align*}
120-
x^{f}_{i} &= \bar{x} + (x_{i}-\bar{x}) \cdot \frac{1}{\mathtt{forget}^2},
121-
\end{align*}
136+
$$
137+
x^{f}_{i} = \bar{x} + (x_{i}-\bar{x}) \cdot \frac{1}{\mathtt{forget}^2},
138+
$$
122139

123140
where $x_{i}$ is the state vector ensemble member $i$ and $\bar{x}$ is
124141
the ensemble mean of the state vector.
125142

126143
For ETKF, see
127144
e.g. <https://github.com/PDAF/PDAF/blob/ae9545227bd4804469dff389a9baadcc9e31906e/src/PDAF_etkf_analysis.F90#L441-L444>
128145

146+
**Adaptive mode (`type_forget = 1` or `2`):** `forget` is used as a
147+
lower bound on the computed forgetting factor. The adaptive algorithm
148+
inflates the ensemble only when its spread in observation space falls
149+
below the observation error standard deviation.
150+
151+
Concretely, the forgetting factor is computed as
152+
153+
$$
154+
\mathtt{forget_adaptive} = \begin{cases}
155+
\mathtt{forget} & \text{if } \sigma^2_{\mathrm{ens}} < \mathtt{forget} \cdot \sigma^2_{\mathrm{obs}}, \\[5pt]
156+
\sigma^2_{\mathrm{ens}} / \sigma^2_{\mathrm{obs}} & \text{if } \mathtt{forget} \cdot \sigma^2_{\mathrm{obs}} \leq \sigma^2_{\mathrm{ens}} \leq \sigma^2_{\mathrm{obs}}, \\[5pt]
157+
1 & \text{otherwise.}
158+
\end{cases}
159+
$$
160+
161+
Note that the result is clipped to $[{\mathtt{forget}},\, 1]$, so
162+
`forget` provides a lower bound (maximum inflation).
163+
164+
The resulting `forget` factor is then used in the same way as the
165+
global one.
166+
167+
A typical choice is `-forget 0.95` to allow moderate inflation while
168+
preventing excessive spread.
169+
129170
## locweight ##
130171

131172
Only for localization.

src/PDAF_set_forget.F90

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,24 @@ SUBROUTINE PDAF_set_forget(step, filterstr, dim_obs_p, dim_ens, mens_p, &
8585
REAL :: var_resid_p, var_resid ! Variance of residual
8686
REAL :: var_obs ! Variance of observation errors
8787
REAL :: forget_neg, forget_max, forget_min ! Limiting values of forgetting factor
88+
REAL :: var_target
89+
REAL :: spread_fac
8890

8991

9092
! **********************
9193
! *** INITIALIZATION ***
9294
! **********************
9395

96+
! spread_fac defines the minimum ensemble std relative to obs std:
97+
! sqrt(var_ens_target) = spread_fac * sqrt(var_obs)
98+
! hence: var_target = spread_fac^2 * var_obs
99+
spread_fac = 1.0
100+
94101
! Define limiting values of forgetting factor
95-
! These are set very arbitrarily for now
102+
! The input 'forget' is used as obs-type dependent lower bound
96103
forget_neg = forget_in
97-
forget_max = 100.0
98-
forget_min = 0.01
104+
forget_max = 1.0
105+
forget_min = forget_in
99106

100107
IF (mype == 0) THEN
101108
WRITE (*, '(a, 5x, a)') &
@@ -180,8 +187,19 @@ SUBROUTINE PDAF_set_forget(step, filterstr, dim_obs_p, dim_ens, mens_p, &
180187

181188
CALL PDAF_timeit(51, 'new')
182189

190+
! Define target ensemble variance in observation space
191+
var_target = spread_fac * spread_fac * var_obs
192+
183193
! *** Compute optimal forgetting factor ***
184-
forget_out = var_ens / (var_resid - var_obs)
194+
IF (var_target > 0.0) THEN
195+
IF (var_ens < var_target) THEN
196+
forget_out = var_ens / var_target
197+
ELSE
198+
forget_out = 1.0
199+
ENDIF
200+
ELSE
201+
forget_out = 1.0
202+
ENDIF
185203

186204
! Apply special condition if observation variance is larger than residual variance
187205
IF (forget_out < 0.0) forget_out = forget_neg
@@ -198,6 +216,14 @@ SUBROUTINE PDAF_set_forget(step, filterstr, dim_obs_p, dim_ens, mens_p, &
198216
! ********************
199217

200218
IF (mype == 0) THEN
219+
#ifdef PDAF_DEBUG
220+
WRITE (*, '(a, 9x, a, es10.2)') &
221+
'PDAF', 'Variance of ensemble', var_ens
222+
WRITE (*, '(a, 9x, a, es10.2)') &
223+
'PDAF', 'Variance of observation errors', var_obs
224+
WRITE (*, '(a, 9x, a, es10.2)') &
225+
'PDAF', 'Target variance of ensemble', var_target
226+
#endif
201227
WRITE (*, '(a, 9x, a, es10.2)') &
202228
'PDAF', '--> Computed forgetting factor', forget_out
203229
ENDIF

src/PDAF_set_forget_local.F90

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,26 @@ SUBROUTINE PDAF_set_forget_local(domain, step, dim_obs_l, dim_ens, mens_l, &
7777
INTEGER, SAVE :: domain_save = 1 ! Index of domain from last call to routine
7878
INTEGER :: n_domains ! Number of local analysis domains
7979
REAL :: forget_neg, forget_max, forget_min ! limiting values of forgetting factor
80+
REAL :: var_target
81+
REAL :: spread_fac
8082

8183

8284
! **********************
8385
! *** INITIALIZATION ***
8486
! **********************
8587

88+
! spread_fac defines the minimum ensemble std relative to obs std:
89+
! sqrt(var_ens_target) = spread_fac * sqrt(var_obs)
90+
! hence: var_target = spread_fac^2 * var_obs
91+
spread_fac = 1.0
92+
8693
! Define limiting values of forgetting factor
87-
! These are set very arbitrarily for now
94+
! The input 'forget' is used as obs-type dependent lower bound
8895
forget_neg = forget
89-
forget_max = 100.0
90-
forget_min = 0.01
96+
forget_max = 1.0
97+
forget_min = forget
98+
99+
91100

92101

93102
! ****************************************************
@@ -150,8 +159,19 @@ SUBROUTINE PDAF_set_forget_local(domain, step, dim_obs_l, dim_ens, mens_l, &
150159
CALL U_init_obsvar_l(domain, step, dim_obs_l, obs_l, var_obs)
151160
CALL PDAF_timeit(52, 'old')
152161

162+
! Define target ensemble variance in local observation space
163+
var_target = spread_fac * spread_fac * var_obs
164+
153165
! *** Compute optimal forgetting factor ***
154-
forget = var_ens / (var_resid - var_obs)
166+
IF (var_target > 0.0) THEN
167+
IF (var_ens < var_target .and. dim_obs_l>3) THEN
168+
forget = var_ens / var_target
169+
ELSE
170+
forget = 1.0
171+
ENDIF
172+
ELSE
173+
forget = 1.0
174+
ENDIF
155175

156176
! Apply special condition if observation variance is larger than residual variance
157177
IF (forget < 0.0) forget = forget_neg

0 commit comments

Comments
 (0)