Skip to content

Commit dbe6efc

Browse files
committed
Implement symmetric CPF
1 parent f7cdc90 commit dbe6efc

File tree

1 file changed

+51
-30
lines changed
  • src/earthkit/meteo/extreme/array

1 file changed

+51
-30
lines changed

src/earthkit/meteo/extreme/array/cpf.py

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,13 @@
1010
import numpy as np
1111

1212

13-
def cpf(clim, ens, sort_clim=True, sort_ens=True, epsilon=None):
14-
"""Compute Crossing Point Forecast (CPF)
15-
16-
WARNING: this code is experimental, use at your own risk!
17-
18-
Parameters
19-
----------
20-
clim: numpy array (nclim, npoints)
21-
Per-point climatology
22-
ens: numpy array (nens, npoints)
23-
Ensemble forecast
24-
sort_clim: bool
25-
If True, sort the climatology first
26-
sort_ens: bool
27-
If True, sort the ensemble first
28-
epsilon: float or None
29-
If set, use this as a threshold for low-signal regions
30-
31-
Returns
32-
-------
33-
numpy array (npoints)
34-
CPF values
35-
"""
13+
def _cpf(clim, ens, epsilon=None):
3614
nclim, npoints = clim.shape
37-
nens, npoints_ens = ens.shape
38-
assert npoints == npoints_ens
15+
nens, _ = ens.shape
3916

4017
cpf = np.ones(npoints, dtype=np.float32)
4118
mask = np.zeros(npoints, dtype=np.bool_)
4219

43-
if sort_clim:
44-
clim = np.sort(clim, axis=0)
45-
if sort_ens:
46-
ens = np.sort(ens, axis=0)
47-
4820
for icl in range(1, nclim - 1):
4921
# quantile level of climatology
5022
tau_c = icl / (nclim - 1.0)
@@ -102,3 +74,52 @@ def cpf(clim, ens, sort_clim=True, sort_ens=True, epsilon=None):
10274
cpf[mask] = 0.0
10375

10476
return cpf
77+
78+
79+
def cpf(clim, ens, sort_clim=True, sort_ens=True, epsilon=None, symmetric=False):
80+
"""Compute Crossing Point Forecast (CPF)
81+
82+
WARNING: this code is experimental, use at your own risk!
83+
84+
Parameters
85+
----------
86+
clim: numpy array (nclim, npoints)
87+
Per-point climatology
88+
ens: numpy array (nens, npoints)
89+
Ensemble forecast
90+
sort_clim: bool
91+
If True, sort the climatology first
92+
sort_ens: bool
93+
If True, sort the ensemble first
94+
epsilon: float or None
95+
If set, use this as a threshold for low-signal regions. Ignored if
96+
`symmetric` is True
97+
symmetric: bool
98+
If True, make CPF values below 0.5 use a symmetric computation (CPF of
99+
opposite values)
100+
101+
Returns
102+
-------
103+
numpy array (npoints)
104+
CPF values
105+
"""
106+
_, npoints = clim.shape
107+
_, npoints_ens = ens.shape
108+
assert npoints == npoints_ens
109+
110+
if sort_clim:
111+
clim = np.sort(clim, axis=0)
112+
if sort_ens:
113+
ens = np.sort(ens, axis=0)
114+
115+
if symmetric:
116+
epsilon = None
117+
118+
cpf_direct = _cpf(clim, ens, epsilon)
119+
120+
if symmetric:
121+
cpf_reverse = _cpf(-clim[::-1, :], -ens[::-1, :])
122+
mask = cpf_direct < 0.5
123+
cpf_direct[mask] = 1 - cpf_reverse[mask]
124+
125+
return cpf_direct

0 commit comments

Comments
 (0)