HydPy 6.2.dev1__cp313-cp313-win_amd64.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 (890) hide show
  1. hydpy/__init__.py +275 -0
  2. hydpy/aliases.py +2554 -0
  3. hydpy/auxs/__init__.py +0 -0
  4. hydpy/auxs/anntools.py +1305 -0
  5. hydpy/auxs/armatools.py +883 -0
  6. hydpy/auxs/calibtools.py +3337 -0
  7. hydpy/auxs/interptools.py +1094 -0
  8. hydpy/auxs/iuhtools.py +543 -0
  9. hydpy/auxs/networktools.py +597 -0
  10. hydpy/auxs/ppolytools.py +809 -0
  11. hydpy/auxs/quadtools.py +61 -0
  12. hydpy/auxs/roottools.py +228 -0
  13. hydpy/auxs/smoothtools.py +273 -0
  14. hydpy/auxs/statstools.py +2125 -0
  15. hydpy/auxs/validtools.py +81 -0
  16. hydpy/conf/HydPyConfigBase.xsd +68637 -0
  17. hydpy/conf/HydPyConfigBase.xsdt +358 -0
  18. hydpy/conf/HydPyConfigMultipleRuns.xsd +25 -0
  19. hydpy/conf/HydPyConfigSingleRun.xsd +24 -0
  20. hydpy/conf/__init__.py +0 -0
  21. hydpy/conf/a_coefficients_explicit_lobatto_sequence.npy +0 -0
  22. hydpy/conf/support_points_for_smoothpar_logistic2.npy +0 -0
  23. hydpy/config.py +42 -0
  24. hydpy/core/__init__.py +0 -0
  25. hydpy/core/aliastools.py +214 -0
  26. hydpy/core/autodoctools.py +1947 -0
  27. hydpy/core/auxfiletools.py +1169 -0
  28. hydpy/core/devicetools.py +3810 -0
  29. hydpy/core/exceptiontools.py +269 -0
  30. hydpy/core/filetools.py +1985 -0
  31. hydpy/core/hydpytools.py +3089 -0
  32. hydpy/core/importtools.py +1398 -0
  33. hydpy/core/indextools.py +345 -0
  34. hydpy/core/itemtools.py +1849 -0
  35. hydpy/core/masktools.py +460 -0
  36. hydpy/core/modeltools.py +4868 -0
  37. hydpy/core/netcdftools.py +2683 -0
  38. hydpy/core/objecttools.py +2023 -0
  39. hydpy/core/optiontools.py +1045 -0
  40. hydpy/core/parametertools.py +4674 -0
  41. hydpy/core/printtools.py +222 -0
  42. hydpy/core/propertytools.py +643 -0
  43. hydpy/core/pubtools.py +254 -0
  44. hydpy/core/selectiontools.py +1571 -0
  45. hydpy/core/sequencetools.py +4476 -0
  46. hydpy/core/seriestools.py +339 -0
  47. hydpy/core/testtools.py +2483 -0
  48. hydpy/core/timetools.py +3567 -0
  49. hydpy/core/typingtools.py +333 -0
  50. hydpy/core/variabletools.py +2615 -0
  51. hydpy/cythons/__init__.py +24 -0
  52. hydpy/cythons/annutils.pxd +33 -0
  53. hydpy/cythons/annutils.pyi +25 -0
  54. hydpy/cythons/annutils.pyx +120 -0
  55. hydpy/cythons/autogen/__init__.py +0 -0
  56. hydpy/cythons/autogen/annutils.cp313-win_amd64.pyd +0 -0
  57. hydpy/cythons/autogen/annutils.pxd +42 -0
  58. hydpy/cythons/autogen/annutils.pyx +129 -0
  59. hydpy/cythons/autogen/c_arma.cp313-win_amd64.pyd +0 -0
  60. hydpy/cythons/autogen/c_arma.pxd +179 -0
  61. hydpy/cythons/autogen/c_arma.pyx +356 -0
  62. hydpy/cythons/autogen/c_arma_rimorido.cp313-win_amd64.pyd +0 -0
  63. hydpy/cythons/autogen/c_arma_rimorido.pxd +179 -0
  64. hydpy/cythons/autogen/c_arma_rimorido.pyx +356 -0
  65. hydpy/cythons/autogen/c_conv.cp313-win_amd64.pyd +0 -0
  66. hydpy/cythons/autogen/c_conv.pxd +198 -0
  67. hydpy/cythons/autogen/c_conv.pyx +491 -0
  68. hydpy/cythons/autogen/c_conv_idw.cp313-win_amd64.pyd +0 -0
  69. hydpy/cythons/autogen/c_conv_idw.pxd +124 -0
  70. hydpy/cythons/autogen/c_conv_idw.pyx +264 -0
  71. hydpy/cythons/autogen/c_conv_idw_ed.cp313-win_amd64.pyd +0 -0
  72. hydpy/cythons/autogen/c_conv_idw_ed.pxd +197 -0
  73. hydpy/cythons/autogen/c_conv_idw_ed.pyx +481 -0
  74. hydpy/cythons/autogen/c_conv_nn.cp313-win_amd64.pyd +0 -0
  75. hydpy/cythons/autogen/c_conv_nn.pxd +120 -0
  76. hydpy/cythons/autogen/c_conv_nn.pyx +224 -0
  77. hydpy/cythons/autogen/c_dam.cp313-win_amd64.pyd +0 -0
  78. hydpy/cythons/autogen/c_dam.pxd +805 -0
  79. hydpy/cythons/autogen/c_dam.pyx +1477 -0
  80. hydpy/cythons/autogen/c_dam_llake.cp313-win_amd64.pyd +0 -0
  81. hydpy/cythons/autogen/c_dam_llake.pxd +364 -0
  82. hydpy/cythons/autogen/c_dam_llake.pyx +705 -0
  83. hydpy/cythons/autogen/c_dam_lreservoir.cp313-win_amd64.pyd +0 -0
  84. hydpy/cythons/autogen/c_dam_lreservoir.pxd +365 -0
  85. hydpy/cythons/autogen/c_dam_lreservoir.pyx +708 -0
  86. hydpy/cythons/autogen/c_dam_lretention.cp313-win_amd64.pyd +0 -0
  87. hydpy/cythons/autogen/c_dam_lretention.pxd +340 -0
  88. hydpy/cythons/autogen/c_dam_lretention.pyx +625 -0
  89. hydpy/cythons/autogen/c_dam_pump.cp313-win_amd64.pyd +0 -0
  90. hydpy/cythons/autogen/c_dam_pump.pxd +402 -0
  91. hydpy/cythons/autogen/c_dam_pump.pyx +724 -0
  92. hydpy/cythons/autogen/c_dam_pump_sluice.cp313-win_amd64.pyd +0 -0
  93. hydpy/cythons/autogen/c_dam_pump_sluice.pxd +452 -0
  94. hydpy/cythons/autogen/c_dam_pump_sluice.pyx +829 -0
  95. hydpy/cythons/autogen/c_dam_sluice.cp313-win_amd64.pyd +0 -0
  96. hydpy/cythons/autogen/c_dam_sluice.pxd +404 -0
  97. hydpy/cythons/autogen/c_dam_sluice.pyx +726 -0
  98. hydpy/cythons/autogen/c_dam_v001.cp313-win_amd64.pyd +0 -0
  99. hydpy/cythons/autogen/c_dam_v001.pxd +452 -0
  100. hydpy/cythons/autogen/c_dam_v001.pyx +816 -0
  101. hydpy/cythons/autogen/c_dam_v002.cp313-win_amd64.pyd +0 -0
  102. hydpy/cythons/autogen/c_dam_v002.pxd +394 -0
  103. hydpy/cythons/autogen/c_dam_v002.pyx +703 -0
  104. hydpy/cythons/autogen/c_dam_v003.cp313-win_amd64.pyd +0 -0
  105. hydpy/cythons/autogen/c_dam_v003.pxd +417 -0
  106. hydpy/cythons/autogen/c_dam_v003.pyx +744 -0
  107. hydpy/cythons/autogen/c_dam_v004.cp313-win_amd64.pyd +0 -0
  108. hydpy/cythons/autogen/c_dam_v004.pxd +486 -0
  109. hydpy/cythons/autogen/c_dam_v004.pyx +891 -0
  110. hydpy/cythons/autogen/c_dam_v005.cp313-win_amd64.pyd +0 -0
  111. hydpy/cythons/autogen/c_dam_v005.pxd +524 -0
  112. hydpy/cythons/autogen/c_dam_v005.pyx +928 -0
  113. hydpy/cythons/autogen/c_dummy.cp313-win_amd64.pyd +0 -0
  114. hydpy/cythons/autogen/c_dummy.pxd +151 -0
  115. hydpy/cythons/autogen/c_dummy.pyx +263 -0
  116. hydpy/cythons/autogen/c_dummy_interceptedwater.cp313-win_amd64.pyd +0 -0
  117. hydpy/cythons/autogen/c_dummy_interceptedwater.pxd +69 -0
  118. hydpy/cythons/autogen/c_dummy_interceptedwater.pyx +104 -0
  119. hydpy/cythons/autogen/c_dummy_node2node.cp313-win_amd64.pyd +0 -0
  120. hydpy/cythons/autogen/c_dummy_node2node.pxd +89 -0
  121. hydpy/cythons/autogen/c_dummy_node2node.pyx +148 -0
  122. hydpy/cythons/autogen/c_dummy_snowalbedo.cp313-win_amd64.pyd +0 -0
  123. hydpy/cythons/autogen/c_dummy_snowalbedo.pxd +69 -0
  124. hydpy/cythons/autogen/c_dummy_snowalbedo.pyx +104 -0
  125. hydpy/cythons/autogen/c_dummy_snowcover.cp313-win_amd64.pyd +0 -0
  126. hydpy/cythons/autogen/c_dummy_snowcover.pxd +69 -0
  127. hydpy/cythons/autogen/c_dummy_snowcover.pyx +104 -0
  128. hydpy/cythons/autogen/c_dummy_snowycanopy.cp313-win_amd64.pyd +0 -0
  129. hydpy/cythons/autogen/c_dummy_snowycanopy.pxd +69 -0
  130. hydpy/cythons/autogen/c_dummy_snowycanopy.pyx +104 -0
  131. hydpy/cythons/autogen/c_dummy_soilwater.cp313-win_amd64.pyd +0 -0
  132. hydpy/cythons/autogen/c_dummy_soilwater.pxd +69 -0
  133. hydpy/cythons/autogen/c_dummy_soilwater.pyx +104 -0
  134. hydpy/cythons/autogen/c_evap.cp313-win_amd64.pyd +0 -0
  135. hydpy/cythons/autogen/c_evap.pxd +1029 -0
  136. hydpy/cythons/autogen/c_evap.pyx +2601 -0
  137. hydpy/cythons/autogen/c_evap_aet_hbv96.cp313-win_amd64.pyd +0 -0
  138. hydpy/cythons/autogen/c_evap_aet_hbv96.pxd +227 -0
  139. hydpy/cythons/autogen/c_evap_aet_hbv96.pyx +584 -0
  140. hydpy/cythons/autogen/c_evap_aet_minhas.cp313-win_amd64.pyd +0 -0
  141. hydpy/cythons/autogen/c_evap_aet_minhas.pxd +193 -0
  142. hydpy/cythons/autogen/c_evap_aet_minhas.pyx +478 -0
  143. hydpy/cythons/autogen/c_evap_aet_morsim.cp313-win_amd64.pyd +0 -0
  144. hydpy/cythons/autogen/c_evap_aet_morsim.pxd +681 -0
  145. hydpy/cythons/autogen/c_evap_aet_morsim.pyx +1642 -0
  146. hydpy/cythons/autogen/c_evap_pet_ambav1.cp313-win_amd64.pyd +0 -0
  147. hydpy/cythons/autogen/c_evap_pet_ambav1.pxd +532 -0
  148. hydpy/cythons/autogen/c_evap_pet_ambav1.pyx +1296 -0
  149. hydpy/cythons/autogen/c_evap_pet_hbv96.cp313-win_amd64.pyd +0 -0
  150. hydpy/cythons/autogen/c_evap_pet_hbv96.pxd +179 -0
  151. hydpy/cythons/autogen/c_evap_pet_hbv96.pyx +328 -0
  152. hydpy/cythons/autogen/c_evap_pet_m.cp313-win_amd64.pyd +0 -0
  153. hydpy/cythons/autogen/c_evap_pet_m.pxd +124 -0
  154. hydpy/cythons/autogen/c_evap_pet_m.pyx +214 -0
  155. hydpy/cythons/autogen/c_evap_pet_mlc.cp313-win_amd64.pyd +0 -0
  156. hydpy/cythons/autogen/c_evap_pet_mlc.pxd +126 -0
  157. hydpy/cythons/autogen/c_evap_pet_mlc.pyx +214 -0
  158. hydpy/cythons/autogen/c_evap_ret_fao56.cp313-win_amd64.pyd +0 -0
  159. hydpy/cythons/autogen/c_evap_ret_fao56.pxd +305 -0
  160. hydpy/cythons/autogen/c_evap_ret_fao56.pyx +624 -0
  161. hydpy/cythons/autogen/c_evap_ret_io.cp313-win_amd64.pyd +0 -0
  162. hydpy/cythons/autogen/c_evap_ret_io.pxd +112 -0
  163. hydpy/cythons/autogen/c_evap_ret_io.pyx +176 -0
  164. hydpy/cythons/autogen/c_evap_ret_tw2002.cp313-win_amd64.pyd +0 -0
  165. hydpy/cythons/autogen/c_evap_ret_tw2002.pxd +139 -0
  166. hydpy/cythons/autogen/c_evap_ret_tw2002.pyx +273 -0
  167. hydpy/cythons/autogen/c_exch.cp313-win_amd64.pyd +0 -0
  168. hydpy/cythons/autogen/c_exch.pxd +230 -0
  169. hydpy/cythons/autogen/c_exch.pyx +462 -0
  170. hydpy/cythons/autogen/c_exch_branch_hbv96.cp313-win_amd64.pyd +0 -0
  171. hydpy/cythons/autogen/c_exch_branch_hbv96.pxd +134 -0
  172. hydpy/cythons/autogen/c_exch_branch_hbv96.pyx +255 -0
  173. hydpy/cythons/autogen/c_exch_waterlevel.cp313-win_amd64.pyd +0 -0
  174. hydpy/cythons/autogen/c_exch_waterlevel.pxd +54 -0
  175. hydpy/cythons/autogen/c_exch_waterlevel.pyx +78 -0
  176. hydpy/cythons/autogen/c_exch_weir_hbv96.cp313-win_amd64.pyd +0 -0
  177. hydpy/cythons/autogen/c_exch_weir_hbv96.pxd +156 -0
  178. hydpy/cythons/autogen/c_exch_weir_hbv96.pyx +282 -0
  179. hydpy/cythons/autogen/c_ga.cp313-win_amd64.pyd +0 -0
  180. hydpy/cythons/autogen/c_ga.pxd +353 -0
  181. hydpy/cythons/autogen/c_ga.pyx +1204 -0
  182. hydpy/cythons/autogen/c_ga_garto.cp313-win_amd64.pyd +0 -0
  183. hydpy/cythons/autogen/c_ga_garto.pxd +330 -0
  184. hydpy/cythons/autogen/c_ga_garto.pyx +1105 -0
  185. hydpy/cythons/autogen/c_ga_garto_submodel1.cp313-win_amd64.pyd +0 -0
  186. hydpy/cythons/autogen/c_ga_garto_submodel1.pxd +236 -0
  187. hydpy/cythons/autogen/c_ga_garto_submodel1.pyx +944 -0
  188. hydpy/cythons/autogen/c_gland.cp313-win_amd64.pyd +0 -0
  189. hydpy/cythons/autogen/c_gland.pxd +437 -0
  190. hydpy/cythons/autogen/c_gland.pyx +726 -0
  191. hydpy/cythons/autogen/c_gland_gr4.cp313-win_amd64.pyd +0 -0
  192. hydpy/cythons/autogen/c_gland_gr4.pxd +382 -0
  193. hydpy/cythons/autogen/c_gland_gr4.pyx +605 -0
  194. hydpy/cythons/autogen/c_gland_gr5.cp313-win_amd64.pyd +0 -0
  195. hydpy/cythons/autogen/c_gland_gr5.pxd +368 -0
  196. hydpy/cythons/autogen/c_gland_gr5.pyx +568 -0
  197. hydpy/cythons/autogen/c_gland_gr6.cp313-win_amd64.pyd +0 -0
  198. hydpy/cythons/autogen/c_gland_gr6.pxd +420 -0
  199. hydpy/cythons/autogen/c_gland_gr6.pyx +673 -0
  200. hydpy/cythons/autogen/c_hland.cp313-win_amd64.pyd +0 -0
  201. hydpy/cythons/autogen/c_hland.pxd +855 -0
  202. hydpy/cythons/autogen/c_hland.pyx +2486 -0
  203. hydpy/cythons/autogen/c_hland_96.cp313-win_amd64.pyd +0 -0
  204. hydpy/cythons/autogen/c_hland_96.pxd +631 -0
  205. hydpy/cythons/autogen/c_hland_96.pyx +1724 -0
  206. hydpy/cythons/autogen/c_hland_96c.cp313-win_amd64.pyd +0 -0
  207. hydpy/cythons/autogen/c_hland_96c.pxd +621 -0
  208. hydpy/cythons/autogen/c_hland_96c.pyx +1822 -0
  209. hydpy/cythons/autogen/c_hland_96p.cp313-win_amd64.pyd +0 -0
  210. hydpy/cythons/autogen/c_hland_96p.pxd +683 -0
  211. hydpy/cythons/autogen/c_hland_96p.pyx +1911 -0
  212. hydpy/cythons/autogen/c_kinw.cp313-win_amd64.pyd +0 -0
  213. hydpy/cythons/autogen/c_kinw.pxd +509 -0
  214. hydpy/cythons/autogen/c_kinw.pyx +965 -0
  215. hydpy/cythons/autogen/c_kinw_williams.cp313-win_amd64.pyd +0 -0
  216. hydpy/cythons/autogen/c_kinw_williams.pxd +409 -0
  217. hydpy/cythons/autogen/c_kinw_williams.pyx +763 -0
  218. hydpy/cythons/autogen/c_kinw_williams_ext.cp313-win_amd64.pyd +0 -0
  219. hydpy/cythons/autogen/c_kinw_williams_ext.pxd +220 -0
  220. hydpy/cythons/autogen/c_kinw_williams_ext.pyx +440 -0
  221. hydpy/cythons/autogen/c_lland.cp313-win_amd64.pyd +0 -0
  222. hydpy/cythons/autogen/c_lland.pxd +1386 -0
  223. hydpy/cythons/autogen/c_lland.pyx +3679 -0
  224. hydpy/cythons/autogen/c_lland_dd.cp313-win_amd64.pyd +0 -0
  225. hydpy/cythons/autogen/c_lland_dd.pxd +679 -0
  226. hydpy/cythons/autogen/c_lland_dd.pyx +1719 -0
  227. hydpy/cythons/autogen/c_lland_knauf.cp313-win_amd64.pyd +0 -0
  228. hydpy/cythons/autogen/c_lland_knauf.pxd +1096 -0
  229. hydpy/cythons/autogen/c_lland_knauf.pyx +2784 -0
  230. hydpy/cythons/autogen/c_lland_knauf_ic.cp313-win_amd64.pyd +0 -0
  231. hydpy/cythons/autogen/c_lland_knauf_ic.pxd +1369 -0
  232. hydpy/cythons/autogen/c_lland_knauf_ic.pyx +3625 -0
  233. hydpy/cythons/autogen/c_meteo.cp313-win_amd64.pyd +0 -0
  234. hydpy/cythons/autogen/c_meteo.pxd +469 -0
  235. hydpy/cythons/autogen/c_meteo.pyx +879 -0
  236. hydpy/cythons/autogen/c_meteo_clear_glob_io.cp313-win_amd64.pyd +0 -0
  237. hydpy/cythons/autogen/c_meteo_clear_glob_io.pxd +75 -0
  238. hydpy/cythons/autogen/c_meteo_clear_glob_io.pyx +107 -0
  239. hydpy/cythons/autogen/c_meteo_glob_fao56.cp313-win_amd64.pyd +0 -0
  240. hydpy/cythons/autogen/c_meteo_glob_fao56.pxd +209 -0
  241. hydpy/cythons/autogen/c_meteo_glob_fao56.pyx +339 -0
  242. hydpy/cythons/autogen/c_meteo_glob_io.cp313-win_amd64.pyd +0 -0
  243. hydpy/cythons/autogen/c_meteo_glob_io.pxd +63 -0
  244. hydpy/cythons/autogen/c_meteo_glob_io.pyx +91 -0
  245. hydpy/cythons/autogen/c_meteo_glob_morsim.cp313-win_amd64.pyd +0 -0
  246. hydpy/cythons/autogen/c_meteo_glob_morsim.pxd +289 -0
  247. hydpy/cythons/autogen/c_meteo_glob_morsim.pyx +527 -0
  248. hydpy/cythons/autogen/c_meteo_precip_io.cp313-win_amd64.pyd +0 -0
  249. hydpy/cythons/autogen/c_meteo_precip_io.pxd +112 -0
  250. hydpy/cythons/autogen/c_meteo_precip_io.pyx +176 -0
  251. hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.cp313-win_amd64.pyd +0 -0
  252. hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.pxd +87 -0
  253. hydpy/cythons/autogen/c_meteo_psun_sun_glob_io.pyx +123 -0
  254. hydpy/cythons/autogen/c_meteo_sun_fao56.cp313-win_amd64.pyd +0 -0
  255. hydpy/cythons/autogen/c_meteo_sun_fao56.pxd +209 -0
  256. hydpy/cythons/autogen/c_meteo_sun_fao56.pyx +343 -0
  257. hydpy/cythons/autogen/c_meteo_sun_morsim.cp313-win_amd64.pyd +0 -0
  258. hydpy/cythons/autogen/c_meteo_sun_morsim.pxd +286 -0
  259. hydpy/cythons/autogen/c_meteo_sun_morsim.pyx +519 -0
  260. hydpy/cythons/autogen/c_meteo_temp_io.cp313-win_amd64.pyd +0 -0
  261. hydpy/cythons/autogen/c_meteo_temp_io.pxd +112 -0
  262. hydpy/cythons/autogen/c_meteo_temp_io.pyx +176 -0
  263. hydpy/cythons/autogen/c_musk.cp313-win_amd64.pyd +0 -0
  264. hydpy/cythons/autogen/c_musk.pxd +282 -0
  265. hydpy/cythons/autogen/c_musk.pyx +605 -0
  266. hydpy/cythons/autogen/c_musk_classic.cp313-win_amd64.pyd +0 -0
  267. hydpy/cythons/autogen/c_musk_classic.pxd +138 -0
  268. hydpy/cythons/autogen/c_musk_classic.pyx +226 -0
  269. hydpy/cythons/autogen/c_musk_mct.cp313-win_amd64.pyd +0 -0
  270. hydpy/cythons/autogen/c_musk_mct.pxd +282 -0
  271. hydpy/cythons/autogen/c_musk_mct.pyx +609 -0
  272. hydpy/cythons/autogen/c_rconc.cp313-win_amd64.pyd +0 -0
  273. hydpy/cythons/autogen/c_rconc.pxd +119 -0
  274. hydpy/cythons/autogen/c_rconc.pyx +174 -0
  275. hydpy/cythons/autogen/c_rconc_nash.cp313-win_amd64.pyd +0 -0
  276. hydpy/cythons/autogen/c_rconc_nash.pxd +111 -0
  277. hydpy/cythons/autogen/c_rconc_nash.pyx +185 -0
  278. hydpy/cythons/autogen/c_rconc_uh.cp313-win_amd64.pyd +0 -0
  279. hydpy/cythons/autogen/c_rconc_uh.pxd +92 -0
  280. hydpy/cythons/autogen/c_rconc_uh.pyx +125 -0
  281. hydpy/cythons/autogen/c_sw1d.cp313-win_amd64.pyd +0 -0
  282. hydpy/cythons/autogen/c_sw1d.pxd +511 -0
  283. hydpy/cythons/autogen/c_sw1d.pyx +1263 -0
  284. hydpy/cythons/autogen/c_sw1d_channel.cp313-win_amd64.pyd +0 -0
  285. hydpy/cythons/autogen/c_sw1d_channel.pxd +119 -0
  286. hydpy/cythons/autogen/c_sw1d_channel.pyx +300 -0
  287. hydpy/cythons/autogen/c_sw1d_gate_out.cp313-win_amd64.pyd +0 -0
  288. hydpy/cythons/autogen/c_sw1d_gate_out.pxd +240 -0
  289. hydpy/cythons/autogen/c_sw1d_gate_out.pyx +476 -0
  290. hydpy/cythons/autogen/c_sw1d_lias.cp313-win_amd64.pyd +0 -0
  291. hydpy/cythons/autogen/c_sw1d_lias.pxd +320 -0
  292. hydpy/cythons/autogen/c_sw1d_lias.pyx +619 -0
  293. hydpy/cythons/autogen/c_sw1d_lias_sluice.cp313-win_amd64.pyd +0 -0
  294. hydpy/cythons/autogen/c_sw1d_lias_sluice.pxd +325 -0
  295. hydpy/cythons/autogen/c_sw1d_lias_sluice.pyx +644 -0
  296. hydpy/cythons/autogen/c_sw1d_network.cp313-win_amd64.pyd +0 -0
  297. hydpy/cythons/autogen/c_sw1d_network.pxd +90 -0
  298. hydpy/cythons/autogen/c_sw1d_network.pyx +246 -0
  299. hydpy/cythons/autogen/c_sw1d_pump.cp313-win_amd64.pyd +0 -0
  300. hydpy/cythons/autogen/c_sw1d_pump.pxd +256 -0
  301. hydpy/cythons/autogen/c_sw1d_pump.pyx +502 -0
  302. hydpy/cythons/autogen/c_sw1d_q_in.cp313-win_amd64.pyd +0 -0
  303. hydpy/cythons/autogen/c_sw1d_q_in.pxd +224 -0
  304. hydpy/cythons/autogen/c_sw1d_q_in.pyx +383 -0
  305. hydpy/cythons/autogen/c_sw1d_q_out.cp313-win_amd64.pyd +0 -0
  306. hydpy/cythons/autogen/c_sw1d_q_out.pxd +224 -0
  307. hydpy/cythons/autogen/c_sw1d_q_out.pyx +383 -0
  308. hydpy/cythons/autogen/c_sw1d_storage.cp313-win_amd64.pyd +0 -0
  309. hydpy/cythons/autogen/c_sw1d_storage.pxd +193 -0
  310. hydpy/cythons/autogen/c_sw1d_storage.pyx +349 -0
  311. hydpy/cythons/autogen/c_sw1d_weir_out.cp313-win_amd64.pyd +0 -0
  312. hydpy/cythons/autogen/c_sw1d_weir_out.pxd +212 -0
  313. hydpy/cythons/autogen/c_sw1d_weir_out.pyx +404 -0
  314. hydpy/cythons/autogen/c_test.cp313-win_amd64.pyd +0 -0
  315. hydpy/cythons/autogen/c_test.pxd +175 -0
  316. hydpy/cythons/autogen/c_test.pyx +348 -0
  317. hydpy/cythons/autogen/c_test_discontinous.cp313-win_amd64.pyd +0 -0
  318. hydpy/cythons/autogen/c_test_discontinous.pxd +146 -0
  319. hydpy/cythons/autogen/c_test_discontinous.pyx +256 -0
  320. hydpy/cythons/autogen/c_test_stiff0d.cp313-win_amd64.pyd +0 -0
  321. hydpy/cythons/autogen/c_test_stiff0d.pxd +146 -0
  322. hydpy/cythons/autogen/c_test_stiff0d.pyx +250 -0
  323. hydpy/cythons/autogen/c_test_stiff1d.cp313-win_amd64.pyd +0 -0
  324. hydpy/cythons/autogen/c_test_stiff1d.pxd +145 -0
  325. hydpy/cythons/autogen/c_test_stiff1d.pyx +294 -0
  326. hydpy/cythons/autogen/c_whmod.cp313-win_amd64.pyd +0 -0
  327. hydpy/cythons/autogen/c_whmod.pxd +482 -0
  328. hydpy/cythons/autogen/c_whmod.pyx +1156 -0
  329. hydpy/cythons/autogen/c_whmod_rural.cp313-win_amd64.pyd +0 -0
  330. hydpy/cythons/autogen/c_whmod_rural.pxd +411 -0
  331. hydpy/cythons/autogen/c_whmod_rural.pyx +982 -0
  332. hydpy/cythons/autogen/c_whmod_urban.cp313-win_amd64.pyd +0 -0
  333. hydpy/cythons/autogen/c_whmod_urban.pxd +482 -0
  334. hydpy/cythons/autogen/c_whmod_urban.pyx +1155 -0
  335. hydpy/cythons/autogen/c_wland.cp313-win_amd64.pyd +0 -0
  336. hydpy/cythons/autogen/c_wland.pxd +842 -0
  337. hydpy/cythons/autogen/c_wland.pyx +1890 -0
  338. hydpy/cythons/autogen/c_wland_gd.cp313-win_amd64.pyd +0 -0
  339. hydpy/cythons/autogen/c_wland_gd.pxd +829 -0
  340. hydpy/cythons/autogen/c_wland_gd.pyx +1847 -0
  341. hydpy/cythons/autogen/c_wland_wag.cp313-win_amd64.pyd +0 -0
  342. hydpy/cythons/autogen/c_wland_wag.pxd +810 -0
  343. hydpy/cythons/autogen/c_wland_wag.pyx +1780 -0
  344. hydpy/cythons/autogen/c_wq.cp313-win_amd64.pyd +0 -0
  345. hydpy/cythons/autogen/c_wq.pxd +275 -0
  346. hydpy/cythons/autogen/c_wq.pyx +652 -0
  347. hydpy/cythons/autogen/c_wq_trapeze.cp313-win_amd64.pyd +0 -0
  348. hydpy/cythons/autogen/c_wq_trapeze.pxd +170 -0
  349. hydpy/cythons/autogen/c_wq_trapeze.pyx +400 -0
  350. hydpy/cythons/autogen/c_wq_trapeze_strickler.cp313-win_amd64.pyd +0 -0
  351. hydpy/cythons/autogen/c_wq_trapeze_strickler.pxd +243 -0
  352. hydpy/cythons/autogen/c_wq_trapeze_strickler.pyx +578 -0
  353. hydpy/cythons/autogen/c_wq_walrus.cp313-win_amd64.pyd +0 -0
  354. hydpy/cythons/autogen/c_wq_walrus.pxd +61 -0
  355. hydpy/cythons/autogen/c_wq_walrus.pyx +82 -0
  356. hydpy/cythons/autogen/configutils.cp313-win_amd64.pyd +0 -0
  357. hydpy/cythons/autogen/configutils.pxd +17 -0
  358. hydpy/cythons/autogen/configutils.pyx +119 -0
  359. hydpy/cythons/autogen/interfaceutils.cp313-win_amd64.pyd +0 -0
  360. hydpy/cythons/autogen/interfaceutils.pxd +31 -0
  361. hydpy/cythons/autogen/interfaceutils.pyx +82 -0
  362. hydpy/cythons/autogen/interputils.cp313-win_amd64.pyd +0 -0
  363. hydpy/cythons/autogen/interputils.pxd +42 -0
  364. hydpy/cythons/autogen/interputils.pyx +118 -0
  365. hydpy/cythons/autogen/masterinterface.cp313-win_amd64.pyd +0 -0
  366. hydpy/cythons/autogen/masterinterface.pxd +153 -0
  367. hydpy/cythons/autogen/masterinterface.pyx +222 -0
  368. hydpy/cythons/autogen/pointerutils.cp313-win_amd64.pyd +0 -0
  369. hydpy/cythons/autogen/pointerutils.pxd +31 -0
  370. hydpy/cythons/autogen/pointerutils.pyx +650 -0
  371. hydpy/cythons/autogen/ppolyutils.cp313-win_amd64.pyd +0 -0
  372. hydpy/cythons/autogen/ppolyutils.pxd +35 -0
  373. hydpy/cythons/autogen/ppolyutils.pyx +59 -0
  374. hydpy/cythons/autogen/quadutils.cp313-win_amd64.pyd +0 -0
  375. hydpy/cythons/autogen/quadutils.pxd +26 -0
  376. hydpy/cythons/autogen/quadutils.pyx +973 -0
  377. hydpy/cythons/autogen/rootutils.cp313-win_amd64.pyd +0 -0
  378. hydpy/cythons/autogen/rootutils.pxd +28 -0
  379. hydpy/cythons/autogen/rootutils.pyx +109 -0
  380. hydpy/cythons/autogen/sequenceutils.cp313-win_amd64.pyd +0 -0
  381. hydpy/cythons/autogen/sequenceutils.pxd +45 -0
  382. hydpy/cythons/autogen/sequenceutils.pyx +101 -0
  383. hydpy/cythons/autogen/smoothutils.cp313-win_amd64.pyd +0 -0
  384. hydpy/cythons/autogen/smoothutils.pxd +29 -0
  385. hydpy/cythons/autogen/smoothutils.pyx +833 -0
  386. hydpy/cythons/configutils.pxd +8 -0
  387. hydpy/cythons/configutils.pyi +5 -0
  388. hydpy/cythons/configutils.pyx +110 -0
  389. hydpy/cythons/interfaceutils.pxd +22 -0
  390. hydpy/cythons/interfaceutils.pyi +15 -0
  391. hydpy/cythons/interfaceutils.pyx +73 -0
  392. hydpy/cythons/interputils.pxd +33 -0
  393. hydpy/cythons/interputils.pyi +32 -0
  394. hydpy/cythons/interputils.pyx +109 -0
  395. hydpy/cythons/modelutils.py +2990 -0
  396. hydpy/cythons/pointerutils.pxd +22 -0
  397. hydpy/cythons/pointerutils.pyi +89 -0
  398. hydpy/cythons/pointerutils.pyx +641 -0
  399. hydpy/cythons/ppolyutils.pxd +26 -0
  400. hydpy/cythons/ppolyutils.pyi +21 -0
  401. hydpy/cythons/ppolyutils.pyx +50 -0
  402. hydpy/cythons/quadutils.pxd +17 -0
  403. hydpy/cythons/quadutils.pyi +13 -0
  404. hydpy/cythons/quadutils.pyx +964 -0
  405. hydpy/cythons/rootutils.pxd +19 -0
  406. hydpy/cythons/rootutils.pyi +21 -0
  407. hydpy/cythons/rootutils.pyx +100 -0
  408. hydpy/cythons/sequenceutils.pxd +36 -0
  409. hydpy/cythons/sequenceutils.pyi +7 -0
  410. hydpy/cythons/sequenceutils.pyx +92 -0
  411. hydpy/cythons/smoothutils.pxd +20 -0
  412. hydpy/cythons/smoothutils.pyi +15 -0
  413. hydpy/cythons/smoothutils.pyx +824 -0
  414. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_dill_assl.py +13 -0
  415. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_kalk.py +13 -0
  416. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_leun.py +14 -0
  417. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/land_lahn_marb.py +13 -0
  418. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_dill_assl_lahn_leun.py +5 -0
  419. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_lahn_leun_lahn_kalk.py +5 -0
  420. hydpy/data/HydPy-H-Lahn/conditions/init_1996_01_01_00_00_00/stream_lahn_marb_lahn_leun.py +5 -0
  421. hydpy/data/HydPy-H-Lahn/control/default/land.py +9 -0
  422. hydpy/data/HydPy-H-Lahn/control/default/land_dill_assl.py +57 -0
  423. hydpy/data/HydPy-H-Lahn/control/default/land_lahn_kalk.py +57 -0
  424. hydpy/data/HydPy-H-Lahn/control/default/land_lahn_leun.py +56 -0
  425. hydpy/data/HydPy-H-Lahn/control/default/land_lahn_marb.py +57 -0
  426. hydpy/data/HydPy-H-Lahn/control/default/stream_dill_assl_lahn_leun.py +7 -0
  427. hydpy/data/HydPy-H-Lahn/control/default/stream_lahn_leun_lahn_kalk.py +7 -0
  428. hydpy/data/HydPy-H-Lahn/control/default/stream_lahn_marb_lahn_leun.py +7 -0
  429. hydpy/data/HydPy-H-Lahn/multiple_runs.xml +309 -0
  430. hydpy/data/HydPy-H-Lahn/multiple_runs_alpha.xml +71 -0
  431. hydpy/data/HydPy-H-Lahn/network/default/headwaters.py +11 -0
  432. hydpy/data/HydPy-H-Lahn/network/default/nonheadwaters.py +11 -0
  433. hydpy/data/HydPy-H-Lahn/network/default/streams.py +8 -0
  434. hydpy/data/HydPy-H-Lahn/series/default/dill_assl_obs_q.asc +11387 -0
  435. hydpy/data/HydPy-H-Lahn/series/default/evap_pet_hbv96_input_normalairtemperature.nc +0 -0
  436. hydpy/data/HydPy-H-Lahn/series/default/evap_pet_hbv96_input_normalevapotranspiration.nc +0 -0
  437. hydpy/data/HydPy-H-Lahn/series/default/hland_96_input_p.nc +0 -0
  438. hydpy/data/HydPy-H-Lahn/series/default/hland_96_input_t.nc +0 -0
  439. hydpy/data/HydPy-H-Lahn/series/default/lahn_kalk_obs_q.asc +11387 -0
  440. hydpy/data/HydPy-H-Lahn/series/default/lahn_leun_obs_q.asc +11387 -0
  441. hydpy/data/HydPy-H-Lahn/series/default/lahn_marb_obs_q.asc +11387 -0
  442. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  443. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  444. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_hland_96_input_p.asc +11387 -0
  445. hydpy/data/HydPy-H-Lahn/series/default/land_dill_assl_hland_96_input_t.asc +11387 -0
  446. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  447. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  448. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_hland_96_input_p.asc +11387 -0
  449. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_kalk_hland_96_input_t.asc +11387 -0
  450. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  451. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  452. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_hland_96_input_p.asc +11387 -0
  453. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_leun_hland_96_input_t.asc +11387 -0
  454. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_evap_pet_hbv96_input_normalairtemperature.asc +11387 -0
  455. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_evap_pet_hbv96_input_normalevapotranspiration.asc +11387 -0
  456. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_hland_96_input_p.asc +11387 -0
  457. hydpy/data/HydPy-H-Lahn/series/default/land_lahn_marb_hland_96_input_t.asc +11387 -0
  458. hydpy/data/HydPy-H-Lahn/series/default/obs_q.nc +0 -0
  459. hydpy/data/HydPy-H-Lahn/single_run.xml +152 -0
  460. hydpy/data/HydPy-H-Lahn/single_run.xmlt +143 -0
  461. hydpy/data/__init__.py +17 -0
  462. hydpy/docs/__init__.py +0 -0
  463. hydpy/docs/autofigs/__init__.py +0 -0
  464. hydpy/docs/bib/__init__.py +0 -0
  465. hydpy/docs/bib/refs.bib +566 -0
  466. hydpy/docs/combine_docversions.py +133 -0
  467. hydpy/docs/draw_model_sketches.py +1301 -0
  468. hydpy/docs/enable_autodoc.py +7 -0
  469. hydpy/docs/figs/HydPy-G-GR4.png +0 -0
  470. hydpy/docs/figs/HydPy-G-GR5.png +0 -0
  471. hydpy/docs/figs/HydPy-G-GR6.png +0 -0
  472. hydpy/docs/figs/HydPy-H-HBV96-COSERO.png +0 -0
  473. hydpy/docs/figs/HydPy-H-HBV96-PREVAH.png +0 -0
  474. hydpy/docs/figs/HydPy-H-HBV96.png +0 -0
  475. hydpy/docs/figs/HydPy-H-Lahn.png +0 -0
  476. hydpy/docs/figs/HydPy-KinW-Williams.png +0 -0
  477. hydpy/docs/figs/HydPy-L-DD.png +0 -0
  478. hydpy/docs/figs/HydPy-W-Wag.png +0 -0
  479. hydpy/docs/figs/HydPy_Logo.png +0 -0
  480. hydpy/docs/figs/HydPy_Logo_Text.png +0 -0
  481. hydpy/docs/figs/IDLE-editor.png +0 -0
  482. hydpy/docs/figs/IDLE-shell.png +0 -0
  483. hydpy/docs/figs/LAWA_river-basin-bumbers.png +0 -0
  484. hydpy/docs/figs/__init__.py +0 -0
  485. hydpy/docs/html_/__init__.py +0 -0
  486. hydpy/docs/polish_html.py +57 -0
  487. hydpy/docs/prepare.py +224 -0
  488. hydpy/docs/publish_docs.py +53 -0
  489. hydpy/docs/rst/HydPy-ARMA.rst +27 -0
  490. hydpy/docs/rst/HydPy-Conv.rst +22 -0
  491. hydpy/docs/rst/HydPy-Dam.rst +79 -0
  492. hydpy/docs/rst/HydPy-Dummy.rst +21 -0
  493. hydpy/docs/rst/HydPy-Evap.rst +26 -0
  494. hydpy/docs/rst/HydPy-Exch.rst +36 -0
  495. hydpy/docs/rst/HydPy-G.rst +40 -0
  496. hydpy/docs/rst/HydPy-GA.rst +34 -0
  497. hydpy/docs/rst/HydPy-H.rst +24 -0
  498. hydpy/docs/rst/HydPy-KinW.rst +32 -0
  499. hydpy/docs/rst/HydPy-L.rst +42 -0
  500. hydpy/docs/rst/HydPy-Meteo.rst +28 -0
  501. hydpy/docs/rst/HydPy-Musk.rst +21 -0
  502. hydpy/docs/rst/HydPy-Rconc.rst +17 -0
  503. hydpy/docs/rst/HydPy-SW1D.rst +49 -0
  504. hydpy/docs/rst/HydPy-Test.rst +19 -0
  505. hydpy/docs/rst/HydPy-W.rst +20 -0
  506. hydpy/docs/rst/HydPy-WHMod.rst +19 -0
  507. hydpy/docs/rst/HydPy-WQ.rst +20 -0
  508. hydpy/docs/rst/__init__.py +0 -0
  509. hydpy/docs/rst/additional_repositories.rst +40 -0
  510. hydpy/docs/rst/auxiliaries.rst +31 -0
  511. hydpy/docs/rst/continuous_integration.rst +75 -0
  512. hydpy/docs/rst/core.rst +75 -0
  513. hydpy/docs/rst/cythons.rst +47 -0
  514. hydpy/docs/rst/definitions.rst +506 -0
  515. hydpy/docs/rst/developer_guide.rst +54 -0
  516. hydpy/docs/rst/example_projects.rst +40 -0
  517. hydpy/docs/rst/execution.rst +22 -0
  518. hydpy/docs/rst/framework_tools.rst +56 -0
  519. hydpy/docs/rst/how_to_read_the_reference_manual.rst +156 -0
  520. hydpy/docs/rst/hydpydependencies.rst +55 -0
  521. hydpy/docs/rst/index.rst +125 -0
  522. hydpy/docs/rst/installation.rst +155 -0
  523. hydpy/docs/rst/model_families.rst +79 -0
  524. hydpy/docs/rst/model_overview.rst +291 -0
  525. hydpy/docs/rst/modelimports.rst +10 -0
  526. hydpy/docs/rst/options.rst +119 -0
  527. hydpy/docs/rst/programming_style.rst +572 -0
  528. hydpy/docs/rst/project_structure.rst +520 -0
  529. hydpy/docs/rst/quickstart.rst +304 -0
  530. hydpy/docs/rst/reference_manual.rst +29 -0
  531. hydpy/docs/rst/required_tools.rst +50 -0
  532. hydpy/docs/rst/simulation.rst +514 -0
  533. hydpy/docs/rst/submodel_interfaces.rst +32 -0
  534. hydpy/docs/rst/tests_and_documentation.rst +85 -0
  535. hydpy/docs/rst/user_guide.rst +38 -0
  536. hydpy/docs/rst/version_control.rst +116 -0
  537. hydpy/docs/rst/zbibliography.rst +8 -0
  538. hydpy/docs/sphinx/__init__.py +0 -0
  539. hydpy/docs/sphinx/_themes/basic_hydpy/changes/frameset.html +11 -0
  540. hydpy/docs/sphinx/_themes/basic_hydpy/changes/rstsource.html +15 -0
  541. hydpy/docs/sphinx/_themes/basic_hydpy/changes/versionchanges.html +33 -0
  542. hydpy/docs/sphinx/_themes/basic_hydpy/defindex.html +35 -0
  543. hydpy/docs/sphinx/_themes/basic_hydpy/domainindex.html +56 -0
  544. hydpy/docs/sphinx/_themes/basic_hydpy/genindex-single.html +63 -0
  545. hydpy/docs/sphinx/_themes/basic_hydpy/genindex-split.html +41 -0
  546. hydpy/docs/sphinx/_themes/basic_hydpy/genindex.html +76 -0
  547. hydpy/docs/sphinx/_themes/basic_hydpy/globaltoc.html +11 -0
  548. hydpy/docs/sphinx/_themes/basic_hydpy/layout.html +221 -0
  549. hydpy/docs/sphinx/_themes/basic_hydpy/localtoc.html +15 -0
  550. hydpy/docs/sphinx/_themes/basic_hydpy/opensearch.xml +13 -0
  551. hydpy/docs/sphinx/_themes/basic_hydpy/page.html +13 -0
  552. hydpy/docs/sphinx/_themes/basic_hydpy/relations.html +23 -0
  553. hydpy/docs/sphinx/_themes/basic_hydpy/search.html +65 -0
  554. hydpy/docs/sphinx/_themes/basic_hydpy/searchbox.html +21 -0
  555. hydpy/docs/sphinx/_themes/basic_hydpy/searchfield.html +23 -0
  556. hydpy/docs/sphinx/_themes/basic_hydpy/sourcelink.html +18 -0
  557. hydpy/docs/sphinx/_themes/basic_hydpy/static/basic.css_t +925 -0
  558. hydpy/docs/sphinx/_themes/basic_hydpy/static/doctools.js +156 -0
  559. hydpy/docs/sphinx/_themes/basic_hydpy/static/documentation_options.js_t +13 -0
  560. hydpy/docs/sphinx/_themes/basic_hydpy/static/file.png +0 -0
  561. hydpy/docs/sphinx/_themes/basic_hydpy/static/language_data.js_t +26 -0
  562. hydpy/docs/sphinx/_themes/basic_hydpy/static/minus.png +0 -0
  563. hydpy/docs/sphinx/_themes/basic_hydpy/static/plus.png +0 -0
  564. hydpy/docs/sphinx/_themes/basic_hydpy/static/searchtools.js +574 -0
  565. hydpy/docs/sphinx/_themes/basic_hydpy/static/sphinx_highlight.js +154 -0
  566. hydpy/docs/sphinx/_themes/basic_hydpy/theme.conf +16 -0
  567. hydpy/docs/sphinx/_themes/classic_hydpy/layout.html +23 -0
  568. hydpy/docs/sphinx/_themes/classic_hydpy/static/classic.css_t +358 -0
  569. hydpy/docs/sphinx/_themes/classic_hydpy/static/sidebar.js_t +72 -0
  570. hydpy/docs/sphinx/_themes/classic_hydpy/theme.conf +32 -0
  571. hydpy/docs/sphinx/conf.py +398 -0
  572. hydpy/docs/sphinx/defaultlinks_extension.py +36 -0
  573. hydpy/docs/sphinx/integrationtest_extension.py +104 -0
  574. hydpy/docs/sphinx/projectstructure_extension.py +58 -0
  575. hydpy/docs/sphinx/submodelgraph_extension.py +53 -0
  576. hydpy/exe/__init__.py +0 -0
  577. hydpy/exe/commandtools.py +651 -0
  578. hydpy/exe/hyd.py +277 -0
  579. hydpy/exe/modelimports.py +41 -0
  580. hydpy/exe/replacetools.py +216 -0
  581. hydpy/exe/servertools.py +2348 -0
  582. hydpy/exe/xmltools.py +3280 -0
  583. hydpy/interfaces/__init__.py +0 -0
  584. hydpy/interfaces/aetinterfaces.py +94 -0
  585. hydpy/interfaces/dischargeinterfaces.py +45 -0
  586. hydpy/interfaces/petinterfaces.py +117 -0
  587. hydpy/interfaces/precipinterfaces.py +42 -0
  588. hydpy/interfaces/radiationinterfaces.py +79 -0
  589. hydpy/interfaces/rconcinterfaces.py +30 -0
  590. hydpy/interfaces/routinginterfaces.py +324 -0
  591. hydpy/interfaces/soilinterfaces.py +96 -0
  592. hydpy/interfaces/stateinterfaces.py +98 -0
  593. hydpy/interfaces/tempinterfaces.py +46 -0
  594. hydpy/models/__init__.py +0 -0
  595. hydpy/models/arma/__init__.py +14 -0
  596. hydpy/models/arma/arma_control.py +383 -0
  597. hydpy/models/arma/arma_derived.py +204 -0
  598. hydpy/models/arma/arma_fluxes.py +41 -0
  599. hydpy/models/arma/arma_inlets.py +11 -0
  600. hydpy/models/arma/arma_logs.py +19 -0
  601. hydpy/models/arma/arma_model.py +461 -0
  602. hydpy/models/arma/arma_outlets.py +11 -0
  603. hydpy/models/arma_rimorido.py +381 -0
  604. hydpy/models/conv/__init__.py +12 -0
  605. hydpy/models/conv/conv_control.py +303 -0
  606. hydpy/models/conv/conv_derived.py +271 -0
  607. hydpy/models/conv/conv_fluxes.py +54 -0
  608. hydpy/models/conv/conv_inlets.py +11 -0
  609. hydpy/models/conv/conv_model.py +687 -0
  610. hydpy/models/conv/conv_outlets.py +11 -0
  611. hydpy/models/conv_idw.py +120 -0
  612. hydpy/models/conv_idw_ed.py +184 -0
  613. hydpy/models/conv_nn.py +112 -0
  614. hydpy/models/dam/__init__.py +16 -0
  615. hydpy/models/dam/dam_aides.py +17 -0
  616. hydpy/models/dam/dam_control.py +346 -0
  617. hydpy/models/dam/dam_derived.py +559 -0
  618. hydpy/models/dam/dam_factors.py +46 -0
  619. hydpy/models/dam/dam_fluxes.py +179 -0
  620. hydpy/models/dam/dam_inlets.py +29 -0
  621. hydpy/models/dam/dam_logs.py +52 -0
  622. hydpy/models/dam/dam_model.py +5011 -0
  623. hydpy/models/dam/dam_outlets.py +23 -0
  624. hydpy/models/dam/dam_receivers.py +41 -0
  625. hydpy/models/dam/dam_senders.py +23 -0
  626. hydpy/models/dam/dam_solver.py +75 -0
  627. hydpy/models/dam/dam_states.py +11 -0
  628. hydpy/models/dam_llake.py +499 -0
  629. hydpy/models/dam_lreservoir.py +548 -0
  630. hydpy/models/dam_lretention.py +343 -0
  631. hydpy/models/dam_pump.py +278 -0
  632. hydpy/models/dam_pump_sluice.py +339 -0
  633. hydpy/models/dam_sluice.py +319 -0
  634. hydpy/models/dam_v001.py +1127 -0
  635. hydpy/models/dam_v002.py +381 -0
  636. hydpy/models/dam_v003.py +422 -0
  637. hydpy/models/dam_v004.py +665 -0
  638. hydpy/models/dam_v005.py +479 -0
  639. hydpy/models/dummy/__init__.py +15 -0
  640. hydpy/models/dummy/dummy_control.py +22 -0
  641. hydpy/models/dummy/dummy_fluxes.py +11 -0
  642. hydpy/models/dummy/dummy_inlets.py +11 -0
  643. hydpy/models/dummy/dummy_inputs.py +41 -0
  644. hydpy/models/dummy/dummy_model.py +196 -0
  645. hydpy/models/dummy/dummy_outlets.py +11 -0
  646. hydpy/models/dummy_interceptedwater.py +85 -0
  647. hydpy/models/dummy_node2node.py +83 -0
  648. hydpy/models/dummy_snowalbedo.py +84 -0
  649. hydpy/models/dummy_snowcover.py +84 -0
  650. hydpy/models/dummy_snowycanopy.py +86 -0
  651. hydpy/models/dummy_soilwater.py +85 -0
  652. hydpy/models/evap/__init__.py +13 -0
  653. hydpy/models/evap/evap_control.py +354 -0
  654. hydpy/models/evap/evap_derived.py +236 -0
  655. hydpy/models/evap/evap_factors.py +188 -0
  656. hydpy/models/evap/evap_fixed.py +68 -0
  657. hydpy/models/evap/evap_fluxes.py +150 -0
  658. hydpy/models/evap/evap_inputs.py +54 -0
  659. hydpy/models/evap/evap_logs.py +91 -0
  660. hydpy/models/evap/evap_masks.py +48 -0
  661. hydpy/models/evap/evap_model.py +9170 -0
  662. hydpy/models/evap/evap_parameters.py +149 -0
  663. hydpy/models/evap/evap_sequences.py +32 -0
  664. hydpy/models/evap/evap_states.py +18 -0
  665. hydpy/models/evap_aet_hbv96.py +372 -0
  666. hydpy/models/evap_aet_minhas.py +331 -0
  667. hydpy/models/evap_aet_morsim.py +627 -0
  668. hydpy/models/evap_pet_ambav1.py +483 -0
  669. hydpy/models/evap_pet_hbv96.py +147 -0
  670. hydpy/models/evap_pet_m.py +94 -0
  671. hydpy/models/evap_pet_mlc.py +107 -0
  672. hydpy/models/evap_ret_fao56.py +265 -0
  673. hydpy/models/evap_ret_io.py +74 -0
  674. hydpy/models/evap_ret_tw2002.py +165 -0
  675. hydpy/models/exch/__init__.py +14 -0
  676. hydpy/models/exch/exch_control.py +262 -0
  677. hydpy/models/exch/exch_derived.py +36 -0
  678. hydpy/models/exch/exch_factors.py +26 -0
  679. hydpy/models/exch/exch_fluxes.py +48 -0
  680. hydpy/models/exch/exch_inlets.py +11 -0
  681. hydpy/models/exch/exch_logs.py +12 -0
  682. hydpy/models/exch/exch_model.py +451 -0
  683. hydpy/models/exch/exch_outlets.py +17 -0
  684. hydpy/models/exch/exch_receivers.py +17 -0
  685. hydpy/models/exch_branch_hbv96.py +186 -0
  686. hydpy/models/exch_waterlevel.py +73 -0
  687. hydpy/models/exch_weir_hbv96.py +609 -0
  688. hydpy/models/ga/__init__.py +14 -0
  689. hydpy/models/ga/ga_aides.py +17 -0
  690. hydpy/models/ga/ga_control.py +208 -0
  691. hydpy/models/ga/ga_derived.py +77 -0
  692. hydpy/models/ga/ga_fluxes.py +83 -0
  693. hydpy/models/ga/ga_inputs.py +26 -0
  694. hydpy/models/ga/ga_logs.py +17 -0
  695. hydpy/models/ga/ga_model.py +2952 -0
  696. hydpy/models/ga/ga_states.py +87 -0
  697. hydpy/models/ga_garto.py +1001 -0
  698. hydpy/models/ga_garto_submodel1.py +79 -0
  699. hydpy/models/gland/__init__.py +14 -0
  700. hydpy/models/gland/gland_control.py +90 -0
  701. hydpy/models/gland/gland_derived.py +113 -0
  702. hydpy/models/gland/gland_fluxes.py +137 -0
  703. hydpy/models/gland/gland_inputs.py +12 -0
  704. hydpy/models/gland/gland_model.py +1439 -0
  705. hydpy/models/gland/gland_outlets.py +11 -0
  706. hydpy/models/gland/gland_states.py +90 -0
  707. hydpy/models/gland_gr4.py +501 -0
  708. hydpy/models/gland_gr5.py +463 -0
  709. hydpy/models/gland_gr6.py +487 -0
  710. hydpy/models/hland/__init__.py +20 -0
  711. hydpy/models/hland/hland_aides.py +19 -0
  712. hydpy/models/hland/hland_constants.py +37 -0
  713. hydpy/models/hland/hland_control.py +1530 -0
  714. hydpy/models/hland/hland_derived.py +683 -0
  715. hydpy/models/hland/hland_factors.py +57 -0
  716. hydpy/models/hland/hland_fixed.py +42 -0
  717. hydpy/models/hland/hland_fluxes.py +279 -0
  718. hydpy/models/hland/hland_inputs.py +19 -0
  719. hydpy/models/hland/hland_masks.py +107 -0
  720. hydpy/models/hland/hland_model.py +4664 -0
  721. hydpy/models/hland/hland_outlets.py +11 -0
  722. hydpy/models/hland/hland_parameters.py +227 -0
  723. hydpy/models/hland/hland_sequences.py +382 -0
  724. hydpy/models/hland/hland_states.py +236 -0
  725. hydpy/models/hland_96.py +1812 -0
  726. hydpy/models/hland_96c.py +1196 -0
  727. hydpy/models/hland_96p.py +1204 -0
  728. hydpy/models/kinw/__init__.py +18 -0
  729. hydpy/models/kinw/kinw_aides.py +306 -0
  730. hydpy/models/kinw/kinw_control.py +270 -0
  731. hydpy/models/kinw/kinw_derived.py +197 -0
  732. hydpy/models/kinw/kinw_fixed.py +33 -0
  733. hydpy/models/kinw/kinw_fluxes.py +37 -0
  734. hydpy/models/kinw/kinw_inlets.py +11 -0
  735. hydpy/models/kinw/kinw_model.py +3026 -0
  736. hydpy/models/kinw/kinw_outlets.py +11 -0
  737. hydpy/models/kinw/kinw_solver.py +45 -0
  738. hydpy/models/kinw/kinw_states.py +17 -0
  739. hydpy/models/kinw_williams.py +1299 -0
  740. hydpy/models/kinw_williams_ext.py +768 -0
  741. hydpy/models/lland/__init__.py +42 -0
  742. hydpy/models/lland/lland_aides.py +38 -0
  743. hydpy/models/lland/lland_constants.py +88 -0
  744. hydpy/models/lland/lland_control.py +1329 -0
  745. hydpy/models/lland/lland_derived.py +380 -0
  746. hydpy/models/lland/lland_factors.py +18 -0
  747. hydpy/models/lland/lland_fixed.py +128 -0
  748. hydpy/models/lland/lland_fluxes.py +626 -0
  749. hydpy/models/lland/lland_inlets.py +12 -0
  750. hydpy/models/lland/lland_inputs.py +33 -0
  751. hydpy/models/lland/lland_logs.py +17 -0
  752. hydpy/models/lland/lland_masks.py +212 -0
  753. hydpy/models/lland/lland_model.py +7690 -0
  754. hydpy/models/lland/lland_outlets.py +12 -0
  755. hydpy/models/lland/lland_parameters.py +195 -0
  756. hydpy/models/lland/lland_sequences.py +67 -0
  757. hydpy/models/lland/lland_states.py +280 -0
  758. hydpy/models/lland_dd.py +2270 -0
  759. hydpy/models/lland_knauf.py +2156 -0
  760. hydpy/models/lland_knauf_ic.py +1920 -0
  761. hydpy/models/meteo/__init__.py +12 -0
  762. hydpy/models/meteo/meteo_control.py +154 -0
  763. hydpy/models/meteo/meteo_derived.py +159 -0
  764. hydpy/models/meteo/meteo_factors.py +88 -0
  765. hydpy/models/meteo/meteo_fixed.py +19 -0
  766. hydpy/models/meteo/meteo_fluxes.py +46 -0
  767. hydpy/models/meteo/meteo_inputs.py +47 -0
  768. hydpy/models/meteo/meteo_logs.py +30 -0
  769. hydpy/models/meteo/meteo_model.py +2904 -0
  770. hydpy/models/meteo/meteo_parameters.py +14 -0
  771. hydpy/models/meteo/meteo_sequences.py +22 -0
  772. hydpy/models/meteo_clear_glob_io.py +77 -0
  773. hydpy/models/meteo_glob_fao56.py +217 -0
  774. hydpy/models/meteo_glob_io.py +68 -0
  775. hydpy/models/meteo_glob_morsim.py +444 -0
  776. hydpy/models/meteo_precip_io.py +76 -0
  777. hydpy/models/meteo_psun_sun_glob_io.py +83 -0
  778. hydpy/models/meteo_sun_fao56.py +188 -0
  779. hydpy/models/meteo_sun_morsim.py +466 -0
  780. hydpy/models/meteo_temp_io.py +76 -0
  781. hydpy/models/musk/__init__.py +15 -0
  782. hydpy/models/musk/musk_control.py +328 -0
  783. hydpy/models/musk/musk_derived.py +32 -0
  784. hydpy/models/musk/musk_factors.py +53 -0
  785. hydpy/models/musk/musk_fluxes.py +24 -0
  786. hydpy/models/musk/musk_inlets.py +11 -0
  787. hydpy/models/musk/musk_masks.py +15 -0
  788. hydpy/models/musk/musk_model.py +838 -0
  789. hydpy/models/musk/musk_outlets.py +11 -0
  790. hydpy/models/musk/musk_sequences.py +88 -0
  791. hydpy/models/musk/musk_solver.py +68 -0
  792. hydpy/models/musk/musk_states.py +64 -0
  793. hydpy/models/musk_classic.py +228 -0
  794. hydpy/models/musk_mct.py +1247 -0
  795. hydpy/models/rconc/__init__.py +12 -0
  796. hydpy/models/rconc/rconc_control.py +473 -0
  797. hydpy/models/rconc/rconc_derived.py +76 -0
  798. hydpy/models/rconc/rconc_fluxes.py +19 -0
  799. hydpy/models/rconc/rconc_logs.py +74 -0
  800. hydpy/models/rconc/rconc_model.py +260 -0
  801. hydpy/models/rconc/rconc_states.py +11 -0
  802. hydpy/models/rconc_nash.py +48 -0
  803. hydpy/models/rconc_uh.py +53 -0
  804. hydpy/models/sw1d/__init__.py +17 -0
  805. hydpy/models/sw1d/sw1d_control.py +356 -0
  806. hydpy/models/sw1d/sw1d_derived.py +85 -0
  807. hydpy/models/sw1d/sw1d_factors.py +78 -0
  808. hydpy/models/sw1d/sw1d_fixed.py +12 -0
  809. hydpy/models/sw1d/sw1d_fluxes.py +55 -0
  810. hydpy/models/sw1d/sw1d_inlets.py +17 -0
  811. hydpy/models/sw1d/sw1d_model.py +3385 -0
  812. hydpy/models/sw1d/sw1d_outlets.py +11 -0
  813. hydpy/models/sw1d/sw1d_receivers.py +11 -0
  814. hydpy/models/sw1d/sw1d_senders.py +11 -0
  815. hydpy/models/sw1d/sw1d_states.py +23 -0
  816. hydpy/models/sw1d_channel.py +2051 -0
  817. hydpy/models/sw1d_gate_out.py +599 -0
  818. hydpy/models/sw1d_lias.py +105 -0
  819. hydpy/models/sw1d_lias_sluice.py +531 -0
  820. hydpy/models/sw1d_network.py +1219 -0
  821. hydpy/models/sw1d_pump.py +448 -0
  822. hydpy/models/sw1d_q_in.py +79 -0
  823. hydpy/models/sw1d_q_out.py +81 -0
  824. hydpy/models/sw1d_storage.py +78 -0
  825. hydpy/models/sw1d_weir_out.py +75 -0
  826. hydpy/models/test/__init__.py +14 -0
  827. hydpy/models/test/test_control.py +28 -0
  828. hydpy/models/test/test_fluxes.py +17 -0
  829. hydpy/models/test/test_model.py +201 -0
  830. hydpy/models/test/test_solver.py +48 -0
  831. hydpy/models/test/test_states.py +17 -0
  832. hydpy/models/test_discontinous.py +46 -0
  833. hydpy/models/test_stiff0d.py +47 -0
  834. hydpy/models/test_stiff1d.py +42 -0
  835. hydpy/models/whmod/__init__.py +21 -0
  836. hydpy/models/whmod/whmod_constants.py +77 -0
  837. hydpy/models/whmod/whmod_control.py +333 -0
  838. hydpy/models/whmod/whmod_derived.py +210 -0
  839. hydpy/models/whmod/whmod_factors.py +9 -0
  840. hydpy/models/whmod/whmod_fluxes.py +105 -0
  841. hydpy/models/whmod/whmod_inputs.py +15 -0
  842. hydpy/models/whmod/whmod_masks.py +178 -0
  843. hydpy/models/whmod/whmod_model.py +2091 -0
  844. hydpy/models/whmod/whmod_parameters.py +155 -0
  845. hydpy/models/whmod/whmod_sequences.py +193 -0
  846. hydpy/models/whmod/whmod_states.py +73 -0
  847. hydpy/models/whmod_rural.py +794 -0
  848. hydpy/models/whmod_urban.py +1011 -0
  849. hydpy/models/wland/__init__.py +43 -0
  850. hydpy/models/wland/wland_aides.py +55 -0
  851. hydpy/models/wland/wland_constants.py +103 -0
  852. hydpy/models/wland/wland_control.py +508 -0
  853. hydpy/models/wland/wland_derived.py +330 -0
  854. hydpy/models/wland/wland_factors.py +11 -0
  855. hydpy/models/wland/wland_fixed.py +12 -0
  856. hydpy/models/wland/wland_fluxes.py +166 -0
  857. hydpy/models/wland/wland_inputs.py +33 -0
  858. hydpy/models/wland/wland_masks.py +54 -0
  859. hydpy/models/wland/wland_model.py +3755 -0
  860. hydpy/models/wland/wland_outlets.py +11 -0
  861. hydpy/models/wland/wland_parameters.py +214 -0
  862. hydpy/models/wland/wland_sequences.py +108 -0
  863. hydpy/models/wland/wland_solver.py +45 -0
  864. hydpy/models/wland/wland_states.py +56 -0
  865. hydpy/models/wland_gd.py +888 -0
  866. hydpy/models/wland_wag.py +1244 -0
  867. hydpy/models/wq/__init__.py +14 -0
  868. hydpy/models/wq/wq_control.py +117 -0
  869. hydpy/models/wq/wq_derived.py +182 -0
  870. hydpy/models/wq/wq_factors.py +79 -0
  871. hydpy/models/wq/wq_fluxes.py +17 -0
  872. hydpy/models/wq/wq_model.py +1889 -0
  873. hydpy/models/wq_trapeze.py +168 -0
  874. hydpy/models/wq_trapeze_strickler.py +101 -0
  875. hydpy/models/wq_walrus.py +57 -0
  876. hydpy/py.typed +0 -0
  877. hydpy/tests/.coveragerc +22 -0
  878. hydpy/tests/__init__.py +0 -0
  879. hydpy/tests/check_consistency.py +32 -0
  880. hydpy/tests/hydpydoctestcustomize.pth +1 -0
  881. hydpy/tests/hydpydoctestcustomize.py +15 -0
  882. hydpy/tests/iotesting/__init__.py +0 -0
  883. hydpy/tests/run_doctests.py +233 -0
  884. hydpy-6.2.dev1.data/scripts/hyd.py +277 -0
  885. hydpy-6.2.dev1.dist-info/LICENSE +165 -0
  886. hydpy-6.2.dev1.dist-info/METADATA +163 -0
  887. hydpy-6.2.dev1.dist-info/RECORD +890 -0
  888. hydpy-6.2.dev1.dist-info/WHEEL +5 -0
  889. hydpy-6.2.dev1.dist-info/licenses_hydpy_installer.txt +42 -0
  890. hydpy-6.2.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3810 @@
1
+ """This modules implements the fundamental features for structuring *HydPy* projects.
2
+
3
+ Module |devicetools| provides two |Device| subclasses, |Node| and |Element|. In this
4
+ documentation, "node" stands for an object of class |Node|, "element" for an object of
5
+ class |Element|, and "device" for either of them (you cannot initialise objects of
6
+ class |Device| directly). On the other hand, the term "nodes", for example, does not
7
+ necessarily mean an object of class |Nodes| but any other group of |Node| objects as
8
+ well.
9
+
10
+ Each element handles a single |Model| object and represents, for example, a subbasin or
11
+ a channel segment. The purpose of a node is to connect different elements and, for
12
+ example, to pass the discharge calculated for a subbasin outlet (from a first element)
13
+ to the top of a channel segment (to second element). Class |Node| and |Element| come
14
+ with specialised container classes (|Nodes| and |Elements|). The names of individual
15
+ nodes and elements serve as identity values, so duplicate names are not permitted.
16
+
17
+ Note that module |devicetools| implements a registry mechanism both for nodes and
18
+ elements, preventing instantiating an object with an already assigned name. This
19
+ mechanism allows to address the same node or element in different network files (see
20
+ module |selectiontools|).
21
+
22
+ Let us take class |Node| as an example. One can call its constructor with the same
23
+ name multiple times, but it returns already existing nodes when available:
24
+
25
+ >>> from hydpy import Node
26
+ >>> node1 = Node("test1")
27
+ >>> node2a = Node("test2")
28
+ >>> node2b = Node("test2")
29
+ >>> node1 is node2a
30
+ False
31
+ >>> node2a is node2b
32
+ True
33
+
34
+ To get information on all currently registered nodes, call method
35
+ |Device.extract_new|:
36
+
37
+ >>> Node.extract_new()
38
+ Nodes("test1", "test2")
39
+
40
+ Method |Device.extract_new| returns only those nodes prepared or
41
+ recovered after its last invocation:
42
+
43
+ >>> node1 = Node("test1")
44
+ >>> node3a = Node("test3")
45
+
46
+ >>> Node.extract_new()
47
+ Nodes("test1", "test3")
48
+
49
+ For a complete list of all available nodes, use the method |Device.query_all|:
50
+
51
+ >>> Node.query_all()
52
+ Nodes("test1", "test2", "test3")
53
+
54
+ When working interactively in the Python interpreter, it might sometimes be helpful to
55
+ clear the registry entirely. However, Do this with care because defining nodes with
56
+ already assigned names might result in surprises due to using their names for
57
+ identification:
58
+
59
+ >>> nodes = Node.query_all()
60
+ >>> Node.clear_all()
61
+ >>> Node.query_all()
62
+ Nodes()
63
+ >>> node3b = Node("test3")
64
+ >>> node3b in nodes
65
+ True
66
+ >>> nodes.test3.name == node3b.name
67
+ True
68
+ >>> nodes.test3 is node3b
69
+ False
70
+ """
71
+
72
+ # import...
73
+ # ...from standard library
74
+ from __future__ import annotations
75
+ import abc
76
+ import collections
77
+ import contextlib
78
+ import copy
79
+ import itertools
80
+ import operator
81
+ import warnings
82
+
83
+ # ...from site-packages
84
+ import numpy
85
+
86
+ # ...from HydPy
87
+ import hydpy
88
+ from hydpy.core import exceptiontools
89
+ from hydpy.core import masktools
90
+ from hydpy.core import netcdftools
91
+ from hydpy.core import objecttools
92
+ from hydpy.core import printtools
93
+ from hydpy.core import propertytools
94
+ from hydpy.core import sequencetools
95
+ from hydpy.core import seriestools
96
+ from hydpy.core import timetools
97
+ from hydpy.core.typingtools import *
98
+
99
+ if TYPE_CHECKING:
100
+ from matplotlib import pyplot
101
+ import pandas
102
+ from hydpy.core import auxfiletools
103
+ from hydpy.core import hydpytools
104
+ from hydpy.core import modeltools
105
+ from hydpy.cythons import pointerutils
106
+ else:
107
+ pandas = exceptiontools.OptionalImport("pandas", ["pandas"], locals())
108
+ pyplot = exceptiontools.OptionalImport("pyplot", ["matplotlib.pyplot"], locals())
109
+ from hydpy.cythons.autogen import pointerutils
110
+
111
+ TypeDevice = TypeVar("TypeDevice", bound="Device")
112
+ TypeDevices = TypeVar("TypeDevices", bound="Devices[Any]")
113
+ NodeOrElement: TypeAlias = Union["Node", "Element"]
114
+ TypeNodeElement = TypeVar("TypeNodeElement", "Node", "Element", NodeOrElement)
115
+
116
+ NodesConstrArg: TypeAlias = MayNonerable2["Node", str]
117
+ ElementsConstrArg: TypeAlias = MayNonerable2["Element", str]
118
+ NodeConstrArg: TypeAlias = Union["Node", str]
119
+ ElementConstrArg: TypeAlias = Union["Element", str]
120
+ IOSequenceArg: TypeAlias = Union[
121
+ sequencetools.IOSequence, type[sequencetools.IOSequence], str
122
+ ]
123
+
124
+ NodeVariableType: TypeAlias = Union[
125
+ sequencetools.InOutSequenceTypes, "FusedVariable", str
126
+ ]
127
+
128
+ _default_variable: NodeVariableType = "Q"
129
+
130
+
131
+ class Keywords(set[str]):
132
+ """Set of keyword arguments used to describe and search for |Element| and |Node|
133
+ objects."""
134
+
135
+ device: Device | None
136
+
137
+ def __init__(self, *names: str):
138
+ self.device = None
139
+ self._check_keywords(names)
140
+ super().__init__(names)
141
+
142
+ def startswith(self, name: str) -> list[str]:
143
+ """Return a list of all keywords, starting with the given string.
144
+
145
+ >>> from hydpy.core.devicetools import Keywords
146
+ >>> keywords = Keywords("first_keyword", "second_keyword",
147
+ ... "keyword_3", "keyword_4",
148
+ ... "keyboard")
149
+ >>> keywords.startswith("keyword")
150
+ ['keyword_3', 'keyword_4']
151
+ """
152
+ return sorted(keyword for keyword in self if keyword.startswith(name))
153
+
154
+ def endswith(self, name: str) -> list[str]:
155
+ """Return a list of all keywords ending with the given string.
156
+
157
+ >>> from hydpy.core.devicetools import Keywords
158
+ >>> keywords = Keywords("first_keyword", "second_keyword",
159
+ ... "keyword_3", "keyword_4",
160
+ ... "keyboard")
161
+ >>> keywords.endswith("keyword")
162
+ ['first_keyword', 'second_keyword']
163
+ """
164
+ return sorted(keyword for keyword in self if keyword.endswith(name))
165
+
166
+ def contains(self, name: str) -> list[str]:
167
+ """Return a list of all keywords containing the given string.
168
+
169
+ >>> from hydpy.core.devicetools import Keywords
170
+ >>> keywords = Keywords("first_keyword", "second_keyword",
171
+ ... "keyword_3", "keyword_4",
172
+ ... "keyboard")
173
+ >>> keywords.contains("keyword")
174
+ ['first_keyword', 'keyword_3', 'keyword_4', 'second_keyword']
175
+ """
176
+ return sorted(keyword for keyword in self if name in keyword)
177
+
178
+ def _check_keywords(self, names: Iterable[str]) -> None:
179
+ for name in names:
180
+ try:
181
+ objecttools.valid_variable_identifier(name)
182
+ except ValueError:
183
+ objecttools.augment_excmessage(
184
+ f"While trying to add the keyword `{name}` to device "
185
+ f"{objecttools.devicename(self.device)}"
186
+ )
187
+
188
+ def update(self, *names: str) -> None: # type: ignore[override]
189
+ """Before updating, the given names are checked to be valid variable
190
+ identifiers.
191
+
192
+ >>> from hydpy.core.devicetools import Keywords
193
+ >>> keywords = Keywords("first_keyword", "second_keyword",
194
+ ... "keyword_3", "keyword_4",
195
+ ... "keyboard")
196
+ >>> keywords.update("test_1", "test 2") # doctest: +ELLIPSIS
197
+ Traceback (most recent call last):
198
+ ...
199
+ ValueError: While trying to add the keyword `test 2` to device ?, the \
200
+ following error occurred: The given name string `test 2` does not define a valid \
201
+ variable identifier. ...
202
+
203
+ Note that even the first string (`test1`) is not added due to the second one
204
+ (`test 2`) being invalid.
205
+
206
+ >>> keywords
207
+ Keywords("first_keyword", "keyboard", "keyword_3", "keyword_4",
208
+ "second_keyword")
209
+
210
+ After correcting the second string, everything works fine:
211
+
212
+ >>> keywords.update("test_1", "test_2")
213
+ >>> keywords
214
+ Keywords("first_keyword", "keyboard", "keyword_3", "keyword_4",
215
+ "second_keyword", "test_1", "test_2")
216
+ """
217
+ _names = [str(name) for name in names]
218
+ self._check_keywords(_names)
219
+ super().update(_names)
220
+
221
+ def add(self, name: Any) -> None:
222
+ """Before adding a new name, it is checked to be a valid variable identifier.
223
+
224
+ >>> from hydpy.core.devicetools import Keywords
225
+ >>> keywords = Keywords("first_keyword", "second_keyword",
226
+ ... "keyword_3", "keyword_4",
227
+ ... "keyboard")
228
+ >>> keywords.add("1_test") # doctest: +ELLIPSIS
229
+ Traceback (most recent call last):
230
+ ...
231
+ ValueError: While trying to add the keyword `1_test` to device ?, the \
232
+ following error occurred: The given name string `1_test` does not define a valid \
233
+ variable identifier. ...
234
+
235
+ >>> keywords
236
+ Keywords("first_keyword", "keyboard", "keyword_3", "keyword_4",
237
+ "second_keyword")
238
+
239
+ After correcting the string, everything works fine:
240
+
241
+ >>> keywords.add("one_test")
242
+ >>> keywords
243
+ Keywords("first_keyword", "keyboard", "keyword_3", "keyword_4",
244
+ "one_test", "second_keyword")
245
+ """
246
+ self._check_keywords([str(name)])
247
+ super().add(str(name))
248
+
249
+ def __repr__(self) -> str:
250
+ with objecttools.repr_.preserve_strings(True):
251
+ return (
252
+ objecttools.assignrepr_values(sorted(self), "Keywords(", width=70) + ")"
253
+ )
254
+
255
+
256
+ _registry_fusedvariable: dict[str, FusedVariable] = {}
257
+
258
+
259
+ class FusedVariable:
260
+ """Combines |InputSequence|, |ReceiverSequence|, and |OutputSequence| subclasses of
261
+ different models dealing with the same property in a single variable.
262
+
263
+ Class |FusedVariable| is one possible type of property |Node.variable| of class
264
+ |Node|. We need it in some *HydPy* projects where the involved models not only
265
+ pass runoff to each other but also share other types of data. Each
266
+ project-specific |FusedVariable| object serves as a "meta-type", indicating which
267
+ input and output sequences of the different models correlate and are thus
268
+ connectable.
269
+
270
+ Using class |FusedVariable| is easiest to explain by a concrete example. Assume we
271
+ use |conv_nn| to interpolate the air temperature for a specific location. We use
272
+ this temperature as input to an |meteo_temp_io| model, which passes it to an
273
+ |evap_ret_fao56| model, which requires this and other meteorological data to
274
+ calculate potential evapotranspiration. Further, we pass the estimated potential
275
+ evapotranspiration as input to |lland_dd| for calculating the actual
276
+ evapotranspiration, which receives it through a submodel instance of |evap_ret_io|.
277
+ Hence, we need to connect the output sequence
278
+ |evap_fluxes.MeanReferenceEvapotranspiration| of |evap_ret_fao56| with the input
279
+ sequence |evap_inputs.ReferenceEvapotranspiration| of |evap_ret_io|.
280
+
281
+ ToDo: This example needs to be updated. Today one could directly use
282
+ |evap_ret_fao56| as a submodel of |lland_dd|. However, it still demonstrates
283
+ the relevant connection mechanisms correctly.
284
+
285
+ Additionally, |lland_dd| requires temperature data itself for modelling snow
286
+ processes, introducing the problem that we need to use the same data (the output of
287
+ |conv_nn|) as the input of two differently named input sequences
288
+ (|meteo_inputs.Temperature| and |lland_inputs.TemL| for |meteo_temp_io| and
289
+ |lland_dd|, respectively).
290
+
291
+ We need to create two |FusedVariable| objects, for our concrete example. `E`
292
+ combines |evap_fluxes.MeanReferenceEvapotranspiration| and
293
+ |evap_inputs.ReferenceEvapotranspiration| and `T` combines
294
+ |meteo_inputs.Temperature| and |lland_inputs.TemL| (for convenience, we import
295
+ their globally available aliases):
296
+
297
+ >>> from hydpy import FusedVariable
298
+ >>> from hydpy.aliases import (
299
+ ... evap_inputs_ReferenceEvapotranspiration, meteo_inputs_Temperature,
300
+ ... lland_inputs_TemL, evap_fluxes_MeanReferenceEvapotranspiration)
301
+ >>> E = FusedVariable("E", evap_inputs_ReferenceEvapotranspiration,
302
+ ... evap_fluxes_MeanReferenceEvapotranspiration)
303
+ >>> T = FusedVariable("T", meteo_inputs_Temperature, lland_inputs_TemL)
304
+
305
+ Now we can construct the network:
306
+
307
+ * Node `t1` handles the original temperature data and serves as the input node to
308
+ element `conv`. We define the (arbitrarily selected) string `Temp` to be its
309
+ variable.
310
+ * Node `e` receives the potential evapotranspiration calculated by element `evap`
311
+ and passes it to element `lland`. Node `e` thus receives the fused variable `E`.
312
+ * Node `t2` handles the interpolated temperature and serves as the outlet node of
313
+ element `conv` and the input node to elements `evap` and `lland`. Node `t2`
314
+ thus receives the fused variable `T`.
315
+
316
+ >>> from hydpy import Node, Element
317
+ >>> t1 = Node("t1", variable="Temp")
318
+ >>> t2 = Node("t2", variable=T)
319
+ >>> e = Node("e", variable=E)
320
+ >>> conv = Element("element_conv", inlets=t1, outlets=t2)
321
+ >>> evap = Element("element_evap", inputs=t2, outputs=e)
322
+ >>> lland = Element("element_lland", inputs=(t2, e), outlets="node_q")
323
+
324
+ Now we can prepare the different model objects and assign them to their
325
+ corresponding elements (note that parameters |conv_control.InputCoordinates| and
326
+ |conv_control.OutputCoordinates| of |conv_nn| first require information on the
327
+ location of the relevant nodes):
328
+
329
+ >>> from hydpy import prepare_model
330
+ >>> model_conv = prepare_model("conv_nn")
331
+ >>> model_conv.parameters.control.inputcoordinates(t1=(0, 0))
332
+ >>> model_conv.parameters.control.outputcoordinates(t2=(1, 1))
333
+ >>> model_conv.parameters.control.maxnmbinputs(1)
334
+ >>> model_conv.parameters.update()
335
+ >>> conv.model = model_conv
336
+ >>> model = prepare_model("evap_ret_fao56")
337
+ >>> model.tempmodel = prepare_model("meteo_temp_io")
338
+ >>> evap.model = model
339
+ >>> model = prepare_model("lland_dd")
340
+ >>> model.aetmodel = prepare_model("evap_aet_minhas")
341
+ >>> model.aetmodel.petmodel = prepare_model("evap_ret_io")
342
+ >>> lland.model = model
343
+
344
+ We assign a temperature value to node `t1`:
345
+
346
+ >>> t1.sequences.sim = -273.15
347
+
348
+ Model |conv_nn| can now perform a simulation step and pass its output to node `t2`:
349
+
350
+ >>> conv.model.simulate(0)
351
+ >>> t2.sequences.sim
352
+ sim(-273.15)
353
+
354
+ Without further configuration, |evap_ret_fao56| cannot perform any simulation
355
+ steps. Hence, we just call its |Model.load_data| method to show that the input
356
+ sequence |meteo_inputs.Temperature| of its submodel is well connected to the |Sim|
357
+ sequence of node `t2` and receives the correct data:
358
+
359
+ >>> evap.model.load_data(0)
360
+ >>> evap.model.tempmodel.sequences.inputs.temperature
361
+ temperature(-273.15)
362
+
363
+ The output sequence |evap_fluxes.MeanReferenceEvapotranspiration| is also well
364
+ connected. A call to method |Model.update_outputs| passes its (manually set) value
365
+ to node `e`, respectively:
366
+
367
+ >>> evap.model.sequences.fluxes.meanreferenceevapotranspiration = 999.9
368
+ >>> evap.model.update_outputs()
369
+ >>> e.sequences.sim
370
+ sim(999.9)
371
+
372
+ Finally, both input sequences |lland_inputs.TemL| and
373
+ |evap_inputs.ReferenceEvapotranspiration| receive the current values of nodes `t2`
374
+ and `e`:
375
+
376
+ >>> lland.model.load_data(0)
377
+ >>> lland.model.sequences.inputs.teml
378
+ teml(-273.15)
379
+ >>> lland.model.aetmodel.petmodel.sequences.inputs.referenceevapotranspiration
380
+ referenceevapotranspiration(999.9)
381
+
382
+ When defining fused variables, class |FusedVariable| performs some registration
383
+ behind the scenes, similar to what classes |Node| and |Element| do. Again, the
384
+ name works as the identifier, and we force the same fused variable to exist only
385
+ once, even when defined in different selection files repeatedly. Hence, when we
386
+ repeat the definition from above, we get the same object:
387
+
388
+ >>> Test = FusedVariable("T", meteo_inputs_Temperature, lland_inputs_TemL)
389
+ >>> T is Test
390
+ True
391
+
392
+ Changing the member sequences of an existing fused variable is not allowed:
393
+
394
+ >>> from hydpy.aliases import hland_inputs_T
395
+ >>> FusedVariable("T", hland_inputs_T, lland_inputs_TemL)
396
+ Traceback (most recent call last):
397
+ ...
398
+ ValueError: The sequences combined by a FusedVariable object cannot be changed. \
399
+ The already defined sequences of the fused variable `T` are `lland_inputs_TemL and \
400
+ meteo_inputs_Temperature` instead of `hland_inputs_T and lland_inputs_TemL`. Keep in \
401
+ mind, that `name` is the unique identifier for fused variable instances.
402
+
403
+ Defining additional fused variables with the same member sequences is not advisable
404
+ but is allowed:
405
+
406
+ >>> Temp = FusedVariable("Temp", meteo_inputs_Temperature, lland_inputs_TemL)
407
+ >>> T is Temp
408
+ False
409
+
410
+ To get an overview of the existing fused variables, call method
411
+ |FusedVariable.get_registry|:
412
+
413
+ >>> len(FusedVariable.get_registry())
414
+ 3
415
+
416
+ Principally, you can clear the registry via method |FusedVariable.clear_registry|,
417
+ but remember it does not remove |FusedVariable| objects from the running process
418
+ being otherwise referenced:
419
+
420
+ >>> FusedVariable.clear_registry()
421
+ >>> FusedVariable.get_registry()
422
+ ()
423
+ >>> t2.variable
424
+ FusedVariable("T", lland_inputs_TemL, meteo_inputs_Temperature)
425
+
426
+ .. testsetup::
427
+
428
+ >>> Node.clear_all()
429
+ >>> Element.clear_all()
430
+ """
431
+
432
+ _name: str
433
+ _aliases: tuple[str, ...]
434
+ _variables: tuple[sequencetools.InOutSequenceTypes, ...]
435
+ _alias2variable: dict[str, sequencetools.InOutSequenceTypes]
436
+
437
+ def __new__(
438
+ cls, name: str, *sequences: sequencetools.InOutSequenceTypes
439
+ ) -> FusedVariable:
440
+ self = super().__new__(cls)
441
+ aliases = tuple(hydpy.sequence2alias[seq] for seq in sequences)
442
+ idxs = numpy.argsort(aliases)
443
+ aliases = tuple(aliases[idx] for idx in idxs)
444
+ variables = tuple(sequences[idx] for idx in idxs)
445
+ fusedvariable = _registry_fusedvariable.get(name)
446
+ if fusedvariable:
447
+ if variables == fusedvariable._variables:
448
+ return fusedvariable
449
+ raise ValueError(
450
+ f"The sequences combined by a {type(self).__name__} object cannot be "
451
+ f"changed. The already defined sequences of the fused variable "
452
+ f"`{name}` are `{objecttools.enumeration(fusedvariable._aliases)}` "
453
+ f"instead of `{objecttools.enumeration(aliases)}`. Keep in mind, "
454
+ f"that `name` is the unique identifier for fused variable instances."
455
+ )
456
+ self._name = name
457
+ self._aliases = aliases
458
+ self._variables = variables
459
+ _registry_fusedvariable[name] = self
460
+ self._alias2variable = dict(zip(self._aliases, self._variables))
461
+ return self
462
+
463
+ @classmethod
464
+ def get_registry(cls) -> tuple[FusedVariable, ...]:
465
+ """Get all |FusedVariable| objects initialised so far."""
466
+ return tuple(_registry_fusedvariable.values())
467
+
468
+ @classmethod
469
+ def clear_registry(cls) -> None:
470
+ """Clear the registry from all |FusedVariable| objects initialised so far.
471
+
472
+ Use this method only for good reasons!
473
+ """
474
+ return _registry_fusedvariable.clear()
475
+
476
+ def __iter__(self) -> Iterator[sequencetools.InOutSequenceTypes]:
477
+ yield from self._variables
478
+
479
+ def __contains__(self, item: object) -> bool:
480
+ sqt = sequencetools
481
+ if isinstance(item, (sqt.LinkSequence, sqt.InputSequence, sqt.OutputSequence)):
482
+ item = type(item)
483
+ return item in self._variables
484
+
485
+ def __str__(self) -> str:
486
+ return self._name
487
+
488
+ def __repr__(self) -> str:
489
+ return f'FusedVariable("{self._name}", {", ".join(self._aliases)})'
490
+
491
+
492
+ class Devices(Generic[TypeDevice]):
493
+ """Base class for class |Elements| and class |Nodes|.
494
+
495
+ The following features are common to class |Nodes| and class |Elements|. We
496
+ arbitrarily select class |Nodes| for all examples.
497
+
498
+ To initialise a |Nodes| collection, pass a variable number of |str| or |Node|
499
+ objects. Strings are used to create new or query already existing nodes
500
+ automatically:
501
+
502
+ >>> from hydpy import Node, Nodes
503
+ >>> nodes = Nodes("na",
504
+ ... Node("nb", variable="W"),
505
+ ... Node("nc", keywords=("group_a", "group_1")),
506
+ ... Node("nd", keywords=("group_a", "group_2")),
507
+ ... Node("ne", keywords=("group_b", "group_1")))
508
+
509
+ |Nodes| instances are containers supporting attribute and item access. You can
510
+ access each node directly by its name:
511
+
512
+ >>> nodes.na
513
+ Node("na", variable="Q")
514
+ >>> nodes["na"]
515
+ Node("na", variable="Q")
516
+
517
+ In many situations, a |Nodes| instance contains a single node only. One can query
518
+ such a single node using zero as the index for convenience:
519
+
520
+ >>> Nodes("na")[0]
521
+ Node("na", variable="Q")
522
+
523
+ Other number-based indexed are not allowed:
524
+
525
+ >>> Nodes("na", "nb")[1]
526
+ Traceback (most recent call last):
527
+ ...
528
+ KeyError: 'Indexing with other numbers than `0` is not supported but `1` is given.'
529
+
530
+ An automatic check prevents unexpected results when applying zero-based indexing on
531
+ |Nodes| instances containing multiple nodes:
532
+
533
+ >>> Nodes("na", "nb")[0]
534
+ Traceback (most recent call last):
535
+ ...
536
+ KeyError: 'Indexing with `0` is only safe for Node handlers containing a single \
537
+ Node.'
538
+
539
+ Wrong node names result in the following error messages:
540
+
541
+ >>> nodes.wrong
542
+ Traceback (most recent call last):
543
+ ...
544
+ AttributeError: The selected Nodes object has neither a `wrong` attribute nor \
545
+ does it handle a Node object with name or keyword `wrong`, which could be returned.
546
+ >>> nodes["wrong"]
547
+ Traceback (most recent call last):
548
+ ...
549
+ KeyError: 'No node named `wrong` available.'
550
+
551
+ As explained in more detail in the documentation on property |Device.keywords|, you
552
+ can also use the keywords of the individual nodes to query the relevant ones:
553
+
554
+ >>> nodes.group_a
555
+ Nodes("nc", "nd")
556
+
557
+ You can remove nodes both via the attribute and item syntax:
558
+
559
+ >>> "na" in nodes
560
+ True
561
+ >>> del nodes.na
562
+ >>> "na" in nodes
563
+ False
564
+ >>> del nodes.na
565
+ Traceback (most recent call last):
566
+ ...
567
+ AttributeError: The actual Nodes object does not handle a Node object named `na` \
568
+ which could be removed, and deleting other attributes is not supported.
569
+
570
+ >>> nodes.add_device("na")
571
+ >>> del nodes["na"]
572
+ >>> del nodes["na"]
573
+ Traceback (most recent call last):
574
+ ...
575
+ KeyError: 'No node named `na` available.'
576
+
577
+ However, as shown by the following example, setting devices via attribute
578
+ assignment or item assignment could result in inconsistencies and is thus not
579
+ allowed (see method |Devices.add_device| instead):
580
+
581
+ >>> nodes.NF = Node("nf")
582
+ Traceback (most recent call last):
583
+ ...
584
+ AttributeError: Setting attributes of Nodes objects could result in confusion \
585
+ whether a new attribute should be handled as a Node object or as a "normal" attribute \
586
+ and is thus not support, hence `NF` is rejected.
587
+ >>> nodes["NF"] = Node("nf")
588
+ Traceback (most recent call last):
589
+ ...
590
+ TypeError: 'Nodes' object does not support item assignment
591
+
592
+ |Nodes| instances support iteration:
593
+
594
+ >>> len(nodes)
595
+ 4
596
+ >>> for node in nodes:
597
+ ... print(node.name, end=",")
598
+ nb,nc,nd,ne,
599
+
600
+ The binary operators `+`, `+=`, `-`, and `-=` support adding and removing single
601
+ devices or groups of devices:
602
+
603
+ >>> nodes
604
+ Nodes("nb", "nc", "nd", "ne")
605
+ >>> nodes - Node("nc")
606
+ Nodes("nb", "nd", "ne")
607
+
608
+ Nodes("nb", "nc", "nd", "ne")
609
+ >>> nodes -= Nodes("nc", "ne")
610
+ >>> nodes
611
+ Nodes("nb", "nd")
612
+
613
+ >>> nodes + "nc"
614
+ Nodes("nb", "nc", "nd")
615
+ >>> nodes
616
+ Nodes("nb", "nd")
617
+ >>> nodes += ("nc", Node("ne"))
618
+ >>> nodes
619
+ Nodes("nb", "nc", "nd", "ne")
620
+
621
+ Attempts to add already existing or to remove non-existing devices do no harm:
622
+
623
+ >>> nodes
624
+ Nodes("nb", "nc", "nd", "ne")
625
+ >>> nodes + ("nc", "ne")
626
+ Nodes("nb", "nc", "nd", "ne")
627
+ >>> nodes - Node("na")
628
+ Nodes("nb", "nc", "nd", "ne")
629
+
630
+ Comparisons are supported, with "x < y" being |True| if "x" is a subset of "y":
631
+
632
+ >>> subgroup = Nodes("nc", "ne")
633
+ >>> subgroup < nodes, nodes < subgroup, nodes < nodes
634
+ (True, False, False)
635
+ >>> subgroup <= nodes, nodes <= subgroup, nodes <= nodes
636
+ (True, False, True)
637
+ >>> subgroup == nodes, nodes == subgroup, nodes == nodes, nodes == "nodes"
638
+ (False, False, True, False)
639
+ >>> subgroup != nodes, nodes != subgroup, nodes != nodes, nodes != "nodes"
640
+ (True, True, False, True)
641
+ >>> subgroup >= nodes, nodes >= subgroup, nodes >= nodes
642
+ (False, True, True)
643
+ >>> subgroup > nodes, nodes > subgroup, nodes > nodes
644
+ (False, True, False)
645
+
646
+ Class |Nodes| supports the `in` operator both for |str| and |Node| objects and
647
+ generally returns |False| for other types:
648
+
649
+ >>> "na" in nodes
650
+ False
651
+ >>> "nb" in nodes
652
+ True
653
+ >>> Node("na") in nodes
654
+ False
655
+ >>> Node("nb") in nodes
656
+ True
657
+ >>> 1 in nodes
658
+ False
659
+
660
+ Passing wrong arguments to the constructor of class |Node| results in errors like
661
+ the following:
662
+
663
+ >>> from hydpy import Element
664
+ >>> Nodes("na", Element("ea"))
665
+ Traceback (most recent call last):
666
+ ...
667
+ TypeError: While trying to initialise a `Nodes` object, the following error \
668
+ occurred: The given (sub)value `Element("ea")` is not an instance of the following \
669
+ classes: Node and str.
670
+ """
671
+
672
+ _mutable: bool
673
+ _name2device: dict[str, TypeDevice]
674
+ _shadowed_keywords: set[str]
675
+
676
+ def __new__(
677
+ cls, *values: MayNonerable2[TypeDevice, str], mutable: bool = True
678
+ ) -> Devices[Any]:
679
+ if len(values) == 1 and isinstance(values[0], Devices):
680
+ return values[0]
681
+ self = super().__new__(cls)
682
+ setattr_ = super().__setattr__
683
+ setattr_(self, "_mutable", mutable)
684
+ setattr_(self, "_name2device", {})
685
+ setattr_(self, "_shadowed_keywords", set())
686
+ contentclass = self.get_contentclass()
687
+ try:
688
+ for value in objecttools.extract(
689
+ values, types_=(contentclass, str), skip=True
690
+ ):
691
+ self.add_device(value, force=True)
692
+ except BaseException:
693
+ objecttools.augment_excmessage(
694
+ f"While trying to initialise a `{type(self).__name__}` object"
695
+ )
696
+ return self
697
+
698
+ @staticmethod
699
+ @abc.abstractmethod
700
+ def get_contentclass() -> type[TypeDevice]:
701
+ """To be overridden."""
702
+
703
+ def add_device(self, device: TypeDevice | str, force: bool = False) -> None:
704
+ """Add the given |Node| or |Element| object to the actual |Nodes| or |Elements|
705
+ object.
706
+
707
+ You can pass either a string or a device:
708
+
709
+ >>> from hydpy import Nodes
710
+ >>> nodes = Nodes()
711
+ >>> nodes.add_device("old_node")
712
+ >>> nodes
713
+ Nodes("old_node")
714
+ >>> nodes.add_device("new_node")
715
+ >>> nodes
716
+ Nodes("new_node", "old_node")
717
+
718
+ Method |Devices.add_device| is disabled for immutable |Nodes| and |Elements|
719
+ objects by default:
720
+
721
+ >>> nodes._mutable = False
722
+ >>> nodes.add_device("newest_node")
723
+ Traceback (most recent call last):
724
+ ...
725
+ RuntimeError: While trying to add the device `newest_node` to a Nodes object, \
726
+ the following error occurred: Adding devices to immutable Nodes objects is not allowed.
727
+
728
+ Use parameter `force` to override this safety mechanism if necessary:
729
+
730
+ >>> nodes.add_device("newest_node", force=True)
731
+ >>> nodes
732
+ Nodes("new_node", "newest_node", "old_node")
733
+ """
734
+ try:
735
+ if force or self._mutable:
736
+ _device = self.get_contentclass()(device)
737
+ self._name2device[_device.name] = _device
738
+ _id2devices[_device][id(self)] = cast(Devices[Device], self) # ToDo
739
+ else:
740
+ raise RuntimeError(
741
+ f"Adding devices to immutable {type(self).__name__} objects is "
742
+ f"not allowed."
743
+ )
744
+ except BaseException:
745
+ objecttools.augment_excmessage(
746
+ f"While trying to add the device `{device}` to a "
747
+ f"{type(self).__name__} object"
748
+ )
749
+
750
+ def remove_device(self, device: TypeDevice | str, force: bool = False) -> None:
751
+ """Remove the given |Node| or |Element| object from the actual |Nodes| or
752
+ |Elements| object.
753
+
754
+ You can pass either a string or a device:
755
+
756
+ >>> from hydpy import Node, Nodes
757
+ >>> nodes = Nodes("node_x", "node_y")
758
+ >>> node_x, node_y = nodes
759
+ >>> nodes.remove_device(Node("node_y"))
760
+ >>> nodes
761
+ Nodes("node_x")
762
+ >>> nodes.remove_device(Node("node_x"))
763
+ >>> nodes
764
+ Nodes()
765
+ >>> nodes.remove_device(Node("node_z"))
766
+ Traceback (most recent call last):
767
+ ...
768
+ ValueError: While trying to remove the device `node_z` from a Nodes object, \
769
+ the following error occurred: The actual Nodes object does not handle such a device.
770
+
771
+ Method |Devices.remove_device| is disabled for immutable |Nodes| and |Elements|
772
+ objects by default:
773
+
774
+ >>> nodes.add_device(node_x)
775
+ >>> nodes._mutable = False
776
+ >>> nodes.remove_device("node_x")
777
+ Traceback (most recent call last):
778
+ ...
779
+ RuntimeError: While trying to remove the device `node_x` from a Nodes object, \
780
+ the following error occurred: Removing devices from immutable Nodes objects is not \
781
+ allowed.
782
+ >>> nodes
783
+ Nodes("node_x")
784
+
785
+ Use parameter `force` to override this safety mechanism if necessary:
786
+
787
+ >>> nodes.remove_device("node_x", force=True)
788
+ >>> nodes
789
+ Nodes()
790
+ """
791
+ try:
792
+ if force or self._mutable:
793
+ _device = self.get_contentclass()(device)
794
+ try:
795
+ del self._name2device[_device.name]
796
+ except KeyError:
797
+ raise ValueError(
798
+ f"The actual {type(self).__name__} object does not handle "
799
+ f"such a device."
800
+ ) from None
801
+ del _id2devices[_device][id(self)]
802
+ else:
803
+ raise RuntimeError(
804
+ f"Removing devices from immutable {type(self).__name__} objects "
805
+ f"is not allowed."
806
+ )
807
+ except BaseException:
808
+ objecttools.augment_excmessage(
809
+ f"While trying to remove the device `{device}` from a "
810
+ f"{type(self).__name__} object"
811
+ )
812
+
813
+ @property
814
+ def names(self) -> tuple[str, ...]:
815
+ """A sorted tuple of the names of the handled devices.
816
+
817
+ >>> from hydpy import Nodes
818
+ >>> Nodes("a", "c", "b").names
819
+ ('a', 'b', 'c')
820
+ """
821
+ return tuple(device.name for device in self)
822
+
823
+ @property
824
+ def devices(self) -> tuple[TypeDevice, ...]:
825
+ """A tuple of the handled devices sorted by the device names.
826
+
827
+ >>> from hydpy import Nodes
828
+ >>> for node in Nodes("a", "c", "b").devices:
829
+ ... print(repr(node))
830
+ Node("a", variable="Q")
831
+ Node("b", variable="Q")
832
+ Node("c", variable="Q")
833
+ """
834
+ return tuple(device for device in self)
835
+
836
+ @property
837
+ def keywords(self) -> set[str]:
838
+ """A set of all keywords of all handled devices.
839
+
840
+ In addition to attribute access via device names, |Nodes| and |Elements|
841
+ objects allow for attribute access via keywords, allowing for an efficient
842
+ search of certain groups of devices. Let us use the example from above, where
843
+ the nodes `na` and `nb` have no keywords, but each of the other three nodes
844
+ both belongs to either `group_a` or `group_b` and `group_1` or `group_2`:
845
+
846
+ >>> from hydpy import Node, Nodes
847
+ >>> nodes = Nodes("na",
848
+ ... Node("nb", variable="W"),
849
+ ... Node("nc", keywords=("group_a", "group_1")),
850
+ ... Node("nd", keywords=("group_a", "group_2")),
851
+ ... Node("ne", keywords=("group_b", "group_1")))
852
+ >>> nodes
853
+ Nodes("na", "nb", "nc", "nd", "ne")
854
+ >>> sorted(nodes.keywords)
855
+ ['group_1', 'group_2', 'group_a', 'group_b']
856
+
857
+ If you are interested in inspecting all devices belonging to `group_a`, select
858
+ them via this keyword:
859
+
860
+ >>> subgroup = nodes.group_1
861
+ >>> subgroup
862
+ Nodes("nc", "ne")
863
+
864
+ You can further restrict the search by also selecting the devices belonging to
865
+ `group_b`, which holds only for node "e", in the discussed example:
866
+
867
+ >>> subsubgroup = subgroup.group_b
868
+ >>> subsubgroup
869
+ Node("ne", variable="Q",
870
+ keywords=["group_1", "group_b"])
871
+
872
+ Note that the keywords already used for building a device subgroup are not
873
+ informative anymore (as they hold for each device) and are thus not shown
874
+ anymore:
875
+
876
+ >>> sorted(subgroup.keywords)
877
+ ['group_a', 'group_b']
878
+
879
+ The latter might be confusing if you intend to work with a device subgroup for
880
+ a longer time. After copying the subgroup, all keywords of the contained
881
+ devices are available again:
882
+
883
+ >>> from copy import copy
884
+ >>> newgroup = copy(subgroup)
885
+ >>> sorted(newgroup.keywords)
886
+ ['group_1', 'group_a', 'group_b']
887
+ """
888
+ return {
889
+ keyword
890
+ for device in self
891
+ for keyword in device.keywords
892
+ if keyword not in self._shadowed_keywords
893
+ }
894
+
895
+ def search_keywords(self: TypeDevices, *keywords: str) -> TypeDevices:
896
+ """Search for all devices handling at least one of the given keywords and
897
+ return them.
898
+
899
+ >>> from hydpy import Node, Nodes
900
+ >>> nodes = Nodes("na",
901
+ ... Node("nb", variable="W"),
902
+ ... Node("nc", keywords=("group_a", "group_1")),
903
+ ... Node("nd", keywords=("group_a", "group_2")),
904
+ ... Node("ne", keywords=("group_b", "group_1")))
905
+ >>> nodes.search_keywords("group_c")
906
+ Nodes()
907
+ >>> nodes.search_keywords("group_a")
908
+ Nodes("nc", "nd")
909
+ >>> nodes.search_keywords("group_a", "group_1")
910
+ Nodes("nc", "nd", "ne")
911
+
912
+ .. testsetup::
913
+
914
+ >>> Node.clear_all()
915
+ """
916
+ keywords_ = set(keywords)
917
+ return type(self)(
918
+ *(device for device in self if keywords_.intersection(device.keywords))
919
+ )
920
+
921
+ def copy(self: TypeDevices) -> TypeDevices:
922
+ """Return a shallow copy of the actual |Nodes| or |Elements| object.
923
+
924
+ Method |Devices.copy| returns a semi-flat copy of |Nodes| or |Elements| objects
925
+ due to their devices being not copyable:
926
+
927
+ >>> from hydpy import Nodes
928
+ >>> old = Nodes("x", "y")
929
+ >>> import copy
930
+ >>> new = copy.copy(old)
931
+ >>> new == old
932
+ True
933
+ >>> new is old
934
+ False
935
+ >>> new.devices is old.devices
936
+ False
937
+ >>> new.x is new.x
938
+ True
939
+
940
+ Changing the |Device.name| of a device is recognised both by the original and
941
+ the copied collection objects:
942
+
943
+ >>> new.x.name = "z"
944
+ >>> old.z
945
+ Node("z", variable="Q")
946
+ >>> new.z
947
+ Node("z", variable="Q")
948
+
949
+ Deep copying is permitted due to the above reason:
950
+
951
+ >>> copy.deepcopy(old)
952
+ Traceback (most recent call last):
953
+ ...
954
+ NotImplementedError: Deep copying of Nodes objects is not supported, as it \
955
+ would require to make deep copies of the Node objects themselves, which is in \
956
+ conflict with using their names as identifiers.
957
+ """
958
+ # pylint: disable=protected-access
959
+ new = type(self)()
960
+ vars(new).update(vars(self))
961
+ new._name2device = copy.copy(self._name2device)
962
+ new._shadowed_keywords.clear()
963
+ for device in self:
964
+ _id2devices[device][id(new)] = new
965
+ return new
966
+
967
+ def intersection(self: TypeDevices, *other: TypeDevices) -> TypeDevices:
968
+ """Return the intersection with the given |Devices| object.
969
+
970
+ >>> from hydpy import Node, Nodes
971
+ >>> nodes1 = Nodes("na", "nb", "nc")
972
+ >>> nodes2 = Nodes("na", "nc", "nd")
973
+ >>> nodes1.intersection(*nodes2)
974
+ Nodes("na", "nc")
975
+
976
+ .. testsetup::
977
+
978
+ >>> Node.clear_all()
979
+ """
980
+ return type(self)(*set(self).intersection(set(other)))
981
+
982
+ __copy__ = copy
983
+
984
+ def __deepcopy__(self, dict_: NoReturn) -> NoReturn:
985
+ classname = type(self).__name__
986
+ raise NotImplementedError(
987
+ f"Deep copying of {classname} objects is not supported, as it would "
988
+ f"require to make deep copies of the {classname[:-1]} objects themselves, "
989
+ f"which is in conflict with using their names as identifiers."
990
+ )
991
+
992
+ def __select_devices_by_keyword(self: TypeDevices, name: str) -> TypeDevices:
993
+ # pylint: disable=protected-access
994
+ devices = type(self)(*(device for device in self if name in device.keywords))
995
+ devices._shadowed_keywords = self._shadowed_keywords.copy()
996
+ devices._shadowed_keywords.add(name)
997
+ return devices
998
+
999
+ def __getattr__(self: TypeDevices, name: str) -> TypeDevice | TypeDevices:
1000
+ if name in self._name2device:
1001
+ return cast(TypeDevice, self._name2device[name]) # ToDo
1002
+ _devices = self.__select_devices_by_keyword(name)
1003
+ if len(_devices) > 1:
1004
+ return _devices
1005
+ if len(_devices) == 1:
1006
+ return cast(TypeDevice, _devices.devices[0]) # ToDo
1007
+ raise AttributeError(
1008
+ f"The selected {type(self).__name__} object has neither a `{name}` "
1009
+ f"attribute nor does it handle a {self.get_contentclass().__name__} "
1010
+ f"object with name or keyword `{name}`, which could be returned."
1011
+ )
1012
+
1013
+ def __setattr__(self, name: str, value: object) -> None:
1014
+ if hasattr(self, name):
1015
+ super().__setattr__(name, value)
1016
+ else:
1017
+ classname = type(self).__name__
1018
+ raise AttributeError(
1019
+ f"Setting attributes of {classname} objects could result in confusion "
1020
+ f"whether a new attribute should be handled as a {classname[:-1]} "
1021
+ f'object or as a "normal" attribute and is thus not support, hence '
1022
+ f"`{name}` is rejected."
1023
+ )
1024
+
1025
+ def __delattr__(self, name: str) -> None:
1026
+ try:
1027
+ self.remove_device(name)
1028
+ except ValueError:
1029
+ raise AttributeError(
1030
+ f"The actual {type(self).__name__} object does not handle a "
1031
+ f"{self.get_contentclass().__name__} object named `{name}` which "
1032
+ f"could be removed, and deleting other attributes is not supported."
1033
+ ) from None
1034
+
1035
+ def __getitem__(self, name: Literal[0] | str) -> TypeDevice:
1036
+ if name == 0:
1037
+ devices = tuple(self._name2device.values())
1038
+ if len(devices) == 1:
1039
+ return tuple(devices)[0]
1040
+ device = self.get_contentclass().__name__
1041
+ raise KeyError(
1042
+ f"Indexing with `0` is only safe for {device} handlers containing "
1043
+ f"a single {device}."
1044
+ ) from None
1045
+ try:
1046
+ return self._name2device[name]
1047
+ except KeyError:
1048
+ if isinstance(name, int): # type: ignore[unreachable]
1049
+ raise KeyError(
1050
+ f"Indexing with other numbers than `0` is not supported but "
1051
+ f"`{name}` is given."
1052
+ ) from None
1053
+ device = self.get_contentclass().__name__.lower()
1054
+ raise KeyError(f"No {device} named `{name}` available.") from None
1055
+
1056
+ def __setitem__(self, name: str, value: TypeDevice) -> NoReturn:
1057
+ raise TypeError(
1058
+ f"'{type(self).__name__}' object does not support item assignment"
1059
+ )
1060
+
1061
+ def __delitem__(self, name: str) -> None:
1062
+ try:
1063
+ del self._name2device[name]
1064
+ except KeyError:
1065
+ device = self.get_contentclass().__name__.lower()
1066
+ raise KeyError(f"No {device} named `{name}` available.") from None
1067
+
1068
+ def __iter__(self) -> Iterator[TypeDevice]:
1069
+ for _, device in sorted(self._name2device.items()):
1070
+ yield device
1071
+
1072
+ def __contains__(self, value: object) -> bool:
1073
+ cls = self.get_contentclass()
1074
+ if isinstance(value, cls):
1075
+ return value.name in self._name2device
1076
+ if isinstance(value, str):
1077
+ return value in self._name2device
1078
+ return False
1079
+
1080
+ def __len__(self) -> int:
1081
+ return len(self._name2device)
1082
+
1083
+ def __add__(self: TypeDevices, other: Mayberable2[TypeDevice, str]) -> TypeDevices:
1084
+ new = copy.copy(self)
1085
+ new._mutable = True
1086
+ for device in type(self)(other):
1087
+ new.add_device(device)
1088
+ return new
1089
+
1090
+ def __iadd__(self: TypeDevices, other: Mayberable2[TypeDevice, str]) -> TypeDevices:
1091
+ for device in type(self)(other):
1092
+ self.add_device(device)
1093
+ return self
1094
+
1095
+ def __sub__(self: TypeDevices, other: Mayberable2[TypeDevice, str]) -> TypeDevices:
1096
+ new = copy.copy(self)
1097
+ new._mutable = True
1098
+ for device in type(self)(other):
1099
+ try:
1100
+ new.remove_device(device)
1101
+ except ValueError:
1102
+ pass
1103
+ return new
1104
+
1105
+ def __isub__(self: TypeDevices, other: Mayberable2[TypeDevice, str]) -> TypeDevices:
1106
+ for device in type(self)(other):
1107
+ try:
1108
+ self.remove_device(device)
1109
+ except ValueError:
1110
+ pass
1111
+ return self
1112
+
1113
+ def __compare(self, other: object, func: Callable[[Any, Any], bool]) -> bool:
1114
+ if isinstance(other, type(self)):
1115
+ return func(set(self), set(other))
1116
+ return NotImplemented
1117
+
1118
+ def __lt__(self: TypeDevices, other: TypeDevices) -> bool:
1119
+ return self.__compare(other, operator.lt)
1120
+
1121
+ def __le__(self: TypeDevices, other: TypeDevices) -> bool:
1122
+ return self.__compare(other, operator.le)
1123
+
1124
+ def __eq__(self, other: object) -> bool:
1125
+ return self.__compare(other, operator.eq)
1126
+
1127
+ def __ne__(self, other: object) -> bool:
1128
+ return self.__compare(other, operator.ne)
1129
+
1130
+ def __ge__(self: TypeDevices, other: TypeDevices) -> bool:
1131
+ return self.__compare(other, operator.ge)
1132
+
1133
+ def __gt__(self: TypeDevices, other: TypeDevices) -> bool:
1134
+ return self.__compare(other, operator.gt)
1135
+
1136
+ def __repr__(self) -> str:
1137
+ return self.assignrepr("")
1138
+
1139
+ def assignrepr(self, prefix: str = "") -> str:
1140
+ """Return a |repr| string with a prefixed assignment."""
1141
+ with objecttools.repr_.preserve_strings(True):
1142
+ options = hydpy.pub.options
1143
+ with options.ellipsis(2, optional=True):
1144
+ prefix += f"{type(self).__name__}("
1145
+ repr_ = objecttools.assignrepr_values(self.names, prefix, width=70)
1146
+ return repr_ + ")"
1147
+
1148
+ def __dir__(self) -> list[str]:
1149
+ """
1150
+ >>> from hydpy import Node, Nodes
1151
+ >>> nodes = Nodes(Node("name1", keywords="keyword1"),
1152
+ ... Node("name2", keywords=("keyword2a", "keyword2a")))
1153
+ >>> sorted(set(dir(nodes)) - set(object.__dir__(nodes)))
1154
+ ['keyword1', 'keyword2a', 'name1', 'name2']
1155
+ """
1156
+ return (
1157
+ cast(list[str], super().__dir__()) + list(self.names) + list(self.keywords)
1158
+ )
1159
+
1160
+
1161
+ class Nodes(Devices["Node"]):
1162
+ """A container class for handling |Node| objects.
1163
+
1164
+ For the general usage of |Nodes| objects, please see the documentation on its base
1165
+ class |Devices|.
1166
+
1167
+ Class |Nodes| provides the additional keyword argument `defaultvariable`. Use it
1168
+ to temporarily change the default variable "Q" to another value during the
1169
+ initialisation of new |Node| objects:
1170
+
1171
+ >>> from hydpy import Nodes
1172
+ >>> a1, t2 = Nodes("a1", "a2", defaultvariable="A")
1173
+ >>> a1
1174
+ Node("a1", variable="A")
1175
+
1176
+ Be aware that changing the default variable does not affect already existing nodes:
1177
+
1178
+ >>> a1, b1 = Nodes("a1", "b1", defaultvariable="B")
1179
+ >>> a1
1180
+ Node("a1", variable="A")
1181
+ >>> b1
1182
+ Node("b1", variable="B")
1183
+ """
1184
+
1185
+ def __new__(
1186
+ cls,
1187
+ *values: MayNonerable2[Node, str],
1188
+ mutable: bool = True,
1189
+ defaultvariable: NodeVariableType = "Q",
1190
+ ) -> Nodes:
1191
+ global _default_variable
1192
+ _default_variable_copy = _default_variable
1193
+ try:
1194
+ _default_variable = defaultvariable
1195
+ return super().__new__( # type: ignore[return-value]
1196
+ cls, *values, mutable=mutable
1197
+ )
1198
+ finally:
1199
+ _default_variable = _default_variable_copy
1200
+
1201
+ @staticmethod
1202
+ def get_contentclass() -> type[Node]:
1203
+ """Return class |Node|."""
1204
+ return Node
1205
+
1206
+ @printtools.print_progress
1207
+ def prepare_allseries(self, allocate_ram: bool = True, jit: bool = False) -> None:
1208
+ """Call method |Node.prepare_allseries| of all handled |Node| objects."""
1209
+ for node in printtools.progressbar(self):
1210
+ node.prepare_allseries(allocate_ram=allocate_ram, jit=jit)
1211
+
1212
+ @printtools.print_progress
1213
+ def prepare_simseries(
1214
+ self, allocate_ram: bool = True, read_jit: bool = False, write_jit: bool = False
1215
+ ) -> None:
1216
+ """Call method |Node.prepare_simseries| of all handled |Node| objects."""
1217
+ for node in printtools.progressbar(self):
1218
+ node.prepare_simseries(
1219
+ allocate_ram=allocate_ram, read_jit=read_jit, write_jit=write_jit
1220
+ )
1221
+
1222
+ @printtools.print_progress
1223
+ def prepare_obsseries(
1224
+ self, allocate_ram: bool = True, read_jit: bool = False, write_jit: bool = False
1225
+ ) -> None:
1226
+ """Call method |Node.prepare_obsseries| of all handled |Node| objects."""
1227
+ for node in printtools.progressbar(self):
1228
+ node.prepare_obsseries(
1229
+ allocate_ram=allocate_ram, read_jit=read_jit, write_jit=write_jit
1230
+ )
1231
+
1232
+ @printtools.print_progress
1233
+ def load_allseries(self) -> None:
1234
+ """Call methods |Nodes.load_simseries| and |Nodes.load_obsseries|."""
1235
+ self.load_simseries()
1236
+ self.load_obsseries()
1237
+
1238
+ @printtools.print_progress
1239
+ def load_simseries(self) -> None:
1240
+ """Call method |IOSequence.load_series| of all |Sim| objects with an activated
1241
+ |IOSequence.memoryflag|."""
1242
+ self.__load_nodeseries("sim")
1243
+
1244
+ @printtools.print_progress
1245
+ def load_obsseries(self) -> None:
1246
+ """Call method |IOSequence.load_series| of all |Obs| objects with an activated
1247
+ |IOSequence.memoryflag|."""
1248
+ self.__load_nodeseries("obs")
1249
+
1250
+ @netcdftools.add_netcdfreading
1251
+ def __load_nodeseries(self, seqname: str) -> None:
1252
+ for node in printtools.progressbar(self):
1253
+ node.sequences[seqname].load_series()
1254
+
1255
+ @printtools.print_progress
1256
+ def save_allseries(self) -> None:
1257
+ """Call methods |Nodes.save_simseries| and |Nodes.save_obsseries|."""
1258
+ self.save_simseries()
1259
+ self.save_obsseries()
1260
+
1261
+ @printtools.print_progress
1262
+ def save_simseries(self) -> None:
1263
+ """Call method |IOSequence.save_series| of all |Sim| objects with an activated
1264
+ |IOSequence.memoryflag|."""
1265
+ self.__save_nodeseries("sim")
1266
+
1267
+ @printtools.print_progress
1268
+ def save_obsseries(self) -> None:
1269
+ """Call method |IOSequence.save_series| of all |Obs| objects with an activated
1270
+ |IOSequence.memoryflag|."""
1271
+ self.__save_nodeseries("obs")
1272
+
1273
+ @netcdftools.add_netcdfwriting
1274
+ def __save_nodeseries(self, seqname: str) -> None:
1275
+ for node in printtools.progressbar(self):
1276
+ seq = node.sequences[seqname]
1277
+ if seq.ramflag:
1278
+ seq.save_series()
1279
+
1280
+ @property
1281
+ def variables(self) -> set[NodeVariableType]:
1282
+ """Return a set of the variables of all handled |Node| objects.
1283
+
1284
+ >>> from hydpy import Node, Nodes
1285
+ >>> nodes = Nodes(Node("x1"),
1286
+ ... Node("x2", variable="Q"),
1287
+ ... Node("x3", variable="H"))
1288
+ >>> sorted(nodes.variables)
1289
+ ['H', 'Q']
1290
+ """
1291
+ return {node.variable for node in self}
1292
+
1293
+
1294
+ class Elements(Devices["Element"]):
1295
+ """A container for handling |Element| objects.
1296
+
1297
+ For the general usage of |Elements| objects, please see the documentation on its
1298
+ base class |Devices|.
1299
+ """
1300
+
1301
+ @staticmethod
1302
+ def get_contentclass() -> type[Element]:
1303
+ """Return class |Element|."""
1304
+ return Element
1305
+
1306
+ @property
1307
+ def collectives(self) -> dict[str | None, tuple[Element, ...]]:
1308
+ """The names and members of all currently relevant collectives.
1309
+
1310
+ Note that all |Element| instances not belonging to any |Element.collective| are
1311
+ returned as a separate group:
1312
+
1313
+ >>> from hydpy import Element, Elements
1314
+ >>> Elements().collectives
1315
+ {}
1316
+ >>> for group, elements in Elements(
1317
+ ... Element("a"), Element("b1", collective="b"), Element("c"),
1318
+ ... Element("d1", collective="d"), Element("b2", collective="b")
1319
+ ... ).collectives.items():
1320
+ ... print(group, [e.name for e in elements])
1321
+ None ['a', 'c']
1322
+ b ['b1', 'b2']
1323
+ d ['d1']
1324
+ """
1325
+ collectives = collections.defaultdict(list)
1326
+ for element in self:
1327
+ collectives[element.collective].append(element)
1328
+ return {c: tuple(e) for c, e in collectives.items()}
1329
+
1330
+ def unite_collectives(self) -> Elements:
1331
+ """Create overarching elements for all original elements that belong to a
1332
+ collective.
1333
+
1334
+ All elements of the same |Element.collective| must be handled as one entity
1335
+ during simulation. A typical use case is that individual elements describe
1336
+ different channels of a large river network, and all of them must be handled
1337
+ simultaneously by a single routing model instance to account for backwater
1338
+ effects. We create such an example by combining instances of |musk_classic|
1339
+ (for "hydrological" routing neglecting backwater effects) and |sw1d_channel|
1340
+ (for "hydrodynamic" routing considering backwater effects).
1341
+
1342
+ First, we create a |FusedVariable| object for connecting the inlets and outlets
1343
+ of |musk_classic| and |sw1d_channel|:
1344
+
1345
+ >>> from hydpy import FusedVariable
1346
+ >>> from hydpy.aliases import (musk_inlets_Q, sw1d_inlets_LongQ,
1347
+ ... musk_outlets_Q, sw1d_outlets_LongQ)
1348
+ >>> q = FusedVariable("Q", musk_inlets_Q, sw1d_inlets_LongQ,
1349
+ ... musk_outlets_Q, sw1d_outlets_LongQ)
1350
+
1351
+ The spatial setting is more concise than realistic and consists of four
1352
+ channels. Channel `A` discharges into channel `B`, which discharges into
1353
+ channel `C`, which discharges into channel `D`. We neglect backwater effects
1354
+ within channels `A` and `D`. Hence we do not need to associate them with a
1355
+ collective and |musk_classic| becomes an appropriate choice. Channel `B` and
1356
+ `C` are represented by separate collectives. Hence, the setting could account
1357
+ for backwater effects within both channels but not between them. Channel `B`
1358
+ consists only of a single subchannel (represented by element `b`), while
1359
+ channel `C` consists of two subchannels (represented by elements `c1` and
1360
+ `c2`):
1361
+
1362
+ >>> from hydpy import Element, Elements, Nodes
1363
+ >>> q_a, q_a_b, q_b_c1, q_c1_c2, q_c2_d, q_d = Nodes(
1364
+ ... "q_a", "q_a_b", "q_b_c1", "q_c1_c2", "q_c2_d", "q_d",
1365
+ ... defaultvariable=q)
1366
+ >>> e_a = Element("e_a", inlets=q_a, outlets=q_a_b)
1367
+ >>> e_b = Element("e_b", collective="B", inlets=q_a_b, outlets=q_b_c1)
1368
+ >>> e_c1 = Element("e_c1", collective="C", inlets=q_b_c1, outlets=q_c1_c2)
1369
+ >>> e_c2 = Element("e_c2", collective="C", inlets=q_c1_c2, outlets=q_c2_d)
1370
+ >>> e_d = Element("e_d", inlets=q_c2_d, outlets=q_d)
1371
+ >>> elements = Elements(e_a, e_b, e_c1, e_c2, e_d)
1372
+
1373
+ Method |Elements.unite_collectives| expects only those elements belonging to a
1374
+ collective to come with a ready |Model| instance. So we only need to prepare
1375
+ |sw1d_channel| instances for elements `b`, `c1`, and `c2`, including the
1376
+ required submodels:
1377
+
1378
+ >>> from hydpy import prepare_model, pub
1379
+ >>> pub.timegrids = "2000-01-01", "2000-01-02", "1d"
1380
+ >>> for element in (e_b, e_c1, e_c2):
1381
+ ... channel = prepare_model("sw1d_channel")
1382
+ ... channel.parameters.control.nmbsegments(1)
1383
+ ... add_storage = channel.add_storagemodel_v1
1384
+ ... with add_storage("sw1d_storage", position=0, update=False):
1385
+ ... pass
1386
+ ... if element in (e_b, e_c1):
1387
+ ... with channel.add_routingmodel_v1("sw1d_q_in", position=0):
1388
+ ... pass
1389
+ ... if element is e_c1:
1390
+ ... with channel.add_routingmodel_v2("sw1d_lias", position=1):
1391
+ ... lengthupstream(1.0)
1392
+ ... lengthdownstream(1.0)
1393
+ ... if element in (e_b, e_c2):
1394
+ ... with channel.add_routingmodel_v3("sw1d_weir_out", position=1):
1395
+ ... pass
1396
+ ... element.model = channel
1397
+
1398
+ Based on the defined five elements, method |Elements.unite_collectives| returns
1399
+ four:
1400
+
1401
+ >>> elements.unite_collectives()
1402
+ Elements("B", "C", "e_a", "e_d")
1403
+
1404
+ The returned elements `a` and `d` are the same as those defined initially, as
1405
+ they do not belong to any collectives:
1406
+
1407
+ >>> collectives = elements.unite_collectives()
1408
+ >>> collectives.e_a is e_a
1409
+ True
1410
+
1411
+ However, the elements `B` and `C` are new. `B` replaces element `b`, and `C`
1412
+ replaces elements `c1` and `c2`. Both handle instances of |sw1d_network|,
1413
+ which is the suitable model for connecting and applying the submodels of
1414
+ |sw1d_channel| (see |ModelCoupler|):
1415
+
1416
+ >>> e_b, e_c = collectives.B, collectives.C
1417
+ >>> e_b.model.name
1418
+ 'sw1d_network'
1419
+
1420
+ The new element `B` has the same inlet and outlet nodes as `b`:
1421
+
1422
+ >>> e_b
1423
+ Element("B",
1424
+ inlets="q_a_b",
1425
+ outlets="q_b_c1")
1426
+
1427
+ However, `C` adopts both outlet nodes of `c1` and `c2` but only the inlet node
1428
+ of `c1`, which is relevant for clarifying the |HydPy.deviceorder| during
1429
+ simulations:
1430
+
1431
+ >>> e_c
1432
+ Element("C",
1433
+ inlets="q_b_c1",
1434
+ outlets=["q_c1_c2", "q_c2_d"])
1435
+
1436
+ The following technical checks ensure the underlying coupling mechanisms
1437
+ actually worked:
1438
+
1439
+ >>> assert e_b.model.storagemodels.number == 1
1440
+ >>> assert e_c.model.storagemodels.number == 2
1441
+ >>> assert e_b.model.routingmodels.number == 2
1442
+ >>> assert e_c.model.routingmodels.number == 3
1443
+ >>> assert e_c.model.routingmodels[1].routingmodelsdownstream[0] is \
1444
+ e_c.model.routingmodels[2]
1445
+
1446
+ |Elements.unite_collectives| raises the following error if an element belonging
1447
+ to a collective does not handle a |Model| instance:
1448
+
1449
+ >>> e_d.collective = "D"
1450
+ >>> elements.unite_collectives()
1451
+ Traceback (most recent call last):
1452
+ ...
1453
+ hydpy.core.exceptiontools.AttributeNotReady: While trying to unite the \
1454
+ elements belonging to collective `D`, the following error occurred: The model object \
1455
+ of element `e_d` has been requested but not been prepared so far.
1456
+
1457
+ |Elements.unite_collectives| raises the following error if an element belonging
1458
+ to a collective does handle an unsuitable |Model| instance:
1459
+
1460
+ >>> e_d.model = prepare_model("musk_classic")
1461
+ >>> elements.unite_collectives()
1462
+ Traceback (most recent call last):
1463
+ ...
1464
+ TypeError: While trying to unite the elements belonging to collective `D`, \
1465
+ the following error occurred: Model `musk_classic` of element `e_d` does not provide \
1466
+ a function for coupling models that belong to the same collective.
1467
+
1468
+ .. testsetup::
1469
+
1470
+ >>> del pub.timegrids
1471
+ >>> FusedVariable.clear_registry()
1472
+ """
1473
+ elements: list[Element] = []
1474
+ for collective, subelements in self.collectives.items():
1475
+ if collective is None:
1476
+ elements.extend(subelements)
1477
+ else:
1478
+ try:
1479
+ outlets = {
1480
+ outlet
1481
+ for subelement in subelements
1482
+ for outlet in subelement.outlets
1483
+ }
1484
+ inlets = {
1485
+ inlet
1486
+ for subelement in subelements
1487
+ for inlet in subelement.inlets
1488
+ if inlet not in outlets
1489
+ }
1490
+ outputs = {
1491
+ output
1492
+ for subelement in subelements
1493
+ for output in subelement.outputs
1494
+ }
1495
+ inputs = {
1496
+ input_
1497
+ for subelement in subelements
1498
+ for input_ in subelement.inputs
1499
+ if input_ not in outputs
1500
+ }
1501
+ _registry[Element].pop(collective, None)
1502
+ newelement = Element(
1503
+ collective,
1504
+ inlets=inlets,
1505
+ outlets=outlets,
1506
+ inputs=inputs,
1507
+ outputs=outputs,
1508
+ )
1509
+ del _selection[Element][collective]
1510
+ elements.append(newelement)
1511
+ if (couple_models := subelements[0].model.couple_models) is None:
1512
+ raise TypeError(
1513
+ f"Model {objecttools.elementphrase(subelements[0].model)} "
1514
+ f"does not provide a function for coupling models that "
1515
+ f"belong to the same collective."
1516
+ )
1517
+ nodes = outlets.union(inlets).union(inputs).union(outputs)
1518
+ newelement.model = couple_models(
1519
+ nodes=Nodes(nodes), elements=Elements(subelements)
1520
+ )
1521
+ newelement.model.update_parameters()
1522
+ except BaseException:
1523
+ objecttools.augment_excmessage(
1524
+ f"While trying to unite the elements belonging to collective "
1525
+ f"`{collective}`"
1526
+ )
1527
+ return Elements(elements)
1528
+
1529
+ @printtools.print_progress
1530
+ def prepare_models(self) -> None:
1531
+ """Call method |Element.prepare_model| of all handle |Element| objects.
1532
+
1533
+ We show, based on the `HydPy-H-Lahn` example project, that method
1534
+ |Element.init_model| prepares the |Model| objects of all elements, including
1535
+ building the required connections and updating the derived parameters:
1536
+
1537
+ >>> from hydpy.core.testtools import prepare_full_example_1
1538
+ >>> prepare_full_example_1()
1539
+ >>> from hydpy import attrready, HydPy, pub, TestIO
1540
+ >>> with TestIO():
1541
+ ... hp = HydPy("HydPy-H-Lahn")
1542
+ ... pub.timegrids = "1996-01-01", "1996-02-01", "1d"
1543
+ ... hp.prepare_network()
1544
+ ... hp.prepare_models()
1545
+ >>> hp.elements.land_dill_assl.model.parameters.derived.dt
1546
+ dt(0.000833)
1547
+
1548
+ Wrong control files result in error messages like the following:
1549
+
1550
+ >>> with TestIO():
1551
+ ... with open("HydPy-H-Lahn/control/default/land_dill_assl.py",
1552
+ ... "a") as file_:
1553
+ ... _ = file_.write("zonetype(-1)")
1554
+ ... hp.prepare_models() # doctest: +ELLIPSIS
1555
+ Traceback (most recent call last):
1556
+ ...
1557
+ ValueError: While trying to initialise the model object of element \
1558
+ `land_dill_assl`, the following error occurred: While trying to load the control file \
1559
+ `...land_dill_assl.py`, the following error occurred: At least one value of parameter \
1560
+ `zonetype` of element `?` is not valid.
1561
+
1562
+ By default, missing control files result in exceptions:
1563
+
1564
+ >>> del hp.elements.land_dill_assl.model
1565
+ >>> import os
1566
+ >>> with TestIO():
1567
+ ... os.remove("HydPy-H-Lahn/control/default/land_dill_assl.py")
1568
+ ... hp.prepare_models() # doctest: +ELLIPSIS
1569
+ Traceback (most recent call last):
1570
+ ...
1571
+ FileNotFoundError: While trying to initialise the model object of element \
1572
+ `land_dill_assl`, the following error occurred: While trying to load the control file \
1573
+ `...land_dill_assl.py`, the following error occurred: ...
1574
+ >>> attrready(hp.elements.land_dill_assl, "model")
1575
+ False
1576
+
1577
+ When building new, still incomplete *HydPy* projects, this behaviour can be
1578
+ annoying. After setting the option |Options.warnmissingcontrolfile| to
1579
+ |False|, missing control files result in a warning only:
1580
+
1581
+ >>> with TestIO():
1582
+ ... with pub.options.warnmissingcontrolfile(True):
1583
+ ... hp.prepare_models()
1584
+ Traceback (most recent call last):
1585
+ ...
1586
+ UserWarning: Due to a missing or no accessible control file, no model could \
1587
+ be initialised for element `land_dill_assl`
1588
+ >>> attrready(hp.elements.land_dill_assl, "model")
1589
+ False
1590
+ """
1591
+ try:
1592
+ for element in printtools.progressbar(self):
1593
+ element.prepare_model(clear_registry=False)
1594
+ finally:
1595
+ hydpy.pub.controlmanager.clear_registry()
1596
+
1597
+ def init_models(self) -> None:
1598
+ """Deprecated: use method |Elements.prepare_models| instead.
1599
+
1600
+ >>> from hydpy import Elements
1601
+ >>> from unittest import mock
1602
+ >>> with mock.patch.object(Elements, "prepare_models") as mocked:
1603
+ ... elements = Elements()
1604
+ ... elements.init_models()
1605
+ Traceback (most recent call last):
1606
+ ...
1607
+ hydpy.core.exceptiontools.HydPyDeprecationWarning: Method `init_models` of \
1608
+ class `Elements` is deprecated. Use method `prepare_models` instead.
1609
+ >>> mocked.call_args_list
1610
+ [call()]
1611
+ """
1612
+ self.prepare_models()
1613
+ warnings.warn(
1614
+ "Method `init_models` of class `Elements` is deprecated. Use method "
1615
+ "`prepare_models` instead.",
1616
+ exceptiontools.HydPyDeprecationWarning,
1617
+ )
1618
+
1619
+ @printtools.print_progress
1620
+ def save_controls(
1621
+ self,
1622
+ parameterstep: timetools.PeriodConstrArg | None = None,
1623
+ simulationstep: timetools.PeriodConstrArg | None = None,
1624
+ auxfiler: auxfiletools.Auxfiler | None = None,
1625
+ ) -> None:
1626
+ """Save the control parameters of the |Model| object handled by each |Element|
1627
+ object and eventually the ones handled by the given |Auxfiler| object."""
1628
+ if auxfiler:
1629
+ auxfiler.write(parameterstep=parameterstep, simulationstep=simulationstep)
1630
+ for element in printtools.progressbar(self):
1631
+ element.model.save_controls(
1632
+ parameterstep=parameterstep,
1633
+ simulationstep=simulationstep,
1634
+ auxfiler=auxfiler,
1635
+ )
1636
+
1637
+ @printtools.print_progress
1638
+ def update_parameters(self) -> None:
1639
+ """Update the derived parameters of all models managed by the respective
1640
+ elements."""
1641
+ for element in printtools.progressbar(self):
1642
+ element.model.update_parameters()
1643
+
1644
+ @printtools.print_progress
1645
+ def load_conditions(self) -> None:
1646
+ """Load the initial conditions of the |Model| object handled by each |Element|
1647
+ object."""
1648
+ for element in printtools.progressbar(self):
1649
+ element.model.load_conditions()
1650
+
1651
+ @printtools.print_progress
1652
+ def save_conditions(self) -> None:
1653
+ """Save the calculated conditions of the |Model| object handled by each
1654
+ |Element| object."""
1655
+ for element in printtools.progressbar(self):
1656
+ element.model.save_conditions()
1657
+
1658
+ def trim_conditions(self) -> None:
1659
+ """Call method |Model.trim_conditions| of the |Model| object handled by each
1660
+ |Element| object."""
1661
+ for element in self:
1662
+ element.model.trim_conditions()
1663
+
1664
+ def reset_conditions(self) -> None:
1665
+ """Call method |Model.reset_conditions| of the |Model| object handled by each
1666
+ |Element| object."""
1667
+ for element in self:
1668
+ element.model.reset_conditions()
1669
+
1670
+ @property
1671
+ def conditions(self) -> Conditions:
1672
+ """A nested dictionary that contains the values of all |ConditionSequence|
1673
+ objects of all currently handled models.
1674
+
1675
+ See the documentation on property |HydPy.conditions| for further information.
1676
+ """
1677
+ return {element.name: element.model.conditions for element in self}
1678
+
1679
+ @conditions.setter
1680
+ def conditions(self, conditions: Conditions) -> None:
1681
+ for name, subconditions in conditions.items():
1682
+ element = getattr(self, name)
1683
+ element.model.conditions = subconditions
1684
+
1685
+ @printtools.print_progress
1686
+ def prepare_allseries(self, allocate_ram: bool = True, jit: bool = False) -> None:
1687
+ """Call method |Element.prepare_allseries| of all handled |Element| objects."""
1688
+ for element in printtools.progressbar(self):
1689
+ element.prepare_allseries(allocate_ram=allocate_ram, jit=jit)
1690
+
1691
+ @printtools.print_progress
1692
+ def prepare_inputseries(
1693
+ self, allocate_ram: bool = True, read_jit: bool = False, write_jit: bool = False
1694
+ ) -> None:
1695
+ """Call method |Element.prepare_inputseries| of all handled |Element|
1696
+ objects."""
1697
+ for element in printtools.progressbar(self):
1698
+ element.prepare_inputseries(
1699
+ allocate_ram=allocate_ram, read_jit=read_jit, write_jit=write_jit
1700
+ )
1701
+
1702
+ @printtools.print_progress
1703
+ def prepare_factorseries(
1704
+ self, allocate_ram: bool = True, write_jit: bool = False
1705
+ ) -> None:
1706
+ """Call method |Element.prepare_factorseries| of all handled |Element|
1707
+ objects."""
1708
+ for element in printtools.progressbar(self):
1709
+ element.prepare_factorseries(allocate_ram=allocate_ram, write_jit=write_jit)
1710
+
1711
+ @printtools.print_progress
1712
+ def prepare_fluxseries(
1713
+ self, allocate_ram: bool = True, write_jit: bool = False
1714
+ ) -> None:
1715
+ """Call method |Element.prepare_fluxseries| of all handled |Element| objects."""
1716
+ for element in printtools.progressbar(self):
1717
+ element.prepare_fluxseries(allocate_ram=allocate_ram, write_jit=write_jit)
1718
+
1719
+ @printtools.print_progress
1720
+ def prepare_stateseries(
1721
+ self, allocate_ram: bool = True, write_jit: bool = False
1722
+ ) -> None:
1723
+ """Call method |Element.prepare_stateseries| of all handled |Element|
1724
+ objects."""
1725
+ for element in printtools.progressbar(self):
1726
+ element.prepare_stateseries(allocate_ram=allocate_ram, write_jit=write_jit)
1727
+
1728
+ @printtools.print_progress
1729
+ @netcdftools.add_netcdfreading
1730
+ def load_allseries(self) -> None:
1731
+ """Call method |Element.load_inputseries| of all handled |Element| objects."""
1732
+ for element in printtools.progressbar(self):
1733
+ element.load_allseries()
1734
+
1735
+ @printtools.print_progress
1736
+ @netcdftools.add_netcdfreading
1737
+ def load_inputseries(self) -> None:
1738
+ """Call method |Element.load_inputseries| of all handled |Element| objects."""
1739
+ for element in printtools.progressbar(self):
1740
+ element.load_inputseries()
1741
+
1742
+ @printtools.print_progress
1743
+ @netcdftools.add_netcdfreading
1744
+ def load_factorseries(self) -> None:
1745
+ """Call method |Element.load_factorseries| of all handled |Element| objects."""
1746
+ for element in printtools.progressbar(self):
1747
+ element.load_factorseries()
1748
+
1749
+ @printtools.print_progress
1750
+ @netcdftools.add_netcdfreading
1751
+ def load_fluxseries(self) -> None:
1752
+ """Call method |Element.load_fluxseries| of all handled |Element| objects."""
1753
+ for element in printtools.progressbar(self):
1754
+ element.load_fluxseries()
1755
+
1756
+ @printtools.print_progress
1757
+ @netcdftools.add_netcdfreading
1758
+ def load_stateseries(self) -> None:
1759
+ """Call method |Element.load_stateseries| of all handled |Element| objects."""
1760
+ for element in printtools.progressbar(self):
1761
+ element.load_stateseries()
1762
+
1763
+ @printtools.print_progress
1764
+ @netcdftools.add_netcdfwriting
1765
+ def save_allseries(self) -> None:
1766
+ """Call method |Element.save_allseries| of all handled |Element| objects."""
1767
+ for element in printtools.progressbar(self):
1768
+ element.save_allseries()
1769
+
1770
+ @printtools.print_progress
1771
+ @netcdftools.add_netcdfwriting
1772
+ def save_inputseries(self) -> None:
1773
+ """Call method |Element.save_inputseries| of all handled |Element| objects."""
1774
+ for element in printtools.progressbar(self):
1775
+ element.save_inputseries()
1776
+
1777
+ @printtools.print_progress
1778
+ @netcdftools.add_netcdfwriting
1779
+ def save_factorseries(self) -> None:
1780
+ """Call method |Element.save_factorseries| of all handled |Element| objects."""
1781
+ for element in printtools.progressbar(self):
1782
+ element.save_factorseries()
1783
+
1784
+ @printtools.print_progress
1785
+ @netcdftools.add_netcdfwriting
1786
+ def save_fluxseries(self) -> None:
1787
+ """Call method |Element.save_fluxseries| of all handled |Element| objects."""
1788
+ for element in printtools.progressbar(self):
1789
+ element.save_fluxseries()
1790
+
1791
+ @printtools.print_progress
1792
+ @netcdftools.add_netcdfwriting
1793
+ def save_stateseries(self) -> None:
1794
+ """Call method |Element.save_stateseries| of all handled |Element| objects."""
1795
+ for element in printtools.progressbar(self):
1796
+ element.save_stateseries()
1797
+
1798
+
1799
+ class Device:
1800
+ """Base class for class |Element| and class |Node|."""
1801
+
1802
+ _name: str
1803
+ _keywords: Keywords
1804
+
1805
+ def __new__(cls, value: Device | str, *args: object, **kwargs: object) -> Device:
1806
+ # pylint: disable=unused-argument
1807
+ # required for consistincy with __init__
1808
+ name = str(value)
1809
+ cls.__check_name(name)
1810
+ try:
1811
+ self = _registry[cls][name]
1812
+ except KeyError:
1813
+ self = object.__new__(cls)
1814
+ self._name = name
1815
+ setattr(self, "new_instance", True)
1816
+ self._keywords = Keywords()
1817
+ self._keywords.device = self
1818
+ _id2devices[self] = {}
1819
+ _registry[cls][name] = self
1820
+ _selection[cls][name] = _registry[cls][name]
1821
+ return self
1822
+
1823
+ @classmethod
1824
+ @abc.abstractmethod
1825
+ def get_handlerclass(cls) -> type[Devices[Any]]:
1826
+ """To be overridden."""
1827
+
1828
+ @classmethod
1829
+ def query_all(cls) -> Devices[Self]:
1830
+ """Get all |Node| or |Element| objects initialised so far.
1831
+
1832
+ See the main documentation on module |devicetools| for further information.
1833
+ """
1834
+ return cls.get_handlerclass()(*_registry[cls].values())
1835
+
1836
+ @classmethod
1837
+ def extract_new(cls) -> Devices[Self]:
1838
+ """Gather all "new" |Node| or |Element| objects.
1839
+
1840
+ See the main documentation on module |devicetools| for further information.
1841
+ """
1842
+ devices = cls.get_handlerclass()(*_selection[cls])
1843
+ _selection[cls].clear()
1844
+ return devices
1845
+
1846
+ @classmethod
1847
+ def clear_all(cls) -> None:
1848
+ """Clear the registry from all initialised |Node| or |Element| objects.
1849
+
1850
+ See the main documentation on module |devicetools| for further information.
1851
+ """
1852
+ _selection[cls].clear()
1853
+ _registry[cls].clear()
1854
+
1855
+ @property
1856
+ def name(self) -> str:
1857
+ """Name of the actual |Node| or |Element| object.
1858
+
1859
+ Device names serve as identifiers, as explained in the main documentation on
1860
+ module |devicetools|. Hence, define them carefully:
1861
+
1862
+ >>> from hydpy import Node
1863
+ >>> Node.clear_all()
1864
+ >>> node1, node2 = Node("n1"), Node("n2")
1865
+ >>> node1 is Node("n1")
1866
+ True
1867
+ >>> node1 is Node("n2")
1868
+ False
1869
+
1870
+ Each device name must be a valid variable identifier (see function
1871
+ |valid_variable_identifier|) to allow for attribute access:
1872
+
1873
+ >>> from hydpy import Nodes
1874
+ >>> nodes = Nodes(node1, "n2")
1875
+ >>> nodes.n1
1876
+ Node("n1", variable="Q")
1877
+
1878
+ Invalid variable identifiers result in errors like the following:
1879
+
1880
+ >>> node3 = Node("n 3") # doctest: +ELLIPSIS
1881
+ Traceback (most recent call last):
1882
+ ...
1883
+ ValueError: While trying to initialize a `Node` object with value `n 3` of \
1884
+ type `str`, the following error occurred: The given name string `n 3` does not define \
1885
+ a valid variable identifier. ...
1886
+
1887
+ When you change the name of a device (only do this for a good reason), the
1888
+ corresponding keys of all related |Nodes| and |Elements| objects (as well as of
1889
+ the internal registry) change automatically:
1890
+
1891
+ >>> Node.query_all()
1892
+ Nodes("n1", "n2")
1893
+ >>> node1.name = "n1a"
1894
+ >>> nodes
1895
+ Nodes("n1a", "n2")
1896
+ >>> Node.query_all()
1897
+ Nodes("n1a", "n2")
1898
+ """
1899
+ return self._name
1900
+
1901
+ @name.setter
1902
+ def name(self, name: str) -> None:
1903
+ self.__check_name(name)
1904
+ for devices in tuple(_id2devices[self].values()):
1905
+ if hasattr(devices, self.name):
1906
+ del devices._name2device[self.name] # pylint: disable=protected-access
1907
+ del _registry[type(self)][self.name]
1908
+ self._name = name
1909
+ _registry[type(self)][self.name] = self
1910
+ for devices in tuple(_id2devices[self].values()):
1911
+ devices._name2device[self.name] = self # pylint: disable=protected-access
1912
+
1913
+ @classmethod
1914
+ def __check_name(cls, name: str) -> None:
1915
+ try:
1916
+ objecttools.valid_variable_identifier(name)
1917
+ except ValueError:
1918
+ objecttools.augment_excmessage(
1919
+ f"While trying to initialize a `{cls.__name__}` object with value "
1920
+ f"`{name}` of type `{type(name).__name__}`"
1921
+ )
1922
+
1923
+ def _get_keywords(self) -> Keywords:
1924
+ """Keywords describing the actual |Node| or |Element| object.
1925
+
1926
+ The keywords are contained within a |Keywords| object:
1927
+
1928
+ >>> from hydpy import Node
1929
+ >>> node = Node("n", keywords="word0")
1930
+ >>> node.keywords
1931
+ Keywords("word0")
1932
+
1933
+ Assigning new words does not overwrite already existing ones. You are allowed
1934
+ to add them individually or within iterable objects:
1935
+
1936
+ >>> node.keywords = "word1"
1937
+ >>> node.keywords = "word2", "word3"
1938
+ >>> node.keywords
1939
+ Keywords("word0", "word1", "word2", "word3")
1940
+
1941
+ Additionally, passing additional keywords to the constructor of class |Node| or
1942
+ |Element| works also fine:
1943
+
1944
+ >>> Node("n", keywords=("word3", "word4", "word5"))
1945
+ Node("n", variable="Q",
1946
+ keywords=["word0", "word1", "word2", "word3", "word4", "word5"])
1947
+
1948
+ You can delete all keywords at once:
1949
+
1950
+ >>> del node.keywords
1951
+ >>> node.keywords
1952
+ Keywords()
1953
+ """
1954
+ return self._keywords
1955
+
1956
+ def _set_keywords(self, keywords: MayNonerable1[str]) -> None:
1957
+ keywords = tuple(objecttools.extract(keywords, (str,), True))
1958
+ self._keywords.update(*keywords)
1959
+
1960
+ def _del_keywords(self) -> None:
1961
+ self._keywords.clear()
1962
+
1963
+ keywords = propertytools.Property(
1964
+ fget=_get_keywords, fset=_set_keywords, fdel=_del_keywords
1965
+ )
1966
+
1967
+ def __str__(self) -> str:
1968
+ return self.name
1969
+
1970
+
1971
+ class Node(Device):
1972
+ """Handles the data flow between |Element| objects.
1973
+
1974
+ |Node| objects always handle two sequences, a |Sim| object for simulated values and
1975
+ an |Obs| object for measured values:
1976
+
1977
+ >>> from hydpy import Node
1978
+ >>> node = Node("test")
1979
+ >>> for sequence in node.sequences:
1980
+ ... print(sequence)
1981
+ sim(0.0)
1982
+ obs(0.0)
1983
+
1984
+ Each node can handle an arbitrary number of "input" and "output" elements,
1985
+ available as instance attributes `entries` and `exits`, respectively:
1986
+
1987
+ >>> node.entries
1988
+ Elements()
1989
+ >>> node.exits
1990
+ Elements()
1991
+
1992
+ You cannot (or at least should not) add new elements manually:
1993
+
1994
+ >>> node.entries = "element" # doctest: +ELLIPSIS
1995
+ Traceback (most recent call last):
1996
+ ...
1997
+ AttributeError: ...
1998
+ >>> node.exits.add_device("element")
1999
+ Traceback (most recent call last):
2000
+ ...
2001
+ RuntimeError: While trying to add the device `element` to a Elements object, the \
2002
+ following error occurred: Adding devices to immutable Elements objects is not allowed.
2003
+
2004
+ Instead, see the documentation on class |Element| on how to connect |Node| and
2005
+ |Element| objects properly.
2006
+ """
2007
+
2008
+ masks = masktools.NodeMasks()
2009
+ sequences: sequencetools.NodeSequences
2010
+
2011
+ _entries: Elements
2012
+ _exits: Elements
2013
+ _variable: NodeVariableType
2014
+ _deploymode: DeployMode
2015
+
2016
+ def __init__(
2017
+ self,
2018
+ value: NodeConstrArg,
2019
+ variable: NodeVariableType | None = None,
2020
+ keywords: MayNonerable1[str] = None,
2021
+ ) -> None:
2022
+ # pylint: disable=unused-argument
2023
+ # required for consistincy with Device.__new__
2024
+ if hasattr(self, "new_instance"):
2025
+ if variable is None:
2026
+ self._variable = _default_variable
2027
+ else:
2028
+ self._variable = variable
2029
+ self._entries = Elements(None, mutable=False)
2030
+ self._exits = Elements(None, mutable=False)
2031
+ self.sequences = sequencetools.NodeSequences(self)
2032
+ self._deploymode = "newsim"
2033
+ self.__blackhole = pointerutils.Double(0.0)
2034
+ delattr(self, "new_instance")
2035
+ if (variable is not None) and (variable != self.variable):
2036
+ raise ValueError(
2037
+ f"The variable to be represented by a {type(self).__name__} instance "
2038
+ f"cannot be changed. The variable of node `{self.name}` is "
2039
+ f"`{self.variable}` instead of `{variable}`. Keep in mind, that "
2040
+ f"`name` is the unique identifier of node objects."
2041
+ )
2042
+ if keywords is not None:
2043
+ self.keywords = keywords
2044
+
2045
+ @classmethod
2046
+ def get_handlerclass(cls) -> type[Nodes]:
2047
+ """Return class |Nodes|."""
2048
+ return Nodes
2049
+
2050
+ @property
2051
+ def entries(self) -> Elements:
2052
+ """Group of |Element| objects which set the the simulated value of the |Node|
2053
+ object."""
2054
+ return self._entries
2055
+
2056
+ @property
2057
+ def exits(self) -> Elements:
2058
+ """Group of |Element| objects that query the simulated or observed value of
2059
+ the actual |Node| object."""
2060
+ return self._exits
2061
+
2062
+ @property
2063
+ def variable(self) -> NodeVariableType:
2064
+ """The variable handled by the actual |Node| object.
2065
+
2066
+ By default, we suppose that nodes route discharge:
2067
+
2068
+ >>> from hydpy import Node
2069
+ >>> node = Node("test1")
2070
+ >>> node.variable
2071
+ 'Q'
2072
+
2073
+ Each other string, as well as each |InputSequence| subclass, is acceptable (for
2074
+ further information, see the documentation on method |Model.connect|):
2075
+
2076
+ >>> Node("test2", variable="H")
2077
+ Node("test2", variable="H")
2078
+ >>> from hydpy.models.hland.hland_inputs import T
2079
+ >>> Node("test3", variable=T)
2080
+ Node("test3", variable=hland_inputs_T)
2081
+
2082
+ The last example above shows that the string representations of nodes handling
2083
+ "class variables" use the aliases importable from the top level of the *HydPy*
2084
+ package:
2085
+
2086
+ >>> from hydpy.aliases import hland_inputs_P
2087
+ >>> Node("test4", variable=hland_inputs_P)
2088
+ Node("test4", variable=hland_inputs_P)
2089
+
2090
+ For some complex *HydPy* projects, one may need to fall back on |FusedVariable|
2091
+ objects. The string representation then relies on the name of the fused
2092
+ variable:
2093
+
2094
+ >>> from hydpy import FusedVariable
2095
+ >>> from hydpy.aliases import lland_inputs_Nied
2096
+ >>> Precipitation = FusedVariable("Precip", hland_inputs_P, lland_inputs_Nied)
2097
+ >>> Node("test5", variable=Precipitation)
2098
+ Node("test5", variable=Precip)
2099
+
2100
+ To avoid confusion, one cannot change |Node.variable|:
2101
+
2102
+ >>> node.variable = "H" # doctest: +ELLIPSIS
2103
+ Traceback (most recent call last):
2104
+ ...
2105
+ AttributeError: ...
2106
+ >>> Node("test1", variable="H")
2107
+ Traceback (most recent call last):
2108
+ ...
2109
+ ValueError: The variable to be represented by a Node instance cannot be \
2110
+ changed. The variable of node `test1` is `Q` instead of `H`. Keep in mind, that \
2111
+ `name` is the unique identifier of node objects.
2112
+ """
2113
+ return self._variable
2114
+
2115
+ @property
2116
+ def deploymode(self) -> DeployMode:
2117
+ """Defines the kind of information a node offers its exit elements, eventually,
2118
+ its entry elements.
2119
+
2120
+ *HydPy* supports the following modes:
2121
+
2122
+ * newsim: Deploy the simulated values calculated just recently. `newsim` is
2123
+ the default mode, used, for example, when a node receives a discharge value
2124
+ from an upstream element and passes it to the downstream element directly.
2125
+ * obs: Deploy observed values instead of simulated values. The node still
2126
+ receives the simulated values from its upstream element(s). However, it
2127
+ deploys values to its downstream element(s), which are defined externally.
2128
+ Usually, these values are observations made available within a time series
2129
+ file. See the documentation on module |sequencetools| for further
2130
+ information on file specifications.
2131
+ * oldsim: Similar to mode `obs`. However, it is usually applied when a node
2132
+ is supposed to deploy simulated values that have been calculated in a
2133
+ previous simulation run and stored in a sequence file.
2134
+ * obs_newsim: Combination of mode `obs` and `newsim`. Mode `obs_newsim`
2135
+ gives priority to the provision of observation values. New simulation
2136
+ values serve as a replacement for missing observed values.
2137
+ * obs_oldsim: Combination of mode `obs` and `oldsim`. Mode `obs_oldsim`
2138
+ gives priority to the provision of observation values. Old simulation
2139
+ values serve as a replacement for missing observed values.
2140
+ * obs_bi: Similar to the `obs` mode but triggers "bidirectional" deployment.
2141
+ All bidirectional modes only apply if the upstream element(s) do not
2142
+ calculate data for but expect from their downstream nodes. A typical
2143
+ example is using discharge measurements as lower boundary conditions for a
2144
+ hydrodynamical flood routing method.
2145
+ * oldsim_bi: The bidirectional version of the `oldsim` mode.
2146
+ * obs_oldsim_bi: The bidirectional version of the `obs_oldsim` mode.
2147
+
2148
+ One relevant difference between modes `obs` and `oldsim` is that the external
2149
+ values are either handled by the `obs` or the `sim` sequence object. Hence,
2150
+ if you select the `oldsim` mode, the values of the upstream elements calculated
2151
+ within the current simulation are not available (e.g. for parameter calibration)
2152
+ after the simulation finishes.
2153
+
2154
+ Please refer to the documentation on method |HydPy.simulate| of class |HydPy|,
2155
+ which provides some application examples.
2156
+
2157
+ >>> from hydpy import Node
2158
+ >>> node = Node("test")
2159
+ >>> node.deploymode
2160
+ 'newsim'
2161
+ >>> node.deploymode = "obs"
2162
+ >>> node.deploymode
2163
+ 'obs'
2164
+ >>> node.deploymode = "oldsim"
2165
+ >>> node.deploymode
2166
+ 'oldsim'
2167
+ >>> node.deploymode = "obs_newsim"
2168
+ >>> node.deploymode
2169
+ 'obs_newsim'
2170
+ >>> node.deploymode = "obs_oldsim"
2171
+ >>> node.deploymode
2172
+ 'obs_oldsim'
2173
+ >>> node.deploymode = "oldsim_bi"
2174
+ >>> node.deploymode
2175
+ 'oldsim_bi'
2176
+ >>> node.deploymode = "obs_bi"
2177
+ >>> node.deploymode
2178
+ 'obs_bi'
2179
+ >>> node.deploymode = "obs_oldsim_bi"
2180
+ >>> node.deploymode
2181
+ 'obs_oldsim_bi'
2182
+ >>> node.deploymode = "newsim"
2183
+ >>> node.deploymode
2184
+ 'newsim'
2185
+ >>> node.deploymode = "oldobs"
2186
+ Traceback (most recent call last):
2187
+ ...
2188
+ ValueError: When trying to set the routing mode of node `test`, the value \
2189
+ `oldobs` was given, but only the following values are allowed: `newsim`, `oldsim`, \
2190
+ `obs`, `obs_newsim`, `obs_oldsim`, `obs_bi.`, `oldsim_bi`, and `obs_oldsim_bi`.
2191
+ """
2192
+ return self._deploymode
2193
+
2194
+ @deploymode.setter
2195
+ def deploymode(self, value: DeployMode) -> None:
2196
+ def _assert_never(v: Never) -> NoReturn:
2197
+ raise ValueError(
2198
+ f"When trying to set the routing mode of node `{self.name}`, the "
2199
+ f"value `{value}` was given, but only the following values are "
2200
+ f"allowed: `newsim`, `oldsim`, `obs`, `obs_newsim`, `obs_oldsim`, "
2201
+ f"`obs_bi.`, `oldsim_bi`, and `obs_oldsim_bi`."
2202
+ )
2203
+
2204
+ # due to https://github.com/python/mypy/issues/9718:
2205
+ # pylint: disable=consider-using-in,too-many-boolean-expressions
2206
+
2207
+ if (
2208
+ value == "newsim"
2209
+ or value == "obs"
2210
+ or value == "obs_newsim"
2211
+ or value == "obs_bi"
2212
+ or value == "oldsim_bi"
2213
+ or value == "obs_oldsim_bi"
2214
+ ):
2215
+ pass
2216
+ elif value == "oldsim" or value == "obs_oldsim":
2217
+ self.__blackhole = pointerutils.Double(0.0)
2218
+ else:
2219
+ _assert_never(value)
2220
+ self._deploymode = value
2221
+ for element in itertools.chain(self.entries, self.exits):
2222
+ model: modeltools.Model | None
2223
+ model = exceptiontools.getattr_(element, "model", None)
2224
+ if model and not model.COMPOSITE:
2225
+ model.connect()
2226
+
2227
+ def get_double(
2228
+ self,
2229
+ group: Literal[
2230
+ "inlets", "receivers", "inputs", "outlets", "senders", "outputs"
2231
+ ],
2232
+ ) -> pointerutils.Double:
2233
+ """Return the |Double| object appropriate for the given |Element| input or
2234
+ output group and the actual |Node.deploymode|.
2235
+
2236
+ Method |Node.get_double| should interest framework developers only (and
2237
+ eventually model developers).
2238
+
2239
+ Let |Node| object `node1` handle different simulation and observation values:
2240
+
2241
+ >>> from hydpy import Node
2242
+ >>> node = Node("node1")
2243
+ >>> node.sequences.sim = 1.0
2244
+ >>> node.sequences.obs = 2.0
2245
+
2246
+ The following `test` function shows for a given |Node.deploymode| if method
2247
+ |Node.get_double| either returns the |Double| object handling the simulated
2248
+ value (1.0) or the one handling the observed value (2.0):
2249
+
2250
+ >>> def test(deploymode):
2251
+ ... node.deploymode = deploymode
2252
+ ... for group in ( "inlets", "receivers", "inputs"):
2253
+ ... end = None if group == "inputs" else ", "
2254
+ ... print(group, node.get_double(group), sep=": ", end=end)
2255
+ ... for group in ("outlets", "senders", "outputs"):
2256
+ ... end = None if group == "outputs" else ", "
2257
+ ... print(group, node.get_double(group), sep=": ", end=end)
2258
+
2259
+ In the default mode, nodes (passively) route simulated values by offering the
2260
+ |Double| object of sequence |Sim| to all |Element| input and output groups:
2261
+
2262
+ >>> test("newsim")
2263
+ inlets: 1.0, receivers: 1.0, inputs: 1.0
2264
+ outlets: 1.0, senders: 1.0, outputs: 1.0
2265
+
2266
+ Setting |Node.deploymode| to `obs` means that a node receives simulated values
2267
+ (from group `outlets` or `senders`) but provides observed values (to group
2268
+ `inlets` or `receivers`):
2269
+
2270
+ >>> test("obs")
2271
+ inlets: 2.0, receivers: 2.0, inputs: 2.0
2272
+ outlets: 1.0, senders: 1.0, outputs: 1.0
2273
+
2274
+ With |Node.deploymode| set to `oldsim`, the node provides (previously)
2275
+ simulated values (to group `inlets`, `receivers`, or `inputs`) but does not
2276
+ receive any. Method |Node.get_double| returns a dummy |Double| object
2277
+ initialised to 0.0 in this case (for group `outlets`, `senders`, or `outputs`):
2278
+
2279
+ >>> test("oldsim")
2280
+ inlets: 1.0, receivers: 1.0, inputs: 1.0
2281
+ outlets: 0.0, senders: 0.0, outputs: 0.0
2282
+
2283
+ For `obs_newsim`, the result is like for `obs` because, for missing data,
2284
+ *HydPy* temporarily copies newly calculated values into the observation
2285
+ sequence during simulation:
2286
+
2287
+ >>> test("obs_newsim")
2288
+ inlets: 2.0, receivers: 2.0, inputs: 2.0
2289
+ outlets: 1.0, senders: 1.0, outputs: 1.0
2290
+
2291
+ Similar holds for the `obs_oldsim` mode, but here |Node.get_double| must ensure
2292
+ newly calculated values do not overwrite the "old" ones:
2293
+
2294
+ >>> test("obs_oldsim")
2295
+ inlets: 2.0, receivers: 2.0, inputs: 2.0
2296
+ outlets: 0.0, senders: 0.0, outputs: 0.0
2297
+
2298
+ All "bidirectional" modes require symmetrical connections, as they long for
2299
+ passing the same information in the downstream and the upstream direction:
2300
+
2301
+ >>> test("obs_bi")
2302
+ inlets: 2.0, receivers: 2.0, inputs: 2.0
2303
+ outlets: 2.0, senders: 2.0, outputs: 2.0
2304
+ >>> test("oldsim_bi")
2305
+ inlets: 1.0, receivers: 1.0, inputs: 1.0
2306
+ outlets: 1.0, senders: 1.0, outputs: 1.0
2307
+ >>> test("obs_oldsim_bi")
2308
+ inlets: 2.0, receivers: 2.0, inputs: 2.0
2309
+ outlets: 2.0, senders: 2.0, outputs: 2.0
2310
+
2311
+ Other |Element| input or output groups are not supported:
2312
+
2313
+ >>> node.get_double("test")
2314
+ Traceback (most recent call last):
2315
+ ...
2316
+ ValueError: Function `get_double` of class `Node` does not support the given \
2317
+ group name `test`.
2318
+ """
2319
+ # pylint: disable=consider-using-in
2320
+
2321
+ dm = self.deploymode
2322
+
2323
+ if group in ("inlets", "receivers", "inputs"):
2324
+ if dm == "newsim" or dm == "oldsim" or dm == "oldsim_bi":
2325
+ return self.sequences.fastaccess.sim
2326
+ if (
2327
+ dm == "obs"
2328
+ or dm == "obs_newsim"
2329
+ or dm == "obs_oldsim"
2330
+ or dm == "obs_bi"
2331
+ or dm == "obs_oldsim_bi"
2332
+ ):
2333
+ return self.sequences.fastaccess.obs
2334
+ assert_never(dm)
2335
+
2336
+ if group in ("outlets", "senders", "outputs"):
2337
+ if dm == "newsim" or dm == "obs" or dm == "obs_newsim" or dm == "oldsim_bi":
2338
+ return self.sequences.fastaccess.sim
2339
+ if dm == "obs_bi" or dm == "obs_oldsim_bi":
2340
+ return self.sequences.fastaccess.obs
2341
+ if dm == "oldsim" or dm == "obs_oldsim":
2342
+ return self.__blackhole
2343
+ assert_never(dm)
2344
+
2345
+ raise ValueError(
2346
+ f"Function `get_double` of class `Node` does not support the given group "
2347
+ f"name `{group}`."
2348
+ )
2349
+
2350
+ def reset(self, idx: int = 0) -> None:
2351
+ """Reset the actual value of the simulation sequence to zero.
2352
+
2353
+ >>> from hydpy import Node
2354
+ >>> node = Node("node1")
2355
+ >>> node.sequences.sim = 1.0
2356
+ >>> node.reset()
2357
+ >>> node.sequences.sim
2358
+ sim(0.0)
2359
+ """
2360
+ self.sequences.fastaccess.reset(idx)
2361
+
2362
+ def prepare_allseries(self, allocate_ram: bool = True, jit: bool = False) -> None:
2363
+ """Call method |Node.prepare_simseries| with `write_jit=jit` and method
2364
+ |Node.prepare_obsseries| with `read_jit=jit`."""
2365
+ self.prepare_simseries(allocate_ram=allocate_ram, write_jit=jit)
2366
+ self.prepare_obsseries(allocate_ram=allocate_ram, read_jit=jit)
2367
+
2368
+ def prepare_simseries(
2369
+ self, allocate_ram: bool = True, read_jit: bool = False, write_jit: bool = False
2370
+ ) -> None:
2371
+ """Call method |IOSequence.prepare_series| of the |Sim| sequence object."""
2372
+ self.sequences.sim.prepare_series(
2373
+ allocate_ram=allocate_ram, read_jit=read_jit, write_jit=write_jit
2374
+ )
2375
+
2376
+ def prepare_obsseries(
2377
+ self, allocate_ram: bool = True, read_jit: bool = False, write_jit: bool = False
2378
+ ) -> None:
2379
+ """Call method |IOSequence.prepare_series| of the |Obs| sequence object."""
2380
+ self.sequences.obs.prepare_series(
2381
+ allocate_ram=allocate_ram, read_jit=read_jit, write_jit=write_jit
2382
+ )
2383
+
2384
+ def plot_allseries(
2385
+ self,
2386
+ *,
2387
+ labels: tuple[str, str] | None = None,
2388
+ colors: str | tuple[str, str] | None = None,
2389
+ linestyles: LineStyle | tuple[LineStyle, LineStyle] | None = None,
2390
+ linewidths: int | tuple[int, int] | None = None,
2391
+ focus: bool = False,
2392
+ stepsize: StepSize | None = None,
2393
+ ) -> pyplot.Figure:
2394
+ """Plot the |IOSequence.series| data of both the |Sim| and the |Obs| sequence
2395
+ object.
2396
+
2397
+ We demonstrate the functionalities of method |Node.plot_allseries| based on the
2398
+ `Lahn` example project:
2399
+
2400
+ >>> from hydpy.core.testtools import prepare_full_example_2
2401
+ >>> hp, pub, _ = prepare_full_example_2(lastdate="1997-01-01")
2402
+
2403
+ We perform a simulation run and calculate "observed" values for node
2404
+ `dill_assl`:
2405
+
2406
+ >>> hp.simulate()
2407
+ >>> dill_assl = hp.nodes.dill_assl
2408
+ >>> dill_assl.sequences.obs.series = dill_assl.sequences.sim.series + 10.0
2409
+
2410
+ A call to method |Node.plot_allseries| prints the time series of both sequences
2411
+ to the screen immediately (if not, you need to activate the interactive mode of
2412
+ `matplotlib` first):
2413
+
2414
+ >>> figure = dill_assl.plot_allseries()
2415
+
2416
+ Subsequent calls to |Node.plot_allseries| or the related methods
2417
+ |Node.plot_simseries| and |Node.plot_obsseries| of nodes add further time
2418
+ series data to the existing plot:
2419
+
2420
+ >>> lahn_marb = hp.nodes.lahn_marb
2421
+ >>> figure = lahn_marb.plot_simseries()
2422
+
2423
+ You can modify the appearance of the lines by passing different arguments:
2424
+
2425
+ >>> lahn_marb.sequences.obs.series = lahn_marb.sequences.sim.series + 10.0
2426
+ >>> figure = lahn_marb.plot_obsseries(color="black", linestyle="dashed")
2427
+
2428
+ All mentioned plotting functions return a |matplotlib| |figure.Figure| object.
2429
+ Use it for further plot handling, e.g. adding a title and saving the current
2430
+ figure to disk:
2431
+
2432
+ >>> from hydpy.core.testtools import save_autofig
2433
+ >>> text = figure.axes[0].set_title('daily')
2434
+ >>> save_autofig("Node_plot_allseries_1.png", figure)
2435
+
2436
+ .. image:: Node_plot_allseries_1.png
2437
+
2438
+ You can plot the data in an aggregated manner (see the documentation on the
2439
+ function |aggregate_series| for the supported step sizes and further details):
2440
+
2441
+ >>> figure = dill_assl.plot_allseries(stepsize="monthly")
2442
+ >>> text = figure.axes[0].set_title('monthly')
2443
+ >>> save_autofig("Node_plot_allseries_2.png", figure)
2444
+
2445
+ .. image:: Node_plot_allseries_2.png
2446
+
2447
+ You can restrict the plotted period via the |Timegrids.eval_| |Timegrid| and
2448
+ overwrite the time series label and other defaults via keyword arguments.
2449
+ For tuples passed to method |Node.plot_allseries|, the first entry corresponds
2450
+ to the observation and the second one to the simulation results:
2451
+
2452
+ >>> pub.timegrids.eval_.dates = "1996-10-01", "1996-11-01"
2453
+ >>> figure = lahn_marb.plot_allseries(labels=("measured", "calculated"),
2454
+ ... colors=("blue", "red"),
2455
+ ... linewidths=2,
2456
+ ... linestyles=("--", ":"),
2457
+ ... focus=True,)
2458
+ >>> save_autofig("Node_plot_allseries_3.png", figure)
2459
+
2460
+ .. image:: Node_plot_allseries_3.png
2461
+
2462
+ When necessary, all plotting methods raise errors like the following:
2463
+
2464
+ >>> figure = lahn_marb.plot_allseries(stepsize="quaterly")
2465
+ Traceback (most recent call last):
2466
+ ...
2467
+ ValueError: While trying to plot the time series of sequence(s) obs and sim \
2468
+ of node `lahn_marb` for the period `1996-10-01 00:00:00` to `1996-11-01 00:00:00`, the \
2469
+ following error occurred: While trying to aggregate the given series, the following \
2470
+ error occurred: Argument `stepsize` received value `quaterly`, but only the following \
2471
+ ones are supported: `monthly` (default), `daily`, and `yearly`.
2472
+
2473
+ >>> from hydpy import pub
2474
+ >>> del pub.timegrids
2475
+ >>> figure = lahn_marb.plot_allseries()
2476
+ Traceback (most recent call last):
2477
+ ...
2478
+ hydpy.core.exceptiontools.AttributeNotReady: While trying to plot the time \
2479
+ series of sequence(s) obs and sim of node `lahn_marb` , the following error occurred: \
2480
+ Attribute timegrids of module `pub` is not defined at the moment.
2481
+ """
2482
+
2483
+ t = TypeVar("t", str, int)
2484
+
2485
+ def _make_tuple(
2486
+ x: t | None | tuple[t | None, t | None],
2487
+ ) -> tuple[t | None, t | None]:
2488
+ return (x, x) if ((x is None) or isinstance(x, (str, int))) else x
2489
+
2490
+ return self._plot_series(
2491
+ sequences=(self.sequences.obs, self.sequences.sim),
2492
+ labels=_make_tuple(labels),
2493
+ colors=_make_tuple(colors),
2494
+ linestyles=_make_tuple(linestyles),
2495
+ linewidths=_make_tuple(linewidths),
2496
+ focus=focus,
2497
+ stepsize=stepsize,
2498
+ )
2499
+
2500
+ def plot_simseries(
2501
+ self,
2502
+ *,
2503
+ label: str | None = None,
2504
+ color: str | None = None,
2505
+ linestyle: LineStyle | None = None,
2506
+ linewidth: int | None = None,
2507
+ focus: bool = False,
2508
+ stepsize: StepSize | None = None,
2509
+ ) -> pyplot.Figure:
2510
+ """Plot the |IOSequence.series| of the |Sim| sequence object.
2511
+
2512
+ See method |Node.plot_allseries| for further information.
2513
+ """
2514
+ return self._plot_series(
2515
+ sequences=[self.sequences.sim],
2516
+ labels=(label,),
2517
+ colors=(color,),
2518
+ linestyles=(linestyle,),
2519
+ linewidths=(linewidth,),
2520
+ focus=focus,
2521
+ stepsize=stepsize,
2522
+ )
2523
+
2524
+ def plot_obsseries(
2525
+ self,
2526
+ *,
2527
+ label: str | None = None,
2528
+ color: str | None = None,
2529
+ linestyle: LineStyle | None = None,
2530
+ linewidth: int | None = None,
2531
+ focus: bool = False,
2532
+ stepsize: StepSize | None = None,
2533
+ ) -> pyplot.Figure:
2534
+ """Plot the |IOSequence.series| of the |Obs| sequence object.
2535
+
2536
+ See method |Node.plot_allseries| for further information.
2537
+ """
2538
+ return self._plot_series(
2539
+ sequences=[self.sequences.obs],
2540
+ labels=(label,),
2541
+ colors=(color,),
2542
+ linestyles=(linestyle,),
2543
+ linewidths=(linewidth,),
2544
+ focus=focus,
2545
+ stepsize=stepsize,
2546
+ )
2547
+
2548
+ def _plot_series(
2549
+ self,
2550
+ *,
2551
+ sequences: Sequence[sequencetools.IOSequence],
2552
+ labels: Iterable[str | None],
2553
+ colors: Iterable[str | None],
2554
+ linestyles: Iterable[str | None],
2555
+ linewidths: Iterable[int | None],
2556
+ focus: bool = False,
2557
+ stepsize: StepSize | None = None,
2558
+ ) -> pyplot.Figure:
2559
+ try:
2560
+ idx0, idx1 = hydpy.pub.timegrids.evalindices
2561
+ for sequence, label, color, linestyle, linewidth in zip(
2562
+ sequences, labels, colors, linestyles, linewidths
2563
+ ):
2564
+ label_ = label if label else " ".join((self.name, sequence.name))
2565
+ if stepsize is None:
2566
+ index = _get_pandasindex()
2567
+ ps = pandas.Series(sequence.evalseries, index=index[idx0:idx1])
2568
+ else:
2569
+ ps = seriestools.aggregate_series(
2570
+ series=sequence.series, stepsize=stepsize, aggregator=numpy.mean
2571
+ )
2572
+ period = "15d" if stepsize.startswith("m") else "12h"
2573
+ ps.index += timetools.Period(period).timedelta
2574
+ ps.name = label_
2575
+ kwargs = {"label": label_, "ax": pyplot.gca()}
2576
+ if color is not None:
2577
+ kwargs["color"] = color
2578
+ if linestyle is not None:
2579
+ kwargs["linestyle"] = linestyle
2580
+ if linewidth is not None:
2581
+ kwargs["linewidth"] = linewidth
2582
+ ps.plot(**kwargs)
2583
+ pyplot.legend()
2584
+ if not focus:
2585
+ pyplot.ylim((0.0, None))
2586
+ if pyplot.get_fignums():
2587
+ if (variable := str(self.variable)) == "Q":
2588
+ variable = "Q [m³/s]"
2589
+ pyplot.ylabel(variable)
2590
+ return pyplot.gcf()
2591
+ except BaseException:
2592
+ if exceptiontools.attrready(hydpy.pub, "timegrids"):
2593
+ tg = hydpy.pub.timegrids.eval_
2594
+ periodstring = f"for the period `{tg.firstdate}` to `{tg.lastdate}`"
2595
+ else:
2596
+ periodstring = ""
2597
+ objecttools.augment_excmessage(
2598
+ f"While trying to plot the time series of sequence(s) "
2599
+ f"{objecttools.enumeration(sequence.name for sequence in sequences)} "
2600
+ f"of node `{objecttools.devicename(sequences[0])}` {periodstring}"
2601
+ )
2602
+
2603
+ def assignrepr(self, prefix: str = "") -> str:
2604
+ """Return a |repr| string with a prefixed assignment."""
2605
+ variable = self.variable
2606
+ if isinstance(variable, str):
2607
+ variable = f'"{variable}"'
2608
+ elif isinstance(variable, FusedVariable):
2609
+ variable = str(variable)
2610
+ else:
2611
+ variable = f"{variable.__module__.split('.')[-1]}_{variable.__name__}"
2612
+ lines = [f'{prefix}Node("{self.name}", variable={variable},']
2613
+ if self.keywords:
2614
+ subprefix = f'{" "*(len(prefix)+5)}keywords='
2615
+ with objecttools.repr_.preserve_strings(True):
2616
+ with objecttools.assignrepr_tuple.always_bracketed(False):
2617
+ line = objecttools.assignrepr_list(
2618
+ values=sorted(self.keywords), prefix=subprefix, width=70
2619
+ )
2620
+ lines.append(line + ",")
2621
+ lines[-1] = lines[-1][:-1] + ")"
2622
+ return "\n".join(lines)
2623
+
2624
+ def __repr__(self) -> str:
2625
+ return self.assignrepr()
2626
+
2627
+
2628
+ class Element(Device):
2629
+ """Handles a |Model| object and connects it to other models via
2630
+ |Node| objects.
2631
+
2632
+ When preparing |Element| objects, one links them to nodes of different "groups",
2633
+ each group of nodes implemented as an immutable |Nodes| object:
2634
+
2635
+ * |Element.inlets| and |Element.outlets| nodes handle, for example, the inflow to
2636
+ and the outflow from the respective element.
2637
+ * |Element.receivers| and |Element.senders| nodes are thought for information flow
2638
+ between arbitrary elements, for example, to inform a |dam| model about the
2639
+ discharge at a gauge downstream.
2640
+ * |Element.inputs| nodes provide optional input information, for example,
2641
+ interpolated precipitation that could alternatively be read from files as well.
2642
+ * |Element.outputs| nodes query optional output information, for example, the
2643
+ water level of a dam.
2644
+
2645
+ You can select the relevant nodes either by passing them explicitly or passing
2646
+ their name both as single objects or as objects contained within an iterable
2647
+ object:
2648
+
2649
+ >>> from hydpy import Element, Node
2650
+ >>> Element("test",
2651
+ ... inlets="inl1",
2652
+ ... outlets=Node("outl1"),
2653
+ ... receivers=("rec1", Node("rec2")))
2654
+ Element("test",
2655
+ inlets="inl1",
2656
+ outlets="outl1",
2657
+ receivers=["rec1", "rec2"])
2658
+
2659
+ Repeating such a statement with different nodes adds them to the existing ones
2660
+ without any conflict in case of repeated specifications:
2661
+
2662
+ >>> Element("test",
2663
+ ... inlets="inl1",
2664
+ ... receivers=("rec2", "rec3"),
2665
+ ... senders="sen1",
2666
+ ... inputs="inp1",
2667
+ ... outputs="outp1")
2668
+ Element("test",
2669
+ inlets="inl1",
2670
+ outlets="outl1",
2671
+ receivers=["rec1", "rec2", "rec3"],
2672
+ senders="sen1",
2673
+ inputs="inp1",
2674
+ outputs="outp1")
2675
+
2676
+ Subsequent adding of nodes also works via property access:
2677
+
2678
+ >>> test = Element("test")
2679
+ >>> test.inlets = "inl2"
2680
+ >>> test.outlets = None
2681
+ >>> test.receivers = ()
2682
+ >>> test.senders = "sen2", Node("sen3")
2683
+ >>> test.inputs = []
2684
+ >>> test.outputs = Node("outp2")
2685
+ >>> test
2686
+ Element("test",
2687
+ inlets=["inl1", "inl2"],
2688
+ outlets="outl1",
2689
+ receivers=["rec1", "rec2", "rec3"],
2690
+ senders=["sen1", "sen2", "sen3"],
2691
+ inputs="inp1",
2692
+ outputs=["outp1", "outp2"])
2693
+
2694
+ The properties try to verify that all connections make sense. For example, an
2695
+ element should never handle an `inlet` node that it also handles as an `outlet`,
2696
+ `input`, or `output` node:
2697
+
2698
+ >>> test.inlets = "outl1"
2699
+ Traceback (most recent call last):
2700
+ ...
2701
+ ValueError: For element `test`, the given inlet node `outl1` is already defined \
2702
+ as a(n) outlet node, which is not allowed.
2703
+
2704
+ >>> test.inlets = "inp1"
2705
+ Traceback (most recent call last):
2706
+ ...
2707
+ ValueError: For element `test`, the given inlet node `inp1` is already defined as \
2708
+ a(n) input node, which is not allowed.
2709
+
2710
+ >>> test.inlets = "outp1"
2711
+ Traceback (most recent call last):
2712
+ ...
2713
+ ValueError: For element `test`, the given inlet node `outp1` is already defined \
2714
+ as a(n) output node, which is not allowed.
2715
+
2716
+ Similar holds for the `outlet` nodes:
2717
+
2718
+ >>> test.outlets = "inl1"
2719
+ Traceback (most recent call last):
2720
+ ...
2721
+ ValueError: For element `test`, the given outlet node `inl1` is already defined \
2722
+ as a(n) inlet node, which is not allowed.
2723
+
2724
+ >>> test.outlets = "inp1"
2725
+ Traceback (most recent call last):
2726
+ ...
2727
+ ValueError: For element `test`, the given outlet node `inp1` is already defined \
2728
+ as a(n) input node, which is not allowed.
2729
+
2730
+ >>> test.outlets = "outp1"
2731
+ Traceback (most recent call last):
2732
+ ...
2733
+ ValueError: For element `test`, the given outlet node `outp1` is already defined \
2734
+ as a(n) output node, which is not allowed.
2735
+
2736
+ The following restrictions hold for the `sender` nodes:
2737
+
2738
+ >>> test.senders = "rec1"
2739
+ Traceback (most recent call last):
2740
+ ...
2741
+ ValueError: For element `test`, the given sender node `rec1` is already defined \
2742
+ as a(n) receiver node, which is not allowed.
2743
+
2744
+ >>> test.senders = "inp1"
2745
+ Traceback (most recent call last):
2746
+ ...
2747
+ ValueError: For element `test`, the given sender node `inp1` is already defined \
2748
+ as a(n) input node, which is not allowed.
2749
+
2750
+ >>> test.senders = "outp1"
2751
+ Traceback (most recent call last):
2752
+ ...
2753
+ ValueError: For element `test`, the given sender node `outp1` is already defined \
2754
+ as a(n) output node, which is not allowed.
2755
+
2756
+ The following restrictions hold for the `receiver` nodes:
2757
+
2758
+ >>> test.receivers = "sen1"
2759
+ Traceback (most recent call last):
2760
+ ...
2761
+ ValueError: For element `test`, the given receiver node `sen1` is already defined \
2762
+ as a(n) sender node, which is not allowed.
2763
+
2764
+ >>> test.receivers = "inp1"
2765
+ Traceback (most recent call last):
2766
+ ...
2767
+ ValueError: For element `test`, the given receiver node `inp1` is already defined \
2768
+ as a(n) input node, which is not allowed.
2769
+
2770
+ >>> test.receivers = "outp1"
2771
+ Traceback (most recent call last):
2772
+ ...
2773
+ ValueError: For element `test`, the given receiver node `outp1` is already \
2774
+ defined as a(n) output node, which is not allowed.
2775
+
2776
+ The following restrictions hold for the `input` nodes:
2777
+
2778
+ >>> test.inputs = "outp1"
2779
+ Traceback (most recent call last):
2780
+ ...
2781
+ ValueError: For element `test`, the given input node `outp1` is already defined \
2782
+ as a(n) output node, which is not allowed.
2783
+
2784
+ >>> test.inputs = "inl1"
2785
+ Traceback (most recent call last):
2786
+ ...
2787
+ ValueError: For element `test`, the given input node `inl1` is already defined as \
2788
+ a(n) inlet node, which is not allowed.
2789
+
2790
+ >>> test.inputs = "outl1"
2791
+ Traceback (most recent call last):
2792
+ ...
2793
+ ValueError: For element `test`, the given input node `outl1` is already defined \
2794
+ as a(n) outlet node, which is not allowed.
2795
+
2796
+ >>> test.inputs = "sen1"
2797
+ Traceback (most recent call last):
2798
+ ...
2799
+ ValueError: For element `test`, the given input node `sen1` is already defined as \
2800
+ a(n) sender node, which is not allowed.
2801
+
2802
+ >>> test.inputs = "rec1"
2803
+ Traceback (most recent call last):
2804
+ ...
2805
+ ValueError: For element `test`, the given input node `rec1` is already defined as \
2806
+ a(n) receiver node, which is not allowed.
2807
+
2808
+ The following restrictions hold for the `output` nodes:
2809
+
2810
+ >>> test.outputs = "inp1"
2811
+ Traceback (most recent call last):
2812
+ ...
2813
+ ValueError: For element `test`, the given output node `inp1` is already defined \
2814
+ as a(n) input node, which is not allowed.
2815
+
2816
+ >>> test.outputs = "inl1"
2817
+ Traceback (most recent call last):
2818
+ ...
2819
+ ValueError: For element `test`, the given output node `inl1` is already defined \
2820
+ as a(n) inlet node, which is not allowed.
2821
+
2822
+ >>> test.outputs = "outl1"
2823
+ Traceback (most recent call last):
2824
+ ...
2825
+ ValueError: For element `test`, the given output node `outl1` is already defined \
2826
+ as a(n) outlet node, which is not allowed.
2827
+
2828
+ >>> test.outputs = "sen1"
2829
+ Traceback (most recent call last):
2830
+ ...
2831
+ ValueError: For element `test`, the given output node `sen1` is already defined \
2832
+ as a(n) sender node, which is not allowed.
2833
+
2834
+ >>> test.outputs = "rec1"
2835
+ Traceback (most recent call last):
2836
+ ...
2837
+ ValueError: For element `test`, the given output node `rec1` is already defined \
2838
+ as a(n) receiver node, which is not allowed.
2839
+
2840
+ Note that the discussed |Nodes| objects are immutable by default, disallowing to
2841
+ change them in other ways as described above:
2842
+
2843
+ >>> test.inlets += "inl3"
2844
+ Traceback (most recent call last):
2845
+ ...
2846
+ RuntimeError: While trying to add the device `inl3` to a Nodes object, the \
2847
+ following error occurred: Adding devices to immutable Nodes objects is not allowed.
2848
+
2849
+ Use the parameter `force` to change this behaviour:
2850
+
2851
+ >>> test.inlets.add_device("inl3", force=True)
2852
+
2853
+ However, it is up to you to make sure that the added node also handles the relevant
2854
+ element in the suitable group. In the discussed example, only node `inl2` has been
2855
+ added properly but not node `inl3`:
2856
+
2857
+ >>> test.inlets.inl2.exits
2858
+ Elements("test")
2859
+ >>> test.inlets.inl3.exits
2860
+ Elements()
2861
+
2862
+ Some elements might belong to a |Element.collective|, which is a group of elements
2863
+ requiring simultaneous handling during simulation (see method
2864
+ |Elements.unite_collectives|). If needed, specify the collective's name by the
2865
+ corresponding argument:
2866
+
2867
+ >>> Element("part_1", collective="NileRiver", inlets="inl1")
2868
+ Element("part_1",
2869
+ collective="NileRiver",
2870
+ inlets="inl1")
2871
+
2872
+ The information persists when querying the same element from the internal registry,
2873
+ whether one specifies the collective's name again or not:
2874
+
2875
+ >>> Element("part_1", collective="NileRiver")
2876
+ Element("part_1",
2877
+ collective="NileRiver",
2878
+ inlets="inl1")
2879
+
2880
+ >>> Element("part_1")
2881
+ Element("part_1",
2882
+ collective="NileRiver",
2883
+ inlets="inl1")
2884
+
2885
+ However, changing the collective via the constructor is forbidden as it might
2886
+ result in hard-to-find configuration errors:
2887
+
2888
+ >>> Element("part_1", collective="AmazonRiver")
2889
+ Traceback (most recent call last):
2890
+ ...
2891
+ RuntimeError: The collective name `AmazonRiver` is given, but element `part_1` is \
2892
+ already a collective `NileRiver` member.
2893
+ """
2894
+
2895
+ collective: str | None = None
2896
+ """The collective the actual |Element| instance belongs to."""
2897
+
2898
+ _inlets: Nodes
2899
+ _outlets: Nodes
2900
+ _receivers: Nodes
2901
+ _senders: Nodes
2902
+ _inputs: Nodes
2903
+ _outputs: Nodes
2904
+ _model: modeltools.Model | None
2905
+
2906
+ def __init__(
2907
+ self,
2908
+ value: ElementConstrArg,
2909
+ *,
2910
+ inlets: NodesConstrArg = None,
2911
+ outlets: NodesConstrArg = None,
2912
+ receivers: NodesConstrArg = None,
2913
+ senders: NodesConstrArg = None,
2914
+ inputs: NodesConstrArg = None,
2915
+ outputs: NodesConstrArg = None,
2916
+ collective: str | None = None,
2917
+ keywords: MayNonerable1[str] = None,
2918
+ ) -> None:
2919
+ # pylint: disable=unused-argument
2920
+ # required for consistincy with Device.__new__
2921
+ if collective is not None:
2922
+ if (col := self.collective) is None:
2923
+ self.collective = collective
2924
+ elif col != collective:
2925
+ raise RuntimeError(
2926
+ f"The collective name `{collective}` is given, but element "
2927
+ f"`{self.name}` is already a collective `{col}` member."
2928
+ )
2929
+ if hasattr(self, "new_instance"):
2930
+ self._inlets = Nodes(mutable=False)
2931
+ self._outlets = Nodes(mutable=False)
2932
+ self._receivers = Nodes(mutable=False)
2933
+ self._senders = Nodes(mutable=False)
2934
+ self._inputs = Nodes(mutable=False)
2935
+ self._outputs = Nodes(mutable=False)
2936
+ self.__connections = (
2937
+ self.inlets,
2938
+ self.outlets,
2939
+ self.receivers,
2940
+ self.senders,
2941
+ self.inputs,
2942
+ self.outputs,
2943
+ )
2944
+ self._model = None
2945
+ delattr(self, "new_instance")
2946
+ self.keywords = keywords
2947
+ if inlets is not None:
2948
+ self.inlets = inlets
2949
+ if outlets is not None:
2950
+ self.outlets = outlets
2951
+ if receivers is not None:
2952
+ self.receivers = receivers
2953
+ if senders is not None:
2954
+ self.senders = senders
2955
+ if inputs is not None:
2956
+ self.inputs = inputs
2957
+ if outputs is not None:
2958
+ self.outputs = outputs
2959
+ # due to internal type conversion
2960
+ # see issue https://github.com/python/mypy/issues/3004
2961
+
2962
+ def __update_group(
2963
+ self,
2964
+ values: NodesConstrArg,
2965
+ targetnodes: str,
2966
+ targetelements: str,
2967
+ incompatiblenodes: tuple[str, ...],
2968
+ ) -> None:
2969
+ elementgroup: Nodes = getattr(self, targetnodes)
2970
+ for node in Nodes(values):
2971
+ for incomp in incompatiblenodes:
2972
+ if node in getattr(self, incomp):
2973
+ raise ValueError(
2974
+ f"For element `{self}`, the given {targetnodes[1:-1]} "
2975
+ f"node `{node}` is already defined as a(n) {incomp[1:-1]} "
2976
+ f"node, which is not allowed."
2977
+ )
2978
+ elementgroup.add_device(node, force=True)
2979
+ nodegroup: Elements = getattr(node, targetelements)
2980
+ nodegroup.add_device(self, force=True)
2981
+
2982
+ def _get_inlets(self) -> Nodes:
2983
+ """Group of |Node| objects from which the handled |Model| object queries its
2984
+ "upstream" input values (e.g. inflow)."""
2985
+ return self._inlets
2986
+
2987
+ def _set_inlets(self, values: NodesConstrArg) -> None:
2988
+ self.__update_group(
2989
+ values,
2990
+ targetnodes="_inlets",
2991
+ targetelements="_exits",
2992
+ incompatiblenodes=("_outlets", "_inputs", "_outputs"),
2993
+ )
2994
+
2995
+ inlets = propertytools.Property(fget=_get_inlets, fset=_set_inlets)
2996
+
2997
+ def _get_outlets(self) -> Nodes:
2998
+ """Group of |Node| objects to which the handled |Model| object passes its
2999
+ "downstream" output values (e.g. outflow)."""
3000
+ return self._outlets
3001
+
3002
+ def _set_outlets(self, values: NodesConstrArg) -> None:
3003
+ self.__update_group(
3004
+ values,
3005
+ targetnodes="_outlets",
3006
+ targetelements="_entries",
3007
+ incompatiblenodes=("_inlets", "_inputs", "_outputs"),
3008
+ )
3009
+
3010
+ outlets = propertytools.Property(fget=_get_outlets, fset=_set_outlets)
3011
+
3012
+ def _get_receivers(self) -> Nodes:
3013
+ """Group of |Node| objects from which the handled |Model| object queries its
3014
+ "remote" information values (e.g. discharge at a remote downstream)."""
3015
+ return self._receivers
3016
+
3017
+ def _set_receivers(self, values: NodesConstrArg) -> None:
3018
+ self.__update_group(
3019
+ values,
3020
+ targetnodes="_receivers",
3021
+ targetelements="_exits",
3022
+ incompatiblenodes=("_senders", "_inputs", "_outputs"),
3023
+ )
3024
+
3025
+ receivers = propertytools.Property(fget=_get_receivers, fset=_set_receivers)
3026
+
3027
+ def _get_senders(self) -> Nodes:
3028
+ """Group of |Node| objects to which the handled |Model| object passes its
3029
+ "remote" information values (e.g. water level of a |dam| model)."""
3030
+ return self._senders
3031
+
3032
+ def _set_senders(self, values: NodesConstrArg) -> None:
3033
+ self.__update_group(
3034
+ values,
3035
+ targetnodes="_senders",
3036
+ targetelements="_entries",
3037
+ incompatiblenodes=("_receivers", "_inputs", "_outputs"),
3038
+ )
3039
+
3040
+ senders = propertytools.Property(fget=_get_senders, fset=_set_senders)
3041
+
3042
+ def _get_inputs(self) -> Nodes:
3043
+ """Group of |Node| objects from which the handled |Model| object queries its
3044
+ "external" input values instead of reading them from files (e.g. interpolated
3045
+ precipitation)."""
3046
+ return self._inputs
3047
+
3048
+ def _set_inputs(self, values: NodesConstrArg) -> None:
3049
+ self.__update_group(
3050
+ values,
3051
+ targetnodes="_inputs",
3052
+ targetelements="_exits",
3053
+ incompatiblenodes=(
3054
+ "_inlets",
3055
+ "_outlets",
3056
+ "_senders",
3057
+ "_receivers",
3058
+ "_outputs",
3059
+ ),
3060
+ )
3061
+
3062
+ inputs = propertytools.Property(fget=_get_inputs, fset=_set_inputs)
3063
+
3064
+ def _get_outputs(self) -> Nodes:
3065
+ """Group of |Node| objects to which the handled |Model| object passes its
3066
+ "internal" output values, available via sequences of type |FluxSequence| or
3067
+ |StateSequence| (e.g. potential evaporation)."""
3068
+ return self._outputs
3069
+
3070
+ def _set_outputs(self, values: NodesConstrArg) -> None:
3071
+ self.__update_group(
3072
+ values,
3073
+ targetnodes="_outputs",
3074
+ targetelements="_entries",
3075
+ incompatiblenodes=(
3076
+ "_inlets",
3077
+ "_outlets",
3078
+ "_senders",
3079
+ "_receivers",
3080
+ "_inputs",
3081
+ ),
3082
+ )
3083
+
3084
+ outputs = propertytools.Property(fget=_get_outputs, fset=_set_outputs)
3085
+
3086
+ @classmethod
3087
+ def get_handlerclass(cls) -> type[Elements]:
3088
+ """Return class |Elements|."""
3089
+ return Elements
3090
+
3091
+ @property
3092
+ def model(self) -> modeltools.Model:
3093
+ """The |Model| object handled by the actual |Element| object.
3094
+
3095
+ Directly after their initialisation, elements do not know which model they
3096
+ require:
3097
+
3098
+ >>> from hydpy import attrready, Element
3099
+ >>> hland = Element("hland", outlets="outlet")
3100
+ >>> hland.model
3101
+ Traceback (most recent call last):
3102
+ ...
3103
+ hydpy.core.exceptiontools.AttributeNotReady: The model object of element \
3104
+ `hland` has been requested but not been prepared so far.
3105
+
3106
+ During scripting and when working interactively in the Python shell, it is
3107
+ often convenient to assign a |model| directly.
3108
+
3109
+ >>> from hydpy.models.hland_96 import *
3110
+ >>> parameterstep("1d")
3111
+ >>> hland.model = model
3112
+ >>> hland.model.name
3113
+ 'hland_96'
3114
+
3115
+ >>> del hland.model
3116
+ >>> attrready(hland, "model")
3117
+ False
3118
+
3119
+ For the "usual" approach to preparing models, please see the method
3120
+ |Element.prepare_model|.
3121
+
3122
+ The following examples show that assigning |Model| objects to property
3123
+ |Element.model| creates some connection required by the respective model type
3124
+ automatically. These examples should be relevant for developers only.
3125
+
3126
+ The following |exch_branch_hbv96| model branches a single input value (from to
3127
+ node `inp`) to multiple outputs (nodes `out1` and `out2`):
3128
+
3129
+ >>> from hydpy import Element, Node, reverse_model_wildcard_import, pub
3130
+ >>> reverse_model_wildcard_import()
3131
+ >>> pub.timegrids = "2000-01-01", "2000-01-02", "1d"
3132
+ >>> element = Element("a_branch",
3133
+ ... inlets="branch_input",
3134
+ ... outlets=("branch_output_1", "branch_output_2"))
3135
+ >>> inp = element.inlets.branch_input
3136
+ >>> out1, out2 = element.outlets
3137
+ >>> from hydpy.models.exch_branch_hbv96 import *
3138
+ >>> parameterstep()
3139
+ >>> delta(0.0)
3140
+ >>> minimum(0.0)
3141
+ >>> xpoints(0.0, 3.0)
3142
+ >>> ypoints(branch_output_1=[0.0, 1.0], branch_output_2=[0.0, 2.0])
3143
+ >>> parameters.update()
3144
+ >>> element.model = model
3145
+
3146
+ To show that the inlet and outlet connections are built properly, we assign a
3147
+ new value to the inlet node `inp` and verify that the suitable fractions of
3148
+ this value are passed to the outlet nodes out1` and `out2` by calling the
3149
+ method |Model.simulate|:
3150
+
3151
+ >>> inp.sequences.sim = 999.0
3152
+ >>> model.simulate(0)
3153
+ >>> fluxes.originalinput
3154
+ originalinput(999.0)
3155
+ >>> out1.sequences.sim
3156
+ sim(333.0)
3157
+ >>> out2.sequences.sim
3158
+ sim(666.0)
3159
+
3160
+ .. testsetup::
3161
+
3162
+ >>> del pub.timegrids
3163
+ """
3164
+ model = self._model
3165
+ if model:
3166
+ return model
3167
+ raise exceptiontools.AttributeNotReady(
3168
+ f"The model object of element `{self.name}` has been requested but not "
3169
+ f"been prepared so far."
3170
+ )
3171
+
3172
+ @model.setter
3173
+ def model(self, model: modeltools.Model) -> None:
3174
+ self._model = model
3175
+ if exceptiontools.getattr_(model, "element", None) is not self:
3176
+ model.element = self
3177
+ if not model.COMPOSITE:
3178
+ for submodel in model.find_submodels().values():
3179
+ submodel.__hydpy_element__ = self
3180
+ model.connect()
3181
+
3182
+ @model.deleter
3183
+ def model(self) -> None:
3184
+ if (model := self._model) is not None:
3185
+ self._model = None
3186
+ if exceptiontools.getattr_(model, "element", None) is self:
3187
+ del model.element
3188
+
3189
+ def prepare_model(self, clear_registry: bool = True) -> None:
3190
+ """Load the control file of the actual |Element| object, initialise its |Model|
3191
+ object, build the required connections via (an eventually overridden version
3192
+ of) method |Model.connect| of class |Model|, and update its derived parameter
3193
+ values via calling (an eventually overridden version) of method
3194
+ |Parameters.update| of class |Parameters|.
3195
+
3196
+ See method |HydPy.prepare_models| of class |HydPy| and property |model| of
3197
+ class |Element| fur further information.
3198
+ """
3199
+ options = hydpy.pub.options
3200
+ try:
3201
+ try:
3202
+ hydpy.pub.timegrids
3203
+ except exceptiontools.AttributeNotReady:
3204
+ raise exceptiontools.AttributeNotReady(
3205
+ "The initialisation period has not been defined via attribute "
3206
+ "`timegrids` of module `pub` yet but might be required to prepare "
3207
+ "the model properly."
3208
+ ) from None
3209
+ with options.warnsimulationstep(False):
3210
+ info = hydpy.pub.controlmanager.load_file(
3211
+ element=self, clear_registry=clear_registry
3212
+ )
3213
+ self.model = info["model"]
3214
+ self.model.parameters.update()
3215
+ except OSError:
3216
+ if options.warnmissingcontrolfile:
3217
+ warnings.warn(
3218
+ f"Due to a missing or no accessible control file, no model could "
3219
+ f"be initialised for element `{self.name}`"
3220
+ )
3221
+ else:
3222
+ objecttools.augment_excmessage(
3223
+ f"While trying to initialise the model object of element "
3224
+ f"`{self.name}`"
3225
+ )
3226
+ except BaseException:
3227
+ objecttools.augment_excmessage(
3228
+ f"While trying to initialise the model object of element `{self.name}`"
3229
+ )
3230
+
3231
+ def init_model(self, clear_registry: bool = True) -> None:
3232
+ """Deprecated: use method |Element.prepare_model| instead.
3233
+
3234
+ >>> from hydpy import Element
3235
+ >>> from unittest import mock
3236
+ >>> with mock.patch.object(Element, "prepare_model") as mocked:
3237
+ ... element = Element("test")
3238
+ ... element.init_model(False)
3239
+ Traceback (most recent call last):
3240
+ ...
3241
+ hydpy.core.exceptiontools.HydPyDeprecationWarning: Method `init_model` of \
3242
+ class `Element` is deprecated. Use method `prepare_model` instead.
3243
+ >>> mocked.call_args_list
3244
+ [call(False)]
3245
+ """
3246
+ self.prepare_model(clear_registry)
3247
+ warnings.warn(
3248
+ "Method `init_model` of class `Element` is deprecated. Use method "
3249
+ "`prepare_model` instead.",
3250
+ exceptiontools.HydPyDeprecationWarning,
3251
+ )
3252
+
3253
+ @property
3254
+ def variables(self) -> set[NodeVariableType]:
3255
+ """A set of all different |Node.variable| values of the |Node| objects directly
3256
+ connected to the actual |Element| object.
3257
+
3258
+ Suppose an element is connected to five nodes, which (partly) represent
3259
+ different variables:
3260
+
3261
+ >>> from hydpy import Element, Node
3262
+ >>> element = Element("Test",
3263
+ ... inlets=(Node("N1", "X"), Node("N2", "Y1")),
3264
+ ... outlets=(Node("N3", "X"), Node("N4", "Y2")),
3265
+ ... receivers=(Node("N5", "X"), Node("N6", "Y3")),
3266
+ ... senders=(Node("N7", "X"), Node("N8", "Y4")))
3267
+
3268
+ Property |Element.variables| puts all the different variables of these nodes
3269
+ together:
3270
+
3271
+ >>> sorted(element.variables)
3272
+ ['X', 'Y1', 'Y2', 'Y3', 'Y4']
3273
+ """
3274
+ variables = set()
3275
+ for connection in self.__connections:
3276
+ variables.update(connection.variables)
3277
+ return variables
3278
+
3279
+ def prepare_allseries(self, allocate_ram: bool = True, jit: bool = False) -> None:
3280
+ """Call method |Model.prepare_allseries| of the currently handled |Model|
3281
+ instance and its submodels."""
3282
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3283
+ model.prepare_allseries(allocate_ram=allocate_ram, jit=jit)
3284
+
3285
+ def prepare_inputseries(
3286
+ self, allocate_ram: bool = True, read_jit: bool = False, write_jit: bool = False
3287
+ ) -> None:
3288
+ """Call method |Model.prepare_inputseries| of the currently handled |Model|
3289
+ instance and its submodels."""
3290
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3291
+ model.prepare_inputseries(
3292
+ allocate_ram=allocate_ram, read_jit=read_jit, write_jit=write_jit
3293
+ )
3294
+
3295
+ def prepare_factorseries(
3296
+ self, allocate_ram: bool = True, write_jit: bool = False
3297
+ ) -> None:
3298
+ """Call method |Model.prepare_factorseries| of the currently handled |Model|
3299
+ instance and its submodels."""
3300
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3301
+ model.prepare_factorseries(allocate_ram=allocate_ram, write_jit=write_jit)
3302
+
3303
+ def prepare_fluxseries(
3304
+ self, allocate_ram: bool = True, write_jit: bool = False
3305
+ ) -> None:
3306
+ """Call method |Model.prepare_fluxseries| of the currently handled |Model|
3307
+ instance and its submodels."""
3308
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3309
+ model.prepare_fluxseries(allocate_ram=allocate_ram, write_jit=write_jit)
3310
+
3311
+ def prepare_stateseries(
3312
+ self, allocate_ram: bool = True, write_jit: bool = False
3313
+ ) -> None:
3314
+ """Call method |Model.prepare_stateseries| of the currently handled |Model|
3315
+ instance and its submodels."""
3316
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3317
+ model.prepare_stateseries(allocate_ram=allocate_ram, write_jit=write_jit)
3318
+
3319
+ def load_allseries(self) -> None:
3320
+ """Call method |Model.load_allseries| of the currently handled |Model|
3321
+ instance and its submodels."""
3322
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3323
+ model.load_allseries()
3324
+
3325
+ def load_inputseries(self) -> None:
3326
+ """Call method |Model.load_inputseries| of the currently handled |Model|
3327
+ instance and its submodels."""
3328
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3329
+ model.load_inputseries()
3330
+
3331
+ def load_factorseries(self) -> None:
3332
+ """Call method |Model.load_factorseries| of the currently handled |Model|
3333
+ instance and its submodels."""
3334
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3335
+ model.load_factorseries()
3336
+
3337
+ def load_fluxseries(self) -> None:
3338
+ """Call method |Model.load_fluxseries| of the currently handled |Model|
3339
+ instance and its submodels."""
3340
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3341
+ model.load_fluxseries()
3342
+
3343
+ def load_stateseries(self) -> None:
3344
+ """Call method |Model.load_stateseries| of the currently handled |Model|
3345
+ instance and its submodels."""
3346
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3347
+ model.load_stateseries()
3348
+
3349
+ def save_allseries(self) -> None:
3350
+ """Call method |Model.save_allseries| of the currently handled |Model|
3351
+ instance and its submodels."""
3352
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3353
+ model.save_allseries()
3354
+
3355
+ def save_inputseries(self) -> None:
3356
+ """Call method |Model.save_inputseries| of the currently handled |Model|
3357
+ instance and its submodels."""
3358
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3359
+ model.save_inputseries()
3360
+
3361
+ def save_factorseries(self) -> None:
3362
+ """Call method |Model.save_factorseries| of the currently handled |Model|
3363
+ instance and its submodels."""
3364
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3365
+ model.save_factorseries()
3366
+
3367
+ def save_fluxseries(self) -> None:
3368
+ """Call method |Model.save_fluxseries| of the currently handled |Model|
3369
+ instance and its submodels."""
3370
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3371
+ model.save_fluxseries()
3372
+
3373
+ def save_stateseries(self) -> None:
3374
+ """Call method |Model.save_stateseries| of the currently handled |Model|
3375
+ instance and its submodels."""
3376
+ for model in self.model.find_submodels(include_mainmodel=True).values():
3377
+ model.save_stateseries()
3378
+
3379
+ def _plot_series(
3380
+ self,
3381
+ *,
3382
+ subseqs: sequencetools.IOSequences[
3383
+ sequencetools.Sequences,
3384
+ sequencetools.IOSequence,
3385
+ sequencetools.FastAccessIOSequence,
3386
+ ],
3387
+ sequences: tuple[IOSequenceArg, ...],
3388
+ average: bool,
3389
+ labels: tuple[str, ...] | None,
3390
+ colors: str | tuple[str, ...] | None,
3391
+ linestyles: LineStyle | tuple[LineStyle, ...] | None,
3392
+ linewidths: int | tuple[int, ...] | None,
3393
+ focus: bool,
3394
+ ) -> pyplot.Figure:
3395
+ def _prepare_tuple(
3396
+ input_: T | tuple[T, ...] | None, nmb_entries: int
3397
+ ) -> tuple[T | None, ...]:
3398
+ if isinstance(input_, tuple):
3399
+ return input_
3400
+ return nmb_entries * (input_,)
3401
+
3402
+ def _make_vectors(array: NDArrayFloat) -> list[NDArrayFloat]:
3403
+ vectors = []
3404
+ for idxs in itertools.product(*(range(shp) for shp in array.shape[1:])):
3405
+ vector = array
3406
+ for idx in idxs:
3407
+ vector = vector[:, idx]
3408
+ vectors.append(vector)
3409
+ return vectors
3410
+
3411
+ idx0, idx1 = hydpy.pub.timegrids.evalindices
3412
+ index = _get_pandasindex()[idx0:idx1]
3413
+ selseqs = self._query_iosequences(subseqs, sequences)
3414
+ nmb_sequences = len(selseqs)
3415
+ labels_: tuple[str | None, ...]
3416
+ if isinstance(labels, tuple):
3417
+ labels_ = labels
3418
+ else:
3419
+ labels_ = nmb_sequences * (labels,)
3420
+ for sequence, label, color, linestyle, linewidth in zip(
3421
+ selseqs,
3422
+ labels_,
3423
+ _prepare_tuple(colors, nmb_sequences),
3424
+ _prepare_tuple(linestyles, nmb_sequences),
3425
+ _prepare_tuple(linewidths, nmb_sequences),
3426
+ ):
3427
+ label_ = label if label else " ".join((self.name, type(sequence).__name__))
3428
+ if average:
3429
+ series = sequence.average_series()[idx0:idx1]
3430
+ label_ = f"{label_}, averaged"
3431
+ else:
3432
+ series = sequence.evalseries
3433
+ kwargs = {"label": label_, "ax": pyplot.gca()}
3434
+ if color is not None:
3435
+ kwargs["color"] = color
3436
+ if linestyle is not None:
3437
+ kwargs["linestyle"] = linestyle
3438
+ if linewidth is not None:
3439
+ kwargs["linewidth"] = linewidth
3440
+ if series.ndim == 1:
3441
+ ps = pandas.Series(series, index=index)
3442
+ ps.plot(**kwargs)
3443
+ elif all(length > 0 for length in series.shape[1:]):
3444
+ vectors = _make_vectors(series)
3445
+ ps = pandas.Series(vectors[0], index=index)
3446
+ axessubplot = ps.plot(**kwargs)
3447
+ kwargs["label"] = "None"
3448
+ kwargs["color"] = axessubplot.get_lines()[-1].get_color()
3449
+ for vector in vectors:
3450
+ ps = pandas.Series(vector, index=index)
3451
+ ps.plot(**kwargs)
3452
+ if color:
3453
+ kwargs["color"] = color
3454
+ else:
3455
+ del kwargs["color"]
3456
+ lines = [l for l in pyplot.legend().get_lines() if l.get_label() != "None"]
3457
+ pyplot.legend(handles=lines)
3458
+ if not focus:
3459
+ pyplot.ylim((0.0, None))
3460
+ return pyplot.gcf()
3461
+
3462
+ def _query_iosequences(
3463
+ self,
3464
+ subseqs: sequencetools.IOSequences[
3465
+ sequencetools.Sequences,
3466
+ sequencetools.IOSequence,
3467
+ sequencetools.FastAccessIOSequence,
3468
+ ],
3469
+ sequences: tuple[IOSequenceArg, ...],
3470
+ ) -> list[sequencetools.IOSequence]:
3471
+ models = tuple(self.model.find_submodels(include_mainmodel=True).values())
3472
+ if sequences:
3473
+ selseqs = []
3474
+ for sequence in sequences:
3475
+ typ: type[sequencetools.IOSequence] | None
3476
+ if isinstance(sequence, str):
3477
+ name = sequence
3478
+ typ = None
3479
+ else:
3480
+ name = sequence.name
3481
+ if isinstance(sequence, sequencetools.IOSequence):
3482
+ typ = type(sequence)
3483
+ else:
3484
+ typ = sequence
3485
+ for model in models:
3486
+ seq = getattr(model.sequences[subseqs.name], name, None)
3487
+ if (seq is not None) and ((typ is None) or isinstance(seq, typ)):
3488
+ selseqs.append(seq)
3489
+ break
3490
+ else:
3491
+ raise ValueError(
3492
+ f"No (sub)model handled by element `{self.name}` has "
3493
+ f"{'an' if subseqs.name == 'inputs' else 'a'} "
3494
+ f"{'flux' if subseqs.name == 'fluxes' else subseqs.name[:-1]} "
3495
+ f"sequence named `{name}`"
3496
+ f"{'' if typ is None else f' of type `{typ.__name__}'}."
3497
+ )
3498
+ return selseqs
3499
+ return list(
3500
+ itertools.chain(*(getattr(m.sequences, subseqs.name) for m in models))
3501
+ )
3502
+
3503
+ def plot_inputseries(
3504
+ self,
3505
+ *sequences: IOSequenceArg,
3506
+ average: bool = False,
3507
+ labels: tuple[str, ...] | None = None,
3508
+ colors: str | tuple[str, ...] | None = None,
3509
+ linestyles: LineStyle | tuple[LineStyle, ...] | None = None,
3510
+ linewidths: int | tuple[int, ...] | None = None,
3511
+ focus: bool = True,
3512
+ ) -> pyplot.Figure:
3513
+ """Plot (the selected) |InputSequence| |IOSequence.series| values.
3514
+
3515
+ We demonstrate the functionalities of method |Element.plot_inputseries| based
3516
+ on the `Lahn` example project:
3517
+
3518
+ >>> from hydpy.core.testtools import prepare_full_example_2
3519
+ >>> hp, pub, _ = prepare_full_example_2(lastdate="1997-01-01")
3520
+
3521
+ Without any arguments, |Element.plot_inputseries| prints the time series of all
3522
+ input sequences handled by its (sub)models directly to the screen (in our
3523
+ example, |hland_inputs.P| and |hland_inputs.T| of |hland_96| and
3524
+ |evap_inputs.NormalAirTemperature| and |evap_inputs.NormalEvapotranspiration|
3525
+ of |evap_pet_hbv96|):
3526
+
3527
+ >>> land = hp.elements.land_dill_assl
3528
+ >>> figure = land.plot_inputseries()
3529
+
3530
+ You can use the `pyplot` API of `matplotlib` to modify the returned figure or
3531
+ to save it to disk (or print it to the screen, in case the interactive mode of
3532
+ `matplotlib` is disabled):
3533
+
3534
+ >>> from hydpy.core.testtools import save_autofig
3535
+ >>> save_autofig("Element_plot_inputseries_complete.png", figure)
3536
+
3537
+ .. image:: Element_plot_inputseries_complete.png
3538
+
3539
+ Select specific sequences by passing their names, types, or example objects:
3540
+
3541
+ >>> from hydpy.models.hland.hland_inputs import T
3542
+ >>> net = land.model.aetmodel.petmodel.sequences.inputs.normalevapotranspiration
3543
+ >>> figure = land.plot_inputseries("p", T, net)
3544
+ >>> save_autofig("Element_plot_inputseries_selection.png", figure)
3545
+
3546
+ .. image:: Element_plot_inputseries_selection.png
3547
+
3548
+ Misleading sequence specifiers result in the following error:
3549
+
3550
+ >>> figure = land.plot_inputseries("xy")
3551
+ Traceback (most recent call last):
3552
+ ...
3553
+ ValueError: No (sub)model handled by element `land_dill_assl` has an input \
3554
+ sequence named `xy`.
3555
+
3556
+ Methods |Element.plot_factorseries|, |Element.plot_fluxseries|, and
3557
+ |Element.plot_stateseries| work in the same manner. Before applying them, one
3558
+ has to calculate the time series of the |FactorSequence|, |FluxSequence|, and
3559
+ |StateSequence| objects:
3560
+
3561
+ >>> hp.simulate()
3562
+
3563
+ The arguments "labels," "colours," "line styles," and "line widths" can accept
3564
+ general or individual values:
3565
+
3566
+ >>> figure = land.plot_fluxseries(
3567
+ ... "q0", "q1", labels=("direct runoff", "base flow"),
3568
+ ... colors=("red", "green"), linestyles="--", linewidths=2)
3569
+ >>> save_autofig("Element_plot_fluxseries.png", figure)
3570
+
3571
+ .. image:: Element_plot_fluxseries.png
3572
+
3573
+ For 1- and 2-dimensional |IOSequence| objects, all three methods plot the
3574
+ individual time series in the same colour. We demonstrate this for the frozen
3575
+ (|hland_states.SP|) and the liquid (|hland_states.WC|) water equivalent of the
3576
+ snow cover of different hydrological response units. Therefore, we restrict
3577
+ the shown period to February and March via the |Timegrids.eval_| time grid:
3578
+
3579
+ >>> with pub.timegrids.eval_(firstdate="1996-02-01", lastdate="1996-04-01"):
3580
+ ... figure = land.plot_stateseries("sp", "wc")
3581
+ >>> save_autofig("Element_plot_stateseries.png", figure)
3582
+
3583
+ .. image:: Element_plot_stateseries.png
3584
+
3585
+ Alternatively, you can print the averaged time series by assigning |True| to the
3586
+ argument `average`. We demonstrate this functionality for the factor sequence
3587
+ |hland_factors.TC| (this time, without focusing on the time-series y-extent):
3588
+
3589
+ >>> figure = land.plot_factorseries("tc", colors=("grey",))
3590
+ >>> figure = land.plot_factorseries(
3591
+ ... "tc", average=True, focus=False, colors="black", linewidths=3)
3592
+ >>> save_autofig("Element_plot_factorseries.png", figure)
3593
+
3594
+ .. image:: Element_plot_factorseries.png
3595
+ """
3596
+ return self._plot_series(
3597
+ subseqs=self.model.sequences.inputs,
3598
+ sequences=sequences,
3599
+ average=average,
3600
+ labels=labels,
3601
+ colors=colors,
3602
+ linestyles=linestyles,
3603
+ linewidths=linewidths,
3604
+ focus=focus,
3605
+ )
3606
+
3607
+ def plot_factorseries(
3608
+ self,
3609
+ *sequences: IOSequenceArg,
3610
+ average: bool = False,
3611
+ labels: tuple[str, ...] | None = None,
3612
+ colors: str | tuple[str, ...] | None = None,
3613
+ linestyles: LineStyle | tuple[LineStyle, ...] | None = None,
3614
+ linewidths: int | tuple[int, ...] | None = None,
3615
+ focus: bool = True,
3616
+ ) -> pyplot.Figure:
3617
+ """Plot the `factor` series of the handled model.
3618
+
3619
+ See the documentation on method |Element.plot_inputseries| for additional
3620
+ information.
3621
+ """
3622
+ return self._plot_series(
3623
+ subseqs=self.model.sequences.factors,
3624
+ sequences=sequences,
3625
+ average=average,
3626
+ labels=labels,
3627
+ colors=colors,
3628
+ linestyles=linestyles,
3629
+ linewidths=linewidths,
3630
+ focus=focus,
3631
+ )
3632
+
3633
+ def plot_fluxseries(
3634
+ self,
3635
+ *sequences: IOSequenceArg,
3636
+ average: bool = False,
3637
+ labels: tuple[str, ...] | None = None,
3638
+ colors: str | tuple[str, ...] | None = None,
3639
+ linestyles: LineStyle | tuple[LineStyle, ...] | None = None,
3640
+ linewidths: int | tuple[int, ...] | None = None,
3641
+ focus: bool = True,
3642
+ ) -> pyplot.Figure:
3643
+ """Plot the `flux` series of the handled model.
3644
+
3645
+ See the documentation on method |Element.plot_inputseries| for additional
3646
+ information.
3647
+ """
3648
+ return self._plot_series(
3649
+ subseqs=self.model.sequences.fluxes,
3650
+ sequences=sequences,
3651
+ average=average,
3652
+ labels=labels,
3653
+ colors=colors,
3654
+ linestyles=linestyles,
3655
+ linewidths=linewidths,
3656
+ focus=focus,
3657
+ )
3658
+
3659
+ def plot_stateseries(
3660
+ self,
3661
+ *sequences: IOSequenceArg,
3662
+ average: bool = False,
3663
+ labels: tuple[str, ...] | None = None,
3664
+ colors: str | tuple[str, ...] | None = None,
3665
+ linestyles: LineStyle | tuple[LineStyle, ...] | None = None,
3666
+ linewidths: int | tuple[int, ...] | None = None,
3667
+ focus: bool = True,
3668
+ ) -> pyplot.Figure:
3669
+ """Plot the `state` series of the handled model.
3670
+
3671
+ See the documentation on method |Element.plot_inputseries| for additional
3672
+ information.
3673
+ """
3674
+ return self._plot_series(
3675
+ subseqs=self.model.sequences.states,
3676
+ sequences=sequences,
3677
+ average=average,
3678
+ labels=labels,
3679
+ colors=colors,
3680
+ linestyles=linestyles,
3681
+ linewidths=linewidths,
3682
+ focus=focus,
3683
+ )
3684
+
3685
+ def assignrepr(self, prefix: str) -> str:
3686
+ """Return a |repr| string with a prefixed assignment."""
3687
+ with objecttools.repr_.preserve_strings(True):
3688
+ with objecttools.assignrepr_tuple.always_bracketed(False):
3689
+ blanks = " " * (len(prefix) + 8)
3690
+ lines = [f'{prefix}Element("{self.name}",']
3691
+ if (collective := self.collective) is not None:
3692
+ lines.append(f'{blanks}collective="{collective}",')
3693
+ for groupname in (
3694
+ "inlets",
3695
+ "outlets",
3696
+ "receivers",
3697
+ "senders",
3698
+ "inputs",
3699
+ "outputs",
3700
+ ):
3701
+ group = getattr(self, groupname, None)
3702
+ if group:
3703
+ subprefix = f"{blanks}{groupname}="
3704
+ nodes = [str(node) for node in group]
3705
+ line = objecttools.assignrepr_list(nodes, subprefix, width=70)
3706
+ lines.append(line + ",")
3707
+ if self.keywords:
3708
+ subprefix = f"{blanks}keywords="
3709
+ line = objecttools.assignrepr_list(
3710
+ sorted(self.keywords), subprefix, width=70
3711
+ )
3712
+ lines.append(line + ",")
3713
+ lines[-1] = lines[-1][:-1] + ")"
3714
+ return "\n".join(lines)
3715
+
3716
+ def __repr__(self) -> str:
3717
+ return self.assignrepr("")
3718
+
3719
+
3720
+ _id2devices: dict[Device, dict[int, Devices[Device]]] = {}
3721
+ _registry: Mapping[type[Device], dict[str, Device]] = {Node: {}, Element: {}}
3722
+ _selection: Mapping[type[Device], dict[str, Device]] = {Node: {}, Element: {}}
3723
+
3724
+
3725
+ @contextlib.contextmanager
3726
+ def clear_registries_temporarily() -> Generator[None, None, None]:
3727
+ """Context manager for clearing the current |Node|, |Element|, and |FusedVariable|
3728
+ registries.
3729
+
3730
+ Function |clear_registries_temporarily| is only available for testing purposes.
3731
+
3732
+ These are the relevant registries for the currently initialised |Node|, |Element|,
3733
+ and |FusedVariable| objects:
3734
+
3735
+ >>> from hydpy.core import devicetools
3736
+ >>> registries = (devicetools._id2devices,
3737
+ ... devicetools._registry[devicetools.Node],
3738
+ ... devicetools._registry[devicetools.Element],
3739
+ ... devicetools._selection[devicetools.Node],
3740
+ ... devicetools._selection[devicetools.Element],
3741
+ ... devicetools._registry_fusedvariable)
3742
+
3743
+ We first clear them and, just for testing, insert some numbers:
3744
+
3745
+ >>> for idx, registry in enumerate(registries):
3746
+ ... registry.clear()
3747
+ ... registry[idx] = idx+1
3748
+
3749
+ Within the `with` block, all registries are empty:
3750
+
3751
+ >>> with devicetools.clear_registries_temporarily():
3752
+ ... for registry in registries:
3753
+ ... print(registry)
3754
+ {}
3755
+ {}
3756
+ {}
3757
+ {}
3758
+ {}
3759
+ {}
3760
+
3761
+ Before leaving the `with` block, the |clear_registries_temporarily| method restores
3762
+ the contents of each dictionary:
3763
+
3764
+ >>> for registry in registries:
3765
+ ... print(registry)
3766
+ ... registry.clear()
3767
+ {0: 1}
3768
+ {1: 2}
3769
+ {2: 3}
3770
+ {3: 4}
3771
+ {4: 5}
3772
+ {5: 6}
3773
+ """
3774
+ registries: tuple[dict[Any, Any], ...] = (
3775
+ _id2devices,
3776
+ _registry[Node],
3777
+ _registry[Element],
3778
+ _selection[Node],
3779
+ _selection[Element],
3780
+ _registry_fusedvariable,
3781
+ )
3782
+ copies = tuple(copy.copy(registry) for registry in registries)
3783
+ try:
3784
+ for registry in registries:
3785
+ registry.clear()
3786
+ yield
3787
+ finally:
3788
+ for registry, copy_ in zip(registries, copies):
3789
+ registry.update(copy_)
3790
+
3791
+
3792
+ def _get_pandasindex() -> pandas.Index:
3793
+ """
3794
+ >>> from hydpy import pub
3795
+ >>> pub.timegrids = "2004.01.01", "2005.01.01", "1d"
3796
+ >>> from hydpy.core.devicetools import _get_pandasindex
3797
+ >>> _get_pandasindex() # doctest: +ELLIPSIS
3798
+ DatetimeIndex(['2004-01-01 12:00:00', '2004-01-02 12:00:00',
3799
+ ...
3800
+ '2004-12-30 12:00:00', '2004-12-31 12:00:00'],
3801
+ dtype='datetime64[ns]', length=366, freq=None)
3802
+ """
3803
+ tg = hydpy.pub.timegrids.init
3804
+ shift = tg.stepsize / 2
3805
+ index = pandas.date_range(
3806
+ (tg.firstdate + shift).datetime,
3807
+ (tg.lastdate - shift).datetime,
3808
+ int((tg.lastdate - tg.firstdate - tg.stepsize) / tg.stepsize) + 1,
3809
+ )
3810
+ return index