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