disdrodb 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- disdrodb/__init__.py +64 -34
- disdrodb/_config.py +5 -4
- disdrodb/_version.py +16 -3
- disdrodb/accessor/__init__.py +20 -0
- disdrodb/accessor/methods.py +125 -0
- disdrodb/api/checks.py +139 -9
- disdrodb/api/configs.py +4 -2
- disdrodb/api/info.py +10 -10
- disdrodb/api/io.py +237 -18
- disdrodb/api/path.py +81 -75
- disdrodb/api/search.py +6 -6
- disdrodb/cli/disdrodb_create_summary_station.py +91 -0
- disdrodb/cli/disdrodb_run_l0.py +1 -1
- disdrodb/cli/disdrodb_run_l0_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0b.py +1 -1
- disdrodb/cli/disdrodb_run_l0b_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0c.py +1 -1
- disdrodb/cli/disdrodb_run_l0c_station.py +1 -1
- disdrodb/cli/disdrodb_run_l2e_station.py +1 -1
- disdrodb/configs.py +149 -4
- disdrodb/constants.py +61 -0
- disdrodb/data_transfer/download_data.py +145 -14
- disdrodb/etc/configs/attributes.yaml +339 -0
- disdrodb/etc/configs/encodings.yaml +473 -0
- disdrodb/etc/products/L1/global.yaml +13 -0
- disdrodb/etc/products/L2E/10MIN.yaml +12 -0
- disdrodb/etc/products/L2E/1MIN.yaml +1 -0
- disdrodb/etc/products/L2E/global.yaml +22 -0
- disdrodb/etc/products/L2M/10MIN.yaml +12 -0
- disdrodb/etc/products/L2M/GAMMA_ML.yaml +8 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_LOG_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_Z_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/global.yaml +26 -0
- disdrodb/l0/__init__.py +13 -0
- disdrodb/l0/configs/LPM/bins_diameter.yml +3 -3
- disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
- disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +3 -3
- disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PARSIVEL2/l0a_encodings.yml +4 -0
- disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +20 -4
- disdrodb/l0/configs/PARSIVEL2/l0b_encodings.yml +44 -3
- disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +41 -1
- disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PWS100/raw_data_format.yml +1 -1
- disdrodb/l0/l0a_processing.py +30 -30
- disdrodb/l0/l0b_nc_processing.py +108 -2
- disdrodb/l0/l0b_processing.py +4 -4
- disdrodb/l0/l0c_processing.py +5 -13
- disdrodb/l0/manuals/SWS250.pdf +0 -0
- disdrodb/l0/manuals/VPF730.pdf +0 -0
- disdrodb/l0/manuals/VPF750.pdf +0 -0
- disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +66 -0
- disdrodb/l0/readers/LPM/SLOVENIA/{CRNI_VRH.py → UL.py} +3 -0
- disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +195 -0
- disdrodb/l0/readers/PARSIVEL/GPM/PIERS.py +105 -0
- disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +128 -0
- disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +168 -0
- disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +69 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +255 -134
- disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +525 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +9 -7
- disdrodb/l0/readers/{PARSIVEL → PARSIVEL2}/KIT/BURKINA_FASO.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +123 -0
- disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +120 -0
- disdrodb/l0/readers/PARSIVEL2/{NETHERLANDS/DELFT.py → NCAR/FARM_PARSIVEL2.py} +43 -70
- disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +126 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +29 -12
- disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +69 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +144 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +201 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +137 -0
- disdrodb/l0/readers/PARSIVEL2/USA/C3WE.py +146 -0
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +105 -99
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +151 -0
- disdrodb/l0/readers/RD80/NOAA/PSL_RD80.py +31 -14
- disdrodb/l0/routines.py +105 -14
- disdrodb/l1/__init__.py +5 -0
- disdrodb/l1/filters.py +34 -20
- disdrodb/l1/processing.py +45 -44
- disdrodb/l1/resampling.py +77 -66
- disdrodb/l1/routines.py +35 -42
- disdrodb/l1_env/routines.py +18 -3
- disdrodb/l2/__init__.py +7 -0
- disdrodb/l2/empirical_dsd.py +58 -10
- disdrodb/l2/event.py +27 -120
- disdrodb/l2/processing.py +267 -116
- disdrodb/l2/routines.py +618 -254
- disdrodb/metadata/standards.py +3 -1
- disdrodb/psd/fitting.py +463 -144
- disdrodb/psd/models.py +8 -5
- disdrodb/routines.py +3 -3
- disdrodb/scattering/__init__.py +16 -4
- disdrodb/scattering/axis_ratio.py +56 -36
- disdrodb/scattering/permittivity.py +486 -0
- disdrodb/scattering/routines.py +701 -159
- disdrodb/summary/__init__.py +17 -0
- disdrodb/summary/routines.py +4120 -0
- disdrodb/utils/attrs.py +68 -125
- disdrodb/utils/compression.py +30 -1
- disdrodb/utils/dask.py +59 -8
- disdrodb/utils/dataframe.py +63 -9
- disdrodb/utils/directories.py +49 -17
- disdrodb/utils/encoding.py +33 -19
- disdrodb/utils/logger.py +13 -6
- disdrodb/utils/manipulations.py +71 -0
- disdrodb/utils/subsetting.py +214 -0
- disdrodb/utils/time.py +165 -19
- disdrodb/utils/writer.py +20 -7
- disdrodb/utils/xarray.py +85 -4
- disdrodb/viz/__init__.py +13 -0
- disdrodb/viz/plots.py +327 -0
- {disdrodb-0.1.1.dist-info → disdrodb-0.1.3.dist-info}/METADATA +3 -2
- {disdrodb-0.1.1.dist-info → disdrodb-0.1.3.dist-info}/RECORD +127 -87
- {disdrodb-0.1.1.dist-info → disdrodb-0.1.3.dist-info}/entry_points.txt +1 -0
- disdrodb/l1/encoding_attrs.py +0 -635
- disdrodb/l2/processing_options.py +0 -213
- /disdrodb/l0/readers/PARSIVEL/SLOVENIA/{UL_FGG.py → UL.py} +0 -0
- {disdrodb-0.1.1.dist-info → disdrodb-0.1.3.dist-info}/WHEEL +0 -0
- {disdrodb-0.1.1.dist-info → disdrodb-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {disdrodb-0.1.1.dist-info → disdrodb-0.1.3.dist-info}/top_level.txt +0 -0
disdrodb/psd/fitting.py
CHANGED
|
@@ -22,96 +22,147 @@ from scipy.integrate import quad
|
|
|
22
22
|
from scipy.optimize import minimize
|
|
23
23
|
from scipy.special import gamma, gammainc, gammaln # Regularized lower incomplete gamma function
|
|
24
24
|
|
|
25
|
+
from disdrodb.constants import DIAMETER_DIMENSION
|
|
26
|
+
from disdrodb.l2.empirical_dsd import (
|
|
27
|
+
get_median_volume_drop_diameter,
|
|
28
|
+
get_moment,
|
|
29
|
+
get_normalized_intercept_parameter_from_moments,
|
|
30
|
+
get_total_number_concentration,
|
|
31
|
+
)
|
|
25
32
|
from disdrodb.psd.models import ExponentialPSD, GammaPSD, LognormalPSD, NormalizedGammaPSD
|
|
33
|
+
from disdrodb.utils.manipulations import get_diameter_bin_edges
|
|
26
34
|
from disdrodb.utils.warnings import suppress_warnings
|
|
27
35
|
|
|
28
36
|
# gamma(>171) return inf !
|
|
29
37
|
|
|
38
|
+
####--------------------------------------------------------------------------------------.
|
|
39
|
+
#### Notes
|
|
40
|
+
## Variable requirements for fitting PSD Models
|
|
41
|
+
# - drop_number_concentration and diameter coordinates
|
|
42
|
+
# - Always recompute other parameters to ensure not use model parameters of L2M
|
|
43
|
+
|
|
44
|
+
# ML: None
|
|
45
|
+
|
|
46
|
+
# MOM: moments
|
|
47
|
+
# --> get_moment(drop_number_concentration, diameter, diameter_bin_width, moment)
|
|
48
|
+
|
|
49
|
+
# GS: fall_velocity if target optimization is R (rain)
|
|
50
|
+
# - NormalizedGamma: "Nw", "D50"
|
|
51
|
+
# --> get_normalized_intercept_parameter_from_moments(moment_3, moment_4)
|
|
52
|
+
# --> get_median_volume_drop_diameter(drop_number_concentration, diameter, diameter_bin_width):
|
|
53
|
+
# --> get_mean_volume_drop_diameter(moment_3, moment_4) (Dm)
|
|
54
|
+
|
|
55
|
+
# - LogNormal,Exponential, Gamma: Nt
|
|
56
|
+
# --> get_total_number_concentration(drop_number_concentration, diameter_bin_width)
|
|
57
|
+
|
|
30
58
|
|
|
31
59
|
####--------------------------------------------------------------------------------------.
|
|
32
60
|
#### Goodness of fit (GOF)
|
|
33
|
-
def compute_gof_stats(
|
|
61
|
+
def compute_gof_stats(obs, pred, dim=DIAMETER_DIMENSION):
|
|
34
62
|
"""
|
|
35
|
-
Compute various goodness-of-fit (GoF) statistics between
|
|
63
|
+
Compute various goodness-of-fit (GoF) statistics between obs and predicted values.
|
|
36
64
|
|
|
37
65
|
Parameters
|
|
38
66
|
----------
|
|
39
|
-
|
|
40
|
-
|
|
67
|
+
obs: xarray.DataArray
|
|
68
|
+
Observations DataArray with at least dimension ``dim``.
|
|
69
|
+
pred: xarray.DataArray
|
|
70
|
+
Predictions DataArray with at least dimension ``dim``.
|
|
71
|
+
dim: str
|
|
72
|
+
DataArray dimension over which to compute GOF statistics.
|
|
73
|
+
The default is DIAMETER_DIMENSION.
|
|
41
74
|
|
|
42
75
|
Returns
|
|
43
76
|
-------
|
|
44
|
-
|
|
77
|
+
ds: xarray.Dataset
|
|
78
|
+
Dataset containing the computed GoF statistics.
|
|
45
79
|
"""
|
|
46
80
|
from disdrodb.l2.empirical_dsd import get_mode_diameter
|
|
47
81
|
|
|
48
|
-
# Retrieve diameter bin width
|
|
49
|
-
diameter =
|
|
50
|
-
diameter_bin_width =
|
|
82
|
+
# Retrieve diameter and diameter bin width
|
|
83
|
+
diameter = obs["diameter_bin_center"]
|
|
84
|
+
diameter_bin_width = obs["diameter_bin_width"]
|
|
85
|
+
|
|
86
|
+
# Compute errors
|
|
87
|
+
error = obs - pred
|
|
88
|
+
|
|
89
|
+
# Compute max obs and pred
|
|
90
|
+
obs_max = obs.max(dim=dim, skipna=False)
|
|
91
|
+
pred_max = pred.max(dim=dim, skipna=False)
|
|
51
92
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
fitted_values = psd(diameter) # .transpose(*observed_values.dims)
|
|
55
|
-
error = observed_values - fitted_values
|
|
93
|
+
# Compute NaN mask
|
|
94
|
+
mask_nan = np.logical_or(np.isnan(obs_max), np.isnan(pred_max))
|
|
56
95
|
|
|
57
96
|
# Compute GOF statistics
|
|
58
97
|
with suppress_warnings():
|
|
59
|
-
# Compute Pearson
|
|
60
|
-
pearson_r = xr.corr(
|
|
61
|
-
|
|
62
|
-
# Compute
|
|
63
|
-
|
|
98
|
+
# Compute Pearson Correlation
|
|
99
|
+
pearson_r = xr.corr(obs, pred, dim=dim)
|
|
100
|
+
|
|
101
|
+
# Compute Mean Absolute Error (MAE)
|
|
102
|
+
mae = np.abs(error).mean(dim=dim, skipna=False)
|
|
103
|
+
|
|
104
|
+
# Compute maximum absolute error
|
|
105
|
+
max_error = np.abs(error).max(dim=dim, skipna=False)
|
|
106
|
+
relative_max_error = xr.where(max_error == 0, 0, xr.where(obs_max == 0, np.nan, max_error / obs_max))
|
|
107
|
+
|
|
108
|
+
# Compute deviation of N(D) at distribution mode
|
|
109
|
+
mode_deviation = obs_max - pred_max
|
|
110
|
+
mode_relative_deviation = xr.where(
|
|
111
|
+
mode_deviation == 0,
|
|
112
|
+
0,
|
|
113
|
+
xr.where(obs_max == 0, np.nan, mode_deviation / obs_max),
|
|
114
|
+
)
|
|
64
115
|
|
|
65
|
-
# Compute
|
|
66
|
-
|
|
67
|
-
|
|
116
|
+
# Compute diameter difference of the distribution mode
|
|
117
|
+
diameter_mode_pred = get_mode_diameter(pred, diameter)
|
|
118
|
+
diameter_mode_obs = get_mode_diameter(obs, diameter)
|
|
119
|
+
diameter_mode_deviation = diameter_mode_obs - diameter_mode_pred
|
|
68
120
|
|
|
69
121
|
# Compute difference in total number concentration
|
|
70
|
-
total_number_concentration_obs = (
|
|
71
|
-
total_number_concentration_pred = (
|
|
122
|
+
total_number_concentration_obs = (obs * diameter_bin_width).sum(dim=dim, skipna=False)
|
|
123
|
+
total_number_concentration_pred = (pred * diameter_bin_width).sum(dim=dim, skipna=False)
|
|
72
124
|
total_number_concentration_difference = total_number_concentration_pred - total_number_concentration_obs
|
|
73
125
|
|
|
74
126
|
# Compute Kullback-Leibler divergence
|
|
75
127
|
# - Compute pdf per bin
|
|
76
|
-
pk_pdf =
|
|
77
|
-
qk_pdf =
|
|
128
|
+
pk_pdf = obs / total_number_concentration_obs
|
|
129
|
+
qk_pdf = pred / total_number_concentration_pred
|
|
78
130
|
|
|
79
131
|
# - Compute probabilities per bin
|
|
80
132
|
pk = pk_pdf * diameter_bin_width
|
|
81
|
-
pk = pk / pk.sum(dim=
|
|
133
|
+
pk = pk / pk.sum(dim=dim, skipna=False) # this might not be necessary
|
|
82
134
|
qk = qk_pdf * diameter_bin_width
|
|
83
|
-
qk = qk / qk.sum(dim=
|
|
135
|
+
qk = qk / qk.sum(dim=dim, skipna=False) # this might not be necessary
|
|
84
136
|
|
|
85
|
-
# - Compute
|
|
137
|
+
# - Compute log probability ratio
|
|
138
|
+
epsilon = 1e-10
|
|
139
|
+
pk = xr.where(pk == 0, epsilon, pk)
|
|
140
|
+
qk = xr.where(qk == 0, epsilon, qk)
|
|
86
141
|
log_prob_ratio = np.log(pk / qk)
|
|
87
142
|
log_prob_ratio = log_prob_ratio.where(np.isfinite(log_prob_ratio))
|
|
88
|
-
kl_divergence = (pk * log_prob_ratio).sum(dim="diameter_bin_center")
|
|
89
|
-
|
|
90
|
-
# Other statistics that can be computed also from different diameter discretization
|
|
91
|
-
# - Compute max deviation at distribution mode
|
|
92
|
-
max_deviation = observed_values.max(dim="diameter_bin_center") - fitted_values.max(dim="diameter_bin_center")
|
|
93
|
-
max_relative_deviation = max_deviation / fitted_values.max(dim="diameter_bin_center")
|
|
94
143
|
|
|
95
|
-
# - Compute
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
diameter,
|
|
99
|
-
)
|
|
144
|
+
# - Compute divergence
|
|
145
|
+
kl_divergence = (pk * log_prob_ratio).sum(dim=dim, skipna=False)
|
|
146
|
+
kl_divergence = xr.where((error == 0).all(dim=dim), 0, kl_divergence)
|
|
100
147
|
|
|
101
148
|
# Create an xarray.Dataset to hold the computed statistics
|
|
102
149
|
ds = xr.Dataset(
|
|
103
150
|
{
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
151
|
+
"R2": pearson_r**2, # Squared Pearson correlation coefficient
|
|
152
|
+
"MAE": mae, # Mean Absolute Error
|
|
153
|
+
"MaxAE": max_error, # Maximum Absolute Error
|
|
154
|
+
"RelMaxAE": relative_max_error, # Relative Maximum Absolute Error
|
|
155
|
+
"PeakDiff": mode_deviation, # Difference at distribution peak
|
|
156
|
+
"RelPeakDiff": mode_relative_deviation, # Relative difference at peak
|
|
157
|
+
"DmodeDiff": diameter_mode_deviation, # Difference in mode diameters
|
|
158
|
+
"NtDiff": total_number_concentration_difference,
|
|
159
|
+
"KLDiv": kl_divergence, # Kullback-Leibler divergence
|
|
113
160
|
},
|
|
114
161
|
)
|
|
162
|
+
# Round
|
|
163
|
+
ds = ds.round(2)
|
|
164
|
+
# Mask where input obs or pred is NaN
|
|
165
|
+
ds = ds.where(~mask_nan)
|
|
115
166
|
return ds
|
|
116
167
|
|
|
117
168
|
|
|
@@ -178,7 +229,7 @@ def get_adjusted_nt(cdf, params, Nt, bin_edges):
|
|
|
178
229
|
# Estimate proportion of missing drops (Johnson's 2011 Eqs. 3)
|
|
179
230
|
# --> Alternative: p = 1 - np.sum(pdf(diameter, params)* diameter_bin_width) # [-]
|
|
180
231
|
p = 1 - np.diff(cdf([bin_edges[0], bin_edges[-1]], params)).item() # [-]
|
|
181
|
-
# Adjusts Nt for the proportion of drops not
|
|
232
|
+
# Adjusts Nt for the proportion of drops not obs
|
|
182
233
|
# p = np.clip(p, 0, 1 - 1e-12)
|
|
183
234
|
if np.isclose(p, 1, atol=1e-12):
|
|
184
235
|
return np.nan
|
|
@@ -206,7 +257,7 @@ def compute_negative_log_likelihood(
|
|
|
206
257
|
bin_edges : array-like
|
|
207
258
|
Edges of the bins (length N+1).
|
|
208
259
|
counts : array-like
|
|
209
|
-
|
|
260
|
+
obs counts in each bin (length N).
|
|
210
261
|
cdf_func : callable
|
|
211
262
|
Cumulative distribution function of the distribution.
|
|
212
263
|
pdf_func : callable
|
|
@@ -259,6 +310,8 @@ def compute_negative_log_likelihood(
|
|
|
259
310
|
|
|
260
311
|
def estimate_lognormal_parameters(
|
|
261
312
|
counts,
|
|
313
|
+
mu,
|
|
314
|
+
sigma,
|
|
262
315
|
bin_edges,
|
|
263
316
|
probability_method="cdf",
|
|
264
317
|
likelihood="multinomial",
|
|
@@ -273,6 +326,12 @@ def estimate_lognormal_parameters(
|
|
|
273
326
|
----------
|
|
274
327
|
counts : array-like
|
|
275
328
|
The counts for each bin in the histogram.
|
|
329
|
+
mu: float
|
|
330
|
+
The initial guess of the mean of the log of the distribution.
|
|
331
|
+
A good default value is 0.
|
|
332
|
+
sigma: float
|
|
333
|
+
The initial guess of the standard deviation of the log distribution.
|
|
334
|
+
A good default value is 1.
|
|
276
335
|
bin_edges : array-like
|
|
277
336
|
The edges of the bins.
|
|
278
337
|
probability_method : str, optional
|
|
@@ -306,9 +365,9 @@ def estimate_lognormal_parameters(
|
|
|
306
365
|
----------
|
|
307
366
|
.. [1] https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.lognorm.html#scipy.stats.lognorm
|
|
308
367
|
"""
|
|
309
|
-
#
|
|
310
|
-
|
|
311
|
-
|
|
368
|
+
# Definite initial guess for the parameters
|
|
369
|
+
scale = np.exp(mu) # mu = np.log(scale)
|
|
370
|
+
initial_params = [sigma, scale]
|
|
312
371
|
|
|
313
372
|
# Initialize bad results
|
|
314
373
|
null_output = (
|
|
@@ -329,9 +388,6 @@ def estimate_lognormal_parameters(
|
|
|
329
388
|
sigma, scale = params
|
|
330
389
|
return sigma > 0 and scale > 0
|
|
331
390
|
|
|
332
|
-
# Definite initial guess for the parameters
|
|
333
|
-
initial_params = [1.0, 1.0] # sigma, scale
|
|
334
|
-
|
|
335
391
|
# Define bounds for sigma and scale
|
|
336
392
|
bounds = [(1e-6, None), (1e-6, None)]
|
|
337
393
|
|
|
@@ -375,6 +431,7 @@ def estimate_lognormal_parameters(
|
|
|
375
431
|
|
|
376
432
|
def estimate_exponential_parameters(
|
|
377
433
|
counts,
|
|
434
|
+
Lambda,
|
|
378
435
|
bin_edges,
|
|
379
436
|
probability_method="cdf",
|
|
380
437
|
likelihood="multinomial",
|
|
@@ -389,6 +446,10 @@ def estimate_exponential_parameters(
|
|
|
389
446
|
----------
|
|
390
447
|
counts : array-like
|
|
391
448
|
The counts for each bin in the histogram.
|
|
449
|
+
Lambda : float
|
|
450
|
+
The initial guess of the scale parameter.
|
|
451
|
+
scale = 1 / lambda correspond to the scale parameter of the scipy.stats.expon distribution.
|
|
452
|
+
A good default value is 1.
|
|
392
453
|
bin_edges : array-like
|
|
393
454
|
The edges of the bins.
|
|
394
455
|
probability_method : str, optional
|
|
@@ -421,6 +482,10 @@ def estimate_exponential_parameters(
|
|
|
421
482
|
----------
|
|
422
483
|
.. [1] https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.expon.html
|
|
423
484
|
"""
|
|
485
|
+
# Definite initial guess for parameters
|
|
486
|
+
scale = 1 / Lambda
|
|
487
|
+
initial_params = [scale]
|
|
488
|
+
|
|
424
489
|
# Initialize bad results
|
|
425
490
|
null_output = {"N0": np.nan, "Lambda": np.nan} if output_dictionary else np.array([np.nan, np.nan])
|
|
426
491
|
|
|
@@ -438,9 +503,6 @@ def estimate_exponential_parameters(
|
|
|
438
503
|
scale = params[0]
|
|
439
504
|
return scale > 0
|
|
440
505
|
|
|
441
|
-
# Definite initial guess for the scale parameter
|
|
442
|
-
initial_params = [1.0] # scale
|
|
443
|
-
|
|
444
506
|
# Define bounds for scale
|
|
445
507
|
bounds = [(1e-6, None)]
|
|
446
508
|
|
|
@@ -485,8 +547,8 @@ def estimate_exponential_parameters(
|
|
|
485
547
|
|
|
486
548
|
def estimate_gamma_parameters(
|
|
487
549
|
counts,
|
|
488
|
-
|
|
489
|
-
|
|
550
|
+
mu,
|
|
551
|
+
Lambda,
|
|
490
552
|
bin_edges,
|
|
491
553
|
probability_method="cdf",
|
|
492
554
|
likelihood="multinomial",
|
|
@@ -501,11 +563,13 @@ def estimate_gamma_parameters(
|
|
|
501
563
|
----------
|
|
502
564
|
counts : array-like
|
|
503
565
|
The counts for each bin in the histogram.
|
|
504
|
-
|
|
505
|
-
The
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
566
|
+
mu: float
|
|
567
|
+
The initial guess of the shape parameter.
|
|
568
|
+
a = mu + 1 correspond to the shape parameter of the scipy.stats.gamma distribution.
|
|
569
|
+
A good default value is 0.
|
|
570
|
+
lambda: float
|
|
571
|
+
The initial guess of the scale parameter.
|
|
572
|
+
scale = 1 / lambda correspond to the scale parameter of the scipy.stats.gamma distribution.
|
|
509
573
|
A good default value is 1.
|
|
510
574
|
bin_edges : array-like
|
|
511
575
|
The edges of the bins.
|
|
@@ -541,6 +605,11 @@ def estimate_gamma_parameters(
|
|
|
541
605
|
.. [1] https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gamma.html
|
|
542
606
|
|
|
543
607
|
"""
|
|
608
|
+
# Define initial guess for parameters
|
|
609
|
+
a = mu + 1 # (mu = a-1, a = mu+1)
|
|
610
|
+
scale = 1 / Lambda
|
|
611
|
+
initial_params = [a, scale]
|
|
612
|
+
|
|
544
613
|
# Initialize bad results
|
|
545
614
|
null_output = (
|
|
546
615
|
{"N0": np.nan, "mu": np.nan, "lambda": np.nan} if output_dictionary else np.array([np.nan, np.nan, np.nan])
|
|
@@ -561,9 +630,6 @@ def estimate_gamma_parameters(
|
|
|
561
630
|
a, scale = params
|
|
562
631
|
return a > 0.1 and scale > 0 # using a > 0 cause some troubles
|
|
563
632
|
|
|
564
|
-
# Definite initial guess for the parameters
|
|
565
|
-
initial_params = [a, scale] # (mu=a-1, a=mu+1)
|
|
566
|
-
|
|
567
633
|
# Define bounds for a and scale
|
|
568
634
|
bounds = [(1e-6, None), (1e-6, None)]
|
|
569
635
|
|
|
@@ -617,12 +683,59 @@ def estimate_gamma_parameters(
|
|
|
617
683
|
return output
|
|
618
684
|
|
|
619
685
|
|
|
686
|
+
def _get_initial_lognormal_parameters(ds, mom_method=None):
|
|
687
|
+
default_mu = 0 # mu = np.log(scale)
|
|
688
|
+
default_sigma = 1
|
|
689
|
+
if mom_method is None or mom_method == "None":
|
|
690
|
+
ds_init = xr.Dataset(
|
|
691
|
+
{
|
|
692
|
+
"mu": default_mu,
|
|
693
|
+
"sigma": default_sigma,
|
|
694
|
+
},
|
|
695
|
+
)
|
|
696
|
+
else:
|
|
697
|
+
ds_init = get_mom_parameters(
|
|
698
|
+
ds=ds,
|
|
699
|
+
psd_model="LognormalPSD",
|
|
700
|
+
mom_methods=mom_method,
|
|
701
|
+
)
|
|
702
|
+
# If initialization results in some not finite number, set default value
|
|
703
|
+
ds_init["mu"] = xr.where(
|
|
704
|
+
np.logical_and(np.isfinite(ds_init["mu"]), ds_init["mu"] > 0),
|
|
705
|
+
ds_init["mu"],
|
|
706
|
+
default_mu,
|
|
707
|
+
)
|
|
708
|
+
ds_init["sigma"] = xr.where(np.isfinite(ds_init["sigma"]), ds_init["sigma"], default_sigma)
|
|
709
|
+
return ds_init
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _get_initial_exponential_parameters(ds, mom_method=None):
|
|
713
|
+
default_lambda = 1 # lambda = 1 /scale
|
|
714
|
+
if mom_method is None or mom_method == "None":
|
|
715
|
+
ds_init = xr.Dataset(
|
|
716
|
+
{
|
|
717
|
+
"Lambda": default_lambda,
|
|
718
|
+
},
|
|
719
|
+
)
|
|
720
|
+
else:
|
|
721
|
+
ds_init = get_mom_parameters(
|
|
722
|
+
ds=ds,
|
|
723
|
+
psd_model="ExponentialPSD",
|
|
724
|
+
mom_methods=mom_method,
|
|
725
|
+
)
|
|
726
|
+
# If initialization results in some not finite number, set default value
|
|
727
|
+
ds_init["Lambda"] = xr.where(np.isfinite(ds_init["Lambda"]), ds_init["Lambda"], default_lambda)
|
|
728
|
+
return ds_init
|
|
729
|
+
|
|
730
|
+
|
|
620
731
|
def _get_initial_gamma_parameters(ds, mom_method=None):
|
|
621
|
-
|
|
732
|
+
default_mu = 0 # a = mu + 1 | mu = a - 1
|
|
733
|
+
default_lambda = 1 # scale = 1 / Lambda
|
|
734
|
+
if mom_method is None or mom_method == "None":
|
|
622
735
|
ds_init = xr.Dataset(
|
|
623
736
|
{
|
|
624
|
-
"
|
|
625
|
-
"
|
|
737
|
+
"mu": default_mu,
|
|
738
|
+
"Lambda": default_lambda,
|
|
626
739
|
},
|
|
627
740
|
)
|
|
628
741
|
else:
|
|
@@ -631,11 +744,13 @@ def _get_initial_gamma_parameters(ds, mom_method=None):
|
|
|
631
744
|
psd_model="GammaPSD",
|
|
632
745
|
mom_methods=mom_method,
|
|
633
746
|
)
|
|
634
|
-
ds_init["a"] = ds_init["mu"] + 1
|
|
635
|
-
ds_init["scale"] = 1 / ds_init["Lambda"]
|
|
636
747
|
# If initialization results in some not finite number, set default value
|
|
637
|
-
ds_init["
|
|
638
|
-
|
|
748
|
+
ds_init["mu"] = xr.where(
|
|
749
|
+
np.logical_and(np.isfinite(ds_init["mu"]), ds_init["mu"] > -1),
|
|
750
|
+
ds_init["mu"],
|
|
751
|
+
default_mu,
|
|
752
|
+
)
|
|
753
|
+
ds_init["Lambda"] = xr.where(np.isfinite(ds_init["Lambda"]), ds_init["Lambda"], default_lambda)
|
|
639
754
|
return ds_init
|
|
640
755
|
|
|
641
756
|
|
|
@@ -663,7 +778,7 @@ def get_gamma_parameters(
|
|
|
663
778
|
with the specified mom_method.
|
|
664
779
|
init_method: str or list
|
|
665
780
|
The method(s) of moments used to initialize the gamma parameters.
|
|
666
|
-
If None, the scale parameter is set to 1 and mu to 0 (a=1).
|
|
781
|
+
If None (or 'None'), the scale parameter is set to 1 and mu to 0 (a=1).
|
|
667
782
|
probability_method : str, optional
|
|
668
783
|
Method to compute probabilities. The default value is ``cdf``.
|
|
669
784
|
likelihood : str, optional
|
|
@@ -690,9 +805,9 @@ def get_gamma_parameters(
|
|
|
690
805
|
"""
|
|
691
806
|
# Define inputs
|
|
692
807
|
counts = ds["drop_number_concentration"] * ds["diameter_bin_width"]
|
|
693
|
-
diameter_breaks =
|
|
808
|
+
diameter_breaks = get_diameter_bin_edges(ds)
|
|
694
809
|
|
|
695
|
-
# Define initial parameters (
|
|
810
|
+
# Define initial parameters (mu, Lambda)
|
|
696
811
|
ds_init = _get_initial_gamma_parameters(ds, mom_method=init_method)
|
|
697
812
|
|
|
698
813
|
# Define kwargs
|
|
@@ -709,10 +824,10 @@ def get_gamma_parameters(
|
|
|
709
824
|
da_params = xr.apply_ufunc(
|
|
710
825
|
estimate_gamma_parameters,
|
|
711
826
|
counts,
|
|
712
|
-
ds_init["
|
|
713
|
-
ds_init["
|
|
827
|
+
ds_init["mu"],
|
|
828
|
+
ds_init["Lambda"],
|
|
714
829
|
kwargs=kwargs,
|
|
715
|
-
input_core_dims=[[
|
|
830
|
+
input_core_dims=[[DIAMETER_DIMENSION], [], []],
|
|
716
831
|
output_core_dims=[["parameters"]],
|
|
717
832
|
vectorize=True,
|
|
718
833
|
dask="parallelized",
|
|
@@ -720,8 +835,6 @@ def get_gamma_parameters(
|
|
|
720
835
|
output_dtypes=["float64"],
|
|
721
836
|
)
|
|
722
837
|
|
|
723
|
-
ds_init.isel(velocity_method=0, time=-3)
|
|
724
|
-
|
|
725
838
|
# Add parameters coordinates
|
|
726
839
|
da_params = da_params.assign_coords({"parameters": ["N0", "mu", "Lambda"]})
|
|
727
840
|
|
|
@@ -735,7 +848,7 @@ def get_gamma_parameters(
|
|
|
735
848
|
|
|
736
849
|
def get_lognormal_parameters(
|
|
737
850
|
ds,
|
|
738
|
-
init_method=None,
|
|
851
|
+
init_method=None,
|
|
739
852
|
probability_method="cdf",
|
|
740
853
|
likelihood="multinomial",
|
|
741
854
|
truncated_likelihood=True,
|
|
@@ -779,7 +892,10 @@ def get_lognormal_parameters(
|
|
|
779
892
|
"""
|
|
780
893
|
# Define inputs
|
|
781
894
|
counts = ds["drop_number_concentration"] * ds["diameter_bin_width"]
|
|
782
|
-
diameter_breaks =
|
|
895
|
+
diameter_breaks = get_diameter_bin_edges(ds)
|
|
896
|
+
|
|
897
|
+
# Define initial parameters (mu, sigma)
|
|
898
|
+
ds_init = _get_initial_lognormal_parameters(ds, mom_method=init_method)
|
|
783
899
|
|
|
784
900
|
# Define kwargs
|
|
785
901
|
kwargs = {
|
|
@@ -795,8 +911,10 @@ def get_lognormal_parameters(
|
|
|
795
911
|
da_params = xr.apply_ufunc(
|
|
796
912
|
estimate_lognormal_parameters,
|
|
797
913
|
counts,
|
|
914
|
+
ds_init["mu"],
|
|
915
|
+
ds_init["sigma"],
|
|
798
916
|
kwargs=kwargs,
|
|
799
|
-
input_core_dims=[[
|
|
917
|
+
input_core_dims=[[DIAMETER_DIMENSION], [], []],
|
|
800
918
|
output_core_dims=[["parameters"]],
|
|
801
919
|
vectorize=True,
|
|
802
920
|
dask="parallelized",
|
|
@@ -818,7 +936,7 @@ def get_lognormal_parameters(
|
|
|
818
936
|
|
|
819
937
|
def get_exponential_parameters(
|
|
820
938
|
ds,
|
|
821
|
-
init_method=None,
|
|
939
|
+
init_method=None,
|
|
822
940
|
probability_method="cdf",
|
|
823
941
|
likelihood="multinomial",
|
|
824
942
|
truncated_likelihood=True,
|
|
@@ -864,7 +982,10 @@ def get_exponential_parameters(
|
|
|
864
982
|
"""
|
|
865
983
|
# Define inputs
|
|
866
984
|
counts = ds["drop_number_concentration"] * ds["diameter_bin_width"]
|
|
867
|
-
diameter_breaks =
|
|
985
|
+
diameter_breaks = get_diameter_bin_edges(ds)
|
|
986
|
+
|
|
987
|
+
# Define initial parameters (Lambda)
|
|
988
|
+
ds_init = _get_initial_exponential_parameters(ds, mom_method=init_method)
|
|
868
989
|
|
|
869
990
|
# Define kwargs
|
|
870
991
|
kwargs = {
|
|
@@ -880,8 +1001,9 @@ def get_exponential_parameters(
|
|
|
880
1001
|
da_params = xr.apply_ufunc(
|
|
881
1002
|
estimate_exponential_parameters,
|
|
882
1003
|
counts,
|
|
1004
|
+
ds_init["Lambda"],
|
|
883
1005
|
kwargs=kwargs,
|
|
884
|
-
input_core_dims=[[
|
|
1006
|
+
input_core_dims=[[DIAMETER_DIMENSION], []],
|
|
885
1007
|
output_core_dims=[["parameters"]],
|
|
886
1008
|
vectorize=True,
|
|
887
1009
|
dask="parallelized",
|
|
@@ -1009,7 +1131,7 @@ def _estimate_gamma_parameters_johnson(
|
|
|
1009
1131
|
p = 1 - np.sum((Lambda ** (mu + 1)) / gamma(mu + 1) * D**mu * np.exp(-Lambda * D) * diameter_bin_width) # [-]
|
|
1010
1132
|
|
|
1011
1133
|
# Convert tilde_N_T to N_T using Johnson's 2013 Eqs. 3 and 4.
|
|
1012
|
-
# - Adjusts for the proportion of drops not
|
|
1134
|
+
# - Adjusts for the proportion of drops not obs
|
|
1013
1135
|
N_T = tilde_N_T / (1 - p) # [m-3]
|
|
1014
1136
|
|
|
1015
1137
|
# Compute N0
|
|
@@ -1030,7 +1152,8 @@ def get_gamma_parameters_johnson2014(ds, method="Nelder-Mead"):
|
|
|
1030
1152
|
"""Deprecated model. See Gamma Model with truncated_likelihood and 'pdf'."""
|
|
1031
1153
|
drop_number_concentration = ds["drop_number_concentration"]
|
|
1032
1154
|
diameter = ds["diameter_bin_center"]
|
|
1033
|
-
diameter_breaks =
|
|
1155
|
+
diameter_breaks = get_diameter_bin_edges(ds)
|
|
1156
|
+
|
|
1034
1157
|
# Define kwargs
|
|
1035
1158
|
kwargs = {
|
|
1036
1159
|
"output_dictionary": False,
|
|
@@ -1043,7 +1166,7 @@ def get_gamma_parameters_johnson2014(ds, method="Nelder-Mead"):
|
|
|
1043
1166
|
diameter,
|
|
1044
1167
|
# diameter_bin_width,
|
|
1045
1168
|
kwargs=kwargs,
|
|
1046
|
-
input_core_dims=[[
|
|
1169
|
+
input_core_dims=[[DIAMETER_DIMENSION], [DIAMETER_DIMENSION]], # [DIAMETER_DIMENSION],
|
|
1047
1170
|
output_core_dims=[["parameters"]],
|
|
1048
1171
|
vectorize=True,
|
|
1049
1172
|
)
|
|
@@ -1346,6 +1469,12 @@ def get_exponential_parameters_gs(ds, target="ND", transformation="log", error_o
|
|
|
1346
1469
|
# "transformation": "log", "identity", "sqrt", # only for drop_number_concentration
|
|
1347
1470
|
# "error_order": 1, # MAE/MSE ... only for drop_number_concentration
|
|
1348
1471
|
|
|
1472
|
+
# Compute required variables
|
|
1473
|
+
ds["Nt"] = get_total_number_concentration(
|
|
1474
|
+
drop_number_concentration=ds["drop_number_concentration"],
|
|
1475
|
+
diameter_bin_width=ds["diameter_bin_width"],
|
|
1476
|
+
)
|
|
1477
|
+
|
|
1349
1478
|
# Define kwargs
|
|
1350
1479
|
kwargs = {
|
|
1351
1480
|
"D": ds["diameter_bin_center"].data,
|
|
@@ -1365,7 +1494,7 @@ def get_exponential_parameters_gs(ds, target="ND", transformation="log", error_o
|
|
|
1365
1494
|
# Other options
|
|
1366
1495
|
kwargs=kwargs,
|
|
1367
1496
|
# Settings
|
|
1368
|
-
input_core_dims=[[], [
|
|
1497
|
+
input_core_dims=[[], [DIAMETER_DIMENSION], [DIAMETER_DIMENSION]],
|
|
1369
1498
|
output_core_dims=[["parameters"]],
|
|
1370
1499
|
vectorize=True,
|
|
1371
1500
|
dask="parallelized",
|
|
@@ -1390,6 +1519,12 @@ def get_gamma_parameters_gs(ds, target="ND", transformation="log", error_order=1
|
|
|
1390
1519
|
# "transformation": "log", "identity", "sqrt", # only for drop_number_concentration
|
|
1391
1520
|
# "error_order": 1, # MAE/MSE ... only for drop_number_concentration
|
|
1392
1521
|
|
|
1522
|
+
# Compute required variables
|
|
1523
|
+
ds["Nt"] = get_total_number_concentration(
|
|
1524
|
+
drop_number_concentration=ds["drop_number_concentration"],
|
|
1525
|
+
diameter_bin_width=ds["diameter_bin_width"],
|
|
1526
|
+
)
|
|
1527
|
+
|
|
1393
1528
|
# Define kwargs
|
|
1394
1529
|
kwargs = {
|
|
1395
1530
|
"D": ds["diameter_bin_center"].data,
|
|
@@ -1409,7 +1544,7 @@ def get_gamma_parameters_gs(ds, target="ND", transformation="log", error_order=1
|
|
|
1409
1544
|
# Other options
|
|
1410
1545
|
kwargs=kwargs,
|
|
1411
1546
|
# Settings
|
|
1412
|
-
input_core_dims=[[], [
|
|
1547
|
+
input_core_dims=[[], [DIAMETER_DIMENSION], [DIAMETER_DIMENSION]],
|
|
1413
1548
|
output_core_dims=[["parameters"]],
|
|
1414
1549
|
vectorize=True,
|
|
1415
1550
|
dask="parallelized",
|
|
@@ -1434,6 +1569,12 @@ def get_lognormal_parameters_gs(ds, target="ND", transformation="log", error_ord
|
|
|
1434
1569
|
# "transformation": "log", "identity", "sqrt", # only for drop_number_concentration
|
|
1435
1570
|
# "error_order": 1, # MAE/MSE ... only for drop_number_concentration
|
|
1436
1571
|
|
|
1572
|
+
# Compute required variables
|
|
1573
|
+
ds["Nt"] = get_total_number_concentration(
|
|
1574
|
+
drop_number_concentration=ds["drop_number_concentration"],
|
|
1575
|
+
diameter_bin_width=ds["diameter_bin_width"],
|
|
1576
|
+
)
|
|
1577
|
+
|
|
1437
1578
|
# Define kwargs
|
|
1438
1579
|
kwargs = {
|
|
1439
1580
|
"D": ds["diameter_bin_center"].data,
|
|
@@ -1453,7 +1594,7 @@ def get_lognormal_parameters_gs(ds, target="ND", transformation="log", error_ord
|
|
|
1453
1594
|
# Other options
|
|
1454
1595
|
kwargs=kwargs,
|
|
1455
1596
|
# Settings
|
|
1456
|
-
input_core_dims=[[], [
|
|
1597
|
+
input_core_dims=[[], [DIAMETER_DIMENSION], [DIAMETER_DIMENSION]],
|
|
1457
1598
|
output_core_dims=[["parameters"]],
|
|
1458
1599
|
vectorize=True,
|
|
1459
1600
|
dask="parallelized",
|
|
@@ -1475,8 +1616,8 @@ def get_lognormal_parameters_gs(ds, target="ND", transformation="log", error_ord
|
|
|
1475
1616
|
def get_normalized_gamma_parameters_gs(ds, target="ND", transformation="log", error_order=1):
|
|
1476
1617
|
r"""Estimate $\mu$ of a Normalized Gamma distribution using Grid Search.
|
|
1477
1618
|
|
|
1478
|
-
The D50 and Nw parameters of the Normalized Gamma distribution are derived empirically from the
|
|
1479
|
-
$\mu$ is derived by minimizing the errors between the
|
|
1619
|
+
The D50 and Nw parameters of the Normalized Gamma distribution are derived empirically from the obs DSD.
|
|
1620
|
+
$\mu$ is derived by minimizing the errors between the obs DSD and modelled Normalized Gamma distribution.
|
|
1480
1621
|
|
|
1481
1622
|
Parameters
|
|
1482
1623
|
----------
|
|
@@ -1501,6 +1642,29 @@ def get_normalized_gamma_parameters_gs(ds, target="ND", transformation="log", er
|
|
|
1501
1642
|
# "transformation": "log", "identity", "sqrt", # only for drop_number_concentration
|
|
1502
1643
|
# "error_order": 1, # MAE/MSE ... only for drop_number_concentration
|
|
1503
1644
|
|
|
1645
|
+
# Compute required variables
|
|
1646
|
+
drop_number_concentration = ds["drop_number_concentration"]
|
|
1647
|
+
diameter_bin_width = ds["diameter_bin_width"]
|
|
1648
|
+
diameter = ds["diameter_bin_center"] / 1000 # conversion from mm to m
|
|
1649
|
+
m3 = get_moment(
|
|
1650
|
+
drop_number_concentration=drop_number_concentration,
|
|
1651
|
+
diameter=diameter, # m
|
|
1652
|
+
diameter_bin_width=diameter_bin_width, # mm
|
|
1653
|
+
moment=3,
|
|
1654
|
+
)
|
|
1655
|
+
m4 = get_moment(
|
|
1656
|
+
drop_number_concentration=drop_number_concentration,
|
|
1657
|
+
diameter=diameter, # m
|
|
1658
|
+
diameter_bin_width=diameter_bin_width, # mm
|
|
1659
|
+
moment=4,
|
|
1660
|
+
)
|
|
1661
|
+
ds["Nw"] = get_normalized_intercept_parameter_from_moments(moment_3=m3, moment_4=m4)
|
|
1662
|
+
ds["D50"] = get_median_volume_drop_diameter(
|
|
1663
|
+
drop_number_concentration=drop_number_concentration,
|
|
1664
|
+
diameter=diameter, # m
|
|
1665
|
+
diameter_bin_width=diameter_bin_width, # mm
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1504
1668
|
# Define kwargs
|
|
1505
1669
|
kwargs = {
|
|
1506
1670
|
"D": ds["diameter_bin_center"].data,
|
|
@@ -1521,7 +1685,7 @@ def get_normalized_gamma_parameters_gs(ds, target="ND", transformation="log", er
|
|
|
1521
1685
|
# Other options
|
|
1522
1686
|
kwargs=kwargs,
|
|
1523
1687
|
# Settings
|
|
1524
|
-
input_core_dims=[[], [], [
|
|
1688
|
+
input_core_dims=[[], [], [DIAMETER_DIMENSION], [DIAMETER_DIMENSION]],
|
|
1525
1689
|
output_core_dims=[["parameters"]],
|
|
1526
1690
|
vectorize=True,
|
|
1527
1691
|
dask="parallelized",
|
|
@@ -1735,12 +1899,25 @@ def get_lognormal_parameters_M346(M3, M4, M6):
|
|
|
1735
1899
|
return Nt, mu, sigma
|
|
1736
1900
|
|
|
1737
1901
|
|
|
1902
|
+
def _compute_moments(ds, moments):
|
|
1903
|
+
list_moments = [
|
|
1904
|
+
get_moment(
|
|
1905
|
+
drop_number_concentration=ds["drop_number_concentration"],
|
|
1906
|
+
diameter=ds["diameter_bin_center"] / 1000, # m
|
|
1907
|
+
diameter_bin_width=ds["diameter_bin_width"], # mm
|
|
1908
|
+
moment=int(moment.replace("M", "")),
|
|
1909
|
+
)
|
|
1910
|
+
for moment in moments
|
|
1911
|
+
]
|
|
1912
|
+
return list_moments
|
|
1913
|
+
|
|
1914
|
+
|
|
1738
1915
|
def _get_gamma_parameters_mom(ds: xr.Dataset, mom_method: str) -> xr.Dataset:
|
|
1739
1916
|
# Get the correct function and list of variables for the requested method
|
|
1740
1917
|
func, needed_moments = MOM_METHODS_DICT["GammaPSD"][mom_method]
|
|
1741
1918
|
|
|
1742
|
-
#
|
|
1743
|
-
arrs =
|
|
1919
|
+
# Compute required moments
|
|
1920
|
+
arrs = _compute_moments(ds, moments=needed_moments)
|
|
1744
1921
|
|
|
1745
1922
|
# Apply the function. This will produce (mu, Lambda, N0) with the same coords/shapes as input data
|
|
1746
1923
|
N0, mu, Lambda = func(*arrs)
|
|
@@ -1761,8 +1938,8 @@ def _get_lognormal_parameters_mom(ds: xr.Dataset, mom_method: str) -> xr.Dataset
|
|
|
1761
1938
|
# Get the correct function and list of variables for the requested method
|
|
1762
1939
|
func, needed_moments = MOM_METHODS_DICT["LognormalPSD"][mom_method]
|
|
1763
1940
|
|
|
1764
|
-
#
|
|
1765
|
-
arrs =
|
|
1941
|
+
# Compute required moments
|
|
1942
|
+
arrs = _compute_moments(ds, moments=needed_moments)
|
|
1766
1943
|
|
|
1767
1944
|
# Apply the function. This will produce (mu, Lambda, N0) with the same coords/shapes as input data
|
|
1768
1945
|
Nt, mu, sigma = func(*arrs)
|
|
@@ -1783,8 +1960,8 @@ def _get_exponential_parameters_mom(ds: xr.Dataset, mom_method: str) -> xr.Datas
|
|
|
1783
1960
|
# Get the correct function and list of variables for the requested method
|
|
1784
1961
|
func, needed_moments = MOM_METHODS_DICT["ExponentialPSD"][mom_method]
|
|
1785
1962
|
|
|
1786
|
-
#
|
|
1787
|
-
arrs =
|
|
1963
|
+
# Compute required moments
|
|
1964
|
+
arrs = _compute_moments(ds, moments=needed_moments)
|
|
1788
1965
|
|
|
1789
1966
|
# Apply the function. This will produce (mu, Lambda, N0) with the same coords/shapes as input data
|
|
1790
1967
|
N0, Lambda = func(*arrs)
|
|
@@ -1803,6 +1980,79 @@ def _get_exponential_parameters_mom(ds: xr.Dataset, mom_method: str) -> xr.Datas
|
|
|
1803
1980
|
####--------------------------------------------------------------------------------------.
|
|
1804
1981
|
#### Routines dictionary
|
|
1805
1982
|
|
|
1983
|
+
####--------------------------------------------------------------------------------------.
|
|
1984
|
+
ATTRS_PARAMS_DICT = {
|
|
1985
|
+
"GammaPSD": {
|
|
1986
|
+
"N0": {
|
|
1987
|
+
"description": "Intercept parameter of the Gamma PSD",
|
|
1988
|
+
"standard_name": "particle_size_distribution_intercept",
|
|
1989
|
+
"units": "mm**(-1-mu) m-3",
|
|
1990
|
+
"long_name": "GammaPSD intercept parameter",
|
|
1991
|
+
},
|
|
1992
|
+
"mu": {
|
|
1993
|
+
"description": "Shape parameter of the Gamma PSD",
|
|
1994
|
+
"standard_name": "particle_size_distribution_shape",
|
|
1995
|
+
"units": "",
|
|
1996
|
+
"long_name": "GammaPSD shape parameter",
|
|
1997
|
+
},
|
|
1998
|
+
"Lambda": {
|
|
1999
|
+
"description": "Slope (rate) parameter of the Gamma PSD",
|
|
2000
|
+
"standard_name": "particle_size_distribution_slope",
|
|
2001
|
+
"units": "mm-1",
|
|
2002
|
+
"long_name": "GammaPSD slope parameter",
|
|
2003
|
+
},
|
|
2004
|
+
},
|
|
2005
|
+
"NormalizedGammaPSD": {
|
|
2006
|
+
"Nw": {
|
|
2007
|
+
"standard_name": "normalized_intercept_parameter",
|
|
2008
|
+
"units": "mm-1 m-3",
|
|
2009
|
+
"long_name": "NormalizedGammaPSD Normalized Intercept Parameter",
|
|
2010
|
+
},
|
|
2011
|
+
"mu": {
|
|
2012
|
+
"description": "Dimensionless shape parameter controlling the curvature of the Normalized Gamma PSD",
|
|
2013
|
+
"standard_name": "particle_size_distribution_shape",
|
|
2014
|
+
"units": "",
|
|
2015
|
+
"long_name": "NormalizedGammaPSD Shape Parameter ",
|
|
2016
|
+
},
|
|
2017
|
+
"D50": {
|
|
2018
|
+
"standard_name": "median_volume_diameter",
|
|
2019
|
+
"units": "mm",
|
|
2020
|
+
"long_name": "NormalizedGammaPSD Median Volume Drop Diameter",
|
|
2021
|
+
},
|
|
2022
|
+
},
|
|
2023
|
+
"LognormalPSD": {
|
|
2024
|
+
"Nt": {
|
|
2025
|
+
"standard_name": "number_concentration_of_rain_drops_in_air",
|
|
2026
|
+
"units": "m-3",
|
|
2027
|
+
"long_name": "Total Number Concentration",
|
|
2028
|
+
},
|
|
2029
|
+
"mu": {
|
|
2030
|
+
"description": "Mean of the Lognormal PSD",
|
|
2031
|
+
"units": "log(mm)",
|
|
2032
|
+
"long_name": "Mean of the Lognormal PSD",
|
|
2033
|
+
},
|
|
2034
|
+
"sigma": {
|
|
2035
|
+
"standard_name": "Standard Deviation of the Lognormal PSD",
|
|
2036
|
+
"units": "",
|
|
2037
|
+
"long_name": "Standard Deviation of the Lognormal PSD",
|
|
2038
|
+
},
|
|
2039
|
+
},
|
|
2040
|
+
"ExponentialPSD": {
|
|
2041
|
+
"N0": {
|
|
2042
|
+
"description": "Intercept parameter of the Exponential PSD",
|
|
2043
|
+
"standard_name": "particle_size_distribution_intercept",
|
|
2044
|
+
"units": "mm-1 m-3",
|
|
2045
|
+
"long_name": "ExponentialPSD intercept parameter",
|
|
2046
|
+
},
|
|
2047
|
+
"Lambda": {
|
|
2048
|
+
"description": "Slope (rate) parameter of the Exponential PSD",
|
|
2049
|
+
"standard_name": "particle_size_distribution_slope",
|
|
2050
|
+
"units": "mm-1",
|
|
2051
|
+
"long_name": "ExponentialPSD slope parameter",
|
|
2052
|
+
},
|
|
2053
|
+
},
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1806
2056
|
|
|
1807
2057
|
MOM_METHODS_DICT = {
|
|
1808
2058
|
"GammaPSD": {
|
|
@@ -1843,6 +2093,8 @@ OPTIMIZATION_ROUTINES_DICT = {
|
|
|
1843
2093
|
|
|
1844
2094
|
def available_mom_methods(psd_model):
|
|
1845
2095
|
"""Implemented MOM methods for a given PSD model."""
|
|
2096
|
+
if psd_model not in MOM_METHODS_DICT:
|
|
2097
|
+
raise NotImplementedError(f"No MOM methods available for {psd_model}")
|
|
1846
2098
|
return list(MOM_METHODS_DICT[psd_model])
|
|
1847
2099
|
|
|
1848
2100
|
|
|
@@ -1863,7 +2115,7 @@ def check_psd_model(psd_model, optimization):
|
|
|
1863
2115
|
f"{optimization} optimization is not available for 'psd_model' {psd_model}. "
|
|
1864
2116
|
f"Accepted PSD models are {valid_psd_models}."
|
|
1865
2117
|
)
|
|
1866
|
-
raise
|
|
2118
|
+
raise NotImplementedError(msg)
|
|
1867
2119
|
|
|
1868
2120
|
|
|
1869
2121
|
def check_target(target):
|
|
@@ -1921,11 +2173,14 @@ def check_optimizer(optimizer):
|
|
|
1921
2173
|
return optimizer
|
|
1922
2174
|
|
|
1923
2175
|
|
|
1924
|
-
def check_mom_methods(mom_methods, psd_model):
|
|
2176
|
+
def check_mom_methods(mom_methods, psd_model, allow_none=False):
|
|
1925
2177
|
"""Check valid mom_methods arguments."""
|
|
1926
|
-
if isinstance(mom_methods, str):
|
|
2178
|
+
if isinstance(mom_methods, (str, type(None))):
|
|
1927
2179
|
mom_methods = [mom_methods]
|
|
2180
|
+
mom_methods = [str(v) for v in mom_methods] # None --> 'None'
|
|
1928
2181
|
valid_mom_methods = available_mom_methods(psd_model)
|
|
2182
|
+
if allow_none:
|
|
2183
|
+
valid_mom_methods = [*valid_mom_methods, "None"]
|
|
1929
2184
|
invalid_mom_methods = np.array(mom_methods)[np.isin(mom_methods, valid_mom_methods, invert=True)]
|
|
1930
2185
|
if len(invalid_mom_methods) > 0:
|
|
1931
2186
|
raise ValueError(
|
|
@@ -1970,36 +2225,50 @@ def check_optimization_kwargs(optimization_kwargs, optimization, psd_model):
|
|
|
1970
2225
|
expected_arguments = dict_arguments.get(optimization, {})
|
|
1971
2226
|
|
|
1972
2227
|
# Check for missing arguments in optimization_kwargs
|
|
1973
|
-
missing_args = [arg for arg in expected_arguments if arg not in optimization_kwargs]
|
|
1974
|
-
if missing_args:
|
|
1975
|
-
|
|
2228
|
+
# missing_args = [arg for arg in expected_arguments if arg not in optimization_kwargs]
|
|
2229
|
+
# if missing_args:
|
|
2230
|
+
# raise ValueError(f"Missing required arguments for {optimization} optimization: {missing_args}")
|
|
1976
2231
|
|
|
1977
|
-
# Validate
|
|
1978
|
-
_ = [
|
|
2232
|
+
# Validate arguments values
|
|
2233
|
+
_ = [
|
|
2234
|
+
check(optimization_kwargs[arg])
|
|
2235
|
+
for arg, check in expected_arguments.items()
|
|
2236
|
+
if callable(check) and arg in optimization_kwargs
|
|
2237
|
+
]
|
|
1979
2238
|
|
|
1980
2239
|
# Further special checks
|
|
1981
|
-
if optimization == "MOM":
|
|
2240
|
+
if optimization == "MOM" and "mom_methods" in optimization_kwargs:
|
|
1982
2241
|
_ = check_mom_methods(mom_methods=optimization_kwargs["mom_methods"], psd_model=psd_model)
|
|
1983
|
-
if optimization == "ML" and optimization_kwargs
|
|
1984
|
-
_ = check_mom_methods(mom_methods=optimization_kwargs["init_method"], psd_model=psd_model)
|
|
2242
|
+
if optimization == "ML" and optimization_kwargs.get("init_method", None) is not None:
|
|
2243
|
+
_ = check_mom_methods(mom_methods=optimization_kwargs["init_method"], psd_model=psd_model, allow_none=True)
|
|
1985
2244
|
|
|
1986
2245
|
|
|
1987
2246
|
####--------------------------------------------------------------------------------------.
|
|
1988
2247
|
#### Wrappers for fitting
|
|
1989
2248
|
|
|
1990
2249
|
|
|
1991
|
-
def
|
|
2250
|
+
def _finalize_attributes(ds_params, psd_model, optimization, optimization_kwargs):
|
|
2251
|
+
ds_params.attrs["disdrodb_psd_model"] = psd_model
|
|
2252
|
+
ds_params.attrs["disdrodb_psd_optimization"] = optimization
|
|
2253
|
+
ds_params.attrs["disdrodb_psd_optimization_kwargs"] = ", ".join(
|
|
2254
|
+
[f"{k}: {v}" for k, v in optimization_kwargs.items()],
|
|
2255
|
+
)
|
|
2256
|
+
return ds_params
|
|
2257
|
+
|
|
2258
|
+
|
|
2259
|
+
def get_mom_parameters(ds: xr.Dataset, psd_model: str, mom_methods=None) -> xr.Dataset:
|
|
1992
2260
|
"""
|
|
1993
2261
|
Compute PSD model parameters using various method-of-moments (MOM) approaches.
|
|
1994
2262
|
|
|
1995
|
-
The method is specified by the `mom_methods`
|
|
2263
|
+
The method is specified by the `mom_methods` abbreviations, e.g. 'M012', 'M234', 'M246'.
|
|
1996
2264
|
|
|
1997
2265
|
Parameters
|
|
1998
2266
|
----------
|
|
1999
2267
|
ds : xarray.Dataset
|
|
2000
2268
|
An xarray Dataset with the required moments M0...M6 as data variables.
|
|
2001
|
-
mom_methods: str or list
|
|
2002
|
-
|
|
2269
|
+
mom_methods: str or list (optional)
|
|
2270
|
+
See valid values with disdrodb.psd.available_mom_methods(psd_model)
|
|
2271
|
+
If None (the default), compute model parameters with all available MOM methods.
|
|
2003
2272
|
|
|
2004
2273
|
Returns
|
|
2005
2274
|
-------
|
|
@@ -2010,6 +2279,8 @@ def get_mom_parameters(ds: xr.Dataset, psd_model: str, mom_methods: str) -> xr.D
|
|
|
2010
2279
|
"""
|
|
2011
2280
|
# Check inputs
|
|
2012
2281
|
check_psd_model(psd_model=psd_model, optimization="MOM")
|
|
2282
|
+
if mom_methods is None:
|
|
2283
|
+
mom_methods = available_mom_methods(psd_model)
|
|
2013
2284
|
mom_methods = check_mom_methods(mom_methods, psd_model=psd_model)
|
|
2014
2285
|
|
|
2015
2286
|
# Retrieve function
|
|
@@ -2017,13 +2288,21 @@ def get_mom_parameters(ds: xr.Dataset, psd_model: str, mom_methods: str) -> xr.D
|
|
|
2017
2288
|
|
|
2018
2289
|
# Compute parameters
|
|
2019
2290
|
if len(mom_methods) == 1:
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2291
|
+
ds_params = func(ds=ds, mom_method=mom_methods[0])
|
|
2292
|
+
else:
|
|
2293
|
+
list_ds = [func(ds=ds, mom_method=mom_method) for mom_method in mom_methods]
|
|
2294
|
+
ds_params = xr.concat(list_ds, dim="mom_method")
|
|
2295
|
+
ds_params = ds_params.assign_coords({"mom_method": mom_methods})
|
|
2296
|
+
|
|
2297
|
+
# Add model attributes
|
|
2298
|
+
optimization_kwargs = {"mom_methods": mom_methods}
|
|
2299
|
+
ds_params = _finalize_attributes(
|
|
2300
|
+
ds_params=ds_params,
|
|
2301
|
+
psd_model=psd_model,
|
|
2302
|
+
optimization="MOM",
|
|
2303
|
+
optimization_kwargs=optimization_kwargs,
|
|
2304
|
+
)
|
|
2305
|
+
return ds_params
|
|
2027
2306
|
|
|
2028
2307
|
|
|
2029
2308
|
def get_ml_parameters(
|
|
@@ -2052,7 +2331,7 @@ def get_ml_parameters(
|
|
|
2052
2331
|
The PSD model to fit. See ``available_psd_models()``.
|
|
2053
2332
|
init_method: str or list
|
|
2054
2333
|
The method(s) of moments used to initialize the PSD model parameters.
|
|
2055
|
-
See ``available_mom_methods(psd_model)``.
|
|
2334
|
+
Multiple methods can be specified. See ``available_mom_methods(psd_model)``.
|
|
2056
2335
|
probability_method : str, optional
|
|
2057
2336
|
Method to compute probabilities. The default value is ``cdf``.
|
|
2058
2337
|
likelihood : str, optional
|
|
@@ -2076,21 +2355,51 @@ def get_ml_parameters(
|
|
|
2076
2355
|
optimizer = check_optimizer(optimizer)
|
|
2077
2356
|
|
|
2078
2357
|
# Check valid init_method
|
|
2079
|
-
|
|
2080
|
-
init_method = check_mom_methods(mom_methods=init_method, psd_model=psd_model)
|
|
2358
|
+
init_method = check_mom_methods(mom_methods=init_method, psd_model=psd_model, allow_none=True)
|
|
2081
2359
|
|
|
2082
2360
|
# Retrieve estimation function
|
|
2083
2361
|
func = OPTIMIZATION_ROUTINES_DICT["ML"][psd_model]
|
|
2084
2362
|
|
|
2085
|
-
#
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2363
|
+
# Compute parameters
|
|
2364
|
+
if init_method is None or len(init_method) == 1:
|
|
2365
|
+
ds_params = func(
|
|
2366
|
+
ds=ds,
|
|
2367
|
+
init_method=init_method[0],
|
|
2368
|
+
probability_method=probability_method,
|
|
2369
|
+
likelihood=likelihood,
|
|
2370
|
+
truncated_likelihood=truncated_likelihood,
|
|
2371
|
+
optimizer=optimizer,
|
|
2372
|
+
)
|
|
2373
|
+
else:
|
|
2374
|
+
list_ds = [
|
|
2375
|
+
func(
|
|
2376
|
+
ds=ds,
|
|
2377
|
+
init_method=method,
|
|
2378
|
+
probability_method=probability_method,
|
|
2379
|
+
likelihood=likelihood,
|
|
2380
|
+
truncated_likelihood=truncated_likelihood,
|
|
2381
|
+
optimizer=optimizer,
|
|
2382
|
+
)
|
|
2383
|
+
for method in init_method
|
|
2384
|
+
]
|
|
2385
|
+
ds_params = xr.concat(list_ds, dim="init_method")
|
|
2386
|
+
ds_params = ds_params.assign_coords({"init_method": init_method})
|
|
2387
|
+
|
|
2388
|
+
# Add model attributes
|
|
2389
|
+
optimization_kwargs = {
|
|
2390
|
+
"init_method": init_method,
|
|
2391
|
+
"probability_method": "probability_method",
|
|
2392
|
+
"likelihood": likelihood,
|
|
2393
|
+
"truncated_likelihood": truncated_likelihood,
|
|
2394
|
+
"optimizer": optimizer,
|
|
2395
|
+
}
|
|
2396
|
+
ds_params = _finalize_attributes(
|
|
2397
|
+
ds_params=ds_params,
|
|
2398
|
+
psd_model=psd_model,
|
|
2399
|
+
optimization="ML",
|
|
2400
|
+
optimization_kwargs=optimization_kwargs,
|
|
2093
2401
|
)
|
|
2402
|
+
|
|
2094
2403
|
# Return dataset with parameters
|
|
2095
2404
|
return ds_params
|
|
2096
2405
|
|
|
@@ -2112,6 +2421,18 @@ def get_gs_parameters(ds, psd_model, target="ND", transformation="log", error_or
|
|
|
2112
2421
|
# Estimate parameters
|
|
2113
2422
|
ds_params = func(ds, target=target, transformation=transformation, error_order=error_order)
|
|
2114
2423
|
|
|
2424
|
+
# Add model attributes
|
|
2425
|
+
optimization_kwargs = {
|
|
2426
|
+
"target": target,
|
|
2427
|
+
"transformation": transformation,
|
|
2428
|
+
"error_order": error_order,
|
|
2429
|
+
}
|
|
2430
|
+
ds_params = _finalize_attributes(
|
|
2431
|
+
ds_params=ds_params,
|
|
2432
|
+
psd_model=psd_model,
|
|
2433
|
+
optimization="GS",
|
|
2434
|
+
optimization_kwargs=optimization_kwargs,
|
|
2435
|
+
)
|
|
2115
2436
|
# Return dataset with parameters
|
|
2116
2437
|
return ds_params
|
|
2117
2438
|
|
|
@@ -2120,9 +2441,10 @@ def estimate_model_parameters(
|
|
|
2120
2441
|
ds,
|
|
2121
2442
|
psd_model,
|
|
2122
2443
|
optimization,
|
|
2123
|
-
optimization_kwargs,
|
|
2444
|
+
optimization_kwargs=None,
|
|
2124
2445
|
):
|
|
2125
2446
|
"""Routine to estimate PSD model parameters."""
|
|
2447
|
+
optimization_kwargs = {} if optimization_kwargs is None else optimization_kwargs
|
|
2126
2448
|
optimization = check_optimization(optimization)
|
|
2127
2449
|
check_optimization_kwargs(optimization_kwargs=optimization_kwargs, optimization=optimization, psd_model=psd_model)
|
|
2128
2450
|
|
|
@@ -2137,10 +2459,7 @@ def estimate_model_parameters(
|
|
|
2137
2459
|
# Retrieve parameters
|
|
2138
2460
|
ds_params = func(ds, psd_model=psd_model, **optimization_kwargs)
|
|
2139
2461
|
|
|
2140
|
-
#
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
if optimization == "GS":
|
|
2144
|
-
ds_params.attrs["disdrodb_psd_optimization_target"] = optimization_kwargs["target"]
|
|
2145
|
-
|
|
2462
|
+
# Add parameters attributes (and units)
|
|
2463
|
+
for var, attrs in ATTRS_PARAMS_DICT[psd_model].items():
|
|
2464
|
+
ds_params[var].attrs = attrs
|
|
2146
2465
|
return ds_params
|