cloudnetpy 1.49.9__py3-none-any.whl → 1.87.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 (116) hide show
  1. cloudnetpy/categorize/__init__.py +1 -2
  2. cloudnetpy/categorize/atmos_utils.py +297 -67
  3. cloudnetpy/categorize/attenuation.py +31 -0
  4. cloudnetpy/categorize/attenuations/__init__.py +37 -0
  5. cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
  6. cloudnetpy/categorize/attenuations/liquid_attenuation.py +84 -0
  7. cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
  8. cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
  9. cloudnetpy/categorize/categorize.py +332 -156
  10. cloudnetpy/categorize/classify.py +127 -125
  11. cloudnetpy/categorize/containers.py +107 -76
  12. cloudnetpy/categorize/disdrometer.py +40 -0
  13. cloudnetpy/categorize/droplet.py +23 -21
  14. cloudnetpy/categorize/falling.py +53 -24
  15. cloudnetpy/categorize/freezing.py +25 -12
  16. cloudnetpy/categorize/insects.py +35 -23
  17. cloudnetpy/categorize/itu.py +243 -0
  18. cloudnetpy/categorize/lidar.py +36 -41
  19. cloudnetpy/categorize/melting.py +34 -26
  20. cloudnetpy/categorize/model.py +84 -37
  21. cloudnetpy/categorize/mwr.py +18 -14
  22. cloudnetpy/categorize/radar.py +215 -102
  23. cloudnetpy/cli.py +578 -0
  24. cloudnetpy/cloudnetarray.py +43 -89
  25. cloudnetpy/concat_lib.py +218 -78
  26. cloudnetpy/constants.py +28 -10
  27. cloudnetpy/datasource.py +61 -86
  28. cloudnetpy/exceptions.py +49 -20
  29. cloudnetpy/instruments/__init__.py +5 -0
  30. cloudnetpy/instruments/basta.py +29 -12
  31. cloudnetpy/instruments/bowtie.py +135 -0
  32. cloudnetpy/instruments/ceilo.py +138 -115
  33. cloudnetpy/instruments/ceilometer.py +164 -80
  34. cloudnetpy/instruments/cl61d.py +21 -5
  35. cloudnetpy/instruments/cloudnet_instrument.py +74 -36
  36. cloudnetpy/instruments/copernicus.py +108 -30
  37. cloudnetpy/instruments/da10.py +54 -0
  38. cloudnetpy/instruments/disdrometer/common.py +126 -223
  39. cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
  40. cloudnetpy/instruments/disdrometer/thies.py +254 -87
  41. cloudnetpy/instruments/fd12p.py +201 -0
  42. cloudnetpy/instruments/galileo.py +65 -23
  43. cloudnetpy/instruments/hatpro.py +123 -49
  44. cloudnetpy/instruments/instruments.py +113 -1
  45. cloudnetpy/instruments/lufft.py +39 -17
  46. cloudnetpy/instruments/mira.py +268 -61
  47. cloudnetpy/instruments/mrr.py +187 -0
  48. cloudnetpy/instruments/nc_lidar.py +19 -8
  49. cloudnetpy/instruments/nc_radar.py +109 -55
  50. cloudnetpy/instruments/pollyxt.py +135 -51
  51. cloudnetpy/instruments/radiometrics.py +313 -59
  52. cloudnetpy/instruments/rain_e_h3.py +171 -0
  53. cloudnetpy/instruments/rpg.py +321 -189
  54. cloudnetpy/instruments/rpg_reader.py +74 -40
  55. cloudnetpy/instruments/toa5.py +49 -0
  56. cloudnetpy/instruments/vaisala.py +95 -343
  57. cloudnetpy/instruments/weather_station.py +774 -105
  58. cloudnetpy/metadata.py +90 -19
  59. cloudnetpy/model_evaluation/file_handler.py +55 -52
  60. cloudnetpy/model_evaluation/metadata.py +46 -20
  61. cloudnetpy/model_evaluation/model_metadata.py +1 -1
  62. cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
  63. cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
  64. cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
  65. cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
  66. cloudnetpy/model_evaluation/products/model_products.py +43 -35
  67. cloudnetpy/model_evaluation/products/observation_products.py +41 -35
  68. cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
  69. cloudnetpy/model_evaluation/products/tools.py +29 -20
  70. cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
  71. cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
  72. cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
  73. cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +15 -14
  74. cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
  75. cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
  76. cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
  77. cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
  78. cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
  79. cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
  80. cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
  81. cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
  82. cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
  83. cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
  84. cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
  85. cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
  86. cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
  87. cloudnetpy/model_evaluation/utils.py +2 -1
  88. cloudnetpy/output.py +170 -111
  89. cloudnetpy/plotting/__init__.py +2 -1
  90. cloudnetpy/plotting/plot_meta.py +562 -822
  91. cloudnetpy/plotting/plotting.py +1142 -704
  92. cloudnetpy/products/__init__.py +1 -0
  93. cloudnetpy/products/classification.py +370 -88
  94. cloudnetpy/products/der.py +85 -55
  95. cloudnetpy/products/drizzle.py +77 -34
  96. cloudnetpy/products/drizzle_error.py +15 -11
  97. cloudnetpy/products/drizzle_tools.py +79 -59
  98. cloudnetpy/products/epsilon.py +211 -0
  99. cloudnetpy/products/ier.py +27 -50
  100. cloudnetpy/products/iwc.py +55 -48
  101. cloudnetpy/products/lwc.py +96 -70
  102. cloudnetpy/products/mwr_tools.py +186 -0
  103. cloudnetpy/products/product_tools.py +170 -128
  104. cloudnetpy/utils.py +455 -240
  105. cloudnetpy/version.py +2 -2
  106. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
  107. cloudnetpy-1.87.3.dist-info/RECORD +127 -0
  108. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
  109. cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
  110. docs/source/conf.py +2 -2
  111. cloudnetpy/categorize/atmos.py +0 -361
  112. cloudnetpy/products/mwr_multi.py +0 -68
  113. cloudnetpy/products/mwr_single.py +0 -75
  114. cloudnetpy-1.49.9.dist-info/RECORD +0 -112
  115. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
  116. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,19 @@
1
1
  """Module for creating Cloudnet droplet effective radius
2
2
  using the Frisch et al. 2002 method.
3
3
  """
4
- from collections import namedtuple
4
+
5
+ from os import PathLike
6
+ from typing import NamedTuple
7
+ from uuid import UUID
5
8
 
6
9
  import numpy as np
10
+ import numpy.typing as npt
7
11
  from numpy import ma
8
12
 
9
13
  from cloudnetpy import output, utils
10
- from cloudnetpy.categorize import atmos
14
+ from cloudnetpy.categorize import atmos_utils
11
15
  from cloudnetpy.datasource import DataSource
16
+ from cloudnetpy.exceptions import InvalidSourceFileError
12
17
  from cloudnetpy.metadata import MetaData
13
18
  from cloudnetpy.products.product_tools import (
14
19
  CategorizeBits,
@@ -16,17 +21,24 @@ from cloudnetpy.products.product_tools import (
16
21
  get_is_rain,
17
22
  )
18
23
 
19
- Parameters = namedtuple("Parameters", "ddBZ N dN sigma_x dsigma_x dQ")
24
+
25
+ class Parameters(NamedTuple):
26
+ ddBZ: float
27
+ N: float
28
+ dN: float
29
+ sigma_x: float
30
+ dsigma_x: float
31
+ dQ: float
20
32
 
21
33
 
22
34
  def generate_der(
23
- categorize_file: str,
24
- output_file: str,
25
- uuid: str | None = None,
35
+ categorize_file: str | PathLike,
36
+ output_file: str | PathLike,
37
+ uuid: str | UUID | None = None,
26
38
  parameters: Parameters | None = None,
27
- ) -> str:
39
+ ) -> UUID:
28
40
  """Generates Cloudnet effective radius of liquid water droplets
29
- product acording to Frisch et al. 2002.
41
+ product according to Frisch et al. 2002.
30
42
 
31
43
  This function calculates liquid droplet effective radius def
32
44
  using the Frisch method. In this method, def is calculated
@@ -37,7 +49,7 @@ def generate_der(
37
49
  categorize_file: Categorize file name.
38
50
  output_file: Output file name.
39
51
  uuid: Set specific UUID for the file.
40
- parameters: Tuple of specific fixed paramaters
52
+ parameters: Tuple of specific fixed parameters
41
53
  (ddBZ, N, dN, sigma_x, dsigma_x, dQ)
42
54
  used in Frisch approach.
43
55
 
@@ -60,16 +72,16 @@ def generate_der(
60
72
  https://doi.org/10.1175/1520-0426(2002)019%3C0835:TROSCD%3E2.0.CO;2
61
73
 
62
74
  """
63
- der_source = DerSource(categorize_file, parameters)
64
- droplet_classification = DropletClassification(categorize_file)
65
- der_source.append_der()
66
- der_source.append_retrieval_status(droplet_classification)
67
- date = der_source.get_date()
68
- attributes = output.add_time_attribute(REFF_ATTRIBUTES, date)
69
- attributes = _add_der_error_comment(attributes, der_source)
70
- output.update_attributes(der_source.data, attributes)
71
- uuid = output.save_product_file("der", der_source, output_file, uuid)
72
- der_source.close()
75
+ uuid = utils.get_uuid(uuid)
76
+ with DerSource(categorize_file, parameters) as der_source:
77
+ droplet_classification = DropletClassification(categorize_file)
78
+ der_source.append_der()
79
+ der_source.append_retrieval_status(droplet_classification)
80
+ date = der_source.get_date()
81
+ attributes = output.add_time_attribute(REFF_ATTRIBUTES, date)
82
+ attributes = _add_der_error_comment(attributes, der_source)
83
+ output.update_attributes(der_source.data, attributes)
84
+ output.save_product_file("der", der_source, output_file, uuid)
73
85
  return uuid
74
86
 
75
87
 
@@ -78,49 +90,53 @@ class DropletClassification(ProductClassification):
78
90
  Child of ProductClassification().
79
91
  """
80
92
 
81
- def __init__(self, categorize_file: str):
93
+ def __init__(self, categorize_file: str | PathLike) -> None:
82
94
  super().__init__(categorize_file)
83
95
  self.is_mixed = self._find_mixed()
84
96
  self.is_droplet = self._find_droplet()
85
97
  self.is_ice = self._find_ice()
86
98
 
87
- def _find_droplet(self) -> np.ndarray:
88
- return self.category_bits["droplet"]
99
+ def _find_droplet(self) -> npt.NDArray:
100
+ return self.category_bits.droplet
89
101
 
90
- def _find_mixed(self) -> np.ndarray:
91
- return self.category_bits["falling"] & self.category_bits["droplet"]
102
+ def _find_mixed(self) -> npt.NDArray:
103
+ return self.category_bits.falling & self.category_bits.droplet
92
104
 
93
- def _find_ice(self) -> np.ndarray:
105
+ def _find_ice(self) -> npt.NDArray:
94
106
  return (
95
- self.category_bits["falling"]
96
- & self.category_bits["cold"]
97
- & ~self.category_bits["melting"]
98
- & ~self.category_bits["droplet"]
99
- & ~self.category_bits["insect"]
107
+ self.category_bits.falling
108
+ & self.category_bits.freezing
109
+ & ~self.category_bits.melting
110
+ & ~self.category_bits.droplet
111
+ & ~self.category_bits.insect
100
112
  )
101
113
 
102
114
 
103
115
  class DerSource(DataSource):
104
116
  """Data container for effective radius calculations."""
105
117
 
106
- def __init__(self, categorize_file: str, parameters: Parameters | None = None):
118
+ def __init__(
119
+ self, categorize_file: str | PathLike, parameters: Parameters | None = None
120
+ ) -> None:
107
121
  super().__init__(categorize_file)
122
+ if "lwp" not in self.dataset.variables:
123
+ msg = "Liquid water path missing from the categorize file."
124
+ raise InvalidSourceFileError(msg)
108
125
  self.is_rain = get_is_rain(categorize_file)
109
126
  self.categorize_bits = CategorizeBits(categorize_file)
127
+ self.height_agl: npt.NDArray
110
128
  if parameters is None:
111
129
  # Default parameters from Frisch et al. 2002
112
130
  self.parameters = Parameters(2.0, 200.0e6, 200.0e6, 0.35, 0.1, 5.0e-3)
113
131
  else:
114
132
  self.parameters = parameters
115
133
 
116
- def append_der(self):
134
+ def append_der(self) -> None:
117
135
  """Estimate liquid droplet effective radius using Frisch et al. 2002."""
118
-
119
136
  params = self.parameters
120
137
  rho_l = 1000 # density of liquid water(kg m-3)
121
138
 
122
139
  var_x = params.sigma_x * params.sigma_x
123
- dheight = utils.mdiff(self.getvar("height"))
124
140
 
125
141
  Z = self.getvar("Z")
126
142
  Z = utils.db2lin(Z)
@@ -135,11 +151,17 @@ class DerSource(DataSource):
135
151
  der_scaled_error = np.zeros(Z.shape)
136
152
  N_scaled = np.zeros(Z.shape)
137
153
 
138
- is_droplet = self.categorize_bits.category_bits["droplet"]
139
- liquid_bases = atmos.find_cloud_bases(is_droplet)
140
- liquid_tops = atmos.find_cloud_tops(is_droplet)
154
+ is_droplet = self.categorize_bits.category_bits.droplet
155
+ liquid_bases = atmos_utils.find_cloud_bases(is_droplet)
156
+ liquid_tops = atmos_utils.find_cloud_tops(is_droplet)
141
157
 
142
- for base, top in zip(zip(*np.where(liquid_bases)), zip(*np.where(liquid_tops))):
158
+ path_lengths = utils.path_lengths_from_ground(self.height_agl)
159
+
160
+ for base, top in zip(
161
+ zip(*np.where(liquid_bases), strict=True),
162
+ zip(*np.where(liquid_tops), strict=True),
163
+ strict=True,
164
+ ):
143
165
  ind_t = base[0]
144
166
  idx_layer = np.arange(base[1], top[1] + 1)
145
167
  idx_layer = idx_layer[~Z[ind_t, idx_layer].mask]
@@ -147,7 +169,7 @@ class DerSource(DataSource):
147
169
  if Z[ind_t, idx_layer].mask.all():
148
170
  continue
149
171
 
150
- integral = ma.sum(ma.sqrt(Z[ind_t, idx_layer])) * dheight
172
+ integral = ma.sum(ma.sqrt(Z[ind_t, idx_layer])) * path_lengths[idx_layer]
151
173
 
152
174
  # der formula (5)
153
175
  A = (Z[ind_t, idx_layer] / params.N) ** (1 / 6)
@@ -159,7 +181,7 @@ class DerSource(DataSource):
159
181
  B = params.sigma_x * params.dsigma_x
160
182
  C = dZ[ind_t, idx_layer] / (6 * Z[ind_t, idx_layer])
161
183
  der_error[ind_t, idx_layer] = der[ind_t, idx_layer] * ma.sqrt(
162
- A * A + B * B + C * C
184
+ A * A + B * B + C * C,
163
185
  )
164
186
 
165
187
  # der scaled formula (6)
@@ -176,7 +198,7 @@ class DerSource(DataSource):
176
198
  B = 4 * params.sigma_x * params.dsigma_x
177
199
  C = params.dQ / (3 * lwp[ind_t])
178
200
  der_scaled_error[ind_t, idx_layer] = der_scaled[ind_t, idx_layer] * ma.sqrt(
179
- A * A + B * B + C * C
201
+ A * A + B * B + C * C,
180
202
  )
181
203
 
182
204
  N_scaled = ma.masked_less_equal(ma.masked_invalid(N_scaled), 0.0) * 1.0e-6
@@ -197,13 +219,14 @@ class DerSource(DataSource):
197
219
  self.append_data(der_scaled_error, "der_scaled_error")
198
220
 
199
221
  def append_retrieval_status(
200
- self, droplet_classification: DropletClassification
222
+ self,
223
+ droplet_classification: DropletClassification,
201
224
  ) -> None:
202
225
  """Returns information about the status of der retrieval."""
203
226
  is_retrieved = ~self.data["der"][:].mask
204
227
  is_mixed = droplet_classification.is_mixed
205
228
  is_ice = droplet_classification.is_ice
206
- is_rain = np.tile(self.is_rain, (is_retrieved.shape[1], 1)).T
229
+ is_rain = np.tile(self.is_rain == 1, (is_retrieved.shape[1], 1)).T
207
230
 
208
231
  retrieval_status = np.zeros(is_retrieved.shape, dtype=int)
209
232
  retrieval_status[is_ice] = 4
@@ -214,17 +237,18 @@ class DerSource(DataSource):
214
237
 
215
238
 
216
239
  DEFINITIONS = {
217
- "der_retrieval_status": (
218
- "\n"
219
- "Value 0: No data: No cloud observed.\n"
220
- "Value 1: Reliable retrieval.\n"
221
- "Value 2: Mix of drops and ice: Droplets and ice crystals coexist within\n"
222
- "pixel. Z may be biased by large crystals.\n"
223
- "Value 3: Precipitation in profile: Drizzle and rain affects LWP retrieval\n"
224
- " of MWR but also the target reflectivity.\n"
225
- "Value 4: Surrounding ice: Less crucial! Ice crystals in the vicinity of a\n"
226
- " droplet pixel may also bias its reflectivity.\n"
227
- )
240
+ "der_retrieval_status": utils.status_field_definition(
241
+ {
242
+ 0: """No data: No cloud observed.""",
243
+ 1: """Reliable retrieval.""",
244
+ 2: """Mix of drops and ice: Droplets and ice crystals coexist within
245
+ pixel. Z may be biased by large crystals.""",
246
+ 3: """Precipitation in profile: Drizzle and rain affects LWP
247
+ retrieval of MWR but also the target reflectivity.""",
248
+ 4: """Surrounding ice: Less crucial! Ice crystals in the vicinity of
249
+ a droplet pixel may also bias its reflectivity.""",
250
+ }
251
+ ),
228
252
  }
229
253
 
230
254
 
@@ -271,7 +295,7 @@ def _add_der_error_comment(attributes: dict, der_source: DerSource) -> dict:
271
295
  comment="This variable is an estimate of the random error in effective\n"
272
296
  f"radius assuming an error in Z of ddBZ = {params.ddBZ} in N of\n"
273
297
  f"dN = {params.dN} and in the spectral width dsigma_x = {params.dsigma_x}\n"
274
- f"and in the LWP Q of {params.dQ} kg m-3."
298
+ f"and in the LWP Q of {params.dQ} kg m-3.",
275
299
  )
276
300
  return attributes
277
301
 
@@ -283,32 +307,38 @@ REFF_ATTRIBUTES = {
283
307
  units="m",
284
308
  ancillary_variables="der_error",
285
309
  comment=COMMENTS["der"],
310
+ dimensions=("time", "height"),
286
311
  ),
287
312
  "der_error": MetaData(
288
313
  long_name="Absolute error in droplet effective radius",
289
314
  units="m",
290
315
  comment="",
316
+ dimensions=("time", "height"),
291
317
  ),
292
318
  "der_scaled": MetaData(
293
319
  long_name="Droplet effective radius (scaled to LWP)",
294
320
  units="m",
295
321
  ancillary_variables="der_scaled_error",
296
322
  comment=COMMENTS["der_scaled"],
323
+ dimensions=("time", "height"),
297
324
  ),
298
325
  "der_scaled_error": MetaData(
299
326
  long_name="Absolute error in droplet effective radius (scaled to LWP)",
300
327
  units="m",
301
328
  comment=COMMENTS["der_scaled_error"],
329
+ dimensions=("time", "height"),
302
330
  ),
303
331
  "N_scaled": MetaData(
304
332
  long_name="Cloud droplet number concentration",
305
333
  units="1",
306
334
  ancillary_variables="der_error der_scaled der_scaled_error",
307
335
  comment=COMMENTS["N_scaled"],
336
+ dimensions=("time", "height"),
308
337
  ),
309
338
  "der_retrieval_status": MetaData(
310
339
  long_name="Droplet effective radius retrieval status",
311
340
  definition=DEFINITIONS["der_retrieval_status"],
312
341
  units="1",
342
+ dimensions=("time", "height"),
313
343
  ),
314
344
  }
@@ -1,6 +1,10 @@
1
- """Module for creating Cloudnet drizzle product.
2
- """
1
+ """Module for creating Cloudnet drizzle product."""
2
+
3
+ from os import PathLike
4
+ from uuid import UUID
5
+
3
6
  import numpy as np
7
+ import numpy.typing as npt
4
8
  from numpy import ma
5
9
  from scipy.special import gamma
6
10
 
@@ -16,8 +20,10 @@ from cloudnetpy.products.drizzle_tools import (
16
20
 
17
21
 
18
22
  def generate_drizzle(
19
- categorize_file: str, output_file: str, uuid: str | None = None
20
- ) -> str:
23
+ categorize_file: str | PathLike,
24
+ output_file: str | PathLike,
25
+ uuid: str | UUID | None = None,
26
+ ) -> UUID:
21
27
  """Generates Cloudnet drizzle product.
22
28
 
23
29
  This function calculates different drizzle properties from
@@ -41,6 +47,7 @@ def generate_drizzle(
41
47
  J. Appl. Meteor., 44, 14–27, https://doi.org/10.1175/JAM-2181.1
42
48
 
43
49
  """
50
+ uuid = utils.get_uuid(uuid)
44
51
  with DrizzleSource(categorize_file) as drizzle_source:
45
52
  drizzle_class = DrizzleClassification(categorize_file)
46
53
  spectral_width = SpectralWidth(categorize_file)
@@ -59,8 +66,8 @@ def generate_drizzle(
59
66
  date = drizzle_source.get_date()
60
67
  attributes = output.add_time_attribute(DRIZZLE_ATTRIBUTES, date)
61
68
  output.update_attributes(drizzle_source.data, attributes)
62
- uuid = output.save_product_file("drizzle", drizzle_source, output_file, uuid)
63
- return uuid
69
+ output.save_product_file("drizzle", drizzle_source, output_file, uuid)
70
+ return uuid
64
71
 
65
72
 
66
73
  class DrizzleProducts:
@@ -76,13 +83,15 @@ class DrizzleProducts:
76
83
 
77
84
  """
78
85
 
79
- def __init__(self, drizzle_source: DrizzleSource, drizzle_solver: DrizzleSolver):
86
+ def __init__(
87
+ self, drizzle_source: DrizzleSource, drizzle_solver: DrizzleSolver
88
+ ) -> None:
80
89
  self._data = drizzle_source
81
90
  self._params = drizzle_solver.params
82
91
  self._ind_drizzle, self._ind_lut = self._find_indices()
83
92
  self.derived_products = self._calc_derived_products()
84
93
 
85
- def _find_indices(self):
94
+ def _find_indices(self) -> tuple:
86
95
  drizzle_ind = np.where(self._params["Do"])
87
96
  ind_mu = np.searchsorted(self._data.mie["mu"], self._params["mu"][drizzle_ind])
88
97
  ind_dia = np.searchsorted(self._data.mie["Do"], self._params["Do"][drizzle_ind])
@@ -91,7 +100,7 @@ class DrizzleProducts:
91
100
  ind_dia[ind_dia >= n_dia] = n_dia - 1
92
101
  return drizzle_ind, (ind_mu, ind_dia)
93
102
 
94
- def _calc_derived_products(self):
103
+ def _calc_derived_products(self) -> dict:
95
104
  density = self._calc_density()
96
105
  lwc = self._calc_lwc()
97
106
  lwf = self._calc_lwf(lwc)
@@ -105,23 +114,23 @@ class DrizzleProducts:
105
114
  "v_air": v_air,
106
115
  }
107
116
 
108
- def _calc_density(self):
117
+ def _calc_density(self) -> npt.NDArray:
109
118
  """Calculates drizzle number density (m-3)."""
110
119
  a = self._data.z * 3.67**6
111
120
  b = self._params["Do"] ** 6
112
121
  return np.divide(a, b, out=np.zeros_like(a), where=b != 0)
113
122
 
114
- def _calc_lwc(self):
115
- """Calculates drizzle liquid water content (kg m-3)"""
123
+ def _calc_lwc(self) -> npt.NDArray:
124
+ """Calculates drizzle liquid water content (kg m-3)."""
116
125
  rho_water = 1000
117
- dia, mu, s = [self._params.get(key) for key in ("Do", "mu", "S")]
118
- assert isinstance(mu, np.ndarray)
119
- assert isinstance(s, np.ndarray)
120
- assert isinstance(dia, np.ndarray)
126
+ dia, mu, s = (self._params.get(key) for key in ("Do", "mu", "S"))
127
+ dia = ma.array(dia)
128
+ mu = ma.array(mu)
129
+ s = ma.array(s)
121
130
  gamma_ratio = gamma(4 + mu) / gamma(3 + mu) / (3.67 + mu)
122
131
  return rho_water / 3 * self._data.beta * s * dia * gamma_ratio
123
132
 
124
- def _calc_lwf(self, lwc_in):
133
+ def _calc_lwf(self, lwc_in: npt.NDArray) -> npt.NDArray:
125
134
  """Calculates drizzle liquid water flux."""
126
135
  flux = ma.copy(lwc_in)
127
136
  flux[self._ind_drizzle] *= (
@@ -130,13 +139,13 @@ class DrizzleProducts:
130
139
  )
131
140
  return flux
132
141
 
133
- def _calc_fall_velocity(self):
142
+ def _calc_fall_velocity(self) -> npt.NDArray:
134
143
  """Calculates drizzle droplet fall velocity (m s-1)."""
135
144
  velocity = np.zeros_like(self._params["Do"])
136
145
  velocity[self._ind_drizzle] = -self._data.mie["v"][self._ind_lut]
137
146
  return velocity
138
147
 
139
- def _calc_v_air(self, droplet_velocity):
148
+ def _calc_v_air(self, droplet_velocity: npt.NDArray) -> npt.NDArray:
140
149
  """Calculates vertical air velocity."""
141
150
  velocity = -np.copy(droplet_velocity)
142
151
  velocity[self._ind_drizzle] += self._data.v[self._ind_drizzle]
@@ -155,42 +164,44 @@ class RetrievalStatus:
155
164
  status information.
156
165
  """
157
166
 
158
- def __init__(self, drizzle_class: DrizzleClassification):
167
+ def __init__(self, drizzle_class: DrizzleClassification) -> None:
159
168
  self.drizzle_class = drizzle_class
160
- self.retrieval_status: np.ndarray = np.array([])
169
+ self.retrieval_status: npt.NDArray = np.array([])
161
170
  self._get_retrieval_status()
162
171
 
163
- def _get_retrieval_status(self):
172
+ def _get_retrieval_status(self) -> None:
164
173
  self.retrieval_status = np.copy(self.drizzle_class.drizzle).astype(int)
165
174
  self._find_retrieval_below_melting()
166
175
  self.retrieval_status[self.drizzle_class.would_be_drizzle == 1] = 3
167
176
  self._find_retrieval_in_warm_liquid()
168
177
  self.retrieval_status[self.drizzle_class.is_rain == 1, :] = 5
169
178
 
170
- def _find_retrieval_below_melting(self):
179
+ def _find_retrieval_below_melting(self) -> None:
171
180
  cold_rain = utils.transpose(self.drizzle_class.cold_rain)
172
181
  below_melting = cold_rain * self.drizzle_class.drizzle
173
182
  self.retrieval_status[below_melting == 1] = 2
174
183
 
175
- def _find_retrieval_in_warm_liquid(self):
184
+ def _find_retrieval_in_warm_liquid(self) -> None:
176
185
  in_warm_liquid = (self.retrieval_status == 0) * self.drizzle_class.warm_liquid
177
186
  self.retrieval_status[in_warm_liquid == 1] = 4
178
187
 
179
188
 
180
- def _screen_rain(results: dict, classification: DrizzleClassification):
189
+ def _screen_rain(results: dict, classification: DrizzleClassification) -> dict:
181
190
  """Removes rainy profiles from drizzle variables.."""
182
- for key in results.keys():
191
+ for key in results:
183
192
  if not utils.isscalar(results[key]):
184
193
  results[key][classification.is_rain, :] = 0
185
194
  return results
186
195
 
187
196
 
188
- def _append_data(drizzle_data: DrizzleSource, results: dict):
197
+ def _append_data(drizzle_data: DrizzleSource, results: dict) -> None:
189
198
  """Save retrieved fields to the drizzle_data object."""
190
199
  for key, value in results.items():
191
200
  if key != "drizzle_retrieval_status":
192
- value = ma.masked_where(value == 0, value)
193
- drizzle_data.append_data(value, key)
201
+ arr = ma.masked_where(value == 0, value)
202
+ else:
203
+ arr = value
204
+ drizzle_data.append_data(arr, key)
194
205
 
195
206
 
196
207
  DRIZZLE_ATTRIBUTES = {
@@ -198,95 +209,127 @@ DRIZZLE_ATTRIBUTES = {
198
209
  long_name="Drizzle number concentration",
199
210
  units="m-3",
200
211
  ancillary_variables="drizzle_N_error drizzle_N_bias",
212
+ dimensions=("time", "height"),
201
213
  ),
202
214
  "drizzle_N_error": MetaData(
203
- long_name="Random error in drizzle number concentration", units="dB"
215
+ long_name="Random error in drizzle number concentration",
216
+ units="dB",
217
+ dimensions=("time", "height"),
204
218
  ),
205
219
  "drizzle_N_bias": MetaData(
206
220
  long_name="Possible bias in drizzle number concentration",
207
221
  units="dB",
222
+ dimensions=None,
208
223
  ),
209
224
  "drizzle_lwc": MetaData(
210
225
  long_name="Drizzle liquid water content",
211
226
  units="kg m-3",
212
227
  ancillary_variables="drizzle_lwc_error drizzle_lwc_bias",
228
+ standard_name="mass_concentration_of_drizzle_in_air",
229
+ dimensions=("time", "height"),
213
230
  ),
214
231
  "drizzle_lwc_error": MetaData(
215
232
  long_name="Random error in drizzle liquid water content",
216
233
  units="dB",
234
+ dimensions=("time", "height"),
217
235
  ),
218
236
  "drizzle_lwc_bias": MetaData(
219
237
  long_name="Possible bias in drizzle liquid water content",
220
238
  units="dB",
239
+ dimensions=None,
221
240
  ),
222
241
  "drizzle_lwf": MetaData(
223
242
  long_name="Drizzle liquid water flux",
224
243
  units="kg m-2 s-1",
225
244
  ancillary_variables="drizzle_lwf_error drizzle_lwf_bias",
245
+ dimensions=("time", "height"),
226
246
  ),
227
247
  "drizzle_lwf_error": MetaData(
228
248
  long_name="Random error in drizzle liquid water flux",
229
249
  units="dB",
250
+ dimensions=("time", "height"),
230
251
  ),
231
252
  "drizzle_lwf_bias": MetaData(
232
253
  long_name="Possible bias in drizzle liquid water flux",
233
254
  units="dB",
255
+ dimensions=None,
234
256
  ),
235
257
  "v_drizzle": MetaData(
236
258
  long_name="Drizzle droplet fall velocity", # TODO: should include 'terminal' ?
237
259
  units="m s-1",
238
260
  ancillary_variables="v_drizzle_error v_drizzle_bias",
239
261
  comment="Positive values are towards the ground.",
262
+ dimensions=("time", "height"),
240
263
  ),
241
264
  "v_drizzle_error": MetaData(
242
- long_name="Random error in drizzle droplet fall velocity", units="dB"
265
+ long_name="Random error in drizzle droplet fall velocity",
266
+ units="dB",
267
+ dimensions=("time", "height"),
243
268
  ),
244
269
  "v_drizzle_bias": MetaData(
245
270
  long_name="Possible bias in drizzle droplet fall velocity",
246
271
  units="dB",
272
+ dimensions=None,
247
273
  ),
248
274
  "v_air": MetaData(
249
275
  long_name="Vertical air velocity",
250
276
  units="m s-1",
251
277
  ancillary_variables="v_air_error",
252
278
  comment="Positive values are towards the sky.",
279
+ standard_name="upward_air_velocity",
280
+ dimensions=("time", "height"),
253
281
  ),
254
282
  "v_air_error": MetaData(
255
- long_name="Random error in vertical air velocity", units="dB"
283
+ long_name="Random error in vertical air velocity",
284
+ units="dB",
285
+ dimensions=("time", "height"),
256
286
  ),
257
287
  "Do": MetaData(
258
288
  long_name="Drizzle median diameter",
259
289
  units="m",
260
290
  ancillary_variables="Do_error Do_bias",
291
+ dimensions=("time", "height"),
261
292
  ),
262
293
  "Do_error": MetaData(
263
294
  long_name="Random error in drizzle median diameter",
264
295
  units="dB",
296
+ dimensions=("time", "height"),
265
297
  ),
266
298
  "Do_bias": MetaData(
267
299
  long_name="Possible bias in drizzle median diameter",
268
300
  units="dB",
301
+ dimensions=None,
269
302
  ),
270
303
  "mu": MetaData(
271
304
  long_name="Drizzle droplet size distribution shape parameter",
272
305
  ancillary_variables="mu_error",
273
306
  units="1",
307
+ dimensions=("time", "height"),
274
308
  ),
275
309
  "mu_error": MetaData(
276
310
  long_name="Random error in drizzle droplet size distribution shape parameter",
277
311
  units="dB",
312
+ dimensions=("time", "height"),
278
313
  ),
279
314
  "S": MetaData(
280
315
  long_name="Lidar backscatter-to-extinction ratio",
281
316
  ancillary_variables="S_error",
282
317
  units="sr",
318
+ dimensions=("time", "height"),
283
319
  ),
284
320
  "S_error": MetaData(
285
321
  long_name="Random error in lidar backscatter-to-extinction ratio",
286
322
  units="dB",
323
+ dimensions=("time", "height"),
324
+ ),
325
+ "beta_corr": MetaData(
326
+ long_name="Lidar backscatter correction factor",
327
+ units="1",
328
+ dimensions=("time", "height"),
287
329
  ),
288
- "beta_corr": MetaData(long_name="Lidar backscatter correction factor", units="1"),
289
330
  "drizzle_retrieval_status": MetaData(
290
- long_name="Drizzle parameter retrieval status", units="1"
331
+ long_name="Drizzle parameter retrieval status",
332
+ units="1",
333
+ dimensions=("time", "height"),
291
334
  ),
292
335
  }