disdrodb 0.0.21__py3-none-any.whl → 0.1.0__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 (264) hide show
  1. disdrodb/__init__.py +132 -15
  2. disdrodb/_config.py +4 -2
  3. disdrodb/_version.py +9 -4
  4. disdrodb/api/checks.py +264 -237
  5. disdrodb/api/configs.py +4 -8
  6. disdrodb/api/create_directories.py +235 -290
  7. disdrodb/api/info.py +217 -26
  8. disdrodb/api/io.py +295 -269
  9. disdrodb/api/path.py +597 -173
  10. disdrodb/api/search.py +486 -0
  11. disdrodb/{metadata/scripts → cli}/disdrodb_check_metadata_archive.py +12 -7
  12. disdrodb/{utils/pandas.py → cli/disdrodb_data_archive_directory.py} +9 -18
  13. disdrodb/cli/disdrodb_download_archive.py +86 -0
  14. disdrodb/cli/disdrodb_download_metadata_archive.py +53 -0
  15. disdrodb/cli/disdrodb_download_station.py +84 -0
  16. disdrodb/{api/scripts → cli}/disdrodb_initialize_station.py +22 -10
  17. disdrodb/cli/disdrodb_metadata_archive_directory.py +32 -0
  18. disdrodb/{data_transfer/scripts/disdrodb_download_station.py → cli/disdrodb_open_data_archive.py} +22 -22
  19. disdrodb/cli/disdrodb_open_logs_directory.py +69 -0
  20. disdrodb/{data_transfer/scripts/disdrodb_upload_station.py → cli/disdrodb_open_metadata_archive.py} +22 -24
  21. disdrodb/cli/disdrodb_open_metadata_directory.py +71 -0
  22. disdrodb/cli/disdrodb_open_product_directory.py +74 -0
  23. disdrodb/cli/disdrodb_open_readers_directory.py +32 -0
  24. disdrodb/{l0/scripts → cli}/disdrodb_run_l0.py +38 -31
  25. disdrodb/{l0/scripts → cli}/disdrodb_run_l0_station.py +32 -30
  26. disdrodb/{l0/scripts → cli}/disdrodb_run_l0a.py +30 -21
  27. disdrodb/{l0/scripts → cli}/disdrodb_run_l0a_station.py +24 -33
  28. disdrodb/{l0/scripts → cli}/disdrodb_run_l0b.py +30 -21
  29. disdrodb/{l0/scripts → cli}/disdrodb_run_l0b_station.py +25 -34
  30. disdrodb/cli/disdrodb_run_l0c.py +130 -0
  31. disdrodb/cli/disdrodb_run_l0c_station.py +129 -0
  32. disdrodb/cli/disdrodb_run_l1.py +122 -0
  33. disdrodb/cli/disdrodb_run_l1_station.py +121 -0
  34. disdrodb/cli/disdrodb_run_l2e.py +122 -0
  35. disdrodb/cli/disdrodb_run_l2e_station.py +122 -0
  36. disdrodb/cli/disdrodb_run_l2m.py +122 -0
  37. disdrodb/cli/disdrodb_run_l2m_station.py +122 -0
  38. disdrodb/cli/disdrodb_upload_archive.py +105 -0
  39. disdrodb/cli/disdrodb_upload_station.py +98 -0
  40. disdrodb/configs.py +90 -25
  41. disdrodb/data_transfer/__init__.py +22 -0
  42. disdrodb/data_transfer/download_data.py +87 -90
  43. disdrodb/data_transfer/upload_data.py +64 -37
  44. disdrodb/data_transfer/zenodo.py +15 -18
  45. disdrodb/docs.py +1 -1
  46. disdrodb/issue/__init__.py +17 -4
  47. disdrodb/issue/checks.py +10 -23
  48. disdrodb/issue/reader.py +9 -12
  49. disdrodb/issue/writer.py +14 -17
  50. disdrodb/l0/__init__.py +17 -26
  51. disdrodb/l0/check_configs.py +35 -23
  52. disdrodb/l0/check_standards.py +32 -42
  53. disdrodb/l0/configs/{Thies_LPM → LPM}/bins_diameter.yml +44 -44
  54. disdrodb/l0/configs/{Thies_LPM → LPM}/bins_velocity.yml +40 -40
  55. disdrodb/l0/configs/LPM/l0a_encodings.yml +80 -0
  56. disdrodb/l0/configs/{Thies_LPM → LPM}/l0b_cf_attrs.yml +62 -59
  57. disdrodb/l0/configs/{Thies_LPM → LPM}/l0b_encodings.yml +9 -9
  58. disdrodb/l0/configs/{Thies_LPM → LPM}/raw_data_format.yml +245 -245
  59. disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/bins_diameter.yml +66 -66
  60. disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/bins_velocity.yml +64 -64
  61. disdrodb/l0/configs/PARSIVEL/l0a_encodings.yml +32 -0
  62. disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/l0b_cf_attrs.yml +22 -20
  63. disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/l0b_encodings.yml +17 -17
  64. disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/raw_data_format.yml +77 -77
  65. disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/bins_diameter.yml +64 -64
  66. disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/bins_velocity.yml +64 -64
  67. disdrodb/l0/configs/PARSIVEL2/l0a_encodings.yml +39 -0
  68. disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/l0b_cf_attrs.yml +24 -22
  69. disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/l0b_encodings.yml +20 -20
  70. disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/raw_data_format.yml +98 -98
  71. disdrodb/l0/configs/{RD_80 → RD80}/bins_diameter.yml +40 -40
  72. disdrodb/l0/configs/RD80/l0a_encodings.yml +16 -0
  73. disdrodb/l0/configs/{RD_80 → RD80}/l0b_cf_attrs.yml +3 -3
  74. disdrodb/l0/configs/RD80/l0b_encodings.yml +135 -0
  75. disdrodb/l0/configs/{RD_80 → RD80}/raw_data_format.yml +48 -48
  76. disdrodb/l0/l0_reader.py +216 -340
  77. disdrodb/l0/l0a_processing.py +237 -208
  78. disdrodb/l0/l0b_nc_processing.py +227 -80
  79. disdrodb/l0/l0b_processing.py +93 -173
  80. disdrodb/l0/l0c_processing.py +627 -0
  81. disdrodb/l0/readers/{ARM → LPM/ARM}/ARM_LPM.py +36 -58
  82. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +226 -0
  83. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +185 -0
  84. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +183 -0
  85. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +179 -0
  86. disdrodb/l0/readers/{UK → LPM/UK}/DIVEN.py +14 -35
  87. disdrodb/l0/readers/PARSIVEL/AUSTRALIA/MELBOURNE_2007_PARSIVEL.py +157 -0
  88. disdrodb/l0/readers/PARSIVEL/CHINA/CHONGQING.py +113 -0
  89. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/ARCTIC_2021.py +40 -57
  90. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/COMMON_2011.py +37 -54
  91. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/DAVOS_2009_2011.py +34 -51
  92. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/EPFL_2009.py +34 -51
  93. disdrodb/l0/readers/{EPFL/PARADISO_2014.py → PARSIVEL/EPFL/EPFL_ROOF_2008.py} +38 -50
  94. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2010.py +105 -0
  95. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/EPFL_ROOF_2011.py +34 -51
  96. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/EPFL_ROOF_2012.py +33 -51
  97. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/GENEPI_2007.py +25 -44
  98. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/GRAND_ST_BERNARD_2007.py +25 -44
  99. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/GRAND_ST_BERNARD_2007_2.py +25 -44
  100. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/HPICONET_2010.py +34 -51
  101. disdrodb/l0/readers/{EPFL/EPFL_ROOF_2010.py → PARSIVEL/EPFL/HYMEX_LTE_SOP2.py} +37 -50
  102. disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP3.py +111 -0
  103. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/HYMEX_LTE_SOP4.py +36 -54
  104. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/LOCARNO_2018.py +34 -52
  105. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/LOCARNO_2019.py +38 -56
  106. disdrodb/l0/readers/PARSIVEL/EPFL/PARADISO_2014.py +105 -0
  107. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/PARSIVEL_2007.py +27 -45
  108. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/PLATO_2019.py +24 -44
  109. disdrodb/l0/readers/PARSIVEL/EPFL/RACLETS_2019.py +140 -0
  110. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/RACLETS_2019_WJF.py +41 -59
  111. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/RIETHOLZBACH_2011.py +34 -51
  112. disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2017.py +117 -0
  113. disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2019.py +137 -0
  114. disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/UNIL_2022.py +42 -55
  115. disdrodb/l0/readers/PARSIVEL/GPM/IFLOODS.py +104 -0
  116. disdrodb/l0/readers/{GPM → PARSIVEL/GPM}/LPVEX.py +29 -48
  117. disdrodb/l0/readers/PARSIVEL/GPM/MC3E.py +184 -0
  118. disdrodb/l0/readers/PARSIVEL/NCAR/CCOPE_2015.py +113 -0
  119. disdrodb/l0/readers/{NCAR/VORTEX_SE_2016_P1.py → PARSIVEL/NCAR/OWLES_MIPS.py} +46 -72
  120. disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +125 -0
  121. disdrodb/l0/readers/{NCAR/OWLES_MIPS.py → PARSIVEL/NCAR/PLOWS_MIPS.py} +45 -64
  122. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +114 -0
  123. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +176 -0
  124. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +183 -0
  125. disdrodb/l0/readers/{ARM/ARM_LD.py → PARSIVEL2/ARM/ARM_PARSIVEL2.py} +27 -50
  126. disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +163 -0
  127. disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +163 -0
  128. disdrodb/l0/readers/{DENMARK → PARSIVEL2/DENMARK}/EROSION_nc.py +14 -35
  129. disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +119 -0
  130. disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +104 -0
  131. disdrodb/l0/readers/PARSIVEL2/GPM/NSSTC.py +176 -0
  132. disdrodb/l0/readers/PARSIVEL2/ITALY/GID_PARSIVEL2.py +32 -0
  133. disdrodb/l0/readers/PARSIVEL2/MEXICO/OH_IIUNAM_nc.py +56 -0
  134. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +120 -0
  135. disdrodb/l0/readers/{NCAR → PARSIVEL2/NCAR}/PECAN_MIPS.py +45 -64
  136. disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +181 -0
  137. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +160 -0
  138. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +160 -0
  139. disdrodb/l0/readers/{NCAR/PLOWS_MIPS.py → PARSIVEL2/NCAR/VORTEX_SE_2016_P1.py} +49 -66
  140. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +118 -0
  141. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +152 -0
  142. disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT.py +166 -0
  143. disdrodb/l0/readers/{NCAR/RELAMPAGO_RD80.py → RD80/BRAZIL/CHUVA_RD80.py} +36 -60
  144. disdrodb/l0/readers/{BRAZIL → RD80/BRAZIL}/GOAMAZON_RD80.py +36 -55
  145. disdrodb/l0/readers/{NCAR → RD80/NCAR}/CINDY_2011_RD80.py +35 -54
  146. disdrodb/l0/readers/{BRAZIL/CHUVA_RD80.py → RD80/NCAR/RELAMPAGO_RD80.py} +40 -54
  147. disdrodb/l0/readers/template_reader_raw_netcdf_data.py +62 -0
  148. disdrodb/l0/readers/{reader_template.py → template_reader_raw_text_data.py} +20 -44
  149. disdrodb/l0/routines.py +885 -581
  150. disdrodb/l0/standards.py +72 -236
  151. disdrodb/l0/template_tools.py +104 -109
  152. disdrodb/l1/__init__.py +17 -0
  153. disdrodb/l1/beard_model.py +716 -0
  154. disdrodb/l1/encoding_attrs.py +620 -0
  155. disdrodb/l1/fall_velocity.py +260 -0
  156. disdrodb/l1/filters.py +192 -0
  157. disdrodb/l1/processing.py +200 -0
  158. disdrodb/l1/resampling.py +236 -0
  159. disdrodb/l1/routines.py +357 -0
  160. disdrodb/l1_env/__init__.py +17 -0
  161. disdrodb/l1_env/routines.py +38 -0
  162. disdrodb/l2/__init__.py +17 -0
  163. disdrodb/l2/empirical_dsd.py +1735 -0
  164. disdrodb/l2/event.py +388 -0
  165. disdrodb/l2/processing.py +519 -0
  166. disdrodb/l2/processing_options.py +213 -0
  167. disdrodb/l2/routines.py +868 -0
  168. disdrodb/metadata/__init__.py +9 -2
  169. disdrodb/metadata/checks.py +165 -118
  170. disdrodb/metadata/download.py +81 -0
  171. disdrodb/metadata/geolocation.py +146 -0
  172. disdrodb/metadata/info.py +20 -13
  173. disdrodb/metadata/manipulation.py +1 -1
  174. disdrodb/metadata/reader.py +59 -8
  175. disdrodb/metadata/search.py +77 -144
  176. disdrodb/metadata/standards.py +7 -8
  177. disdrodb/metadata/writer.py +8 -14
  178. disdrodb/psd/__init__.py +38 -0
  179. disdrodb/psd/fitting.py +2146 -0
  180. disdrodb/psd/models.py +774 -0
  181. disdrodb/routines.py +1176 -0
  182. disdrodb/scattering/__init__.py +28 -0
  183. disdrodb/scattering/axis_ratio.py +344 -0
  184. disdrodb/scattering/routines.py +456 -0
  185. disdrodb/utils/__init__.py +17 -0
  186. disdrodb/utils/attrs.py +208 -0
  187. disdrodb/utils/cli.py +269 -0
  188. disdrodb/utils/compression.py +60 -42
  189. disdrodb/utils/dask.py +62 -0
  190. disdrodb/utils/decorators.py +110 -0
  191. disdrodb/utils/directories.py +107 -46
  192. disdrodb/utils/encoding.py +127 -0
  193. disdrodb/utils/list.py +29 -0
  194. disdrodb/utils/logger.py +168 -46
  195. disdrodb/utils/time.py +657 -0
  196. disdrodb/utils/warnings.py +30 -0
  197. disdrodb/utils/writer.py +57 -0
  198. disdrodb/utils/xarray.py +138 -47
  199. disdrodb/utils/yaml.py +0 -1
  200. disdrodb/viz/__init__.py +17 -0
  201. disdrodb/viz/plots.py +17 -0
  202. disdrodb-0.1.0.dist-info/METADATA +321 -0
  203. disdrodb-0.1.0.dist-info/RECORD +216 -0
  204. {disdrodb-0.0.21.dist-info → disdrodb-0.1.0.dist-info}/WHEEL +1 -1
  205. disdrodb-0.1.0.dist-info/entry_points.txt +30 -0
  206. disdrodb/data_transfer/scripts/disdrodb_download_archive.py +0 -53
  207. disdrodb/data_transfer/scripts/disdrodb_upload_archive.py +0 -57
  208. disdrodb/l0/configs/OTT_Parsivel/l0a_encodings.yml +0 -32
  209. disdrodb/l0/configs/OTT_Parsivel2/l0a_encodings.yml +0 -39
  210. disdrodb/l0/configs/RD_80/l0a_encodings.yml +0 -16
  211. disdrodb/l0/configs/RD_80/l0b_encodings.yml +0 -135
  212. disdrodb/l0/configs/Thies_LPM/l0a_encodings.yml +0 -80
  213. disdrodb/l0/io.py +0 -257
  214. disdrodb/l0/l0_processing.py +0 -1091
  215. disdrodb/l0/readers/AUSTRALIA/MELBOURNE_2007_OTT.py +0 -178
  216. disdrodb/l0/readers/AUSTRALIA/MELBOURNE_2007_THIES.py +0 -247
  217. disdrodb/l0/readers/BRAZIL/CHUVA_LPM.py +0 -204
  218. disdrodb/l0/readers/BRAZIL/CHUVA_OTT.py +0 -183
  219. disdrodb/l0/readers/BRAZIL/GOAMAZON_LPM.py +0 -204
  220. disdrodb/l0/readers/BRAZIL/GOAMAZON_OTT.py +0 -183
  221. disdrodb/l0/readers/CHINA/CHONGQING.py +0 -131
  222. disdrodb/l0/readers/EPFL/EPFL_ROOF_2008.py +0 -128
  223. disdrodb/l0/readers/EPFL/HYMEX_LTE_SOP2.py +0 -127
  224. disdrodb/l0/readers/EPFL/HYMEX_LTE_SOP3.py +0 -129
  225. disdrodb/l0/readers/EPFL/RACLETS_2019.py +0 -158
  226. disdrodb/l0/readers/EPFL/SAMOYLOV_2017.py +0 -136
  227. disdrodb/l0/readers/EPFL/SAMOYLOV_2019.py +0 -158
  228. disdrodb/l0/readers/FRANCE/SIRTA_OTT2.py +0 -138
  229. disdrodb/l0/readers/GPM/GCPEX.py +0 -123
  230. disdrodb/l0/readers/GPM/IFLOODS.py +0 -123
  231. disdrodb/l0/readers/GPM/MC3E.py +0 -123
  232. disdrodb/l0/readers/GPM/NSSTC.py +0 -164
  233. disdrodb/l0/readers/ITALY/GID.py +0 -199
  234. disdrodb/l0/readers/MEXICO/OH_IIUNAM_nc.py +0 -92
  235. disdrodb/l0/readers/NCAR/CCOPE_2015.py +0 -133
  236. disdrodb/l0/readers/NCAR/PECAN_FP3.py +0 -137
  237. disdrodb/l0/readers/NCAR/PECAN_MOBILE.py +0 -144
  238. disdrodb/l0/readers/NCAR/RELAMPAGO_OTT.py +0 -195
  239. disdrodb/l0/readers/NCAR/SNOWIE_PJ.py +0 -172
  240. disdrodb/l0/readers/NCAR/SNOWIE_SB.py +0 -179
  241. disdrodb/l0/readers/NCAR/VORTEX2_2009.py +0 -133
  242. disdrodb/l0/readers/NCAR/VORTEX2_2010.py +0 -188
  243. disdrodb/l0/readers/NCAR/VORTEX2_2010_UF.py +0 -191
  244. disdrodb/l0/readers/NCAR/VORTEX_SE_2016_P2.py +0 -135
  245. disdrodb/l0/readers/NCAR/VORTEX_SE_2016_PIPS.py +0 -170
  246. disdrodb/l0/readers/NETHERLANDS/DELFT.py +0 -187
  247. disdrodb/l0/readers/SPAIN/SBEGUERIA.py +0 -179
  248. disdrodb/l0/scripts/disdrodb_run_l0b_concat.py +0 -93
  249. disdrodb/l0/scripts/disdrodb_run_l0b_concat_station.py +0 -85
  250. disdrodb/utils/netcdf.py +0 -452
  251. disdrodb/utils/scripts.py +0 -102
  252. disdrodb-0.0.21.dist-info/AUTHORS.md +0 -18
  253. disdrodb-0.0.21.dist-info/METADATA +0 -186
  254. disdrodb-0.0.21.dist-info/RECORD +0 -168
  255. disdrodb-0.0.21.dist-info/entry_points.txt +0 -15
  256. /disdrodb/l0/configs/{RD_80 → RD80}/bins_velocity.yml +0 -0
  257. /disdrodb/l0/manuals/{Thies_LPM.pdf → LPM.pdf} +0 -0
  258. /disdrodb/l0/manuals/{ODM_470.pdf → ODM470.pdf} +0 -0
  259. /disdrodb/l0/manuals/{OTT_Parsivel.pdf → PARSIVEL.pdf} +0 -0
  260. /disdrodb/l0/manuals/{OTT_Parsivel2.pdf → PARSIVEL2.pdf} +0 -0
  261. /disdrodb/l0/manuals/{PWS_100.pdf → PWS100.pdf} +0 -0
  262. /disdrodb/l0/manuals/{RD_80.pdf → RD80.pdf} +0 -0
  263. {disdrodb-0.0.21.dist-info → disdrodb-0.1.0.dist-info/licenses}/LICENSE +0 -0
  264. {disdrodb-0.0.21.dist-info → disdrodb-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1735 @@
1
+ # -----------------------------------------------------------------------------.
2
+ # Copyright (c) 2021-2023 DISDRODB developers
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ # -----------------------------------------------------------------------------.
17
+ """Functions for computation of DSD parameters.
18
+
19
+ The functions of this module expects xarray.DataArray objects as input.
20
+ Zeros and NaN values input arrays are correctly processed.
21
+ Infinite values should be removed beforehand or otherwise are propagated throughout the computations.
22
+ """
23
+ import numpy as np
24
+ import xarray as xr
25
+
26
+ from disdrodb import DIAMETER_DIMENSION, VELOCITY_DIMENSION
27
+ from disdrodb.api.checks import check_sensor_name
28
+ from disdrodb.utils.xarray import (
29
+ remove_diameter_coordinates,
30
+ remove_velocity_coordinates,
31
+ xr_get_last_valid_idx,
32
+ )
33
+
34
+
35
+ def get_drop_volume(diameter):
36
+ """
37
+ Compute the volume of a droplet assuming it is spherical.
38
+
39
+ Parameters
40
+ ----------
41
+ diameter : float or array-like
42
+ The diameter of the droplet(s). Can be a scalar or an array of diameters.
43
+
44
+ Returns
45
+ -------
46
+ array-like
47
+ The volume of the droplet(s) calculated in cubic units based on the input diameter(s).
48
+
49
+ Notes
50
+ -----
51
+ The volume is calculated using the formula for the volume of a sphere:
52
+ V = (π/6) * d^3, where d is the diameter of the droplet.
53
+ """
54
+ return np.pi / 6 * diameter**3 # 1/6 = 4/3*(0.5**3)
55
+
56
+
57
+ def get_drop_average_velocity(drop_number):
58
+ r"""
59
+ Calculate the drop average velocity \\( v_m(D))) \\) per diameter class.
60
+
61
+ The average velocity is obtained by weighting by the number of drops in each velocity bin.
62
+ If in a given diameter bin no drops are recorded, the resulting average drop size velocity for
63
+ such bin will be set to NaN.
64
+
65
+ Parameters
66
+ ----------
67
+ drop_number : xarray.DataArray
68
+ Array of drop counts \\( n(D,v) \\) per diameter (and velocity, if available) bins
69
+ over the time integration period.
70
+ The DataArray must have the ``velocity_bin_center`` coordinate.
71
+
72
+ Returns
73
+ -------
74
+ average_velocity : xarray.DataArray
75
+ Array of drop average velocity \\( v_m(D))) \\) in m·s⁻¹ .
76
+ At timesteps with zero drop counts, it returns NaN.
77
+ """
78
+ velocity = xr.ones_like(drop_number) * drop_number["velocity_bin_center"]
79
+ average_velocity = ((velocity * drop_number).sum(dim=VELOCITY_DIMENSION, skipna=False)) / drop_number.sum(
80
+ dim=VELOCITY_DIMENSION,
81
+ skipna=False,
82
+ )
83
+ return average_velocity
84
+
85
+
86
+ def count_bins_with_drops(ds):
87
+ """Count the number of diameter bins with data."""
88
+ # Select useful variable
89
+ candidate_variables = ["drop_counts", "drop_number_concentration", "drop_number"]
90
+ available_variables = [var for var in candidate_variables if var in ds]
91
+ if len(available_variables) == 0:
92
+ raise ValueError(f"One of these variables is required: {candidate_variables}")
93
+ da = ds[available_variables[0]]
94
+ if VELOCITY_DIMENSION in da.dims:
95
+ da = da.sum(dim=VELOCITY_DIMENSION)
96
+ # Count number of bins with data
97
+ da = (da > 0).sum(dim=DIAMETER_DIMENSION)
98
+ # TODO: remove this in future !
99
+ if "velocity_method" in da.dims:
100
+ da = da.max(dim="velocity_method")
101
+ return da
102
+
103
+
104
+ ####-------------------------------------------------------------------------------------------------------------------.
105
+ #### DSD Spectrum, Concentration, Moments
106
+
107
+
108
+ def get_effective_sampling_area(sensor_name, diameter):
109
+ """Compute the effective sampling area in m2 of the disdrometer.
110
+
111
+ The diameter must be provided in meters !
112
+ """
113
+ check_sensor_name(sensor_name)
114
+ if sensor_name in ["PARSIVEL", "PARSIVEL2"]:
115
+ # Calculate sampling area for each diameter bin (S_i)
116
+ L = 180 / 1000 # Length of the Parsivel beam in m (180 mm)
117
+ B = 30 / 1000 # Width of the Parsivel beam in m (30mm)
118
+ sampling_area = L * (B - diameter / 2)
119
+ return sampling_area
120
+ if sensor_name in "LPM":
121
+ # Calculate sampling area for each diameter bin (S_i)
122
+ L = 228 / 1000 # Length of the Parsivel beam in m (228 mm)
123
+ B = 20 / 1000 # Width of the Parsivel beam in m (20 mm)
124
+ sampling_area = L * (B - diameter / 2)
125
+ return sampling_area
126
+ if sensor_name in "RD80":
127
+ sampling_area = 0.005 # m2
128
+ return sampling_area
129
+ raise NotImplementedError(f"Effective sampling area for {sensor_name} must yet to be specified in the software.")
130
+
131
+
132
+ def get_bin_dimensions(xr_obj):
133
+ """Return the dimensions of the drop spectrum."""
134
+ return sorted([k for k in [DIAMETER_DIMENSION, VELOCITY_DIMENSION] if k in xr_obj.dims])
135
+
136
+
137
+ def get_drop_number_concentration(drop_number, velocity, diameter_bin_width, sampling_area, sample_interval):
138
+ r"""
139
+ Calculate the volumetric drop number concentration \\( N(D) \\) per diameter class.
140
+
141
+ Computes the drop number concentration \\( N(D) \\) [m⁻³·mm⁻¹] for each diameter
142
+ class based on the measured drop counts and sensor parameters.
143
+ This represents the number of drops per unit volume per unit diameter interval.
144
+ It is also referred to as the drop size distribution N(D) per cubic metre per millimetre [m-3 mm-1]
145
+
146
+ Parameters
147
+ ----------
148
+ velocity : xarray.DataArray
149
+ Array of drop fall velocities \\( v(D) \\) corresponding to each diameter bin in meters per second (m/s).
150
+ Typically the estimated fall velocity is used.
151
+ But one can also pass the velocity bin center of the optical disdrometer, which get broadcasted
152
+ along the diameter bin dimension.
153
+ diameter_bin_width : xarray.DataArray
154
+ Width of each diameter bin \\( \\Delta D \\) in millimeters (mm).
155
+ drop_number : xarray.DataArray
156
+ Array of drop counts \\( n(D) or n(D,v) \\) per diameter (and velocity if available)
157
+ bins over the time integration period.
158
+ sample_interval : float or xarray.DataArray
159
+ Time over which the drops are counted \\( \\Delta t \\) in seconds (s).
160
+ sampling_area : float or xarray.DataArray
161
+ The effective sampling area \\( A \\) of the sensor in square meters (m²).
162
+
163
+ Returns
164
+ -------
165
+ drop_number_concentration : xarray.DataArray or ndarray
166
+ Array of drop number concentrations \\( N(D) \\) in m⁻³·mm⁻¹, representing
167
+ the number of drops per unit volume per unit diameter interval.
168
+
169
+ Notes
170
+ -----
171
+ The drop number concentration \\( N(D) \\) is calculated using:
172
+
173
+ .. math::
174
+
175
+ N(D) = \frac{n(D)}{A_{\text{eff}}(D) \\cdot \\Delta D \\cdot \\Delta t \\cdot v(D)}
176
+
177
+ where:
178
+
179
+ - \\( n(D,v) \\): Number of drops counted in diameter (and velocity) bins.
180
+ - \\( A_{\text{eff}}(D) \\): Effective sampling area of the sensor for diameter \\( D \\) in square meters (m²).
181
+ - \\( \\Delta D \\): Diameter bin width in millimeters (mm).
182
+ - \\( \\Delta t \\): Time integration period in seconds (s).
183
+ - \\( v(D) \\): Fall velocity of drops in diameter bin \\( D \\) in meters per second (m/s).
184
+
185
+ The effective sampling area \\( A_{\text{eff}}(D) \\) depends on the sensor and may vary with drop diameter.
186
+ """
187
+ # Ensure velocity is 2D (diameter, velocity)
188
+ velocity = xr.ones_like(drop_number) * velocity
189
+
190
+ # Compute drop number concentration
191
+ # - For disdrometer with velocity bins
192
+ if VELOCITY_DIMENSION in drop_number.dims:
193
+ drop_number_concentration = (drop_number / velocity).sum(dim=VELOCITY_DIMENSION, skipna=False) / (
194
+ sampling_area * diameter_bin_width * sample_interval
195
+ )
196
+ # - For impact disdrometers
197
+ else:
198
+ drop_number_concentration = (drop_number / velocity) / (sampling_area * diameter_bin_width * sample_interval)
199
+ return drop_number_concentration
200
+
201
+
202
+ def get_total_number_concentration(drop_number_concentration, diameter_bin_width):
203
+ r"""
204
+ Compute the total number concentration \\( N_t \\) from the drop size distribution.
205
+
206
+ Calculates the total number concentration \\( N_t \\) [m⁻³] by integrating the
207
+ drop number concentration over all diameter bins.
208
+
209
+ Parameters
210
+ ----------
211
+ drop_number_concentration : xarray.DataArray
212
+ Array of drop number concentrations \\( N(D) \\) in m⁻³·mm⁻¹.
213
+ diameter_bin_width : xarray.DataArray
214
+ Width of each diameter bin \\( \\Delta D \\) in millimeters (mm).
215
+
216
+ Returns
217
+ -------
218
+ total_number_concentration : xarray.DataArray or ndarray
219
+ Total number concentration \\( N_t \\) in m⁻³, representing the total number
220
+ of drops per unit volume.
221
+
222
+ Notes
223
+ -----
224
+ The total number concentration \\( N_t \\) is calculated by integrating over the diameter bins:
225
+
226
+ .. math::
227
+
228
+ N_t = \\sum_{\text{bins}} N(D) \\cdot \\Delta D
229
+
230
+ where:
231
+
232
+ - \\( N(D) \\): Drop number concentration in each diameter bin [m⁻³·mm⁻¹].
233
+ - \\( \\Delta D \\): Diameter bin width in millimeters (mm).
234
+
235
+ """
236
+ total_number_concentration = (drop_number_concentration * diameter_bin_width).sum(
237
+ dim=DIAMETER_DIMENSION,
238
+ skipna=False,
239
+ )
240
+ return total_number_concentration
241
+
242
+
243
+ def get_moment(drop_number_concentration, diameter, diameter_bin_width, moment):
244
+ r"""
245
+ Calculate the m-th moment of the drop size distribution.
246
+
247
+ Computes the m-th moment of the drop size distribution (DSD), denoted as E[D**m],
248
+ where D is the drop diameter and m is the order of the moment. This is useful
249
+ in meteorology and hydrology for characterizing precipitation. For example,
250
+ weather radar measurements correspond to the sixth moment of the DSD (m = 6).
251
+
252
+ Parameters
253
+ ----------
254
+ drop_number_concentration : xarray.DataArray
255
+ The drop number concentration N(D) for each diameter bin,
256
+ typically in units of number per cubic meter per millimeter (m⁻³ mm⁻¹).
257
+ diameter : xarray.DataArray
258
+ The equivalent volume diameters D of the drops in each bin, in meters (m).
259
+ diameter_bin_width : xarray.DataArray
260
+ The width dD of each diameter bin, in millimeters (mm).
261
+ moment : int or float
262
+ The order m of the moment to compute.
263
+
264
+ Returns
265
+ -------
266
+ moment_value : xarray.DataArray
267
+ The computed m-th moment of the drop size distribution, typically in units
268
+ dependent on the input units, such as mmᵐ m⁻³.
269
+
270
+ Notes
271
+ -----
272
+ The m-th moment is calculated using the formula:
273
+
274
+ .. math::
275
+
276
+ M_m = \\sum_{\text{bins}} N(D) \\cdot D^m \\cdot dD
277
+
278
+ where:
279
+
280
+ - \\( M_m \\) is the m-th moment of the DSD.
281
+ - \\( N(D) \\) is the drop number concentration for diameter \\( D \\).
282
+ - \\( D^m \\) is the diameter raised to the power of \\( m \\).
283
+ - \\( dD \\) is the diameter bin width.
284
+
285
+ This computation integrates over the drop size distribution to provide a
286
+ scalar value representing the statistical momen
287
+ """
288
+ return ((diameter * 1000) ** moment * drop_number_concentration * diameter_bin_width).sum(
289
+ dim=DIAMETER_DIMENSION,
290
+ skipna=False,
291
+ )
292
+
293
+
294
+ ####------------------------------------------------------------------------------------------------------------------
295
+ #### Rain Rate and Accumulation
296
+
297
+
298
+ def get_rain_rate_from_drop_number(drop_number, sampling_area, diameter, sample_interval):
299
+ r"""
300
+ Compute the rain rate \\( R \\) [mm/h] based on the drop size distribution and drop velocities.
301
+
302
+ This function calculates the rain rate by integrating over the drop size distribution (DSD),
303
+ considering the volume of water falling per unit time and area. It uses the number of drops
304
+ counted in each diameter class, the effective sampling area of the sensor, the diameters of the
305
+ drops, and the time interval over which the drops are counted.
306
+
307
+ Parameters
308
+ ----------
309
+ drop_number : xarray.DataArray
310
+ Array representing the number of drops per diameter class
311
+ and, optionally, velocity class \\( n(D, (v)) \\).
312
+ sample_interval : float or xarray.DataArray
313
+ The time duration over which drops are counted \\( \\Delta t \\) in seconds (s).
314
+ sampling_area : float or xarray.DataArray
315
+ The effective sampling area \\( A \\) of the sensor in square meters (m²).
316
+ diameter : xarray.DataArray
317
+ Array of drop diameters \\( D \\) in meters (m).
318
+
319
+ Returns
320
+ -------
321
+ rain_rate : xarray.DataArray
322
+ The computed rain rate \\( R \\) in millimeters per hour (mm/h), which represents the volume
323
+ of water falling per unit area per unit time.
324
+
325
+ Notes
326
+ -----
327
+ The rain rate \\( R \\) is calculated using the following formula:
328
+
329
+ .. math::
330
+
331
+ R = \frac{\\pi}{6} \times 10^{3} \times 3600 \times
332
+ \\sum_{\text{bins}} n(D) \cdot A(D) \cdot D^3 \cdot \\Delta t
333
+
334
+ = \\pi \times 0.6 \times 10^{6} \times
335
+ \\sum_{\text{bins}} n(D) \cdot A(D) \cdot D^3 \cdot \\Delta t
336
+
337
+ = \\pi \times 6 \times 10^{5} \times
338
+ \\sum_{\text{bins}} n(D) \cdot A(D) \cdot D^3 \cdot \\Delta t
339
+
340
+ Where:
341
+ - \\( n(D) \\) is the number of drops in each diameter class.
342
+ - \\( A(D) \\) is the effective sampling area.
343
+ - \\( D \\) is the drop diameter.
344
+ - \\( \\Delta t \\) is the time interval for drop counts.
345
+
346
+ This formula incorporates a conversion factor to express the rain rate in millimeters per hour.
347
+
348
+ In the literature, when the diameter is expected in millimeters, the formula is given
349
+ as:
350
+ .. math::
351
+
352
+ R = \\pi \times {6} \times 10^{-4} \times
353
+ \\sum_{\text{bins}} n(D) \cdot A(D) \cdot D^3 \cdot \\Delta t
354
+
355
+ """
356
+ dim = get_bin_dimensions(drop_number)
357
+ rain_rate = (
358
+ np.pi
359
+ / 6
360
+ / sample_interval
361
+ * (drop_number * (diameter**3 / sampling_area)).sum(dim=dim, skipna=False)
362
+ * 3600
363
+ * 1000
364
+ )
365
+ return rain_rate
366
+
367
+
368
+ def get_rain_rate(drop_number_concentration, velocity, diameter, diameter_bin_width):
369
+ r"""
370
+ Compute the rain rate \\( R \\) [mm/h] based on the drop size distribution and raindrop velocities.
371
+
372
+ Calculates the rain rate by integrating over the drop size distribution (DSD),
373
+ considering the volume of water falling per unit time and area.
374
+
375
+ Parameters
376
+ ----------
377
+ drop_number_concentration : xarray.DataArray
378
+ Array of drop number concentrations \\( N(D) \\) in m⁻³·mm⁻¹.
379
+ velocity : xarray.DataArray
380
+ Array of drop fall velocities \\( v(D) \\) corresponding to each diameter bin in meters per second (m/s).
381
+ diameter : xarray.DataArray
382
+ Array of drop diameters \\( D \\) in meters (m).
383
+ diameter_bin_width : xarray.DataArray
384
+ Width of each diameter bin \\( \\Delta D \\) in millimeters (mm).
385
+
386
+ Returns
387
+ -------
388
+ rain_rate : xarray.DataArray
389
+ The rain rate \\( R \\) in millimeters per hour (mm/h), representing the volume
390
+ of water falling per unit area per unit time.
391
+
392
+ Notes
393
+ -----
394
+ The rain rate \\( R \\) is calculated using:
395
+
396
+ .. math::
397
+
398
+ R = \frac{\\pi}{6} \times 10^{-3} \times 3600 \times
399
+ \\sum_{\text{bins}} N(D) \\cdot v(D) \\cdot D^3 \\cdot \\Delta D
400
+
401
+ where:
402
+
403
+ - \\( N(D) \\): Drop number concentration [m⁻³·mm⁻¹].
404
+ - \\( v(D) \\): Fall velocity of drops in diameter bin \\( D \\) [m/s].
405
+ - \\( D \\): Drop diameter [mm].
406
+ - \\( \\Delta D \\): Diameter bin width [mm].
407
+ - The factor \\( \frac{\\pi}{6} \\) converts the diameter cubed to volume of a sphere.
408
+ - The factor \\( 10^{-3} \\) converts from mm³ to m³.
409
+ - The factor \\( 3600 \\) converts from seconds to hours.
410
+
411
+ """
412
+ if VELOCITY_DIMENSION in velocity.dims:
413
+ raise ValueError(f"The 'velocity' DataArray must not have the {VELOCITY_DIMENSION} dimension.")
414
+
415
+ rain_rate = (
416
+ 6
417
+ * np.pi
418
+ * 1e5
419
+ * (drop_number_concentration * (velocity * diameter**3 * diameter_bin_width)).sum(
420
+ dim=DIAMETER_DIMENSION,
421
+ skipna=False,
422
+ )
423
+ )
424
+ return rain_rate
425
+
426
+
427
+ def get_rain_rate_spectrum(drop_number_concentration, velocity, diameter):
428
+ r"""
429
+ Compute the rain rate per diameter class.
430
+
431
+ It represents the rain rate as a function of raindrop diameter.
432
+ The total rain rate can be obtained by multiplying the spectrum with
433
+ the diameter bin width and summing over the diameter bins.
434
+
435
+ Parameters
436
+ ----------
437
+ drop_number_concentration : xarray.DataArray
438
+ Array of drop number concentrations \\( N(D) \\) in m⁻³·mm⁻¹.
439
+ velocity : xarray.DataArray
440
+ Array of drop fall velocities \\( v(D) \\) corresponding to each diameter bin in meters per second (m/s).
441
+ diameter : xarray.DataArray
442
+ Array of drop diameters \\( D \\) in meters (m).
443
+
444
+ Returns
445
+ -------
446
+ xarray.DataArray
447
+ The rain rate spectrum in millimeters per hour per mm, representing the volume
448
+ of water falling per unit area per unit time per unit diameter.
449
+
450
+ """
451
+ rain_rate = 6 * np.pi * 1e5 * (drop_number_concentration * (velocity * diameter**3))
452
+ return rain_rate
453
+
454
+
455
+ def get_rain_rate_contribution(drop_number_concentration, velocity, diameter, diameter_bin_width):
456
+ r"""Compute the rain rate contribution per diameter class.
457
+
458
+ Parameters
459
+ ----------
460
+ drop_number_concentration : xarray.DataArray
461
+ Array of drop number concentrations \\( N(D) \\) in m⁻³·mm⁻¹.
462
+ velocity : xarray.DataArray
463
+ Array of drop fall velocities \\( v(D) \\) corresponding to each diameter bin in meters per second (m/s).
464
+ diameter : xarray.DataArray
465
+ Array of drop diameters \\( D \\) in meters (m).
466
+ diameter_bin_width : xarray.DataArray
467
+ Width of each diameter bin \\( \\Delta D \\) in millimeters (mm).
468
+
469
+ Returns
470
+ -------
471
+ xarray.DataArray
472
+ The rain rate contribution percentage per diameter class.
473
+
474
+ """
475
+ rain_rate_spectrum = (6 * np.pi * 1e5 * (velocity * diameter**3 * diameter_bin_width)) * drop_number_concentration
476
+
477
+ rain_rate_total = rain_rate_spectrum.sum(dim=DIAMETER_DIMENSION, skipna=False)
478
+ rain_rate_contribution = rain_rate_spectrum / rain_rate_total * 100
479
+ return rain_rate_contribution
480
+
481
+
482
+ def get_rain_accumulation(rain_rate, sample_interval):
483
+ """
484
+ Calculate the total rain accumulation over a specified time period.
485
+
486
+ Parameters
487
+ ----------
488
+ rain_rate : float or array-like
489
+ The rain rate in millimeters per hour (mm/h).
490
+ sample_interval : int
491
+ The time over which to accumulate rain, specified in seconds.
492
+
493
+ Returns
494
+ -------
495
+ float or numpy.ndarray
496
+ The total rain accumulation in millimeters (mm) over the specified time period.
497
+
498
+ """
499
+ rain_accumulation = rain_rate / 3600 * sample_interval
500
+ return rain_accumulation
501
+
502
+
503
+ ####------------------------------------------------------------------------------------------------------------------
504
+ #### Reflectivity
505
+ def get_equivalent_reflectivity_factor(drop_number_concentration, diameter, diameter_bin_width):
506
+ r"""
507
+ Compute the equivalent reflectivity factor in decibels relative to 1 mm⁶·m⁻³ (dBZ).
508
+
509
+ The equivalent reflectivity (in mm⁶·m⁻³) is obtained from the sixth moment of the drop size distribution (DSD).
510
+ The reflectivity factor is expressed in decibels relative to 1 mm⁶·m⁻³ using the formula:
511
+
512
+ .. math::
513
+
514
+ Z = 10 \cdot \log_{10}(z)
515
+
516
+ where \\( z \\) is the reflectivity in linear units of the DSD.
517
+
518
+ To convert back the reflectivity factor to linear units (mm⁶·m⁻³), use the formula:
519
+
520
+ .. math::
521
+
522
+ z = 10^{(Z/10)}
523
+
524
+ Parameters
525
+ ----------
526
+ drop_number_concentration : xarray.DataArray
527
+ Array representing the concentration of droplets per diameter class in number per unit volume.
528
+ diameter : xarray.DataArray
529
+ Array of droplet diameters in meters (m).
530
+ diameter_bin_width : xarray.DataArray
531
+ Array representing the width of each diameter bin in millimeters (mm).
532
+
533
+ Returns
534
+ -------
535
+ xarray.DataArray
536
+ The equivalent reflectivity factor in decibels (dBZ).
537
+
538
+ Notes
539
+ -----
540
+ The function computes the sixth moment of the DSD using the formula:
541
+
542
+ .. math::
543
+
544
+ z = \\sum n(D) \cdot D^6 \cdot \\Delta D
545
+
546
+ where \\( n(D) \\) is the drop number concentration, \\( D \\) is the drop diameter, and
547
+ \\( \\Delta D \\) is the diameter bin width.
548
+
549
+ """
550
+ # Compute reflectivity in mm⁶·m⁻³
551
+ z = (drop_number_concentration * ((diameter * 1000) ** 6 * diameter_bin_width)).sum(
552
+ dim=DIAMETER_DIMENSION,
553
+ skipna=False,
554
+ )
555
+ invalid_mask = z > 0
556
+ z = z.where(invalid_mask)
557
+ # Compute equivalent reflectivity factor in dBZ
558
+ # - np.log10(np.nan) returns -Inf !
559
+ # --> We mask again after the log
560
+ Z = 10 * np.log10(z)
561
+ Z = Z.where(invalid_mask)
562
+ # Clip reflectivity at -60 dBZ
563
+ Z = Z.clip(-60, None)
564
+ return Z
565
+
566
+
567
+ def get_equivalent_reflectivity_spectrum(drop_number_concentration, diameter):
568
+ r"""
569
+ Compute the equivalent reflectivity per diameter class.
570
+
571
+ The equivalent reflectivity per unit diameter Z(D) [in mm⁶·m⁻³ / mm] is expressed in decibels
572
+ using the formula:
573
+
574
+ .. math::
575
+
576
+ Z(D) = 10 \cdot \log_{10}(z(D))
577
+
578
+ where \\( z(D) \\) is the equivalent reflectivity spectrum in linear units of the DSD.
579
+
580
+ To convert back the reflectivity factor to linear units (mm⁶·m⁻³ / mm), use the formula:
581
+
582
+ .. math::
583
+
584
+ z(D) = 10^{(Z(D)/10)}
585
+
586
+ To obtain the total equivalent reflectivity factor (z) one has to multiply z(D) with the diameter
587
+ bins intervals and summing over the diameter bins.
588
+
589
+ Parameters
590
+ ----------
591
+ drop_number_concentration : xarray.DataArray
592
+ Array representing the concentration of droplets per diameter class in number per unit volume.
593
+ diameter : xarray.DataArray
594
+ Array of droplet diameters in meters (m).
595
+
596
+ Returns
597
+ -------
598
+ xarray.DataArray
599
+ The equivalent reflectivity spectrum in decibels (dBZ).
600
+
601
+ """
602
+ # Compute reflectivity in mm⁶·m⁻³
603
+ z = drop_number_concentration * ((diameter * 1000) ** 6)
604
+ invalid_mask = z > 0
605
+ z = z.where(invalid_mask)
606
+ # Compute equivalent reflectivity factor in dBZ
607
+ # - np.log10(np.nan) returns -Inf !
608
+ # --> We mask again after the log
609
+ Z = 10 * np.log10(z)
610
+ Z = Z.where(invalid_mask)
611
+ # Clip reflectivity at -60 dBZ
612
+ Z = Z.clip(-60, None)
613
+ return Z
614
+
615
+
616
+ ####------------------------------------------------------------------------------------------------------------------
617
+ #### Liquid Water Content / Mass Parameters
618
+
619
+
620
+ def get_liquid_water_spectrum(drop_number_concentration, diameter, water_density=1000):
621
+ """
622
+ Calculate the mass spectrum W(D) per diameter class.
623
+
624
+ It represents the mass of liquid water as a function of raindrop diameter.
625
+ The integrated liquid water content can be obtained by multiplying
626
+ the spectrum with the diameter bins intervals and summing over the diameter bins.
627
+
628
+ Parameters
629
+ ----------
630
+ drop_number_concentration : array-like
631
+ The concentration of droplets (number of droplets per unit volume) in each diameter bin.
632
+ diameter : array-like
633
+ The diameters of the droplets for each bin, in meters (m).
634
+
635
+ Returns
636
+ -------
637
+ array-like
638
+ The calculated rain drop mass spectrum in grams per cubic meter per unit diameter (g/m3/mm).
639
+
640
+ """
641
+ # Convert water density from kg/m3 to g/m3
642
+ water_density = water_density * 1000
643
+
644
+ # Calculate the mass spectrum (LWC per diameter bin)
645
+ return (np.pi / 6.0 * water_density * diameter**3) * drop_number_concentration # [g/m3 mm-1]
646
+
647
+
648
+ # def get_mass_flux(drop_number_concentration, diameter, velocity, diameter_bin_width, water_density=1000):
649
+ # """
650
+ # Calculate the mass flux based on drop number concentration and drop diameter and velocity.
651
+
652
+ # Parameters
653
+ # ----------
654
+ # drop_number_concentration : array-like
655
+ # The concentration of droplets (number of droplets per unit volume) in each diameter bin.
656
+ # diameter : array-like
657
+ # The diameters of the droplets for each bin, in meters (m).
658
+ # water_density : float, optional
659
+ # The density of water in kg/m^3. The default is 1000 kg/m3.
660
+
661
+ # Returns
662
+ # -------
663
+ # array-like
664
+ # The calculated mass in grams per cubic meter per second (g/m3/s).
665
+
666
+ # """
667
+ # if VELOCITY_DIMENSION in velocity.dims:
668
+ # raise ValueError("The 'velocity' DataArray must not have the {VELOCITY_DIMENSION} dimension.")
669
+ # # Convert water density from kg/m3 to g/m3
670
+ # water_density = water_density * 1000
671
+
672
+ # # Calculate the volume constant for the water droplet formula
673
+ # vol_constant = np.pi / 6.0 * water_density
674
+
675
+ # # Calculate the mass flux
676
+ # # TODO: check equal to density * R
677
+ # mass_flux = (vol_constant *
678
+ # (drop_number_concentration *
679
+ # (diameter**3 * velocity)).sum(dim=DIAMETER_DIMENSION, skipna=False)
680
+ # )
681
+ # return mass_flux
682
+
683
+
684
+ def get_liquid_water_content(drop_number_concentration, diameter, diameter_bin_width, water_density=1000):
685
+ """
686
+ Calculate the liquid water content based on drop number concentration and drop diameter.
687
+
688
+ Parameters
689
+ ----------
690
+ drop_number_concentration : array-like
691
+ The concentration of droplets (number of droplets per unit volume) in each diameter bin.
692
+ diameter : array-like
693
+ The diameters of the droplets for each bin, in meters (m).
694
+ diameter_bin_width : array-like
695
+ The width of each diameter bin, in millimeters (mm).
696
+ water_density : float, optional
697
+ The density of water in kg/m^3. The default is 1000 kg/m3.
698
+
699
+ Returns
700
+ -------
701
+ array-like
702
+ The calculated liquid water content in grams per cubic meter (g/m3).
703
+
704
+ """
705
+ # Convert water density from kg/m3 to g/m3
706
+ water_density = water_density * 1000
707
+
708
+ # Calculate the liquid water content
709
+ lwc = (
710
+ np.pi
711
+ / 6.0
712
+ * water_density
713
+ * (drop_number_concentration * (diameter**3 * diameter_bin_width)).sum(
714
+ dim=DIAMETER_DIMENSION,
715
+ skipna=False,
716
+ )
717
+ )
718
+ return lwc
719
+
720
+
721
+ def get_liquid_water_content_from_moments(moment_3, water_density=1000):
722
+ r"""
723
+ Calculate the liquid water content (LWC) from the third moment of the DSD.
724
+
725
+ LWC represents the mass of liquid water per unit volume of air.
726
+
727
+ Parameters
728
+ ----------
729
+ moment_3 : float or array-like
730
+ The third moment of the drop size distribution, \\( M_3 \\), in units of
731
+ [m⁻³·mm³] (number per cubic meter times diameter cubed).
732
+ water_density : float, optional
733
+ The density of water in kilograms per cubic meter (kg/m³).
734
+ Default is 1000 kg/m³ (approximate density of water at 20°C).
735
+
736
+ Returns
737
+ -------
738
+ lwc : float or array-like
739
+ The liquid water content in grams per cubic meter (g/m³).
740
+
741
+ Notes
742
+ -----
743
+ The liquid water content is calculated using the formula:
744
+
745
+ .. math::
746
+
747
+ \text{LWC} = \frac{\\pi \rho_w}{6} \\cdot M_3
748
+
749
+ where:
750
+
751
+ - \\( \text{LWC} \\) is the liquid water content [g/m³].
752
+ - \\( \rho_w \\) is the density of water [g/mm³].
753
+ - \\( M_3 \\) is the third moment of the DSD [m⁻³·mm³].
754
+
755
+ Examples
756
+ --------
757
+ Compute the liquid water content from the third moment:
758
+
759
+ >>> moment_3 = 1e6 # Example value in [m⁻³·mm³]
760
+ >>> lwc = get_liquid_water_content_from_moments(moment_3)
761
+ >>> print(f"LWC: {lwc:.4f} g/m³")
762
+ LWC: 0.0005 g/m³
763
+ """
764
+ # Convert water density from kg/m³ to g/mm³
765
+ water_density = water_density * 1e-6 # [kg/m³] * 1e-6 = [g/mm³]
766
+ # Calculate LWC [g/m3]
767
+ lwc = (np.pi * water_density / 6) * moment_3 # [g/mm³] * [m⁻³·mm³] = [g/m³]
768
+ return lwc
769
+
770
+
771
+ ####--------------------------------------------------------------------------------------------------------
772
+ #### Diameter Statistics
773
+
774
+
775
+ def get_min_max_diameter(drop_counts):
776
+ """
777
+ Get the minimum and maximum diameters where drop_counts is non-zero.
778
+
779
+ Parameters
780
+ ----------
781
+ drop_counts : xarray.DataArray
782
+ Drop counts with dimensions ("time", "diameter_bin_center") and
783
+ coordinate "diameter_bin_center".
784
+ It assumes the diameter coordinate to be monotonically increasing !
785
+
786
+ Returns
787
+ -------
788
+ min_drop_diameter : xarray.DataArray
789
+ Minimum diameter where drop_counts is non-zero, for each time step.
790
+ max_drop_diameter : xarray.DataArray
791
+ Maximum diameter where drop_counts is non-zero, for each time step.
792
+ """
793
+ # Create a boolean mask where drop_counts is non-zero
794
+ non_zero_mask = drop_counts > 0
795
+
796
+ # Find the first non-zero index along 'diameter_bin_center' for each time
797
+ # - Return 0 if all False, zero or NaN
798
+ first_non_zero_idx = non_zero_mask.argmax(dim=DIAMETER_DIMENSION)
799
+
800
+ # Calculate the last non-zero index in the original array
801
+ last_non_zero_idx = xr_get_last_valid_idx(da_condition=non_zero_mask, dim=DIAMETER_DIMENSION)
802
+
803
+ # Get the 'diameter_bin_center' coordinate
804
+ diameters = drop_counts["diameter_bin_center"]
805
+
806
+ # Retrieve the diameters corresponding to the first and last non-zero indices
807
+ min_drop_diameter = diameters.isel({DIAMETER_DIMENSION: first_non_zero_idx.astype(int)})
808
+ max_drop_diameter = diameters.isel({DIAMETER_DIMENSION: last_non_zero_idx.astype(int)})
809
+
810
+ # Identify time steps where all drop_counts are zero
811
+ is_all_zero_or_nan = ~non_zero_mask.any(dim=DIAMETER_DIMENSION)
812
+
813
+ # Mask with NaN where no drop or all values are NaN
814
+ min_drop_diameter = min_drop_diameter.where(~is_all_zero_or_nan)
815
+ max_drop_diameter = max_drop_diameter.where(~is_all_zero_or_nan)
816
+
817
+ # Remove diameter coordinates
818
+ min_drop_diameter = remove_diameter_coordinates(min_drop_diameter)
819
+ max_drop_diameter = remove_diameter_coordinates(max_drop_diameter)
820
+
821
+ return min_drop_diameter, max_drop_diameter
822
+
823
+
824
+ def get_mode_diameter(drop_number_concentration, diameter):
825
+ """Get raindrop diameter with highest occurrence."""
826
+ # If all NaN, set to 0 otherwise argmax fail when all NaN data
827
+ idx_all_nan_mask = np.isnan(drop_number_concentration).all(dim=DIAMETER_DIMENSION)
828
+ drop_number_concentration = drop_number_concentration.where(~idx_all_nan_mask, 0)
829
+ # Find index where all 0
830
+ # --> argmax will return 0
831
+ idx_all_zero = (drop_number_concentration == 0).all(dim=DIAMETER_DIMENSION)
832
+ # Find the diameter index corresponding the "mode"
833
+ idx_observed_mode = drop_number_concentration.argmax(dim=DIAMETER_DIMENSION)
834
+ # Find the diameter corresponding to the "mode"
835
+ diameter_mode = diameter.isel({DIAMETER_DIMENSION: idx_observed_mode})
836
+ # Remove diameter coordinates
837
+ diameter_mode = remove_diameter_coordinates(diameter_mode)
838
+ # Set to np.nan where data where all NaN or all 0
839
+ idx_mask = np.logical_or(idx_all_nan_mask, idx_all_zero)
840
+ diameter_mode = diameter_mode.where(~idx_mask)
841
+ return diameter_mode
842
+
843
+
844
+ ####-------------------------------------------------------------------------------------------------------------------.
845
+ #### Mass Distribution Diameters
846
+
847
+
848
+ def get_mean_volume_drop_diameter(moment_3, moment_4):
849
+ r"""
850
+ Calculate the volume-weighted mean volume diameter \\( D_m \\) from DSD moments.
851
+
852
+ The mean volume diameter of a drop size distribution (DSD) is computed using
853
+ the third and fourth moments.
854
+
855
+ The volume-weighted mean volume diameter is also referred as the mass mean diameter.
856
+ It represents the first moment of the mass spectrum.
857
+
858
+ If no drops are recorded, the output values is NaN.
859
+
860
+ Parameters
861
+ ----------
862
+ moment_3 : float or array-like
863
+ The third moment of the drop size distribution, \\( M_3 \\), in units of
864
+ [m⁻³·mm³].
865
+ moment_4 : float or array-like
866
+ The fourth moment of the drop size distribution, \\( M_4 \\), in units of
867
+ [m⁻³·mm⁴].
868
+
869
+ Returns
870
+ -------
871
+ D_m : float or array-like
872
+ The mean volume diameter in millimeters (mm).
873
+
874
+ Notes
875
+ -----
876
+ The mean volume diameter is calculated using the formula:
877
+
878
+ .. math::
879
+
880
+ D_m = \frac{M_4}{M_3}
881
+
882
+ where:
883
+
884
+ - \\( D_m \\) is the mean volume diameter [mm].
885
+ - \\( M_3 \\) is the third moment of the DSD [m⁻³·mm³].
886
+ - \\( M_4 \\) is the fourth moment of the DSD [m⁻³·mm⁴].
887
+
888
+ Examples
889
+ --------
890
+ Compute the mean volume diameter from the third and fourth moments:
891
+
892
+ >>> moment_3 = 1e6 # Example value in [m⁻³·mm³]
893
+ >>> moment_4 = 5e6 # Example value in [m⁻³·mm⁴]
894
+ >>> D_m = get_mean_volume_drop_diameter(moment_3, moment_4)
895
+ >>> print(f"Mean Volume Diameter D_m: {D_m:.4f} mm")
896
+ Mean Volume Diameter D_m: 5.0000 mm
897
+
898
+ """
899
+ # Note:
900
+ # - 0/0 return NaN
901
+ # - <number>/0 return Inf
902
+ D_m = moment_4 / moment_3 # Units: [mm⁴] / [mm³] = [mm]
903
+ return D_m
904
+
905
+
906
+ def get_std_volume_drop_diameter(moment_3, moment_4, moment_5):
907
+ r"""
908
+ Calculate the standard deviation of the mass-weighted drop diameter (σₘ).
909
+
910
+ This parameter is often also referred as the mass spectrum standard deviation.
911
+ It quantifies the spread or variability of DSD.
912
+
913
+ If drops are recorded in just one bin, the standard deviation of the mass-weighted drop diameter
914
+ is set to 0.
915
+ If no drops are recorded, the output values is NaN.
916
+
917
+ Parameters
918
+ ----------
919
+ drop_number_concentration : xarray.DataArray
920
+ The drop number concentration \\( N(D) \\) for each diameter bin, typically in units of
921
+ number per cubic meter per millimeter (m⁻³·mm⁻¹).
922
+ diameter : xarray.DataArray
923
+ The equivalent volume diameters \\( D \\) of the drops in each bin, in meters (m).
924
+ diameter_bin_width : xarray.DataArray
925
+ The width \\( \\Delta D \\) of each diameter bin, in millimeters (mm).
926
+ mean_volume_diameter : xarray.DataArray
927
+ The mean volume diameter \\( D_m \\), in millimeters (mm). This is typically computed using the
928
+ third and fourth moments or directly from the DSD.
929
+
930
+ Returns
931
+ -------
932
+ sigma_m : xarray.DataArray or float
933
+ The standard deviation of the mass-weighted drop diameter, \\( \\sigma_m \\),
934
+ in millimeters (mm).
935
+
936
+ Notes
937
+ -----
938
+ The standard deviation of the mass-weighted drop diameter is calculated using the formula:
939
+
940
+ .. math::
941
+
942
+ \\sigma_m = \\sqrt{\frac{\\sum [N(D) \\cdot (D - D_m)^2 \\cdot D^3
943
+ \\cdot \\Delta D]}{\\sum [N(D) \\cdot D^3 \\cdot \\Delta D]}}
944
+
945
+ where:
946
+
947
+ - \\( N(D) \\) is the drop number concentration for diameter \\( D \\) [m⁻³·mm⁻¹].
948
+ - \\( D \\) is the drop diameter [mm].
949
+ - \\( D_m \\) is the mean volume diameter [mm].
950
+ - \\( \\Delta D \\) is the diameter bin width [mm].
951
+ - The numerator computes the weighted variance of diameters.
952
+ - The weighting factor \\( D^3 \\) accounts for mass (since mass ∝ \\( D^3 \\)).
953
+
954
+ **Physical Interpretation:**
955
+
956
+ - A smaller \\( \\sigma_m \\) indicates that the mass is concentrated around the
957
+ mean mass-weighted diameter, implying less variability in drop sizes.
958
+ - A larger \\( \\sigma_m \\) suggests a wider spread of drop sizes contributing
959
+ to the mass, indicating greater variability.
960
+
961
+ References
962
+ ----------
963
+ - Smith, P. L., Johnson, R. W., & Kliche, D. V. (2019). On Use of the Standard
964
+ Deviation of the Mass Distribution as a Parameter in Raindrop Size Distribution
965
+ Functions. *Journal of Applied Meteorology and Climatology*, 58(4), 787-796.
966
+ https://doi.org/10.1175/JAMC-D-18-0086.1
967
+ - Williams, C. R., and Coauthors, 2014: Describing the Shape of Raindrop Size Distributions Using Uncorrelated
968
+ Raindrop Mass Spectrum Parameters. J. Appl. Meteor. Climatol., 53, 1282-1296, https://doi.org/10.1175/JAMC-D-13-076.1.
969
+ """
970
+ # # Full formula
971
+ # const = drop_number_concentration * diameter_bin_width * diameter**3
972
+ # numerator = ((diameter * 1000 - mean_volume_diameter) ** 2 * const).sum(dim=DIAMETER_DIMENSION, skipna=False)
973
+ # variance_m = numerator / const.sum(dim=DIAMETER_DIMENSION, skipna=False))
974
+
975
+ # Compute variance using moment formula
976
+ variance_m = (moment_3 * moment_5 - moment_4**2) / moment_3**2
977
+
978
+ # Set to 0 when very low values (resulting from numerical errors)
979
+ # --> For example should return 0 when drops only recorded in 1 bin !
980
+ variance_m = xr.where(np.logical_and(variance_m < 1e-5, ~np.isnan(variance_m)), 0, variance_m)
981
+
982
+ # Compute standard deviation
983
+ sigma_m = np.sqrt(variance_m)
984
+ return sigma_m
985
+
986
+
987
+ def get_median_volume_drop_diameter(drop_number_concentration, diameter, diameter_bin_width, water_density=1000):
988
+ r"""
989
+ Compute the median volume drop diameter (D50).
990
+
991
+ The median volume drop diameter (D50) is defined as the diameter at which half of the total liquid water content
992
+ is contributed by drops smaller than D50, and half by drops larger than D50.
993
+
994
+ Drops smaller (respectively larger) than D50 contribute to half of the
995
+ total rainwater content in the sampled volume.
996
+ D50 is sensitive to the concentration of large drops.
997
+
998
+ Often referred also as D50 (50 for 50 percentile of the distribution).
999
+
1000
+ Parameters
1001
+ ----------
1002
+ drop_number_concentration : xarray.DataArray
1003
+ The drop number concentration \( N(D) \) for each diameter bin, typically in units of
1004
+ number per cubic meter per millimeter (m⁻³·mm⁻¹).
1005
+ diameter : xarray.DataArray
1006
+ The equivalent volume diameters \( D \) of the drops in each bin, in meters (m).
1007
+ diameter_bin_width : xarray.DataArray
1008
+ The width \( \Delta D \) of each diameter bin, in millimeters (mm).
1009
+ water_density : float, optional
1010
+ The density of water in kg/m^3. The default is 1000 kg/m3.
1011
+
1012
+ Returns
1013
+ -------
1014
+ xarray.DataArray
1015
+ Median volume drop diameter (D50) [mm].
1016
+ The drop diameter that divides the volume of water contained in the sample into two equal parts.
1017
+
1018
+ """
1019
+ d50 = get_quantile_volume_drop_diameter(
1020
+ drop_number_concentration=drop_number_concentration,
1021
+ diameter=diameter,
1022
+ diameter_bin_width=diameter_bin_width,
1023
+ fraction=0.5,
1024
+ water_density=water_density,
1025
+ )
1026
+ return d50
1027
+
1028
+
1029
+ def _get_quantile_volume_drop_diameter(
1030
+ drop_number_concentration,
1031
+ diameter,
1032
+ diameter_bin_width,
1033
+ fraction,
1034
+ water_density=1000,
1035
+ ):
1036
+ # Check fraction value(s)
1037
+ fraction = np.atleast_1d(fraction)
1038
+ for value in fraction:
1039
+ if not (0 < value < 1):
1040
+ raise ValueError("Fraction values must be between 0 and 1 (exclusive)")
1041
+
1042
+ # Create fraction DataArray
1043
+ fraction = xr.DataArray(fraction, coords={"quantile": fraction}, dims="quantile")
1044
+
1045
+ # Convert water density from kg/m3 to g/m3
1046
+ water_density = water_density * 1000
1047
+
1048
+ # Compute LWC per diameter bin [g/m3]
1049
+ mass_spectrum = get_liquid_water_spectrum(
1050
+ drop_number_concentration=drop_number_concentration,
1051
+ diameter=diameter,
1052
+ water_density=water_density,
1053
+ )
1054
+ lwc_per_diameter = diameter_bin_width * mass_spectrum
1055
+
1056
+ # Compute the cumulative sum of LWC along the diameter bins
1057
+ cumulative_lwc = lwc_per_diameter.cumsum(dim=DIAMETER_DIMENSION, skipna=False)
1058
+
1059
+ # Check if single bin
1060
+ is_single_bin = (lwc_per_diameter != 0).sum(dim=DIAMETER_DIMENSION) == 1
1061
+
1062
+ # Retrieve total lwc and target lwc
1063
+ total_lwc = cumulative_lwc.isel({DIAMETER_DIMENSION: -1})
1064
+ target_lwc = total_lwc * fraction
1065
+
1066
+ # Retrieve bin indices between which the quantile of the volume is reached
1067
+ # --> If all NaN or False, argmax and xr_get_last_valid_idx(fill_value=0) return 0 !
1068
+ idx_upper = (cumulative_lwc >= target_lwc).argmax(dim=DIAMETER_DIMENSION).astype(int)
1069
+ idx_lower = xr_get_last_valid_idx(
1070
+ da_condition=(cumulative_lwc <= target_lwc),
1071
+ dim=DIAMETER_DIMENSION,
1072
+ fill_value=0,
1073
+ ).astype(int)
1074
+
1075
+ # Retrieve cumulative LWC values at such bins and target LWC
1076
+ y1 = cumulative_lwc.isel({DIAMETER_DIMENSION: idx_lower})
1077
+ y2 = cumulative_lwc.isel({DIAMETER_DIMENSION: idx_upper})
1078
+ yt = target_lwc
1079
+
1080
+ # ------------------------------------------------------.
1081
+ ## Case with multiple bins
1082
+ # Define interpolation slope, avoiding division by zero if y1 equals y2.
1083
+ # - When target LWC exactly equal to cumulative_lwc value of a bin --> Diameter is the bin center diameter !
1084
+ slope = xr.where(y1 == y2, 0, (yt - y1) / (y2 - y1))
1085
+
1086
+ # Define diameter increment from lower bin center
1087
+ d1 = diameter.isel(diameter_bin_center=idx_lower) # m
1088
+ d2 = diameter.isel(diameter_bin_center=idx_upper) # m
1089
+ d_increment = (d2 - d1) * slope
1090
+
1091
+ # Define quantile diameter
1092
+ quantile_diameter_multi_bin = d1 + d_increment
1093
+
1094
+ ## ------------------------------------------------------.
1095
+ ## Case with single bin
1096
+ # When no accumulation has yet occurred (y1==0), use the upper bin for both indices.
1097
+ idx_lower = xr.where(y1 == 0, idx_upper, idx_lower)
1098
+ # Identify the bin center diameter
1099
+ d = diameter.isel(diameter_bin_center=idx_lower) # m
1100
+ d_width = diameter_bin_width.isel({DIAMETER_DIMENSION: idx_lower}) / 1000 # m
1101
+ d_lower = d - d_width / 2
1102
+ quantile_diameter_single_bin = d_lower + d_width * fraction
1103
+
1104
+ ## ------------------------------------------------------.
1105
+ # Define quantile diameter
1106
+ quantile_diameter = xr.where(is_single_bin, quantile_diameter_single_bin, quantile_diameter_multi_bin)
1107
+
1108
+ # Set NaN where total sum is 0 or all NaN
1109
+ mask_invalid = np.logical_or(total_lwc == 0, np.isnan(total_lwc))
1110
+ quantile_diameter = quantile_diameter.where(~mask_invalid)
1111
+
1112
+ # Convert diameter to mm
1113
+ quantile_diameter = quantile_diameter * 1000
1114
+
1115
+ # If only 1 fraction specified, squeeze and drop quantile coordinate
1116
+ if quantile_diameter.sizes["quantile"] == 1:
1117
+ quantile_diameter = quantile_diameter.drop_vars("quantile").squeeze()
1118
+
1119
+ # Drop meaningless coordinates
1120
+ quantile_diameter = remove_diameter_coordinates(quantile_diameter)
1121
+ quantile_diameter = remove_velocity_coordinates(quantile_diameter)
1122
+ return quantile_diameter
1123
+
1124
+
1125
+ def get_quantile_volume_drop_diameter(
1126
+ drop_number_concentration,
1127
+ diameter,
1128
+ diameter_bin_width,
1129
+ fraction,
1130
+ water_density=1000,
1131
+ ):
1132
+ r"""
1133
+ Compute the diameter corresponding to a specified fraction of the cumulative liquid water content (LWC).
1134
+
1135
+ This function calculates the diameter \( D_f \) at which the cumulative LWC reaches
1136
+ a specified fraction \( f \) of the total LWC for each drop size distribution (DSD).
1137
+ When \( f = 0.5 \), it computes the median volume drop diameter.
1138
+
1139
+
1140
+ Parameters
1141
+ ----------
1142
+ drop_number_concentration : xarray.DataArray
1143
+ The drop number concentration \( N(D) \) for each diameter bin, typically in units of
1144
+ number per cubic meter per millimeter (m⁻³·mm⁻¹).
1145
+ diameter : xarray.DataArray
1146
+ The equivalent volume diameters \( D \) of the drops in each bin, in meters (m).
1147
+ diameter_bin_width : xarray.DataArray
1148
+ The width \( \Delta D \) of each diameter bin, in millimeters (mm).
1149
+ fraction : float or numpy.ndarray
1150
+ The fraction \( f \) of the total liquid water content to compute the diameter for.
1151
+ Default is 0.5, which computes the median volume diameter (D50).
1152
+ For other percentiles, use 0.1 for D10, 0.9 for D90, etc.
1153
+ Values must be between 0 and 1 (exclusive).
1154
+ water_density : float, optional
1155
+ The density of water in kg/m^3. The default is 1000 kg/m3.
1156
+
1157
+ Returns
1158
+ -------
1159
+ D_f : xarray.DataArray
1160
+ The diameter \( D_f \) corresponding to the specified fraction \( f \) of cumulative LWC,
1161
+ in millimeters (mm). For `fraction=0.5`, this is the median volume drop diameter D50.
1162
+
1163
+ Notes
1164
+ -----
1165
+ The calculation involves computing the cumulative sum of the liquid water content
1166
+ contributed by each diameter bin and finding the diameter at which the cumulative
1167
+ sum reaches the specified fraction \( f \) of the total liquid water content.
1168
+
1169
+ Linear interpolation is used between the two diameter bins where the cumulative LWC
1170
+ crosses the target LWC fraction.
1171
+
1172
+ """
1173
+ # Dask array backend
1174
+ if hasattr(drop_number_concentration.data, "chunks"):
1175
+ fraction = np.atleast_1d(fraction)
1176
+ if fraction.size > 1:
1177
+ dask_gufunc_kwargs = {"output_sizes": {"quantile": fraction.size}}
1178
+ output_core_dims = [["quantile"]]
1179
+ else:
1180
+ dask_gufunc_kwargs = None
1181
+ output_core_dims = ((),)
1182
+ quantile_diameter = xr.apply_ufunc(
1183
+ _get_quantile_volume_drop_diameter,
1184
+ drop_number_concentration,
1185
+ kwargs={
1186
+ "fraction": fraction,
1187
+ "diameter": diameter.compute(),
1188
+ "diameter_bin_width": diameter_bin_width.compute(),
1189
+ "water_density": water_density,
1190
+ },
1191
+ input_core_dims=[[DIAMETER_DIMENSION]],
1192
+ vectorize=True,
1193
+ dask="parallelized",
1194
+ output_core_dims=output_core_dims,
1195
+ dask_gufunc_kwargs=dask_gufunc_kwargs,
1196
+ output_dtypes=["float64"],
1197
+ )
1198
+ if fraction.size > 1:
1199
+ quantile_diameter = quantile_diameter.assign_coords({"quantile": fraction})
1200
+ return quantile_diameter
1201
+
1202
+ # Numpy array backed
1203
+ quantile_diameter = _get_quantile_volume_drop_diameter(
1204
+ drop_number_concentration=drop_number_concentration,
1205
+ diameter=diameter,
1206
+ diameter_bin_width=diameter_bin_width,
1207
+ fraction=fraction,
1208
+ water_density=water_density,
1209
+ )
1210
+ return quantile_diameter
1211
+
1212
+
1213
+ ####-----------------------------------------------------------------------------------------------------
1214
+ #### Normalized Gamma Parameters
1215
+
1216
+
1217
+ def get_normalized_intercept_parameter(liquid_water_content, mean_volume_diameter, water_density=1000):
1218
+ r"""
1219
+ Calculate the normalized intercept parameter \\( N_w \\) of the drop size distribution.
1220
+
1221
+ A higher \\( N_w \\) indicates a higher concentration of smaller drops.
1222
+ The \\( N_w \\) is used in models to represent the DSD when assuming a normalized gamma distribution.
1223
+
1224
+ Parameters
1225
+ ----------
1226
+ liquid_water_content : float or array-like
1227
+ Liquid water content \\( LWC \\) in grams per cubic meter (g/m³).
1228
+ mean_volume_diameter : float or array-like
1229
+ Mean volume diameter \\( D_m \\) in millimeters (mm).
1230
+ water_density : float, optional
1231
+ Density of water \\( \rho_w \\) in kilograms per cubic meter (kg/m³).
1232
+ The default is 1000 kg/m³.
1233
+
1234
+ Returns
1235
+ -------
1236
+ Nw : xarray.DataArray or float
1237
+ Normalized intercept parameter \\( N_w \\) in units of m⁻3·mm⁻¹.
1238
+
1239
+ Notes
1240
+ -----
1241
+ The normalized intercept parameter \\( N_w \\) is calculated using the formula:
1242
+
1243
+ .. math::
1244
+
1245
+ N_w = \frac{256}{\\pi \rho_w} \\cdot \frac{W}{D_m^4}
1246
+
1247
+ where:
1248
+
1249
+ - \\( N_w \\) is the normalized intercept parameter.
1250
+ - \\( W \\) is the liquid water content in g/m³.
1251
+ - \\( D_m \\) is the mean volume diameter in mm.
1252
+ - \\( \rho_w \\) is the density of water in kg/m³.
1253
+ """
1254
+ # Conversion to g/m3
1255
+ water_density = water_density * 1000 # g/m3
1256
+
1257
+ # Compute Nw
1258
+ # --> 1e9 is used to convert from mm-4 to m-3 mm-1
1259
+ # - 256 = 4**4
1260
+ # - lwc = (np.pi * water_density / 6) * moment_3
1261
+ Nw = (256.0 / (np.pi * water_density)) * liquid_water_content / mean_volume_diameter**4 * 1e9
1262
+ return Nw
1263
+
1264
+
1265
+ def get_normalized_intercept_parameter_from_moments(moment_3, moment_4):
1266
+ r"""
1267
+ Calculate the normalized intercept parameter \\( N_w \\) of the drop size distribution.
1268
+
1269
+ moment_3 : float or array-like
1270
+ The third moment of the drop size distribution, \\( M_3 \\), in units of
1271
+ [m⁻³·mm³] (number per cubic meter times diameter cubed).
1272
+
1273
+ moment_4 : float or array-like
1274
+ The foruth moment of the drop size distribution, \\( M_3 \\), in units of
1275
+ [m⁻³·mm4].
1276
+
1277
+ Returns
1278
+ -------
1279
+ Nw : xarray.DataArray or float
1280
+ Normalized intercept parameter \\( N_w \\) in units of m⁻3·mm⁻¹.
1281
+
1282
+ References
1283
+ ----------
1284
+ Testud, J., S. Oury, R. A. Black, P. Amayenc, and X. Dou, 2001:
1285
+ The Concept of “Normalized” Distribution to Describe Raindrop spectrum:
1286
+ A Tool for Cloud Physics and Cloud Remote Sensing.
1287
+ J. Appl. Meteor. Climatol., 40, 1118-1140,
1288
+ https://doi.org/10.1175/1520-0450(2001)040<1118:TCONDT>2.0.CO;2
1289
+
1290
+ """
1291
+ Nw = 256 / 6 * moment_3**5 / moment_4**4
1292
+ return Nw
1293
+
1294
+
1295
+ ####--------------------------------------------------------------------------------------------------------
1296
+ #### Kinetic Energy Parameters
1297
+
1298
+
1299
+ def get_kinetic_energy_spectrum(
1300
+ drop_number_concentration,
1301
+ velocity,
1302
+ diameter,
1303
+ sample_interval,
1304
+ water_density=1000,
1305
+ ):
1306
+ r"""Compute the rainfall kinetic energy per diameter class.
1307
+
1308
+ To obtain the Total Kinetic Energy (TKE) one has to multiply KE(D) with the diameter
1309
+ bins intervals and summing over the diameter bins.
1310
+
1311
+ Parameters
1312
+ ----------
1313
+ drop_number_concentration : xarray.DataArray
1314
+ Array of drop number concentrations \\( N(D) \\) in m⁻³·mm⁻¹.
1315
+ velocity : xarray.DataArray or float
1316
+ The fall velocities \\( v \\) of the drops, in meters per second (m/s).
1317
+ diameter : xarray.DataArray
1318
+ The equivalent volume diameters \\( D \\) of the drops in each bin, in meters (m).
1319
+ sample_interval : float
1320
+ The time over which the drops are counted \\( \\Delta t \\) in seconds (s).
1321
+ water_density : float, optional
1322
+ The density of water \\( \rho_w \\) in kilograms per cubic meter (kg/m³).
1323
+ Default is 1000 kg/m³.
1324
+
1325
+ Returns
1326
+ -------
1327
+ xr.DataArray
1328
+ Kinetic Energy Spectrum [J/m2/mm]
1329
+ """
1330
+ KE_spectrum = (
1331
+ np.pi / 12 * water_density * sample_interval * (drop_number_concentration * (diameter**3 * velocity**3))
1332
+ )
1333
+ return KE_spectrum
1334
+
1335
+
1336
+ def get_kinetic_energy_variables(
1337
+ drop_number_concentration,
1338
+ velocity,
1339
+ diameter,
1340
+ diameter_bin_width,
1341
+ sample_interval,
1342
+ water_density=1000,
1343
+ ):
1344
+ r"""Compute rainfall kinetic energy descriptors from the drop number concentration.
1345
+
1346
+ Parameters
1347
+ ----------
1348
+ drop_number_concentration : xarray.DataArray
1349
+ Array of drop number concentrations \\( N(D) \\) in m⁻³·mm⁻¹.
1350
+ velocity : xarray.DataArray or float
1351
+ The fall velocities \\( v \\) of the drops, in meters per second (m/s).
1352
+ diameter : xarray.DataArray
1353
+ The equivalent volume diameters \\( D \\) of the drops in each bin, in meters (m).
1354
+ diameter_bin_width : xarray.DataArray
1355
+ Width of each diameter bin \\( \\Delta D \\) in millimeters (mm).
1356
+ sample_interval : float
1357
+ The time over which the drops are counted \\( \\Delta t \\) in seconds (s).
1358
+ water_density : float, optional
1359
+ The density of water \\( \rho_w \\) in kilograms per cubic meter (kg/m³).
1360
+ Default is 1000 kg/m³.
1361
+
1362
+ Returns
1363
+ -------
1364
+ xarray.Dataset
1365
+ Xarray Dataset with relevant rainfall kinetic energy variables:
1366
+ - TKE: Total Kinetic Energy [J/m2]
1367
+ - KED: Kinetic Energy per unit rainfall Depth [J·m⁻²·mm⁻¹]. Typical values range between 0 and 40 J·m⁻²·mm⁻¹.
1368
+ - KEF: Kinetic Energy Flux [J·m⁻²·h⁻¹]. Typical values range between 0 and 5000 J·m⁻²·h⁻¹.
1369
+ KEF is related to the KED by the rain rate: KED = KEF/R .
1370
+ """
1371
+ # Check velocity DataArray does not have a velocity dimension
1372
+ if VELOCITY_DIMENSION in velocity.dims:
1373
+ raise ValueError(f"The 'velocity' DataArray must not have the '{VELOCITY_DIMENSION}' dimension.")
1374
+
1375
+ # Compute rain rate
1376
+ R = get_rain_rate(
1377
+ drop_number_concentration=drop_number_concentration,
1378
+ velocity=velocity,
1379
+ diameter=diameter,
1380
+ diameter_bin_width=diameter_bin_width,
1381
+ )
1382
+
1383
+ # Compute total kinetic energy in [J/m2]
1384
+ TKE = (
1385
+ np.pi
1386
+ / 12
1387
+ * water_density
1388
+ * sample_interval
1389
+ * (drop_number_concentration * diameter**3 * velocity**3 * diameter_bin_width).sum(
1390
+ dim=DIAMETER_DIMENSION,
1391
+ skipna=False,
1392
+ )
1393
+ )
1394
+
1395
+ # Compute Kinetic Energy Flux (KEF) [J/m2/h]
1396
+ KEF = TKE / sample_interval * 3600
1397
+
1398
+ # Compute Kinetic Energy per Rainfall Depth [J/m2/mm]
1399
+ KED = KEF / R
1400
+ KED = xr.where(R == 0, 0, KED) # Ensure KED is 0 when R (and thus drop number is 0)
1401
+
1402
+ # Create dataset
1403
+ dict_vars = {
1404
+ "TKE": TKE,
1405
+ "KEF": KEF,
1406
+ "KED": KED,
1407
+ }
1408
+ ds = xr.Dataset(dict_vars)
1409
+ return ds
1410
+
1411
+
1412
+ def get_kinetic_energy_variables_from_drop_number(
1413
+ drop_number,
1414
+ velocity,
1415
+ sampling_area,
1416
+ diameter,
1417
+ sample_interval,
1418
+ water_density=1000,
1419
+ ):
1420
+ r"""Compute rainfall kinetic energy descriptors from the measured drop number spectrum.
1421
+
1422
+ Parameters
1423
+ ----------
1424
+ drop_number : xarray.DataArray
1425
+ The number of drops in each diameter (and velocity, if available) bin(s).
1426
+ velocity : xarray.DataArray or float
1427
+ The fall velocities \\( v \\) of the drops in each bin, in meters per second (m/s).
1428
+ Values are broadcasted to match the dimensions of `drop_number`.
1429
+ diameter : xarray.DataArray
1430
+ The equivalent volume diameters \\( D \\) of the drops in each bin, in meters (m).
1431
+ sampling_area : float
1432
+ The effective sampling area \\( A \\) of the sensor in square meters (m²).
1433
+ sample_interval : float
1434
+ The time over which the drops are counted \\( \\Delta t \\) in seconds (s).
1435
+ water_density : float, optional
1436
+ The density of water \\( \rho_w \\) in kilograms per cubic meter (kg/m³).
1437
+ Default is 1000 kg/m³.
1438
+
1439
+ Returns
1440
+ -------
1441
+ xarray.Dataset
1442
+ Xarray Dataset with relevant rainfall kinetic energy variables:
1443
+ - TKE: Total Kinetic Energy [J/m2]
1444
+ - KED: Kinetic Energy per unit rainfall Depth [J·m⁻²·mm⁻¹]. Typical values range between 0 and 40 J·m⁻²·mm⁻¹.
1445
+ - KEF: Kinetic Energy Flux [J·m⁻²·h⁻¹]. Typical values range between 0 and 5000 J·m⁻²·h⁻¹.
1446
+ KEF is related to the KED by the rain rate: KED = KEF/R .
1447
+
1448
+ Notes
1449
+ -----
1450
+ KED provides a measure of the energy associated with each unit of rainfall depth.
1451
+ KED is useful for analyze the potential impact of raindrop erosion as a function of
1452
+ the intensity of rainfall events.
1453
+
1454
+ The kinetic energy of a rain drop is defined as:
1455
+
1456
+ .. math::
1457
+
1458
+ KE(D) = \frac{1}{2} · m_{drop} · v_{drop}^2 = \frac{\\pi \rho_{w}}{12} · D^3 · v^2
1459
+
1460
+ The Total Kinetic Energy (TKE) is calculated using:
1461
+
1462
+ .. math::
1463
+
1464
+ TKE = \\sum_{i,j} \\left({n_{ij} · KE(D_{i}) \right)
1465
+ = \frac{\\pi \rho_{w}}{12 · A} \\sum_{i,j} \\left( {n_{ij} · D_{i}^3 · v_{j}^2}} \right)
1466
+
1467
+ The Kinetic Energy Flux (KEF) is calculated using:
1468
+
1469
+ .. math::
1470
+
1471
+ KEF = \frac{TKE}{\\Delta t } · 3600
1472
+
1473
+ KED is calculated using:
1474
+
1475
+ .. math::
1476
+
1477
+ KED = \frac{KEF}{R} \\cdot \frac{\\pi}{6} \\cdot \frac{\rho_w}{R} \\cdot \\sum_{i,j}
1478
+ \\left( \frac{n_{ij} \\cdot D_i^3 \\cdot v_j^2}{A} \right)
1479
+
1480
+ where:
1481
+
1482
+ - \\( n_{ij} \\) is the number of drops in diameter bin \\( i \\) and velocity bin \\( j \\).
1483
+ - \\( D_i \\) is the diameter of bin \\( i \\).
1484
+ - \\( v_j \\) is the velocity of bin \\( j \\).
1485
+ - \\( A \\) is the sampling area.
1486
+ - \\( \\Delta t \\) is the time integration period in seconds.
1487
+ - \\( R \\) is the rainfall rate in mm/hr.
1488
+
1489
+ """
1490
+ # Get drop number core dimensions
1491
+ dim = get_bin_dimensions(drop_number)
1492
+
1493
+ # Ensure velocity is 2D if drop number has velocity dimension
1494
+ # - if measured velocity --> already 2D
1495
+ # - if estimated velocity --> broadcasted to 2D
1496
+ velocity = xr.ones_like(drop_number) * velocity
1497
+
1498
+ # Compute rain rate
1499
+ R = get_rain_rate_from_drop_number(
1500
+ drop_number=drop_number,
1501
+ sampling_area=sampling_area,
1502
+ diameter=diameter,
1503
+ sample_interval=sample_interval,
1504
+ )
1505
+
1506
+ # Compute drop size kinetic energy per diameter (and velocity bin)
1507
+ KE = np.pi / 12 * water_density * diameter**3 * velocity**2 # [J]
1508
+
1509
+ # Compute total kinetic energy in [J/m2]
1510
+ TKE = (KE * drop_number / sampling_area).sum(dim=dim, skipna=False)
1511
+
1512
+ # Compute Kinetic Energy Flux (KEF) [J/m2/h]
1513
+ KEF = TKE / sample_interval * 3600
1514
+
1515
+ # Compute Kinetic Energy per Rainfall Depth [J/m2/mm]
1516
+ KED = KEF / R
1517
+ KED = xr.where(R == 0, 0, KED) # Ensure KED is 0 when R (and thus drop number is 0)
1518
+
1519
+ # Create dataset
1520
+ dict_vars = {
1521
+ "TKE": TKE,
1522
+ "KEF": KEF,
1523
+ "KED": KED,
1524
+ }
1525
+ ds = xr.Dataset(dict_vars)
1526
+ return ds
1527
+
1528
+
1529
+ ####-------------------------------------------------------------------------------------------------------.
1530
+ #### Wrapper ####
1531
+
1532
+
1533
+ def compute_integral_parameters(
1534
+ drop_number_concentration,
1535
+ velocity,
1536
+ diameter,
1537
+ diameter_bin_width,
1538
+ sample_interval,
1539
+ water_density,
1540
+ ):
1541
+ """
1542
+ Compute integral parameters of a drop size distribution (DSD).
1543
+
1544
+ Parameters
1545
+ ----------
1546
+ drop_number_concentration : xr.DataArray
1547
+ Drop number concentration in each diameter bin [#/m3/mm].
1548
+ velocity : xr.DataArray
1549
+ Fall velocity of drops in each diameter bin [m/s].
1550
+ The presence of a velocity_method dimension enable to compute the parameters
1551
+ with different velocity estimates.
1552
+ diameter : array-like
1553
+ Diameter of drops in each bin in m.
1554
+ diameter_bin_width : array-like
1555
+ Width of each diameter bin in mm.
1556
+ sample_interval : float
1557
+ Time interval over which the samples are collected in seconds.
1558
+ water_density : float or array-like
1559
+ Density of water [kg/m3].
1560
+
1561
+ Returns
1562
+ -------
1563
+ ds : xarray.Dataset
1564
+ Dataset containing the computed integral parameters:
1565
+ - Nt : Total number concentration [#/m3]
1566
+ - M1 to M6 : Moments of the drop size distribution
1567
+ - Z : Reflectivity factor [dBZ]
1568
+ - W : Liquid water content [g/m3]
1569
+ - D10 : Diameter at the 10th quantile of the cumulative LWC distribution [mm]
1570
+ - D50 : Median volume drop diameter [mm]
1571
+ - D90 : Diameter at the 90th quantile of the cumulative LWC distribution [mm]
1572
+ - Dmode : Diameter at which the distribution peaks [mm]
1573
+ - Dm : Mean volume drop diameter [mm]
1574
+ - sigma_m : Standard deviation of the volume drop diameter [mm]
1575
+ - Nw : Normalized intercept parameter [m-3·mm⁻¹]
1576
+ - R : Rain rate [mm/h]
1577
+ - P : Rain accumulation [mm]
1578
+ - TKE: Total Kinetic Energy [J/m2]
1579
+ - KED: Kinetic Energy per unit rainfall Depth [J·m⁻²·mm⁻¹].
1580
+ - KEF: Kinetic Energy Flux [J·m⁻²·h⁻¹].
1581
+ """
1582
+ # Initialize dataset
1583
+ ds = xr.Dataset()
1584
+
1585
+ # Compute total number concentration (Nt) [#/m3]
1586
+ ds["Nt"] = get_total_number_concentration(
1587
+ drop_number_concentration=drop_number_concentration,
1588
+ diameter_bin_width=diameter_bin_width,
1589
+ )
1590
+
1591
+ # Compute rain rate
1592
+ ds["R"] = get_rain_rate(
1593
+ drop_number_concentration=drop_number_concentration,
1594
+ velocity=velocity,
1595
+ diameter=diameter,
1596
+ diameter_bin_width=diameter_bin_width,
1597
+ )
1598
+
1599
+ # Compute rain accumulation (P) [mm]
1600
+ ds["P"] = get_rain_accumulation(rain_rate=ds["R"], sample_interval=sample_interval)
1601
+
1602
+ # Compute moments (m0 to m6)
1603
+ for moment in range(0, 7):
1604
+ ds[f"M{moment}"] = get_moment(
1605
+ drop_number_concentration=drop_number_concentration,
1606
+ diameter=diameter,
1607
+ diameter_bin_width=diameter_bin_width,
1608
+ moment=moment,
1609
+ )
1610
+
1611
+ # Compute Liquid Water Content (LWC) (W) [g/m3]
1612
+ # ds["W"] = get_liquid_water_content(
1613
+ # drop_number_concentration=drop_number_concentration,
1614
+ # diameter=diameter,
1615
+ # diameter_bin_width=diameter_bin_width,
1616
+ # water_density=water_density,
1617
+ # )
1618
+
1619
+ ds["W"] = get_liquid_water_content_from_moments(moment_3=ds["M3"], water_density=water_density)
1620
+
1621
+ # Compute reflectivity in dBZ
1622
+ ds["Z"] = get_equivalent_reflectivity_factor(
1623
+ drop_number_concentration=drop_number_concentration,
1624
+ diameter=diameter,
1625
+ diameter_bin_width=diameter_bin_width,
1626
+ )
1627
+
1628
+ # Compute the diameter at which the distribution peak
1629
+ ds["Dmode"] = get_mode_diameter(drop_number_concentration, diameter=diameter) * 1000 # Output converted to mm
1630
+
1631
+ # Compute mean_volume_diameter (Dm) [mm]
1632
+ ds["Dm"] = get_mean_volume_drop_diameter(moment_3=ds["M3"], moment_4=ds["M4"])
1633
+
1634
+ # Compute σₘ[mm]
1635
+ ds["sigma_m"] = get_std_volume_drop_diameter(
1636
+ moment_3=ds["M3"],
1637
+ moment_4=ds["M4"],
1638
+ moment_5=ds["M5"],
1639
+ )
1640
+
1641
+ # Compute normalized_intercept_parameter (Nw) [m-3·mm⁻¹]
1642
+ # ds["Nw"] = get_normalized_intercept_parameter(
1643
+ # liquid_water_content=liquid_water_content,
1644
+ # mean_volume_diameter=mean_volume_diameter,
1645
+ # water_density=water_density,
1646
+ # )
1647
+
1648
+ ds["Nw"] = get_normalized_intercept_parameter_from_moments(moment_3=ds["M3"], moment_4=ds["M4"])
1649
+
1650
+ # Compute median volume_drop_diameter
1651
+ # --> Equivalent to get_quantile_volume_drop_diameter with fraction = 0.5
1652
+ # ds["D50"] = get_median_volume_drop_diameter(
1653
+ # drop_number_concentration=drop_number_concentration,
1654
+ # diameter=diameter,
1655
+ # diameter_bin_width=diameter_bin_width,
1656
+ # water_density=water_density,
1657
+ # )
1658
+
1659
+ # Compute volume_drop_diameter for the 10th and 90th quantile of the cumulative LWC distribution
1660
+ fractions = [0.1, 0.5, 0.9]
1661
+ d_q = get_quantile_volume_drop_diameter(
1662
+ drop_number_concentration=drop_number_concentration,
1663
+ diameter=diameter,
1664
+ diameter_bin_width=diameter_bin_width,
1665
+ fraction=fractions,
1666
+ water_density=water_density,
1667
+ )
1668
+ for fraction in fractions:
1669
+ var = f"D{round(fraction*100)!s}" # D10, D50, D90
1670
+ ds[var] = d_q.sel(quantile=fraction).drop_vars("quantile")
1671
+
1672
+ # Compute kinetic energy variables
1673
+ ds_ke = get_kinetic_energy_variables(
1674
+ drop_number_concentration=drop_number_concentration,
1675
+ velocity=velocity,
1676
+ diameter=diameter,
1677
+ diameter_bin_width=diameter_bin_width,
1678
+ sample_interval=sample_interval,
1679
+ water_density=water_density,
1680
+ )
1681
+ ds.update(ds_ke)
1682
+ return ds
1683
+
1684
+
1685
+ def compute_spectrum_parameters(
1686
+ drop_number_concentration,
1687
+ velocity,
1688
+ diameter,
1689
+ sample_interval,
1690
+ water_density=1000,
1691
+ ):
1692
+ """
1693
+ Compute drop size spectrum of rain rate, kinetic energy, mass and reflectivity.
1694
+
1695
+ Parameters
1696
+ ----------
1697
+ drop_number_concentration : xr.DataArray
1698
+ Drop number concentration in each diameter bin [#/m3/mm].
1699
+ velocity : xr.DataArray
1700
+ Fall velocity of drops in each diameter bin [m/s].
1701
+ The presence of a velocity_method dimension enable to compute the parameters
1702
+ with different velocity estimates.
1703
+ diameter : array-like
1704
+ Diameter of drops in each bin in m.
1705
+ sample_interval : float
1706
+ Time interval over which the samples are collected in seconds.
1707
+ water_density : float or array-like
1708
+ Density of water [kg/m3].
1709
+
1710
+ Returns
1711
+ -------
1712
+ ds : xarray.Dataset
1713
+ Dataset containing the following spectrum:
1714
+ - KE_spectrum : Kinetic Energy spectrum [J/m2/mm]
1715
+ - R_spectrum : Rain Rate spectrum [mm/h/mm]
1716
+ - W_spectrum : Mass spectrum [g/m3/mm]
1717
+ - Z_spectrum : Reflectivity spectrum [dBZ of mm6/m3/mm]
1718
+ """
1719
+ # Initialize dataset
1720
+ ds = xr.Dataset()
1721
+ ds["KE_spectrum"] = get_kinetic_energy_spectrum(
1722
+ drop_number_concentration,
1723
+ velocity=velocity,
1724
+ diameter=diameter,
1725
+ sample_interval=sample_interval,
1726
+ water_density=water_density,
1727
+ )
1728
+ ds["R_spectrum"] = get_rain_rate_spectrum(drop_number_concentration, velocity=velocity, diameter=diameter)
1729
+ ds["W_spectrum"] = get_liquid_water_spectrum(
1730
+ drop_number_concentration,
1731
+ diameter=diameter,
1732
+ water_density=water_density,
1733
+ )
1734
+ ds["Z_spectrum"] = get_equivalent_reflectivity_spectrum(drop_number_concentration, diameter=diameter)
1735
+ return ds