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.
- disdrodb/__init__.py +64 -34
- disdrodb/_config.py +5 -4
- disdrodb/_version.py +16 -3
- disdrodb/accessor/__init__.py +20 -0
- disdrodb/accessor/methods.py +125 -0
- disdrodb/api/checks.py +139 -9
- disdrodb/api/configs.py +4 -2
- disdrodb/api/info.py +10 -10
- disdrodb/api/io.py +237 -18
- disdrodb/api/path.py +81 -75
- disdrodb/api/search.py +6 -6
- disdrodb/cli/disdrodb_create_summary_station.py +91 -0
- disdrodb/cli/disdrodb_run_l0.py +1 -1
- disdrodb/cli/disdrodb_run_l0_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0b.py +1 -1
- disdrodb/cli/disdrodb_run_l0b_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0c.py +1 -1
- disdrodb/cli/disdrodb_run_l0c_station.py +1 -1
- disdrodb/cli/disdrodb_run_l2e_station.py +1 -1
- disdrodb/configs.py +149 -4
- disdrodb/constants.py +61 -0
- disdrodb/data_transfer/download_data.py +5 -5
- disdrodb/etc/configs/attributes.yaml +339 -0
- disdrodb/etc/configs/encodings.yaml +473 -0
- disdrodb/etc/products/L1/global.yaml +13 -0
- disdrodb/etc/products/L2E/10MIN.yaml +12 -0
- disdrodb/etc/products/L2E/1MIN.yaml +1 -0
- disdrodb/etc/products/L2E/global.yaml +22 -0
- disdrodb/etc/products/L2M/10MIN.yaml +12 -0
- disdrodb/etc/products/L2M/GAMMA_ML.yaml +8 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_LOG_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_Z_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/global.yaml +26 -0
- disdrodb/l0/__init__.py +13 -0
- disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
- disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +3 -3
- disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +5 -5
- disdrodb/l0/configs/PARSIVEL2/l0b_encodings.yml +3 -3
- disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PWS100/raw_data_format.yml +1 -1
- disdrodb/l0/l0a_processing.py +30 -30
- disdrodb/l0/l0b_nc_processing.py +108 -2
- disdrodb/l0/l0b_processing.py +4 -4
- disdrodb/l0/l0c_processing.py +5 -13
- disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +66 -0
- disdrodb/l0/readers/LPM/SLOVENIA/{CRNI_VRH.py → UL.py} +3 -0
- disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +195 -0
- disdrodb/l0/readers/PARSIVEL/GPM/PIERS.py +0 -2
- disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +4 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +168 -0
- disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +69 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +255 -134
- disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +525 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +9 -7
- disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +123 -0
- disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +120 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +1 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +126 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +20 -12
- disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +2 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +144 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +201 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +137 -0
- disdrodb/l0/readers/PARSIVEL2/{NETHERLANDS/DELFT.py → USA/C3WE.py} +65 -85
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +105 -99
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +151 -0
- disdrodb/l0/routines.py +105 -14
- disdrodb/l1/__init__.py +5 -0
- disdrodb/l1/filters.py +34 -20
- disdrodb/l1/processing.py +45 -44
- disdrodb/l1/resampling.py +77 -66
- disdrodb/l1/routines.py +35 -43
- disdrodb/l1_env/routines.py +18 -3
- disdrodb/l2/__init__.py +7 -0
- disdrodb/l2/empirical_dsd.py +58 -10
- disdrodb/l2/event.py +27 -120
- disdrodb/l2/processing.py +267 -116
- disdrodb/l2/routines.py +618 -254
- disdrodb/metadata/standards.py +3 -1
- disdrodb/psd/fitting.py +463 -144
- disdrodb/psd/models.py +8 -5
- disdrodb/routines.py +3 -3
- disdrodb/scattering/__init__.py +16 -4
- disdrodb/scattering/axis_ratio.py +56 -36
- disdrodb/scattering/permittivity.py +486 -0
- disdrodb/scattering/routines.py +701 -159
- disdrodb/summary/__init__.py +17 -0
- disdrodb/summary/routines.py +4120 -0
- disdrodb/utils/attrs.py +68 -125
- disdrodb/utils/compression.py +30 -1
- disdrodb/utils/dask.py +59 -8
- disdrodb/utils/dataframe.py +61 -7
- disdrodb/utils/directories.py +35 -15
- disdrodb/utils/encoding.py +33 -19
- disdrodb/utils/logger.py +13 -6
- disdrodb/utils/manipulations.py +71 -0
- disdrodb/utils/subsetting.py +214 -0
- disdrodb/utils/time.py +165 -19
- disdrodb/utils/writer.py +20 -7
- disdrodb/utils/xarray.py +2 -4
- disdrodb/viz/__init__.py +13 -0
- disdrodb/viz/plots.py +327 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/METADATA +3 -2
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/RECORD +121 -88
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/entry_points.txt +1 -0
- disdrodb/l1/encoding_attrs.py +0 -642
- disdrodb/l2/processing_options.py +0 -213
- /disdrodb/l0/readers/PARSIVEL/SLOVENIA/{UL_FGG.py → UL.py} +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/WHEEL +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/top_level.txt +0 -0
disdrodb/scattering/routines.py
CHANGED
|
@@ -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.
|
|
29
|
-
from disdrodb.
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
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(
|
|
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
|
|
57
|
-
"""
|
|
58
|
-
|
|
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
|
|
63
|
-
"""
|
|
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 =
|
|
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
|
-
#
|
|
68
|
-
# -
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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 =
|
|
249
|
+
scatterer.psd_integrator.num_points = num_points
|
|
93
250
|
# - Define maximum drop diameter
|
|
94
|
-
scatterer.psd_integrator.D_max =
|
|
251
|
+
scatterer.psd_integrator.D_max = diameter_max
|
|
95
252
|
# - Define geometries
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
188
|
-
|
|
543
|
+
frequency,
|
|
544
|
+
num_points=1024,
|
|
189
545
|
diameter_max=10,
|
|
190
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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 =
|
|
230
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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={
|
|
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
|
-
#
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
651
|
+
frequency,
|
|
652
|
+
num_points=1024,
|
|
653
|
+
diameter_max=10,
|
|
278
654
|
canting_angle_std=7,
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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
|
|
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
|
-
|
|
295
|
-
|
|
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 =
|
|
707
|
+
bin_edges = get_diameter_bin_edges(ds)
|
|
307
708
|
|
|
308
709
|
# Check argument validity
|
|
309
|
-
|
|
310
|
-
|
|
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 =
|
|
317
|
-
|
|
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
|
-
|
|
320
|
-
|
|
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={
|
|
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":
|
|
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
|
-
"
|
|
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
|
-
|
|
364
|
-
|
|
868
|
+
frequency=None,
|
|
869
|
+
num_points=1024,
|
|
365
870
|
diameter_max=8,
|
|
366
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
383
|
-
|
|
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
|
-
#
|
|
406
|
-
if
|
|
407
|
-
|
|
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
|
-
#
|
|
424
|
-
list_params =
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
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
|
|
451
|
-
|
|
452
|
-
for param in
|
|
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
|
-
|
|
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
|