disdrodb 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. disdrodb/__init__.py +64 -34
  2. disdrodb/_config.py +5 -4
  3. disdrodb/_version.py +16 -3
  4. disdrodb/accessor/__init__.py +20 -0
  5. disdrodb/accessor/methods.py +125 -0
  6. disdrodb/api/checks.py +139 -9
  7. disdrodb/api/configs.py +4 -2
  8. disdrodb/api/info.py +10 -10
  9. disdrodb/api/io.py +237 -18
  10. disdrodb/api/path.py +81 -75
  11. disdrodb/api/search.py +6 -6
  12. disdrodb/cli/disdrodb_create_summary_station.py +91 -0
  13. disdrodb/cli/disdrodb_run_l0.py +1 -1
  14. disdrodb/cli/disdrodb_run_l0_station.py +1 -1
  15. disdrodb/cli/disdrodb_run_l0b.py +1 -1
  16. disdrodb/cli/disdrodb_run_l0b_station.py +1 -1
  17. disdrodb/cli/disdrodb_run_l0c.py +1 -1
  18. disdrodb/cli/disdrodb_run_l0c_station.py +1 -1
  19. disdrodb/cli/disdrodb_run_l2e_station.py +1 -1
  20. disdrodb/configs.py +149 -4
  21. disdrodb/constants.py +61 -0
  22. disdrodb/data_transfer/download_data.py +5 -5
  23. disdrodb/etc/configs/attributes.yaml +339 -0
  24. disdrodb/etc/configs/encodings.yaml +473 -0
  25. disdrodb/etc/products/L1/global.yaml +13 -0
  26. disdrodb/etc/products/L2E/10MIN.yaml +12 -0
  27. disdrodb/etc/products/L2E/1MIN.yaml +1 -0
  28. disdrodb/etc/products/L2E/global.yaml +22 -0
  29. disdrodb/etc/products/L2M/10MIN.yaml +12 -0
  30. disdrodb/etc/products/L2M/GAMMA_ML.yaml +8 -0
  31. disdrodb/etc/products/L2M/NGAMMA_GS_LOG_ND_MAE.yaml +6 -0
  32. disdrodb/etc/products/L2M/NGAMMA_GS_ND_MAE.yaml +6 -0
  33. disdrodb/etc/products/L2M/NGAMMA_GS_Z_MAE.yaml +6 -0
  34. disdrodb/etc/products/L2M/global.yaml +26 -0
  35. disdrodb/l0/__init__.py +13 -0
  36. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +4 -4
  37. disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
  38. disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +3 -3
  39. disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
  40. disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +5 -5
  41. disdrodb/l0/configs/PARSIVEL2/l0b_encodings.yml +3 -3
  42. disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +1 -1
  43. disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +4 -4
  44. disdrodb/l0/configs/PWS100/raw_data_format.yml +1 -1
  45. disdrodb/l0/l0a_processing.py +30 -30
  46. disdrodb/l0/l0b_nc_processing.py +108 -2
  47. disdrodb/l0/l0b_processing.py +4 -4
  48. disdrodb/l0/l0c_processing.py +5 -13
  49. disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +66 -0
  50. disdrodb/l0/readers/LPM/SLOVENIA/{CRNI_VRH.py → UL.py} +3 -0
  51. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +195 -0
  52. disdrodb/l0/readers/PARSIVEL/GPM/PIERS.py +0 -2
  53. disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +4 -1
  54. disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
  55. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
  56. disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +168 -0
  57. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +165 -0
  58. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +69 -0
  59. disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +255 -134
  60. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +525 -0
  61. disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -1
  62. disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +9 -7
  63. disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
  64. disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +123 -0
  65. disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +120 -0
  66. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +1 -0
  67. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
  68. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +126 -0
  69. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +165 -0
  70. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
  71. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +20 -12
  72. disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +2 -0
  73. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +144 -0
  74. disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +201 -0
  75. disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +137 -0
  76. disdrodb/l0/readers/PARSIVEL2/{NETHERLANDS/DELFT.py → USA/C3WE.py} +65 -85
  77. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +105 -99
  78. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +151 -0
  79. disdrodb/l0/routines.py +105 -14
  80. disdrodb/l1/__init__.py +5 -0
  81. disdrodb/l1/filters.py +34 -20
  82. disdrodb/l1/processing.py +45 -44
  83. disdrodb/l1/resampling.py +77 -66
  84. disdrodb/l1/routines.py +35 -43
  85. disdrodb/l1_env/routines.py +18 -3
  86. disdrodb/l2/__init__.py +7 -0
  87. disdrodb/l2/empirical_dsd.py +58 -10
  88. disdrodb/l2/event.py +27 -120
  89. disdrodb/l2/processing.py +267 -116
  90. disdrodb/l2/routines.py +618 -254
  91. disdrodb/metadata/standards.py +3 -1
  92. disdrodb/psd/fitting.py +463 -144
  93. disdrodb/psd/models.py +8 -5
  94. disdrodb/routines.py +3 -3
  95. disdrodb/scattering/__init__.py +16 -4
  96. disdrodb/scattering/axis_ratio.py +56 -36
  97. disdrodb/scattering/permittivity.py +486 -0
  98. disdrodb/scattering/routines.py +701 -159
  99. disdrodb/summary/__init__.py +17 -0
  100. disdrodb/summary/routines.py +4120 -0
  101. disdrodb/utils/attrs.py +68 -125
  102. disdrodb/utils/compression.py +30 -1
  103. disdrodb/utils/dask.py +59 -8
  104. disdrodb/utils/dataframe.py +61 -7
  105. disdrodb/utils/directories.py +35 -15
  106. disdrodb/utils/encoding.py +33 -19
  107. disdrodb/utils/logger.py +13 -6
  108. disdrodb/utils/manipulations.py +71 -0
  109. disdrodb/utils/subsetting.py +214 -0
  110. disdrodb/utils/time.py +165 -19
  111. disdrodb/utils/writer.py +20 -7
  112. disdrodb/utils/xarray.py +2 -4
  113. disdrodb/viz/__init__.py +13 -0
  114. disdrodb/viz/plots.py +327 -0
  115. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/METADATA +3 -2
  116. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/RECORD +121 -88
  117. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/entry_points.txt +1 -0
  118. disdrodb/l1/encoding_attrs.py +0 -642
  119. disdrodb/l2/processing_options.py +0 -213
  120. /disdrodb/l0/readers/PARSIVEL/SLOVENIA/{UL_FGG.py → UL.py} +0 -0
  121. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/WHEEL +0 -0
  122. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/licenses/LICENSE +0 -0
  123. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/top_level.txt +0 -0
@@ -17,32 +17,55 @@
17
17
  """Implement PSD scattering routines."""
18
18
 
19
19
  import itertools
20
+ import logging
21
+ import os
20
22
 
21
23
  import dask
22
24
  import numpy as np
23
25
  import xarray as xr
24
- from pytmatrix import orientation, radar, refractive, tmatrix_aux
25
- from pytmatrix.psd import BinnedPSD, PSDIntegrator
26
- from pytmatrix.tmatrix import Scatterer
27
26
 
28
- from disdrodb.psd.models import create_psd, get_required_parameters
29
- from disdrodb.scattering.axis_ratio import check_axis_ratio, get_axis_ratio_method
27
+ from disdrodb.configs import get_scattering_table_dir
28
+ from disdrodb.constants import DIAMETER_DIMENSION
29
+ from disdrodb.l1.filters import filter_diameter_bins
30
+ from disdrodb.psd.models import BinnedPSD, create_psd, get_required_parameters
31
+ from disdrodb.scattering.axis_ratio import check_axis_ratio_model, get_axis_ratio_model
32
+ from disdrodb.scattering.permittivity import (
33
+ check_permittivity_model,
34
+ get_rayleigh_dielectric_factor,
35
+ get_refractive_index,
36
+ )
37
+ from disdrodb.utils.logger import log_info
38
+ from disdrodb.utils.manipulations import get_diameter_bin_edges
30
39
  from disdrodb.utils.warnings import suppress_warnings
31
40
 
32
- # Wavelengths for which the refractive index is defined in pytmatrix (in mm)
33
- wavelength_dict = {
34
- "S": tmatrix_aux.wl_S,
35
- "C": tmatrix_aux.wl_C,
36
- "X": tmatrix_aux.wl_X,
37
- "Ku": tmatrix_aux.wl_Ku,
38
- "Ka": tmatrix_aux.wl_Ka,
39
- "W": tmatrix_aux.wl_W,
41
+ logger = logging.getLogger(__name__)
42
+
43
+ RADAR_OPTIONS = [
44
+ "frequency",
45
+ "diameter_max",
46
+ "num_points",
47
+ "canting_angle_std",
48
+ "axis_ratio_model",
49
+ "permittivity_model",
50
+ "water_temperature",
51
+ "elevation_angle",
52
+ ]
53
+
54
+ # Common radar frequencies (in GHz)
55
+ frequency_dict = {
56
+ "S": 2.70, # e.g. NEXRAD radars
57
+ "C": 5.4, # e.g. MeteoSwiss Rad4Alp radars
58
+ "X": 9.4, # e.g. LTE MXPOL radar
59
+ "Ku": 13.6, # e.g. DPR-Ku
60
+ "K": 24.2, # e.g. MRR-PRO
61
+ "Ka": 35.5, # e.g. DPR-Ka
62
+ "W": 94.05, # e.g. CloudSat, EarthCare
40
63
  }
41
64
 
42
65
 
43
66
  def available_radar_bands():
44
67
  """Return a list of the available radar bands."""
45
- return list(wavelength_dict)
68
+ return list(frequency_dict)
46
69
 
47
70
 
48
71
  def check_radar_band(radar_band):
@@ -53,84 +76,424 @@ def check_radar_band(radar_band):
53
76
  return radar_band
54
77
 
55
78
 
56
- def get_radar_wavelength(radar_band):
57
- """Get the wavelength of a radar band."""
58
- wavelength = wavelength_dict[radar_band]
79
+ def _check_frequency(frequency):
80
+ """Check the validity of the specified frequency."""
81
+ if isinstance(frequency, str):
82
+ frequency = check_radar_band(frequency)
83
+ frequency = frequency_dict[frequency]
84
+ return frequency
85
+ if not isinstance(frequency, (int, float)):
86
+ raise TypeError(f"Frequency {frequency} must be a string or a number.")
87
+ return frequency
88
+
89
+
90
+ def ensure_numerical_frequency(frequency):
91
+ """Ensure that the frequencies are numerical values in GHz."""
92
+ if isinstance(frequency, (str, int, float)):
93
+ frequency = [frequency]
94
+ frequency = np.array([_check_frequency(f) for f in frequency])
95
+ return frequency.squeeze()
96
+
97
+
98
+ # Wavelength, Frequency Conversion
99
+ def wavelength_to_frequency(wavelength):
100
+ """Convert wavelength in millimeters to frequency in GHz."""
101
+ c = 299_792_458 # speed of light in m/s
102
+ frequency = c / np.array(wavelength) / 1e6
103
+ return frequency
104
+
105
+
106
+ def frequency_to_wavelength(frequency):
107
+ """Convert frequency in GHz to wavelength millimeters."""
108
+ c = 299_792_458 # speed of light in m/s
109
+ wavelength = c / np.array(frequency) / 1e6
59
110
  return wavelength
60
111
 
61
112
 
62
- def initialize_scatterer(wavelength, canting_angle_std=7, D_max=8, axis_ratio="Thurai2007"):
63
- """Initialize T-matrix scatterer object for a given wavelength."""
113
+ def get_backward_geometry(elevation_angle):
114
+ """Define backward geometry given a radar elevation angle."""
115
+ # - Format (thet0, thet0, phi0, phi0, alpha, beta
116
+ # - thet0, thet0, thet: The zenith angles of incident and scattered radiation (default to 90)
117
+ # - phi0, phi: The azimuth angles of incident and scattered radiation (default to 0 and 180)
118
+ # - alpha, beta: Defaults to 0.0, 0.0. Valid values: alpha = [0, 360] beta = [0, 180]
119
+
120
+ # Retrieve zenith angle of incident beam (from vertical)
121
+ theta = 90.0 - elevation_angle
122
+
123
+ # Return (thet0, thet0, phi0, phi0, alpha, beta) tuple
124
+ return (theta, 180 - theta, 0.0, 180, 0.0, 0.0)
125
+
126
+
127
+ def get_forward_geometry(elevation_angle):
128
+ """Define forward geometry given a radar elevation angle."""
129
+ # - Format (thet0, thet0, phi0, phi0, alpha, beta
130
+ # - thet0, thet0, thet: The zenith angles of incident and scattered radiation (default to 90)
131
+ # - phi0, phi: The azimuth angles of incident and scattered radiation (default to 0 and 180)
132
+ # - alpha, beta: Defaults to 0.0, 0.0. Valid values: alpha = [0, 360] beta = [0, 180]
133
+
134
+ # Retrieve zenith angle of incident beam (from vertical)
135
+ theta = 90.0 - elevation_angle
136
+
137
+ # Return (thet0, thet0, phi0, phi0, alpha, beta) tuple
138
+ return (theta, theta, 0.0, 0.0, 0.0, 0.0)
139
+
140
+
141
+ # from pytmatrix import tmatrix_aux
142
+ # get_backward_geometry(0)
143
+ # tmatrix_aux.geom_horiz_back
144
+ # get_backward_geometry(90)
145
+ # tmatrix_aux.geom_vert_back # phi0 varies (180 instead of pytmatrix 0)
146
+
147
+ # get_forward_geometry(0)
148
+ # tmatrix_aux.geom_horiz_forw
149
+ # get_forward_geometry(90) # theta and thet0 are 0 instead of 180
150
+ # tmatrix_aux.geom_vert_forw
151
+
152
+
153
+ def initialize_scatterer(
154
+ wavelength,
155
+ refractive_index,
156
+ num_points=1024,
157
+ diameter_max=8,
158
+ canting_angle_std=7,
159
+ axis_ratio_model="Thurai2007",
160
+ elevation_angle=0,
161
+ ):
162
+ """Initialize T-matrix scatterer object for a given frequency.
163
+
164
+ Load a scatterer object with cached scattering table.
165
+
166
+ If the scattering table does not exist at the specified location, it will be
167
+ created and saved to disk. If the file is found, it will be loaded and used
168
+ to configure the scatterer.
169
+
170
+ Parameters
171
+ ----------
172
+ wavelength : float
173
+ Radar wavelength in mm.
174
+ refractive_index: complex
175
+ Water refractive index.
176
+ num_points: int
177
+ Number of bins into which discretize the PSD.
178
+ diameter_max : float
179
+ Maximum drop diameter in millimeters for the scattering table.
180
+ canting_angle_std : float, optional
181
+ Standard deviation of the canting angle distribution in degrees,
182
+ by default 7.
183
+ axis_ratio_model: str
184
+ Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
185
+ See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
186
+ elevation_angle: str
187
+ Radar elevation angle in degrees.
188
+ Specify 90 degrees for vertically pointing radars.
189
+ The default is 0 degrees.
190
+ scattering_table_dir : str or Path, optional
191
+ Directory path where T-Matrix scattering tables are stored. If None, the default
192
+ location will be used.
193
+ verbose: bool
194
+ Whether to verbose the computation of the scattering table. The default is False.
195
+
196
+ Returns
197
+ -------
198
+ scatterer : Scatterer
199
+ A scatterer object with the PSD integrator configured and scattering
200
+ table loaded or generated.
201
+ """
202
+ from pytmatrix import orientation
203
+ from pytmatrix.psd import PSDIntegrator
204
+ from pytmatrix.tmatrix import Scatterer
205
+
64
206
  # Retrieve custom axis ratio function
65
- axis_ratio_func = get_axis_ratio_method(axis_ratio)
207
+ axis_ratio_func = get_axis_ratio_model(axis_ratio_model)
208
+
209
+ # Define radar dielectric factor
210
+ Kw_sqr = get_rayleigh_dielectric_factor(refractive_index)
66
211
 
67
- # Retrieve water complex refractive index
68
- # - Here we currently assume 10 °C
69
- # - m_w_0C and m_w_20C are also available
70
- # TODO: should be another dimension ? Or use scatterer.psd_integrator.m_func?
71
- water_refractive_index = refractive.m_w_10C[wavelength]
212
+ # Define backward and forward geometries
213
+ # - Format (thet0, thet0, phi0, phi0, alpha, beta
214
+ backward_geom = get_backward_geometry(elevation_angle)
215
+ forward_geom = get_forward_geometry(elevation_angle)
216
+
217
+ # ---------------------------------------------------------------.
218
+ # For W band limits diameter_max up to 9.5, otherwise the kernel dies !
219
+ if wavelength < 3.5:
220
+ diameter_max = min(diameter_max, 9.5)
72
221
 
73
222
  # ---------------------------------------------------------------.
74
223
  # Initialize Scatterer class
75
- scatterer = Scatterer(wavelength=wavelength, m=water_refractive_index)
224
+ # - By specifying m, we assume same refractive index for all particles diameters
225
+ scatterer = Scatterer(wavelength=wavelength, m=refractive_index, Kw_sqr=Kw_sqr)
226
+
227
+ # - Define geometry
228
+ scatterer.set_geometry(backward_geom)
229
+
230
+ # - Define orientation methods
231
+ # --> Alternatives: orient_averaged_adaptive, orient_single,
232
+ # --> Speed: orient_single > orient_averaged_fixed > orient_averaged_adaptive
233
+ scatterer.orient = orientation.orient_averaged_fixed
234
+
76
235
  # - Define particle orientation PDF for orientational averaging
77
236
  # --> The standard deviation of the angle with respect to vertical orientation (the canting angle).
78
237
  scatterer.or_pdf = orientation.gaussian_pdf(std=canting_angle_std)
79
- # - Define orientation methods
80
- # --> Alternatives: orient_averaged_fixed, orient_single
81
- scatterer.orient = orientation.orient_averaged_fixed
82
238
 
83
239
  # ---------------------------------------------------------------.
84
240
  # Initialize PSDIntegrator
85
241
  scatterer.psd_integrator = PSDIntegrator()
86
242
  # - Define axis_ratio_func
87
243
  # --> The Scatterer class expects horizontal to vertical
244
+ # --> Axis ratio model are defined to return vertical to horizontal aspect ratio !
88
245
  scatterer.psd_integrator.axis_ratio_func = lambda D: 1.0 / axis_ratio_func(D)
89
246
  # - Define function to compute refrative index (as function of D)
90
247
  # scatterer.psd_integrator.m_func = None # Use constant value of scatterer.m
91
248
  # - Define number of points over which to integrate
92
- scatterer.psd_integrator.num_points = 1024
249
+ scatterer.psd_integrator.num_points = num_points
93
250
  # - Define maximum drop diameter
94
- scatterer.psd_integrator.D_max = D_max
251
+ scatterer.psd_integrator.D_max = diameter_max
95
252
  # - Define geometries
96
- scatterer.psd_integrator.geometries = (tmatrix_aux.geom_horiz_back, tmatrix_aux.geom_horiz_forw)
253
+ # --> convention: first is backward, second is forward
254
+ scatterer.psd_integrator.geometries = (backward_geom, forward_geom)
255
+ return scatterer
256
+
257
+
258
+ def calculate_scatterer(
259
+ wavelength,
260
+ refractive_index,
261
+ num_points=1024,
262
+ diameter_max=8,
263
+ canting_angle_std=7,
264
+ axis_ratio_model="Thurai2007",
265
+ elevation_angle=0,
266
+ ):
267
+ """Initialize T-matrix scatterer object for a given frequency.
268
+
269
+ Load a scatterer object with cached scattering table.
270
+
271
+ If the scattering table does not exist at the specified location, it will be
272
+ created and saved to disk. If the file is found, it will be loaded and used
273
+ to configure the scatterer.
274
+
275
+ Parameters
276
+ ----------
277
+ wavelength : float
278
+ Radar wavelength in millimeters.
279
+ num_points: int
280
+ Number of bins into which discretize the PSD.
281
+ diameter_max : float
282
+ Maximum drop diameter in millimeters for the scattering table.
283
+ canting_angle_std : float, optional
284
+ Standard deviation of the canting angle distribution in degrees,
285
+ by default 7.
286
+ axis_ratio_model : str, optional
287
+ Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
288
+ See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
289
+ elevation_angle: str
290
+ Radar elevation angle in degrees.
291
+ Specify 90 degrees for vertically pointing radars.
292
+ The default is 0 degrees.
293
+
294
+ Returns
295
+ -------
296
+ scatterer : Scatterer
297
+ A scatterer object with the PSD integrator configured and scattering
298
+ table loaded or generated.
299
+ """
97
300
  # ---------------------------------------------------------------.
98
- # Initialize scattering table
301
+ # Initialize Scatterer class
302
+ scatterer = initialize_scatterer(
303
+ wavelength=wavelength,
304
+ refractive_index=refractive_index,
305
+ num_points=num_points,
306
+ diameter_max=diameter_max,
307
+ canting_angle_std=canting_angle_std,
308
+ axis_ratio_model=axis_ratio_model,
309
+ elevation_angle=elevation_angle,
310
+ )
311
+
312
+ # ---------------------------------------------------------------.
313
+ # Calculate scattering table
99
314
  scatterer.psd_integrator.init_scatter_table(scatterer)
100
315
  return scatterer
101
316
 
102
317
 
318
+ def load_scatterer(
319
+ frequency,
320
+ num_points=1024,
321
+ diameter_max=8,
322
+ canting_angle_std=7,
323
+ axis_ratio_model="Thurai2007",
324
+ permittivity_model="Turner2016",
325
+ water_temperature=10,
326
+ elevation_angle=0,
327
+ scattering_table_dir=None,
328
+ verbose=False,
329
+ ):
330
+ """
331
+ Load a scatterer object with cached scattering table.
332
+
333
+ If the scattering table does not exist at the specified location, it will be
334
+ created and saved to disk. If the file is found, it will be loaded and used
335
+ to configure the scatterer.
336
+
337
+ Parameters
338
+ ----------
339
+ frequency : float
340
+ Radar frequency in GHz.
341
+ num_points: int
342
+ Number of bins into which discretize the PSD.
343
+ diameter_max : float
344
+ Maximum drop diameter in millimeters for the scattering table.
345
+ canting_angle_std : float, optional
346
+ Standard deviation of the canting angle distribution in degrees,
347
+ by default 7.
348
+ axis_ratio_model : str, optional
349
+ Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
350
+ See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
351
+ permittivity_model : str
352
+ Permittivity model to use to compute the refractive index and the
353
+ rayleigh_dielectric_factor. The default is ``Turner2016``.
354
+ See available models with ``disdrodb.scattering.available_permittivity_models()``.
355
+ water_temperature : float
356
+ Water temperature in degree Celsius to be used in the permittivity model.
357
+ The default is 10 degC.
358
+ elevation_angle: str
359
+ Radar elevation angle in degrees.
360
+ Specify 90 degrees for vertically pointing radars.
361
+ The default is 0 degrees.
362
+ scattering_table_dir : str or Path, optional
363
+ Directory path where T-Matrix scattering tables are stored. If None, the default
364
+ location will be used.
365
+ verbose: bool
366
+ Whether to verbose the computation of the scattering table. The default is False.
367
+
368
+ Returns
369
+ -------
370
+ scatterer : Scatterer
371
+ A scatterer object with the PSD integrator configured and scattering
372
+ table loaded or generated.
373
+ """
374
+ # Define wavelength (in mm)
375
+ wavelength = frequency_to_wavelength(frequency)
376
+
377
+ # Define complex refractive index
378
+ refractive_index = get_refractive_index(
379
+ frequency=frequency,
380
+ temperature=water_temperature,
381
+ permittivity_model=permittivity_model,
382
+ )
383
+
384
+ # Retrieve scattering table directory
385
+ scattering_table_dir = get_scattering_table_dir(scattering_table_dir)
386
+
387
+ # Define a filename based on the key parameters
388
+ filename = "_".join(
389
+ [
390
+ "ScatteringTable",
391
+ f"wl-{wavelength:.2f}",
392
+ f"el-{elevation_angle:.1f}",
393
+ f"dmax-{diameter_max:.1f}",
394
+ f"npts-{num_points}",
395
+ f"m-{refractive_index:.3f}",
396
+ f"cant-{canting_angle_std:.1f}",
397
+ f"ar-{axis_ratio_model}.pkl",
398
+ ],
399
+ )
400
+ scatter_table_filepath = os.path.join(scattering_table_dir, filename)
401
+
402
+ # Load or create scattering table
403
+ if os.path.exists(scatter_table_filepath):
404
+ scatterer = initialize_scatterer(
405
+ wavelength=wavelength,
406
+ refractive_index=refractive_index,
407
+ num_points=num_points,
408
+ diameter_max=diameter_max,
409
+ canting_angle_std=canting_angle_std,
410
+ axis_ratio_model=axis_ratio_model,
411
+ elevation_angle=elevation_angle,
412
+ )
413
+ _ = scatterer.psd_integrator.load_scatter_table(scatter_table_filepath)
414
+
415
+ else:
416
+ if verbose:
417
+ msg = f"- Computing pyTmatrix {filename}"
418
+ log_info(logger=logger, msg=msg, verbose=verbose)
419
+
420
+ # Calculate scatterer
421
+ scatterer = calculate_scatterer(
422
+ wavelength=wavelength,
423
+ refractive_index=refractive_index,
424
+ num_points=num_points,
425
+ diameter_max=diameter_max,
426
+ canting_angle_std=canting_angle_std,
427
+ axis_ratio_model=axis_ratio_model,
428
+ elevation_angle=elevation_angle,
429
+ )
430
+
431
+ scatterer.psd_integrator.save_scatter_table(scatter_table_filepath)
432
+ return scatterer
433
+
434
+
435
+ ####----------------------------------------------------------------------
436
+ #### Scattering functions
437
+
438
+
103
439
  def compute_radar_variables(scatterer):
104
440
  """Compute radar variables for a given scatter object with a specified PSD.
105
441
 
106
442
  To speed up computations, this function should input a scatterer object with
107
443
  a preinitialized scattering table.
108
444
  """
109
- # Compute radar parameters
110
- radar_vars = {}
111
- scatterer.set_geometry(tmatrix_aux.geom_horiz_back)
112
- radar_vars["Zh"] = 10 * np.log10(radar.refl(scatterer, h_pol=True)) # dBZ
113
- radar_vars["Zdr"] = 10 * np.log10(radar.Zdr(scatterer)) # dB
114
- radar_vars["rho_hv"] = radar.rho_hv(scatterer)
115
- radar_vars["ldr"] = radar.ldr(scatterer)
116
- scatterer.set_geometry(tmatrix_aux.geom_horiz_forw)
117
- radar_vars["Kdp"] = radar.Kdp(scatterer)
118
- radar_vars["Ai"] = radar.Ai(scatterer)
445
+ from pytmatrix import radar
446
+
447
+ with suppress_warnings():
448
+ radar_vars = {}
449
+ # Retrieve backward and forward_geometries
450
+ # - Convention (first is backward, second is forward)
451
+ backward_geom = scatterer.psd_integrator.geometries[0]
452
+ forward_geom = scatterer.psd_integrator.geometries[1]
453
+
454
+ # Set backward scattering for reflectivity calculations
455
+ scatterer.set_geometry(backward_geom)
456
+
457
+ radar_vars["DBZH"] = 10 * np.log10(radar.refl(scatterer, h_pol=True)) # dBZ
458
+ radar_vars["DBZV"] = 10 * np.log10(radar.refl(scatterer, h_pol=False)) # dBZ
459
+
460
+ radar_vars["ZDR"] = 10 * np.log10(radar.Zdr(scatterer)) # dB
461
+ if ~np.isfinite(radar_vars["ZDR"]):
462
+ radar_vars["ZDR"] = np.nan
463
+
464
+ radar_vars["LDR"] = 10 * np.log10(radar.ldr(scatterer)) # dBZ
465
+ if ~np.isfinite(radar_vars["LDR"]):
466
+ radar_vars["LDR"] = np.nan
467
+
468
+ radar_vars["RHOHV"] = radar.rho_hv(scatterer) # deg/km
469
+ radar_vars["DELTAHV"] = radar.delta_hv(scatterer) * 180.0 / np.pi # [deg]
470
+
471
+ # Set forward scattering for attenuation and phase calculations
472
+ scatterer.set_geometry(forward_geom)
473
+ radar_vars["KDP"] = radar.Kdp(scatterer) # deg/km
474
+ radar_vars["AH"] = radar.Ai(scatterer, h_pol=True) # dB/km
475
+ radar_vars["AV"] = radar.Ai(scatterer, h_pol=False) # dB/km
476
+ radar_vars["ADP"] = radar_vars["AH"] - radar_vars["AV"] # dB/km
119
477
  return radar_vars
120
478
 
121
479
 
480
+ # Radar variables computed by DISDRODB
481
+ # - Must reflect dictionary order output of compute_radar_variables
482
+ RADAR_VARIABLES = ["DBZH", "DBZV", "ZDR", "LDR", "RHOHV", "DELTAHV", "KDP", "AH", "AV", "ADP"]
483
+
484
+
485
+ def _initialize_null_output(output_dictionary):
486
+ if output_dictionary:
487
+ return dict.fromkeys(RADAR_VARIABLES, np.nan)
488
+ return np.zeros(len(RADAR_VARIABLES)) * np.nan
489
+
490
+
122
491
  def _estimate_empirical_radar_parameters(
123
492
  drop_number_concentration,
124
493
  bin_edges,
125
494
  scatterer,
126
495
  output_dictionary,
127
496
  ):
128
- # Initialize bad results
129
- if output_dictionary:
130
- null_output = {"Zh": np.nan, "Zdr": np.nan, "rho_hv": np.nan, "ldr": np.nan, "Kdp": np.nan, "Ai": np.nan}
131
- else:
132
- null_output = np.array([np.nan, np.nan, np.nan, np.nan, np.nan, np.nan])
133
-
134
497
  # Assign PSD model to the scatterer object
135
498
  scatterer.psd = BinnedPSD(bin_edges, drop_number_concentration)
136
499
 
@@ -140,7 +503,7 @@ def _estimate_empirical_radar_parameters(
140
503
  radar_vars = compute_radar_variables(scatterer)
141
504
  output = radar_vars if output_dictionary else np.array(list(radar_vars.values()))
142
505
  except Exception:
143
- output = null_output
506
+ output = _initialize_null_output(output_dictionary)
144
507
  return output
145
508
 
146
509
 
@@ -151,24 +514,17 @@ def _estimate_model_radar_parameters(
151
514
  scatterer,
152
515
  output_dictionary,
153
516
  ):
154
- # Initialize bad results
155
- if output_dictionary:
156
- null_output = {"Zh": np.nan, "Zdr": np.nan, "rho_hv": np.nan, "ldr": np.nan, "Kdp": np.nan, "Ai": np.nan}
157
- else:
158
- null_output = np.array([np.nan, np.nan, np.nan, np.nan, np.nan, np.nan])
159
-
160
517
  # Assign PSD model to the scatterer object
161
518
  parameters = dict(zip(psd_parameters_names, parameters))
162
519
  scatterer.psd = create_psd(psd_model, parameters)
163
520
 
164
521
  # Get radar variables
165
522
  with suppress_warnings():
166
- radar_vars = compute_radar_variables(scatterer)
167
523
  try:
168
524
  radar_vars = compute_radar_variables(scatterer)
169
525
  output = radar_vars if output_dictionary else np.array(list(radar_vars.values()))
170
526
  except Exception:
171
- output = null_output
527
+ output = _initialize_null_output(output_dictionary)
172
528
  return output
173
529
 
174
530
 
@@ -184,26 +540,44 @@ def get_psd_parameters(ds):
184
540
 
185
541
  def get_model_radar_parameters(
186
542
  ds,
187
- radar_band,
188
- canting_angle_std=7,
543
+ frequency,
544
+ num_points=1024,
189
545
  diameter_max=10,
190
- axis_ratio="Thurai2007",
546
+ canting_angle_std=7,
547
+ axis_ratio_model="Thurai2007",
548
+ permittivity_model="Turner2016",
549
+ water_temperature=10,
550
+ elevation_angle=0,
191
551
  ):
192
552
  """Compute radar parameters from a PSD model.
193
553
 
554
+ This function retrieve values for a single set of parameter only !
555
+
194
556
  Parameters
195
557
  ----------
196
558
  ds : xarray.Dataset
197
559
  Dataset containing the parameters of the PSD model.
198
560
  The dataset attribute disdrodb_psd_model specifies the PSD model to use.
199
- radar_band : str
200
- Radar band to be used.
561
+ frequency : float
562
+ Frequency in GHz for which to compute the radar parameters.
201
563
  canting_angle_std : float, optional
202
- Standard deviation of the canting angle. The default value is 7.
564
+ Standard deviation of the canting angle. The default value is 10.
203
565
  diameter_max : float, optional
204
566
  Maximum diameter. The default value is 8 mm.
205
- axis_ratio : str, optional
206
- Method to compute the axis ratio. The default method is ``Thurai2007``.
567
+ axis_ratio_model : str, optional
568
+ Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
569
+ See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
570
+ permittivity_model : str
571
+ Permittivity model to use to compute the refractive index and the
572
+ rayleigh_dielectric_factor. The default is ``Turner2016``.
573
+ See available models with ``disdrodb.scattering.available_permittivity_models()``.
574
+ water_temperature : float
575
+ Water temperature in degree Celsius to be used in the permittivity model.
576
+ The default is 10 degC.
577
+ elevation_angle: str
578
+ Radar elevation angle in degrees.
579
+ Specify 90 degrees for vertically pointing radars.
580
+ The default is 0 degrees.
207
581
 
208
582
  Returns
209
583
  -------
@@ -216,21 +590,22 @@ def get_model_radar_parameters(
216
590
  ds_parameters = get_psd_parameters(ds)
217
591
 
218
592
  # Check argument validity
219
- axis_ratio = check_axis_ratio(axis_ratio)
220
- radar_band = check_radar_band(radar_band)
221
-
222
- # Retrieve wavelengths in mm
223
- wavelength = get_radar_wavelength(radar_band)
593
+ axis_ratio_model = check_axis_ratio_model(axis_ratio_model)
594
+ permittivity_model = check_permittivity_model(permittivity_model)
224
595
 
225
596
  # Create DataArray with PSD parameters
226
597
  da_parameters = ds_parameters.to_array(dim="psd_parameters").compute()
227
598
 
228
599
  # Initialize scattering table
229
- scatterer = initialize_scatterer(
230
- wavelength=wavelength,
600
+ scatterer = load_scatterer(
601
+ frequency=frequency,
602
+ num_points=num_points,
603
+ diameter_max=diameter_max,
231
604
  canting_angle_std=canting_angle_std,
232
- D_max=diameter_max,
233
- axis_ratio=axis_ratio,
605
+ axis_ratio_model=axis_ratio_model,
606
+ permittivity_model=permittivity_model,
607
+ water_temperature=water_temperature,
608
+ elevation_angle=elevation_angle,
234
609
  )
235
610
 
236
611
  # Define kwargs
@@ -242,7 +617,6 @@ def get_model_radar_parameters(
242
617
  }
243
618
 
244
619
  # Loop over each PSD (not in parallel --> dask="forbidden")
245
- # - It costs much more to initiate the scatterer rather than looping over timesteps !
246
620
  da_radar = xr.apply_ufunc(
247
621
  _estimate_model_radar_parameters,
248
622
  da_parameters,
@@ -251,73 +625,101 @@ def get_model_radar_parameters(
251
625
  output_core_dims=[["radar_variables"]],
252
626
  vectorize=True,
253
627
  dask="forbidden",
254
- dask_gufunc_kwargs={"output_sizes": {"radar_variables": 5}}, # lengths of the new output_core_dims dimensions.
628
+ dask_gufunc_kwargs={
629
+ "output_sizes": {"radar_variables": len(RADAR_VARIABLES)},
630
+ }, # lengths of the new output_core_dims dimensions.
255
631
  output_dtypes=["float64"],
256
632
  )
257
633
 
258
- # Add parameters coordinates
259
- da_radar = da_radar.assign_coords({"radar_variables": ["Zh", "Zdr", "rho_hv", "ldr", "Kdp", "Ai"]})
260
-
261
- # Create parameters dataset
262
- ds_radar = da_radar.to_dataset(dim="radar_variables")
263
-
264
- # Expand dimensions for later merging
265
- dims_dict = {
266
- "radar_band": [radar_band],
267
- "axis_ratio": [axis_ratio],
268
- "canting_angle_std": [canting_angle_std],
269
- "diameter_max": [diameter_max],
270
- }
271
- ds_radar = ds_radar.expand_dims(dim=dims_dict)
634
+ # Finalize radar dataset (add name, coordinates)
635
+ ds_radar = _finalize_radar_dataset(
636
+ da_radar=da_radar,
637
+ frequency=frequency,
638
+ num_points=num_points,
639
+ diameter_max=diameter_max,
640
+ canting_angle_std=canting_angle_std,
641
+ axis_ratio_model=axis_ratio_model,
642
+ permittivity_model=permittivity_model,
643
+ water_temperature=water_temperature,
644
+ elevation_angle=elevation_angle,
645
+ )
272
646
  return ds_radar
273
647
 
274
648
 
275
649
  def get_empirical_radar_parameters(
276
650
  ds,
277
- radar_band=None,
651
+ frequency,
652
+ num_points=1024,
653
+ diameter_max=10,
278
654
  canting_angle_std=7,
279
- diameter_max=8,
280
- axis_ratio="Thurai2007",
655
+ axis_ratio_model="Thurai2007",
656
+ permittivity_model="Turner2016",
657
+ water_temperature=10,
658
+ elevation_angle=0,
281
659
  ):
282
- """Compute radar parameters from empirical drop number concentration.
660
+ """Compute radar parameters from an empirical drop number concentration.
661
+
662
+ This function retrieve values for a single set of parameter only !
283
663
 
284
664
  Parameters
285
665
  ----------
286
666
  ds : xarray.Dataset
287
667
  Dataset containing the drop number concentration variable.
288
- radar_band : str
289
- Radar band to be used.
668
+ frequency : float
669
+ Frequency in GHz for which to compute the radar parameters.
290
670
  canting_angle_std : float, optional
291
- Standard deviation of the canting angle. The default value is 7.
671
+ Standard deviation of the canting angle. The default value is 10.
292
672
  diameter_max : float, optional
293
673
  Maximum diameter. The default value is 8 mm.
294
- axis_ratio : str, optional
295
- Method to compute the axis ratio. The default method is ``Thurai2007``.
674
+ axis_ratio_model : str, optional
675
+ Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
676
+ See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
677
+ permittivity_model : str
678
+ Permittivity model to use to compute the refractive index and the
679
+ rayleigh_dielectric_factor. The default is ``Turner2016``.
680
+ See available models with ``disdrodb.scattering.available_permittivity_models()``.
681
+ water_temperature : float
682
+ Water temperature in degree Celsius to be used in the permittivity model.
683
+ The default is 10 degC.
684
+ elevation_angle: str
685
+ Radar elevation angle in degrees.
686
+ Specify 90 degrees for vertically pointing radars.
687
+ The default is 0 degrees.
296
688
 
297
689
  Returns
298
690
  -------
299
691
  xarray.Dataset
300
692
  Dataset containing the computed radar parameters.
301
693
  """
694
+ # Subset dataset based on diameter max
695
+ ds = filter_diameter_bins(ds=ds, maximum_diameter=diameter_max)
696
+
302
697
  # Define inputs
303
698
  da_drop_number_concentration = ds["drop_number_concentration"].compute()
304
699
 
700
+ # Set all zeros drop number concentration to np.nan
701
+ # --> Otherwise inf can appear in the output
702
+ # --> Note that if a single np.nan is present, the output simulation will be NaN values
703
+ valid_obs = da_drop_number_concentration.sum(dim=DIAMETER_DIMENSION) != 0
704
+ da_drop_number_concentration = da_drop_number_concentration.where(valid_obs)
705
+
305
706
  # Define bin edges
306
- bin_edges = np.append(ds["diameter_bin_lower"].compute().data, ds["diameter_bin_upper"].compute().data[-1])
707
+ bin_edges = get_diameter_bin_edges(ds)
307
708
 
308
709
  # Check argument validity
309
- axis_ratio = check_axis_ratio(axis_ratio)
310
- radar_band = check_radar_band(radar_band)
311
-
312
- # Retrieve wavelengths in mm
313
- wavelength = get_radar_wavelength(radar_band)
710
+ axis_ratio_model = check_axis_ratio_model(axis_ratio_model)
711
+ permittivity_model = check_permittivity_model(permittivity_model)
314
712
 
315
713
  # Initialize scattering table
316
- scatterer = initialize_scatterer(
317
- wavelength=wavelength,
714
+ scatterer = load_scatterer(
715
+ frequency=frequency,
716
+ num_points=num_points,
717
+ diameter_max=diameter_max,
318
718
  canting_angle_std=canting_angle_std,
319
- D_max=diameter_max,
320
- axis_ratio=axis_ratio,
719
+ axis_ratio_model=axis_ratio_model,
720
+ permittivity_model=permittivity_model,
721
+ water_temperature=water_temperature,
722
+ elevation_angle=elevation_angle,
321
723
  )
322
724
 
323
725
  # Define kwargs
@@ -337,33 +739,140 @@ def get_empirical_radar_parameters(
337
739
  output_core_dims=[["radar_variables"]],
338
740
  vectorize=True,
339
741
  dask="forbidden",
340
- dask_gufunc_kwargs={"output_sizes": {"radar_variables": 5}}, # lengths of the new output_core_dims dimensions.
742
+ dask_gufunc_kwargs={
743
+ "output_sizes": {"radar_variables": len(RADAR_VARIABLES)},
744
+ }, # lengths of the new output_core_dims dimensions.
341
745
  output_dtypes=["float64"],
342
746
  )
343
747
 
748
+ # Finalize radar dataset (add name, coordinates)
749
+ ds_radar = _finalize_radar_dataset(
750
+ da_radar=da_radar,
751
+ frequency=frequency,
752
+ num_points=num_points,
753
+ diameter_max=diameter_max,
754
+ canting_angle_std=canting_angle_std,
755
+ axis_ratio_model=axis_ratio_model,
756
+ permittivity_model=permittivity_model,
757
+ water_temperature=water_temperature,
758
+ elevation_angle=elevation_angle,
759
+ )
760
+ return ds_radar
761
+
762
+
763
+ def _finalize_radar_dataset(
764
+ da_radar,
765
+ frequency,
766
+ num_points,
767
+ diameter_max,
768
+ canting_angle_std,
769
+ axis_ratio_model,
770
+ permittivity_model,
771
+ water_temperature,
772
+ elevation_angle,
773
+ ):
344
774
  # Add parameters coordinates
345
- da_radar = da_radar.assign_coords({"radar_variables": ["Zh", "Zdr", "rho_hv", "ldr", "Kdp", "Ai"]})
775
+ da_radar = da_radar.assign_coords({"radar_variables": RADAR_VARIABLES})
346
776
 
347
777
  # Create parameters dataset
348
778
  ds_radar = da_radar.to_dataset(dim="radar_variables")
349
779
 
350
- # Expand dimensions for later merging
780
+ # Expand dimensions for later merging in get_radar_parameters()
351
781
  dims_dict = {
352
- "radar_band": [radar_band],
353
- "axis_ratio": [axis_ratio],
354
- "canting_angle_std": [canting_angle_std],
782
+ "frequency": [frequency],
355
783
  "diameter_max": [diameter_max],
784
+ "num_points": [num_points],
785
+ "axis_ratio_model": [axis_ratio_model],
786
+ "canting_angle_std": [canting_angle_std],
787
+ "permittivity_model": [permittivity_model],
788
+ "water_temperature": [water_temperature],
789
+ "elevation_angle": [elevation_angle],
356
790
  }
357
791
  ds_radar = ds_radar.expand_dims(dim=dims_dict)
358
792
  return ds_radar
359
793
 
360
794
 
795
+ ####----------------------------------------------------------------------
796
+ #### Wrapper for L2E and L2M products
797
+
798
+
799
+ def ensure_rounded_unique_array(arr, decimals=None):
800
+ """Ensure that the input array is a unique, rounded array."""
801
+ arr = np.atleast_1d(arr)
802
+ if decimals is not None:
803
+ arr = arr.round(decimals)
804
+ return np.unique(arr)
805
+
806
+
807
+ def get_list_simulations_params(
808
+ frequency,
809
+ num_points,
810
+ diameter_max,
811
+ canting_angle_std,
812
+ axis_ratio_model,
813
+ permittivity_model,
814
+ water_temperature,
815
+ elevation_angle,
816
+ ):
817
+ """Return list with the set of parameters required for each simulation."""
818
+ # Ensure numeric frequencies
819
+ frequency = ensure_numerical_frequency(frequency)
820
+
821
+ # Ensure arguments are unique set of values
822
+ # - Otherwise problems with non-unique xarray dataset coordinates
823
+ frequency = ensure_rounded_unique_array(frequency, decimals=2)
824
+ num_points = ensure_rounded_unique_array(num_points, decimals=0)
825
+ diameter_max = ensure_rounded_unique_array(diameter_max, decimals=1)
826
+ canting_angle_std = ensure_rounded_unique_array(canting_angle_std, decimals=1)
827
+ axis_ratio_model = ensure_rounded_unique_array(axis_ratio_model)
828
+ permittivity_model = ensure_rounded_unique_array(permittivity_model)
829
+ water_temperature = ensure_rounded_unique_array(water_temperature, decimals=1)
830
+ elevation_angle = ensure_rounded_unique_array(elevation_angle, decimals=1)
831
+
832
+ # Check parameters validity
833
+ axis_ratio_model = [check_axis_ratio_model(model) for model in axis_ratio_model]
834
+ permittivity_model = [check_permittivity_model(model) for model in permittivity_model]
835
+
836
+ # Order frequency from lowest to highest
837
+ # --> ['S', 'C', 'X', 'Ku', 'K', 'Ka', 'W']
838
+ frequency = sorted(frequency)
839
+
840
+ # Retrieve combination of parameters
841
+ list_params = [
842
+ {
843
+ "frequency": freq.item(),
844
+ "diameter_max": d_max.item(),
845
+ "num_points": n_p.item(),
846
+ "canting_angle_std": cas.item(),
847
+ "axis_ratio_model": ar.item(),
848
+ "permittivity_model": perm.item(),
849
+ "water_temperature": t_w.item(),
850
+ "elevation_angle": el.item(),
851
+ }
852
+ for freq, d_max, n_p, cas, ar, perm, t_w, el in itertools.product(
853
+ frequency,
854
+ diameter_max,
855
+ num_points,
856
+ canting_angle_std,
857
+ axis_ratio_model,
858
+ permittivity_model,
859
+ water_temperature,
860
+ elevation_angle,
861
+ )
862
+ ]
863
+ return list_params
864
+
865
+
361
866
  def get_radar_parameters(
362
867
  ds,
363
- radar_band=None,
364
- canting_angle_std=7,
868
+ frequency=None,
869
+ num_points=1024,
365
870
  diameter_max=8,
366
- axis_ratio="Thurai2007",
871
+ canting_angle_std=7,
872
+ axis_ratio_model="Thurai2007",
873
+ permittivity_model="Turner2016",
874
+ water_temperature=10,
875
+ elevation_angle=0,
367
876
  parallel=True,
368
877
  ):
369
878
  """Compute radar parameters from empirical drop number concentration or PSD model.
@@ -372,15 +881,31 @@ def get_radar_parameters(
372
881
  ----------
373
882
  ds : xarray.Dataset
374
883
  Dataset containing the drop number concentration variable.
375
- radar_band : str or list of str, optional
376
- Radar band(s) to be used.
377
- If ``None`` (the default), all available radar bands are used.
378
- canting_angle_std : float or list of float, optional
379
- Standard deviation of the canting angle. The default value is 7.
884
+ frequency : str, float, or list of str and float, optional
885
+ Frequencies in GHz for which to compute the radar parameters.
886
+ Alternatively, also strings can be used to specify common radar frequencies.
887
+ If ``None``, the common radar frequencies will be used.
888
+ See ``disdrodb.scattering.available_radar_bands()``.
889
+ num_points: int or lis tof integer, optional
890
+ Number of bins into which discretize the PSD.
380
891
  diameter_max : float or list of float, optional
381
892
  Maximum diameter. The default value is 8 mm.
382
- axis_ratio : str or list of str, optional
383
- Method to compute the axis ratio. The default method is ``Thurai2007``.
893
+ canting_angle_std : float or list of float, optional
894
+ Standard deviation of the canting angle. The default value is 7.
895
+ axis_ratio_model : str or list of str, optional
896
+ Models to compute the axis ratio. The default model is ``Thurai2007``.
897
+ See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
898
+ permittivity_model : str str or list of str, optional
899
+ Permittivity model to use to compute the refractive index and the
900
+ rayleigh_dielectric_factor. The default is ``Turner2016``.
901
+ See available models with ``disdrodb.scattering.available_permittivity_models()``.
902
+ water_temperature : float or list of float, optional
903
+ Water temperature in degree Celsius to be used in the permittivity model.
904
+ The default is 10 degC.
905
+ elevation_angle: float or list of float, optional
906
+ Radar elevation angles in degrees.
907
+ Specify 90 degrees for vertically pointing radars.
908
+ The default is 0 degrees.
384
909
  parallel : bool, optional
385
910
  Whether to compute radar variables in parallel.
386
911
  The default value is ``True``.
@@ -393,6 +918,7 @@ def get_radar_parameters(
393
918
  # Decide whether to simulate radar parameters based on empirical PSD or model PSD
394
919
  if "disdrodb_psd_model" not in ds.attrs and "drop_number_concentration" not in ds:
395
920
  raise ValueError("The input dataset is not a DISDRODB L2E or L2M product.")
921
+
396
922
  # Model-based simulation
397
923
  if "disdrodb_psd_model" in ds.attrs:
398
924
  func = get_model_radar_parameters
@@ -402,34 +928,21 @@ def get_radar_parameters(
402
928
  func = get_empirical_radar_parameters
403
929
  ds_subset = ds[["drop_number_concentration"]].compute()
404
930
 
405
- # Initialize radar band if not provided
406
- if radar_band is None:
407
- radar_band = available_radar_bands()
408
-
409
- # Ensure parameters are list
410
- diameter_max = np.atleast_1d(diameter_max)
411
- canting_angle_std = np.atleast_1d(canting_angle_std)
412
- axis_ratio = np.atleast_1d(axis_ratio)
413
- radar_band = np.atleast_1d(radar_band)
414
-
415
- # Check parameters validity
416
- axis_ratio = [check_axis_ratio(method) for method in axis_ratio]
417
- radar_band = [check_radar_band(band) for band in radar_band]
418
-
419
- # Order radar band from longest to shortest wavelength
420
- # - ["S", "C", "X", "Ku", "Ka", "W"]
421
- radar_band = sorted(radar_band, key=lambda x: wavelength_dict[x])[::-1]
931
+ # Define default frequencies if not specified
932
+ if frequency is None:
933
+ frequency = available_radar_bands()
422
934
 
423
- # Retrieve combination of parameters
424
- list_params = [
425
- {
426
- "radar_band": rb.item(),
427
- "canting_angle_std": cas.item(),
428
- "axis_ratio": ar.item(),
429
- "diameter_max": d_max.item(),
430
- }
431
- for rb, cas, ar, d_max in itertools.product(radar_band, canting_angle_std, axis_ratio, diameter_max)
432
- ]
935
+ # Define parameters for all requested simulations
936
+ list_params = get_list_simulations_params(
937
+ frequency=frequency,
938
+ num_points=num_points,
939
+ diameter_max=diameter_max,
940
+ canting_angle_std=canting_angle_std,
941
+ axis_ratio_model=axis_ratio_model,
942
+ permittivity_model=permittivity_model,
943
+ water_temperature=water_temperature,
944
+ elevation_angle=elevation_angle,
945
+ )
433
946
 
434
947
  # Compute radar variables for each configuration in parallel
435
948
  # - The function expects the data into memory (no dask arrays !)
@@ -440,17 +953,46 @@ def get_radar_parameters(
440
953
  list_ds = [func(ds_subset, **params) for params in list_params]
441
954
 
442
955
  # Merge into a single dataset
443
- # - Order radar bands from longest to shortest wavelength
444
956
  ds_radar = xr.merge(list_ds)
445
- ds_radar = ds_radar.sel(radar_band=radar_band)
957
+
958
+ # Order frequency from lowest to highest
959
+ # --> ['S', 'C', 'X', 'Ku', 'K', 'Ka', 'W']
960
+ frequency = sorted(ds_radar["frequency"].to_numpy())
961
+ ds_radar = ds_radar.sel(frequency=frequency)
962
+
963
+ # Map default frequency to classical radar band
964
+ # --> This transform the frequency coordinate to dtype object
965
+ ds_radar = _replace_common_frequency_with_radar_band(ds_radar)
446
966
 
447
967
  # Copy global attributes from input dataset
448
968
  ds_radar.attrs = ds.attrs.copy()
449
969
 
450
- # Remove single dimensions (add info to attributes)
451
- parameters = ["radar_band", "canting_angle_std", "axis_ratio", "diameter_max"]
452
- for param in parameters:
970
+ # Remove single dimensions and add scattering settings information for single dimensions
971
+ scattering_string = ""
972
+ for param in RADAR_OPTIONS:
453
973
  if ds_radar.sizes[param] == 1:
454
- ds_radar.attrs[f"disdrodb_scattering_{param}"] = ds_radar[param].item()
974
+ value = ds_radar[param].item()
975
+ scattering_string += f"param: {value}; "
976
+
977
+ if scattering_string != "":
978
+ ds_radar.attrs["disdrodb_scattering_options"] = scattering_string
455
979
  ds_radar = ds_radar.squeeze()
456
980
  return ds_radar
981
+
982
+
983
+ def _map_frequency_to_band(f):
984
+ """Function to map frequency value to radar band."""
985
+ for band, val in frequency_dict.items():
986
+ if np.isclose(f, val):
987
+ return band
988
+ return f
989
+
990
+
991
+ def _replace_common_frequency_with_radar_band(ds_radar):
992
+ """Replace dataset coordinates with radar band if the case."""
993
+ # Map frequencies to radar bands
994
+ frequency = ds_radar["frequency"].to_numpy()
995
+ frequency = [_map_frequency_to_band(f) for f in frequency]
996
+ # Update dataset with new coordinate labels
997
+ ds_radar = ds_radar.assign_coords({"frequency": ("frequency", frequency)})
998
+ return ds_radar