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,2483 @@
1
+ """This module implements tools for testing *HydPy* and its models."""
2
+
3
+ # import...
4
+ # ...from standard library
5
+ from __future__ import annotations
6
+ import abc
7
+ import builtins
8
+ import contextlib
9
+ import copy
10
+ import datetime
11
+ import doctest
12
+ import importlib
13
+ import inspect
14
+ import io
15
+ import itertools
16
+ import os
17
+ import shutil
18
+ import sys
19
+ import types
20
+ import warnings
21
+
22
+ # ...from site-packages
23
+ import numpy
24
+
25
+ # ...from HydPy
26
+ import hydpy
27
+ from hydpy import config
28
+ from hydpy import data
29
+ from hydpy import docs
30
+ from hydpy.docs import autofigs
31
+ from hydpy.core import devicetools
32
+ from hydpy.core import exceptiontools
33
+ from hydpy.core import filetools
34
+ from hydpy.core import hydpytools
35
+ from hydpy.core import importtools
36
+ from hydpy.core import modeltools
37
+ from hydpy.core import objecttools
38
+ from hydpy.core import pubtools
39
+ from hydpy.core import sequencetools
40
+ from hydpy.core import timetools
41
+ from hydpy.core import typingtools
42
+ from hydpy.core import variabletools
43
+ from hydpy.core.typingtools import *
44
+ from hydpy.tests import iotesting
45
+
46
+ # from hydpy.models import hland actual import below
47
+ # from hydpy.models import lland actual import below
48
+
49
+
50
+ if TYPE_CHECKING:
51
+ import matplotlib
52
+ from matplotlib import pyplot
53
+ import pandas
54
+ import plotly
55
+ from plotly import subplots
56
+
57
+ class TestIOSequence(sequencetools.IOSequence):
58
+ """|IOSequence| subclass for testing purposes."""
59
+
60
+ testarray: NDArrayFloat
61
+ descr_device = "just_for_testing"
62
+ descr_sequence = "just_for_testing"
63
+
64
+ else:
65
+ matplotlib = exceptiontools.OptionalImport("matplotlib", ["matplotlib"], locals())
66
+ pyplot = exceptiontools.OptionalImport("pyplot", ["matplotlib.pyplot"], locals())
67
+ pandas = exceptiontools.OptionalImport("pandas", ["pandas"], locals())
68
+ plotly = exceptiontools.OptionalImport("plotly", ["plotly"], locals())
69
+ subplots = exceptiontools.OptionalImport("subplots", ["plotly.subplots"], locals())
70
+
71
+
72
+ class StdOutErr:
73
+ """Replaces `sys.stdout` and `sys.stderr` temporarily when calling
74
+ method |Tester.perform_tests| of class |Tester|."""
75
+
76
+ indent: int
77
+ texts: list[str]
78
+
79
+ def __init__(self, indent: int = 0):
80
+ self.indent = indent
81
+ self.stdout = sys.stdout
82
+ self.stderr = sys.stderr
83
+ self.encoding = sys.stdout.encoding
84
+ self.texts = []
85
+
86
+ def __enter__(self) -> None:
87
+ self.encoding = sys.stdout.encoding
88
+ # just for testing:
89
+ sys.stdout = self
90
+ sys.stderr = self
91
+
92
+ def __exit__(
93
+ self,
94
+ exception_type: type[BaseException],
95
+ exception_value: BaseException,
96
+ traceback: types.TracebackType,
97
+ ) -> None:
98
+ if not self.texts:
99
+ self.print_("no failures occurred")
100
+ else:
101
+ for text in self.texts:
102
+ self.print_(text)
103
+ sys.stdout = self.stdout
104
+ sys.stderr = self.stderr
105
+
106
+ def write(self, text: str) -> None:
107
+ """Memorise the given text for later writing."""
108
+ self.texts.extend(text.split("\n"))
109
+
110
+ def print_(self, text: str) -> None:
111
+ """Print the memorised text to the original `sys.stdout`."""
112
+ if text.strip():
113
+ self.stdout.write(self.indent * " " + text + "\n")
114
+
115
+ def flush(self) -> None:
116
+ """Do nothing."""
117
+
118
+
119
+ class Tester:
120
+ """Tests either a base or an application model.
121
+
122
+ Usually, a |Tester| object is initialised at the end of the `__init__`
123
+ file of its base model or the end of the module of an application model.
124
+
125
+ >>> from hydpy.models import hland, hland_96
126
+
127
+ >>> hland.tester.package
128
+ 'hydpy.models.hland'
129
+ >>> hland_96.tester.package
130
+ 'hydpy.models'
131
+ """
132
+
133
+ filepath: str
134
+ package: str
135
+ ispackage: bool
136
+
137
+ def __init__(self) -> None:
138
+ frame = inspect.currentframe()
139
+ assert isinstance(frame, types.FrameType)
140
+ frame = frame.f_back
141
+ assert isinstance(frame, types.FrameType)
142
+ self.filepath = frame.f_code.co_filename
143
+ self.package = frame.f_locals["__package__"]
144
+ self.ispackage = os.path.split(self.filepath)[-1] == "__init__.py"
145
+
146
+ @property
147
+ def filenames(self) -> list[str]:
148
+ """The filenames which define the considered base or application model.
149
+
150
+ >>> from hydpy.models import hland, hland_96
151
+ >>> from pprint import pprint
152
+ >>> pprint(hland.tester.filenames)
153
+ ['__init__.py',
154
+ 'hland_aides.py',
155
+ 'hland_constants.py',
156
+ 'hland_control.py',
157
+ 'hland_derived.py',
158
+ 'hland_factors.py',
159
+ 'hland_fixed.py',
160
+ 'hland_fluxes.py',
161
+ 'hland_inputs.py',
162
+ 'hland_masks.py',
163
+ 'hland_model.py',
164
+ 'hland_outlets.py',
165
+ 'hland_parameters.py',
166
+ 'hland_sequences.py',
167
+ 'hland_states.py']
168
+ >>> hland_96.tester.filenames
169
+ ['hland_96.py']
170
+ """
171
+ if self.ispackage:
172
+ filenames = os.listdir(os.path.dirname(self.filepath))
173
+ return sorted(fn for fn in filenames if fn.endswith(".py"))
174
+ return [os.path.split(self.filepath)[1]]
175
+
176
+ @property
177
+ def modulenames(self) -> list[str]:
178
+ """The module names to be taken into account for testing.
179
+
180
+ >>> from hydpy.models import hland, hland_96
181
+ >>> from pprint import pprint
182
+ >>> pprint(hland.tester.modulenames)
183
+ ['hland_aides',
184
+ 'hland_constants',
185
+ 'hland_control',
186
+ 'hland_derived',
187
+ 'hland_factors',
188
+ 'hland_fixed',
189
+ 'hland_fluxes',
190
+ 'hland_inputs',
191
+ 'hland_masks',
192
+ 'hland_model',
193
+ 'hland_outlets',
194
+ 'hland_parameters',
195
+ 'hland_sequences',
196
+ 'hland_states']
197
+ >>> hland_96.tester.modulenames
198
+ ['hland_96']
199
+ """
200
+ return [
201
+ os.path.split(fn)[-1].split(".")[0]
202
+ for fn in self.filenames
203
+ if (fn.endswith(".py") and not fn.startswith("_"))
204
+ ]
205
+
206
+ def perform_tests(self) -> None:
207
+ """Perform all doctests either in Python or in Cython mode depending
208
+ on the state of |Options.usecython| set in module |pub|.
209
+
210
+ Usually, |Tester.perform_tests| is triggered automatically by a |Cythonizer|
211
+ object assigned to the same base or application model as a |Tester| object.
212
+ However, you are free to call it any time when in doubt of the functionality
213
+ of a particular base or application model. Doing so might change some of the
214
+ states of your current configuration, but only temporarily (besides
215
+ "projectname") we pick the |Timegrids| object of module |pub| as an example,
216
+ which is changed multiple times during testing but finally reset to the
217
+ original value):
218
+
219
+ >>> from hydpy import pub
220
+ >>> pub.projectname = "test"
221
+ >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
222
+
223
+ >>> from hydpy.models import hland, hland_96
224
+ >>> hland.tester.perform_tests() # doctest: +ELLIPSIS
225
+ Test package hydpy.models.hland in ...ython mode.
226
+ * hland_aides:
227
+ no failures occurred
228
+ * hland_constants:
229
+ no failures occurred
230
+ * hland_control:
231
+ no failures occurred
232
+ * hland_derived:
233
+ no failures occurred
234
+ * hland_factors:
235
+ no failures occurred
236
+ * hland_fixed:
237
+ no failures occurred
238
+ * hland_fluxes:
239
+ no failures occurred
240
+ * hland_inputs:
241
+ no failures occurred
242
+ * hland_masks:
243
+ no failures occurred
244
+ * hland_model:
245
+ no failures occurred
246
+ * hland_outlets:
247
+ no failures occurred
248
+ * hland_parameters:
249
+ no failures occurred
250
+ * hland_sequences:
251
+ no failures occurred
252
+ * hland_states:
253
+ no failures occurred
254
+
255
+ >>> hland_96.tester.perform_tests() # doctest: +ELLIPSIS
256
+ Test module hland_96 in ...ython mode.
257
+ * hland_96:
258
+ no failures occurred
259
+
260
+ >>> pub.projectname
261
+ 'test'
262
+ >>> pub.timegrids
263
+ Timegrids("2000-01-01 00:00:00",
264
+ "2001-01-01 00:00:00",
265
+ "1d")
266
+
267
+ To show the reporting of possible errors, we change the string representation
268
+ of parameter |hland_control.ZoneType| temporarily. Again, the |Timegrids|
269
+ object is reset to its initial state after testing:
270
+
271
+ >>> from unittest import mock
272
+ >>> with mock.patch(
273
+ ... "hydpy.models.hland.hland_control.ZoneType.__repr__",
274
+ ... return_value="damaged"):
275
+ ... hland.tester.perform_tests() # doctest: +ELLIPSIS
276
+ Test package hydpy.models.hland in ...ython mode.
277
+ * hland_aides:
278
+ no failures occurred
279
+ * hland_constants:
280
+ no failures occurred
281
+ * hland_control:
282
+ ******...hland_control.py", line ..., in \
283
+ hydpy.models.hland.hland_control.ZoneType
284
+ Failed example:
285
+ zonetype
286
+ Expected:
287
+ zonetype(FIELD, FOREST, GLACIER, ILAKE, ILAKE, FIELD)
288
+ Got:
289
+ damaged
290
+ ************************************************************\
291
+ **********
292
+ ...
293
+ * hland_derived:
294
+ no failures occurred
295
+ ...
296
+ * hland_states:
297
+ no failures occurred
298
+
299
+ >>> pub.projectname
300
+ 'test'
301
+ >>> pub.timegrids
302
+ Timegrids("2000-01-01 00:00:00",
303
+ "2001-01-01 00:00:00",
304
+ "1d")
305
+ """
306
+ opt = hydpy.pub.options
307
+ print(
308
+ f"Test {'package' if self.ispackage else 'module'} "
309
+ f"{self.package if self.ispackage else self.modulenames[0]} "
310
+ f"in {'C' if hydpy.pub.options.usecython else 'P'}ython mode."
311
+ )
312
+ for name in self.modulenames:
313
+ print(f" * {name}:")
314
+ with (
315
+ StdOutErr(indent=8),
316
+ opt.ellipsis(0),
317
+ opt.printprogress(False),
318
+ opt.reprdigits(6),
319
+ opt.usedefaultvalues(False),
320
+ opt.utclongitude(15),
321
+ opt.utcoffset(60),
322
+ opt.timestampleft(True),
323
+ opt.warnsimulationstep(False),
324
+ opt.warntrim(False),
325
+ opt.parameterstep(timetools.Period("1d")),
326
+ opt.simulationstep(timetools.Period()),
327
+ devicetools.clear_registries_temporarily(),
328
+ ):
329
+ projectname = exceptiontools.getattr_(
330
+ hydpy.pub, "projectname", None, str
331
+ )
332
+ del hydpy.pub.projectname
333
+ timegrids = exceptiontools.getattr_(hydpy.pub, "timegrids", None)
334
+ del hydpy.pub.timegrids
335
+ plotting_options = IntegrationTest.plotting_options
336
+ IntegrationTest.plotting_options = PlottingOptions()
337
+ try:
338
+ modulename = ".".join((self.package, name))
339
+ module = importlib.import_module(modulename)
340
+ with warnings.catch_warnings():
341
+ doctest.testmod(
342
+ module,
343
+ extraglobs={"testing": True},
344
+ optionflags=doctest.ELLIPSIS,
345
+ )
346
+ finally:
347
+ if projectname is not None:
348
+ hydpy.pub.projectname = projectname
349
+ if timegrids is not None:
350
+ hydpy.pub.timegrids = timegrids
351
+ IntegrationTest.plotting_options = plotting_options
352
+
353
+
354
+ class Array:
355
+ """Assures that attributes are |numpy.ndarray| objects."""
356
+
357
+ def __setattr__(self, name: str, value: NDArrayFloat) -> None:
358
+ object.__setattr__(self, name, numpy.array(value))
359
+
360
+
361
+ class ArrayDescriptor:
362
+ """A descriptor for handling values of |Array| objects."""
363
+
364
+ def __init__(self) -> None:
365
+ self.values = Array()
366
+
367
+ def __set__(
368
+ self,
369
+ obj: Test,
370
+ sequence2value: (
371
+ Sequence[tuple[sequencetools.ConditionSequence, ArrayFloat]]
372
+ ) | None,
373
+ ) -> None:
374
+ self.__delete__(obj)
375
+ if sequence2value is not None:
376
+ names = [value[0].name for value in sequence2value]
377
+ duplicates = tuple(name for name in set(names) if names.count(name) > 1)
378
+ for i, (name, (seq, _)) in enumerate(tuple(zip(names, sequence2value))):
379
+ if name in duplicates:
380
+ names[i] = f"{name}_{objecttools.devicename(seq)}"
381
+ duplicates = tuple(name for name in set(names) if names.count(name) > 1)
382
+ for i, (name, (seq, _)) in enumerate(tuple(zip(names, sequence2value))):
383
+ if name in duplicates:
384
+ names[i] = f"{name}_{id(seq)}"
385
+ for name, (_, value) in zip(names, sequence2value):
386
+ setattr(self.values, name, value)
387
+
388
+ def __get__(self, obj: Test, type_: type[Test] | None = None) -> Array:
389
+ return self.values
390
+
391
+ def __delete__(self, obj: Test) -> None:
392
+ for name in tuple(vars(self.values).keys()):
393
+ delattr(self.values, name)
394
+
395
+
396
+ class Test:
397
+ """Base class for |IntegrationTest| and |UnitTest|.
398
+
399
+ This base class defines the printing of the test results primarily.
400
+ How the tests shall be prepared and performed is to be defined in
401
+ its subclasses.
402
+ """
403
+
404
+ parseqs: Any
405
+ HEADER_OF_FIRST_COL: Any
406
+
407
+ inits = ArrayDescriptor()
408
+ """Stores arrays for setting the same values of parameters and/or sequences before
409
+ each new experiment."""
410
+
411
+ @property
412
+ @abc.abstractmethod
413
+ def raw_first_col_strings(self) -> tuple[str, ...]:
414
+ """To be implemented by the subclasses of |Test|."""
415
+
416
+ @abc.abstractmethod
417
+ def get_output_array(self, parseqs):
418
+ """To be implemented by the subclasses of |Test|."""
419
+
420
+ @property
421
+ def nmb_rows(self) -> int:
422
+ """The number of rows of the table."""
423
+ return len(self.raw_first_col_strings) + 1
424
+
425
+ @property
426
+ def nmb_cols(self) -> int:
427
+ """The number of columns in the table."""
428
+ nmb = 1
429
+ for parseq in self.parseqs:
430
+ nmb += max(parseq.numberofvalues, 1)
431
+ return nmb
432
+
433
+ @property
434
+ def raw_header_strings(self) -> list[str]:
435
+ """All raw strings for the tables header."""
436
+ strings = [self.HEADER_OF_FIRST_COL]
437
+ for parseq in self.parseqs:
438
+ for dummy in range(parseq.numberofvalues - 1):
439
+ strings.append("")
440
+ if (parseq.name == "sim") and isinstance(parseq, sequencetools.Sequence_):
441
+ strings.append(parseq.subseqs.node.name)
442
+ else:
443
+ strings.append(parseq.name)
444
+ return strings
445
+
446
+ @property
447
+ def raw_body_strings(self) -> list[list[str]]:
448
+ """All raw strings for the body of the table."""
449
+ strings = []
450
+ for idx, first_string in enumerate(self.raw_first_col_strings):
451
+ strings.append([first_string])
452
+ for parseq in self.parseqs:
453
+ array = self.get_output_array(parseq)
454
+ if parseq.NDIM == 0:
455
+ strings[-1].append(objecttools.repr_(array[idx]))
456
+ elif len(parseq) == 0:
457
+ strings[-1].append("-")
458
+ else:
459
+ strings[-1].extend(
460
+ objecttools.repr_(value) for value in array[idx].flatten()
461
+ )
462
+ return strings
463
+
464
+ @property
465
+ def raw_strings(self) -> list[list[str]]:
466
+ """All raw strings for the complete table."""
467
+ return [self.raw_header_strings] + self.raw_body_strings
468
+
469
+ @property
470
+ def col_widths(self) -> list[int]:
471
+ """The widths of all columns of the table."""
472
+ strings = self.raw_strings
473
+ widths: list[int] = []
474
+ for jdx in range(self.nmb_cols):
475
+ widths.append(0)
476
+ for idx in range(self.nmb_rows):
477
+ widths[-1] = max(len(strings[idx][jdx]), widths[-1])
478
+ return widths
479
+
480
+ @property
481
+ def col_separators(self) -> list[str]:
482
+ """The separators for adjacent columns."""
483
+ seps = ["| "]
484
+ for parseq in self.parseqs:
485
+ seps.append(" | ")
486
+ for dummy in range(parseq.numberofvalues - 1):
487
+ seps.append(" ")
488
+ seps.append(" |")
489
+ return seps
490
+
491
+ @property
492
+ def row_nmb_characters(self) -> int:
493
+ """The number of characters of a single row of the table."""
494
+ return sum(self.col_widths) + sum(len(sep) for sep in self.col_separators)
495
+
496
+ @staticmethod
497
+ def _interleave(
498
+ separators: Sequence[str], strings: Iterable[str], widths: Iterable[int]
499
+ ) -> str:
500
+ """Generate a table line from the given arguments."""
501
+ lst = [
502
+ value
503
+ for (separator, string, width) in zip(separators, strings, widths)
504
+ for value in (separator, string.rjust(width))
505
+ ]
506
+ lst.append(separators[-1])
507
+ return "".join(lst)
508
+
509
+ def make_table(self, idx1: int | None = None, idx2: int | None = None) -> str:
510
+ """Return the result table between the given indices."""
511
+ lines = []
512
+ col_widths = self.col_widths
513
+ col_separators = self.col_separators
514
+ lines.append(
515
+ self._interleave(self.col_separators, self.raw_header_strings, col_widths)
516
+ )
517
+ lines.append("-" * self.row_nmb_characters)
518
+ for strings_in_line in self.raw_body_strings[idx1:idx2]:
519
+ lines.append(self._interleave(col_separators, strings_in_line, col_widths))
520
+ return "\n".join(lines)
521
+
522
+ def print_table(self, idx1: int | None = None, idx2: int | None = None) -> None:
523
+ """Print the result table between the given indices."""
524
+ print(self.make_table(idx1=idx1, idx2=idx2))
525
+
526
+
527
+ class PlottingOptions:
528
+ """Plotting options of class |IntegrationTest|."""
529
+
530
+ width: int
531
+ height: int
532
+ axis1: typingtools.MayNonerable1[sequencetools.IOSequence]
533
+ axis2: typingtools.MayNonerable1[sequencetools.IOSequence]
534
+ activated: tuple[sequencetools.IOSequence, ...] | None
535
+
536
+ def __init__(self) -> None:
537
+ self.width = 600
538
+ self.height = 300
539
+ self.selected = None
540
+ self.activated = None
541
+ self.axis1 = None
542
+ self.axis2 = None
543
+
544
+
545
+ class IntegrationTest(Test):
546
+ """Defines model integration doctests.
547
+
548
+ The functionality of |IntegrationTest| is easiest to understand by inspecting
549
+ doctests like the ones of modules |arma_rimorido|.
550
+
551
+ Note that all condition sequences (state and logging sequences) are initialised in
552
+ accordance with the values are given as `inits` values. The values of the
553
+ simulation sequences of outlet and sender nodes are always set to zero before each
554
+ test run. All other parameter and sequence values can be changed between different
555
+ test runs.
556
+ """
557
+
558
+ HEADER_OF_FIRST_COL = "date"
559
+ """The header of the first column containing dates."""
560
+
561
+ plotting_options = PlottingOptions()
562
+ element: devicetools.Element
563
+ elements: devicetools.Devices[devicetools.Element]
564
+ nodes: devicetools.Devices[devicetools.Node]
565
+ parseqs: tuple[sequencetools.IOSequence, ...]
566
+
567
+ def __init__(
568
+ self,
569
+ element: devicetools.Element | None = None,
570
+ seqs: tuple[sequencetools.IOSequence, ...] | None = None,
571
+ inits=None,
572
+ ) -> None:
573
+ """Prepare the element and its nodes, put them into a HydPy object, and make
574
+ their sequences ready for use for integration testing."""
575
+ del self.inits
576
+ self.elements = devicetools.Element.query_all()
577
+ self.nodes = devicetools.Node.query_all()
578
+ self.hydpy = hydpytools.HydPy()
579
+ self.hydpy.update_devices(nodes=self.nodes, elements=self.elements)
580
+ if element is None:
581
+ self.element = self.hydpy.collectives[0]
582
+ else:
583
+ self.element = element
584
+ self.model = self.element.model
585
+ self.prepare_node_sequences()
586
+ self.prepare_input_model_sequences()
587
+ self.parseqs = seqs if seqs else self.extract_print_sequences()
588
+ self.inits = inits
589
+ self._src = None
590
+
591
+ @overload
592
+ def __call__(
593
+ self,
594
+ filename: str | None = None,
595
+ *,
596
+ axis1: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
597
+ axis2: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
598
+ update_parameters: bool = True,
599
+ get_conditions: Literal[None] = ...,
600
+ use_conditions: ConditionsModel | None = None,
601
+ ) -> None:
602
+ """do not return conditions"""
603
+
604
+ @overload
605
+ def __call__(
606
+ self,
607
+ filename: str | None = None,
608
+ *,
609
+ axis1: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
610
+ axis2: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
611
+ update_parameters: bool = True,
612
+ get_conditions: timetools.DateConstrArg,
613
+ use_conditions: ConditionsModel | None,
614
+ ) -> ConditionsModel:
615
+ """do return conditions"""
616
+
617
+ def __call__(
618
+ self,
619
+ filename: str | None = None,
620
+ *,
621
+ axis1: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
622
+ axis2: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
623
+ update_parameters: bool = True,
624
+ get_conditions: timetools.DateConstrArg | None = None,
625
+ use_conditions: ConditionsModel | None = None,
626
+ ) -> ConditionsModel | None:
627
+ """Prepare and perform an integration test and print and eventually plot its
628
+ results.
629
+
630
+ Note that the conditions defined under |IntegrationTest.inits| override the
631
+ ones given via keyword `use_conditions`.
632
+ """
633
+ self.prepare_model(
634
+ update_parameters=update_parameters, use_conditions=use_conditions
635
+ )
636
+ seq2value = self._perform_simulation(get_conditions)
637
+ self.print_table()
638
+ if filename:
639
+ self.plot(filename=filename, axis1=axis1, axis2=axis2)
640
+ return seq2value
641
+
642
+ def _perform_simulation(
643
+ self, get_conditions: timetools.DateConstrArg | None
644
+ ) -> ConditionsModel | None:
645
+ if get_conditions:
646
+ sim = copy.deepcopy(hydpy.pub.timegrids.sim)
647
+ date = timetools.Date(get_conditions)
648
+ if date > hydpy.pub.timegrids.init.firstdate:
649
+ hydpy.pub.timegrids.sim.lastdate = date
650
+ self.hydpy.simulate()
651
+ conditions = self.element.model.conditions
652
+ if date < hydpy.pub.timegrids.init.lastdate:
653
+ hydpy.pub.timegrids.sim.dates = date, sim.lastdate
654
+ self.hydpy.simulate()
655
+ hydpy.pub.timegrids.sim.firstdate = sim.firstdate
656
+ return conditions
657
+ self.hydpy.simulate()
658
+ return None
659
+
660
+ @property
661
+ def _datetimes(self) -> tuple[datetime.datetime, ...]:
662
+ return tuple(date.datetime for date in hydpy.pub.timegrids.sim)
663
+
664
+ @property
665
+ def raw_first_col_strings(self) -> tuple[str, ...]:
666
+ """The raw date strings of the first column, except the header."""
667
+ return tuple(_.strftime(self.dateformat) for _ in self._datetimes)
668
+
669
+ @property
670
+ def dateformat(self) -> str:
671
+ """Format string for printing dates in the first column of the table.
672
+
673
+ See the documentation on module |datetime| for the format strings allowed.
674
+
675
+ You can query and change property |IntegrationTest.dateformat|:
676
+
677
+ >>> from hydpy import Element, IntegrationTest, prepare_model, pub
678
+ >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
679
+ >>> element = Element("element", outlets="node")
680
+ >>> element.model = prepare_model("hland_96")
681
+ >>> __package__ = "testpackage"
682
+ >>> tester = IntegrationTest(element)
683
+ >>> tester.dateformat
684
+ '%Y-%m-%d %H:%M:%S'
685
+
686
+ Passing an ill-defined format string leads to the following error:
687
+
688
+ >>> tester.dateformat = 999
689
+ Traceback (most recent call last):
690
+ ...
691
+ ValueError: The given date format `999` is not a valid format string for \
692
+ `datetime` objects. Please read the documentation on module datetime of the Python \
693
+ standard library for for further information.
694
+
695
+ >>> tester.dateformat = "%x"
696
+ >>> tester.dateformat
697
+ '%x'
698
+ """
699
+ dateformat = vars(self).get("dateformat")
700
+ if dateformat is None:
701
+ return timetools.Date.formatstrings["iso2"]
702
+ return dateformat
703
+
704
+ @dateformat.setter
705
+ def dateformat(self, dateformat: str) -> None:
706
+ try:
707
+ datetime.datetime(2000, 1, 1).strftime(dateformat)
708
+ except BaseException as exc:
709
+ raise ValueError(
710
+ f"The given date format `{dateformat}` is not a valid format string "
711
+ f"for `datetime` objects. Please read the documentation on module "
712
+ f"datetime of the Python standard library for for further information."
713
+ ) from exc
714
+ vars(self)["dateformat"] = dateformat
715
+
716
+ def get_output_array(self, parseqs: sequencetools.IOSequence):
717
+ """Return the array containing the output results of the given sequence."""
718
+ return parseqs.series
719
+
720
+ def prepare_node_sequences(self) -> None:
721
+ """Prepare the simulations series of all nodes.
722
+
723
+ This preparation might not be suitable for all types of integration tests.
724
+ Prepare those node sequences manually, for which this method does not result in
725
+ the desired outcome."""
726
+ for node in self.nodes:
727
+ if not node.entries:
728
+ node.deploymode = "oldsim"
729
+ sim = node.sequences.sim
730
+ sim.prepare_series(allocate_ram=False)
731
+ sim.prepare_series(allocate_ram=True)
732
+
733
+ def prepare_input_model_sequences(self) -> None:
734
+ """Configure the input sequences of the model in a manner that allows for
735
+ applying their time series data in integration tests."""
736
+ prepare_inputseries = self.element.prepare_inputseries
737
+ prepare_inputseries(allocate_ram=False)
738
+ prepare_inputseries(allocate_ram=True)
739
+
740
+ def extract_print_sequences(self) -> tuple[sequencetools.IOSequence, ...]:
741
+ """Return a list of all input, factor, flux, and state sequences of the model
742
+ and the simulation sequences of all nodes."""
743
+ seqs = []
744
+ for name in ("inputs", "factors", "fluxes", "states"):
745
+ subseqs = getattr(self.element.model.sequences, name, ())
746
+ for seq in subseqs:
747
+ seqs.append(seq)
748
+ for node in self.nodes:
749
+ seqs.append(node.sequences.sim)
750
+ return tuple(seqs)
751
+
752
+ def prepare_model(
753
+ self, update_parameters: bool, use_conditions: ConditionsModel | None
754
+ ) -> None:
755
+ """Derive the secondary parameter values, prepare all required time series and
756
+ set the initial conditions."""
757
+ if update_parameters:
758
+ self.model.update_parameters()
759
+ self.reset_values()
760
+ self.reset_series()
761
+ self.reset_outputs()
762
+ if use_conditions:
763
+ with hydpy.pub.options.trimvariables(False):
764
+ self.element.model.conditions = use_conditions
765
+ self.reset_inits()
766
+
767
+ def reset_values(self) -> None:
768
+ """Set the current values of all factor and flux sequences to |numpy.nan|."""
769
+ for model in self.model.find_submodels(include_mainmodel=True).values():
770
+ for seqs in (model.sequences.factors, model.sequences.fluxes):
771
+ for seq in seqs:
772
+ seq.value = numpy.nan
773
+
774
+ def reset_series(self) -> None:
775
+ """Initialise all time series with |numpy.nan| values."""
776
+ for flag in (False, True):
777
+ self.element.prepare_factorseries(allocate_ram=flag)
778
+ self.element.prepare_fluxseries(allocate_ram=flag)
779
+ self.element.prepare_stateseries(allocate_ram=flag)
780
+
781
+ def reset_outputs(self) -> None:
782
+ """Set the values of the simulation sequences of all outlet nodes to zero."""
783
+ for node in self.nodes:
784
+ if (node in self.element.outlets) or (node in self.element.senders):
785
+ node.sequences.sim[:] = 0.0
786
+
787
+ def reset_inits(self) -> None:
788
+ """Set all initial conditions of all models."""
789
+ with hydpy.pub.options.trimvariables(False):
790
+ inits = self.inits
791
+ for subname in ("states", "logs"):
792
+ for element in self.elements:
793
+ for model in element.model.find_submodels(
794
+ include_mainmodel=True
795
+ ).values():
796
+ for seq in getattr(model.sequences, subname, ()):
797
+ value = getattr(inits, seq.name, None)
798
+ if value is None:
799
+ name = f"{seq.name}_{element.name}"
800
+ value = getattr(inits, name, None)
801
+ if value is None:
802
+ name = f"{seq.name}_{element.name}_{id(seq)}"
803
+ value = getattr(inits, name, None)
804
+ if value is not None:
805
+ seq(value)
806
+
807
+ def plot(
808
+ self,
809
+ filename: str,
810
+ axis1: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
811
+ axis2: typingtools.MayNonerable1[sequencetools.IOSequence] = None,
812
+ ) -> None:
813
+ """Save a plotly HTML file plotting the current test results.
814
+
815
+ (Optional) arguments:
816
+ * filename: Name of the file. If necessary, the file ending `html` is
817
+ added automatically. The file is stored in the `html_` folder of
818
+ subpackage `docs`.
819
+ * act_sequences: List of the sequences to be shown initially (deprecated).
820
+ * axis1: sequences to be shown initially on the first axis.
821
+ * axis2: sequences to be shown initially on the second axis.
822
+ """
823
+
824
+ def _update_act_names(sequence_: sequencetools.IOSequence, name_: str) -> None:
825
+ if isinstance(sequence_, act_types1):
826
+ act_names1.append(name_)
827
+ if isinstance(sequence_, act_types2):
828
+ act_names2.append(name_)
829
+
830
+ if not filename.endswith(".html"):
831
+ filename += ".html"
832
+ if self.plotting_options.activated:
833
+ axis1 = self.plotting_options.activated
834
+ axis2 = ()
835
+ else:
836
+ if not (axis1 or axis2):
837
+ axis1 = self.plotting_options.axis1
838
+ axis2 = self.plotting_options.axis2
839
+ if axis1 is None:
840
+ axis1 = self.parseqs
841
+ if axis2 is None:
842
+ axis2 = ()
843
+ axis1 = objecttools.extract(
844
+ axis1, (sequencetools.IOSequence,) # type: ignore[type-abstract]
845
+ )
846
+ axis2 = objecttools.extract(
847
+ axis2, (sequencetools.IOSequence,) # type: ignore[type-abstract]
848
+ )
849
+ sel_sequences = self.plotting_options.selected
850
+ if sel_sequences is None:
851
+ sel_sequences = self.parseqs
852
+ sel_sequences = tuple(sorted(sel_sequences, key=lambda seq_: seq_.name))
853
+ act_types1 = tuple(type(seq_) for seq_ in axis1)
854
+ act_types2 = tuple(type(seq_) for seq_ in axis2)
855
+ sel_names, sel_series, sel_units = [], [], []
856
+ act_names1: list[str] = []
857
+ act_names2: list[str] = []
858
+ for sequence in sel_sequences:
859
+ name = type(sequence).__name__
860
+ if sequence.NDIM == 0:
861
+ sel_names.append(name)
862
+ sel_units.append(sequence.unit)
863
+ sel_series.append(list(sequence.series))
864
+ _update_act_names(sequence, name)
865
+ elif all(length == 1 for length in sequence.shape):
866
+ sel_names.append(name)
867
+ sel_units.append(sequence.unit)
868
+ sel_series.append(list(sequence.series[:, 0]))
869
+ _update_act_names(sequence, name)
870
+ else:
871
+ ranges = (range(length) for length in sequence.shape)
872
+ for idxs in itertools.product(*ranges):
873
+ subname = f"{name}_{'-'.join(str(idx+1) for idx in idxs)}"
874
+ sel_names.append(subname)
875
+ sel_units.append(sequence.unit)
876
+ series = sequence.series
877
+ for idx in idxs:
878
+ series = series[:, idx]
879
+ sel_series.append(list(series))
880
+ _update_act_names(sequence, subname)
881
+
882
+ fig = subplots.make_subplots(rows=1, cols=1, specs=[[{"secondary_y": True}]])
883
+ fig.update_xaxes(showgrid=False, zeroline=False)
884
+ fig.update_yaxes(showgrid=False, zeroline=False)
885
+ fig.update_layout(showlegend=True)
886
+
887
+ cmap = pyplot.get_cmap("tab20", 2 * len(sel_names))
888
+ dates = list(
889
+ pandas.date_range(
890
+ start=hydpy.pub.timegrids.eval_.firstdate.datetime,
891
+ end=hydpy.pub.timegrids.eval_.lastdate.datetime,
892
+ freq=hydpy.pub.timegrids.eval_.stepsize.timedelta,
893
+ )
894
+ )
895
+ for idx, (name, series, unit) in enumerate(
896
+ zip(sel_names, sel_series, sel_units)
897
+ ):
898
+ fig.add_trace(
899
+ plotly.graph_objects.Scattergl(
900
+ x=dates,
901
+ y=series,
902
+ name=f"{name} [{unit}] (1)",
903
+ visible=name in act_names1,
904
+ legendgroup="axis 1",
905
+ line={"color": matplotlib.colors.rgb2hex(cmap(2 * idx))},
906
+ )
907
+ )
908
+ fig.add_trace(
909
+ plotly.graph_objects.Scattergl(
910
+ x=dates,
911
+ y=series,
912
+ name=f"{name} [{unit}] (2)",
913
+ visible=name in act_names2,
914
+ legendgroup="axis 2",
915
+ line={"color": matplotlib.colors.rgb2hex(cmap(2 * idx + 1))},
916
+ ),
917
+ secondary_y=True,
918
+ )
919
+
920
+ buttons = []
921
+ for label, visibles in (
922
+ ("add all to y-axis 1", (True, False)),
923
+ ("remove all", (False, False)),
924
+ ("add all to y-axis 2", (False, True)),
925
+ ):
926
+ subbuttons: list[dict[str, str | list[Any]]] = [
927
+ {
928
+ "label": label,
929
+ "method": "restyle",
930
+ "args": [{"visible": len(sel_sequences) * visibles}],
931
+ }
932
+ ]
933
+ for idx, name in enumerate(sel_names):
934
+ subbuttons.append(
935
+ {
936
+ "label": name,
937
+ "method": "restyle",
938
+ "args": [{"visible": visibles}, [2 * idx, 2 * idx + 1]],
939
+ }
940
+ )
941
+ buttons.append(subbuttons)
942
+
943
+ fig.update_layout(
944
+ hovermode="x unified",
945
+ updatemenus=[
946
+ {
947
+ "active": 0,
948
+ "xanchor": "left",
949
+ "x": 0.0,
950
+ "yanchor": "bottom",
951
+ "y": 1.02,
952
+ "buttons": buttons[0],
953
+ },
954
+ {
955
+ "active": 0,
956
+ "xanchor": "center",
957
+ "x": 0.5,
958
+ "yanchor": "bottom",
959
+ "y": 1.02,
960
+ "buttons": buttons[1],
961
+ },
962
+ {
963
+ "active": 0,
964
+ "xanchor": "right",
965
+ "x": 1.0,
966
+ "yanchor": "bottom",
967
+ "y": 1.02,
968
+ "buttons": buttons[2],
969
+ },
970
+ ],
971
+ legend={"tracegroupgap": 100},
972
+ )
973
+
974
+ docspath = docs.__path__[0]
975
+ fig.write_html(
976
+ os.path.join(docspath, "html_", filename), include_plotlyjs="directory"
977
+ )
978
+
979
+
980
+ class UnitTest(Test):
981
+ """Defines unit doctests for a single model method."""
982
+
983
+ HEADER_OF_FIRST_COL = "ex."
984
+ """The header of the first column containing sequential numbers."""
985
+
986
+ nexts = ArrayDescriptor()
987
+ """Stores arrays for setting different values of parameters and/or
988
+ sequences before each new experiment."""
989
+
990
+ results = ArrayDescriptor()
991
+ """Stores arrays with the resulting values of parameters and/or
992
+ sequences of each new experiment."""
993
+
994
+ def __init__(self, model, method, *, first_example=1, last_example=1, parseqs=None):
995
+ del self.inits
996
+ del self.nexts
997
+ del self.results
998
+ self.model = model
999
+ self.method = method
1000
+ self.first_example_calc = first_example
1001
+ self.last_example_calc = last_example
1002
+ self.first_example_plot = first_example
1003
+ self.last_example_plot = last_example
1004
+ self.parseqs = parseqs
1005
+ self.memorise_inits()
1006
+ self.prepare_output_arrays()
1007
+
1008
+ @property
1009
+ def nmb_examples(self):
1010
+ """The number of examples to be calculated."""
1011
+ return self.last_example_calc - self.first_example_calc + 1
1012
+
1013
+ @property
1014
+ def idx0(self):
1015
+ """The first index of the examples selected for printing."""
1016
+ return self.first_example_plot - self.first_example_calc
1017
+
1018
+ @property
1019
+ def idx1(self):
1020
+ """The last index of the examples selected for printing."""
1021
+ return self.nmb_examples - (self.last_example_calc - self.last_example_plot)
1022
+
1023
+ def __call__(self, first_example=None, last_example=None) -> None:
1024
+ if first_example is None:
1025
+ self.first_example_plot = self.first_example_calc
1026
+ else:
1027
+ self.first_example_plot = first_example
1028
+ if last_example is None:
1029
+ self.last_example_plot = self.last_example_calc
1030
+ else:
1031
+ self.last_example_plot = last_example
1032
+ for idx in range(self.nmb_examples):
1033
+ self.reset_inits()
1034
+ self._update_inputs(idx)
1035
+ self.method()
1036
+ self._update_outputs(idx)
1037
+ self.print_table(self.idx0, self.idx1)
1038
+
1039
+ def get_output_array(self, parseqs):
1040
+ """Return the array containing the output results of the given
1041
+ parameter or sequence."""
1042
+ return getattr(self.results, parseqs.name)
1043
+
1044
+ @property
1045
+ def raw_first_col_strings(self):
1046
+ """The raw integer strings of the first column, except the header."""
1047
+ return [
1048
+ str(example)
1049
+ for example in range(self.first_example_plot, self.last_example_plot + 1)
1050
+ ]
1051
+
1052
+ def memorise_inits(self):
1053
+ """Memorise all initial conditions."""
1054
+ for parseq in self.parseqs:
1055
+ value = exceptiontools.getattr_(parseq, "value", None)
1056
+ if value is not None:
1057
+ setattr(self.inits, parseq.name, value)
1058
+
1059
+ def prepare_output_arrays(self):
1060
+ """Prepare arrays for storing the calculated results for the
1061
+ respective parameters and/or sequences."""
1062
+ for parseq in self.parseqs:
1063
+ shape = [len(self.raw_first_col_strings)] + list(parseq.shape)
1064
+ type_ = getattr(parseq, "TYPE", float)
1065
+ init = 0 if issubclass(type_, int) else numpy.nan
1066
+ array = numpy.full(shape, init, type_)
1067
+ setattr(self.results, parseq.name, array)
1068
+
1069
+ def reset_inits(self):
1070
+ """Set all initial conditions."""
1071
+ for parseq in self.parseqs:
1072
+ inits = getattr(self.inits, parseq.name, None)
1073
+ if inits is not None:
1074
+ parseq(inits)
1075
+
1076
+ def _update_inputs(self, idx):
1077
+ """Update the actual values with the |UnitTest.nexts| data of
1078
+ the given index."""
1079
+ for parseq in self.parseqs:
1080
+ if hasattr(self.nexts, parseq.name):
1081
+ parseq(getattr(self.nexts, parseq.name)[idx])
1082
+
1083
+ def _update_outputs(self, idx):
1084
+ """Update the |UnitTest.results| data with the actual values of
1085
+ the given index."""
1086
+ for parseq in self.parseqs:
1087
+ if hasattr(self.results, parseq.name):
1088
+ getattr(self.results, parseq.name)[idx] = parseq.values
1089
+
1090
+
1091
+ class _Open:
1092
+ __readingerror = (
1093
+ "Reading is not possible at the moment. Please see the "
1094
+ "documentation on class `Open` of module `testtools` "
1095
+ "for further information."
1096
+ )
1097
+
1098
+ def __init__(self, path, mode, *args, **kwargs):
1099
+ # pylint: disable=unused-argument
1100
+ # all further positional and keyword arguments are ignored.
1101
+ self.path = path.replace(os.sep, "/")
1102
+ self.mode = mode
1103
+ self.texts = []
1104
+ self.entered = False
1105
+
1106
+ def __enter__(self):
1107
+ self.entered = True
1108
+ return self
1109
+
1110
+ def __exit__(self, exception, message, traceback_):
1111
+ self.close()
1112
+
1113
+ def read(self):
1114
+ """Raise a |NotImplementedError| in any case."""
1115
+ raise NotImplementedError(self.__readingerror)
1116
+
1117
+ def readline(self):
1118
+ """Raise a |NotImplementedError| in any case."""
1119
+ raise NotImplementedError(self.__readingerror)
1120
+
1121
+ def readlines(self):
1122
+ """Raise a |NotImplementedError| in any case."""
1123
+ raise NotImplementedError(self.__readingerror)
1124
+
1125
+ def write(self, text):
1126
+ """Replace the `write` method of file objects."""
1127
+ self.texts.append(text)
1128
+
1129
+ def writelines(self, lines):
1130
+ """Replace the `writelines` method of file objects."""
1131
+ self.texts.extend(lines)
1132
+
1133
+ def close(self):
1134
+ """Replace the `close` method of file objects."""
1135
+ text = "".join(self.texts)
1136
+ maxchars = len(self.path)
1137
+ lines = []
1138
+ for line in text.split("\n"):
1139
+ if not line:
1140
+ line = "<BLANKLINE>"
1141
+ lines.append(line)
1142
+ maxchars = max(maxchars, len(line))
1143
+ text = "\n".join(lines)
1144
+ print("~" * maxchars)
1145
+ print(self.path)
1146
+ print("-" * maxchars)
1147
+ print(text)
1148
+ print("~" * maxchars)
1149
+
1150
+
1151
+ class Open:
1152
+ """Replace |open| in doctests temporarily.
1153
+
1154
+ Class |Open| to intended to make writing to files visible and testable
1155
+ in docstrings. Therefore, Python's built-in function |open| is
1156
+ temporarily replaced by another object, printing the filename and the
1157
+ file content, as shown in the following example:
1158
+
1159
+ >>> import os
1160
+ >>> path = os.path.join("folder", "test.py")
1161
+ >>> from hydpy import Open
1162
+ >>> with Open():
1163
+ ... with open(path, "w") as file_:
1164
+ ... file_.write("first line\\n")
1165
+ ... file_.writelines(["\\n", "third line\\n"])
1166
+ ~~~~~~~~~~~~~~
1167
+ folder/test.py
1168
+ --------------
1169
+ first line
1170
+ <BLANKLINE>
1171
+ third line
1172
+ <BLANKLINE>
1173
+ ~~~~~~~~~~~~~~
1174
+
1175
+ Note that, for simplicity, the UNIX style path separator `/` is used
1176
+ to print the file path on all systems.
1177
+
1178
+ Class |Open| is rather restricted at the moment. Functionalities
1179
+ like reading are not supported so far:
1180
+
1181
+ >>> with Open():
1182
+ ... with open(path, "r") as file_:
1183
+ ... file_.read()
1184
+ Traceback (most recent call last):
1185
+ ...
1186
+ NotImplementedError: Reading is not possible at the moment. \
1187
+ Please see the documentation on class `Open` of module `testtools` \
1188
+ for further information.
1189
+
1190
+ >>> with Open():
1191
+ ... with open(path, "r") as file_:
1192
+ ... file_.readline()
1193
+ Traceback (most recent call last):
1194
+ ...
1195
+ NotImplementedError: Reading is not possible at the moment. \
1196
+ Please see the documentation on class `Open` of module `testtools` \
1197
+ for further information.
1198
+
1199
+ >>> with Open():
1200
+ ... with open(path, "r") as file_:
1201
+ ... file_.readlines()
1202
+ Traceback (most recent call last):
1203
+ ...
1204
+ NotImplementedError: Reading is not possible at the moment. \
1205
+ Please see the documentation on class `Open` of module `testtools` \
1206
+ for further information.
1207
+ """
1208
+
1209
+ def __init__(self):
1210
+ self.open = builtins.open
1211
+
1212
+ def __enter__(self):
1213
+ builtins.open = _Open
1214
+ return self
1215
+
1216
+ def __exit__(self, exception, message, traceback_):
1217
+ builtins.open = self.open
1218
+
1219
+
1220
+ class TestIO:
1221
+ """Prepare an environment for testing IO functionalities.
1222
+
1223
+ Primarily, |TestIO| changes the current working during the
1224
+ execution of with| blocks. Inspecting your current working
1225
+ directory, |os| will likely find no file called `testfile.txt`:
1226
+
1227
+ >>> import os
1228
+ >>> os.path.exists("testfile.txt")
1229
+ False
1230
+
1231
+ If some tests require writing such a file, this should be done
1232
+ within HydPy's `iotesting` folder in subpackage `tests`, which
1233
+ is achieved by applying the `with` statement on |TestIO|:
1234
+
1235
+ >>> from hydpy import TestIO
1236
+ >>> with TestIO():
1237
+ ... open("testfile.txt", "w").close()
1238
+ ... print(os.path.exists("testfile.txt"))
1239
+ True
1240
+
1241
+ After the `with` block, the working directory is reset automatically:
1242
+
1243
+ >>> os.path.exists("testfile.txt")
1244
+ False
1245
+
1246
+ Nevertheless, `testfile.txt` still exists in the folder `iotesting`:
1247
+
1248
+ >>> with TestIO():
1249
+ ... print(os.path.exists("testfile.txt"))
1250
+ True
1251
+
1252
+ Optionally, files and folders created within the current `with` block
1253
+ can be removed automatically by setting `clear_own` to |True|
1254
+ (modified files and folders are not affected):
1255
+
1256
+ >>> with TestIO(clear_own=True):
1257
+ ... open("testfile.txt", "w").close()
1258
+ ... os.makedirs("testfolder")
1259
+ ... print(os.path.exists("testfile.txt"),
1260
+ ... os.path.exists("testfolder"))
1261
+ True True
1262
+ >>> with TestIO(clear_own=True):
1263
+ ... print(os.path.exists("testfile.txt"),
1264
+ ... os.path.exists("testfolder"))
1265
+ True False
1266
+
1267
+ Alternatively, all files and folders contained in folder `iotesting`
1268
+ can be removed after leaving the `with` block:
1269
+
1270
+ >>> with TestIO(clear_all=True):
1271
+ ... os.makedirs("testfolder")
1272
+ ... print(os.path.exists("testfile.txt"),
1273
+ ... os.path.exists("testfolder"))
1274
+ True True
1275
+ >>> with TestIO(clear_own=True):
1276
+ ... print(os.path.exists("testfile.txt"),
1277
+ ... os.path.exists("testfolder"))
1278
+ False False
1279
+
1280
+ For just clearing the `iofolder`, one can call method |TestIO.clear|
1281
+ alternatively:
1282
+
1283
+ >>> with TestIO():
1284
+ ... open("testfile.txt", "w").close()
1285
+ ... print(os.path.exists("testfile.txt"))
1286
+ True
1287
+ >>> TestIO.clear()
1288
+ >>> with TestIO():
1289
+ ... print(os.path.exists("testfile.txt"))
1290
+ False
1291
+
1292
+ Note that class |TestIO| copies all eventually generated `.coverage`
1293
+ files into the `test` subpackage to assure no covered lines are
1294
+ reported as uncovered.
1295
+ """
1296
+
1297
+ _clear_own: bool
1298
+ _clear_all: bool
1299
+ _path: str | None
1300
+ _olds: list[str] | None
1301
+
1302
+ def __init__(self, clear_own: bool = False, clear_all: bool = False) -> None:
1303
+ self._clear_own = clear_own
1304
+ self._clear_all = clear_all
1305
+ self._path = None
1306
+ self._olds = None
1307
+
1308
+ def __enter__(self) -> TestIO:
1309
+ assert (path := os.getcwd()) is not None
1310
+ self._path = path
1311
+ iotestingpath: str = iotesting.__path__[0]
1312
+ os.chdir(os.path.join(iotestingpath))
1313
+ if self._clear_own:
1314
+ self._olds = sorted(os.listdir("."))
1315
+ return self
1316
+
1317
+ def __exit__(
1318
+ self,
1319
+ exception_type: type[BaseException],
1320
+ exception_value: BaseException,
1321
+ traceback_: types.TracebackType,
1322
+ ) -> None:
1323
+ for file in sorted(os.listdir(".")):
1324
+ if (file != "__init__.py") and (
1325
+ self._clear_all
1326
+ or (
1327
+ self._clear_own
1328
+ and ((olds := self._olds) is not None)
1329
+ and (file not in olds)
1330
+ )
1331
+ ):
1332
+ if os.path.exists(file):
1333
+ if os.path.isfile(file):
1334
+ os.remove(file)
1335
+ else:
1336
+ shutil.rmtree(file)
1337
+ assert (path := self._path) is not None
1338
+ os.chdir(path)
1339
+
1340
+ @classmethod
1341
+ def clear(cls) -> None:
1342
+ """Remove all files from the `iotesting` folder."""
1343
+ with cls(clear_all=True):
1344
+ pass
1345
+
1346
+
1347
+ def make_abc_testable(abstract: type[T]) -> type[T]:
1348
+ """Return a concrete version of the given abstract base class for testing purposes.
1349
+
1350
+ Abstract base classes cannot be (and, at least in production code, should not be)
1351
+ instantiated:
1352
+
1353
+ >>> from hydpy.core.netcdftools import NetCDFVariable
1354
+ >>> var = NetCDFVariable() # doctest: +ELLIPSIS
1355
+ Traceback (most recent call last):
1356
+ ...
1357
+ TypeError: Can't instantiate abstract class NetCDFVariable with...
1358
+
1359
+ However, it is convenient to do so for testing (partly) abstract base classes in
1360
+ doctests. The derived class returned by function |make_abc_testable| is identical
1361
+ with the original one, except that its protection against initialisation is
1362
+ disabled:
1363
+
1364
+ >>> from hydpy import make_abc_testable, classname
1365
+ >>> var = make_abc_testable(NetCDFVariable)("filepath")
1366
+
1367
+ To avoid confusion, |make_abc_testable| appends an underscore to the original class
1368
+ name:
1369
+
1370
+ >>> classname(var)
1371
+ 'NetCDFVariable_'
1372
+ """
1373
+ concrete = type(abstract.__name__ + "_", (abstract,), {})
1374
+ concrete.__abstractmethods__ = frozenset() # type: ignore[attr-defined]
1375
+ return concrete
1376
+
1377
+
1378
+ @contextlib.contextmanager
1379
+ def mock_datetime_now(testdatetime):
1380
+ """Let class method |datetime.datetime.now| of class |datetime.datetime|
1381
+ of module |datetime| return the given date for testing purposes within
1382
+ a "with-block".
1383
+
1384
+ >>> import datetime
1385
+ >>> testdate = datetime.datetime(2000, 10, 1, 12, 30, 0, 999)
1386
+ >>> testdate == datetime.datetime.now()
1387
+ False
1388
+ >>> from hydpy import classname
1389
+ >>> classname(datetime.datetime)
1390
+ 'datetime'
1391
+ >>> from hydpy.core.testtools import mock_datetime_now
1392
+ >>> with mock_datetime_now(testdate):
1393
+ ... testdate == datetime.datetime.now()
1394
+ ... classname(datetime.datetime)
1395
+ True
1396
+ '_DateTime'
1397
+ >>> testdate == datetime.datetime.now()
1398
+ False
1399
+ >>> classname(datetime.datetime)
1400
+ 'datetime'
1401
+
1402
+ The following test shows that mocking |datetime.datetime| does not
1403
+ interfere with initialising |Date| objects and that the relevant
1404
+ exceptions are properly handled:
1405
+
1406
+ >>> from hydpy import Date
1407
+ >>> with mock_datetime_now(testdate):
1408
+ ... Date(datetime.datetime(2000, 10, 1, 12, 30, 0, 999))
1409
+ Traceback (most recent call last):
1410
+ ...
1411
+ ValueError: While trying to initialise a `Date` object based on \
1412
+ argument `2000-10-01 12:30:00.000999`, the following error occurred: \
1413
+ For `Date` instances, the microsecond must be zero, \
1414
+ but for the given `datetime` object it is `999` instead.
1415
+
1416
+ >>> classname(datetime.datetime)
1417
+ 'datetime'
1418
+ """
1419
+ _datetime = datetime.datetime
1420
+
1421
+ class _DateTime(datetime.datetime):
1422
+ @classmethod
1423
+ def now(cls, tz=None):
1424
+ return testdatetime
1425
+
1426
+ try:
1427
+ datetime.datetime = _DateTime
1428
+ yield
1429
+ finally:
1430
+ datetime.datetime = _datetime
1431
+
1432
+
1433
+ class NumericalDifferentiator:
1434
+ """Approximate the derivatives of |ModelSequence| values based on the finite
1435
+ difference approach.
1436
+
1437
+ .. _`here`: https://en.wikipedia.org/wiki/Finite_difference_coefficient
1438
+
1439
+ Class |NumericalDifferentiator| is thought for testing purposes only. See, for
1440
+ example, the documentation on method |kinw_model.Calc_RHMDH_V1|, which uses a
1441
+ |NumericalDifferentiator| object to validate that this method calculates the
1442
+ derivative of sequence |kinw_aides.RHM| (`ysequence`) with respect to sequence
1443
+ |kinw_states.H| (`xsequence`) correctly. Therefore, it must know the relationship
1444
+ between |kinw_aides.RHM| and |kinw_states.H|, being defined by method
1445
+ |kinw_model.Calc_RHM_V1|.
1446
+
1447
+ See also the documentation on method |kinw_model.Calc_AMDH_UMDH_V1|, which explains
1448
+ how to apply class |NumericalDifferentiator| on multiple target sequences
1449
+ (`ysequences`). Note that, in order to calculate the correct derivatives of
1450
+ sequences |kinw_aides.AM| and |kinw_aides.UM|, we need not only to pass
1451
+ |kinw_model.Calc_AM_UM_V1|, but also methods |kinw_model.Calc_RHM_V1| and
1452
+ |kinw_model.Calc_RHV_V1|, as sequences |kinw_aides.RHM| and |kinw_aides.RHV|, which
1453
+ are required for calculating |kinw_aides.AM| and |kinw_aides.UM|, depend on
1454
+ |kinw_states.H| themselves.
1455
+
1456
+ Numerical approximations of derivatives are of limited precision.
1457
+ |NumericalDifferentiator| achieves the second order of accuracy due to using the
1458
+ coefficients given `here`_. If results are too inaccurate, you might improve them
1459
+ by changing the finite difference method (`backward` or `central` instead of
1460
+ `forward`) or by changing the default interval width `dx`.
1461
+ """
1462
+
1463
+ __NMBNODES = 3
1464
+ __XSHIFTS = {
1465
+ "forward": numpy.array([0.0, 1.0, 2.0]),
1466
+ "backward": numpy.array([-2.0, -1.0, 0.0]),
1467
+ "central": numpy.array([-1.0, 0.0, 1.0]),
1468
+ }
1469
+ __YCOEFFS = {
1470
+ "forward": numpy.array([-3.0, 4.0, -1.0]) / 2.0,
1471
+ "backward": numpy.array([1.0, -4.0, 3]) / 2.0,
1472
+ "central": numpy.array([-1.0, 0.0, 1]) / 2.0,
1473
+ }
1474
+
1475
+ def __init__(
1476
+ self,
1477
+ *,
1478
+ xsequence: sequencetools.ModelSequence,
1479
+ ysequences: Iterable[sequencetools.ModelSequence],
1480
+ methods: Iterable[modeltools.Method],
1481
+ dx: float = 1e-6,
1482
+ method: Literal["forward", "central", "backward"] = "forward",
1483
+ ):
1484
+ self._xsequence = xsequence
1485
+ self._ysequences = tuple(ysequences)
1486
+ self._methods = tuple(methods)
1487
+ self._span = dx / 2.0
1488
+ self._method = method
1489
+
1490
+ @property
1491
+ def _ycoeffs(self) -> NDArrayFloat:
1492
+ return self.__YCOEFFS[self._method] / self._span
1493
+
1494
+ @property
1495
+ def _xshifts(self) -> NDArrayFloat:
1496
+ return self.__XSHIFTS[self._method] * self._span
1497
+
1498
+ @property
1499
+ def _yvalues(self) -> dict[sequencetools.ModelSequence, NDArrayFloat]:
1500
+ xvalues = copy.deepcopy(self._xsequence.values)
1501
+ ndim = self._ysequences[0].NDIM
1502
+ assert all(ndim == seq.NDIM for seq in self._ysequences)
1503
+ nmb = self._ysequences[0].numberofvalues
1504
+ assert all(nmb == seq.numberofvalues for seq in self._ysequences)
1505
+ yvalues = {
1506
+ ysequence: numpy.empty((nmb, self.__NMBNODES))
1507
+ for ysequence in self._ysequences
1508
+ }
1509
+ try:
1510
+ for idx, shift in enumerate(self._xshifts):
1511
+ self._xsequence.values = xvalues + shift
1512
+ for method in self._methods:
1513
+ method()
1514
+ for ysequence in self._ysequences:
1515
+ yvalues[ysequence][:, idx] = copy.deepcopy(ysequence.values)
1516
+ return yvalues
1517
+ finally:
1518
+ self._xsequence.values = xvalues
1519
+
1520
+ @property
1521
+ def _derivatives(self) -> dict[sequencetools.ModelSequence, NDArrayFloat]:
1522
+ return {
1523
+ ysequence: numpy.dot(self._ycoeffs, yvalues.T)
1524
+ for ysequence, yvalues in self._yvalues.items()
1525
+ }
1526
+
1527
+ def __call__(self) -> None:
1528
+ for ysequence, derivatives in self._derivatives.items():
1529
+ print(f"d_{ysequence.name}/d_{self._xsequence.name}", end=": ")
1530
+ objecttools.print_vector(derivatives, width=1000)
1531
+
1532
+
1533
+ def update_integrationtests(
1534
+ applicationmodel: types.ModuleType | str,
1535
+ resultfilepath: str = "update_integrationtests.txt",
1536
+ ) -> None:
1537
+ """Write the docstring of the given application model, updated with the current
1538
+ simulation results, to file.
1539
+
1540
+ Sometimes, even tiny model-related changes bring a great deal of work concerning
1541
+ *HydPy's* integration test strategy. For example, if you modify the value of a
1542
+ fixed parameter, the results of possibly dozens of integration tests of your
1543
+ application model might become wrong. In such situations, function
1544
+ |update_integrationtests| helps you in replacing all integration tests results at
1545
+ once. Therefore, it calculates the new results, updates the old module docstring
1546
+ and writes it. You only need to copy-paste the printed result into the affected
1547
+ module. But be aware that function |update_integrationtests| cannot guarantee the
1548
+ correctness of the new results. Whenever in doubt if the new results are really
1549
+ correct under all possible conditions, you should inspect and replace each
1550
+ integration test result manually.
1551
+
1552
+ In the following example, we disable method |conv_model.Pass_Outputs_V1|
1553
+ temporarily. Accordingly, application model |conv_nn| does not pass any output to
1554
+ its outlet nodes, which is why the last four columns of both integration test
1555
+ tables now contain zero value only (we can perform this mocking-based test in
1556
+ Python-mode only):
1557
+
1558
+ >>> from hydpy import pub, TestIO, update_integrationtests
1559
+ >>> from unittest import mock
1560
+ >>> pass_output = "hydpy.models.conv.conv_model.Pass_Outputs_V1.__call__"
1561
+ >>> with TestIO(), pub.options.usecython(False), mock.patch(pass_output):
1562
+ ... update_integrationtests("conv_nn", "temp.txt")
1563
+ ... with open("temp.txt") as resultfile:
1564
+ ... print(resultfile.read()) # doctest: +ELLIPSIS
1565
+ Number of replacements: 2
1566
+ <BLANKLINE>
1567
+ ... test()
1568
+ | date | inputs | outputs | in1 | in2 | out1 \
1569
+ | out2 | out3 | out4 |
1570
+ -----------------------------------------------------------------------\
1571
+ ----------------------
1572
+ | 2000-01-01 | 1.0 4.0 | 1.0 4.0 1.0 1.0 | 1.0 | 4.0 | 0.0 \
1573
+ | 0.0 | 0.0 | 0.0 |
1574
+ | 2000-01-02 | 2.0 nan | 2.0 nan 2.0 2.0 | 2.0 | nan | 0.0 \
1575
+ | 0.0 | 0.0 | 0.0 |
1576
+ | 2000-01-03 | nan nan | nan nan nan nan | nan | nan | 0.0 \
1577
+ | 0.0 | 0.0 | 0.0 |
1578
+ <BLANKLINE>
1579
+ ... test()
1580
+ | date | inputs | outputs | in1 | in2 | out1 \
1581
+ | out2 | out3 | out4 |
1582
+ -----------------------------------------------------------------------\
1583
+ ----------------------
1584
+ | 2000-01-01 | 1.0 4.0 | 1.0 4.0 1.0 1.0 | 1.0 | 4.0 | 0.0 \
1585
+ | 0.0 | 0.0 | 0.0 |
1586
+ | 2000-01-02 | 2.0 nan | 2.0 2.0 2.0 2.0 | 2.0 | nan | 0.0 \
1587
+ | 0.0 | 0.0 | 0.0 |
1588
+ | 2000-01-03 | nan nan | nan nan nan nan | nan | nan | 0.0 \
1589
+ | 0.0 | 0.0 | 0.0 |
1590
+ <BLANKLINE>
1591
+ """
1592
+ module = importtools.load_modelmodule(applicationmodel)
1593
+ assert (docstring := module.__doc__) is not None
1594
+ stringio = io.StringIO
1595
+ with stringio() as file_, contextlib.redirect_stdout(file_):
1596
+ module.tester.perform_tests()
1597
+ result = file_.getvalue()
1598
+ oldlines: list[str] = []
1599
+ newlines: list[str] = []
1600
+ expected, got = False, False
1601
+ nmb_replacements = 0
1602
+ for line in result.split("\n"):
1603
+ line = line.strip()
1604
+ if line == "Expected:":
1605
+ expected = True
1606
+ elif line == "Got:":
1607
+ expected = False
1608
+ got = True
1609
+ elif got and ("***********************************" in line):
1610
+ expected = False
1611
+ got = False
1612
+ if oldlines or newlines:
1613
+ nmb_replacements += 1
1614
+ docstring = docstring.replace("\n".join(oldlines), "\n".join(newlines))
1615
+ docstring = docstring.replace(
1616
+ "\n".join(f" {line}" for line in oldlines),
1617
+ "\n".join(f" {line}" for line in newlines),
1618
+ )
1619
+ oldlines, newlines = [], []
1620
+ elif expected:
1621
+ oldlines.append(line)
1622
+ elif got:
1623
+ newlines.append(line)
1624
+ with open(resultfilepath, "w", encoding="utf-8") as resultfile:
1625
+ resultfile.write(f"Number of replacements: {nmb_replacements}\n\n")
1626
+ resultfile.write(docstring)
1627
+
1628
+
1629
+ def _enumerate(variables: tuple[type[variabletools.Variable], ...]) -> str:
1630
+ return objecttools.enumeration(
1631
+ v.__name__ for v in variabletools.sort_variables(variables)
1632
+ )
1633
+
1634
+
1635
+ def check_methodorder(model: modeltools.Model, indent: int = 0) -> str:
1636
+ """Check that *HydPy* calls the methods of the given application model in the
1637
+ correct order for each simulation step.
1638
+
1639
+ The purpose of this function is to help model developers ensure that each method
1640
+ uses only the values of those sequences that have been calculated by other methods
1641
+ beforehand. *HydPy's* test routines apply |check_methodorder| automatically on
1642
+ each available application model. Alternatively, you can also execute it at the end
1643
+ of the docstring of an individual application model "manually", which suppresses
1644
+ the automatic execution and allows to check and discuss exceptional cases where
1645
+ |check_methodorder| generates false alarms.
1646
+
1647
+ Function |check_methodorder| relies on the class constants `REQUIREDSEQUENCES`,
1648
+ `UPDATEDSEQUENCES`, and `RESULTSEQUENCES` of all relevant |Method| subclasses.
1649
+ Hence, the correctness of its results depends on the correctness of these tuples.
1650
+ However, even if those tuples are well-defined, one cannot expect
1651
+ |check_methodorder| to catch all kinds of order-related errors. For example,
1652
+ consider the case where one method calculates only some values of a
1653
+ multi-dimensional sequence and another method the remaining ones.
1654
+ |check_methodorder| would not report anything when a third method, relying on the
1655
+ completeness of the sequence's values, were called after the first but before the
1656
+ second method.
1657
+
1658
+ We use the quite complex model |lland_knauf| as an example. |check_methodorder|
1659
+ does not report any problems:
1660
+
1661
+ >>> from hydpy.core.testtools import check_methodorder
1662
+ >>> from hydpy.models.lland_knauf import Model
1663
+ >>> print(check_methodorder(Model))
1664
+ <BLANKLINE>
1665
+
1666
+ To show how |check_methodorder| reports errors, we modify the `RESULTSEQUENCES`
1667
+ tuples of methods |lland_model.Calc_TKor_V1|, |lland_model.Calc_TZ_V1|, and
1668
+ |lland_model.Calc_QA_V1|:
1669
+
1670
+ >>> from hydpy.models.lland.lland_model import (
1671
+ ... Calc_TKor_V1, Calc_TZ_V1, Calc_QA_V1)
1672
+ >>> results_tkor = Calc_TKor_V1.RESULTSEQUENCES
1673
+ >>> results_tz = Calc_TZ_V1.RESULTSEQUENCES
1674
+ >>> results_qa = Calc_QA_V1.RESULTSEQUENCES
1675
+ >>> Calc_TKor_V1.RESULTSEQUENCES = ()
1676
+ >>> Calc_TZ_V1.RESULTSEQUENCES = ()
1677
+ >>> Calc_QA_V1.RESULTSEQUENCES += results_tkor
1678
+
1679
+ Now, none of the relevant models calculates the value of sequence
1680
+ |lland_fluxes.TZ|. For |lland_fluxes.TKor|, there is still a method
1681
+ (|lland_model.Calc_QA_V1|) calculating its values, but at a too-late stage of the
1682
+ simulation step:
1683
+
1684
+ >>> print(check_methodorder(Model)) # doctest: +ELLIPSIS
1685
+ Method Calc_SaturationVapourPressure_V1 requires the following sequences, which \
1686
+ are not among the result sequences of any of its predecessors: TKor
1687
+ ...
1688
+ Method Update_ESnow_V1 requires the following sequences, which are not among the \
1689
+ result sequences of any of its predecessors: TKor and TZ
1690
+
1691
+ To tidy up, we need to revert the above changes:
1692
+
1693
+ >>> Calc_TKor_V1.RESULTSEQUENCES = results_tkor
1694
+ >>> Calc_TZ_V1.RESULTSEQUENCES = results_tz
1695
+ >>> Calc_QA_V1.RESULTSEQUENCES = results_qa
1696
+ >>> print(check_methodorder(Model))
1697
+ <BLANKLINE>
1698
+ """
1699
+ blanks = " " * indent
1700
+ results: list[str] = []
1701
+ excluded = (
1702
+ sequencetools.InputSequence,
1703
+ sequencetools.InletSequence,
1704
+ sequencetools.ReceiverSequence,
1705
+ sequencetools.StateSequence,
1706
+ sequencetools.LogSequence,
1707
+ )
1708
+ methods = tuple(model.get_methods(skip=("ADD_METHODS", "INTERFACE_METHODS")))
1709
+ for idx, method1 in enumerate(methods):
1710
+ required = {
1711
+ seq for seq in method1.REQUIREDSEQUENCES if not issubclass(seq, excluded)
1712
+ }
1713
+ for method0 in methods[:idx]:
1714
+ for seq in itertools.chain(
1715
+ method0.RESULTSEQUENCES, method0.UPDATEDSEQUENCES
1716
+ ):
1717
+ if seq in required:
1718
+ required.remove(seq)
1719
+ if required:
1720
+ results.append(
1721
+ f"{blanks}Method {method1.__name__} requires the following sequences, "
1722
+ f"which are not among the result sequences of any of its "
1723
+ f"predecessors: {_enumerate(tuple(required))}"
1724
+ )
1725
+ return "\n".join(results)
1726
+
1727
+
1728
+ def check_selectedvariables(method: type[modeltools.Method], indent: int = 0) -> str:
1729
+ """Perform consistency checks regarding the |Parameter| and |Sequence_|
1730
+ subclasses selected by the given |Method| subclass.
1731
+
1732
+ The purpose of this function is to help model developers ensure that the class
1733
+ tuples `CONTROLPARAMETERS`, `DERIVEDPARAMETERS`, `FIXEDPARAMETERS`,
1734
+ `SOLVERPARAMETERS`, `REQUIREDSEQUENCES`, `UPDATEDSEQUENCES`, and `RESULTSEQUENCES`
1735
+ contain the correct parameter and sequence subclasses. *HydPy's* test routines
1736
+ apply |check_selectedvariables| automatically on each method of each available
1737
+ application model. Alternatively, you can also execute it at the end of the
1738
+ docstring of an individual |Method| subclass "manually", which suppresses the
1739
+ automatic execution and allows to check and discuss exceptional cases where
1740
+ |check_selectedvariables| generates false alarms.
1741
+
1742
+ Do not expect |check_selectedvariables| to catch all possible errors. Also, false
1743
+ positives might occur. However, in our experience, function
1744
+ |check_selectedvariables| is of great help to prevent the most common mistakes when
1745
+ defining the parameter and sequence classes relevant for a specific method.
1746
+
1747
+ As an example, we select method |evap_model.Calc_WindSpeed2m_V1| of base model
1748
+ |evap|. |check_selectedvariables| does not reportany problems:
1749
+
1750
+ >>> from hydpy.core.testtools import check_selectedvariables
1751
+ >>> from hydpy.models.evap.evap_model import Calc_WindSpeed10m_V1
1752
+ >>> print(check_selectedvariables(Calc_WindSpeed10m_V1))
1753
+ <BLANKLINE>
1754
+
1755
+ To show how |check_selectedvariables| reports errors, we clear the
1756
+ `RESULTSEQUENCES` tuple of method |evap_model.Calc_WindSpeed10m_V1|. Now
1757
+ |check_selectedvariables| realises the usage of the factor sequence object
1758
+ `windspeed10m` within the source code of method |evap_model.Calc_WindSpeed10m_V1|,
1759
+ which is neither available within the `REQUIREDSEQUENCES`, the `UPDATEDSEQUENCES`,
1760
+ nor the`RESULTSEQUENCES` tuple:
1761
+
1762
+ >>> resultseqs = Calc_WindSpeed10m_V1.RESULTSEQUENCES
1763
+ >>> Calc_WindSpeed10m_V1.RESULTSEQUENCES = ()
1764
+ >>> print(check_selectedvariables(Calc_WindSpeed10m_V1))
1765
+ Definitely missing: windspeed10m
1766
+
1767
+ After putting the wrong flux sequence class |evap_factors.WindSpeed2m| into the
1768
+ tuple, we get an additional warning pointing to our mistake:
1769
+
1770
+ >>> from hydpy.models.evap.evap_factors import WindSpeed2m
1771
+ >>> Calc_WindSpeed10m_V1.RESULTSEQUENCES = WindSpeed2m,
1772
+ >>> print(check_selectedvariables(Calc_WindSpeed10m_V1))
1773
+ Definitely missing: windspeed10m
1774
+ Possibly erroneously selected (RESULTSEQUENCES): WindSpeed2m
1775
+
1776
+ Method |evap_model.Calc_WindSpeed10m_V1| uses
1777
+ |evap_model.Return_AdjustedWindSpeed_V1| as a submethod. Hence,
1778
+ |evap_model.Calc_WindSpeed10m_V1| most likely needs to select each variable
1779
+ selected by |evap_model.Return_AdjustedWindSpeed_V1|. After adding additional
1780
+ variables to the `DERIVEDPARAMETERS` tuple of
1781
+ |evap_model.Return_AdjustedWindSpeed_V1|, we get another warning message:
1782
+
1783
+ >>> from hydpy.models.evap.evap_model import Return_AdjustedWindSpeed_V1
1784
+ >>> from hydpy.models.evap.evap_derived import Days, Hours, Seconds
1785
+ >>> derivedpars = Return_AdjustedWindSpeed_V1.DERIVEDPARAMETERS
1786
+ >>> Return_AdjustedWindSpeed_V1.DERIVEDPARAMETERS = Days, Hours, Seconds
1787
+ >>> print(check_selectedvariables(Calc_WindSpeed10m_V1))
1788
+ Definitely missing: windspeed10m
1789
+ Possibly missing (DERIVEDPARAMETERS):
1790
+ Return_AdjustedWindSpeed_V1: Seconds, Hours, and Days
1791
+ Possibly erroneously selected (RESULTSEQUENCES): WindSpeed2m
1792
+
1793
+ Finally, |check_selectedvariables| checks for duplicates both within and between
1794
+ the different tuples:
1795
+
1796
+ >>> from hydpy.models.evap.evap_inputs import WindSpeed, RelativeHumidity
1797
+ >>> requiredseqs = Calc_WindSpeed10m_V1.REQUIREDSEQUENCES
1798
+ >>> Calc_WindSpeed10m_V1.REQUIREDSEQUENCES = WindSpeed, WindSpeed, RelativeHumidity
1799
+ >>> Calc_WindSpeed10m_V1.UPDATEDSEQUENCES = RelativeHumidity,
1800
+ >>> print(check_selectedvariables(Calc_WindSpeed10m_V1))
1801
+ Definitely missing: windspeed10m
1802
+ Possibly missing (DERIVEDPARAMETERS):
1803
+ Return_AdjustedWindSpeed_V1: Seconds, Hours, and Days
1804
+ Possibly erroneously selected (REQUIREDSEQUENCES): RelativeHumidity
1805
+ Possibly erroneously selected (UPDATEDSEQUENCES): RelativeHumidity
1806
+ Possibly erroneously selected (RESULTSEQUENCES): WindSpeed2m
1807
+ Duplicates: RelativeHumidity and WindSpeed
1808
+
1809
+ To tidy up, we need to revert the above changes:
1810
+
1811
+ >>> Calc_WindSpeed10m_V1.RESULTSEQUENCES = resultseqs
1812
+ >>> Return_AdjustedWindSpeed_V1.DERIVEDPARAMETERS = derivedpars
1813
+ >>> Calc_WindSpeed10m_V1.REQUIREDSEQUENCES = requiredseqs
1814
+ >>> Calc_WindSpeed10m_V1.UPDATEDSEQUENCES = ()
1815
+ >>> print(check_selectedvariables(Calc_WindSpeed10m_V1))
1816
+ <BLANKLINE>
1817
+
1818
+ Some methods, such as |arma_model.Pick_Q_V1|, of base model |arma| rely on the
1819
+ `len` attribute of 1-dimensional sequences. Function |check_selectedvariables|
1820
+ does not report false alarms in such cases:
1821
+
1822
+ >>> from hydpy.models.arma.arma_model import Pick_Q_V1
1823
+ >>> print(check_selectedvariables(Pick_Q_V1))
1824
+ <BLANKLINE>
1825
+
1826
+ Some methods such as |evap_model.Calc_PotentialEvapotranspiration_V1| of base model
1827
+ |evap| rely on the |KeywordParameter1D.entrymin| attribute of |KeywordParameter1D|
1828
+ instances. Function |check_selectedvariables| does not report false alarms in such
1829
+ cases:
1830
+
1831
+ >>> from hydpy.models.evap.evap_model import Calc_PotentialEvapotranspiration_V1
1832
+ >>> from hydpy.models.evap.evap_control import MonthFactor
1833
+ >>> MonthFactor in Calc_PotentialEvapotranspiration_V1.CONTROLPARAMETERS
1834
+ True
1835
+ >>> print(check_selectedvariables(Calc_PotentialEvapotranspiration_V1))
1836
+ <BLANKLINE>
1837
+
1838
+ Some methods, such as |evap_model.Calc_PotentialEvapotranspiration_V2| of base
1839
+ model |evap|, rely on the |KeywordParameter2D.rowmin| or the
1840
+ |KeywordParameter2D.columnmin| attribute of |KeywordParameter2D| instances.
1841
+ Function |check_selectedvariables| does not report false alarms in such cases:
1842
+
1843
+ >>> from hydpy.models.evap.evap_model import Calc_PotentialEvapotranspiration_V2
1844
+ >>> from hydpy.models.evap.evap_control import LandMonthFactor
1845
+ >>> LandMonthFactor in Calc_PotentialEvapotranspiration_V2.CONTROLPARAMETERS
1846
+ True
1847
+ >>> print(check_selectedvariables(Calc_PotentialEvapotranspiration_V2))
1848
+ <BLANKLINE>
1849
+
1850
+ Some methods, such as |lland_model.Update_ESnow_V1| of base model |lland|, update a
1851
+ sequence (meaning, they require its old value and calculate a new one), but their
1852
+ submethods (in this case |lland_model.Return_BackwardEulerError_V1|) just require
1853
+ them as input. Function |check_selectedvariables| does not report false alarms in
1854
+ such cases:
1855
+
1856
+ >>> from hydpy.models.lland.lland_model import Update_ESnow_V1
1857
+ >>> print(check_selectedvariables(Update_ESnow_V1))
1858
+ <BLANKLINE>
1859
+
1860
+ Similarly, methods such as |ga_model.Perform_GARTO_V1| calculate sequence values
1861
+ from scratch but require submethods for updating them:
1862
+
1863
+ >>> from hydpy.models.ga.ga_model import Perform_GARTO_V1
1864
+ >>> print(check_selectedvariables(Perform_GARTO_V1))
1865
+ <BLANKLINE>
1866
+
1867
+ If a |AutoMethod| subclass selects multiple submethods and one requires sequence
1868
+ values that are calculated by another one, |check_selectedvariables| does not
1869
+ report this as a problem if they are listed in the correct order, as is the case
1870
+ for method |evap_model.Determine_InterceptionEvaporation_V1|:
1871
+
1872
+ >>> from hydpy.models.evap.evap_model import Determine_InterceptionEvaporation_V1
1873
+ >>> print(check_selectedvariables(Determine_InterceptionEvaporation_V1))
1874
+ <BLANKLINE>
1875
+
1876
+ However, when reversing the submethod order, |check_selectedvariables| complains
1877
+ that |evap_model.Determine_InterceptionEvaporation_V1| does not specify all
1878
+ requirements of the first submethod |evap_model.Calc_InterceptionEvaporation_V1|,
1879
+ which would be calculated too late by the second
1880
+ (|evap_model.Calc_InterceptedWater_V1|) and the third
1881
+ (|evap_model.Calc_PotentialInterceptionEvaporation_V3|) submethod:
1882
+
1883
+ >>> submethods = Determine_InterceptionEvaporation_V1.SUBMETHODS
1884
+ >>> Determine_InterceptionEvaporation_V1.SUBMETHODS = tuple(reversed(submethods))
1885
+ >>> print(check_selectedvariables(Determine_InterceptionEvaporation_V1))
1886
+ Possibly missing (REQUIREDSEQUENCES):
1887
+ Calc_InterceptionEvaporation_V1: InterceptedWater and \
1888
+ PotentialInterceptionEvaporation
1889
+
1890
+ >>> Determine_InterceptionEvaporation_V1.SUBMETHODS = submethods
1891
+ """
1892
+ # pylint: disable=too-many-branches
1893
+ # ToDo: needs refactoring
1894
+ prefixes = (
1895
+ "con",
1896
+ "der",
1897
+ "fix",
1898
+ "sol",
1899
+ "inp",
1900
+ "fac",
1901
+ "flu",
1902
+ "sta",
1903
+ "old",
1904
+ "new",
1905
+ "log",
1906
+ "aid",
1907
+ "inl",
1908
+ "out",
1909
+ "rec",
1910
+ "sen",
1911
+ )
1912
+ groups = (
1913
+ "CONTROLPARAMETERS",
1914
+ "DERIVEDPARAMETERS",
1915
+ "FIXEDPARAMETERS",
1916
+ "SOLVERPARAMETERS",
1917
+ "REQUIREDSEQUENCES",
1918
+ "UPDATEDSEQUENCES",
1919
+ "RESULTSEQUENCES",
1920
+ )
1921
+ blanks = " " * indent
1922
+ results: list[str] = []
1923
+ # search for variables that are used in the source code but not among the selected
1924
+ # variables:
1925
+ source = inspect.getsource(method.__call__)
1926
+ varnames_source: set[str] = set()
1927
+ varnames_candidates: set[str] = set(method.__call__.__code__.co_names)
1928
+ names_builtin = set(dir(builtins))
1929
+ for varname in tuple(varnames_candidates):
1930
+ if (varname in names_builtin) or (f"modelutils.{varname}" in source):
1931
+ varnames_candidates.remove(varname)
1932
+ for varname, prefix in itertools.product(varnames_candidates, prefixes):
1933
+ if f"{prefix}.{varname}" in source:
1934
+ if varname.startswith("len_"):
1935
+ varname = varname[4:]
1936
+ else:
1937
+ for suffix in ("_rowmin", "_columnmin", "_entrymin"):
1938
+ if varname.endswith(suffix):
1939
+ varname = varname[1 : -len(suffix)]
1940
+ varname = varname.replace("_callback", "")
1941
+ varnames_source.add(varname)
1942
+ varnames_selected: set[str] = set()
1943
+ for group in groups:
1944
+ varnames_selected.update(g.__name__.lower() for g in getattr(method, group))
1945
+ varnames_diff: list[str] = sorted(varnames_source - varnames_selected)
1946
+ if varnames_diff:
1947
+ results.append(
1948
+ f"{blanks}Definitely missing: {objecttools.enumeration(varnames_diff)}"
1949
+ )
1950
+
1951
+ # search for variables selected by at least one submethod but not by the method
1952
+ # calling these submethods:
1953
+ vars_method: set[type[variabletools.Variable]]
1954
+ vars_submethods: set[type[variabletools.Variable]]
1955
+ for group in groups:
1956
+ vars_method = set(getattr(method, group))
1957
+ found_problem = False
1958
+ for idx_submethod, submethod in enumerate(method.SUBMETHODS):
1959
+ vars_submethods = set(getattr(submethod, group))
1960
+ if group == "REQUIREDSEQUENCES":
1961
+ vars_method.update(
1962
+ set(method.UPDATEDSEQUENCES).intersection(
1963
+ submethod.REQUIREDSEQUENCES
1964
+ )
1965
+ )
1966
+ if issubclass(
1967
+ method, (modeltools.AutoMethod, modeltools.SetAutoMethod)
1968
+ ):
1969
+ for previous in method.SUBMETHODS[:idx_submethod]:
1970
+ vars_submethods.difference_update(previous.RESULTSEQUENCES)
1971
+ diff = vars_submethods - vars_method
1972
+ if diff and (group == "UPDATEDSEQUENCES"):
1973
+ diff.difference_update(set(method.RESULTSEQUENCES))
1974
+ if diff:
1975
+ if not found_problem:
1976
+ found_problem = True
1977
+ results.append(f"{blanks}Possibly missing ({group}):")
1978
+ results.append(
1979
+ f"{blanks} {submethod.__name__}: {_enumerate(tuple(diff))}"
1980
+ )
1981
+
1982
+ # search for selected variables that are neither used within the source code nor
1983
+ # selected by any submethod:
1984
+ group2vars_method: dict[str, set[type[variabletools.Variable]]] = {
1985
+ g: set(getattr(method, g)) for g in groups
1986
+ }
1987
+ group2vars_submethods: dict[str, set[type[variabletools.Variable]]] = {
1988
+ g: set() for g in groups
1989
+ }
1990
+ for submethod in method.SUBMETHODS:
1991
+ for group, vars_submethods in group2vars_submethods.items():
1992
+ vars_submethods.update(getattr(submethod, group))
1993
+ for group, vars_method in group2vars_method.items():
1994
+ vars_submethods = group2vars_submethods[group]
1995
+ diff_ = tuple(
1996
+ method
1997
+ for method in vars_method - vars_submethods
1998
+ if method.__name__.lower() not in varnames_source
1999
+ )
2000
+ if diff_:
2001
+ results.append(
2002
+ f"{blanks}Possibly erroneously selected ({group}): "
2003
+ f"{_enumerate(diff_)}"
2004
+ )
2005
+
2006
+ # search for variables that are selected multiple times:
2007
+ vars1: tuple[type[variabletools.Variable], ...]
2008
+ vars2: tuple[type[variabletools.Variable], ...]
2009
+ dupl: set[type[variabletools.Variable]] = set()
2010
+ for group1 in groups:
2011
+ vars1 = getattr(method, group1)
2012
+ for var in vars1:
2013
+ if vars1.count(var) > 1:
2014
+ dupl.add(var)
2015
+ for group2 in groups:
2016
+ if group1 is not group2:
2017
+ vars2 = getattr(method, group2)
2018
+ dupl.update(set(vars1).intersection(vars2))
2019
+ if dupl:
2020
+ results.append(f"{blanks}Duplicates: {_enumerate(tuple(dupl))}")
2021
+ return "\n".join(results)
2022
+
2023
+
2024
+ def perform_consistencychecks(
2025
+ applicationmodel: types.ModuleType | str, indent: int = 0
2026
+ ) -> str:
2027
+ """Perform all available consistency checks for the given application model.
2028
+
2029
+ At the moment, function |perform_consistencychecks| calls function
2030
+ |check_selectedvariables| for each relevant model method and function
2031
+ |check_methodorder| for the application model itself. Note that
2032
+ |perform_consistencychecks| executes only those checks not already executed in the
2033
+ doctest of the respective method or model. This alternative allows model
2034
+ developers to perform the tests themselves whenever exceptional cases result in
2035
+ misleading error reports and discuss any related potential pitfalls in the official
2036
+ documentation.
2037
+
2038
+ As an example, we apply |perform_consistencychecks| on the application model
2039
+ |lland_knauf|. It does not report any potential problems (not already discussed in
2040
+ the documentation on the individual model methods):
2041
+
2042
+ >>> from hydpy.core.testtools import perform_consistencychecks
2043
+ >>> print(perform_consistencychecks("lland_knauf"))
2044
+ <BLANKLINE>
2045
+
2046
+ To show how |perform_consistencychecks| reports errors, we modify the
2047
+ `RESULTSEQUENCES` tuple of method |lland_model.Calc_NKor_V1|:
2048
+
2049
+ >>> from hydpy.models.lland.lland_model import Calc_NKor_V1
2050
+ >>> resultsequences = Calc_NKor_V1.RESULTSEQUENCES
2051
+ >>> Calc_NKor_V1.RESULTSEQUENCES = ()
2052
+ >>> print(perform_consistencychecks("lland_knauf"))
2053
+ Potential consistency problems for individual methods:
2054
+ Method Calc_NKor_V1:
2055
+ Definitely missing: nkor
2056
+ Potential consistency problems between methods:
2057
+ Method Calc_NBes_Inzp_V1 requires the following sequences, which are not among \
2058
+ the result sequences of any of its predecessors: NKor
2059
+ Method Calc_QBGZ_V1 requires the following sequences, which are not among the \
2060
+ result sequences of any of its predecessors: NKor
2061
+ Method Calc_QDGZ_V1 requires the following sequences, which are not among the \
2062
+ result sequences of any of its predecessors: NKor
2063
+ Method Calc_QAH_V1 requires the following sequences, which are not among the \
2064
+ result sequences of any of its predecessors: NKor
2065
+
2066
+ To tidy up, we need to revert the above changes:
2067
+
2068
+ >>> Calc_NKor_V1.RESULTSEQUENCES = resultsequences
2069
+ >>> print(perform_consistencychecks("lland_knauf"))
2070
+ <BLANKLINE>
2071
+ """
2072
+ blanks = " " * indent
2073
+ model = importtools.prepare_model(applicationmodel)
2074
+ results: list[str] = []
2075
+ method2errors: dict[str, str] = {}
2076
+ for method in model.get_methods():
2077
+ assert (methoddoc := method.__doc__) is not None
2078
+ if "check_selectedvariables(" not in methoddoc:
2079
+ subresult = check_selectedvariables(method=method, indent=indent + 8)
2080
+ if subresult:
2081
+ method2errors[method.__name__] = subresult
2082
+ if method2errors:
2083
+ results.append(
2084
+ f"{blanks}Potential consistency problems for individual methods:"
2085
+ )
2086
+ for methodname, errors in method2errors.items():
2087
+ results.append(f"{blanks} Method {methodname}:")
2088
+ results.append(errors)
2089
+ assert (modeldoc := model.__doc__) is not None
2090
+ if "check_methodorder(" not in modeldoc:
2091
+ subresult = check_methodorder(model, indent + 4)
2092
+ if subresult:
2093
+ results.append(f"{blanks}Potential consistency problems between methods:")
2094
+ results.append(subresult)
2095
+ return "\n".join(results)
2096
+
2097
+
2098
+ def save_autofig(filename: str, figure: pyplot.Figure | None = None) -> None:
2099
+ """Save a figure automatically generated during testing in the special `autofig`
2100
+ sub-package so that Sphinx can include it into the documentation later.
2101
+
2102
+ When passing no figure, function |save_autofig| takes the currently active one.
2103
+ """
2104
+ filepath = f"{autofigs.__path__[0]}/{filename}"
2105
+ if figure:
2106
+ figure.savefig(filepath)
2107
+ figure.clear()
2108
+ else:
2109
+ pyplot.savefig(filepath)
2110
+ pyplot.close()
2111
+
2112
+
2113
+ @contextlib.contextmanager
2114
+ def warn_later() -> Iterator[None]:
2115
+ """Suppress warnings and print them upon exit.
2116
+
2117
+ The context manager |warn_later| helps demonstrate functionalities in doctests that
2118
+ emit warnings:
2119
+
2120
+ >>> import warnings
2121
+ >>> def get_number():
2122
+ ... warnings.warn("This is a warning.")
2123
+ ... return 1
2124
+
2125
+ >>> get_number()
2126
+ Traceback (most recent call last):
2127
+ ...
2128
+ UserWarning: This is a warning.
2129
+
2130
+ >>> from hydpy.core.testtools import warn_later
2131
+ >>> with warn_later():
2132
+ ... get_number()
2133
+ 1
2134
+ UserWarning: This is a warning.
2135
+ """
2136
+ with warnings.catch_warnings(record=True) as records:
2137
+ warnings.resetwarnings()
2138
+ yield
2139
+ for record in records:
2140
+ print(record.category.__name__, record.message, sep=": ")
2141
+
2142
+
2143
+ def print_filestructure(dirpath: str) -> None:
2144
+ """Print the file structure of the given directory path in alphabetical order.
2145
+
2146
+ >>> import os
2147
+ >>> dirpath = os.path.join(data.__path__[0], "HydPy-H-Lahn")
2148
+ >>> from hydpy import data
2149
+ >>> from hydpy.core.testtools import print_filestructure
2150
+ >>> print_filestructure(dirpath) # doctest: +ELLIPSIS
2151
+ * ...hydpy/data/HydPy-H-Lahn
2152
+ - conditions
2153
+ - init_1996_01_01_00_00_00
2154
+ + land_dill_assl.py
2155
+ ...
2156
+ + stream_lahn_marb_lahn_leun.py
2157
+ - control
2158
+ - default
2159
+ + land.py
2160
+ ...
2161
+ + stream_lahn_marb_lahn_leun.py
2162
+ + multiple_runs.xml
2163
+ + multiple_runs_alpha.xml
2164
+ - network
2165
+ - default
2166
+ + headwaters.py
2167
+ + nonheadwaters.py
2168
+ + streams.py
2169
+ - series
2170
+ - default
2171
+ + dill_assl_obs_q.asc
2172
+ ...
2173
+ + obs_q.nc
2174
+ + single_run.xml
2175
+ + single_run.xmlt
2176
+ """
2177
+
2178
+ def _print_filestructure(dirpath: str, indent: int, /) -> None:
2179
+ prefix = indent * " "
2180
+ for name in sorted(os.listdir(dirpath)):
2181
+ if name != "__pycache__":
2182
+ subpath = os.path.join(dirpath, name)
2183
+ if os.path.isdir(subpath):
2184
+ print(f"{prefix}- {name}")
2185
+ _print_filestructure(subpath, indent + 4)
2186
+ else:
2187
+ print(f"{prefix}+ {name}")
2188
+
2189
+ dirpath = os.path.abspath(dirpath)
2190
+ print(objecttools.repr_(f"* {dirpath}"))
2191
+ _print_filestructure(dirpath, 4)
2192
+
2193
+
2194
+ def prepare_io_example_1() -> tuple[devicetools.Nodes, devicetools.Elements]:
2195
+ """Prepare an IO example configuration for testing purposes.
2196
+
2197
+ Function |prepare_io_example_1| is thought for testing the functioning of *HydPy*
2198
+ and thus should be of interest for framework developers only. It uses the main
2199
+ models |lland_dd|, |lland_knauf|, and |hland_96| and the submodel
2200
+ |evap_aet_morsim|. Here, we apply |prepare_io_example_1| and shortly discuss
2201
+ different aspects of its generated data:
2202
+
2203
+ >>> from hydpy.core.testtools import prepare_io_example_1
2204
+ >>> nodes, elements = prepare_io_example_1()
2205
+
2206
+ It defines a short initialisation period of five days:
2207
+
2208
+ >>> from hydpy import pub
2209
+ >>> pub.timegrids
2210
+ Timegrids("2000-01-01 00:00:00",
2211
+ "2000-01-05 00:00:00",
2212
+ "1d")
2213
+
2214
+ It prepares an empty directory for IO testing:
2215
+
2216
+ >>> import os
2217
+ >>> from hydpy import repr_, TestIO
2218
+ >>> with TestIO(): # doctest: +ELLIPSIS
2219
+ ... repr_(pub.sequencemanager.currentpath)
2220
+ ... os.listdir("project/series/default")
2221
+ '...iotesting/project/series/default'
2222
+ []
2223
+
2224
+ It returns four |Element| objects handling either application model |lland_dd|
2225
+ |lland_knauf|, or |hland_96|:
2226
+
2227
+ >>> for element in elements:
2228
+ ... print(element.name, element.model)
2229
+ element1 lland_dd
2230
+ element2 lland_dd
2231
+ element3 lland_knauf
2232
+ element4 hland_96
2233
+
2234
+ The |lland_knauf| instance has a submodel of type |evap_aet_morsim|:
2235
+
2236
+ >>> print(elements.element3.model.aetmodel.name)
2237
+ evap_aet_morsim
2238
+
2239
+ Two |Node| objects handling variables `Q` and `T`:
2240
+
2241
+ >>> for node in nodes:
2242
+ ... print(node.name, node.variable)
2243
+ node1 Q
2244
+ node2 T
2245
+
2246
+ It generates artificial time series data for the input sequence
2247
+ |lland_inputs.Nied|, the flux sequence |lland_fluxes.NKor|, and the state sequence
2248
+ |lland_states.BoWa| of each |lland| model instance, the equally named wind speed
2249
+ sequences of |lland_knauf| and |evap_aet_morsim|, the state sequence
2250
+ |hland_states.SP| of the |hland_96| model instance, and the |Sim| sequence of each
2251
+ node instance. For precise test results, all generated values are unique:
2252
+
2253
+ >>> nied1 = elements.element1.model.sequences.inputs.nied
2254
+ >>> nied1.series
2255
+ InfoArray([0., 1., 2., 3.])
2256
+ >>> nkor1 = elements.element1.model.sequences.fluxes.nkor
2257
+ >>> nkor1.series
2258
+ InfoArray([[12.],
2259
+ [13.],
2260
+ [14.],
2261
+ [15.]])
2262
+ >>> bowa3 = elements.element3.model.sequences.states.bowa
2263
+ >>> bowa3.series
2264
+ InfoArray([[48., 49., 50.],
2265
+ [51., 52., 53.],
2266
+ [54., 55., 56.],
2267
+ [57., 58., 59.]])
2268
+ >>> sim2 = nodes.node2.sequences.sim
2269
+ >>> sim2.series
2270
+ InfoArray([64., 65., 66., 67.])
2271
+ >>> sp4 = elements.element4.model.sequences.states.sp
2272
+ >>> sp4.series
2273
+ InfoArray([[[68., 69., 70.],
2274
+ [71., 72., 73.]],
2275
+ <BLANKLINE>
2276
+ [[74., 75., 76.],
2277
+ [77., 78., 79.]],
2278
+ <BLANKLINE>
2279
+ [[80., 81., 82.],
2280
+ [83., 84., 85.]],
2281
+ <BLANKLINE>
2282
+ [[86., 87., 88.],
2283
+ [89., 90., 91.]]])
2284
+ >>> v_l = elements.element3.model.sequences.inputs.windspeed
2285
+ >>> v_l.series
2286
+ InfoArray([68., 69., 70., 71.])
2287
+ >>> v_e = elements.element3.model.aetmodel.sequences.inputs.windspeed
2288
+ >>> v_e.series
2289
+ InfoArray([68., 69., 70., 71.])
2290
+
2291
+ All sequences carry |numpy.ndarray| objects with (deep) copies of the time
2292
+ series data for testing:
2293
+
2294
+ >>> import numpy
2295
+ >>> assert numpy.all(nied1.series == nied1.testarray)
2296
+ >>> assert numpy.all(nkor1.series == nkor1.testarray)
2297
+ >>> assert numpy.all(bowa3.series == bowa3.testarray)
2298
+ >>> assert numpy.all(sim2.series == sim2.testarray)
2299
+ >>> assert numpy.all(sp4.series == sp4.testarray)
2300
+ >>> assert numpy.all(v_l.series == v_l.testarray)
2301
+ >>> assert numpy.all(v_e.series == v_e.testarray)
2302
+ >>> bowa3.series[1, 2] = -999.0
2303
+ >>> assert not numpy.all(bowa3.series == bowa3.testarray)
2304
+ """
2305
+ from hydpy.models import hland # pylint: disable=import-outside-toplevel
2306
+ from hydpy.models import lland # pylint: disable=import-outside-toplevel
2307
+
2308
+ TestIO.clear()
2309
+ devicetools.Node.clear_all()
2310
+ devicetools.Element.clear_all()
2311
+
2312
+ hydpy.pub.projectname = "project"
2313
+ hydpy.pub.sequencemanager = filetools.SequenceManager()
2314
+ with TestIO():
2315
+ os.makedirs("project/series/default")
2316
+
2317
+ hydpy.pub.timegrids = "2000-01-01", "2000-01-05", "1d"
2318
+
2319
+ node1 = devicetools.Node("node1")
2320
+ node2 = devicetools.Node("node2", variable="T")
2321
+ nodes = devicetools.Nodes(node1, node2)
2322
+ element1 = devicetools.Element("element1", outlets=node1)
2323
+ element2 = devicetools.Element("element2", outlets=node1)
2324
+ element3 = devicetools.Element("element3", outlets=node1)
2325
+ element4 = devicetools.Element("element4", outlets=node1)
2326
+ elements_lland = devicetools.Elements(element1, element2, element3)
2327
+ elements = elements_lland + element4
2328
+
2329
+ element1.model = importtools.prepare_model("lland_dd")
2330
+ element2.model = importtools.prepare_model("lland_dd")
2331
+ element3.model = importtools.prepare_model("lland_knauf")
2332
+ element4.model = importtools.prepare_model("hland_96")
2333
+
2334
+ control3 = element3.model.parameters.control
2335
+ control3.nhru(1)
2336
+ control3.ft(1.0)
2337
+ control3.fhru(1.0)
2338
+ control3.lnk(lland.ACKER)
2339
+ control3.measuringheightwindspeed(10.0)
2340
+ control3.lai(3.0)
2341
+ control3.wmax(300.0)
2342
+ with element3.model.add_aetmodel_v1("evap_aet_morsim"):
2343
+ pass
2344
+
2345
+ for idx, element in enumerate(elements_lland):
2346
+ parameters = element.model.parameters
2347
+ parameters.control.nhru(idx + 1)
2348
+ parameters.control.lnk(lland.ACKER)
2349
+ parameters.derived.absfhru(10.0)
2350
+ control4 = element4.model.parameters.control
2351
+ control4.nmbzones(3)
2352
+ control4.sclass(2)
2353
+ control4.zonetype(hland.FIELD)
2354
+ control4.zonearea.values = 10.0
2355
+
2356
+ with hydpy.pub.options.printprogress(False):
2357
+ nodes.prepare_simseries(allocate_ram=False) # ToDo: add option "reset"
2358
+ nodes.prepare_simseries(allocate_ram=True)
2359
+ elements.prepare_inputseries(allocate_ram=False)
2360
+ elements.prepare_inputseries(allocate_ram=True)
2361
+ elements.prepare_factorseries(allocate_ram=False)
2362
+ elements.prepare_factorseries(allocate_ram=True)
2363
+ elements.prepare_fluxseries(allocate_ram=False)
2364
+ elements.prepare_fluxseries(allocate_ram=True)
2365
+ elements.prepare_stateseries(allocate_ram=False)
2366
+ elements.prepare_stateseries(allocate_ram=True)
2367
+
2368
+ def init_values(seq: TestIOSequence, value1_: float) -> float:
2369
+ value2_ = value1_ + len(seq.series.flatten())
2370
+ values_ = numpy.arange(value1_, value2_, dtype=config.NP_FLOAT)
2371
+ seq.testarray = values_.reshape(seq.seriesshape)
2372
+ seq.series = seq.testarray.copy()
2373
+ return value2_
2374
+
2375
+ value1 = 0.0
2376
+ for subname, seqname in zip(
2377
+ ["inputs", "fluxes", "states"], ["nied", "nkor", "bowa"]
2378
+ ):
2379
+ for element in elements_lland:
2380
+ subseqs = getattr(element.model.sequences, subname)
2381
+ value1 = init_values(getattr(subseqs, seqname), value1)
2382
+ for node in nodes:
2383
+ value1 = init_values(node.sequences.sim, value1) # type: ignore[arg-type]
2384
+ init_values(element4.model.sequences.states.sp, value1) # type: ignore[arg-type]
2385
+ init_values(
2386
+ element3.model.sequences.inputs.windspeed, value1 # type: ignore[arg-type]
2387
+ )
2388
+ init_values(element3.model.aetmodel.sequences.inputs.windspeed, value1)
2389
+
2390
+ return nodes, elements
2391
+
2392
+
2393
+ def prepare_full_example_1(dirpath: str | None = None) -> None:
2394
+ """Prepare the `HydPy-H-Lahn` example project on disk.
2395
+
2396
+ By default, function |prepare_full_example_1| copies the original project data into
2397
+ the `iotesting` directory, thought for performing automated tests on real-world
2398
+ data. The following doctest shows the generated folder structure:
2399
+
2400
+ >>> from hydpy.core.testtools import prepare_full_example_1
2401
+ >>> prepare_full_example_1()
2402
+ >>> from hydpy import TestIO
2403
+ >>> import os
2404
+ >>> with TestIO():
2405
+ ... print("root:", *sorted(os.listdir(".")))
2406
+ ... for folder in ("control", "conditions", "series"):
2407
+ ... print(f"HydPy-H-Lahn/{folder}:",
2408
+ ... *sorted(os.listdir(f"HydPy-H-Lahn/{folder}")))
2409
+ root: HydPy-H-Lahn __init__.py
2410
+ HydPy-H-Lahn/control: default
2411
+ HydPy-H-Lahn/conditions: init_1996_01_01_00_00_00
2412
+ HydPy-H-Lahn/series: default
2413
+
2414
+ Pass an alternative path if you prefer to work in another directory:
2415
+
2416
+ .. testsetup::
2417
+
2418
+ >>> "HydPy-H-Lahn" in os.listdir(".")
2419
+ False
2420
+
2421
+ >>> prepare_full_example_1(dirpath=".")
2422
+
2423
+ .. testsetup::
2424
+
2425
+ >>> "HydPy-H-Lahn" in os.listdir(".")
2426
+ True
2427
+ >>> import shutil
2428
+ >>> shutil.rmtree("HydPy-H-Lahn")
2429
+ """
2430
+ devicetools.Node.clear_all()
2431
+ devicetools.Element.clear_all()
2432
+ if dirpath is None:
2433
+ TestIO.clear()
2434
+ dirpath = iotesting.__path__[0]
2435
+ datapath: str = data.__path__[0]
2436
+ shutil.copytree(
2437
+ os.path.join(datapath, "HydPy-H-Lahn"), os.path.join(dirpath, "HydPy-H-Lahn")
2438
+ )
2439
+
2440
+
2441
+ def prepare_full_example_2(
2442
+ lastdate: timetools.DateConstrArg = "1996-01-05",
2443
+ ) -> tuple[hydpytools.HydPy, pubtools.Pub, type[TestIO]]:
2444
+ """Prepare the `HydPy-H-Lahn` project on disk and in RAM.
2445
+
2446
+ Function |prepare_full_example_2| is an extensions of function
2447
+ |prepare_full_example_1|. Besides preparing the project data of the `HydPy-H-Lahn`
2448
+ example project, it performs all necessary steps to start a simulation run.
2449
+ Therefore, it returns a readily prepared |HydPy| instance, as well as, for
2450
+ convenience, module |pub| and class |TestIO|:
2451
+
2452
+ >>> from hydpy.core.testtools import prepare_full_example_2
2453
+ >>> hp, pub, TestIO = prepare_full_example_2()
2454
+ >>> hp.nodes
2455
+ Nodes("dill_assl", "lahn_kalk", "lahn_leun", "lahn_marb")
2456
+ >>> hp.elements
2457
+ Elements("land_dill_assl", "land_lahn_kalk", "land_lahn_leun",
2458
+ "land_lahn_marb", "stream_dill_assl_lahn_leun",
2459
+ "stream_lahn_leun_lahn_kalk", "stream_lahn_marb_lahn_leun")
2460
+ >>> pub.timegrids
2461
+ Timegrids("1996-01-01 00:00:00",
2462
+ "1996-01-05 00:00:00",
2463
+ "1d")
2464
+ >>> from hydpy import classname
2465
+ >>> classname(TestIO)
2466
+ 'TestIO'
2467
+
2468
+ Function |prepare_full_example_2| is primarily thought for testing and thus does
2469
+ not allow for many configurations except changing the end date of the
2470
+ initialisation period:
2471
+
2472
+ >>> hp, pub, TestIO = prepare_full_example_2(lastdate="1996-01-02")
2473
+ >>> pub.timegrids
2474
+ Timegrids("1996-01-01 00:00:00",
2475
+ "1996-01-02 00:00:00",
2476
+ "1d")
2477
+ """
2478
+ prepare_full_example_1()
2479
+ with TestIO():
2480
+ hp = hydpytools.HydPy("HydPy-H-Lahn")
2481
+ hydpy.pub.timegrids = "1996-01-01", lastdate, "1d"
2482
+ hp.prepare_everything()
2483
+ return hp, hydpy.pub, TestIO