|
10 | 10 | import numpy as np |
11 | 11 |
|
12 | 12 |
|
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): |
36 | 14 | nclim, npoints = clim.shape |
37 | | - nens, npoints_ens = ens.shape |
38 | | - assert npoints == npoints_ens |
| 15 | + nens, _ = ens.shape |
39 | 16 |
|
40 | 17 | cpf = np.ones(npoints, dtype=np.float32) |
41 | 18 | mask = np.zeros(npoints, dtype=np.bool_) |
42 | 19 |
|
43 | | - if sort_clim: |
44 | | - clim = np.sort(clim, axis=0) |
45 | | - if sort_ens: |
46 | | - ens = np.sort(ens, axis=0) |
47 | | - |
48 | 20 | for icl in range(1, nclim - 1): |
49 | 21 | # quantile level of climatology |
50 | 22 | tau_c = icl / (nclim - 1.0) |
@@ -102,3 +74,52 @@ def cpf(clim, ens, sort_clim=True, sort_ens=True, epsilon=None): |
102 | 74 | cpf[mask] = 0.0 |
103 | 75 |
|
104 | 76 | 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