disdrodb 0.4.0__py3-none-any.whl → 0.5.1__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 +4 -0
- disdrodb/_version.py +2 -2
- disdrodb/accessor/methods.py +14 -0
- disdrodb/api/checks.py +8 -7
- disdrodb/api/io.py +81 -29
- disdrodb/api/path.py +17 -14
- disdrodb/api/search.py +15 -18
- disdrodb/cli/disdrodb_open_products_options.py +38 -0
- disdrodb/cli/disdrodb_run.py +2 -2
- disdrodb/cli/disdrodb_run_station.py +4 -4
- disdrodb/configs.py +1 -1
- disdrodb/data_transfer/download_data.py +70 -1
- disdrodb/etc/configs/attributes.yaml +62 -8
- disdrodb/etc/configs/encodings.yaml +28 -0
- disdrodb/etc/products/L2M/MODELS/GAMMA_GS_ND_SSE.yaml +8 -0
- disdrodb/etc/products/L2M/MODELS/GAMMA_ML.yaml +1 -1
- disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_LOG_ND_SSE.yaml +8 -0
- disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_ND_SSE.yaml +8 -0
- disdrodb/etc/products/L2M/MODELS/LOGNORMAL_ML.yaml +1 -1
- disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_LOG_ND_SSE.yaml +8 -0
- disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_ND_SSE.yaml +8 -0
- disdrodb/etc/products/L2M/global.yaml +4 -4
- disdrodb/fall_velocity/graupel.py +8 -8
- disdrodb/fall_velocity/hail.py +2 -2
- disdrodb/fall_velocity/rain.py +33 -5
- disdrodb/issue/checks.py +1 -1
- disdrodb/l0/l0_reader.py +1 -1
- disdrodb/l0/l0a_processing.py +2 -2
- disdrodb/l0/l0b_nc_processing.py +5 -5
- disdrodb/l0/l0b_processing.py +20 -24
- disdrodb/l0/l0c_processing.py +18 -13
- disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +4 -0
- disdrodb/l0/readers/PARSIVEL2/VIETNAM/IGE_PARSIVEL2.py +239 -0
- disdrodb/l0/template_tools.py +13 -13
- disdrodb/l1/classification.py +10 -6
- disdrodb/l2/empirical_dsd.py +25 -15
- disdrodb/l2/processing.py +32 -14
- disdrodb/metadata/download.py +1 -1
- disdrodb/metadata/geolocation.py +4 -4
- disdrodb/metadata/reader.py +3 -3
- disdrodb/metadata/search.py +10 -8
- disdrodb/psd/__init__.py +4 -0
- disdrodb/psd/fitting.py +2660 -592
- disdrodb/psd/gof_metrics.py +389 -0
- disdrodb/psd/grid_search.py +1066 -0
- disdrodb/psd/models.py +1281 -145
- disdrodb/routines/l2.py +6 -6
- disdrodb/routines/options_validation.py +8 -8
- disdrodb/scattering/axis_ratio.py +70 -2
- disdrodb/scattering/permittivity.py +13 -10
- disdrodb/scattering/routines.py +10 -10
- disdrodb/summary/routines.py +23 -20
- disdrodb/utils/archiving.py +29 -22
- disdrodb/utils/attrs.py +6 -4
- disdrodb/utils/dataframe.py +4 -4
- disdrodb/utils/encoding.py +3 -1
- disdrodb/utils/event.py +9 -9
- disdrodb/utils/logger.py +4 -7
- disdrodb/utils/manipulations.py +2 -2
- disdrodb/utils/subsetting.py +1 -1
- disdrodb/utils/time.py +8 -7
- disdrodb/viz/plots.py +25 -17
- {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/METADATA +44 -33
- {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/RECORD +68 -66
- {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/WHEEL +1 -1
- {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/entry_points.txt +1 -0
- disdrodb/etc/products/L2M/MODELS/GAMMA_GS_ND_MAE.yaml +0 -6
- disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_LOG_ND_MAE.yaml +0 -6
- disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_ND_MAE.yaml +0 -6
- disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_LOG_ND_MAE.yaml +0 -6
- disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_ND_MAE.yaml +0 -6
- disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_R_MAE.yaml +0 -6
- disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_Z_MAE.yaml +0 -6
- {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/top_level.txt +0 -0
disdrodb/psd/models.py
CHANGED
|
@@ -25,6 +25,7 @@ Source code:
|
|
|
25
25
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
+
import ast
|
|
28
29
|
import importlib
|
|
29
30
|
|
|
30
31
|
import dask.array
|
|
@@ -32,6 +33,7 @@ import numpy as np
|
|
|
32
33
|
import xarray as xr
|
|
33
34
|
from scipy.interpolate import PchipInterpolator, interp1d
|
|
34
35
|
from scipy.special import gamma as gamma_f
|
|
36
|
+
from scipy.special import gammaln
|
|
35
37
|
|
|
36
38
|
from disdrodb.constants import DIAMETER_DIMENSION
|
|
37
39
|
from disdrodb.utils.warnings import suppress_warnings
|
|
@@ -43,18 +45,45 @@ if importlib.util.find_spec("pytmatrix") is not None:
|
|
|
43
45
|
else:
|
|
44
46
|
|
|
45
47
|
class PSD:
|
|
46
|
-
"""Dummy.
|
|
48
|
+
"""Dummy PSD class placeholder when pytmatrix is not available.
|
|
49
|
+
|
|
50
|
+
This class serves as a placeholder when the pytmatrix library is not installed,
|
|
51
|
+
allowing the module to be imported without errors while maintaining the class
|
|
52
|
+
hierarchy for PSD models.
|
|
53
|
+
"""
|
|
47
54
|
|
|
48
55
|
pass
|
|
49
56
|
|
|
50
57
|
|
|
51
58
|
def available_psd_models():
|
|
52
|
-
"""Return a list of available PSD models.
|
|
59
|
+
"""Return a list of available PSD models.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
list of str
|
|
64
|
+
List of available PSD model names.
|
|
65
|
+
"""
|
|
53
66
|
return list(PSD_MODELS_DICT)
|
|
54
67
|
|
|
55
68
|
|
|
56
69
|
def check_psd_model(psd_model):
|
|
57
|
-
"""Check validity of a PSD model.
|
|
70
|
+
"""Check validity of a PSD model.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
psd_model : str
|
|
75
|
+
Name of the PSD model to validate.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
str
|
|
80
|
+
The validated PSD model name.
|
|
81
|
+
|
|
82
|
+
Raises
|
|
83
|
+
------
|
|
84
|
+
ValueError
|
|
85
|
+
If the PSD model is not valid.
|
|
86
|
+
"""
|
|
58
87
|
available_models = available_psd_models()
|
|
59
88
|
if psd_model not in available_models:
|
|
60
89
|
raise ValueError(f"{psd_model} is an invalid PSD model. Valid models are: {available_models}.")
|
|
@@ -62,7 +91,23 @@ def check_psd_model(psd_model):
|
|
|
62
91
|
|
|
63
92
|
|
|
64
93
|
def check_input_parameters(parameters):
|
|
65
|
-
"""Check
|
|
94
|
+
"""Check validity of input parameters.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
parameters : dict
|
|
99
|
+
Dictionary of PSD parameters to validate.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
dict
|
|
104
|
+
The validated parameters dictionary.
|
|
105
|
+
|
|
106
|
+
Raises
|
|
107
|
+
------
|
|
108
|
+
TypeError
|
|
109
|
+
If any parameter is not a scalar or xarray.DataArray.
|
|
110
|
+
"""
|
|
66
111
|
for param, value in parameters.items():
|
|
67
112
|
if not (is_scalar(value) or isinstance(value, xr.DataArray)):
|
|
68
113
|
raise TypeError(f"Parameter {param} must be a scalar or xarray.DataArray, not {type(value)}")
|
|
@@ -70,7 +115,25 @@ def check_input_parameters(parameters):
|
|
|
70
115
|
|
|
71
116
|
|
|
72
117
|
def check_diameter_inputs(D):
|
|
73
|
-
"""Check
|
|
118
|
+
"""Check validity of diameter input.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
D : int, float, array-like, or xarray.DataArray
|
|
123
|
+
Diameter values to validate [mm].
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
int, float, numpy.ndarray, dask.array.Array, or xarray.DataArray
|
|
128
|
+
The validated diameter input.
|
|
129
|
+
|
|
130
|
+
Raises
|
|
131
|
+
------
|
|
132
|
+
ValueError
|
|
133
|
+
If the diameter array is not 1-dimensional or is empty.
|
|
134
|
+
TypeError
|
|
135
|
+
If the diameter type is invalid.
|
|
136
|
+
"""
|
|
74
137
|
if isinstance(D, xr.DataArray) or is_scalar(D):
|
|
75
138
|
return D
|
|
76
139
|
if isinstance(D, (tuple, list)):
|
|
@@ -85,61 +148,285 @@ def check_diameter_inputs(D):
|
|
|
85
148
|
|
|
86
149
|
|
|
87
150
|
def get_psd_model(psd_model):
|
|
88
|
-
"""Retrieve the PSD
|
|
151
|
+
"""Retrieve the PSD class.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
psd_model : str
|
|
156
|
+
Name of the PSD model.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
type
|
|
161
|
+
The PSD class corresponding to the model name.
|
|
162
|
+
"""
|
|
89
163
|
return PSD_MODELS_DICT[psd_model]
|
|
90
164
|
|
|
91
165
|
|
|
92
166
|
def get_psd_model_formula(psd_model):
|
|
93
|
-
"""Retrieve the PSD formula.
|
|
167
|
+
"""Retrieve the PSD formula function.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
psd_model : str
|
|
172
|
+
Name of the PSD model.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
callable
|
|
177
|
+
The static formula method of the PSD class.
|
|
178
|
+
"""
|
|
94
179
|
return PSD_MODELS_DICT[psd_model].formula
|
|
95
180
|
|
|
96
181
|
|
|
97
182
|
def create_psd(psd_model, parameters):
|
|
98
|
-
"""
|
|
183
|
+
"""Create a PSD instance from model name and parameters.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
psd_model : str
|
|
188
|
+
Name of the PSD model.
|
|
189
|
+
parameters : dict or xarray.Dataset
|
|
190
|
+
Dictionary or Dataset containing the PSD parameters.
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
XarrayPSD
|
|
195
|
+
An instance of the specified PSD model initialized with the given parameters.
|
|
196
|
+
"""
|
|
99
197
|
psd_class = get_psd_model(psd_model)
|
|
100
198
|
psd = psd_class.from_parameters(parameters)
|
|
101
199
|
return psd
|
|
102
200
|
|
|
103
201
|
|
|
104
202
|
def get_required_parameters(psd_model):
|
|
105
|
-
"""Retrieve the list of parameters required by a PSD model.
|
|
203
|
+
"""Retrieve the list of parameters required by a PSD model.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
psd_model : str
|
|
208
|
+
Name of the PSD model.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
list of str
|
|
213
|
+
List of required parameter names for the specified PSD model.
|
|
214
|
+
"""
|
|
106
215
|
psd_class = get_psd_model(psd_model)
|
|
107
216
|
return psd_class.required_parameters()
|
|
108
217
|
|
|
109
218
|
|
|
219
|
+
def create_psd_from_dataset(ds_params):
|
|
220
|
+
"""Create a PSD instance from a DISDRODB L2M product.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
ds_params : xarray.Dataset
|
|
225
|
+
DISDRODB L2M dataset containing PSD parameters and metadata.
|
|
226
|
+
Must have 'disdrodb_psd_model' attribute.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
XarrayPSD
|
|
231
|
+
An instance of the PSD model specified in the dataset attributes.
|
|
232
|
+
|
|
233
|
+
Raises
|
|
234
|
+
------
|
|
235
|
+
ValueError
|
|
236
|
+
If the dataset does not contain 'disdrodb_psd_model' attribute.
|
|
237
|
+
"""
|
|
238
|
+
if "disdrodb_psd_model" not in ds_params.attrs:
|
|
239
|
+
raise ValueError("Expecting a DISDRODB L2M product with attribute 'disdrodb_psd_model'.")
|
|
240
|
+
return create_psd(ds_params.attrs["disdrodb_psd_model"], ds_params)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def get_parameters_from_dataset(ds):
|
|
244
|
+
"""Extract PSD parameters from DISDRODB L2M dataset.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
ds : xarray.Dataset
|
|
249
|
+
DISDRODB L2M dataset containing PSD parameters.
|
|
250
|
+
Must have 'disdrodb_psd_model' attribute.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
xarray.Dataset
|
|
255
|
+
Dataset containing only the PSD parameter variables.
|
|
256
|
+
|
|
257
|
+
Raises
|
|
258
|
+
------
|
|
259
|
+
ValueError
|
|
260
|
+
If the dataset does not contain 'disdrodb_psd_model' attribute.
|
|
261
|
+
"""
|
|
262
|
+
if "disdrodb_psd_model" not in ds.attrs:
|
|
263
|
+
raise ValueError("Expecting a DISDRODB L2M product with attribute 'disdrodb_psd_model'.")
|
|
264
|
+
psd_model = ds.attrs["disdrodb_psd_model"]
|
|
265
|
+
# Retrieve psd parameters list
|
|
266
|
+
required_parameters = get_required_parameters(psd_model)
|
|
267
|
+
required_parameters = set(required_parameters) - {"i", "j"}
|
|
268
|
+
return ds[required_parameters]
|
|
269
|
+
|
|
270
|
+
|
|
110
271
|
def is_scalar(value):
|
|
111
|
-
"""
|
|
272
|
+
"""Determine if the input value is a scalar.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
value : any
|
|
277
|
+
Value to check.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
bool
|
|
282
|
+
True if the value is a scalar, False otherwise.
|
|
283
|
+
|
|
284
|
+
Notes
|
|
285
|
+
-----
|
|
286
|
+
A value is considered scalar if it is an int, float, or a numpy/xarray
|
|
287
|
+
array with exactly one element.
|
|
288
|
+
"""
|
|
112
289
|
return isinstance(value, (float, int)) or (isinstance(value, (np.ndarray, xr.DataArray)) and value.size == 1)
|
|
113
290
|
|
|
114
291
|
|
|
292
|
+
def compute_Nc(i, j, Mi, Mj):
|
|
293
|
+
r"""Compute double moment normalization intercept parameter N_c.
|
|
294
|
+
|
|
295
|
+
The normalized intercept parameter is calculated as:
|
|
296
|
+
|
|
297
|
+
.. math::
|
|
298
|
+
|
|
299
|
+
N_c = M_i^{\frac{j + 1}{j - i}} M_j^{\frac{i + 1}{i - j}}
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
i : float or array-like
|
|
304
|
+
Moment index i
|
|
305
|
+
j : float or array-like
|
|
306
|
+
Moment index j
|
|
307
|
+
Mi : float or array-like
|
|
308
|
+
Moment parameter Mi (Γ_i)
|
|
309
|
+
Mj : float or array-like
|
|
310
|
+
Moment parameter Mj (Γ_j)
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
float or array-like
|
|
315
|
+
The normalized intercept parameter N_c with units m-3 mm-1.
|
|
316
|
+
"""
|
|
317
|
+
exponent_i = (j + 1) / (j - i)
|
|
318
|
+
exponent_j = (i + 1) / (i - j)
|
|
319
|
+
return (Mi**exponent_i) * (Mj**exponent_j)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def compute_Dc(i, j, Mi, Mj):
|
|
323
|
+
r"""Compute double moment normalization characteristic diameter D_c.
|
|
324
|
+
|
|
325
|
+
The characteristic diameter is calculated as:
|
|
326
|
+
|
|
327
|
+
.. math::
|
|
328
|
+
|
|
329
|
+
D_c = \left(\frac{M_j}{M_i}\right)^{\frac{1}{j - i}}
|
|
330
|
+
|
|
331
|
+
Parameters
|
|
332
|
+
----------
|
|
333
|
+
i : float or array-like
|
|
334
|
+
Moment index i
|
|
335
|
+
j : float or array-like
|
|
336
|
+
Moment index j
|
|
337
|
+
Mi : float or array-like
|
|
338
|
+
Moment parameter Mi (Γ_i)
|
|
339
|
+
Mj : float or array-like
|
|
340
|
+
Moment parameter Mj (Γ_j)
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
float or array-like
|
|
345
|
+
The characteristic diameter parameter D_c with units mm.
|
|
346
|
+
"""
|
|
347
|
+
exponent = 1.0 / (j - i)
|
|
348
|
+
return (Mj / Mi) ** exponent
|
|
349
|
+
|
|
350
|
+
|
|
115
351
|
class XarrayPSD(PSD):
|
|
116
352
|
"""PSD class template allowing vectorized computations with xarray.
|
|
117
353
|
|
|
118
|
-
|
|
119
|
-
|
|
354
|
+
This class serves as a base template for Particle Size Distribution (PSD) models
|
|
355
|
+
that support vectorized computations with xarray.DataArray objects. It extends
|
|
356
|
+
the pytmatrix PSD class to maintain compatibility with scattering simulations.
|
|
357
|
+
|
|
358
|
+
Notes
|
|
359
|
+
-----
|
|
360
|
+
This class inherits from pytmatrix PSD to enable scattering simulations.
|
|
361
|
+
See: https://github.com/ltelab/pytmatrix-lte/blob/880170b4ca62a04e8c843619fa1b8713b9e11894/pytmatrix/psd.py#L321
|
|
362
|
+
|
|
363
|
+
The class supports both scalar and xarray.DataArray parameters, enabling
|
|
364
|
+
efficient vectorized operations across multiple dimensions.
|
|
120
365
|
"""
|
|
121
366
|
|
|
122
|
-
def __call__(self, D):
|
|
123
|
-
"""Compute the PSD.
|
|
367
|
+
def __call__(self, D, zero_below=1e-3):
|
|
368
|
+
"""Compute the PSD values for given diameters.
|
|
369
|
+
|
|
370
|
+
Parameters
|
|
371
|
+
----------
|
|
372
|
+
D : scalar, array-like, or xarray.DataArray
|
|
373
|
+
Particle diameter(s) [mm].
|
|
374
|
+
zero_below : float, optional
|
|
375
|
+
Threshold below which PSD values are set to zero.
|
|
376
|
+
Default is 1e-3.
|
|
377
|
+
|
|
378
|
+
Returns
|
|
379
|
+
-------
|
|
380
|
+
scalar, numpy.ndarray, or xarray.DataArray
|
|
381
|
+
PSD values N(D) [m^-3 mm^-1] corresponding to the input diameter(s).
|
|
382
|
+
"""
|
|
124
383
|
D = check_diameter_inputs(D)
|
|
125
384
|
if self.has_xarray_parameters() and not np.isscalar(D):
|
|
126
385
|
D = xr.DataArray(D, dims=DIAMETER_DIMENSION)
|
|
127
386
|
with suppress_warnings():
|
|
128
|
-
|
|
387
|
+
nd = self.formula(D=D, **self.parameters)
|
|
388
|
+
|
|
389
|
+
# Clip values to ensure non-negative PSD (and set values < zero_below to 0)
|
|
390
|
+
nd = nd.where(nd >= zero_below, 0) if isinstance(nd, xr.DataArray) else np.where(nd < zero_below, 0, nd)
|
|
391
|
+
return nd
|
|
129
392
|
|
|
130
393
|
def has_scalar_parameters(self):
|
|
131
|
-
"""Check if the PSD object contains only
|
|
394
|
+
"""Check if the PSD object contains only scalar parameters.
|
|
395
|
+
|
|
396
|
+
Returns
|
|
397
|
+
-------
|
|
398
|
+
bool
|
|
399
|
+
True if all parameters are scalars, False otherwise.
|
|
400
|
+
"""
|
|
132
401
|
return np.all([is_scalar(value) for value in self.parameters.values()])
|
|
133
402
|
|
|
134
403
|
def has_xarray_parameters(self):
|
|
135
|
-
"""Check if the PSD object contains at least one xarray parameter.
|
|
404
|
+
"""Check if the PSD object contains at least one xarray parameter.
|
|
405
|
+
|
|
406
|
+
Returns
|
|
407
|
+
-------
|
|
408
|
+
bool
|
|
409
|
+
True if at least one parameter is an xarray.DataArray, False otherwise.
|
|
410
|
+
"""
|
|
136
411
|
return any(isinstance(value, xr.DataArray) for param, value in self.parameters.items())
|
|
137
412
|
|
|
138
413
|
def isel(self, **kwargs):
|
|
139
414
|
"""Subset the parameters by index using xarray.isel.
|
|
140
415
|
|
|
141
|
-
|
|
142
|
-
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
**kwargs : dict
|
|
419
|
+
Indexing arguments passed to xarray.DataArray.isel().
|
|
420
|
+
|
|
421
|
+
Returns
|
|
422
|
+
-------
|
|
423
|
+
XarrayPSD
|
|
424
|
+
A new PSD instance with subset parameters.
|
|
425
|
+
|
|
426
|
+
Raises
|
|
427
|
+
------
|
|
428
|
+
ValueError
|
|
429
|
+
If the PSD does not have xarray parameters.
|
|
143
430
|
"""
|
|
144
431
|
if not self.has_xarray_parameters():
|
|
145
432
|
raise ValueError("isel() can only be used when PSD model parameters are xarray DataArrays")
|
|
@@ -158,8 +445,20 @@ class XarrayPSD(PSD):
|
|
|
158
445
|
def sel(self, **kwargs):
|
|
159
446
|
"""Subset the parameters by label using xarray.sel.
|
|
160
447
|
|
|
161
|
-
|
|
162
|
-
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
**kwargs : dict
|
|
451
|
+
Indexing arguments passed to xarray.DataArray.sel().
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
XarrayPSD
|
|
456
|
+
A new PSD instance with subset parameters.
|
|
457
|
+
|
|
458
|
+
Raises
|
|
459
|
+
------
|
|
460
|
+
ValueError
|
|
461
|
+
If the PSD does not have xarray parameters.
|
|
163
462
|
"""
|
|
164
463
|
if not self.has_xarray_parameters():
|
|
165
464
|
raise ValueError("sel() can only be used when PSD model parameters are xarray DataArrays")
|
|
@@ -176,7 +475,18 @@ class XarrayPSD(PSD):
|
|
|
176
475
|
return self.__class__.from_parameters(new_params)
|
|
177
476
|
|
|
178
477
|
def __eq__(self, other):
|
|
179
|
-
"""Check if two objects are equal.
|
|
478
|
+
"""Check if two PSD objects are equal.
|
|
479
|
+
|
|
480
|
+
Parameters
|
|
481
|
+
----------
|
|
482
|
+
other : XarrayPSD
|
|
483
|
+
Another PSD object to compare with.
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
bool
|
|
488
|
+
True if the objects have the same class and parameter values, False otherwise.
|
|
489
|
+
"""
|
|
180
490
|
# Check class equality
|
|
181
491
|
if not isinstance(other, self.__class__):
|
|
182
492
|
return False
|
|
@@ -214,25 +524,44 @@ class XarrayPSD(PSD):
|
|
|
214
524
|
|
|
215
525
|
|
|
216
526
|
class LognormalPSD(XarrayPSD):
|
|
217
|
-
"""Lognormal
|
|
527
|
+
r"""Lognormal particle size distribution (PSD).
|
|
218
528
|
|
|
219
|
-
|
|
529
|
+
This class implements a lognormal PSD model, which is commonly used to
|
|
530
|
+
describe particle size distributions in atmospheric sciences.
|
|
220
531
|
|
|
221
|
-
The PSD
|
|
532
|
+
The PSD is defined by the formula:
|
|
222
533
|
|
|
223
|
-
|
|
534
|
+
.. math::
|
|
224
535
|
|
|
225
|
-
|
|
226
|
-
# theta = 0
|
|
536
|
+
N(D) = \frac{N_t}{\sqrt{2\pi} \sigma D} \exp\left(-\frac{(\ln(D) - \mu)^2}{2\sigma^2}\right)
|
|
227
537
|
|
|
228
|
-
|
|
538
|
+
Parameters
|
|
229
539
|
----------
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
540
|
+
Nt : float or xarray.DataArray, optional
|
|
541
|
+
Total concentration parameter [m^-3].
|
|
542
|
+
Default is 1.0.
|
|
543
|
+
mu : float or xarray.DataArray, optional
|
|
544
|
+
Location parameter of the underlying normal distribution [-].
|
|
545
|
+
Default is 0.0.
|
|
546
|
+
sigma : float or xarray.DataArray, optional
|
|
547
|
+
Scale parameter (standard deviation) of the underlying normal distribution [-].
|
|
548
|
+
Default is 1.0.
|
|
235
549
|
|
|
550
|
+
Attributes
|
|
551
|
+
----------
|
|
552
|
+
Nt : float or xarray.DataArray
|
|
553
|
+
Total concentration parameter.
|
|
554
|
+
mu : float or xarray.DataArray
|
|
555
|
+
Location parameter.
|
|
556
|
+
sigma : float or xarray.DataArray
|
|
557
|
+
Scale parameter.
|
|
558
|
+
parameters : dict
|
|
559
|
+
Dictionary containing all PSD parameters.
|
|
560
|
+
|
|
561
|
+
Notes
|
|
562
|
+
-----
|
|
563
|
+
The lognormal distribution is characterized by the fact that the logarithm
|
|
564
|
+
of the variable follows a normal distribution.
|
|
236
565
|
"""
|
|
237
566
|
|
|
238
567
|
def __init__(self, Nt=1.0, mu=0.0, sigma=1.0):
|
|
@@ -249,20 +578,40 @@ class LognormalPSD(XarrayPSD):
|
|
|
249
578
|
|
|
250
579
|
@staticmethod
|
|
251
580
|
def formula(D, Nt, mu, sigma):
|
|
252
|
-
"""
|
|
581
|
+
"""Calculate the Lognormal PSD values.
|
|
582
|
+
|
|
583
|
+
Parameters
|
|
584
|
+
----------
|
|
585
|
+
D : array-like
|
|
586
|
+
Particle diameter [mm].
|
|
587
|
+
Nt : float or array-like
|
|
588
|
+
Total concentration parameter [m^-3].
|
|
589
|
+
mu : float or array-like
|
|
590
|
+
Location parameter [-].
|
|
591
|
+
sigma : float or array-like
|
|
592
|
+
Scale parameter [-].
|
|
593
|
+
|
|
594
|
+
Returns
|
|
595
|
+
-------
|
|
596
|
+
array-like
|
|
597
|
+
PSD values N(D) [m^-3 mm^-1].
|
|
598
|
+
"""
|
|
253
599
|
coeff = Nt / (np.sqrt(2.0 * np.pi) * sigma * (D))
|
|
254
600
|
return coeff * np.exp(-((np.log(D) - mu) ** 2) / (2.0 * sigma**2))
|
|
255
601
|
|
|
256
602
|
@staticmethod
|
|
257
603
|
def from_parameters(parameters):
|
|
258
|
-
"""Initialize LognormalPSD from a dictionary or
|
|
604
|
+
"""Initialize LognormalPSD from a dictionary or xarray.Dataset.
|
|
259
605
|
|
|
260
|
-
|
|
261
|
-
|
|
606
|
+
Parameters
|
|
607
|
+
----------
|
|
608
|
+
parameters : dict or xarray.Dataset
|
|
609
|
+
Parameters to initialize the class. Must contain 'Nt', 'mu', and 'sigma'.
|
|
262
610
|
|
|
263
611
|
Returns
|
|
264
612
|
-------
|
|
265
|
-
|
|
613
|
+
LognormalPSD
|
|
614
|
+
An instance of LognormalPSD initialized with the parameters.
|
|
266
615
|
"""
|
|
267
616
|
Nt = parameters["Nt"]
|
|
268
617
|
mu = parameters["mu"]
|
|
@@ -271,7 +620,13 @@ class LognormalPSD(XarrayPSD):
|
|
|
271
620
|
|
|
272
621
|
@staticmethod
|
|
273
622
|
def required_parameters():
|
|
274
|
-
"""Return the required parameters of the PSD.
|
|
623
|
+
"""Return the required parameters of the PSD.
|
|
624
|
+
|
|
625
|
+
Returns
|
|
626
|
+
-------
|
|
627
|
+
list of str
|
|
628
|
+
List of required parameter names.
|
|
629
|
+
"""
|
|
275
630
|
return ["Nt", "mu", "sigma"]
|
|
276
631
|
|
|
277
632
|
def parameters_summary(self):
|
|
@@ -290,25 +645,39 @@ class LognormalPSD(XarrayPSD):
|
|
|
290
645
|
|
|
291
646
|
|
|
292
647
|
class ExponentialPSD(XarrayPSD):
|
|
293
|
-
"""Exponential particle size distribution (PSD).
|
|
648
|
+
r"""Exponential particle size distribution (PSD).
|
|
294
649
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
constructor.
|
|
650
|
+
This class implements an exponential PSD model, which is one of the simplest
|
|
651
|
+
forms used to describe particle size distributions.
|
|
298
652
|
|
|
299
|
-
The PSD
|
|
300
|
-
N(D) = N0 * exp(-Lambda*D)
|
|
653
|
+
The PSD is defined by the formula:
|
|
301
654
|
|
|
302
|
-
|
|
303
|
-
----------
|
|
304
|
-
N0: the intercept parameter.
|
|
305
|
-
Lambda: the inverse scale parameter
|
|
655
|
+
.. math::
|
|
306
656
|
|
|
307
|
-
|
|
308
|
-
D: the particle diameter in millimeter.
|
|
657
|
+
N(D) = N_0 \exp(-\Lambda D)
|
|
309
658
|
|
|
310
|
-
|
|
311
|
-
|
|
659
|
+
Parameters
|
|
660
|
+
----------
|
|
661
|
+
N0 : float or xarray.DataArray, optional
|
|
662
|
+
Intercept parameter [m^-3 mm^-1].
|
|
663
|
+
Default is 1.0.
|
|
664
|
+
Lambda : float or xarray.DataArray, optional
|
|
665
|
+
Inverse scale parameter (slope parameter) [mm^-1].
|
|
666
|
+
Default is 1.0.
|
|
667
|
+
|
|
668
|
+
Attributes
|
|
669
|
+
----------
|
|
670
|
+
N0 : float or xarray.DataArray
|
|
671
|
+
Intercept parameter.
|
|
672
|
+
Lambda : float or xarray.DataArray
|
|
673
|
+
Inverse scale parameter.
|
|
674
|
+
parameters : dict
|
|
675
|
+
Dictionary containing all PSD parameters.
|
|
676
|
+
|
|
677
|
+
Notes
|
|
678
|
+
-----
|
|
679
|
+
The exponential distribution is a special case of the gamma distribution
|
|
680
|
+
with shape parameter mu = 0.
|
|
312
681
|
"""
|
|
313
682
|
|
|
314
683
|
def __init__(self, N0=1.0, Lambda=1.0):
|
|
@@ -325,19 +694,37 @@ class ExponentialPSD(XarrayPSD):
|
|
|
325
694
|
|
|
326
695
|
@staticmethod
|
|
327
696
|
def formula(D, N0, Lambda):
|
|
328
|
-
"""
|
|
697
|
+
"""Calculate the Exponential PSD values.
|
|
698
|
+
|
|
699
|
+
Parameters
|
|
700
|
+
----------
|
|
701
|
+
D : array-like
|
|
702
|
+
Particle diameter [mm].
|
|
703
|
+
N0 : float or array-like
|
|
704
|
+
Intercept parameter [m^-3 mm^-1].
|
|
705
|
+
Lambda : float or array-like
|
|
706
|
+
Inverse scale parameter [mm^-1].
|
|
707
|
+
|
|
708
|
+
Returns
|
|
709
|
+
-------
|
|
710
|
+
array-like
|
|
711
|
+
PSD values N(D) [m^-3 mm^-1].
|
|
712
|
+
"""
|
|
329
713
|
return N0 * np.exp(-Lambda * D)
|
|
330
714
|
|
|
331
715
|
@staticmethod
|
|
332
716
|
def from_parameters(parameters):
|
|
333
|
-
"""Initialize ExponentialPSD from a dictionary or
|
|
717
|
+
"""Initialize ExponentialPSD from a dictionary or xarray.Dataset.
|
|
334
718
|
|
|
335
|
-
|
|
336
|
-
|
|
719
|
+
Parameters
|
|
720
|
+
----------
|
|
721
|
+
parameters : dict or xarray.Dataset
|
|
722
|
+
Parameters to initialize the class. Must contain 'N0' and 'Lambda'.
|
|
337
723
|
|
|
338
724
|
Returns
|
|
339
725
|
-------
|
|
340
|
-
|
|
726
|
+
ExponentialPSD
|
|
727
|
+
An instance of ExponentialPSD initialized with the parameters.
|
|
341
728
|
"""
|
|
342
729
|
N0 = parameters["N0"]
|
|
343
730
|
Lambda = parameters["Lambda"]
|
|
@@ -345,11 +732,23 @@ class ExponentialPSD(XarrayPSD):
|
|
|
345
732
|
|
|
346
733
|
@staticmethod
|
|
347
734
|
def required_parameters():
|
|
348
|
-
"""Return the required parameters of the PSD.
|
|
735
|
+
"""Return the required parameters of the PSD.
|
|
736
|
+
|
|
737
|
+
Returns
|
|
738
|
+
-------
|
|
739
|
+
list of str
|
|
740
|
+
List of required parameter names.
|
|
741
|
+
"""
|
|
349
742
|
return ["N0", "Lambda"]
|
|
350
743
|
|
|
351
744
|
def parameters_summary(self):
|
|
352
|
-
"""Return a string with the parameter summary.
|
|
745
|
+
"""Return a string with the parameter summary.
|
|
746
|
+
|
|
747
|
+
Returns
|
|
748
|
+
-------
|
|
749
|
+
str
|
|
750
|
+
Formatted string summarizing the PSD parameters.
|
|
751
|
+
"""
|
|
353
752
|
if self.has_scalar_parameters():
|
|
354
753
|
summary = "".join(
|
|
355
754
|
[
|
|
@@ -364,32 +763,53 @@ class ExponentialPSD(XarrayPSD):
|
|
|
364
763
|
|
|
365
764
|
|
|
366
765
|
class GammaPSD(ExponentialPSD):
|
|
367
|
-
"""Gamma particle size distribution (PSD).
|
|
766
|
+
r"""Gamma particle size distribution (PSD).
|
|
368
767
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
768
|
+
This class implements a gamma PSD model, which is widely used to describe
|
|
769
|
+
raindrop size distributions and other particle size distributions in
|
|
770
|
+
atmospheric sciences.
|
|
372
771
|
|
|
373
|
-
The PSD
|
|
374
|
-
N(D) = N0 * D**mu * exp(-Lambda*D)
|
|
772
|
+
The PSD is defined by the formula:
|
|
375
773
|
|
|
376
|
-
|
|
377
|
-
----------
|
|
378
|
-
N0: the intercept parameter [mm**(-1-mu) m**-3] (scale parameter)
|
|
379
|
-
Lambda: the inverse scale parameter [mm-1] (slope parameter)
|
|
380
|
-
mu: the shape parameter [-]
|
|
774
|
+
.. math::
|
|
381
775
|
|
|
382
|
-
|
|
383
|
-
D: the particle diameter in millimeter.
|
|
776
|
+
N(D) = N_0 D^{\mu} \exp(-\Lambda D)
|
|
384
777
|
|
|
385
|
-
|
|
386
|
-
|
|
778
|
+
Parameters
|
|
779
|
+
----------
|
|
780
|
+
N0 : float or xarray.DataArray, optional
|
|
781
|
+
Intercept parameter (scale parameter) [m^-3 mm^(-1-mu)].
|
|
782
|
+
Default is 1.0.
|
|
783
|
+
mu : float or xarray.DataArray, optional
|
|
784
|
+
Shape parameter [-].
|
|
785
|
+
Default is 0.0.
|
|
786
|
+
Lambda : float or xarray.DataArray, optional
|
|
787
|
+
Inverse scale parameter (slope parameter) [mm^-1].
|
|
788
|
+
Default is 1.0.
|
|
789
|
+
|
|
790
|
+
Attributes
|
|
791
|
+
----------
|
|
792
|
+
N0 : float or xarray.DataArray
|
|
793
|
+
Intercept parameter.
|
|
794
|
+
mu : float or xarray.DataArray
|
|
795
|
+
Shape parameter.
|
|
796
|
+
Lambda : float or xarray.DataArray
|
|
797
|
+
Inverse scale parameter.
|
|
798
|
+
parameters : dict
|
|
799
|
+
Dictionary containing all PSD parameters.
|
|
800
|
+
|
|
801
|
+
Notes
|
|
802
|
+
-----
|
|
803
|
+
The gamma distribution reduces to the exponential distribution when mu = 0.
|
|
804
|
+
This formulation is particularly useful for representing natural variations
|
|
805
|
+
in raindrop size distributions.
|
|
387
806
|
|
|
388
807
|
References
|
|
389
808
|
----------
|
|
390
|
-
Ulbrich, C. W.,
|
|
391
|
-
|
|
392
|
-
J. Appl. Meteor. Climatol.,
|
|
809
|
+
Ulbrich, C. W., 1983.
|
|
810
|
+
Natural Variations in the Analytical Form of the Raindrop Size Distribution.
|
|
811
|
+
J. Appl. Meteor. Climatol., 22, 1764-1775,
|
|
812
|
+
https://doi.org/10.1175/1520-0450(1983)022<1764:NVITAF>2.0.CO;2
|
|
393
813
|
"""
|
|
394
814
|
|
|
395
815
|
def __init__(self, N0=1.0, mu=0.0, Lambda=1.0):
|
|
@@ -407,19 +827,39 @@ class GammaPSD(ExponentialPSD):
|
|
|
407
827
|
|
|
408
828
|
@staticmethod
|
|
409
829
|
def formula(D, N0, Lambda, mu):
|
|
410
|
-
"""
|
|
830
|
+
"""Calculate the Gamma PSD values.
|
|
831
|
+
|
|
832
|
+
Parameters
|
|
833
|
+
----------
|
|
834
|
+
D : array-like
|
|
835
|
+
Particle diameter [mm].
|
|
836
|
+
N0 : float or array-like
|
|
837
|
+
Intercept parameter [m^-3 mm^(-1-mu)].
|
|
838
|
+
Lambda : float or array-like
|
|
839
|
+
Inverse scale parameter [mm^-1].
|
|
840
|
+
mu : float or array-like
|
|
841
|
+
Shape parameter [-].
|
|
842
|
+
|
|
843
|
+
Returns
|
|
844
|
+
-------
|
|
845
|
+
array-like
|
|
846
|
+
PSD values N(D) [m^-3 mm^-1].
|
|
847
|
+
"""
|
|
411
848
|
return N0 * np.exp(mu * np.log(D) - Lambda * D)
|
|
412
849
|
|
|
413
850
|
@staticmethod
|
|
414
851
|
def from_parameters(parameters):
|
|
415
|
-
"""Initialize GammaPSD from a dictionary or
|
|
852
|
+
"""Initialize GammaPSD from a dictionary or xarray.Dataset.
|
|
416
853
|
|
|
417
|
-
|
|
418
|
-
|
|
854
|
+
Parameters
|
|
855
|
+
----------
|
|
856
|
+
parameters : dict or xarray.Dataset
|
|
857
|
+
Parameters to initialize the class. Must contain 'N0', 'Lambda', and 'mu'.
|
|
419
858
|
|
|
420
859
|
Returns
|
|
421
860
|
-------
|
|
422
|
-
|
|
861
|
+
GammaPSD
|
|
862
|
+
An instance of GammaPSD initialized with the parameters.
|
|
423
863
|
"""
|
|
424
864
|
N0 = parameters["N0"]
|
|
425
865
|
Lambda = parameters["Lambda"]
|
|
@@ -428,11 +868,23 @@ class GammaPSD(ExponentialPSD):
|
|
|
428
868
|
|
|
429
869
|
@staticmethod
|
|
430
870
|
def required_parameters():
|
|
431
|
-
"""Return the required parameters of the PSD.
|
|
871
|
+
"""Return the required parameters of the PSD.
|
|
872
|
+
|
|
873
|
+
Returns
|
|
874
|
+
-------
|
|
875
|
+
list of str
|
|
876
|
+
List of required parameter names.
|
|
877
|
+
"""
|
|
432
878
|
return ["N0", "mu", "Lambda"]
|
|
433
879
|
|
|
434
880
|
def parameters_summary(self):
|
|
435
|
-
"""Return a string with the parameter summary.
|
|
881
|
+
"""Return a string with the parameter summary.
|
|
882
|
+
|
|
883
|
+
Returns
|
|
884
|
+
-------
|
|
885
|
+
str
|
|
886
|
+
Formatted string summarizing the PSD parameters.
|
|
887
|
+
"""
|
|
436
888
|
if self.has_scalar_parameters():
|
|
437
889
|
summary = "".join(
|
|
438
890
|
[
|
|
@@ -446,45 +898,121 @@ class GammaPSD(ExponentialPSD):
|
|
|
446
898
|
summary = "" f"{self.name} with N-d parameters \n"
|
|
447
899
|
return summary
|
|
448
900
|
|
|
901
|
+
@staticmethod
|
|
902
|
+
def compute_Dm(mu, Lambda):
|
|
903
|
+
"""Compute mass-weighted mean diameter from PSD parameters.
|
|
904
|
+
|
|
905
|
+
Parameters
|
|
906
|
+
----------
|
|
907
|
+
mu : float or array-like
|
|
908
|
+
Shape parameter [-].
|
|
909
|
+
Lambda : float or array-like
|
|
910
|
+
Inverse scale parameter [mm^-1].
|
|
911
|
+
|
|
912
|
+
Returns
|
|
913
|
+
-------
|
|
914
|
+
float or array-like
|
|
915
|
+
Mass-weighted mean diameter Dm [mm].
|
|
916
|
+
"""
|
|
917
|
+
return (mu + 4) / Lambda
|
|
918
|
+
|
|
919
|
+
@staticmethod
|
|
920
|
+
def compute_sigma_m(mu, Lambda):
|
|
921
|
+
"""Compute standard deviation of mass-weighted distribution.
|
|
922
|
+
|
|
923
|
+
Parameters
|
|
924
|
+
----------
|
|
925
|
+
mu : float or array-like
|
|
926
|
+
Shape parameter [-].
|
|
927
|
+
Lambda : float or array-like
|
|
928
|
+
Inverse scale parameter [mm^-1].
|
|
929
|
+
|
|
930
|
+
Returns
|
|
931
|
+
-------
|
|
932
|
+
float or array-like
|
|
933
|
+
Standard deviation sigma_m [mm].
|
|
934
|
+
"""
|
|
935
|
+
return (mu + 4) ** 0.5 / Lambda
|
|
936
|
+
|
|
449
937
|
|
|
450
938
|
class NormalizedGammaPSD(XarrayPSD):
|
|
451
|
-
"""Normalized gamma particle size distribution (PSD).
|
|
939
|
+
r"""Normalized gamma particle size distribution (PSD).
|
|
940
|
+
|
|
941
|
+
Callable class implementing a normalized gamma particle size distribution
|
|
942
|
+
parameterized by a characteristic diameter and shape parameter. The PSD
|
|
943
|
+
can be evaluated by calling the instance with particle diameters.
|
|
944
|
+
|
|
945
|
+
Notes
|
|
946
|
+
-----
|
|
947
|
+
The normalized gamma PSD is defined as:
|
|
948
|
+
|
|
949
|
+
.. math::
|
|
950
|
+
|
|
951
|
+
N(D) = N_w \ f(\mu) \left( \frac{D}{D_{50}} \right)^{\mu} \exp\!\left[-(\mu + 3.67)\frac{D}{D_{50}}\right]
|
|
952
|
+
|
|
953
|
+
with
|
|
954
|
+
|
|
955
|
+
.. math::
|
|
956
|
+
|
|
957
|
+
f(\mu) = \frac{6}{3.67^4} \frac{(\mu + 3.67)^{\mu + 4}}{\Gamma(\mu + 4)}
|
|
452
958
|
|
|
453
|
-
|
|
454
|
-
parameters. The attributes can also be given as arguments to the
|
|
455
|
-
constructor.
|
|
959
|
+
where:
|
|
456
960
|
|
|
457
|
-
|
|
961
|
+
- :math:`D` is the particle diameter,
|
|
962
|
+
- :math:`D_{50}` is the median volume diameter,
|
|
963
|
+
- :math:`N_w` is the intercept parameter,
|
|
964
|
+
- :math:`\mu` is the shape parameter,
|
|
965
|
+
- :math:`\Gamma(\cdot)` denotes the gamma function.
|
|
458
966
|
|
|
459
|
-
|
|
460
|
-
|
|
967
|
+
Alternative formulation using the mass-weighted mean diameter :math:`D_m`
|
|
968
|
+
(Testud et al., 2001; Bringi et al., 2001; Williams et al., 2014; Dolan et al., 2018):
|
|
461
969
|
|
|
462
|
-
|
|
463
|
-
# Testud (2001), Bringi (2001), Williams et al., 2014, Dolan 2018
|
|
464
|
-
# --> Normalized with respect to liquid water content (mass) --> Nx=D3/Dm4
|
|
465
|
-
N(D) = Nw * f1(mu) * (D/Dm)**mu * exp(-(mu+4)*D/Dm) # Nw * f(D; Dm, mu)
|
|
466
|
-
f1(mu) = 6/(4**4) * (mu+4)**(mu+4)/Gamma(mu+4)
|
|
970
|
+
.. math::
|
|
467
971
|
|
|
468
|
-
|
|
972
|
+
N(D) = N_w \, f_1(\mu) \left( \frac{D}{D_m} \right)^{\mu} \exp\!\left[-(\mu + 4)\frac{D}{D_m}\right]
|
|
469
973
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
974
|
+
with
|
|
975
|
+
|
|
976
|
+
.. math::
|
|
977
|
+
|
|
978
|
+
f_1(\mu) = \frac{6}{4^4} \frac{(\mu + 4)^{\mu + 4}}{\Gamma(\mu + 4)}
|
|
979
|
+
|
|
980
|
+
This formulation corresponds to a normalization with respect to liquid
|
|
981
|
+
water content.
|
|
982
|
+
|
|
983
|
+
Another alternative formulation normalized by total number concentration
|
|
984
|
+
(Tokay et al., 2010; Illingworth et al., 2002):
|
|
985
|
+
|
|
986
|
+
.. math::
|
|
987
|
+
|
|
988
|
+
N(D) = N_t \, f_2(\mu) \left( \frac{D}{D_m} \right)^{\mu} \exp\!\left[-(\mu + 4)\frac{D}{D_m}\right]
|
|
989
|
+
|
|
990
|
+
with
|
|
991
|
+
|
|
992
|
+
.. math::
|
|
993
|
+
|
|
994
|
+
f_2(\mu) = \frac{(\mu + 4)^{\mu + 1}}{\Gamma(\mu + 1)}
|
|
995
|
+
|
|
996
|
+
Note that :math:`\Gamma(4) = 6`.
|
|
476
997
|
|
|
477
998
|
Attributes
|
|
478
999
|
----------
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
1000
|
+
D50 : float or xarray.DataArray
|
|
1001
|
+
Median volume diameter.
|
|
1002
|
+
Nw : float or xarray.DataArray
|
|
1003
|
+
Intercept parameter.
|
|
1004
|
+
mu : float or xarray.DataArray
|
|
1005
|
+
Shape parameter.
|
|
482
1006
|
|
|
483
|
-
|
|
484
|
-
|
|
1007
|
+
Parameters
|
|
1008
|
+
----------
|
|
1009
|
+
D : float or array-like
|
|
1010
|
+
Particle diameter (same units as :math:`D_{50}` or :math:`D_m`).
|
|
485
1011
|
|
|
486
|
-
Returns
|
|
487
|
-
|
|
1012
|
+
Returns
|
|
1013
|
+
-------
|
|
1014
|
+
float or array-like
|
|
1015
|
+
Particle size distribution value evaluated at diameter ``D``.
|
|
488
1016
|
|
|
489
1017
|
References
|
|
490
1018
|
----------
|
|
@@ -528,7 +1056,24 @@ class NormalizedGammaPSD(XarrayPSD):
|
|
|
528
1056
|
|
|
529
1057
|
@staticmethod
|
|
530
1058
|
def formula(D, Nw, D50, mu):
|
|
531
|
-
"""
|
|
1059
|
+
"""Calculate the Normalized Gamma PSD values.
|
|
1060
|
+
|
|
1061
|
+
Parameters
|
|
1062
|
+
----------
|
|
1063
|
+
D : array-like
|
|
1064
|
+
Particle diameter [mm].
|
|
1065
|
+
Nw : float or array-like
|
|
1066
|
+
Intercept parameter [m^-3 mm^-1].
|
|
1067
|
+
D50 : float or array-like
|
|
1068
|
+
Median volume diameter [mm].
|
|
1069
|
+
mu : float or array-like
|
|
1070
|
+
Shape parameter [-].
|
|
1071
|
+
|
|
1072
|
+
Returns
|
|
1073
|
+
-------
|
|
1074
|
+
array-like
|
|
1075
|
+
PSD values N(D) [m^-3 mm^-1].
|
|
1076
|
+
"""
|
|
532
1077
|
d_ratio = D / D50
|
|
533
1078
|
nf = Nw * 6.0 / 3.67**4 * (3.67 + mu) ** (mu + 4) / gamma_f(mu + 4)
|
|
534
1079
|
# return nf * d_ratio ** mu * np.exp(-(mu + 3.67) * d_ratio)
|
|
@@ -536,14 +1081,17 @@ class NormalizedGammaPSD(XarrayPSD):
|
|
|
536
1081
|
|
|
537
1082
|
@staticmethod
|
|
538
1083
|
def from_parameters(parameters):
|
|
539
|
-
"""Initialize NormalizedGammaPSD from a dictionary or
|
|
1084
|
+
"""Initialize NormalizedGammaPSD from a dictionary or xarray.Dataset.
|
|
540
1085
|
|
|
541
|
-
|
|
542
|
-
|
|
1086
|
+
Parameters
|
|
1087
|
+
----------
|
|
1088
|
+
parameters : dict or xarray.Dataset
|
|
1089
|
+
Parameters to initialize the class. Must contain 'Nw', 'D50', and 'mu'.
|
|
543
1090
|
|
|
544
1091
|
Returns
|
|
545
1092
|
-------
|
|
546
|
-
|
|
1093
|
+
NormalizedGammaPSD
|
|
1094
|
+
An instance of NormalizedGammaPSD initialized with the parameters.
|
|
547
1095
|
"""
|
|
548
1096
|
D50 = parameters["D50"]
|
|
549
1097
|
Nw = parameters["Nw"]
|
|
@@ -552,7 +1100,13 @@ class NormalizedGammaPSD(XarrayPSD):
|
|
|
552
1100
|
|
|
553
1101
|
@staticmethod
|
|
554
1102
|
def required_parameters():
|
|
555
|
-
"""Return the required parameters of the PSD.
|
|
1103
|
+
"""Return the required parameters of the PSD.
|
|
1104
|
+
|
|
1105
|
+
Returns
|
|
1106
|
+
-------
|
|
1107
|
+
list of str
|
|
1108
|
+
List of required parameter names.
|
|
1109
|
+
"""
|
|
556
1110
|
return ["Nw", "D50", "mu"]
|
|
557
1111
|
|
|
558
1112
|
def parameters_summary(self):
|
|
@@ -571,28 +1125,510 @@ class NormalizedGammaPSD(XarrayPSD):
|
|
|
571
1125
|
return summary
|
|
572
1126
|
|
|
573
1127
|
|
|
1128
|
+
class GeneralizedGammaPSD(XarrayPSD):
|
|
1129
|
+
r"""Generalized gamma particle size distribution (PSD).
|
|
1130
|
+
|
|
1131
|
+
This class implements a generalized gamma PSD model, which extends the standard
|
|
1132
|
+
gamma distribution by introducing an additional shape parameter c. This provides
|
|
1133
|
+
greater flexibility in representing diverse particle size distributions.
|
|
1134
|
+
|
|
1135
|
+
The PSD is defined by the formula:
|
|
1136
|
+
|
|
1137
|
+
.. math::
|
|
1138
|
+
|
|
1139
|
+
N(D; N_t, \\Lambda, \\mu, c) = N_t \\frac{c\\Lambda}{\\Gamma(\\mu+1)} (\\Lambda D)^{c(\\mu+1)-1} \\exp[-(\\Lambda D)^c]
|
|
1140
|
+
|
|
1141
|
+
Parameters
|
|
1142
|
+
----------
|
|
1143
|
+
Nt : float or xarray.DataArray, optional
|
|
1144
|
+
Total concentration parameter [m^-3].
|
|
1145
|
+
Default is 1.0.
|
|
1146
|
+
Lambda : float or xarray.DataArray, optional
|
|
1147
|
+
Inverse scale parameter (slope parameter) [mm^-1].
|
|
1148
|
+
Default is 1.0.
|
|
1149
|
+
mu : float or xarray.DataArray, optional
|
|
1150
|
+
Shape parameter, must satisfy mu > -1 [-].
|
|
1151
|
+
Default is 0.0.
|
|
1152
|
+
c : float or xarray.DataArray, optional
|
|
1153
|
+
Additional shape parameter, must satisfy c ≠ 0 [-].
|
|
1154
|
+
Default is 1.0.
|
|
1155
|
+
|
|
1156
|
+
Attributes
|
|
1157
|
+
----------
|
|
1158
|
+
Nt : float or xarray.DataArray
|
|
1159
|
+
Total concentration parameter.
|
|
1160
|
+
Lambda : float or xarray.DataArray
|
|
1161
|
+
Inverse scale parameter.
|
|
1162
|
+
mu : float or xarray.DataArray
|
|
1163
|
+
Shape parameter.
|
|
1164
|
+
c : float or xarray.DataArray
|
|
1165
|
+
Additional shape parameter.
|
|
1166
|
+
parameters : dict
|
|
1167
|
+
Dictionary containing all PSD parameters.
|
|
1168
|
+
|
|
1169
|
+
Notes
|
|
1170
|
+
-----
|
|
1171
|
+
The generalized gamma distribution reduces to the standard gamma distribution
|
|
1172
|
+
when c = 1. The parameter c provides additional flexibility in controlling
|
|
1173
|
+
the shape of the distribution, particularly useful for representing
|
|
1174
|
+
diverse atmospheric particle populations.
|
|
1175
|
+
|
|
1176
|
+
References
|
|
1177
|
+
----------
|
|
1178
|
+
Lee, G. W., I. Zawadzki, W. Szyrmer, D. Sempere-Torres, and R. Uijlenhoet, 2004.
|
|
1179
|
+
A General Approach to Double-Moment Normalization of Drop Size Distributions.
|
|
1180
|
+
J. Appl. Meteor. Climatol., 43, 264-281,
|
|
1181
|
+
https://doi.org/10.1175/1520-0450(2004)043<0264:AGATDN>2.0.CO;2
|
|
1182
|
+
""" # noqa: E501
|
|
1183
|
+
|
|
1184
|
+
def __init__(self, Nt=1.0, Lambda=1.0, mu=0.0, c=1.0):
|
|
1185
|
+
self.Nt = Nt
|
|
1186
|
+
self.Lambda = Lambda
|
|
1187
|
+
self.mu = mu
|
|
1188
|
+
self.c = c
|
|
1189
|
+
self.parameters = {
|
|
1190
|
+
"Nt": self.Nt,
|
|
1191
|
+
"Lambda": self.Lambda,
|
|
1192
|
+
"mu": self.mu,
|
|
1193
|
+
"c": self.c,
|
|
1194
|
+
}
|
|
1195
|
+
check_input_parameters(self.parameters)
|
|
1196
|
+
|
|
1197
|
+
@property
|
|
1198
|
+
def name(self):
|
|
1199
|
+
"""Return the PSD name."""
|
|
1200
|
+
return "GeneralizedGammaPSD"
|
|
1201
|
+
|
|
1202
|
+
@staticmethod
|
|
1203
|
+
def formula(D, Nt, Lambda, mu, c):
|
|
1204
|
+
"""Calculates the Generalized Gamma PSD values.
|
|
1205
|
+
|
|
1206
|
+
Parameters
|
|
1207
|
+
----------
|
|
1208
|
+
D : array-like
|
|
1209
|
+
Particle diameter
|
|
1210
|
+
Nt : float or array-like
|
|
1211
|
+
Total concentration parameter [m^-3]
|
|
1212
|
+
Lambda : float or array-like
|
|
1213
|
+
Inverse scale parameter [???]
|
|
1214
|
+
mu : float or array-like
|
|
1215
|
+
Shape parameter (μ > -1)
|
|
1216
|
+
c : float or array-like
|
|
1217
|
+
Shape parameter (c ≠ 0)
|
|
1218
|
+
|
|
1219
|
+
Returns
|
|
1220
|
+
-------
|
|
1221
|
+
array-like
|
|
1222
|
+
PSD values
|
|
1223
|
+
"""
|
|
1224
|
+
# N(D) = N_t * (c*Λ/Γ(μ+1)) * (Λ*D)^(c(μ+1)-1) * exp(-(Λ*D)^c)
|
|
1225
|
+
lambda_d = Lambda * D
|
|
1226
|
+
intercept = Nt * c * Lambda / gamma_f(mu + 1)
|
|
1227
|
+
power_term = lambda_d ** (c * (mu + 1) - 1)
|
|
1228
|
+
exp_term = np.exp(-(lambda_d**c))
|
|
1229
|
+
return intercept * power_term * exp_term
|
|
1230
|
+
|
|
1231
|
+
@staticmethod
|
|
1232
|
+
def from_parameters(parameters):
|
|
1233
|
+
"""Initialize GeneralizedGammaPSD from a dictionary or xarray.Dataset.
|
|
1234
|
+
|
|
1235
|
+
Parameters
|
|
1236
|
+
----------
|
|
1237
|
+
parameters : dict or xarray.Dataset
|
|
1238
|
+
Parameters to initialize the class. Must contain 'Nt', 'Lambda', 'mu', and 'c'.
|
|
1239
|
+
|
|
1240
|
+
Returns
|
|
1241
|
+
-------
|
|
1242
|
+
GeneralizedGammaPSD
|
|
1243
|
+
An instance of GeneralizedGammaPSD initialized with the parameters.
|
|
1244
|
+
"""
|
|
1245
|
+
Nt = parameters["Nt"]
|
|
1246
|
+
Lambda = parameters["Lambda"]
|
|
1247
|
+
mu = parameters["mu"]
|
|
1248
|
+
c = parameters["c"]
|
|
1249
|
+
return GeneralizedGammaPSD(Nt=Nt, Lambda=Lambda, mu=mu, c=c)
|
|
1250
|
+
|
|
1251
|
+
@staticmethod
|
|
1252
|
+
def required_parameters():
|
|
1253
|
+
"""Return the required parameters of the PSD.
|
|
1254
|
+
|
|
1255
|
+
Returns
|
|
1256
|
+
-------
|
|
1257
|
+
list of str
|
|
1258
|
+
List of required parameter names.
|
|
1259
|
+
"""
|
|
1260
|
+
return ["Nt", "Lambda", "mu", "c"]
|
|
1261
|
+
|
|
1262
|
+
def parameters_summary(self):
|
|
1263
|
+
"""Return a string with the parameter summary."""
|
|
1264
|
+
if self.has_scalar_parameters():
|
|
1265
|
+
summary = "".join(
|
|
1266
|
+
[
|
|
1267
|
+
f"{self.name}\n",
|
|
1268
|
+
f"$N_t = {self.Nt:.2f}$\n",
|
|
1269
|
+
f"$\\lambda = {self.Lambda:.2f}$\n",
|
|
1270
|
+
f"$\\mu = {self.mu:.2f}$\n",
|
|
1271
|
+
f"$c = {self.c:.2f}$\n",
|
|
1272
|
+
],
|
|
1273
|
+
)
|
|
1274
|
+
else:
|
|
1275
|
+
summary = "" f"{self.name} with N-d parameters \n"
|
|
1276
|
+
return summary
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
class NormalizedGeneralizedGammaPSD(XarrayPSD):
|
|
1280
|
+
r"""Normalized generalized gamma particle size distribution (PSD).
|
|
1281
|
+
|
|
1282
|
+
This class implements a normalized generalized gamma PSD model based on the
|
|
1283
|
+
double-moment normalization framework. This formulation uses two moments of
|
|
1284
|
+
the distribution to derive normalized parameters, providing a flexible
|
|
1285
|
+
representation of particle size distributions.
|
|
1286
|
+
|
|
1287
|
+
The PSD is defined by the formula:
|
|
1288
|
+
|
|
1289
|
+
.. math::
|
|
1290
|
+
|
|
1291
|
+
N(D; M_i, M_j, \mu, c) = N_c \, c \,
|
|
1292
|
+
\Gamma_i^{\frac{j + c(\mu + 1)}{i - j}}
|
|
1293
|
+
\Gamma_j^{\frac{-i - c(\mu + 1)}{i - j}}
|
|
1294
|
+
\left(\frac{D}{D_c}\right)^{c(\mu + 1) - 1}
|
|
1295
|
+
\exp\left[
|
|
1296
|
+
-\left(\frac{\Gamma_i}{\Gamma_j}\right)^{\frac{c}{i - j}}
|
|
1297
|
+
\left(\frac{D}{D_c}\right)^c
|
|
1298
|
+
\right]
|
|
1299
|
+
|
|
1300
|
+
where the normalization parameters are defined as:
|
|
1301
|
+
|
|
1302
|
+
.. math::
|
|
1303
|
+
|
|
1304
|
+
N_c = M_i^{\frac{j + 1}{j - i}} M_j^{\frac{i + 1}{i - j}}
|
|
1305
|
+
|
|
1306
|
+
.. math::
|
|
1307
|
+
|
|
1308
|
+
D_c = \left(\frac{M_j}{M_i}\right)^{\frac{1}{j - i}}
|
|
1309
|
+
|
|
1310
|
+
with :math:`M_i = \Gamma_i` and :math:`M_j = \Gamma_j` representing the i-th and j-th
|
|
1311
|
+
moments of the distribution.
|
|
1312
|
+
|
|
1313
|
+
Parameters
|
|
1314
|
+
----------
|
|
1315
|
+
i : float or int, optional
|
|
1316
|
+
Moment index i [-].
|
|
1317
|
+
Default is 1.0.
|
|
1318
|
+
j : float or int, optional
|
|
1319
|
+
Moment index j [-].
|
|
1320
|
+
Default is 0.0.
|
|
1321
|
+
Nc : float or xarray.DataArray, optional
|
|
1322
|
+
Normalized intercept parameter [m^-3 mm^-1].
|
|
1323
|
+
Default is 1.0.
|
|
1324
|
+
Dc : float or xarray.DataArray, optional
|
|
1325
|
+
Characteristic diameter parameter [mm].
|
|
1326
|
+
Default is 1.0.
|
|
1327
|
+
c : float or xarray.DataArray, optional
|
|
1328
|
+
Shape parameter, must satisfy c ≠ 0 [-].
|
|
1329
|
+
Default is 1.0.
|
|
1330
|
+
mu : float or xarray.DataArray, optional
|
|
1331
|
+
Shape parameter, must satisfy mu > -1 [-].
|
|
1332
|
+
Default is 0.0.
|
|
1333
|
+
|
|
1334
|
+
Attributes
|
|
1335
|
+
----------
|
|
1336
|
+
i : float or int
|
|
1337
|
+
Moment index i.
|
|
1338
|
+
j : float or int
|
|
1339
|
+
Moment index j.
|
|
1340
|
+
Nc : float or xarray.DataArray
|
|
1341
|
+
Normalized intercept parameter computed from moments.
|
|
1342
|
+
Dc : float or xarray.DataArray
|
|
1343
|
+
Characteristic diameter parameter computed from moments.
|
|
1344
|
+
c : float or xarray.DataArray
|
|
1345
|
+
Shape parameter.
|
|
1346
|
+
mu : float or xarray.DataArray
|
|
1347
|
+
Shape parameter.
|
|
1348
|
+
parameters : dict
|
|
1349
|
+
Dictionary containing all PSD parameters.
|
|
1350
|
+
|
|
1351
|
+
Notes
|
|
1352
|
+
-----
|
|
1353
|
+
The double-moment normalization framework uses two arbitrary moments of the
|
|
1354
|
+
distribution to compute the normalization parameters Nc and Dc. This approach
|
|
1355
|
+
provides a unified framework for comparing different PSD models and relating
|
|
1356
|
+
them to observable quantities.
|
|
1357
|
+
|
|
1358
|
+
The moment indices i and j are typically chosen based on the moments that can
|
|
1359
|
+
be most reliably measured or estimated from observations. Common choices include
|
|
1360
|
+
(i=3, j=4) or (i=3, j=6) for radar applications.
|
|
1361
|
+
|
|
1362
|
+
References
|
|
1363
|
+
----------
|
|
1364
|
+
Lee, G. W., I. Zawadzki, W. Szyrmer, D. Sempere-Torres, and R. Uijlenhoet, 2004:
|
|
1365
|
+
A General Approach to Double-Moment Normalization of Drop Size Distributions.
|
|
1366
|
+
J. Appl. Meteor. Climatol., 43, 264-281,
|
|
1367
|
+
https://doi.org/10.1175/1520-0450(2004)043<0264:AGATDN>2.0.CO;2
|
|
1368
|
+
"""
|
|
1369
|
+
|
|
1370
|
+
def __init__(self, i=1.0, j=0.0, Nc=1, Dc=1.0, c=1.0, mu=0.0):
|
|
1371
|
+
self.i = i
|
|
1372
|
+
self.j = j
|
|
1373
|
+
self.Nc = Nc
|
|
1374
|
+
self.Dc = Dc
|
|
1375
|
+
self.c = c
|
|
1376
|
+
self.mu = mu
|
|
1377
|
+
self.parameters = {
|
|
1378
|
+
"i": self.i,
|
|
1379
|
+
"j": self.j,
|
|
1380
|
+
"Nc": self.Nc,
|
|
1381
|
+
"Dc": self.Dc,
|
|
1382
|
+
"c": self.c,
|
|
1383
|
+
"mu": self.mu,
|
|
1384
|
+
}
|
|
1385
|
+
check_input_parameters(self.parameters)
|
|
1386
|
+
|
|
1387
|
+
@staticmethod
|
|
1388
|
+
def compute_Nc(i, j, Mi, Mj):
|
|
1389
|
+
r"""Compute N_c from i, j, Mi, Mj.
|
|
1390
|
+
|
|
1391
|
+
.. math::
|
|
1392
|
+
|
|
1393
|
+
N_c = M_i^{\frac{j + 1}{j - i}} M_j^{\frac{i + 1}{i - j}}
|
|
1394
|
+
|
|
1395
|
+
Parameters
|
|
1396
|
+
----------
|
|
1397
|
+
i : float or array-like
|
|
1398
|
+
Moment index i
|
|
1399
|
+
j : float or array-like
|
|
1400
|
+
Moment index j
|
|
1401
|
+
Mi : float or array-like
|
|
1402
|
+
Moment parameter Mi (Γ_i)
|
|
1403
|
+
Mj : float or array-like
|
|
1404
|
+
Moment parameter Mj (Γ_j)
|
|
1405
|
+
|
|
1406
|
+
Returns
|
|
1407
|
+
-------
|
|
1408
|
+
float or array-like
|
|
1409
|
+
The normalized intercept parameter N_c with units m-3 mm-1.
|
|
1410
|
+
"""
|
|
1411
|
+
return compute_Nc(i=i, j=j, Mi=Mi, Mj=Mj)
|
|
1412
|
+
|
|
1413
|
+
@staticmethod
|
|
1414
|
+
def compute_Dc(i, j, Mi, Mj):
|
|
1415
|
+
r"""Compute D_c from i, j, Mi, Mj.
|
|
1416
|
+
|
|
1417
|
+
.. math::
|
|
1418
|
+
|
|
1419
|
+
D_c = \left(\frac{M_j}{M_i}\right)^{\frac{1}{j - i}}
|
|
1420
|
+
|
|
1421
|
+
Parameters
|
|
1422
|
+
----------
|
|
1423
|
+
i : float or array-like
|
|
1424
|
+
Moment index i
|
|
1425
|
+
j : float or array-like
|
|
1426
|
+
Moment index j
|
|
1427
|
+
Mi : float or array-like
|
|
1428
|
+
Moment parameter Mi (Γ_i)
|
|
1429
|
+
Mj : float or array-like
|
|
1430
|
+
Moment parameter Mj (Γ_j)
|
|
1431
|
+
|
|
1432
|
+
Returns
|
|
1433
|
+
-------
|
|
1434
|
+
float or array-like
|
|
1435
|
+
The characteristic diameter parameter D_c with units mm.
|
|
1436
|
+
"""
|
|
1437
|
+
return compute_Dc(i=i, j=j, Mi=Mi, Mj=Mj)
|
|
1438
|
+
|
|
1439
|
+
@property
|
|
1440
|
+
def name(self):
|
|
1441
|
+
"""Return the PSD name."""
|
|
1442
|
+
return "NormalizedGeneralizedGammaPSD"
|
|
1443
|
+
|
|
1444
|
+
@staticmethod
|
|
1445
|
+
def normalized_formula(x, i, j, c, mu):
|
|
1446
|
+
"""Calculates N(D)/Nc from x=D/Dc.
|
|
1447
|
+
|
|
1448
|
+
This formula is useful to fit a single normalized PSD shape to data
|
|
1449
|
+
in the double normalization framework.
|
|
1450
|
+
|
|
1451
|
+
Parameters
|
|
1452
|
+
----------
|
|
1453
|
+
x : array-like
|
|
1454
|
+
Normalized particle diameter: x = D/Dc
|
|
1455
|
+
i : float
|
|
1456
|
+
Moment index i
|
|
1457
|
+
j : float
|
|
1458
|
+
Moment index j
|
|
1459
|
+
c : float
|
|
1460
|
+
Shape parameter c
|
|
1461
|
+
mu : float
|
|
1462
|
+
Shape parameter μ
|
|
1463
|
+
|
|
1464
|
+
Returns
|
|
1465
|
+
-------
|
|
1466
|
+
array-like
|
|
1467
|
+
N(D)/Nc values
|
|
1468
|
+
"""
|
|
1469
|
+
# ---------------------------------------------------------------
|
|
1470
|
+
# Compute lngamma i and j
|
|
1471
|
+
gammaln_i = gammaln(mu + 1 + (i / c))
|
|
1472
|
+
gammaln_j = gammaln(mu + 1 + (j / c))
|
|
1473
|
+
|
|
1474
|
+
# Compute gamma i and j
|
|
1475
|
+
# gamma_i = gamma_f(mu + 1 + i / c)
|
|
1476
|
+
# gamma_j = gamma_f(mu + 1 + j / c)
|
|
1477
|
+
|
|
1478
|
+
# Calculate normalization coefficient
|
|
1479
|
+
# Equation: c * Γ_i^((j+c(μ+1))/(i-j)) * Γ_j^((-i-c(μ+1))/(i-j))
|
|
1480
|
+
pow_i = (j + c * (mu + 1)) / (i - j)
|
|
1481
|
+
pow_j = (-i - c * (mu + 1)) / (i - j)
|
|
1482
|
+
norm_coeff = c * np.exp(pow_i * gammaln_i + pow_j * gammaln_j)
|
|
1483
|
+
# norm_coeff = c * (gamma_i ** pow_i) * (gamma_j ** pow_j)
|
|
1484
|
+
|
|
1485
|
+
# Compute ratio gammas
|
|
1486
|
+
# ratio_gammas = gamma_i / gamma_j
|
|
1487
|
+
ratio_gammas = np.exp(gammaln_i - gammaln_j)
|
|
1488
|
+
|
|
1489
|
+
# Calculate the full PSD formula
|
|
1490
|
+
# N_c * norm_coeff * (D/D_c)^(c(μ+1)) * exp(-(Γ_i/Γ_j)^(c/(i-j)) * (D/D_c)^c)
|
|
1491
|
+
exponent_power = (ratio_gammas) ** (c / (i - j))
|
|
1492
|
+
power_term = x ** (c * (mu + 1) - 1)
|
|
1493
|
+
exp_term = np.exp(-exponent_power * (x**c))
|
|
1494
|
+
return norm_coeff * power_term * exp_term
|
|
1495
|
+
|
|
1496
|
+
@staticmethod
|
|
1497
|
+
def formula(D, i, j, Nc, Dc, c, mu):
|
|
1498
|
+
"""Calculates the Normalized Generalized Gamma PSD N(D) values.
|
|
1499
|
+
|
|
1500
|
+
N_c and D_c are computed internally from the parameters.
|
|
1501
|
+
|
|
1502
|
+
Parameters
|
|
1503
|
+
----------
|
|
1504
|
+
D : array-like
|
|
1505
|
+
Particle diameter
|
|
1506
|
+
i : float
|
|
1507
|
+
Moment index i
|
|
1508
|
+
j : float
|
|
1509
|
+
Moment index j
|
|
1510
|
+
Nc : float
|
|
1511
|
+
General characteristic intercept (mm-1 m-3)
|
|
1512
|
+
Dc : float
|
|
1513
|
+
General characteristic diameter (mm)
|
|
1514
|
+
c : float
|
|
1515
|
+
Shape parameter c
|
|
1516
|
+
mu : float
|
|
1517
|
+
Shape parameter μ
|
|
1518
|
+
|
|
1519
|
+
Returns
|
|
1520
|
+
-------
|
|
1521
|
+
array-like
|
|
1522
|
+
PSD values
|
|
1523
|
+
"""
|
|
1524
|
+
# Compute x
|
|
1525
|
+
x = D / Dc
|
|
1526
|
+
|
|
1527
|
+
norm_nd = NormalizedGeneralizedGammaPSD.normalized_formula(
|
|
1528
|
+
x=x,
|
|
1529
|
+
i=i,
|
|
1530
|
+
j=j,
|
|
1531
|
+
c=c,
|
|
1532
|
+
mu=mu,
|
|
1533
|
+
)
|
|
1534
|
+
return Nc * norm_nd
|
|
1535
|
+
|
|
1536
|
+
@staticmethod
|
|
1537
|
+
def from_parameters(parameters):
|
|
1538
|
+
"""Initialize NormalizedGeneralizedGammaPSD from a dictionary or xarray.Dataset.
|
|
1539
|
+
|
|
1540
|
+
Parameters
|
|
1541
|
+
----------
|
|
1542
|
+
parameters : dict or xarray.Dataset
|
|
1543
|
+
Parameters to initialize the class. Must contain 'i', 'j', 'Nc', 'Dc', 'c', and 'mu'.
|
|
1544
|
+
The moment indices 'i' and 'j' can also be provided in the 'disdrodb_psd_model_kwargs'
|
|
1545
|
+
attribute if parameters is an xarray.Dataset.
|
|
1546
|
+
|
|
1547
|
+
Returns
|
|
1548
|
+
-------
|
|
1549
|
+
NormalizedGeneralizedGammaPSD
|
|
1550
|
+
An instance of NormalizedGeneralizedGammaPSD initialized with the parameters.
|
|
1551
|
+
"""
|
|
1552
|
+
if hasattr(parameters, "attrs") and "disdrodb_psd_model_kwargs" in parameters.attrs:
|
|
1553
|
+
model_kwargs = ast.literal_eval(parameters.attrs["disdrodb_psd_model_kwargs"])
|
|
1554
|
+
i = model_kwargs["i"]
|
|
1555
|
+
j = model_kwargs["j"]
|
|
1556
|
+
else:
|
|
1557
|
+
i = parameters["i"]
|
|
1558
|
+
j = parameters["j"]
|
|
1559
|
+
Dc = parameters["Dc"]
|
|
1560
|
+
Nc = parameters["Nc"]
|
|
1561
|
+
c = parameters["c"]
|
|
1562
|
+
mu = parameters["mu"]
|
|
1563
|
+
return NormalizedGeneralizedGammaPSD(i=i, j=j, Nc=Nc, Dc=Dc, c=c, mu=mu)
|
|
1564
|
+
|
|
1565
|
+
@staticmethod
|
|
1566
|
+
def required_parameters():
|
|
1567
|
+
"""Return the required parameters of the PSD.
|
|
1568
|
+
|
|
1569
|
+
Returns
|
|
1570
|
+
-------
|
|
1571
|
+
list of str
|
|
1572
|
+
List of required parameter names.
|
|
1573
|
+
"""
|
|
1574
|
+
return ["i", "j", "Nc", "Dc", "c", "mu"]
|
|
1575
|
+
|
|
1576
|
+
def parameters_summary(self):
|
|
1577
|
+
"""Return a string with the parameter summary."""
|
|
1578
|
+
if self.has_scalar_parameters():
|
|
1579
|
+
summary = "".join(
|
|
1580
|
+
[
|
|
1581
|
+
f"{self.name}\n",
|
|
1582
|
+
f"$i = {self.i:.2f}$\n",
|
|
1583
|
+
f"$j = {self.j:.2f}$\n",
|
|
1584
|
+
f"$c = {self.c:.2f}$\n",
|
|
1585
|
+
f"$\\mu = {self.mu:.2f}$\n",
|
|
1586
|
+
f"$N_c = {self.Nc:.2f}$\n",
|
|
1587
|
+
f"$D_c = {self.Dc:.2f}$\n",
|
|
1588
|
+
],
|
|
1589
|
+
)
|
|
1590
|
+
else:
|
|
1591
|
+
summary = "" f"{self.name} with N-d parameters \n"
|
|
1592
|
+
return summary
|
|
1593
|
+
|
|
1594
|
+
|
|
1595
|
+
####-------------------------------------------------------------------------.
|
|
1596
|
+
#### PSD_MODELS_DICT
|
|
574
1597
|
PSD_MODELS_DICT = {
|
|
575
1598
|
"LognormalPSD": LognormalPSD,
|
|
576
1599
|
"ExponentialPSD": ExponentialPSD,
|
|
577
1600
|
"GammaPSD": GammaPSD,
|
|
1601
|
+
"GeneralizedGammaPSD": GeneralizedGammaPSD,
|
|
578
1602
|
"NormalizedGammaPSD": NormalizedGammaPSD,
|
|
1603
|
+
"NormalizedGeneralizedGammaPSD": NormalizedGeneralizedGammaPSD,
|
|
579
1604
|
}
|
|
580
1605
|
|
|
581
1606
|
|
|
1607
|
+
####-------------------------------------------------------------------------.
|
|
1608
|
+
#### BinnedPSD
|
|
1609
|
+
|
|
1610
|
+
|
|
582
1611
|
def define_interpolator(bin_edges, bin_values, interp_method):
|
|
583
|
-
"""
|
|
584
|
-
Returns an interpolation function that takes one argument D.
|
|
1612
|
+
"""Create an interpolation function for binned data.
|
|
585
1613
|
|
|
586
1614
|
Parameters
|
|
587
1615
|
----------
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
1616
|
+
bin_edges : array-like
|
|
1617
|
+
Sorted array of n+1 bin edge values [mm].
|
|
1618
|
+
bin_values : array-like
|
|
1619
|
+
Array of n bin values corresponding to each bin.
|
|
1620
|
+
interp_method : str
|
|
1621
|
+
Interpolation method:
|
|
1622
|
+
|
|
1623
|
+
- 'step_left': Piecewise constant, left-continuous
|
|
1624
|
+
- 'step_right': Piecewise constant, right-continuous
|
|
1625
|
+
- 'linear': Linear interpolation
|
|
1626
|
+
- 'pchip': Piecewise Cubic Hermite Interpolating Polynomial
|
|
591
1627
|
|
|
592
1628
|
Returns
|
|
593
1629
|
-------
|
|
594
1630
|
callable
|
|
595
|
-
|
|
1631
|
+
A function f(D) that returns the interpolated values for diameter D.
|
|
596
1632
|
"""
|
|
597
1633
|
# Ensure bin_edges and bin_values are NumPy arrays
|
|
598
1634
|
bin_edges = np.asarray(bin_edges)
|
|
@@ -638,25 +1674,48 @@ def _stepwise_interpolator(bin_edges, bin_values, D, side="left"):
|
|
|
638
1674
|
|
|
639
1675
|
|
|
640
1676
|
class BinnedPSD(PSD):
|
|
641
|
-
"""Binned
|
|
1677
|
+
"""Binned particle size distribution (PSD).
|
|
1678
|
+
|
|
1679
|
+
This class represents a binned PSD that computes values through interpolation
|
|
1680
|
+
between discretized bin values. This approach is useful for representing
|
|
1681
|
+
empirically measured or discretized PSDs.
|
|
642
1682
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1683
|
+
The PSD values are computed via interpolation from discrete bin values using
|
|
1684
|
+
various methods. Values outside the defined bin range are set to zero, and
|
|
1685
|
+
all returned values are non-negative.
|
|
646
1686
|
|
|
647
1687
|
Parameters
|
|
648
1688
|
----------
|
|
649
|
-
bin_edges :
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1689
|
+
bin_edges : array-like
|
|
1690
|
+
Sequence of n+1 bin edge values defining the bins [mm].
|
|
1691
|
+
Must be monotonically increasing.
|
|
1692
|
+
bin_psd : array-like
|
|
1693
|
+
Sequence of n PSD values corresponding to the intervals defined by bin_edges [m^-3 mm^-1].
|
|
1694
|
+
interp_method : str, optional
|
|
1695
|
+
Interpolation method for computing PSD values between bin centers. Valid methods can be:
|
|
655
1696
|
|
|
656
|
-
|
|
657
|
-
-
|
|
658
|
-
-
|
|
659
|
-
-
|
|
1697
|
+
- 'step_left': Use the value from the left bin (piecewise constant, left-continuous)
|
|
1698
|
+
- 'step_right': Use the value from the right bin (piecewise constant, right-continuous)
|
|
1699
|
+
- 'linear': Linear interpolation between bin centers
|
|
1700
|
+
- 'pchip': Piecewise Cubic Hermite Interpolating Polynomial, preserves monotonicity
|
|
1701
|
+
|
|
1702
|
+
Default is 'step_left'.
|
|
1703
|
+
|
|
1704
|
+
Attributes
|
|
1705
|
+
----------
|
|
1706
|
+
bin_edges : numpy.ndarray
|
|
1707
|
+
Bin edge values.
|
|
1708
|
+
bin_psd : numpy.ndarray
|
|
1709
|
+
PSD values for each bin.
|
|
1710
|
+
interp_method : str
|
|
1711
|
+
Selected interpolation method.
|
|
1712
|
+
|
|
1713
|
+
Notes
|
|
1714
|
+
-----
|
|
1715
|
+
- Values for diameters D outside the range (bin_edges[0], bin_edges[-1]) are set to 0
|
|
1716
|
+
- Interpolation is performed using bin centers computed as the midpoint of each bin
|
|
1717
|
+
- All PSD values are clipped to be non-negative after interpolation
|
|
1718
|
+
- The 'pchip' method is recommended when smoothness and monotonicity preservation are important
|
|
660
1719
|
|
|
661
1720
|
Examples
|
|
662
1721
|
--------
|
|
@@ -669,7 +1728,9 @@ class BinnedPSD(PSD):
|
|
|
669
1728
|
>>> psd_linear = BinnedPSD(bin_edges, bin_psd, interp_method="linear")
|
|
670
1729
|
>>> psd_values = psd_linear(D)
|
|
671
1730
|
>>>
|
|
672
|
-
>>> #
|
|
1731
|
+
>>> # Using step interpolation
|
|
1732
|
+
>>> psd_step = BinnedPSD(bin_edges, bin_psd, interp_method="step_left")
|
|
1733
|
+
>>> psd_values_step = psd_step(D)
|
|
673
1734
|
"""
|
|
674
1735
|
|
|
675
1736
|
def __init__(self, bin_edges, bin_psd, interp_method="step_left"):
|
|
@@ -717,7 +1778,18 @@ class BinnedPSD(PSD):
|
|
|
717
1778
|
return values
|
|
718
1779
|
|
|
719
1780
|
def __eq__(self, other):
|
|
720
|
-
"""Check Binned PSD equality.
|
|
1781
|
+
"""Check Binned PSD equality.
|
|
1782
|
+
|
|
1783
|
+
Parameters
|
|
1784
|
+
----------
|
|
1785
|
+
other : BinnedPSD or None
|
|
1786
|
+
Another BinnedPSD object to compare with.
|
|
1787
|
+
|
|
1788
|
+
Returns
|
|
1789
|
+
-------
|
|
1790
|
+
bool
|
|
1791
|
+
True if both objects have the same bin edges and PSD values, False otherwise.
|
|
1792
|
+
"""
|
|
721
1793
|
if other is None:
|
|
722
1794
|
return False
|
|
723
1795
|
if not isinstance(other, self.__class__):
|
|
@@ -734,26 +1806,74 @@ class BinnedPSD(PSD):
|
|
|
734
1806
|
|
|
735
1807
|
|
|
736
1808
|
def get_exponential_moment(N0, Lambda, moment):
|
|
737
|
-
"""Compute exponential distribution
|
|
1809
|
+
"""Compute moments of the exponential distribution.
|
|
1810
|
+
|
|
1811
|
+
Parameters
|
|
1812
|
+
----------
|
|
1813
|
+
N0 : float or array-like
|
|
1814
|
+
Intercept parameter [m^-3 mm^-1].
|
|
1815
|
+
Lambda : float or array-like
|
|
1816
|
+
Inverse scale parameter [mm^-1].
|
|
1817
|
+
moment : int or float
|
|
1818
|
+
Moment order.
|
|
1819
|
+
|
|
1820
|
+
Returns
|
|
1821
|
+
-------
|
|
1822
|
+
float or array-like
|
|
1823
|
+
The computed moment value.
|
|
1824
|
+
"""
|
|
738
1825
|
return N0 * gamma_f(moment + 1) / Lambda ** (moment + 1)
|
|
739
1826
|
|
|
740
1827
|
|
|
741
1828
|
def get_gamma_moment_v1(N0, mu, Lambda, moment):
|
|
742
|
-
"""Compute gamma distribution
|
|
1829
|
+
"""Compute moments of the gamma distribution (version 1).
|
|
1830
|
+
|
|
1831
|
+
Parameters
|
|
1832
|
+
----------
|
|
1833
|
+
N0 : float or array-like
|
|
1834
|
+
Intercept parameter [m^-3 mm^(-1-mu)].
|
|
1835
|
+
mu : float or array-like
|
|
1836
|
+
Shape parameter [-].
|
|
1837
|
+
Lambda : float or array-like
|
|
1838
|
+
Inverse scale parameter [mm^-1].
|
|
1839
|
+
moment : int or float
|
|
1840
|
+
Moment order.
|
|
1841
|
+
|
|
1842
|
+
Returns
|
|
1843
|
+
-------
|
|
1844
|
+
float or array-like
|
|
1845
|
+
The computed moment value.
|
|
743
1846
|
|
|
744
1847
|
References
|
|
745
1848
|
----------
|
|
746
1849
|
Kozu, T., and K. Nakamura, 1991:
|
|
747
1850
|
Rainfall Parameter Estimation from Dual-Radar Measurements
|
|
748
1851
|
Combining Reflectivity Profile and Path-integrated Attenuation.
|
|
749
|
-
J. Atmos. Oceanic Technol., 8, 259-270,
|
|
1852
|
+
J. Atmos. Oceanic Technol., 8, 259-270,
|
|
1853
|
+
https://doi.org/10.1175/1520-0426(1991)008<0259:RPEFDR>2.0.CO;2
|
|
750
1854
|
"""
|
|
751
1855
|
# Zhang et al 2001: N0 * gamma_f(mu + moment + 1) * Lambda ** (-(mu + moment + 1))
|
|
752
1856
|
return N0 * gamma_f(mu + moment + 1) / Lambda ** (mu + moment + 1)
|
|
753
1857
|
|
|
754
1858
|
|
|
755
1859
|
def get_gamma_moment_v2(Nt, mu, Lambda, moment):
|
|
756
|
-
"""Compute gamma distribution
|
|
1860
|
+
"""Compute moments of the gamma distribution (version 2).
|
|
1861
|
+
|
|
1862
|
+
Parameters
|
|
1863
|
+
----------
|
|
1864
|
+
Nt : float or array-like
|
|
1865
|
+
Total concentration parameter [m^-3].
|
|
1866
|
+
mu : float or array-like
|
|
1867
|
+
Shape parameter [-].
|
|
1868
|
+
Lambda : float or array-like
|
|
1869
|
+
Inverse scale parameter [mm^-1].
|
|
1870
|
+
moment : int or float
|
|
1871
|
+
Moment order.
|
|
1872
|
+
|
|
1873
|
+
Returns
|
|
1874
|
+
-------
|
|
1875
|
+
float or array-like
|
|
1876
|
+
The computed moment value.
|
|
757
1877
|
|
|
758
1878
|
References
|
|
759
1879
|
----------
|
|
@@ -766,7 +1886,23 @@ def get_gamma_moment_v2(Nt, mu, Lambda, moment):
|
|
|
766
1886
|
|
|
767
1887
|
|
|
768
1888
|
def get_lognormal_moment(Nt, sigma, mu, moment):
|
|
769
|
-
"""Compute lognormal distribution
|
|
1889
|
+
"""Compute moments of the lognormal distribution.
|
|
1890
|
+
|
|
1891
|
+
Parameters
|
|
1892
|
+
----------
|
|
1893
|
+
Nt : float or array-like
|
|
1894
|
+
Total concentration parameter [m^-3].
|
|
1895
|
+
sigma : float or array-like
|
|
1896
|
+
Scale parameter [-].
|
|
1897
|
+
mu : float or array-like
|
|
1898
|
+
Location parameter [-].
|
|
1899
|
+
moment : int or float
|
|
1900
|
+
Moment order.
|
|
1901
|
+
|
|
1902
|
+
Returns
|
|
1903
|
+
-------
|
|
1904
|
+
float or array-like
|
|
1905
|
+
The computed moment value.
|
|
770
1906
|
|
|
771
1907
|
References
|
|
772
1908
|
----------
|