disdrodb 0.1.3__py3-none-any.whl → 0.1.5__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/api/checks.py +70 -47
- disdrodb/api/configs.py +0 -2
- disdrodb/api/create_directories.py +0 -2
- disdrodb/api/info.py +3 -3
- disdrodb/api/io.py +48 -8
- disdrodb/api/path.py +116 -133
- disdrodb/api/search.py +12 -3
- disdrodb/cli/disdrodb_create_summary.py +113 -0
- disdrodb/cli/disdrodb_create_summary_station.py +11 -1
- disdrodb/cli/disdrodb_run_l0a_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0b_station.py +2 -2
- disdrodb/cli/disdrodb_run_l0c_station.py +2 -2
- disdrodb/cli/disdrodb_run_l1_station.py +2 -2
- disdrodb/cli/disdrodb_run_l2e_station.py +2 -2
- disdrodb/cli/disdrodb_run_l2m_station.py +2 -2
- disdrodb/constants.py +1 -1
- disdrodb/data_transfer/download_data.py +123 -7
- disdrodb/etc/products/L1/global.yaml +1 -1
- disdrodb/etc/products/L2E/5MIN.yaml +1 -0
- disdrodb/etc/products/L2E/global.yaml +1 -1
- disdrodb/etc/products/L2M/GAMMA_GS_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/GAMMA_ML.yaml +1 -1
- disdrodb/etc/products/L2M/LOGNORMAL_GS_LOG_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/LOGNORMAL_GS_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/LOGNORMAL_ML.yaml +8 -0
- disdrodb/etc/products/L2M/global.yaml +11 -3
- disdrodb/issue/writer.py +2 -0
- disdrodb/l0/check_configs.py +49 -16
- disdrodb/l0/configs/LPM/l0a_encodings.yml +2 -2
- disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +2 -2
- disdrodb/l0/configs/LPM/l0b_encodings.yml +2 -2
- disdrodb/l0/configs/LPM/raw_data_format.yml +2 -2
- disdrodb/l0/configs/PWS100/l0b_encodings.yml +1 -0
- disdrodb/l0/configs/SWS250/bins_diameter.yml +108 -0
- disdrodb/l0/configs/SWS250/bins_velocity.yml +83 -0
- disdrodb/l0/configs/SWS250/l0a_encodings.yml +18 -0
- disdrodb/l0/configs/SWS250/l0b_cf_attrs.yml +72 -0
- disdrodb/l0/configs/SWS250/l0b_encodings.yml +155 -0
- disdrodb/l0/configs/SWS250/raw_data_format.yml +148 -0
- disdrodb/l0/l0a_processing.py +10 -5
- disdrodb/l0/l0b_nc_processing.py +10 -6
- disdrodb/l0/l0b_processing.py +92 -72
- disdrodb/l0/l0c_processing.py +369 -251
- disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +8 -1
- disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +2 -2
- disdrodb/l0/readers/LPM/BELGIUM/ULIEGE.py +256 -0
- disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +2 -2
- disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +2 -2
- disdrodb/l0/readers/LPM/GERMANY/DWD.py +491 -0
- disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +2 -2
- disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +2 -2
- disdrodb/l0/readers/LPM/KIT/CHWALA.py +2 -2
- disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +107 -12
- disdrodb/l0/readers/LPM/SLOVENIA/UL.py +3 -3
- disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +2 -2
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +5 -14
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +5 -14
- disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL.py +117 -8
- disdrodb/l0/readers/PARSIVEL2/ARM/ARM_PARSIVEL2.py +4 -0
- disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +10 -14
- disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +10 -14
- disdrodb/l0/readers/PARSIVEL2/CANADA/UQAM_NC.py +69 -0
- disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +8 -14
- disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_raw.py +382 -0
- disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +4 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/GREECE/NOA.py +127 -0
- disdrodb/l0/readers/PARSIVEL2/ITALY/HYDROX.py +239 -0
- disdrodb/l0/readers/PARSIVEL2/MPI/BCO_PARSIVEL2.py +136 -0
- disdrodb/l0/readers/PARSIVEL2/MPI/BOWTIE.py +220 -0
- disdrodb/l0/readers/PARSIVEL2/NASA/LPVEX.py +109 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +5 -11
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +4 -17
- disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +5 -14
- disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +10 -13
- disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +10 -13
- disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +3 -0
- disdrodb/l0/readers/PARSIVEL2/PHILIPPINES/PANGASA.py +232 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +6 -18
- disdrodb/l0/readers/PARSIVEL2/SPAIN/GRANADA.py +120 -0
- disdrodb/l0/readers/PARSIVEL2/USA/C3WE.py +7 -25
- disdrodb/l0/readers/PWS100/AUSTRIA/HOAL.py +321 -0
- disdrodb/l0/readers/SW250/BELGIUM/KMI.py +239 -0
- disdrodb/l1/beard_model.py +31 -129
- disdrodb/l1/fall_velocity.py +156 -57
- disdrodb/l1/filters.py +25 -28
- disdrodb/l1/processing.py +12 -14
- disdrodb/l1_env/routines.py +46 -17
- disdrodb/l2/empirical_dsd.py +6 -0
- disdrodb/l2/processing.py +3 -3
- disdrodb/metadata/checks.py +132 -125
- disdrodb/metadata/geolocation.py +0 -2
- disdrodb/psd/fitting.py +180 -210
- disdrodb/psd/models.py +1 -1
- disdrodb/routines/__init__.py +54 -0
- disdrodb/{l0/routines.py → routines/l0.py} +288 -418
- disdrodb/{l1/routines.py → routines/l1.py} +60 -92
- disdrodb/{l2/routines.py → routines/l2.py} +284 -485
- disdrodb/{routines.py → routines/wrappers.py} +100 -7
- disdrodb/scattering/axis_ratio.py +95 -85
- disdrodb/scattering/permittivity.py +24 -0
- disdrodb/scattering/routines.py +56 -36
- disdrodb/summary/routines.py +147 -45
- disdrodb/utils/archiving.py +434 -0
- disdrodb/utils/attrs.py +2 -0
- disdrodb/utils/cli.py +5 -5
- disdrodb/utils/dask.py +62 -1
- disdrodb/utils/decorators.py +31 -0
- disdrodb/utils/encoding.py +10 -1
- disdrodb/{l2 → utils}/event.py +1 -66
- disdrodb/utils/logger.py +1 -1
- disdrodb/utils/manipulations.py +22 -12
- disdrodb/utils/routines.py +166 -0
- disdrodb/utils/time.py +5 -293
- disdrodb/utils/xarray.py +3 -0
- disdrodb/viz/plots.py +109 -15
- {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/METADATA +3 -2
- {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/RECORD +124 -96
- {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/entry_points.txt +1 -0
- {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/WHEEL +0 -0
- {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/top_level.txt +0 -0
disdrodb/psd/fitting.py
CHANGED
|
@@ -20,9 +20,10 @@ import scipy.stats as ss
|
|
|
20
20
|
import xarray as xr
|
|
21
21
|
from scipy.integrate import quad
|
|
22
22
|
from scipy.optimize import minimize
|
|
23
|
-
from scipy.special import gamma,
|
|
23
|
+
from scipy.special import gamma, gammaln # Regularized lower incomplete gamma function
|
|
24
24
|
|
|
25
25
|
from disdrodb.constants import DIAMETER_DIMENSION
|
|
26
|
+
from disdrodb.l1.fall_velocity import get_raindrop_fall_velocity_from_ds
|
|
26
27
|
from disdrodb.l2.empirical_dsd import (
|
|
27
28
|
get_median_volume_drop_diameter,
|
|
28
29
|
get_moment,
|
|
@@ -227,13 +228,13 @@ def get_expected_probabilities(params, cdf_func, pdf_func, bin_edges, probabilit
|
|
|
227
228
|
def get_adjusted_nt(cdf, params, Nt, bin_edges):
|
|
228
229
|
"""Adjust Nt for the proportion of missing drops. See Johnson's et al., 2013 Eqs. 3 and 4."""
|
|
229
230
|
# Estimate proportion of missing drops (Johnson's 2011 Eqs. 3)
|
|
230
|
-
# --> Alternative:
|
|
231
|
+
# --> Alternative:
|
|
232
|
+
# - p = 1 - np.sum(pdf(diameter, params)* diameter_bin_width) # [-]
|
|
233
|
+
# - p = 1 - np.sum((Lambda ** (mu + 1)) / gamma(mu + 1) * D**mu * np.exp(-Lambda * D) * diameter_bin_width) # [-]
|
|
231
234
|
p = 1 - np.diff(cdf([bin_edges[0], bin_edges[-1]], params)).item() # [-]
|
|
232
|
-
# Adjusts Nt for the proportion of drops
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return np.nan
|
|
236
|
-
return Nt / (1 - p) # [m-3]
|
|
235
|
+
# Adjusts Nt for the proportion of missing drops
|
|
236
|
+
nt_adj = np.nan if np.isclose(p, 1, atol=1e-12) else Nt / (1 - p) # [m-3]
|
|
237
|
+
return nt_adj
|
|
237
238
|
|
|
238
239
|
|
|
239
240
|
def compute_negative_log_likelihood(
|
|
@@ -606,7 +607,7 @@ def estimate_gamma_parameters(
|
|
|
606
607
|
|
|
607
608
|
"""
|
|
608
609
|
# Define initial guess for parameters
|
|
609
|
-
a = mu + 1 # (mu = a-1, a = mu+1)
|
|
610
|
+
a = mu + 1 # (mu = a-1, a = mu+1) (a > 0 --> mu=-1)
|
|
610
611
|
scale = 1 / Lambda
|
|
611
612
|
initial_params = [a, scale]
|
|
612
613
|
|
|
@@ -669,7 +670,7 @@ def estimate_gamma_parameters(
|
|
|
669
670
|
|
|
670
671
|
# Compute N0
|
|
671
672
|
# - Use logarithmic computations to prevent overflow
|
|
672
|
-
# - N0 = Nt * Lambda ** (mu + 1) / gamma(mu + 1)
|
|
673
|
+
# - N0 = Nt * Lambda ** (mu + 1) / gamma(mu + 1) # [m-3 * mm^(-mu-1)]
|
|
673
674
|
with suppress_warnings():
|
|
674
675
|
log_N0 = np.log(Nt) + (mu + 1) * np.log(Lambda) - gammaln(mu + 1)
|
|
675
676
|
N0 = np.exp(log_N0)
|
|
@@ -785,6 +786,7 @@ def get_gamma_parameters(
|
|
|
785
786
|
Likelihood function to use for fitting. The default value is ``multinomial``.
|
|
786
787
|
truncated_likelihood : bool, optional
|
|
787
788
|
Whether to use truncated likelihood. The default value is ``True``.
|
|
789
|
+
See Johnson et al., 2011 and 2011 for more information.
|
|
788
790
|
optimizer : str, optional
|
|
789
791
|
Optimization method to use. The default value is ``Nelder-Mead``.
|
|
790
792
|
|
|
@@ -802,6 +804,15 @@ def get_gamma_parameters(
|
|
|
802
804
|
The function uses `xr.apply_ufunc` to fit the lognormal distribution parameters
|
|
803
805
|
in parallel, leveraging Dask for parallel computation.
|
|
804
806
|
|
|
807
|
+
References
|
|
808
|
+
----------
|
|
809
|
+
Johnson, R. W., D. V. Kliche, and P. L. Smith, 2011: Comparison of Estimators for Parameters of Gamma Distributions
|
|
810
|
+
with Left-Truncated Samples. J. Appl. Meteor. Climatol., 50, 296-310, https://doi.org/10.1175/2010JAMC2478.1
|
|
811
|
+
|
|
812
|
+
Johnson, R.W., Kliche, D., & Smith, P.L. (2010).
|
|
813
|
+
Maximum likelihood estimation of gamma parameters for coarsely binned and truncated raindrop size data.
|
|
814
|
+
Quarterly Journal of the Royal Meteorological Society, 140. DOI:10.1002/qj.2209
|
|
815
|
+
|
|
805
816
|
"""
|
|
806
817
|
# Define inputs
|
|
807
818
|
counts = ds["drop_number_concentration"] * ds["diameter_bin_width"]
|
|
@@ -981,7 +992,7 @@ def get_exponential_parameters(
|
|
|
981
992
|
|
|
982
993
|
"""
|
|
983
994
|
# Define inputs
|
|
984
|
-
counts = ds["drop_number_concentration"] * ds["diameter_bin_width"]
|
|
995
|
+
counts = ds["drop_number_concentration"] * ds["diameter_bin_width"] # mm-1 m-3 --> m-3
|
|
985
996
|
diameter_breaks = get_diameter_bin_edges(ds)
|
|
986
997
|
|
|
987
998
|
# Define initial parameters (Lambda)
|
|
@@ -1022,163 +1033,6 @@ def get_exponential_parameters(
|
|
|
1022
1033
|
return ds_params
|
|
1023
1034
|
|
|
1024
1035
|
|
|
1025
|
-
####-------------------------------------------------------------------------------------------------------------------.
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
def _estimate_gamma_parameters_johnson(
|
|
1029
|
-
drop_number_concentration,
|
|
1030
|
-
diameter,
|
|
1031
|
-
diameter_breaks,
|
|
1032
|
-
output_dictionary=True,
|
|
1033
|
-
method="Nelder-Mead",
|
|
1034
|
-
mu=0.5,
|
|
1035
|
-
Lambda=3,
|
|
1036
|
-
**kwargs,
|
|
1037
|
-
):
|
|
1038
|
-
"""Deprecated Maximum likelihood estimation of Gamma model.
|
|
1039
|
-
|
|
1040
|
-
N(D) = N_t * lambda**(mu+1) / gamma(mu+1) D**mu exp(-lambda*D)
|
|
1041
|
-
|
|
1042
|
-
Args:
|
|
1043
|
-
spectra: The DSD for which to find parameters [mm-1 m-3].
|
|
1044
|
-
widths: Class widths for each DSD bin [mm].
|
|
1045
|
-
diams: Class-centre diameters for each DSD bin [mm].
|
|
1046
|
-
mu: Initial value for shape parameter mu [-].
|
|
1047
|
-
lambda_param: Initial value for slope parameter lambda [mm^-1].
|
|
1048
|
-
kwargs: Extra arguments for the optimization process.
|
|
1049
|
-
|
|
1050
|
-
Returns
|
|
1051
|
-
-------
|
|
1052
|
-
Dictionary with estimated mu, lambda, and N0.
|
|
1053
|
-
mu (shape) N0 (scale) lambda(slope)
|
|
1054
|
-
|
|
1055
|
-
Notes
|
|
1056
|
-
-----
|
|
1057
|
-
The last bin counts are not accounted in the fitting procedure !
|
|
1058
|
-
|
|
1059
|
-
References
|
|
1060
|
-
----------
|
|
1061
|
-
Johnson, R. W., D. V. Kliche, and P. L. Smith, 2011: Comparison of Estimators for Parameters of Gamma Distributions
|
|
1062
|
-
with Left-Truncated Samples. J. Appl. Meteor. Climatol., 50, 296-310, https://doi.org/10.1175/2010JAMC2478.1
|
|
1063
|
-
|
|
1064
|
-
Johnson, R.W., Kliche, D., & Smith, P.L. (2010).
|
|
1065
|
-
Maximum likelihood estimation of gamma parameters for coarsely binned and truncated raindrop size data.
|
|
1066
|
-
Quarterly Journal of the Royal Meteorological Society, 140. DOI:10.1002/qj.2209
|
|
1067
|
-
|
|
1068
|
-
"""
|
|
1069
|
-
# Initialize bad results
|
|
1070
|
-
if output_dictionary:
|
|
1071
|
-
null_output = {"mu": np.nan, "lambda": np.nan, "N0": np.nan}
|
|
1072
|
-
else:
|
|
1073
|
-
null_output = np.array([np.nan, np.nan, np.nan])
|
|
1074
|
-
|
|
1075
|
-
# Initialize parameters
|
|
1076
|
-
# --> Ideally with method of moments estimate
|
|
1077
|
-
# --> See equation 8 of Johnson's 2013
|
|
1078
|
-
x0 = [mu, Lambda]
|
|
1079
|
-
|
|
1080
|
-
# Compute diameter_bin_width
|
|
1081
|
-
diameter_bin_width = np.diff(diameter_breaks)
|
|
1082
|
-
|
|
1083
|
-
# Convert drop_number_concentration from mm-1 m-3 to m-3.
|
|
1084
|
-
spectra = np.asarray(drop_number_concentration) * diameter_bin_width
|
|
1085
|
-
|
|
1086
|
-
# Define cost function
|
|
1087
|
-
# - Parameter to be optimized on first positions
|
|
1088
|
-
def _cost_function(parameters, spectra, diameter_breaks):
|
|
1089
|
-
# Assume spectra to be in unit [m-3] (drop_number_concentration*diameter_bin_width) !
|
|
1090
|
-
mu, Lambda = parameters
|
|
1091
|
-
# Precompute gamma integrals between various diameter bins
|
|
1092
|
-
# - gamminc(mu+1) already divides the integral by gamma(mu+1) !
|
|
1093
|
-
pgamma_d = gammainc(mu + 1, Lambda * diameter_breaks)
|
|
1094
|
-
# Compute probability with interval
|
|
1095
|
-
delta_pgamma_bins = pgamma_d[1:] - pgamma_d[:-1]
|
|
1096
|
-
# Compute normalization over interval
|
|
1097
|
-
denominator = pgamma_d[-1] - pgamma_d[0]
|
|
1098
|
-
# Compute cost function
|
|
1099
|
-
# a = mu - 1, x = lambda
|
|
1100
|
-
if mu > -1 and Lambda > 0:
|
|
1101
|
-
cost = np.sum(-spectra * np.log(delta_pgamma_bins / denominator))
|
|
1102
|
-
return cost
|
|
1103
|
-
return np.inf
|
|
1104
|
-
|
|
1105
|
-
# Minimize the cost function
|
|
1106
|
-
with suppress_warnings():
|
|
1107
|
-
bounds = [(0, None), (0, None)] # Force mu and lambda to be non-negative
|
|
1108
|
-
res = minimize(
|
|
1109
|
-
_cost_function,
|
|
1110
|
-
x0=x0,
|
|
1111
|
-
args=(spectra, diameter_breaks),
|
|
1112
|
-
method=method,
|
|
1113
|
-
bounds=bounds,
|
|
1114
|
-
**kwargs,
|
|
1115
|
-
)
|
|
1116
|
-
|
|
1117
|
-
# Check if the fit had success
|
|
1118
|
-
if not res.success:
|
|
1119
|
-
return null_output
|
|
1120
|
-
|
|
1121
|
-
# Extract parameters
|
|
1122
|
-
mu = res.x[0] # [-]
|
|
1123
|
-
Lambda = res.x[1] # [mm-1]
|
|
1124
|
-
|
|
1125
|
-
# Estimate tilde_N_T using the total drop concentration
|
|
1126
|
-
tilde_N_T = np.sum(drop_number_concentration * diameter_bin_width) # [m-3]
|
|
1127
|
-
|
|
1128
|
-
# Estimate proportion of missing drops (Johnson's 2011 Eqs. 3)
|
|
1129
|
-
with suppress_warnings():
|
|
1130
|
-
D = diameter
|
|
1131
|
-
p = 1 - np.sum((Lambda ** (mu + 1)) / gamma(mu + 1) * D**mu * np.exp(-Lambda * D) * diameter_bin_width) # [-]
|
|
1132
|
-
|
|
1133
|
-
# Convert tilde_N_T to N_T using Johnson's 2013 Eqs. 3 and 4.
|
|
1134
|
-
# - Adjusts for the proportion of drops not obs
|
|
1135
|
-
N_T = tilde_N_T / (1 - p) # [m-3]
|
|
1136
|
-
|
|
1137
|
-
# Compute N0
|
|
1138
|
-
N0 = N_T * (Lambda ** (mu + 1)) / gamma(mu + 1) # [m-3 * mm^(-mu-1)]
|
|
1139
|
-
|
|
1140
|
-
# Compute Dm
|
|
1141
|
-
# Dm = (mu + 4)/ Lambda
|
|
1142
|
-
|
|
1143
|
-
# Compute Nw
|
|
1144
|
-
# Nw = N0* D^mu / f(mu) , with f(mu of the Normalized PSD)
|
|
1145
|
-
|
|
1146
|
-
# Define output
|
|
1147
|
-
output = {"mu": mu, "Lambda": Lambda, "N0": N0} if output_dictionary else np.array([mu, Lambda, N0])
|
|
1148
|
-
return output
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
def get_gamma_parameters_johnson2014(ds, method="Nelder-Mead"):
|
|
1152
|
-
"""Deprecated model. See Gamma Model with truncated_likelihood and 'pdf'."""
|
|
1153
|
-
drop_number_concentration = ds["drop_number_concentration"]
|
|
1154
|
-
diameter = ds["diameter_bin_center"]
|
|
1155
|
-
diameter_breaks = get_diameter_bin_edges(ds)
|
|
1156
|
-
|
|
1157
|
-
# Define kwargs
|
|
1158
|
-
kwargs = {
|
|
1159
|
-
"output_dictionary": False,
|
|
1160
|
-
"diameter_breaks": diameter_breaks,
|
|
1161
|
-
"method": method,
|
|
1162
|
-
}
|
|
1163
|
-
da_params = xr.apply_ufunc(
|
|
1164
|
-
_estimate_gamma_parameters_johnson,
|
|
1165
|
-
drop_number_concentration,
|
|
1166
|
-
diameter,
|
|
1167
|
-
# diameter_bin_width,
|
|
1168
|
-
kwargs=kwargs,
|
|
1169
|
-
input_core_dims=[[DIAMETER_DIMENSION], [DIAMETER_DIMENSION]], # [DIAMETER_DIMENSION],
|
|
1170
|
-
output_core_dims=[["parameters"]],
|
|
1171
|
-
vectorize=True,
|
|
1172
|
-
)
|
|
1173
|
-
|
|
1174
|
-
# Add parameters coordinates
|
|
1175
|
-
da_params = da_params.assign_coords({"parameters": ["mu", "Lambda", "N0"]})
|
|
1176
|
-
|
|
1177
|
-
# Convert to skill Dataset
|
|
1178
|
-
ds_params = da_params.to_dataset(dim="parameters")
|
|
1179
|
-
return ds_params
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
1036
|
####-----------------------------------------------------------------------------------------.
|
|
1183
1037
|
#### Grid Search (GS)
|
|
1184
1038
|
|
|
@@ -1202,24 +1056,60 @@ def _compute_z(ND, D, dD):
|
|
|
1202
1056
|
return Z
|
|
1203
1057
|
|
|
1204
1058
|
|
|
1059
|
+
def _compute_target_variable_error(target, ND_obs, ND_preds, D, dD, V):
|
|
1060
|
+
if target == "Z":
|
|
1061
|
+
errors = np.abs(_compute_z(ND_obs, D, dD) - _compute_z(ND_preds, D, dD))
|
|
1062
|
+
elif target == "R":
|
|
1063
|
+
errors = np.abs(_compute_rain_rate(ND_obs, D, dD, V) - _compute_rain_rate(ND_preds, D, dD, V))
|
|
1064
|
+
else: # if target == "LWC":
|
|
1065
|
+
errors = np.abs(_compute_lwc(ND_obs, D, dD) - _compute_lwc(ND_preds, D, dD))
|
|
1066
|
+
return errors
|
|
1067
|
+
|
|
1068
|
+
|
|
1205
1069
|
def _compute_cost_function(ND_obs, ND_preds, D, dD, V, target, transformation, error_order):
|
|
1206
1070
|
# Assume ND_obs of shape (D bins) and ND_preds of shape (# params, D bins)
|
|
1207
1071
|
if target == "ND":
|
|
1208
1072
|
if transformation == "identity":
|
|
1209
1073
|
errors = np.mean(np.abs(ND_obs[None, :] - ND_preds) ** error_order, axis=1)
|
|
1074
|
+
return errors
|
|
1210
1075
|
if transformation == "log":
|
|
1211
1076
|
errors = np.mean(np.abs(np.log(ND_obs[None, :] + 1) - np.log(ND_preds + 1)) ** error_order, axis=1)
|
|
1212
|
-
|
|
1077
|
+
return errors
|
|
1078
|
+
if transformation == "sqrt":
|
|
1213
1079
|
errors = np.mean(np.abs(np.sqrt(ND_obs[None, :]) - np.sqrt(ND_preds)) ** error_order, axis=1)
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1080
|
+
return errors
|
|
1081
|
+
# if target in ["Z", "R", "LWC"]:
|
|
1082
|
+
return _compute_target_variable_error(target, ND_obs, ND_preds, D, dD, V)
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
def define_param_range(center, step, bounds, factor=2, refinement=20):
|
|
1086
|
+
"""
|
|
1087
|
+
Create a refined parameter search range around a center value, constrained to bounds.
|
|
1088
|
+
|
|
1089
|
+
Parameters
|
|
1090
|
+
----------
|
|
1091
|
+
center : float
|
|
1092
|
+
Center of the range (e.g., current best estimate).
|
|
1093
|
+
step : float
|
|
1094
|
+
Coarse step size used in the first search.
|
|
1095
|
+
bounds : tuple of (float, float)
|
|
1096
|
+
Lower and upper bounds (can include -np.inf, np.inf).
|
|
1097
|
+
factor : float, optional
|
|
1098
|
+
How wide the refined range extends from the center (in multiples of step).
|
|
1099
|
+
Default = 2.
|
|
1100
|
+
refinement : int, optional
|
|
1101
|
+
Factor to refine the step size (smaller step = finer grid).
|
|
1102
|
+
Default = 20.
|
|
1103
|
+
|
|
1104
|
+
Returns
|
|
1105
|
+
-------
|
|
1106
|
+
np.ndarray
|
|
1107
|
+
Array of values constrained to bounds.
|
|
1108
|
+
"""
|
|
1109
|
+
lower = max(center - factor * step, bounds[0])
|
|
1110
|
+
upper = min(center + factor * step, bounds[1])
|
|
1111
|
+
new_step = step / refinement
|
|
1112
|
+
return np.arange(lower, upper, new_step)
|
|
1223
1113
|
|
|
1224
1114
|
|
|
1225
1115
|
def apply_exponential_gs(
|
|
@@ -1255,9 +1145,15 @@ def apply_exponential_gs(
|
|
|
1255
1145
|
transformation=transformation,
|
|
1256
1146
|
error_order=error_order,
|
|
1257
1147
|
)
|
|
1148
|
+
# Replace inf with NaN
|
|
1149
|
+
errors[~np.isfinite(errors)] = np.nan
|
|
1150
|
+
|
|
1151
|
+
# If all invalid, return NaN parameters
|
|
1152
|
+
if np.all(np.isnan(errors)):
|
|
1153
|
+
return np.array([np.nan, np.nan])
|
|
1258
1154
|
|
|
1259
|
-
#
|
|
1260
|
-
best_index = np.
|
|
1155
|
+
# Otherwise, choose the best index
|
|
1156
|
+
best_index = np.nanargmin(errors)
|
|
1261
1157
|
return np.array([N0_arr[best_index].item(), lambda_arr[best_index].item()])
|
|
1262
1158
|
|
|
1263
1159
|
|
|
@@ -1286,8 +1182,15 @@ def _apply_gamma_gs(mu_values, lambda_values, Nt, ND_obs, D, dD, V, target, tran
|
|
|
1286
1182
|
error_order=error_order,
|
|
1287
1183
|
)
|
|
1288
1184
|
|
|
1289
|
-
#
|
|
1290
|
-
|
|
1185
|
+
# Replace inf with NaN
|
|
1186
|
+
errors[~np.isfinite(errors)] = np.nan
|
|
1187
|
+
|
|
1188
|
+
# If all invalid, return NaN parameters
|
|
1189
|
+
if np.all(np.isnan(errors)):
|
|
1190
|
+
return np.array([np.nan, np.nan, np.nan])
|
|
1191
|
+
|
|
1192
|
+
# Otherwise, choose the best index
|
|
1193
|
+
best_index = np.nanargmin(errors)
|
|
1291
1194
|
return N0[best_index].item(), mu_arr[best_index].item(), lambda_arr[best_index].item()
|
|
1292
1195
|
|
|
1293
1196
|
|
|
@@ -1304,10 +1207,14 @@ def apply_gamma_gs(
|
|
|
1304
1207
|
error_order,
|
|
1305
1208
|
):
|
|
1306
1209
|
"""Estimate GammaPSD model parameters using Grid Search."""
|
|
1210
|
+
# Define parameters bounds
|
|
1211
|
+
mu_bounds = (-1, 40)
|
|
1212
|
+
lambda_bounds = (0, 60)
|
|
1213
|
+
|
|
1307
1214
|
# Define initial set of parameters
|
|
1308
|
-
mu_step = 0.
|
|
1215
|
+
mu_step = 0.25
|
|
1309
1216
|
lambda_step = 0.5
|
|
1310
|
-
mu_values = np.arange(0
|
|
1217
|
+
mu_values = np.arange(0, 40, step=mu_step)
|
|
1311
1218
|
lambda_values = np.arange(0, 60, step=lambda_step)
|
|
1312
1219
|
|
|
1313
1220
|
# First round of GS
|
|
@@ -1323,10 +1230,13 @@ def apply_gamma_gs(
|
|
|
1323
1230
|
transformation=transformation,
|
|
1324
1231
|
error_order=error_order,
|
|
1325
1232
|
)
|
|
1233
|
+
if np.isnan(N0): # if np.nan, return immediately
|
|
1234
|
+
return np.array([N0, mu, Lambda])
|
|
1326
1235
|
|
|
1327
1236
|
# Second round of GS
|
|
1328
|
-
mu_values =
|
|
1329
|
-
lambda_values =
|
|
1237
|
+
mu_values = define_param_range(mu, mu_step, bounds=mu_bounds)
|
|
1238
|
+
lambda_values = define_param_range(Lambda, lambda_step, bounds=lambda_bounds)
|
|
1239
|
+
|
|
1330
1240
|
N0, mu, Lambda = _apply_gamma_gs(
|
|
1331
1241
|
mu_values=mu_values,
|
|
1332
1242
|
lambda_values=lambda_values,
|
|
@@ -1367,8 +1277,15 @@ def _apply_lognormal_gs(mu_values, sigma_values, Nt, ND_obs, D, dD, V, target, t
|
|
|
1367
1277
|
error_order=error_order,
|
|
1368
1278
|
)
|
|
1369
1279
|
|
|
1370
|
-
#
|
|
1371
|
-
|
|
1280
|
+
# Replace inf with NaN
|
|
1281
|
+
errors[~np.isfinite(errors)] = np.nan
|
|
1282
|
+
|
|
1283
|
+
# If all invalid, return NaN parameters
|
|
1284
|
+
if np.all(np.isnan(errors)):
|
|
1285
|
+
return np.array([np.nan, np.nan, np.nan])
|
|
1286
|
+
|
|
1287
|
+
# Otherwise, choose the best index
|
|
1288
|
+
best_index = np.nanargmin(errors)
|
|
1372
1289
|
return Nt, mu_arr[best_index].item(), sigma_arr[best_index].item()
|
|
1373
1290
|
|
|
1374
1291
|
|
|
@@ -1385,11 +1302,19 @@ def apply_lognormal_gs(
|
|
|
1385
1302
|
error_order,
|
|
1386
1303
|
):
|
|
1387
1304
|
"""Estimate LognormalPSD model parameters using Grid Search."""
|
|
1305
|
+
# Define parameters bounds
|
|
1306
|
+
sigma_bounds = (0, np.inf) # > 0
|
|
1307
|
+
scale_bounds = (0, np.inf) # > 0
|
|
1308
|
+
# mu_bounds = (- np.inf, np.inf) # mu = np.log(scale)
|
|
1309
|
+
|
|
1388
1310
|
# Define initial set of parameters
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1311
|
+
# --> Typically sigma between 0 and 3
|
|
1312
|
+
# --> Typically mu between -2 and 2
|
|
1313
|
+
scale_step = 0.2
|
|
1314
|
+
sigma_step = 0.2
|
|
1315
|
+
scale_values = np.arange(scale_step, 20, step=scale_step)
|
|
1316
|
+
mu_values = np.log(scale_values)
|
|
1317
|
+
sigma_values = np.arange(0, 3, step=sigma_step)
|
|
1393
1318
|
|
|
1394
1319
|
# First round of GS
|
|
1395
1320
|
Nt, mu, sigma = _apply_lognormal_gs(
|
|
@@ -1404,10 +1329,14 @@ def apply_lognormal_gs(
|
|
|
1404
1329
|
transformation=transformation,
|
|
1405
1330
|
error_order=error_order,
|
|
1406
1331
|
)
|
|
1332
|
+
if np.isnan(mu): # if np.nan, return immediately
|
|
1333
|
+
return np.array([Nt, mu, sigma])
|
|
1407
1334
|
|
|
1408
1335
|
# Second round of GS
|
|
1409
|
-
|
|
1410
|
-
|
|
1336
|
+
sigma_values = define_param_range(sigma, sigma_step, bounds=sigma_bounds)
|
|
1337
|
+
scale_values = define_param_range(np.exp(mu), scale_step, bounds=scale_bounds)
|
|
1338
|
+
with suppress_warnings():
|
|
1339
|
+
mu_values = np.log(scale_values)
|
|
1411
1340
|
Nt, mu, sigma = _apply_lognormal_gs(
|
|
1412
1341
|
mu_values=mu_values,
|
|
1413
1342
|
sigma_values=sigma_values,
|
|
@@ -1439,7 +1368,7 @@ def apply_normalized_gamma_gs(
|
|
|
1439
1368
|
):
|
|
1440
1369
|
"""Estimate NormalizedGammaPSD model parameters using Grid Search."""
|
|
1441
1370
|
# Define set of mu values
|
|
1442
|
-
mu_arr = np.arange(
|
|
1371
|
+
mu_arr = np.arange(-4, 30, step=0.01)
|
|
1443
1372
|
|
|
1444
1373
|
# Perform grid search
|
|
1445
1374
|
with suppress_warnings():
|
|
@@ -1458,8 +1387,16 @@ def apply_normalized_gamma_gs(
|
|
|
1458
1387
|
error_order=error_order,
|
|
1459
1388
|
)
|
|
1460
1389
|
|
|
1461
|
-
#
|
|
1462
|
-
|
|
1390
|
+
# Replace inf with NaN
|
|
1391
|
+
errors[~np.isfinite(errors)] = np.nan
|
|
1392
|
+
|
|
1393
|
+
# If all invalid, return NaN parameters
|
|
1394
|
+
if np.all(np.isnan(errors)):
|
|
1395
|
+
return np.array([np.nan, np.nan, np.nan])
|
|
1396
|
+
|
|
1397
|
+
# Otherwise, choose the best index
|
|
1398
|
+
best_index = np.nanargmin(errors)
|
|
1399
|
+
mu = mu_arr[best_index]
|
|
1463
1400
|
return np.array([Nw, mu, D50])
|
|
1464
1401
|
|
|
1465
1402
|
|
|
@@ -1733,6 +1670,8 @@ def get_exponential_parameters_Zhang2008(moment_l, moment_m, l, m): # noqa: E74
|
|
|
1733
1670
|
Meteor. Climatol.,
|
|
1734
1671
|
https://doi.org/10.1175/2008JAMC1876.1
|
|
1735
1672
|
"""
|
|
1673
|
+
if l == m:
|
|
1674
|
+
raise ValueError("Equal l and m moment orders are not allowed.")
|
|
1736
1675
|
num = moment_l * gamma(m + 1)
|
|
1737
1676
|
den = moment_m * gamma(l + 1)
|
|
1738
1677
|
Lambda = np.power(num / den, (1 / (m - l)))
|
|
@@ -1757,21 +1696,21 @@ def get_exponential_parameters_M34(moment_3, moment_4):
|
|
|
1757
1696
|
return N0, Lambda
|
|
1758
1697
|
|
|
1759
1698
|
|
|
1760
|
-
def get_gamma_parameters_M012(M0, M1, M2):
|
|
1761
|
-
|
|
1699
|
+
# def get_gamma_parameters_M012(M0, M1, M2):
|
|
1700
|
+
# """Compute gamma distribution parameters following Cao et al., 2009.
|
|
1762
1701
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1702
|
+
# References
|
|
1703
|
+
# ----------
|
|
1704
|
+
# Cao, Q., and G. Zhang, 2009:
|
|
1705
|
+
# Errors in Estimating Raindrop Size Distribution Parameters Employing Disdrometer and Simulated Raindrop Spectra.
|
|
1706
|
+
# J. Appl. Meteor. Climatol., 48, 406-425, https://doi.org/10.1175/2008JAMC2026.1.
|
|
1707
|
+
# """
|
|
1708
|
+
# # TODO: really bad results. check formula !
|
|
1709
|
+
# G = M1**3 / M0 / M2
|
|
1710
|
+
# mu = 1 / (1 - G) - 2
|
|
1711
|
+
# Lambda = M0 / M1 * (mu + 1)
|
|
1712
|
+
# N0 = Lambda ** (mu + 1) * M0 / gamma(mu + 1)
|
|
1713
|
+
# return N0, mu, Lambda
|
|
1775
1714
|
|
|
1776
1715
|
|
|
1777
1716
|
def get_gamma_parameters_M234(M2, M3, M4):
|
|
@@ -2415,6 +2354,10 @@ def get_gs_parameters(ds, psd_model, target="ND", transformation="log", error_or
|
|
|
2415
2354
|
# Check valid transformation
|
|
2416
2355
|
transformation = check_transformation(transformation)
|
|
2417
2356
|
|
|
2357
|
+
# Check fall velocity is available if target R
|
|
2358
|
+
if "fall_velocity" not in ds:
|
|
2359
|
+
ds["fall_velocity"] = get_raindrop_fall_velocity_from_ds(ds)
|
|
2360
|
+
|
|
2418
2361
|
# Retrieve estimation function
|
|
2419
2362
|
func = OPTIMIZATION_ROUTINES_DICT["GS"][psd_model]
|
|
2420
2363
|
|
|
@@ -2437,6 +2380,25 @@ def get_gs_parameters(ds, psd_model, target="ND", transformation="log", error_or
|
|
|
2437
2380
|
return ds_params
|
|
2438
2381
|
|
|
2439
2382
|
|
|
2383
|
+
def sanitize_drop_number_concentration(drop_number_concentration):
|
|
2384
|
+
"""Sanitize drop number concentration array.
|
|
2385
|
+
|
|
2386
|
+
If N(D) is all zero or contain not finite values, set everything to np.nan
|
|
2387
|
+
"""
|
|
2388
|
+
# Condition 1: all zeros along diameter_bin_center
|
|
2389
|
+
all_zero = (drop_number_concentration == 0).all(dim="diameter_bin_center")
|
|
2390
|
+
|
|
2391
|
+
# Condition 2: any non-finite along diameter_bin_center
|
|
2392
|
+
any_nonfinite = (~np.isfinite(drop_number_concentration)).any(dim="diameter_bin_center")
|
|
2393
|
+
|
|
2394
|
+
# Combine conditions
|
|
2395
|
+
invalid = all_zero | any_nonfinite
|
|
2396
|
+
|
|
2397
|
+
# Replace entire profile with NaN where invalid
|
|
2398
|
+
drop_number_concentration = drop_number_concentration.where(~invalid, np.nan)
|
|
2399
|
+
return drop_number_concentration
|
|
2400
|
+
|
|
2401
|
+
|
|
2440
2402
|
def estimate_model_parameters(
|
|
2441
2403
|
ds,
|
|
2442
2404
|
psd_model,
|
|
@@ -2444,10 +2406,18 @@ def estimate_model_parameters(
|
|
|
2444
2406
|
optimization_kwargs=None,
|
|
2445
2407
|
):
|
|
2446
2408
|
"""Routine to estimate PSD model parameters."""
|
|
2409
|
+
# Check inputs arguments
|
|
2447
2410
|
optimization_kwargs = {} if optimization_kwargs is None else optimization_kwargs
|
|
2448
2411
|
optimization = check_optimization(optimization)
|
|
2449
2412
|
check_optimization_kwargs(optimization_kwargs=optimization_kwargs, optimization=optimization, psd_model=psd_model)
|
|
2450
2413
|
|
|
2414
|
+
# Check N(D)
|
|
2415
|
+
# --> If all 0, set to np.nan
|
|
2416
|
+
# --> If any is not finite --> set to np.nan
|
|
2417
|
+
if "drop_number_concentration" not in ds:
|
|
2418
|
+
raise ValueError("'drop_number_concentration' variable not present in input xarray.Dataset.")
|
|
2419
|
+
ds["drop_number_concentration"] = sanitize_drop_number_concentration(ds["drop_number_concentration"])
|
|
2420
|
+
|
|
2451
2421
|
# Define function
|
|
2452
2422
|
dict_func = {
|
|
2453
2423
|
"ML": get_ml_parameters,
|
disdrodb/psd/models.py
CHANGED
|
@@ -93,7 +93,7 @@ def get_psd_model_formula(psd_model):
|
|
|
93
93
|
return PSD_MODELS_DICT[psd_model].formula
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def create_psd(psd_model, parameters):
|
|
96
|
+
def create_psd(psd_model, parameters):
|
|
97
97
|
"""Define a PSD from a dictionary or xr.Dataset of parameters."""
|
|
98
98
|
psd_class = get_psd_model(psd_model)
|
|
99
99
|
psd = psd_class.from_parameters(parameters)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------.
|
|
2
|
+
# Copyright (c) 2021-2023 DISDRODB developers
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
# -----------------------------------------------------------------------------.
|
|
17
|
+
"""DISDRODB L0 software."""
|
|
18
|
+
from disdrodb.routines.wrappers import (
|
|
19
|
+
create_summary,
|
|
20
|
+
create_summary_station,
|
|
21
|
+
run_l0,
|
|
22
|
+
run_l0_station,
|
|
23
|
+
run_l0a,
|
|
24
|
+
run_l0a_station,
|
|
25
|
+
run_l0b,
|
|
26
|
+
run_l0b_station,
|
|
27
|
+
run_l0c,
|
|
28
|
+
run_l0c_station,
|
|
29
|
+
run_l1,
|
|
30
|
+
run_l1_station,
|
|
31
|
+
run_l2e,
|
|
32
|
+
run_l2e_station,
|
|
33
|
+
run_l2m,
|
|
34
|
+
run_l2m_station,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"create_summary",
|
|
39
|
+
"create_summary_station",
|
|
40
|
+
"run_l0",
|
|
41
|
+
"run_l0_station",
|
|
42
|
+
"run_l0a",
|
|
43
|
+
"run_l0a_station",
|
|
44
|
+
"run_l0b",
|
|
45
|
+
"run_l0b_station",
|
|
46
|
+
"run_l0c",
|
|
47
|
+
"run_l0c_station",
|
|
48
|
+
"run_l1",
|
|
49
|
+
"run_l1_station",
|
|
50
|
+
"run_l2e",
|
|
51
|
+
"run_l2e_station",
|
|
52
|
+
"run_l2m",
|
|
53
|
+
"run_l2m_station",
|
|
54
|
+
]
|