1010from earthkit .utils .array import array_namespace
1111
1212
13- def _cpf (clim , ens , epsilon = None ):
13+ def _cpf (clim , ens , epsilon = None , from_zero = False ):
1414 xp = array_namespace (clim , ens )
1515 clim = xp .asarray (clim )
1616 ens = xp .asarray (ens )
1717
1818 nclim , npoints = clim .shape
1919 nens , _ = ens .shape
2020
21- cpf = xp .ones (npoints , dtype = xp .float32 )
21+ cpf = xp .zeros (npoints , dtype = xp .float32 )
2222 mask = xp .zeros (npoints , dtype = xp .bool )
23+ prim = xp .zeros (npoints , dtype = xp .bool )
24+
25+ # start scanning ensemble from iq_start
26+ iq_start = 0 if from_zero else nens // 2
2327
2428 for icl in range (1 , nclim - 1 ):
2529 # quantile level of climatology
2630 tau_c = icl / (nclim - 1.0 )
27- for iq in range (nens ):
31+ for iq in range (iq_start , nens ):
2832 # quantile level of forecast
2933 tau_f = (iq + 1.0 ) / (nens + 1.0 )
30- if tau_f >= tau_c :
31- # quantile values of forecast and climatology
32- qv_f = ens [iq , :]
33- qv_c = clim [icl , :]
3434
35+ # quantile values of forecast and climatology
36+ qv_f = ens [iq , :]
37+ qv_c = clim [icl , :]
38+
39+ # primary condition (to ensure crossing and not reverse-crossing)
40+ if tau_f < tau_c :
41+ idx = (qv_f >= qv_c ) & (~ mask )
42+ prim [idx ] = True
43+
44+ if tau_f >= tau_c :
3545 # lowest climate quantile: interpolate between 2 consecutive quantiles
3646 if iq < 2 :
3747 # quantile value and quantile level of climatology at previous
3848 qv_c_2 = clim [icl - 1 , :]
3949 tau_c_2 = (icl - 1 ) / (nclim - 1 )
4050
4151 # condition of crossing situtaion:
42- idx = (qv_f < qv_c ) & (qv_c_2 < qv_c )
52+ idx = (qv_f < qv_c ) & (qv_c_2 < qv_c ) & prim
4353
4454 # intersection between two lines
4555 tau_i = (tau_c * (qv_c_2 [idx ] - qv_f [idx ]) + tau_c_2 * (qv_f [idx ] - qv_c [idx ])) / (
@@ -51,7 +61,7 @@ def _cpf(clim, ens, epsilon=None):
5161 mask [idx ] = True
5262
5363 # check crossing cases
54- idx = (qv_f < qv_c ) & (~ mask )
64+ idx = (qv_f < qv_c ) & (~ mask ) & prim
5565 cpf [idx ] = tau_f
5666 mask [idx ] = True
5767
@@ -60,7 +70,7 @@ def _cpf(clim, ens, epsilon=None):
6070 qv_c_2 = clim [nclim - 1 , :]
6171 tau_c_2 = 1.0
6272
63- idx = (qv_f > qv_c ) & (qv_c_2 > qv_c ) & (~ mask )
73+ idx = (qv_f > qv_c ) & (qv_c_2 > qv_c ) & (~ mask ) & prim
6474
6575 tau_i = (tau_c * (qv_c_2 [idx ] - qv_f [idx ]) + tau_c_2 * (qv_f [idx ] - qv_c [idx ])) / (
6676 qv_c_2 [idx ] - qv_c [idx ]
@@ -80,7 +90,15 @@ def _cpf(clim, ens, epsilon=None):
8090 return cpf
8191
8292
83- def cpf (clim , ens , sort_clim = True , sort_ens = True , epsilon = None , symmetric = False ):
93+ def cpf (
94+ clim ,
95+ ens ,
96+ sort_clim = True ,
97+ sort_ens = True ,
98+ epsilon = None ,
99+ symmetric = False ,
100+ from_zero = False ,
101+ ):
84102 """Compute Crossing Point Forecast (CPF)
85103
86104 WARNING: this code is experimental, use at your own risk!
@@ -101,6 +119,9 @@ def cpf(clim, ens, sort_clim=True, sort_ens=True, epsilon=None, symmetric=False)
101119 symmetric: bool
102120 If True, make CPF values below 0.5 use a symmetric computation (CPF of
103121 opposite values)
122+ from_zero: bool
123+ If True, start looking for a crossing from the minimum, rather than the
124+ median
104125
105126 Returns
106127 -------
@@ -123,10 +144,10 @@ def cpf(clim, ens, sort_clim=True, sort_ens=True, epsilon=None, symmetric=False)
123144 if symmetric :
124145 epsilon = None
125146
126- cpf_direct = _cpf (clim , ens , epsilon )
147+ cpf_direct = _cpf (clim , ens , epsilon , from_zero )
127148
128149 if symmetric :
129- cpf_reverse = _cpf (- clim [::- 1 , :], - ens [::- 1 , :])
150+ cpf_reverse = _cpf (- clim [::- 1 , :], - ens [::- 1 , :], from_zero = from_zero )
130151 mask = cpf_direct < 0.5
131152 cpf_direct [mask ] = 1 - cpf_reverse [mask ]
132153
0 commit comments