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.
Files changed (75) hide show
  1. disdrodb/__init__.py +4 -0
  2. disdrodb/_version.py +2 -2
  3. disdrodb/accessor/methods.py +14 -0
  4. disdrodb/api/checks.py +8 -7
  5. disdrodb/api/io.py +81 -29
  6. disdrodb/api/path.py +17 -14
  7. disdrodb/api/search.py +15 -18
  8. disdrodb/cli/disdrodb_open_products_options.py +38 -0
  9. disdrodb/cli/disdrodb_run.py +2 -2
  10. disdrodb/cli/disdrodb_run_station.py +4 -4
  11. disdrodb/configs.py +1 -1
  12. disdrodb/data_transfer/download_data.py +70 -1
  13. disdrodb/etc/configs/attributes.yaml +62 -8
  14. disdrodb/etc/configs/encodings.yaml +28 -0
  15. disdrodb/etc/products/L2M/MODELS/GAMMA_GS_ND_SSE.yaml +8 -0
  16. disdrodb/etc/products/L2M/MODELS/GAMMA_ML.yaml +1 -1
  17. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_LOG_ND_SSE.yaml +8 -0
  18. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_ND_SSE.yaml +8 -0
  19. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_ML.yaml +1 -1
  20. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_LOG_ND_SSE.yaml +8 -0
  21. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_ND_SSE.yaml +8 -0
  22. disdrodb/etc/products/L2M/global.yaml +4 -4
  23. disdrodb/fall_velocity/graupel.py +8 -8
  24. disdrodb/fall_velocity/hail.py +2 -2
  25. disdrodb/fall_velocity/rain.py +33 -5
  26. disdrodb/issue/checks.py +1 -1
  27. disdrodb/l0/l0_reader.py +1 -1
  28. disdrodb/l0/l0a_processing.py +2 -2
  29. disdrodb/l0/l0b_nc_processing.py +5 -5
  30. disdrodb/l0/l0b_processing.py +20 -24
  31. disdrodb/l0/l0c_processing.py +18 -13
  32. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +4 -0
  33. disdrodb/l0/readers/PARSIVEL2/VIETNAM/IGE_PARSIVEL2.py +239 -0
  34. disdrodb/l0/template_tools.py +13 -13
  35. disdrodb/l1/classification.py +10 -6
  36. disdrodb/l2/empirical_dsd.py +25 -15
  37. disdrodb/l2/processing.py +32 -14
  38. disdrodb/metadata/download.py +1 -1
  39. disdrodb/metadata/geolocation.py +4 -4
  40. disdrodb/metadata/reader.py +3 -3
  41. disdrodb/metadata/search.py +10 -8
  42. disdrodb/psd/__init__.py +4 -0
  43. disdrodb/psd/fitting.py +2660 -592
  44. disdrodb/psd/gof_metrics.py +389 -0
  45. disdrodb/psd/grid_search.py +1066 -0
  46. disdrodb/psd/models.py +1281 -145
  47. disdrodb/routines/l2.py +6 -6
  48. disdrodb/routines/options_validation.py +8 -8
  49. disdrodb/scattering/axis_ratio.py +70 -2
  50. disdrodb/scattering/permittivity.py +13 -10
  51. disdrodb/scattering/routines.py +10 -10
  52. disdrodb/summary/routines.py +23 -20
  53. disdrodb/utils/archiving.py +29 -22
  54. disdrodb/utils/attrs.py +6 -4
  55. disdrodb/utils/dataframe.py +4 -4
  56. disdrodb/utils/encoding.py +3 -1
  57. disdrodb/utils/event.py +9 -9
  58. disdrodb/utils/logger.py +4 -7
  59. disdrodb/utils/manipulations.py +2 -2
  60. disdrodb/utils/subsetting.py +1 -1
  61. disdrodb/utils/time.py +8 -7
  62. disdrodb/viz/plots.py +25 -17
  63. {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/METADATA +44 -33
  64. {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/RECORD +68 -66
  65. {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/WHEEL +1 -1
  66. {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/entry_points.txt +1 -0
  67. disdrodb/etc/products/L2M/MODELS/GAMMA_GS_ND_MAE.yaml +0 -6
  68. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_LOG_ND_MAE.yaml +0 -6
  69. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_ND_MAE.yaml +0 -6
  70. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_LOG_ND_MAE.yaml +0 -6
  71. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_ND_MAE.yaml +0 -6
  72. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_R_MAE.yaml +0 -6
  73. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_Z_MAE.yaml +0 -6
  74. {disdrodb-0.4.0.dist-info → disdrodb-0.5.1.dist-info}/licenses/LICENSE +0 -0
  75. {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 valid input parameters."""
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 valid diameter input."""
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 Class."""
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
- """Define a PSD from a dictionary or xr.Dataset of parameters."""
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
- """Determines if the input value is a scalar."""
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
- We currently inherit from pytmatrix PSD to allow scattering simulations:
119
- --> https://github.com/ltelab/pytmatrix-lte/blob/880170b4ca62a04e8c843619fa1b8713b9e11894/pytmatrix/psd.py#L321
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
- return self.formula(D=D, **self.parameters)
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 a single set of parameters."""
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
- If the PSD has xarray parameters, returns a new PSD with subset parameters.
142
- Otherwise raises an error.
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
- If the PSD has xarray parameters, returns a new PSD with subset parameters.
162
- Otherwise raises an error.
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 drop size distribution (DSD).
527
+ r"""Lognormal particle size distribution (PSD).
218
528
 
219
- Callable class to provide a lognormal PSD with the given parameters.
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 form is:
532
+ The PSD is defined by the formula:
222
533
 
223
- N(D) = Nt/(sqrt(2*pi)*sigma*D)) * exp(-(ln(D)-mu)**2 / (2*sigma**2))
534
+ .. math::
224
535
 
225
- # g = sigma
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
- Attributes
538
+ Parameters
229
539
  ----------
230
- Nt:
231
- g:
232
- theta:
233
- mu:
234
- sigma:
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
- """Calculates the Lognormal PSD values."""
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 xr.Dataset.
604
+ """Initialize LognormalPSD from a dictionary or xarray.Dataset.
259
605
 
260
- Args:
261
- parameters (dict or xr.Dataset): Parameters to initialize the class.
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
- LognormalPSD: An instance of LognormalPSD initialized with the parameters.
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
- Callable class to provide an exponential PSD with the given
296
- parameters. The attributes can also be given as arguments to the
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 form is:
300
- N(D) = N0 * exp(-Lambda*D)
653
+ The PSD is defined by the formula:
301
654
 
302
- Attributes
303
- ----------
304
- N0: the intercept parameter.
305
- Lambda: the inverse scale parameter
655
+ .. math::
306
656
 
307
- Args (call):
308
- D: the particle diameter in millimeter.
657
+ N(D) = N_0 \exp(-\Lambda D)
309
658
 
310
- Returns (call):
311
- The PSD value for the given diameter.
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
- """Calculates the Exponential PSD values."""
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 xr.Dataset.
717
+ """Initialize ExponentialPSD from a dictionary or xarray.Dataset.
334
718
 
335
- Args:
336
- parameters (dict or xr.Dataset): Parameters to initialize the class.
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
- ExponentialPSD: An instance of ExponentialPSD initialized with the parameters.
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
- Callable class to provide an gamma PSD with the given
370
- parameters. The attributes can also be given as arguments to the
371
- constructor.
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 form is:
374
- N(D) = N0 * D**mu * exp(-Lambda*D)
772
+ The PSD is defined by the formula:
375
773
 
376
- Attributes
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
- Args (call):
383
- D: the particle diameter in millimeter.
776
+ N(D) = N_0 D^{\mu} \exp(-\Lambda D)
384
777
 
385
- Returns (call):
386
- The PSD value for the given diameter.
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., 1985: The Effects of Drop Size Distribution Truncation on
391
- Rainfall Integral Parameters and Empirical Relations.
392
- J. Appl. Meteor. Climatol., 24, 580-590, https://doi.org/10.1175/1520-0450(1985)024<0580:TEODSD>2.0.CO;2
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
- """Calculates the Gamma PSD values."""
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 xr.Dataset.
852
+ """Initialize GammaPSD from a dictionary or xarray.Dataset.
416
853
 
417
- Args:
418
- parameters (dict or xr.Dataset): Parameters to initialize the class.
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
- GammaPSD: An instance of GammaPSD initialized with the parameters.
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
- Callable class to provide a normalized gamma PSD with the given
454
- parameters. The attributes can also be given as arguments to the
455
- constructor.
959
+ where:
456
960
 
457
- The PSD form is:
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
- N(D) = Nw * f(mu) * (D/D50)**mu * exp(-(mu+3.67)*D/D50)
460
- f(mu) = 6/(3.67**4) * (mu+3.67)**(mu+4)/Gamma(mu+4)
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
- An alternative formulation as function of Dm:
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
- Note: gamma(4) = 6
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
- An alternative formulation as function of Dm:
471
- # Tokay et al., 2010
472
- # Illingworth et al., 2002 (see eq10 to derive full formulation!)
473
- # --> Normalized with respect to total concentration --> Nx = #/Dm
474
- N(D) = Nt* * f2(mu) * (D/Dm)**mu * exp(-(mu+4)*D/Dm)
475
- f2(mu) = (mu+4)**(mu+1)/Gamma(mu+1)
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
- D50: the median volume diameter.
480
- Nw: the intercept parameter.
481
- mu: the shape parameter.
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
- Args (call):
484
- D: the particle diameter in millimeter.
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 (call):
487
- The PSD value for the given diameter.
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
- """Calculates the NormalizedGamma PSD values."""
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 xr.Dataset.
1084
+ """Initialize NormalizedGammaPSD from a dictionary or xarray.Dataset.
540
1085
 
541
- Args:
542
- parameters (dict or xr.Dataset): Parameters to initialize the class.
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
- NormalizedGammaPSD: An instance of NormalizedGammaPSD initialized with the parameters.
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
- interp_method (str): Interpolation method: 'step_left', 'step_right', 'linear' or 'pchip'.
589
- bin_edges (array-like): Sorted array of bin edge values.
590
- bin_values (array-like): Array of bin values corresponding to each bin.
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
- A function f(D) that returns the interpolated values.
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 Particle Size Distribution (PSD).
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
- This class represents a binned particle size distribution (PSD) that computes PSD values
644
- based on provided bin edges and corresponding PSD values. The PSD is evaluated via interpolation
645
- using one of several available methods.
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 : array_like
650
- A sequence of n+1 bin edge values defining the bins. The edges must be monotonically increasing.
651
- bin_psd : array_like
652
- A sequence of n PSD values corresponding to the intervals defined by bin_edges.
653
- interp_method : {'step_left', 'step_right', 'linear', 'pchip'}, optional
654
- The interpolation method used to compute the PSD values. The default is 'step_left'.
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
- For any input diameter (or diameters) D:
657
- - If D lies outside the range (bin_edges[0], bin_edges[-1]), the PSD value is set to 0.
658
- - The interpolation function is defined internally based on the chosen method.
659
- - PSD values are clipped to ensure they are non-negative.
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
- >>> # Values for D outside (bin_edges[0], bin_edges[-1]) are set to 0
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 moments."""
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 moments.
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, https://doi.org/10.1175/1520-0426(1991)008<0259:RPEFDR>2.0.CO;2
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 moments.
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 moments.
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
  ----------