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,3337 @@
1
+ """This module implements features for calibrating model parameters.
2
+
3
+ .. _`NLopt`: https://nlopt.readthedocs.io/en/latest/
4
+ """
5
+
6
+ # import...
7
+ # ...from standard library
8
+ from __future__ import annotations
9
+ import abc
10
+ import collections
11
+ import itertools
12
+ import time
13
+ import types
14
+ import warnings
15
+
16
+ # ...from site-packages
17
+ import black
18
+ import numpy
19
+
20
+ # ...from hydpy
21
+ import hydpy
22
+ from hydpy import config
23
+ from hydpy.core import devicetools
24
+ from hydpy.core import hydpytools
25
+ from hydpy.core import masktools
26
+ from hydpy.core import objecttools
27
+ from hydpy.core import parametertools
28
+ from hydpy.core import propertytools
29
+ from hydpy.core import selectiontools
30
+ from hydpy.core import timetools
31
+ from hydpy.core import variabletools
32
+ from hydpy.auxs import iuhtools
33
+ from hydpy.core.typingtools import *
34
+
35
+ if TYPE_CHECKING:
36
+ from hydpy.models.arma import arma_control
37
+
38
+ TypeParameter = TypeVar("TypeParameter", bound=parametertools.Parameter)
39
+ TypeRule1 = TypeVar(
40
+ "TypeRule1", bound=Union["Replace", "Add", "Multiply", "ReplaceIUH", "MultiplyIUH"]
41
+ )
42
+ TypeRule2 = TypeVar(
43
+ "TypeRule2", bound=Union["Replace", "Add", "Multiply", "ReplaceIUH", "MultiplyIUH"]
44
+ )
45
+ TypeRule = TypeVar("TypeRule", "Replace", "Add", "Multiply")
46
+ Target: TypeAlias = Optional[str]
47
+
48
+
49
+ class TargetFunction(Protocol):
50
+ """Protocol class for the target function required by class |CalibrationInterface|.
51
+
52
+ The target functions must calculate and return a floating-point number reflecting
53
+ the quality of the current parameterisation of the models of the current project.
54
+ Often, as in the following example, the target function relies on objective
55
+ functions as |nse|, applied on the time series of the |Sim| and |Obs| sequences
56
+ handled by the |HydPy| object:
57
+
58
+ >>> from hydpy import HydPy, nse, TargetFunction
59
+ >>> class Target(TargetFunction):
60
+ ... def __init__(self, hp):
61
+ ... self.hp = hp
62
+ ... def __call__(self):
63
+ ... return sum(nse(node=node) for node in self.hp.nodes)
64
+ >>> target = Target(HydPy())
65
+
66
+ See the documentation on class |CalibrationInterface| for more information.
67
+ """
68
+
69
+ def __call__(self) -> float:
70
+ """Return some kind of efficience criterion."""
71
+
72
+
73
+ class Adaptor(Protocol):
74
+ """Protocol class for defining adoptors required by |Replace| objects.
75
+
76
+ Often, one calibration parameter (represented by one |Replace| object) depends on
77
+ other calibration parameters (represented by other |Replace| objects) or other
78
+ "real" parameter values. Please select an existing or define a new adaptor and
79
+ assign it to a |Replace| object to introduce such dependencies.
80
+
81
+ See class |SumAdaptor| or class |FactorAdaptor| for concrete examples.
82
+ """
83
+
84
+ def __call__(self, target: parametertools.Parameter) -> None:
85
+ """Modify the value(s) of the given target |Parameter| object."""
86
+
87
+
88
+ class SumAdaptor(Adaptor):
89
+ """Adaptor, which calculates the sum of the values of multiple |Rule| objects and
90
+ assigns it to the value(s) of the target |Parameter| object.
91
+
92
+ Class |SumAdaptor| helps to introduce "larger than" relationships between
93
+ calibration parameters. A common use case is the time of concentration of
94
+ different runoff components. For example, the time of concentration of base flow
95
+ should be larger than the one of direct runoff. Accordingly, when modelling runoff
96
+ concentration with linear storages, the recession coefficient of direct runoff
97
+ should be larger. Principally, we could ensure this during a calibration process by
98
+ defining two |Rule| objects with fixed non-overlapping parameter ranges. For
99
+ example, we could search for the best direct runoff delay between 1 and 5 days and
100
+ the base flow delay between 5 and 100 days. We demonstrate this for the recession
101
+ coefficient parameters |hland_control.K| and |hland_control.K4| of application
102
+ model |hland_96| (assuming the nonlinearity parameter |hland_control.Alpha| to be
103
+ zero):
104
+
105
+ >>> from hydpy.core.testtools import prepare_full_example_2
106
+ >>> hp, pub, TestIO = prepare_full_example_2()
107
+ >>> from hydpy import Replace, SumAdaptor
108
+ >>> k = Replace(name="k",
109
+ ... parameter="k",
110
+ ... value=2.0**-1,
111
+ ... lower=5.0**-1,
112
+ ... upper=1.0**-1,
113
+ ... parameterstep="1d",
114
+ ... model="hland_96")
115
+ >>> k4 = Replace(name="k4",
116
+ ... parameter="k4",
117
+ ... value=10.0**-1,
118
+ ... lower=100.0**-1,
119
+ ... upper=5.0**-1,
120
+ ... parameterstep="1d",
121
+ ... model="hland_96")
122
+
123
+ To allow for non-fixed non-overlapping ranges, we can prepare a |SumAdaptor| object,
124
+ knowing both our |Rule| objects, assign it the direct runoff-related |Rule| object,
125
+ and, for example, set its lower boundary to zero:
126
+
127
+ >>> k.adaptor = SumAdaptor(k, k4)
128
+ >>> k.lower = 0.0
129
+
130
+ Calling method |Replace.apply_value| of the |Replace| objects makes our
131
+ |SumAdaptor| object apply the sum of the values of all of its |Rule| objects:
132
+
133
+ >>> control = hp.elements.land_dill_assl.model.parameters.control
134
+ >>> k.apply_value()
135
+ >>> with pub.options.parameterstep("1d"):
136
+ ... control.k
137
+ k(0.6)
138
+ """
139
+
140
+ _rules: tuple[Rule[parametertools.Parameter], ...]
141
+
142
+ def __init__(self, *rules: Rule[parametertools.Parameter]):
143
+ self._rules = tuple(rules)
144
+
145
+ def __call__(self, target: parametertools.Parameter) -> None:
146
+ target(sum(rule.value for rule in self._rules))
147
+
148
+
149
+ class FactorAdaptor(Adaptor):
150
+ """Adaptor, which calculates the product of the value of the parent |Replace|
151
+ object and the value(s) of a given reference |Parameter| object and assigns it to
152
+ the value(s) of the target |Parameter| object.
153
+
154
+ Class |FactorAdaptor| helps to respect dependencies between model parameters. If
155
+ you, for example, aim at calibrating the permanent wilting point
156
+ (|lland_control.PWP|) of model |lland_dd|, you need to make sure it always agrees
157
+ with the maximum soil water storage (|lland_control.WMax|). Especially, one should
158
+ avoid permanent wilting points larger than total porosity. Due to the high
159
+ variability of soil properties within most catchments, it is no real option to
160
+ define a fixed upper threshold for |lland_control.PWP|. By using class
161
+ |FactorAdaptor|, you can instead calibrate a multiplication factor. Setting the
162
+ bounds of such a factor to 0.0 and 0.5, for example, would result in
163
+ |lland_control.PWP| values ranging from zero up to half of |lland_control.WMax| for
164
+ each respective response unit.
165
+
166
+ To show how class |FactorAdaptor| works, we select another use-case based on the
167
+ `Lahn` example project prepared by function |prepare_full_example_2|:
168
+
169
+ >>> from hydpy.core.testtools import prepare_full_example_2
170
+ >>> hp, pub, TestIO = prepare_full_example_2()
171
+
172
+ |hland_96| calculates the "normal" potential snow-melt with the degree-day factor
173
+ |hland_control.CFMax|. For glacial zones, it also calculates a separate potential
174
+ glacier-melt with the additional degree-day factor |hland_control.GMelt|. Suppose
175
+ we have |hland_control.CFMax| readily available for the different hydrological
176
+ response units of the Lahn catchment. We might find it useful to calibrate
177
+ |hland_control.GMelt| based on the spatial pattern of |hland_control.CFMax|.
178
+ Therefore, we first define an |Replace| rule for parameter |hland_control.GMelt|:
179
+
180
+ >>> from hydpy import Replace, FactorAdaptor
181
+ >>> gmelt = Replace(name="gmelt",
182
+ ... parameter="gmelt",
183
+ ... value=2.0,
184
+ ... lower=0.5,
185
+ ... upper=2.0,
186
+ ... parameterstep="1d",
187
+ ... model="hland_96")
188
+
189
+ Second, we initialise a |FactorAdaptor| object based on target rule `gmelt` and our
190
+ reference parameter |hland_control.CFMax| and assign it our rule object:
191
+
192
+ >>> gmelt.adaptor = FactorAdaptor(gmelt, "cfmax")
193
+
194
+ The `dill_assl` subcatchment, like the whole `Lahn` basin, does not contain any
195
+ glaciers. Hence it defines (identical) |hland_control.CFMax| values for the zones
196
+ of type |hland_constants.FIELD| and |hland_constants.FOREST| but must not specify
197
+ any value for |hland_control.GMelt|:
198
+
199
+ >>> control = hp.elements.land_dill_assl.model.parameters.control
200
+ >>> control.cfmax
201
+ cfmax(field=4.55853, forest=2.735118)
202
+ >>> control.gmelt
203
+ gmelt(nan)
204
+
205
+ Next, we call method |Replace.apply_value| of the |Replace| object to apply the
206
+ |FactorAdaptor| object on all relevant |hland_control.GMelt| instances of the `Lahn`
207
+ catchment:
208
+
209
+ >>> gmelt.adaptor(control.gmelt)
210
+
211
+ The string representation of the |hland_control.GMelt| instance of the Dill
212
+ catchment indicates nothing happened:
213
+
214
+ >>> control.gmelt
215
+ gmelt(nan)
216
+
217
+ However, inspecting the individual values of the respective response units reveals
218
+ the multiplication was successful:
219
+
220
+ >>> from hydpy import print_vector
221
+ >>> print_vector(control.gmelt.values)
222
+ 9.11706, 5.470236, 9.11706, 5.470236, 9.11706, 5.470236, 9.11706,
223
+ 5.470236, 9.11706, 5.470236, 9.11706, 5.470236
224
+
225
+ Calculating values for response units that do not require these values can be
226
+ misleading. We can improve the situation by using the masks provided by the
227
+ respective model; in our example, mask |hland_masks.Glacier|. To make this
228
+ clearer, we set the first six response units to |hland_control.ZoneType|
229
+ |hland_constants.GLACIER|:
230
+
231
+ >>> from hydpy.models.hland_96 import *
232
+ >>> control.zonetype(GLACIER, GLACIER, GLACIER, GLACIER, GLACIER, GLACIER,
233
+ ... FIELD, FOREST, ILAKE, FIELD, FOREST, ILAKE)
234
+
235
+ We now can assign the |SumAdaptor| object to the direct runoff-related |Replace|
236
+ object and, for example, set its lower boundary to zero:
237
+
238
+ Now we create a new |FactorAdaptor| object, handling the same parameters but also
239
+ the |hland_masks.Glacier| mask:
240
+
241
+ >>> gmelt.adaptor = FactorAdaptor(gmelt, "cfmax", "glacier")
242
+
243
+ To see the results of our new adaptor object, we change the values both of our
244
+ reference parameter and our rule object:
245
+
246
+ >>> control.cfmax(field=5.0, forest=3.0, glacier=6.0)
247
+ >>> gmelt.value = 0.5
248
+
249
+ The string representation of our target parameter shows that the glacier-related
250
+ day degree factor of all glacier zones is now half as large as the snow-related one:
251
+
252
+ >>> gmelt.apply_value()
253
+ >>> control.gmelt
254
+ gmelt(3.0)
255
+
256
+ Note that all remaining values (for zone types |hland_constants.FIELD|,
257
+ |hland_constants.FOREST|, and |hland_constants.ILAKE| are still the same. This
258
+ intended behaviour allows calibrating, for example, hydrological response units of
259
+ different types with different rule objects:
260
+
261
+ >>> print_vector(control.gmelt.values)
262
+ 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 9.11706, 5.470236, 9.11706, 5.470236,
263
+ 9.11706, 5.470236
264
+ """
265
+
266
+ _rule: Rule[parametertools.Parameter]
267
+ _reference: str
268
+ _mask: str | None
269
+
270
+ def __init__(
271
+ self,
272
+ rule: Rule[parametertools.Parameter],
273
+ reference: type[parametertools.Parameter] | parametertools.Parameter | str,
274
+ mask: masktools.BaseMask | str | None = None,
275
+ ):
276
+ self._rule = rule
277
+ self._reference = str(getattr(reference, "name", reference))
278
+ self._mask = mask if ((mask is None) or isinstance(mask, str)) else mask.name
279
+
280
+ def __call__(self, target: parametertools.Parameter) -> None:
281
+ ref = target.subpars[self._reference]
282
+ if self._mask:
283
+ mask = ref.get_submask(self._mask)
284
+ values = ref.values[mask] if ref.NDIM else ref.value
285
+ target.values[mask] = self._rule.value * values
286
+ else:
287
+ target.value = self._rule.value * ref.value
288
+
289
+
290
+ class Rule(abc.ABC, Generic[TypeParameter]):
291
+ """Base class for defining calibration rules.
292
+
293
+ Each |Rule| object relates one calibration parameter with some model parameters.
294
+ We select the class |Replace| as a concrete example for the following explanations
295
+ and use the `Lahn` example project, which we prepare by calling function
296
+ |prepare_full_example_2|:
297
+
298
+ >>> from hydpy.core.testtools import prepare_full_example_2
299
+ >>> hp, pub, TestIO = prepare_full_example_2()
300
+
301
+ We define a |Rule| object supposed to replace the values of parameter
302
+ |hland_control.FC| of application model |lland_dd|. Note that argument `name` is
303
+ the rule's name, whereas the argument `parameter` is the parameter's name:
304
+
305
+ >>> from hydpy import Replace
306
+ >>> rule = Replace(name="fc",
307
+ ... parameter="fc",
308
+ ... value=100.0,
309
+ ... model="hland_96")
310
+
311
+ The following string representation shows us the complete list of available
312
+ arguments:
313
+
314
+ >>> rule
315
+ Replace(
316
+ name="fc",
317
+ parameter="fc",
318
+ value=100.0,
319
+ lower=-inf,
320
+ upper=inf,
321
+ keyword=None,
322
+ parameterstep=None,
323
+ model="hland_96",
324
+ selections=("complete",),
325
+ )
326
+
327
+ The initial value of parameter |hland_control.FC| is 206 mm:
328
+
329
+ >>> fc = hp.elements.land_lahn_marb.model.parameters.control.fc
330
+ >>> fc
331
+ fc(206.0)
332
+
333
+ We can modify it by calling method |Rule.apply_value|:
334
+
335
+ >>> rule.apply_value()
336
+ >>> fc
337
+ fc(100.0)
338
+
339
+ You can change and apply the value at any time:
340
+
341
+ >>> rule.value = 200.0
342
+ >>> rule.apply_value()
343
+ >>> fc
344
+ fc(200.0)
345
+
346
+ Sometimes, one must differentiate between the original value to be calibrated and
347
+ the actually applied value. Therefore, (only) the |Replace| class allows for
348
+ defining custom "adaptors". Prepare an |Adaptor| function and assign it to the
349
+ relevant |Replace| object (see the documentation on class |SumAdaptor| or
350
+ |FactorAdaptor| for more realistic examples):
351
+
352
+ >>> rule.adaptor = lambda target: target(2.0 * rule.value)
353
+
354
+ Now, our rule does not apply the original but the adapted calibration parameter
355
+ value:
356
+
357
+ >>> rule.apply_value()
358
+ >>> fc
359
+ fc(400.0)
360
+
361
+ Use method |Rule.reset_parameters| to restore the original states of the affected
362
+ parameters ("original" here means at the time of initialisation of the |Rule|
363
+ object):
364
+
365
+ >>> rule.reset_parameters()
366
+ >>> fc
367
+ fc(206.0)
368
+
369
+ Some parameter types support defining their values via custom keywords.
370
+ |hland_control.FC|, for example, allows setting the values of multiple zones of
371
+ the same land-use type via keyword arguments such as `forest`:
372
+
373
+ >>> rule = Replace(name="fc",
374
+ ... parameter="fc",
375
+ ... value=100.0,
376
+ ... keyword="forest",
377
+ ... model="hland_96")
378
+ >>> rule.apply_value()
379
+ >>> fc
380
+ fc(field=206.0, forest=100.0)
381
+
382
+ The value of parameter |hland_control.FC| is not time-dependent. Therefore, any
383
+ |Options.parameterstep| information given to its |Rule| object is ignored (note
384
+ that we pass an example parameter object of type |hland_control.FC| instead of the
385
+ string `fc` this time):
386
+
387
+ >>> Replace(name="fc",
388
+ ... parameter=fc,
389
+ ... value=100.0,
390
+ ... model="hland_96",
391
+ ... parameterstep="1d")
392
+ Replace(
393
+ name="fc",
394
+ parameter="fc",
395
+ value=100.0,
396
+ lower=-inf,
397
+ upper=inf,
398
+ keyword=None,
399
+ parameterstep=None,
400
+ model="hland_96",
401
+ selections=("complete",),
402
+ )
403
+
404
+ For time-dependent parameters, the rule queries the current global
405
+ |Options.parameterstep| value if you do not specify one explicitly (note that we
406
+ pass the parameter type |hland_control.PercMax| and the module |hland_96| this
407
+ time):
408
+
409
+ >>> from hydpy.models import hland_96
410
+ >>> from hydpy.models.hland.hland_control import PercMax
411
+ >>> rule = Replace(name="percmax",
412
+ ... parameter=PercMax,
413
+ ... value=5.0,
414
+ ... model=hland_96)
415
+
416
+ The |Rule| object internally handles, to avoid confusion, a copy of
417
+ |Options.parameterstep|.
418
+
419
+ >>> from hydpy import pub
420
+ >>> pub.options.parameterstep = None
421
+ >>> rule
422
+ Replace(
423
+ name="percmax",
424
+ parameter="percmax",
425
+ value=5.0,
426
+ lower=-inf,
427
+ upper=inf,
428
+ keyword=None,
429
+ parameterstep="1d",
430
+ model="hland_96",
431
+ selections=("complete",),
432
+ )
433
+ >>> rule.apply_value()
434
+ >>> percmax = hp.elements.land_lahn_marb.model.parameters.control.percmax
435
+ >>> with pub.options.parameterstep("1d"):
436
+ ... percmax
437
+ percmax(5.0)
438
+
439
+ Alternatively, you can pass a parameter step size yourself:
440
+
441
+ >>> rule = Replace(name="percmax",
442
+ ... parameter="percmax",
443
+ ... value=5.0,
444
+ ... model="hland_96",
445
+ ... parameterstep="2d")
446
+ >>> rule.apply_value()
447
+ >>> with pub.options.parameterstep("1d"):
448
+ ... percmax
449
+ percmax(2.5)
450
+
451
+ Missing parameter step-size information results in the following error:
452
+
453
+ >>> Replace(name="percmax",
454
+ ... parameter="percmax",
455
+ ... value=5.0,
456
+ ... model="hland_96")
457
+ Traceback (most recent call last):
458
+ ...
459
+ RuntimeError: While trying to initialise the `Replace` rule object `percmax`, the \
460
+ following error occurred: Rules which handle time-dependent parameters require \
461
+ information on the parameter timestep size. Either assign it directly or define it \
462
+ via option `parameterstep`.
463
+
464
+ With the following definition, the |Rule| object queries all |Element| objects
465
+ handling |hland_96| instances from the global |Selections| object `pub.selections`:
466
+
467
+ >>> rule = Replace(name="fc",
468
+ ... parameter="fc",
469
+ ... value=100.0,
470
+ ... model="hland_96")
471
+ >>> rule.elements
472
+ Elements("land_dill_assl", "land_lahn_kalk", "land_lahn_leun",
473
+ "land_lahn_marb")
474
+
475
+ Alternatively, you can specify selections by passing themselves or their names (the
476
+ latter requires them to be a member of `pub.selections`):
477
+
478
+ >>> rule = Replace(name="fc",
479
+ ... parameter="fc",
480
+ ... value=100.0,
481
+ ... selections=[pub.selections.headwaters, "nonheadwaters"])
482
+ >>> rule.elements
483
+ Elements("land_dill_assl", "land_lahn_kalk", "land_lahn_leun",
484
+ "land_lahn_marb")
485
+
486
+ When not using the model argument, you must ensure the selected elements handle the
487
+ correct model instance:
488
+
489
+ >>> Replace(name="fc",
490
+ ... parameter="fc",
491
+ ... value=100.0)
492
+ Traceback (most recent call last):
493
+ ...
494
+ RuntimeError: While trying to initialise the `Replace` rule object `fc`, the \
495
+ following error occurred: No (sub)model of element `stream_dill_assl_lahn_leun` \
496
+ defines a control parameter named `fc`.
497
+
498
+ "Empty" rule objects are always considered erroneous:
499
+
500
+ >>> Replace(name="fc",
501
+ ... parameter="fc",
502
+ ... value=100.0,
503
+ ... model="musk_classic",
504
+ ... selections=[pub.selections.headwaters, "nonheadwaters"])
505
+ Traceback (most recent call last):
506
+ ...
507
+ ValueError: While trying to initialise the `Replace` rule object `fc`, the \
508
+ following error occurred: Object `Selections("headwaters", "nonheadwaters")` does not \
509
+ handle any `musk_classic` model instances.
510
+
511
+ All mentioned functionalities also work for submodels:
512
+
513
+ >>> rule = Replace(name="soilmoisturelimit",
514
+ ... parameter="soilmoisturelimit",
515
+ ... value=0.8,
516
+ ... model="evap_aet_hbv96")
517
+ >>> submodel = hp.elements.land_lahn_marb.model.aetmodel
518
+ >>> soilmoisturelimit = submodel.parameters.control.soilmoisturelimit
519
+ >>> soilmoisturelimit
520
+ soilmoisturelimit(0.9)
521
+ >>> rule.apply_value()
522
+ >>> soilmoisturelimit
523
+ soilmoisturelimit(0.8)
524
+
525
+ We encourage explicitly defining the model type when working with complex submodel
526
+ combinations so as not to calibrate different but equally named parameters
527
+ accidentally:
528
+
529
+ >>> rule = Replace(name="fc",
530
+ ... parameter="fc",
531
+ ... value=0.8,
532
+ ... model="evap_aet_hbv96")
533
+ Traceback (most recent call last):
534
+ ...
535
+ RuntimeError: While trying to initialise the `Replace` rule object `fc`, the \
536
+ following error occurred: Model `evap_aet_hbv96` of element `land_dill_assl` does not \
537
+ define a control parameter named `fc`.
538
+
539
+ We consider name clashes like the following made-up example unlikely but still
540
+ carry out additional runtime type checks as a precaution:
541
+
542
+ >>> control = hp.elements.land_lahn_marb.model.parameters.control
543
+ >>> control.soilmoisturelimit = control.fc
544
+ >>> rule = Replace(name="?",
545
+ ... parameter="soilmoisturelimit",
546
+ ... value=0.8,
547
+ ... selections=[pub.selections.headwaters])
548
+ Traceback (most recent call last):
549
+ ...
550
+ RuntimeError: While trying to initialise the `Replace` rule object `?`, the \
551
+ following error occurred: Parameter types are inconsistent: \
552
+ `hydpy.models.hland.hland_control.FC` vs \
553
+ `hydpy.models.evap.evap_control.SoilMoistureLimit`.
554
+ """
555
+
556
+ name: str
557
+ """The name of the |Rule| object."""
558
+
559
+ lower: float
560
+ """Lower boundary value.
561
+
562
+ No lower boundary corresponds to minus |numpy.inf|.
563
+ """
564
+
565
+ upper: float
566
+ """Upper boundary value.
567
+
568
+ No upper boundary corresponds to plus |numpy.inf|.
569
+ """
570
+
571
+ parametername: str
572
+ """The name of the addressed |Parameter| objects."""
573
+
574
+ parametertype: type[TypeParameter]
575
+ """The type of the addressed |Parameter| objects."""
576
+
577
+ keyword: str | None
578
+ """The name of the addressed keyword argument or, for a positional argument,
579
+ |None|."""
580
+
581
+ element2parameters: dict[devicetools.Element, list[TypeParameter]]
582
+ """The |Element| objects and their related parameter objects."""
583
+
584
+ selections: tuple[str, ...]
585
+ """The names of all relevant |Selection| objects."""
586
+
587
+ _value: float
588
+ _model: str | None
589
+ _parameterstep: timetools.Period | None
590
+ _original_parameter_values: tuple[Any, ...]
591
+
592
+ def __init__(
593
+ self,
594
+ *,
595
+ name: str,
596
+ parameter: type[TypeParameter] | TypeParameter | str,
597
+ value: float,
598
+ lower: float = -numpy.inf,
599
+ upper: float = numpy.inf,
600
+ keyword: str | None = None,
601
+ parameterstep: timetools.PeriodConstrArg | None = None,
602
+ selections: Iterable[selectiontools.Selection | str] | None = None,
603
+ model: types.ModuleType | str | None = None,
604
+ ) -> None:
605
+
606
+ def _add_parameter(element: hydpy.Element, parameter: TypeParameter, /) -> None:
607
+ if hasattr(self, "parametertype"):
608
+ if not isinstance(parameter, self.parametertype):
609
+ type1 = type(parameter)
610
+ name1 = ".".join([type1.__module__, type1.__name__])
611
+ type2 = self.parametertype
612
+ name2 = ".".join([type2.__module__, type2.__name__])
613
+ raise RuntimeError(
614
+ f"Parameter types are inconsistent: `{name1}` vs `{name2}`."
615
+ )
616
+ else:
617
+ self.parametertype = type(parameter)
618
+ if element not in self.element2parameters:
619
+ self.element2parameters[element] = []
620
+ self.element2parameters[element].append(parameter)
621
+
622
+ try:
623
+ self.name = name
624
+ self.parametername = str(getattr(parameter, "name", parameter))
625
+ self.keyword = keyword
626
+ self.upper = upper
627
+ self.lower = lower
628
+ self.value = value
629
+
630
+ if model is None:
631
+ self._model = model
632
+ elif isinstance(model, str):
633
+ self._model = model
634
+ else:
635
+ self._model = model.__name__.rpartition(".")[-1]
636
+
637
+ if selections is None:
638
+ selections = (hydpy.pub.selections.complete,)
639
+
640
+ names, sels = [], []
641
+ for sel in selections:
642
+ if isinstance(sel, str):
643
+ name_ = sel
644
+ if sel == "complete":
645
+ sel = hydpy.pub.selections.complete.copy("__complete__")
646
+ else:
647
+ sel = hydpy.pub.selections[name_]
648
+ else:
649
+ name_ = sel.name
650
+ if name_ == "complete":
651
+ sel = sel.copy("__complete__")
652
+ names.append(name_)
653
+ sels.append(sel)
654
+ selections = selectiontools.Selections(*sels)
655
+ self.selections = tuple(names)
656
+
657
+ parname = self.parametername
658
+ self.element2parameters = {}
659
+ for element in selections.elements:
660
+ if self._model is None:
661
+ found_submodel = False
662
+ for submodel in element.model.find_submodels(
663
+ include_mainmodel=True
664
+ ).values():
665
+ control = submodel.parameters.control
666
+ if (par := getattr(control, parname, None)) is not None:
667
+ found_submodel = True
668
+ _add_parameter(element, par)
669
+ if not found_submodel:
670
+ raise RuntimeError(
671
+ f"No (sub)model of element `{element.name}` defines a "
672
+ f"control parameter named `{parname}`."
673
+ )
674
+ else:
675
+ for submodel in element.model.query_submodels(self._model):
676
+ control = submodel.parameters.control
677
+ if (par := getattr(control, parname, None)) is None:
678
+ raise RuntimeError(
679
+ f"Model {objecttools.elementphrase(submodel)} does "
680
+ f"not define a control parameter named `{parname}`."
681
+ )
682
+ _add_parameter(element, par)
683
+ if not self.element2parameters:
684
+ raise ValueError(
685
+ f"Object `{selections}` does not handle any `{self._model}` model "
686
+ f"instances."
687
+ )
688
+
689
+ self.parameterstep = parameterstep
690
+ self._original_parameter_values = self._get_original_parameter_values()
691
+ except BaseException:
692
+ objecttools.augment_excmessage(
693
+ f"While trying to initialise the `{type(self).__name__}` rule object "
694
+ f"`{name}`"
695
+ )
696
+
697
+ @property
698
+ def elements(self) -> hydpy.Elements:
699
+ """The |Element| objects, which handle the relevant target |Parameter|
700
+ instances."""
701
+ return hydpy.Elements(self.element2parameters)
702
+
703
+ def _get_original_parameter_values(self) -> tuple[Any, ...]:
704
+ with hydpy.pub.options.parameterstep(self.parameterstep):
705
+ if self.keyword is None:
706
+ return tuple(par.revert_timefactor(par.value) for par in self)
707
+ return tuple(par.keywordarguments[self.keyword] for par in self)
708
+
709
+ @property
710
+ def value(self) -> float:
711
+ """The calibration parameter value.
712
+
713
+ Property |Rule.value| ensures that the given value adheres to the defined lower
714
+ and upper boundaries:
715
+
716
+ >>> from hydpy import Replace
717
+ >>> from hydpy.core.testtools import prepare_full_example_2
718
+ >>> hp, pub, TestIO = prepare_full_example_2()
719
+ >>> rule = Replace(name="fc",
720
+ ... parameter="fc",
721
+ ... value=100.0,
722
+ ... lower=50.0,
723
+ ... upper=200.0,
724
+ ... model="hland_96")
725
+
726
+ >>> rule.value = 0.0
727
+ >>> rule.value
728
+ 50.0
729
+
730
+ With option |Options.warntrim| enabled (the default), property |Rule.value|
731
+ also emits a warning like the following:
732
+
733
+ >>> from hydpy.core.testtools import warn_later
734
+ >>> with pub.options.warntrim(True), warn_later():
735
+ ... rule.value = 300.0
736
+ UserWarning: The value of the `Replace` object `fc` must not be smaller than \
737
+ `50.0` or larger than `200.0`, but the given value is `300.0`. Applying the trimmed \
738
+ value `200.0` instead.
739
+ >>> rule.value
740
+ 200.0
741
+ """
742
+ return self._value
743
+
744
+ @value.setter
745
+ def value(self, value: float) -> None:
746
+ if self.lower <= value <= self.upper:
747
+ self._value = value
748
+ else:
749
+ self._value = min(max(value, self.lower), self.upper)
750
+ if hydpy.pub.options.warntrim:
751
+ repr_ = objecttools.repr_
752
+ warnings.warn(
753
+ f"The value of the `{type(self).__name__}` object `{self}` must "
754
+ f"not be smaller than `{repr_(self.lower)}` or larger than "
755
+ f"`{repr_(self.upper)}`, but the given value is `{repr_(value)}`. "
756
+ f"Applying the trimmed value `{repr_(self._value)}` instead."
757
+ )
758
+
759
+ @abc.abstractmethod
760
+ def apply_value(self) -> None:
761
+ """Apply the current value to the relevant |Parameter| objects.
762
+
763
+ To be overridden by the concrete subclasses.
764
+ """
765
+
766
+ def _update_parameter(
767
+ self,
768
+ parameter: parametertools.Parameter,
769
+ value: float | VectorFloat | MatrixFloat,
770
+ ) -> None:
771
+ if self.keyword is None:
772
+ parameter(value)
773
+ else:
774
+ keywordarguments = parameter.keywordarguments
775
+ keywordarguments.valid = True
776
+ keywordarguments[self.keyword] = value
777
+ parameter(**dict(keywordarguments))
778
+
779
+ def reset_parameters(self) -> None:
780
+ """Reset all relevant parameter objects to their original states.
781
+
782
+ >>> from hydpy.core.testtools import prepare_full_example_2
783
+ >>> hp, pub, TestIO = prepare_full_example_2()
784
+ >>> from hydpy import Replace
785
+ >>> rule = Replace(name="fc",
786
+ ... parameter="fc",
787
+ ... value=100.0,
788
+ ... model="hland_96")
789
+ >>> fc = hp.elements.land_lahn_marb.model.parameters.control.fc
790
+ >>> fc
791
+ fc(206.0)
792
+ >>> fc(100.0)
793
+ >>> fc
794
+ fc(100.0)
795
+ >>> rule.reset_parameters()
796
+ >>> fc
797
+ fc(206.0)
798
+ """
799
+ with hydpy.pub.options.parameterstep(self.parameterstep):
800
+ for parameter, orig in zip(self, self._original_parameter_values):
801
+ self._update_parameter(parameter, orig)
802
+
803
+ def _get_parameterstep(self) -> timetools.Period | None:
804
+ """The parameter step size relevant to the related model parameter.
805
+
806
+ For non-time-dependent parameters, property |Rule.parameterstep| is (usually)
807
+ |None|.
808
+ """
809
+ return self._parameterstep
810
+
811
+ def _set_parameterstep(self, value: timetools.PeriodConstrArg | None) -> None:
812
+ if self.keyword is None:
813
+ time_ = self.parametertype.TIME
814
+ else:
815
+ keyword = self.parametertype.KEYWORDS.get(self.keyword, None)
816
+ time_ = self.parametertype.TIME if keyword is None else keyword.time
817
+ if time_ is None:
818
+ self._parameterstep = None
819
+ else:
820
+ if value is None:
821
+ value = hydpy.pub.options.parameterstep
822
+ try:
823
+ value.check()
824
+ except RuntimeError:
825
+ raise RuntimeError(
826
+ "Rules which handle time-dependent parameters require "
827
+ "information on the parameter timestep size. Either assign "
828
+ "it directly or define it via option `parameterstep`."
829
+ ) from None
830
+ self._parameterstep = timetools.Period(value)
831
+
832
+ parameterstep = propertytools.Property(
833
+ fget=_get_parameterstep, fset=_set_parameterstep
834
+ )
835
+
836
+ def assignrepr(self, prefix: str, indent: int = 0) -> str:
837
+ """Return a string representation of the actual |Rule| object prefixed with the
838
+ given string."""
839
+
840
+ def _none_or_string(obj: object) -> str:
841
+ return f'"{obj}"' if obj else str(obj)
842
+
843
+ blanks = (indent + 4) * " "
844
+ selprefix = f"{blanks}selections="
845
+ selline = objecttools.assignrepr_tuple(
846
+ values=tuple(f'"{sel}"' for sel in self.selections), prefix=selprefix
847
+ )
848
+ return (
849
+ f"{prefix}{type(self).__name__}(\n"
850
+ f'{blanks}name="{self}",\n'
851
+ f'{blanks}parameter="{self.parametername}",\n'
852
+ f"{blanks}value={objecttools.repr_(self.value)},\n"
853
+ f"{blanks}lower={objecttools.repr_(self.lower)},\n"
854
+ f"{blanks}upper={objecttools.repr_(self.upper)},\n"
855
+ f"{blanks}keyword={_none_or_string(self.keyword)},\n"
856
+ f"{blanks}parameterstep={_none_or_string(self.parameterstep)},\n"
857
+ f"{blanks}model={_none_or_string(self._model)},\n"
858
+ f"{selline},\n"
859
+ f"{indent*' '})"
860
+ )
861
+
862
+ def __repr__(self) -> str:
863
+ return self.assignrepr(prefix="")
864
+
865
+ def __str__(self) -> str:
866
+ return self.name
867
+
868
+ def __iter__(self) -> Iterator[TypeParameter]:
869
+ for parameters in self.element2parameters.values():
870
+ yield from parameters
871
+
872
+
873
+ class Replace(Rule[parametertools.Parameter]):
874
+ """|Rule| class, which simply replaces the current model parameter value(s) with
875
+ the current calibration parameter value.
876
+
877
+ See the documentation on class |Rule| for further information.
878
+ """
879
+
880
+ adaptor: Adaptor | None = None
881
+ """An optional function object for customising individual calibration strategies.
882
+
883
+ See the documentation on the classes |Rule|, |SumAdaptor|, and |FactorAdaptor| for
884
+ further information.
885
+ """
886
+
887
+ def apply_value(self) -> None:
888
+ """Apply the current value to the relevant |Parameter| objects.
889
+
890
+ See the documentation on class |Rule| for further information.
891
+ """
892
+ opt = hydpy.pub.options
893
+ with opt.parameterstep(self.parameterstep):
894
+ for parameter in self:
895
+ if self.adaptor:
896
+ self.adaptor(parameter)
897
+ else:
898
+ self._update_parameter(parameter, self.value)
899
+
900
+
901
+ class Add(Rule[parametertools.Parameter]):
902
+ """|Rule| class, which adds its calibration delta to the original model parameter
903
+ value(s).
904
+
905
+ Please read the examples of the documentation on class |Rule| first. Here, we
906
+ modify some of these examples to show the unique features of class |Add|.
907
+
908
+ The first example deals with the non-time-dependent parameter |hland_control.FC|.
909
+ The following |Add| object adds its current value to the parameter's original
910
+ values:
911
+
912
+ >>> from hydpy.core.testtools import prepare_full_example_2
913
+ >>> hp, pub, TestIO = prepare_full_example_2()
914
+ >>> from hydpy import Add
915
+ >>> rule = Add(name="fc",
916
+ ... parameter="fc",
917
+ ... value=100.0,
918
+ ... model="hland_96")
919
+ >>> fc = hp.elements.land_lahn_marb.model.parameters.control.fc
920
+ >>> fc
921
+ fc(206.0)
922
+ >>> rule.apply_value()
923
+ >>> fc
924
+ fc(306.0)
925
+
926
+ When specifying the keyword `field`, the |Add| rule modifies the field capacity of
927
+ zones of type |hland_constants.FIELD| only:
928
+
929
+ >>> fc(206.0)
930
+ >>> rule = Add(name="fc",
931
+ ... parameter="fc",
932
+ ... value=100.0,
933
+ ... keyword="field",
934
+ ... model="hland_96")
935
+ >>> rule.apply_value()
936
+ >>> fc
937
+ fc(field=306.0, forest=206.0)
938
+
939
+ The second example deals with the time-dependent parameter |hland_control.CFMax|
940
+ and shows that everything works even when the actual |Options.parameterstep|
941
+ (2 days) differs from the current |Options.simulationstep| (1 day):
942
+
943
+ >>> rule = Add(name="cfmax",
944
+ ... parameter="cfmax",
945
+ ... value=2.0,
946
+ ... model="hland_96",
947
+ ... parameterstep="2d")
948
+ >>> cfmax = hp.elements.land_lahn_marb.model.parameters.control.cfmax
949
+ >>> cfmax
950
+ cfmax(field=5.0, forest=3.0)
951
+ >>> rule.apply_value()
952
+ >>> cfmax
953
+ cfmax(field=6.0, forest=4.0)
954
+
955
+ This time, we modify the |hland_constants.FOREST| zones only:
956
+
957
+ >>> cfmax(field=5.0, forest=3.0)
958
+ >>> rule = Add(name="cfmax",
959
+ ... parameter="cfmax",
960
+ ... value=2.0,
961
+ ... keyword="forest",
962
+ ... model="hland_96",
963
+ ... parameterstep="2d")
964
+ >>> rule.apply_value()
965
+ >>> cfmax
966
+ cfmax(field=5.0, forest=4.0)
967
+
968
+ In the third example, we modify the scalar parameter |musk_control.NmbSegments| by
969
+ its optional keyword argument `lag`:
970
+
971
+ >>> rule = Add(name="lag",
972
+ ... parameter="nmbsegments",
973
+ ... value=1.0,
974
+ ... keyword="lag",
975
+ ... model="musk_classic",
976
+ ... parameterstep="2d")
977
+ >>> nmbsegments = \
978
+ hp.elements.stream_lahn_marb_lahn_leun.model.parameters.control.nmbsegments
979
+ >>> nmbsegments
980
+ nmbsegments(lag=0.583)
981
+ >>> rule.apply_value()
982
+ >>> nmbsegments
983
+ nmbsegments(lag=2.583)
984
+ """
985
+
986
+ def apply_value(self) -> None:
987
+ """Apply the current (adapted) value to the relevant |Parameter| objects."""
988
+ with hydpy.pub.options.parameterstep(self.parameterstep):
989
+ for parameter, orig in zip(self, self._original_parameter_values):
990
+ self._update_parameter(parameter, self.value + orig)
991
+
992
+
993
+ class Multiply(Rule[parametertools.Parameter]):
994
+ """|Rule| class for multiplying the original model parameter value(s) by its
995
+ calibration factor.
996
+
997
+ Please read the examples of the documentation on class |Rule| first. Here, we
998
+ modify some of these examples to show the unique features of class |Multiply|.
999
+
1000
+ The first example deals with the non-time-dependent parameter |hland_control.FC|.
1001
+ The following |Multiply| object multiplies the parameter's original values by its
1002
+ current calibration factor:
1003
+
1004
+ >>> from hydpy.core.testtools import prepare_full_example_2
1005
+ >>> hp, pub, TestIO = prepare_full_example_2()
1006
+ >>> from hydpy import Add
1007
+ >>> rule = Multiply(name="fc",
1008
+ ... parameter="fc",
1009
+ ... value=2.0,
1010
+ ... model="hland_96")
1011
+ >>> fc = hp.elements.land_lahn_marb.model.parameters.control.fc
1012
+ >>> fc
1013
+ fc(206.0)
1014
+ >>> rule.apply_value()
1015
+ >>> fc
1016
+ fc(412.0)
1017
+
1018
+ When specifying the keyword `field`, the |Multiply| rule modifies the field
1019
+ capacity of zones of type |hland_constants.FIELD| only:
1020
+
1021
+ >>> fc(206.0)
1022
+ >>> rule = Multiply(name="fc",
1023
+ ... parameter="fc",
1024
+ ... value=2.0,
1025
+ ... keyword="field",
1026
+ ... model="hland_96")
1027
+ >>> rule.apply_value()
1028
+ >>> fc
1029
+ fc(field=412.0, forest=206.0)
1030
+
1031
+ The second example deals with the time-dependent parameter |hland_control.CFMax|
1032
+ and shows that everything works even when the actual |Options.parameterstep|
1033
+ (2 days) differs from the current |Options.simulationstep| (1 day):
1034
+
1035
+ >>> rule = Multiply(name="cfmax",
1036
+ ... parameter="cfmax",
1037
+ ... value=2.0,
1038
+ ... model="hland_96",
1039
+ ... parameterstep="2d")
1040
+ >>> cfmax = hp.elements.land_lahn_marb.model.parameters.control.cfmax
1041
+ >>> cfmax
1042
+ cfmax(field=5.0, forest=3.0)
1043
+ >>> rule.apply_value()
1044
+ >>> cfmax
1045
+ cfmax(field=10.0, forest=6.0)
1046
+
1047
+ This time, we modify the |hland_constants.FOREST| zones only:
1048
+
1049
+ >>> cfmax(field=5.0, forest=3.0)
1050
+ >>> rule = Multiply(name="cfmax",
1051
+ ... parameter="cfmax",
1052
+ ... value=2.0,
1053
+ ... keyword="forest",
1054
+ ... model="hland_96",
1055
+ ... parameterstep="2d")
1056
+ >>> cfmax
1057
+ cfmax(field=5.0, forest=3.0)
1058
+ >>> rule.apply_value()
1059
+ >>> cfmax
1060
+ cfmax(field=5.0, forest=6.0)
1061
+
1062
+ In the third example, we modify the scalar parameter |musk_control.NmbSegments| by
1063
+ its optional keyword argument `lag`:
1064
+
1065
+ >>> rule = Multiply(name="lag",
1066
+ ... parameter="nmbsegments",
1067
+ ... value=2.0,
1068
+ ... keyword="lag",
1069
+ ... model="musk_classic",
1070
+ ... parameterstep="2d")
1071
+ >>> nmbsegments = \
1072
+ hp.elements.stream_lahn_marb_lahn_leun.model.parameters.control.nmbsegments
1073
+ >>> nmbsegments
1074
+ nmbsegments(lag=0.583)
1075
+ >>> rule.apply_value()
1076
+ >>> nmbsegments
1077
+ nmbsegments(lag=1.166)
1078
+ """
1079
+
1080
+ def apply_value(self) -> None:
1081
+ """Apply the current (adapted) value to the relevant |Parameter| objects."""
1082
+ with hydpy.pub.options.parameterstep(self.parameterstep):
1083
+ for parameter, orig in zip(self, self._original_parameter_values):
1084
+ self._update_parameter(parameter, self.value * orig)
1085
+
1086
+
1087
+ class CalibrationInterface(Generic[TypeRule1]):
1088
+ """Interface for the coupling of *HydPy* to optimisation libraries like `NLopt`_.
1089
+
1090
+ Essentially, class |CalibrationInterface| is supposed for the structured handling
1091
+ of multiple objects of the different |Rule| subclasses. Hence, please read the
1092
+ documentation on class |Rule| before continuing, on which we base the following
1093
+ explanations.
1094
+
1095
+ We work with the `Lahn` example project again:
1096
+
1097
+ >>> from hydpy.core.testtools import prepare_full_example_2
1098
+ >>> hp, pub, TestIO = prepare_full_example_2()
1099
+
1100
+ First, we create a |CalibrationInterface| object. Initially, it needs to know the
1101
+ relevant |HydPy| object and the target or objective function (here, we define the
1102
+ target function sloppily via the `lambda` statement; see the documentation on the
1103
+ protocol class |TargetFunction| for a more formal definition and further
1104
+ explanations):
1105
+
1106
+ >>> from hydpy import CalibrationInterface, nse
1107
+ >>> ci = CalibrationInterface(
1108
+ ... hp=hp,
1109
+ ... targetfunction=lambda: sum(nse(node=node) for node in hp.nodes))
1110
+
1111
+ Next, we use function |make_rules|, which creates one |Replace| rule related to
1112
+ parameter |hland_control.FC| and another one related to parameter
1113
+ |hland_control.PercMax| in one step, and add them via method
1114
+ |CalibrationInterface.add_rules|:
1115
+
1116
+ >>> from hydpy import Replace
1117
+ >>> from hydpy.auxs.calibtools import make_rules
1118
+ >>> ci.add_rules(*make_rules(rule=Replace,
1119
+ ... names=["fc", "percmax"],
1120
+ ... parameters=["fc", "percmax"],
1121
+ ... values=[100.0, 5.0],
1122
+ ... keywords=[None, None],
1123
+ ... lowers=[50.0, 1.0],
1124
+ ... uppers=[200.0, 10.0],
1125
+ ... parametersteps="1d",
1126
+ ... model="hland_96"))
1127
+
1128
+ >>> print(ci)
1129
+ CalibrationInterface
1130
+ >>> ci
1131
+ Replace(
1132
+ name="fc",
1133
+ parameter="fc",
1134
+ value=100.0,
1135
+ lower=50.0,
1136
+ upper=200.0,
1137
+ keyword=None,
1138
+ parameterstep=None,
1139
+ model="hland_96",
1140
+ selections=("complete",),
1141
+ )
1142
+ Replace(
1143
+ name="percmax",
1144
+ parameter="percmax",
1145
+ value=5.0,
1146
+ lower=1.0,
1147
+ upper=10.0,
1148
+ keyword=None,
1149
+ parameterstep="1d",
1150
+ model="hland_96",
1151
+ selections=("complete",),
1152
+ )
1153
+
1154
+ Adding rules later does not remove already available ones. For demonstration, we
1155
+ add one for calibrating parameter |musk_control.Coefficients| of application model
1156
+ |musk_classic| via its keyword `damp`:
1157
+
1158
+ >>> len(ci)
1159
+ 2
1160
+ >>> ci.add_rules(Replace(name="damp",
1161
+ ... parameter="coefficients",
1162
+ ... value=0.2,
1163
+ ... lower=0.0,
1164
+ ... upper=0.5,
1165
+ ... keyword="damp",
1166
+ ... selections=["complete"],
1167
+ ... model="musk_classic"))
1168
+ >>> len(ci)
1169
+ 3
1170
+
1171
+ All rules are available via attribute and keyword access:
1172
+
1173
+ >>> ci.fc
1174
+ Replace(
1175
+ name="fc",
1176
+ parameter="fc",
1177
+ value=100.0,
1178
+ lower=50.0,
1179
+ upper=200.0,
1180
+ keyword=None,
1181
+ parameterstep=None,
1182
+ model="hland_96",
1183
+ selections=("complete",),
1184
+ )
1185
+
1186
+ >>> ci.FC # doctest: +ELLIPSIS
1187
+ Traceback (most recent call last):
1188
+ ...
1189
+ AttributeError: The actual calibration interface does neither handle a normal \
1190
+ attribute nor a rule object named `FC`...
1191
+
1192
+ >>> ci["damp"]
1193
+ Replace(
1194
+ name="damp",
1195
+ parameter="coefficients",
1196
+ value=0.2,
1197
+ lower=0.0,
1198
+ upper=0.5,
1199
+ keyword="damp",
1200
+ parameterstep=None,
1201
+ model="musk_classic",
1202
+ selections=("complete",),
1203
+ )
1204
+
1205
+ >>> ci["Damp"]
1206
+ Traceback (most recent call last):
1207
+ ...
1208
+ KeyError: 'The actual calibration interface does not handle a rule object named \
1209
+ `Damp`.'
1210
+
1211
+ The following properties return consistently sorted information on the handles
1212
+ |Rule| objects:
1213
+
1214
+ >>> ci.names
1215
+ ('fc', 'percmax', 'damp')
1216
+ >>> ci.keywords
1217
+ (None, None, 'damp')
1218
+ >>> ci.values
1219
+ (100.0, 5.0, 0.2)
1220
+ >>> ci.lowers
1221
+ (50.0, 1.0, 0.0)
1222
+ >>> ci.uppers
1223
+ (200.0, 10.0, 0.5)
1224
+
1225
+ All tuples reflect the current state of all rules:
1226
+
1227
+ >>> ci.damp.value = 0.3
1228
+ >>> ci.values
1229
+ (100.0, 5.0, 0.3)
1230
+
1231
+ For the following examples, we perform a simulation run and assign the values of
1232
+ the simulated time series to the observed series:
1233
+
1234
+ >>> conditions = hp.conditions
1235
+ >>> hp.simulate()
1236
+ >>> for node in hp.nodes:
1237
+ ... node.sequences.obs.series = node.sequences.sim.series
1238
+ >>> hp.conditions = conditions
1239
+
1240
+ As the agreement between the simulated and the "observed" time series is perfect
1241
+ for all four gauges, method |CalibrationInterface.calculate_likelihood| returns the
1242
+ highest possible sum of four |nse| values and also stores it under the attribute
1243
+ `result`:
1244
+
1245
+ >>> from hydpy import round_
1246
+ >>> round_(ci.calculate_likelihood())
1247
+ 4.0
1248
+ >>> round_(ci.result)
1249
+ 4.0
1250
+
1251
+ When performing a manual calibration, it might be convenient to use method
1252
+ |CalibrationInterface.apply_values|. To explain how it works, we first show the
1253
+ values of the relevant parameters of some randomly selected model instances:
1254
+
1255
+ >>> stream = hp.elements.stream_lahn_marb_lahn_leun.model
1256
+ >>> stream.parameters.control
1257
+ nmbsegments(lag=0.583)
1258
+ coefficients(damp=0.0)
1259
+ >>> land = hp.elements.land_lahn_marb.model
1260
+ >>> land.parameters.control.fc
1261
+ fc(206.0)
1262
+ >>> land.parameters.control.percmax
1263
+ percmax(1.02978)
1264
+
1265
+ Method |CalibrationInterface.apply_values| of class |CalibrationInterface| calls
1266
+ method |Rule.apply_value| of all handled |Rule| objects, performs some preparations
1267
+ (for example, it derives the values of the secondary parameters), executes a
1268
+ simulation run, calls method |CalibrationInterface.calculate_likelihood|, and
1269
+ returns the result:
1270
+
1271
+ >>> result = ci.apply_values()
1272
+ >>> stream.parameters.control
1273
+ nmbsegments(lag=0.583)
1274
+ coefficients(damp=0.3)
1275
+ >>> land.parameters.control.fc
1276
+ fc(100.0)
1277
+ >>> land.parameters.control.percmax
1278
+ percmax(5.0)
1279
+
1280
+ Due to the changes in our parameter values, our simulation is not "perfect"
1281
+ anymore:
1282
+
1283
+ >>> round_(ci.result)
1284
+ 1.638413
1285
+
1286
+ Use method |CalibrationInterface.reset_parameters| to restore the initial states of
1287
+ all affected parameters:
1288
+
1289
+ >>> ci.reset_parameters()
1290
+ >>> stream.parameters.control
1291
+ nmbsegments(lag=0.583)
1292
+ coefficients(damp=0.0)
1293
+ >>> land = hp.elements.land_lahn_marb.model
1294
+ >>> land.parameters.control.fc
1295
+ fc(206.0)
1296
+ >>> land.parameters.control.percmax
1297
+ percmax(1.02978)
1298
+
1299
+ Now we get the same "perfect" efficiency again:
1300
+
1301
+ >>> hp.simulate()
1302
+ >>> round_(ci.calculate_likelihood())
1303
+ 4.0
1304
+ >>> hp.conditions = conditions
1305
+
1306
+ Note the `perform_simulation` argument of method
1307
+ |CalibrationInterface.apply_values|, which allows changing the model parameter
1308
+ values and updating the |HydPy| object only without triggering a simulation run
1309
+ (and to calculate and return a new likelihood value):
1310
+
1311
+ >>> ci.apply_values(perform_simulation=False)
1312
+ >>> stream.parameters.control
1313
+ nmbsegments(lag=0.583)
1314
+ coefficients(damp=0.3)
1315
+ >>> land.parameters.control.fc
1316
+ fc(100.0)
1317
+ >>> land.parameters.control.percmax
1318
+ percmax(5.0)
1319
+
1320
+ Optimisers, like those implemented in `NLopt`_, often provide their new parameter
1321
+ estimates via vectors. Method |CalibrationInterface.perform_calibrationstep|
1322
+ accepts such vectors and updates the handled |Rule| objects accordingly. After
1323
+ that, it performs the same steps as described for method
1324
+ |CalibrationInterface.apply_values|:
1325
+
1326
+ >>> round_(ci.perform_calibrationstep([100.0, 5.0, 0.3]))
1327
+ 1.638413
1328
+
1329
+ >>> stream.parameters.control
1330
+ nmbsegments(lag=0.583)
1331
+ coefficients(damp=0.3)
1332
+
1333
+ >>> land.parameters.control.fc
1334
+ fc(100.0)
1335
+ >>> land.parameters.control.percmax
1336
+ percmax(5.0)
1337
+
1338
+ Method |CalibrationInterface.perform_calibrationstep| writes intermediate results
1339
+ into a log file, if available. Prepare it beforehand via method
1340
+ |CalibrationInterface.prepare_logfile|:
1341
+
1342
+ >>> with TestIO():
1343
+ ... ci.prepare_logfile(logfilepath="example_calibration.log",
1344
+ ... objectivefunction="NSE",
1345
+ ... documentation="Just a doctest example.")
1346
+
1347
+ To continue "manually", we now can call method
1348
+ |CalibrationInterface.update_logfile| to write the lastly calculated efficiency and
1349
+ the corresponding calibration parameter values to the log file:
1350
+
1351
+ >>> with TestIO(): # doctest: +NORMALIZE_WHITESPACE
1352
+ ... ci.update_logfile()
1353
+ ... with open("example_calibration.log") as file_:
1354
+ ... print(file_.read())
1355
+ # Just a doctest example.
1356
+ <BLANKLINE>
1357
+ NSE fc percmax damp
1358
+ parameterstep None 1d None
1359
+ 1.638413 100.0 5.0 0.3
1360
+ <BLANKLINE>
1361
+
1362
+ To prevent (automatic) calibration runs from crashing due to IO problems, method
1363
+ |CalibrationInterface.update_logfile| raises warnings instead of errors in such
1364
+ cases and logs the inwritten data internally:
1365
+
1366
+ >>> import os
1367
+ >>> from hydpy.core.testtools import warn_later
1368
+ >>> with TestIO(), warn_later():
1369
+ ... ci._logfilepath = "dirname1/filename.log"
1370
+ ... ci.update_logfile()
1371
+ UserWarning: While trying to update the logfile `dirname1/filename.log`, the \
1372
+ following problem occured: [Errno 2] No such file or directory: 'dirname1/filename.log'.
1373
+
1374
+ On subsequent calls, it tries to write both the previously logged and the new data:
1375
+
1376
+ >>> with TestIO(): # doctest: +NORMALIZE_WHITESPACE
1377
+ ... os.makedirs("dirname1", exist_ok=True)
1378
+ ... ci.update_logfile()
1379
+ ... with open("dirname1/filename.log") as file_:
1380
+ ... print(file_.read())
1381
+ 1.638413 100.0 5.0 0.3
1382
+ 1.638413 100.0 5.0 0.3
1383
+ <BLANKLINE>
1384
+
1385
+ Call method |CalibrationInterface.finalise_logfile| to ensure the
1386
+ |CalibrationInterface| object does not withhold data after the end of a calibration
1387
+ run. If you do so, it sleeps until it gets the chance to write the logged data and
1388
+ warns you about this problem from time to time (we demonstrate this by mocking the
1389
+ |warnings.warn| function and, to keep our test example awake, the |time.sleep|
1390
+ function):
1391
+
1392
+ >>> with TestIO():
1393
+ ... ci._logfilepath = "dirname2/filename.log"
1394
+ ... ci.update_logfile()
1395
+ Traceback (most recent call last):
1396
+ ...
1397
+ UserWarning: While trying to update the logfile `dirname2/filename.log`, the \
1398
+ following problem occured: [Errno 2] No such file or directory: 'dirname2/filename.log'.
1399
+ >>> from unittest import mock
1400
+ >>> with TestIO():
1401
+ ... with mock.patch("time.sleep") as mocked:
1402
+ ... mocked.side_effect = Exception("time.sleep actually called")
1403
+ ... ci.finalise_logfile()
1404
+ Traceback (most recent call last):
1405
+ ...
1406
+ UserWarning: Trying to finalise logfile `dirname2/filename.log` failed 1 times.
1407
+ >>> with TestIO():
1408
+ ... with mock.patch("warnings.warn"), mock.patch("time.sleep") as mocked:
1409
+ ... mocked.side_effect = Exception("time.sleep actually called")
1410
+ ... ci.finalise_logfile()
1411
+ Traceback (most recent call last):
1412
+ ...
1413
+ Exception: time.sleep actually called
1414
+ >>> with TestIO(): # doctest: +NORMALIZE_WHITESPACE
1415
+ ... os.makedirs("dirname2", exist_ok=True)
1416
+ ... ci.finalise_logfile()
1417
+ ... with open("dirname2/filename.log") as file_:
1418
+ ... print(file_.read())
1419
+ 1.638413 100.0 5.0 0.3
1420
+ <BLANKLINE>
1421
+
1422
+ >>> ci._logfilepath = "example_calibration.log"
1423
+
1424
+ For automatic calibration, one needs a calibration algorithm like the following,
1425
+ which checks the lower and upper boundaries and the initial values of all |Rule|
1426
+ objects:
1427
+
1428
+ >>> def find_max(function, lowers, uppers, inits):
1429
+ ... best_result = -999.0
1430
+ ... best_parameters = None
1431
+ ... for values in (lowers, uppers, inits):
1432
+ ... result = function(values)
1433
+ ... if result > best_result:
1434
+ ... best_result = result
1435
+ ... best_parameters = values
1436
+ ... return best_parameters
1437
+
1438
+ Now we can assign method |CalibrationInterface.perform_calibrationstep| to this
1439
+ oversimplified optimiser, which then returns the best examined calibration
1440
+ parameter values:
1441
+
1442
+ >>> with TestIO():
1443
+ ... find_max(function=ci.perform_calibrationstep,
1444
+ ... lowers=ci.lowers,
1445
+ ... uppers=ci.uppers,
1446
+ ... inits=ci.values)
1447
+ (200.0, 10.0, 0.5)
1448
+
1449
+ The log file now contains one line for our old result and three lines for the
1450
+ results of our optimiser:
1451
+
1452
+ >>> with TestIO(): # doctest: +NORMALIZE_WHITESPACE
1453
+ ... with open("example_calibration.log") as file_:
1454
+ ... print(file_.read())
1455
+ # Just a doctest example.
1456
+ <BLANKLINE>
1457
+ NSE fc percmax damp
1458
+ parameterstep None 1d None
1459
+ 1.638413 100.0 5.0 0.3
1460
+ -0.722221 50.0 1.0 0.0
1461
+ 2.347116 200.0 10.0 0.5
1462
+ 1.638413 100.0 5.0 0.3
1463
+ <BLANKLINE>
1464
+
1465
+ Class |CalibrationInterface| also provides method
1466
+ |CalibrationInterface.read_logfile|, which automatically selects the best
1467
+ calibration result. Therefore, it needs to know that the highest result is the
1468
+ best, which we indicate by setting argument `maximisation` to |True|:
1469
+
1470
+ >>> with TestIO():
1471
+ ... ci.read_logfile(logfilepath="example_calibration.log", maximisation=True)
1472
+ >>> ci.fc.value
1473
+ 200.0
1474
+ >>> ci.percmax.value
1475
+ 10.0
1476
+ >>> ci.damp.value
1477
+ 0.5
1478
+ >>> round_(ci.result)
1479
+ 2.347116
1480
+ >>> round_(ci.apply_values())
1481
+ 2.347116
1482
+
1483
+ On the contrary, if we set argument `maximisation` to |False|, method
1484
+ |CalibrationInterface.read_logfile| returns the worst result in our example:
1485
+
1486
+ >>> with TestIO():
1487
+ ... ci.read_logfile(logfilepath="example_calibration.log", maximisation=False)
1488
+ >>> ci.fc.value
1489
+ 50.0
1490
+ >>> ci.percmax.value
1491
+ 1.0
1492
+ >>> ci.damp.value
1493
+ 0.0
1494
+ >>> round_(ci.result)
1495
+ -0.722221
1496
+ >>> round_(ci.apply_values())
1497
+ -0.722221
1498
+
1499
+ To prevent errors due to different parameter step-sizes, method
1500
+ |CalibrationInterface.read_logfile| raises the following error whenever it detects
1501
+ inconsistencies:
1502
+
1503
+ >>> ci.percmax.parameterstep = "2d"
1504
+ >>> with TestIO():
1505
+ ... ci.read_logfile(logfilepath="example_calibration.log",maximisation=True)
1506
+ Traceback (most recent call last):
1507
+ ...
1508
+ RuntimeError: The current parameterstep of the `Replace` rule `percmax` (`2d`) \
1509
+ does not agree with the one documentated in log file `example_calibration.log` (`1d`).
1510
+
1511
+ Method |CalibrationInterface.read_logfile| reports inconsistent rule names as
1512
+ follows:
1513
+
1514
+ >>> ci.remove_rules(ci.percmax)
1515
+ >>> with TestIO():
1516
+ ... ci.read_logfile(logfilepath="example_calibration.log",maximisation=True)
1517
+ Traceback (most recent call last):
1518
+ ...
1519
+ RuntimeError: The names of the rules handled by the actual calibration interface \
1520
+ (damp and fc) do not agree with the names in the header of logfile \
1521
+ `example_calibration.log` (damp, fc, and percmax).
1522
+
1523
+ The last consistency check is optional. Set argument `check` to |False| to force
1524
+ method |CalibrationInterface.read_logfile| to query all available data instead of
1525
+ raising an error:
1526
+
1527
+ >>> ci.add_rules(Replace(name="beta",
1528
+ ... parameter="beta",
1529
+ ... value=2.0,
1530
+ ... lower=1.0,
1531
+ ... upper=4.0,
1532
+ ... selections=["complete"],
1533
+ ... model="hland_96"))
1534
+ >>> ci.fc.value = 0.0
1535
+ >>> ci.damp.value = 0.0
1536
+ >>> with TestIO():
1537
+ ... ci.read_logfile(
1538
+ ... logfilepath="example_calibration.log",
1539
+ ... maximisation=True,
1540
+ ... check=False,
1541
+ ... )
1542
+ >>> ci.beta.value
1543
+ 2.0
1544
+ >>> ci.fc.value
1545
+ 200.0
1546
+ >>> ci.damp.value
1547
+ 0.5
1548
+ """
1549
+
1550
+ result: float | None
1551
+ """The last result, as calculated by the target function."""
1552
+ conditions: Conditions
1553
+ """The |HydPy.conditions| of the given |HydPy| object.
1554
+
1555
+ |CalibrationInterface| queries the conditions during its initialisation and uses
1556
+ them later to reset all relevant conditions before each new simulation run.
1557
+ """
1558
+ _logfilepath: str | None
1559
+ _logfilelines: collections.deque[str]
1560
+ _hp: hydpytools.HydPy
1561
+ _targetfunction: TargetFunction
1562
+ _rules: dict[str, TypeRule1]
1563
+ _elements: devicetools.Elements
1564
+
1565
+ def __init__(self, hp: hydpytools.HydPy, targetfunction: TargetFunction) -> None:
1566
+ self._hp = hp
1567
+ self._targetfunction = targetfunction
1568
+ self.conditions = hp.conditions
1569
+ self._rules = {}
1570
+ self._elements = devicetools.Elements()
1571
+ self._logfilepath = None
1572
+ self._logfilelines = collections.deque()
1573
+ self.result = None
1574
+
1575
+ def add_rules(self, *rules: TypeRule1) -> None:
1576
+ """Add some |Rule| objects to the actual |CalibrationInterface| object.
1577
+
1578
+ >>> from hydpy.core.testtools import prepare_full_example_2
1579
+ >>> hp, pub, TestIO = prepare_full_example_2()
1580
+ >>> from hydpy import CalibrationInterface
1581
+ >>> ci = CalibrationInterface(hp=hp, targetfunction=lambda: None)
1582
+ >>> from hydpy import Replace
1583
+ >>> ci.add_rules(Replace(name="fc",
1584
+ ... parameter="fc",
1585
+ ... value=100.0,
1586
+ ... model="hland_96"),
1587
+ ... Replace(name="percmax",
1588
+ ... parameter="percmax",
1589
+ ... value=5.0,
1590
+ ... model="hland_96"))
1591
+
1592
+ Note that method |CalibrationInterface.add_rules| might change the number of
1593
+ |Element| objects relevant for the |CalibrationInterface| object:
1594
+
1595
+ >>> damp = Replace(name="damp",
1596
+ ... parameter="coefficients",
1597
+ ... value=0.2,
1598
+ ... keyword="damp",
1599
+ ... model="musk_classic")
1600
+ >>> len(ci._elements)
1601
+ 4
1602
+ >>> ci.add_rules(damp)
1603
+ >>> len(ci._elements)
1604
+ 7
1605
+ """
1606
+ for rule in rules:
1607
+ self._rules[rule.name] = rule
1608
+ self._update_elements_when_adding_a_rule(rule)
1609
+
1610
+ @overload
1611
+ def get_rule(self, name: str) -> TypeRule1: ...
1612
+
1613
+ @overload
1614
+ def get_rule(self, name: str, type_: type[TypeRule2]) -> TypeRule2: ...
1615
+
1616
+ def get_rule(
1617
+ self, name: str, type_: type[TypeRule2] | None = None
1618
+ ) -> TypeRule1 | TypeRule2:
1619
+ """Return a |Rule| object (of a specific type).
1620
+
1621
+ Method |CalibrationInterface.get_rule| is a more typesafe alternative to simple
1622
+ keyword access. Besides the name of the required |Rule| object, pass its
1623
+ subclass to convince your IDE (and yourself) that the returned rule follows
1624
+ this more specific type:
1625
+
1626
+ >>> from hydpy.core.testtools import prepare_full_example_2
1627
+ >>> hp, pub, TestIO = prepare_full_example_2()
1628
+ >>> from hydpy import Add, CalibrationInterface, make_rules, nse, Replace
1629
+ >>> ci = CalibrationInterface(
1630
+ ... hp=hp,
1631
+ ... targetfunction=lambda: sum(nse(node=node) for node in hp.nodes))
1632
+ >>> ci.add_rules(*make_rules(rule=Replace,
1633
+ ... names=["fc", "percmax"],
1634
+ ... parameters=["fc", "percmax"],
1635
+ ... values=[100.0, 5.0],
1636
+ ... keywords=["forest", None],
1637
+ ... lowers=[50.0, 1.0],
1638
+ ... uppers=[200.0, 10.0],
1639
+ ... parametersteps="1d",
1640
+ ... model="hland_96"))
1641
+
1642
+ >>> ci.get_rule("fc", Replace).name
1643
+ 'fc'
1644
+
1645
+ >>> ci.get_rule("Fc", Replace).name
1646
+ Traceback (most recent call last):
1647
+ ...
1648
+ RuntimeError: The actual calibration interface does not handle a rule object \
1649
+ named `Fc`.
1650
+
1651
+ >>> ci.get_rule("fc", Replace).name
1652
+ 'fc'
1653
+
1654
+ >>> ci.get_rule("fc", Add).name
1655
+ Traceback (most recent call last):
1656
+ ...
1657
+ RuntimeError: The actual calibration interface does not handle a rule object \
1658
+ named `fc` of type `Add`.
1659
+ """
1660
+ try:
1661
+ rule = self._rules[name]
1662
+ except KeyError:
1663
+ raise RuntimeError(
1664
+ f"The actual calibration interface does not handle a rule object "
1665
+ f"named `{name}`."
1666
+ ) from None
1667
+ if (type_ is None) or isinstance(rule, type_):
1668
+ return rule
1669
+ raise RuntimeError(
1670
+ f"The actual calibration interface does not handle a rule object named "
1671
+ f"`{name}` of type `{type_.__name__}`."
1672
+ )
1673
+
1674
+ def remove_rules(self, *rules: str | TypeRule1) -> None:
1675
+ """Remove some |Rule| objects from the actual |CalibrationInterface| object.
1676
+
1677
+ >>> from hydpy.core.testtools import prepare_full_example_2
1678
+ >>> hp, pub, TestIO = prepare_full_example_2()
1679
+ >>> from hydpy import CalibrationInterface
1680
+ >>> ci = CalibrationInterface(hp=hp, targetfunction=lambda: None)
1681
+ >>> from hydpy import Replace
1682
+ >>> ci.add_rules(Replace(name="fc",
1683
+ ... parameter="fc",
1684
+ ... value=100.0,
1685
+ ... model="hland_96"),
1686
+ ... Replace(name="percmax",
1687
+ ... parameter="percmax",
1688
+ ... value=5.0,
1689
+ ... model="hland_96"),
1690
+ ... Replace(name="damp",
1691
+ ... parameter="coefficients",
1692
+ ... value=0.2,
1693
+ ... keyword="damp",
1694
+ ... model="musk_classic"))
1695
+
1696
+ You can remove each rule either by passing itself or its name (note that method
1697
+ |CalibrationInterface.remove_rules| might change the number of |Element|
1698
+ objects relevant for the |CalibrationInterface| object):
1699
+
1700
+ >>> len(ci._elements)
1701
+ 7
1702
+ >>> fc = ci.fc
1703
+ >>> fc in ci
1704
+ True
1705
+ >>> "damp" in ci
1706
+ True
1707
+ >>> ci.remove_rules(fc, "damp")
1708
+ >>> fc in ci
1709
+ False
1710
+ >>> "damp" in ci
1711
+ False
1712
+ >>> len(ci._elements)
1713
+ 4
1714
+
1715
+ Trying to remove a non-existing rule results in the following error:
1716
+
1717
+ >>> ci.remove_rules("fc")
1718
+ Traceback (most recent call last):
1719
+ ...
1720
+ RuntimeError: The actual calibration interface object does not handle a rule \
1721
+ object named `fc`.
1722
+ """
1723
+ for rule in rules:
1724
+ if not isinstance(rule, str):
1725
+ rule = rule.name
1726
+ try:
1727
+ del self._rules[rule]
1728
+ except KeyError:
1729
+ raise RuntimeError(
1730
+ f"The actual calibration interface object does not handle a rule "
1731
+ f"object named `{rule}`."
1732
+ ) from None
1733
+ self._update_elements_when_deleting_a_rule()
1734
+
1735
+ def prepare_logfile(
1736
+ self,
1737
+ logfilepath: str,
1738
+ objectivefunction: str = "result",
1739
+ documentation: str | None = None,
1740
+ ) -> None:
1741
+ """Prepare a log file.
1742
+
1743
+ Use argument `objectivefunction` to describe the |TargetFunction| used for
1744
+ calculating the efficiency and argument `documentation` to add some information
1745
+ to the header of the logfile.
1746
+
1747
+ See the main documentation on class |CalibrationInterface| for further
1748
+ information.
1749
+ """
1750
+ self._logfilepath = logfilepath
1751
+ self._logfilelines = collections.deque()
1752
+ with open(logfilepath, "w", encoding=config.ENCODING) as logfile:
1753
+ if documentation:
1754
+ lines = (f"# {line}" for line in documentation.split("\n"))
1755
+ logfile.write("\n".join(lines))
1756
+ logfile.write("\n\n")
1757
+ logfile.write(f"{objectivefunction}\t")
1758
+ names = (rule.name for rule in self)
1759
+ logfile.write("\t".join(names))
1760
+ logfile.write("\n")
1761
+ steps = [str(rule.parameterstep) for rule in self]
1762
+ logfile.write("\t".join(["parameterstep"] + steps))
1763
+ logfile.write("\n")
1764
+
1765
+ def update_logfile(self) -> None:
1766
+ """Update the current log file, if available.
1767
+
1768
+ See the main documentation on class |CalibrationInterface| for further
1769
+ information.
1770
+ """
1771
+ if self._logfilepath:
1772
+ result = objecttools.repr_(self.result)
1773
+ values = "\t".join(objecttools.repr_(value) for value in self.values)
1774
+ self._logfilelines.append(f"{result}\t{values}\n")
1775
+ try:
1776
+ self._write_data_into_logfile()
1777
+ except BaseException as exc:
1778
+ warnings.warn(
1779
+ f"While trying to update the logfile `{self._logfilepath}`, the "
1780
+ f"following problem occured: {exc}."
1781
+ )
1782
+
1783
+ def finalise_logfile(self) -> None:
1784
+ """Update the current log file if method |CalibrationInterface.update_logfile|
1785
+ was not entirely successful in doing so.
1786
+
1787
+ See the main documentation on class |CalibrationInterface| for further
1788
+ information.
1789
+ """
1790
+ if self._logfilepath:
1791
+ counter = 0
1792
+ while self._logfilelines:
1793
+ try:
1794
+ self._write_data_into_logfile()
1795
+ except BaseException:
1796
+ counter += 1
1797
+ warnings.warn(
1798
+ f"Trying to finalise logfile `{self._logfilepath}` failed "
1799
+ f"{counter} times."
1800
+ )
1801
+ time.sleep(10.0)
1802
+
1803
+ def _write_data_into_logfile(self) -> None:
1804
+ assert self._logfilepath
1805
+ with open(self._logfilepath, "a", encoding=config.ENCODING) as logfile:
1806
+ while self._logfilelines:
1807
+ logfile.write(self._logfilelines.popleft())
1808
+
1809
+ def read_logfile(
1810
+ self, logfilepath: str, maximisation: bool, check: bool = True
1811
+ ) -> None:
1812
+ """Read the log file with the given file path.
1813
+
1814
+ See the main documentation on class |CalibrationInterface| for further
1815
+ information.
1816
+ """
1817
+ with open(logfilepath, encoding=config.ENCODING) as logfile:
1818
+ lines = tuple(
1819
+ line
1820
+ for line in logfile # pylint: disable=not-an-iterable
1821
+ if (line.strip() and (not line.startswith("#")))
1822
+ )
1823
+ idx2name, idx2rule = {}, {}
1824
+ parameterstep: str | timetools.Period | None
1825
+ for idx, (name, parameterstep) in enumerate(
1826
+ zip(lines[0].split()[1:], lines[1].split()[1:])
1827
+ ):
1828
+ if name in self._rules:
1829
+ rule = self._rules[name]
1830
+ if parameterstep == "None":
1831
+ parameterstep = None
1832
+ else:
1833
+ parameterstep = timetools.Period(parameterstep)
1834
+ if parameterstep != rule.parameterstep:
1835
+ raise RuntimeError(
1836
+ f"The current parameterstep of the `{type(rule).__name__}` "
1837
+ f"rule `{rule.name}` (`{rule.parameterstep}`) does not agree "
1838
+ f"with the one documentated in log file `{self._logfilepath}` "
1839
+ f"(`{parameterstep}`)."
1840
+ )
1841
+ idx2rule[idx] = rule
1842
+ idx2name[idx] = name
1843
+ if check:
1844
+ names_int = set(self.names)
1845
+ names_ext = set(idx2name.values())
1846
+ if names_int != names_ext:
1847
+ enumeration = objecttools.enumeration
1848
+ raise RuntimeError(
1849
+ f"The names of the rules handled by the actual calibration "
1850
+ f"interface ({enumeration(sorted(names_int))}) do not agree with "
1851
+ f"the names in the header of logfile `{self._logfilepath}` "
1852
+ f"({enumeration(sorted(names_ext))})."
1853
+ )
1854
+ jdx_best = 0
1855
+ result_best = -numpy.inf if maximisation else numpy.inf
1856
+ for jdx, line in enumerate(lines[2:]):
1857
+ result = float(line.split()[0])
1858
+ if (maximisation and (result > result_best)) or (
1859
+ (not maximisation) and (result < result_best)
1860
+ ):
1861
+ jdx_best = jdx
1862
+ result_best = result
1863
+
1864
+ for idx, value in enumerate(lines[jdx_best + 2].split()[1:]):
1865
+ if idx in idx2rule:
1866
+ idx2rule[idx].value = float(value)
1867
+ self.result = result_best
1868
+
1869
+ def _update_elements_when_adding_a_rule(self, rule: TypeRule1) -> None:
1870
+ self._elements += rule.elements
1871
+
1872
+ def _update_elements_when_deleting_a_rule(self) -> None:
1873
+ self._elements = devicetools.Elements()
1874
+ for rule in self:
1875
+ self._elements += rule.elements
1876
+
1877
+ @property
1878
+ def names(self) -> tuple[str, ...]:
1879
+ """The names of all handled |Rule| objects.
1880
+
1881
+ See the main documentation on class |CalibrationInterface| for further
1882
+ information.
1883
+ """
1884
+ return tuple(rule.name for rule in self)
1885
+
1886
+ @property
1887
+ def values(self) -> tuple[float, ...]:
1888
+ """The values of all handled |Rule| objects.
1889
+
1890
+ See the main documentation on class |CalibrationInterface| for further
1891
+ information.
1892
+ """
1893
+ return tuple(rule.value for rule in self)
1894
+
1895
+ @property
1896
+ def keywords(self) -> tuple[str | None, ...]:
1897
+ """The (optional) target keywords of all handled |Rule| objects.
1898
+
1899
+ See the main documentation on class |CalibrationInterface| for further
1900
+ information.
1901
+ """
1902
+ return tuple(rule.keyword for rule in self)
1903
+
1904
+ @property
1905
+ def lowers(self) -> tuple[float, ...]:
1906
+ """The lower boundaries of all handled |Rule| objects.
1907
+
1908
+ See the main documentation on class |CalibrationInterface| for further
1909
+ information.
1910
+ """
1911
+ return tuple(rule.lower for rule in self)
1912
+
1913
+ @property
1914
+ def uppers(self) -> tuple[float, ...]:
1915
+ """The upper boundaries of all handled |Rule| objects.
1916
+
1917
+ See the main documentation on class |CalibrationInterface| for further
1918
+ information.
1919
+ """
1920
+ return tuple(rule.upper for rule in self)
1921
+
1922
+ @property
1923
+ def selections(self) -> tuple[str, ...]:
1924
+ """The names of all |Selection| objects addressed at least one of the handled
1925
+ |Rule| objects.
1926
+
1927
+ See the documentation on function |make_rules| for further information.
1928
+ """
1929
+ return tuple(
1930
+ sorted(set(itertools.chain.from_iterable(rule.selections for rule in self)))
1931
+ )
1932
+
1933
+ @property
1934
+ def parametertypes(
1935
+ self,
1936
+ ) -> tuple[tuple[type[parametertools.Parameter], Target], ...]:
1937
+ """The types of all |Parameter| objects addressed by at least one of the
1938
+ handled |Rule| objects.
1939
+
1940
+ See the documentation on function |make_rules| for further information.
1941
+ """
1942
+ parametertypes: list[tuple[type[parametertools.Parameter], Target]] = []
1943
+ for rule in self:
1944
+ if isinstance(rule, RuleIUH):
1945
+ parametertypes.append((rule.parametertype, rule.target))
1946
+ else:
1947
+ parametertypes.append((rule.parametertype, None))
1948
+ return variabletools.sort_variables(set(parametertypes))
1949
+
1950
+ def _update_values(self, values: Iterable[float]) -> None:
1951
+ for rule, value in zip(self, values):
1952
+ rule.value = value
1953
+
1954
+ def _refresh_hp(self) -> None:
1955
+ for element in self._elements:
1956
+ element.model.update_parameters()
1957
+ self._hp.conditions = self.conditions
1958
+
1959
+ @overload
1960
+ def apply_values(self, perform_simulation: Literal[True] = ...) -> float: ...
1961
+
1962
+ @overload
1963
+ def apply_values(self, perform_simulation: Literal[False]) -> None: ...
1964
+
1965
+ def apply_values(self, perform_simulation: bool = True) -> float | None:
1966
+ """Apply all current calibration parameter values on all relevant parameters.
1967
+
1968
+ Set argument `perform_simulation` to |False| to only change the actual
1969
+ parameter values and update the |HydPy| object without performing a simulation
1970
+ run.
1971
+
1972
+ See the main documentation on class |CalibrationInterface| for further
1973
+ information.
1974
+ """
1975
+ for rule in self:
1976
+ rule.apply_value()
1977
+ self._refresh_hp()
1978
+ if perform_simulation:
1979
+ self._hp.simulate()
1980
+ return self.calculate_likelihood()
1981
+ return None
1982
+
1983
+ def reset_parameters(self) -> None:
1984
+ """Reset all relevant parameters to their original states.
1985
+
1986
+ See the main documentation on class |CalibrationInterface| for further
1987
+ information.
1988
+ """
1989
+ for rule in self:
1990
+ rule.reset_parameters()
1991
+ self._refresh_hp()
1992
+
1993
+ def calculate_likelihood(self) -> float:
1994
+ """Apply the defined |TargetFunction| and return the result.
1995
+
1996
+ See the main documentation on class |CalibrationInterface| for further
1997
+ information.
1998
+ """
1999
+ self.result = self._targetfunction()
2000
+ return self.result
2001
+
2002
+ def perform_calibrationstep(
2003
+ self, values: Iterable[float], *args: Any, **kwargs: Any
2004
+ ) -> float:
2005
+ # pylint: disable=unused-argument
2006
+ # for optimisers that pass additional informative data
2007
+ """Update all calibration parameters with the given values, update the |HydPy|
2008
+ object, perform a simulation run, and calculate and return the achieved
2009
+ efficiency.
2010
+
2011
+ See the main documentation on class |CalibrationInterface| for further
2012
+ information.
2013
+ """
2014
+ self._update_values(values)
2015
+ likelihood = self.apply_values()
2016
+ self.update_logfile()
2017
+ return likelihood
2018
+
2019
+ def print_table(
2020
+ self,
2021
+ *,
2022
+ parametertypes: (
2023
+ Sequence[
2024
+ (
2025
+ type[parametertools.Parameter]
2026
+ | tuple[type[parametertools.Parameter], Target]
2027
+ )
2028
+ ]
2029
+ ) | None = None,
2030
+ selections: Sequence[str] | None = None,
2031
+ bounds: tuple[str, str] | None = ("lower", "upper"),
2032
+ fillvalue: str = "/",
2033
+ sep: str = "\t",
2034
+ file_: TextIO | None = None,
2035
+ ) -> None:
2036
+ """Print the current calibration parameter values in a table format.
2037
+
2038
+ The following examples combine the base examples of the documentation on class
2039
+ |CalibrationInterface| and class |ReplaceIUH|, so please make sure to
2040
+ understand them before proceeding.
2041
+
2042
+ We again use the `Lahn` example project but replace the |musk_classic| model
2043
+ instances with those of application model |arma_rimorido|, which allows
2044
+ discussing some special cases concerning the handling of |RuleIUH|:
2045
+
2046
+ >>> from hydpy.core.testtools import prepare_full_example_2
2047
+ >>> hp, pub, TestIO = prepare_full_example_2()
2048
+ >>> from hydpy import prepare_model
2049
+ >>> for element in hp.elements.river:
2050
+ ... element.model = prepare_model("arma_rimorido")
2051
+ ... element.model.parameters.control.responses([[], [1.0]])
2052
+ ... element.model.parameters.update()
2053
+
2054
+ We pass a (useless) dummy target function to the |CalibrationInterface| object:
2055
+
2056
+ >>> from hydpy import CalibrationInterface
2057
+ >>> ci = CalibrationInterface(hp=hp, targetfunction=lambda: 1.0)
2058
+
2059
+ Regarding |hland_96|, we intend to calibrate the parameters |hland_control.FC|
2060
+ and |hland_control.PercMax| with different values for the selections
2061
+ `headwaters` and `nonheadwaters`:
2062
+
2063
+ >>> from hydpy import CalibSpec, CalibSpecs, make_rules, Replace
2064
+ >>> calibspecs = CalibSpecs(
2065
+ ... CalibSpec(name="fc", default=100.0, lower=50.0, upper=200.0),
2066
+ ... CalibSpec(name="percmax", default=5.0, lower=1.0, upper=10.0, \
2067
+ parameterstep="1d"))
2068
+ >>> ci.add_rules(*make_rules(rule=Replace,
2069
+ ... calibspecs=calibspecs,
2070
+ ... model="hland_96",
2071
+ ... selections=("headwaters", "nonheadwaters"),
2072
+ ... product=True))
2073
+
2074
+ Regarding |arma_rimorido|, we cannot calibrate the values of parameter
2075
+ |arma_control.Responses| in a meaningful way. So instead, we use the
2076
+ |LinearStorageCascade| as a meta-model and calibrate its parameters
2077
+ |LinearStorageCascade.k| and |LinearStorageCascade.n|:
2078
+
2079
+ >>> from hydpy import LinearStorageCascade, ReplaceIUH
2080
+ >>> k = ReplaceIUH(name="k_global",
2081
+ ... target="k",
2082
+ ... parameter="responses",
2083
+ ... value=2.0,
2084
+ ... lower=1.0,
2085
+ ... parameterstep="1d",
2086
+ ... selections=("streams",))
2087
+ >>> n = ReplaceIUH(name="n_global",
2088
+ ... target="n",
2089
+ ... parameter="responses",
2090
+ ... value=4.0,
2091
+ ... lower=1.0,
2092
+ ... upper=100.0,
2093
+ ... selections=("streams",))
2094
+ >>> name2lsc = {element.name: LinearStorageCascade(k=1.0, n=1.0)
2095
+ ... for element in hp.elements.river}
2096
+ >>> k.add_iuhs(**name2lsc)
2097
+ >>> n.add_iuhs(**name2lsc)
2098
+ >>> ci.add_rules(k, n)
2099
+
2100
+ We change the values of two |Rule| objects related to |hland_96| to clarify
2101
+ that all values appear in the correct table cells:
2102
+
2103
+ >>> ci["fc_headwaters"].value = 200.0
2104
+ >>> ci["percmax_nonheadwaters"].value = 10.0
2105
+
2106
+ By default, method |CalibrationInterface.print_table| prints the values of all
2107
+ handled |Rule| objects. It varies the target control parameters on the first
2108
+ axis and the target selections on the second axis. Row two and three contain
2109
+ the (identical) lower and upper boundary values corresponding to the respective
2110
+ control parameters:
2111
+
2112
+ >>> ci.print_table() # doctest: +NORMALIZE_WHITESPACE
2113
+ lower upper headwaters nonheadwaters streams
2114
+ k->Responses 1.0 inf / / 2.0
2115
+ n->Responses 1.0 100.0 / / 4.0
2116
+ FC 50.0 200.0 200.0 100.0 /
2117
+ PercMax 1.0 10.0 5.0 10.0 /
2118
+
2119
+ For non-identical boundary values, method |CalibrationInterface.print_table|
2120
+ prints fill values in the relevant cells. Besides this, the following example
2121
+ shows how to define alternative titles for the boundary value columns:
2122
+
2123
+ >>> ci["fc_headwaters"].lower = 60.0
2124
+ >>> ci["percmax_nonheadwaters"].upper = 20.0
2125
+ >>> ci.print_table(bounds=("min", "max")) # doctest: +NORMALIZE_WHITESPACE
2126
+ min max headwaters nonheadwaters streams
2127
+ k->Responses 1.0 inf / / 2.0
2128
+ n->Responses 1.0 100.0 / / 4.0
2129
+ FC / 200.0 200.0 100.0 /
2130
+ PercMax 1.0 / 5.0 10.0 /
2131
+
2132
+ Pass |None| to argument `bounds` to omit writing any boundary value column:
2133
+
2134
+ >>> ci.print_table(bounds=None) # doctest: +NORMALIZE_WHITESPACE
2135
+ headwaters nonheadwaters streams
2136
+ k->Responses / / 2.0
2137
+ n->Responses / / 4.0
2138
+ FC 200.0 100.0 /
2139
+ PercMax 5.0 10.0 /
2140
+
2141
+ The next example shows how to change the tabulated target parameters and
2142
+ selections. Method |CalibrationInterface.print_table| uses the (given
2143
+ alternative) fill value for each parameter-selection-combination not met by any
2144
+ of the available |Rule| objects. For |RuleIUH|-related parameters, we must
2145
+ specify both the control parameter (as a type, in our example
2146
+ |arma_control.Responses|) and the meta-parameter (as a string, in our example
2147
+ |LinearStorageCascade.k|) within a |tuple|:
2148
+
2149
+ >>> from hydpy.models.hland.hland_control import CFlux, PercMax
2150
+ >>> from hydpy.models.arma.arma_control import Responses
2151
+ >>> ci.print_table( # doctest: +NORMALIZE_WHITESPACE
2152
+ ... parametertypes=(PercMax, CFlux, (Responses, "k")),
2153
+ ... selections=("streams", "headwaters"),
2154
+ ... bounds=None,
2155
+ ... fillvalue="-")
2156
+ streams headwaters
2157
+ PercMax - 5.0
2158
+ CFlux - -
2159
+ k->Responses 2.0 -
2160
+
2161
+ Note that the value of the same calibration parameter might appear multiple
2162
+ times when targeting multiple |Selection| objects:
2163
+
2164
+ >>> ci["fc_headwaters"].selections = ("headwaters", "streams")
2165
+ >>> ci.print_table(bounds=None) # doctest: +NORMALIZE_WHITESPACE
2166
+ headwaters nonheadwaters streams
2167
+ k->Responses / / 2.0
2168
+ n->Responses / / 4.0
2169
+ FC 200.0 100.0 200.0
2170
+ PercMax 5.0 10.0 /
2171
+ """
2172
+ none = type("_None", (), {})()
2173
+ if parametertypes is None:
2174
+ parametertypes_ = self.parametertypes
2175
+ else:
2176
+ parametertypes_ = tuple(
2177
+ item if isinstance(item, tuple) else (item, None)
2178
+ for item in parametertypes
2179
+ )
2180
+ if selections is None:
2181
+ selections = self.selections
2182
+ delta = 3 if bounds else 1
2183
+ table = numpy.full(
2184
+ shape=(len(parametertypes_) + 1, (len(selections)) + delta),
2185
+ fill_value=fillvalue,
2186
+ dtype=object,
2187
+ )
2188
+ table[0, 0] = ""
2189
+ table[1:, 0] = tuple(
2190
+ f"{target}->{par.__name__}" if target else par.__name__
2191
+ for par, target in parametertypes_
2192
+ )
2193
+ if bounds:
2194
+ table[0, 1:3] = bounds
2195
+ table[0, delta:] = selections
2196
+ par2idx = {par: idx + 1 for idx, par in enumerate(parametertypes_)}
2197
+ sel2jdx = {sel: jdx + delta for jdx, sel in enumerate(selections)}
2198
+ for rule in self:
2199
+ if isinstance(rule, RuleIUH):
2200
+ idx = par2idx.get((rule.parametertype, rule.target))
2201
+ else:
2202
+ idx = par2idx.get((rule.parametertype, None))
2203
+ if idx is not None:
2204
+ if bounds:
2205
+ if table[idx, 1] in (fillvalue, rule.lower):
2206
+ table[idx, 1] = rule.lower
2207
+ else:
2208
+ table[idx, 1] = none
2209
+ if table[idx, 2] in (fillvalue, rule.upper):
2210
+ table[idx, 2] = rule.upper
2211
+ else:
2212
+ table[idx, 2] = none
2213
+ for selection in rule.selections:
2214
+ jdx = sel2jdx.get(selection)
2215
+ if jdx is not None:
2216
+ table[idx, jdx] = rule.value
2217
+ table[table == none] = fillvalue
2218
+ for row in table:
2219
+ print(*row, sep=sep, file=file_)
2220
+
2221
+ def __len__(self) -> int:
2222
+ return len(self._rules)
2223
+
2224
+ def __iter__(self) -> Iterator[TypeRule1]:
2225
+ yield from self._rules.values()
2226
+
2227
+ def __getattr__(self, item: str) -> TypeRule1:
2228
+ try:
2229
+ return self._rules[item]
2230
+ except KeyError:
2231
+ raise AttributeError(
2232
+ f"The actual calibration interface does neither handle a normal "
2233
+ f"attribute nor a rule object named `{item}`."
2234
+ ) from None
2235
+
2236
+ def __getitem__(self, key: str) -> TypeRule1:
2237
+ try:
2238
+ return self._rules[key]
2239
+ except KeyError:
2240
+ raise KeyError(
2241
+ f"The actual calibration interface does not handle a rule object "
2242
+ f"named `{key}`."
2243
+ ) from None
2244
+
2245
+ def __contains__(self, item: str | Rule[Any]) -> bool:
2246
+ return (item in self._rules) or (item in self._rules.values())
2247
+
2248
+ def __repr__(self) -> str:
2249
+ return "\n".join(repr(rule) for rule in self)
2250
+
2251
+ def __str__(self) -> str:
2252
+ return type(self).__name__
2253
+
2254
+ def __dir__(self) -> list[str]:
2255
+ """
2256
+ >>> from hydpy.core.testtools import prepare_full_example_2
2257
+ >>> hp, pub, TestIO = prepare_full_example_2()
2258
+ >>> from hydpy import CalibrationInterface, make_rules, Replace
2259
+ >>> ci = CalibrationInterface[Replace](hp=hp, targetfunction=lambda: None)
2260
+ >>> ci.add_rules(*make_rules(rule=Replace,
2261
+ ... names=["fc", "percmax"],
2262
+ ... parameters=["fc", "percmax"],
2263
+ ... values=[100.0, 5.0],
2264
+ ... keywords=["forest", None],
2265
+ ... lowers=[50.0, 1.0],
2266
+ ... uppers=[200.0, 10.0],
2267
+ ... parametersteps="1d",
2268
+ ... model="hland_96"))
2269
+ >>> sorted(set(dir(ci)) - set(object.__dir__(ci)))
2270
+ ['fc', 'percmax']
2271
+ """
2272
+ return cast(list[str], super().__dir__()) + list(self._rules.keys())
2273
+
2274
+
2275
+ class RuleIUH(Rule["arma_control.Responses"]):
2276
+ """A |Rule|, class specialised for |IUH| parameters.
2277
+
2278
+ |RuleIUH| serves as a base class only. Please see the concrete implementation
2279
+ |ReplaceIUH| for further information.
2280
+ """
2281
+
2282
+ target: str
2283
+ """Name of the addressed property of the relevant |IUH| subclass."""
2284
+
2285
+ update_parameters: bool = True
2286
+ """Flag indicating whether method |ReplaceIUH.apply_value| should calculate the
2287
+ |ARMA.coefs| and pass them to the relevant model parameter or not.
2288
+
2289
+ Set this flag to |False| for the first |ReplaceIUH| object when another handles the
2290
+ same elements and is applied afterwards.
2291
+ """
2292
+ _element2iuh: dict[str, iuhtools.IUH] | None = None
2293
+
2294
+ def __init__(
2295
+ self,
2296
+ *,
2297
+ name: str,
2298
+ target: str,
2299
+ parameter: type[arma_control.Responses] | arma_control.Responses | str,
2300
+ value: float,
2301
+ lower: float = -numpy.inf,
2302
+ upper: float = numpy.inf,
2303
+ parameterstep: timetools.PeriodConstrArg | None = None,
2304
+ selections: Iterable[selectiontools.Selection | str] | None = None,
2305
+ model: types.ModuleType | str | None = None,
2306
+ ) -> None:
2307
+ super().__init__(
2308
+ name=name,
2309
+ parameter=parameter,
2310
+ value=value,
2311
+ lower=lower,
2312
+ upper=upper,
2313
+ parameterstep=parameterstep,
2314
+ selections=selections,
2315
+ model=model,
2316
+ )
2317
+ self.target = target
2318
+
2319
+ def _get_original_parameter_values(self) -> tuple[Any, ...]:
2320
+ return tuple(
2321
+ (par.ar_coefs[0, :].copy(), par.ma_coefs[0, :].copy()) for par in self
2322
+ )
2323
+
2324
+ def add_iuhs(self, **iuhs: iuhtools.IUH) -> None:
2325
+ """Add one |IUH| object for each relevant |Element| object.
2326
+
2327
+ See the main documentation on class |ReplaceIUH| for further information.
2328
+ """
2329
+ try:
2330
+ names_int = set(self.elements.names)
2331
+ names_ext = set(iuhs.keys())
2332
+ if names_int != names_ext:
2333
+ enumeration = objecttools.enumeration
2334
+ raise RuntimeError(
2335
+ f"The given elements ({enumeration(sorted(names_ext))}) do not "
2336
+ f"agree with the complete set of relevant elements "
2337
+ f"({enumeration(sorted(names_int))})."
2338
+ )
2339
+ element2iuh = self._element2iuh = {}
2340
+ for element in self.elements:
2341
+ element2iuh[element.name] = iuhs[element.name]
2342
+ except BaseException:
2343
+ objecttools.augment_excmessage(
2344
+ f"While trying to add `IUH` objects to the `{type(self).__name__}` "
2345
+ f"rule `{self}`"
2346
+ )
2347
+
2348
+ @property
2349
+ def _iuhs(self) -> Iterable[iuhtools.IUH]:
2350
+ element2iuh = {} if self._element2iuh is None else self._element2iuh
2351
+ yield from element2iuh.values()
2352
+
2353
+ def reset_parameters(self) -> None:
2354
+ """Reset all relevant parameter objects to their original states.
2355
+
2356
+ See the main documentation on class |ReplaceIUH| for further information.
2357
+ """
2358
+ for parameter, orig in zip(self, self._original_parameter_values):
2359
+ parameter(orig)
2360
+
2361
+
2362
+ class ReplaceIUH(RuleIUH):
2363
+ """A |RuleIUH| class for replacing |IUH| parameter values with the current
2364
+ calibration parameter values.
2365
+
2366
+ Usually, it is not a good idea to calibrate the AR and MA coefficients of
2367
+ parameters like |arma_control.Responses| of model |arma_rimorido| individually.
2368
+ Instead, we need to calibrate the few coefficients of the underlying |IUH| objects,
2369
+ which calculate the ARMA coefficients. Class |ReplaceIUH| helps to accomplish this
2370
+ task.
2371
+
2372
+ .. note::
2373
+
2374
+ Class |ReplaceIUH| is still under development. For example, it does not
2375
+ address the possibility of different ARMA coefficients related to different
2376
+ discharge thresholds. Hence, the usage of class |ReplaceIUH| might change in
2377
+ the future.
2378
+
2379
+ So far, there is no example project containing |arma_rimorido| models instances.
2380
+ Therefore, we generate a simple one consisting of two |Element| objects only:
2381
+
2382
+ >>> from hydpy import Element, prepare_model, Selection
2383
+ >>> element1 = Element("element1", inlets="in1", outlets="out1")
2384
+ >>> element2 = Element("element2", inlets="in2", outlets="out2")
2385
+ >>> complete = Selection("complete", elements=[element1, element2])
2386
+ >>> element1.model = prepare_model("arma_rimorido")
2387
+ >>> element2.model = prepare_model("arma_rimorido")
2388
+
2389
+ We focus on class |TranslationDiffusionEquation| in the following. First, we
2390
+ create two separate instances and use them to calculate the response coefficients
2391
+ of both |arma_rimorido| instances:
2392
+
2393
+ >>> from hydpy import TranslationDiffusionEquation
2394
+ >>> tde1 = TranslationDiffusionEquation(u=5.0, d=15.0, x=1.0)
2395
+ >>> tde2 = TranslationDiffusionEquation(u=5.0, d=15.0, x=2.0)
2396
+ >>> element1.model.parameters.control.responses(tde1.arma.coefs)
2397
+ >>> element1.model.parameters.control.responses
2398
+ responses(th_0_0=((0.906536, -0.197555, 0.002128, 0.000276),
2399
+ (0.842788, -0.631499, 0.061685, 0.015639, 0.0, 0.0, 0.0,
2400
+ -0.000001, 0.0, 0.0, 0.0, 0.0)))
2401
+ >>> element2.model.parameters.control.responses(tde2.arma.coefs)
2402
+ >>> element2.model.parameters.control.responses
2403
+ responses(th_0_0=((1.298097, -0.536702, 0.072903, -0.001207, -0.00004),
2404
+ (0.699212, -0.663835, 0.093935, 0.046177, -0.00854)))
2405
+
2406
+ Next, we define one |ReplaceIUH| for modifying parameter
2407
+ |TranslationDiffusionEquation.u| and another one for changing
2408
+ |TranslationDiffusionEquation.d|:
2409
+
2410
+ >>> from hydpy import ReplaceIUH
2411
+ >>> u = ReplaceIUH(name="U",
2412
+ ... target="u",
2413
+ ... parameter="responses",
2414
+ ... value=5.0,
2415
+ ... lower=1.0,
2416
+ ... upper=10.0,
2417
+ ... selections=[complete])
2418
+ >>> d = ReplaceIUH(name="D",
2419
+ ... target="d",
2420
+ ... parameter="responses",
2421
+ ... value=15.0,
2422
+ ... lower=5.0,
2423
+ ... upper=50.0,
2424
+ ... selections=[complete])
2425
+
2426
+ We add and thereby connect the |Element| and |TranslationDiffusionEquation| objects
2427
+ to both |ReplaceIUH| objects via method |RuleIUH.add_iuhs|:
2428
+
2429
+ >>> u.add_iuhs(element1=tde1, element2=tde2)
2430
+ >>> d.add_iuhs(element1=tde1, element2=tde2)
2431
+
2432
+ Note that method |RuleIUH.add_iuhs| enforces to add all |IUH| objects at ones to
2433
+ avoid inconsistencies that might be hard to track later:
2434
+
2435
+ >>> d.add_iuhs(element1=tde1)
2436
+ Traceback (most recent call last):
2437
+ ...
2438
+ RuntimeError: While trying to add `IUH` objects to the `ReplaceIUH` rule `D`, the \
2439
+ following error occurred: The given elements (element1) do not agree with the \
2440
+ complete set of relevant elements (element1 and element2).
2441
+
2442
+ By default, each |ReplaceIUH| object triggers the calculation of the ARMA
2443
+ coefficients during the execution of its method |ReplaceIUH.apply_value|, which can
2444
+ be a waste of computation time if we want to calibrate multiple |IUH| coefficients.
2445
+ To save computation time in such cases, set option |RuleIUH.update_parameters|
2446
+ to |False| for all except the lastly executed |ReplaceIUH| objects:
2447
+
2448
+ >>> u.update_parameters = False
2449
+
2450
+ Now, changing the value of rule `U` and calling method |ReplaceIUH.apply_value|
2451
+ does not affect the coefficients of both |arma_control.Responses| parameters:
2452
+
2453
+ >>> u.value = 10.0
2454
+ >>> u.apply_value()
2455
+ >>> tde1
2456
+ TranslationDiffusionEquation(d=15.0, u=10.0, x=1.0)
2457
+ >>> element1.model.parameters.control.responses
2458
+ responses(th_0_0=((0.906536, -0.197555, 0.002128, 0.000276),
2459
+ (0.842788, -0.631499, 0.061685, 0.015639, 0.0, 0.0, 0.0,
2460
+ -0.000001, 0.0, 0.0, 0.0, 0.0)))
2461
+ >>> tde2
2462
+ TranslationDiffusionEquation(d=15.0, u=10.0, x=2.0)
2463
+ >>> element2.model.parameters.control.responses
2464
+ responses(th_0_0=((1.298097, -0.536702, 0.072903, -0.001207, -0.00004),
2465
+ (0.699212, -0.663835, 0.093935, 0.046177, -0.00854)))
2466
+
2467
+ On the other side, calling method |ReplaceIUH.apply_value| of rule `D` does
2468
+ activate the freshly set value of rule `D` and the previously set value of rule
2469
+ `U`, as well:
2470
+
2471
+ >>> d.value = 50.0
2472
+ >>> d.apply_value()
2473
+ >>> tde1
2474
+ TranslationDiffusionEquation(d=50.0, u=10.0, x=1.0)
2475
+ >>> element1.model.parameters.control.responses
2476
+ responses(th_0_0=((0.811473, -0.15234, -0.000256, 0.000177),
2477
+ (0.916619, -0.670781, 0.087185, 0.007923)))
2478
+ >>> tde2
2479
+ TranslationDiffusionEquation(d=50.0, u=10.0, x=2.0)
2480
+ >>> element2.model.parameters.control.responses
2481
+ responses(th_0_0=((0.832237, -0.167205, 0.002007, 0.000184),
2482
+ (0.836513, -0.555399, 0.037628, 0.014035)))
2483
+
2484
+ Use method |RuleIUH.reset_parameters| to restore the original ARMA coefficients:
2485
+
2486
+ >>> d.reset_parameters()
2487
+ >>> element1.model.parameters.control.responses
2488
+ responses(th_0_0=((0.906536, -0.197555, 0.002128, 0.000276),
2489
+ (0.842788, -0.631499, 0.061685, 0.015639, 0.0, 0.0, 0.0,
2490
+ -0.000001, 0.0, 0.0, 0.0, 0.0)))
2491
+ >>> element2.model.parameters.control.responses
2492
+ responses(th_0_0=((1.298097, -0.536702, 0.072903, -0.001207, -0.00004),
2493
+ (0.699212, -0.663835, 0.093935, 0.046177, -0.00854)))
2494
+ """
2495
+
2496
+ def apply_value(self) -> None:
2497
+ """Apply all current calibration parameter values to all relevant |IUH| objects
2498
+ and eventually update the related parameter's ARMA coefficients.
2499
+
2500
+ See the main documentation on class |ReplaceIUH| for further information.
2501
+ """
2502
+ for parameter, iuh in zip(self, self._iuhs):
2503
+ setattr(iuh, self.target, self.value)
2504
+ if self.update_parameters:
2505
+ parameter(iuh.arma.coefs)
2506
+
2507
+
2508
+ class MultiplyIUH(RuleIUH):
2509
+ """A |RuleIUH| class for replacing |IUH| parameter values with the current
2510
+ calibration parameter values, applied on the original |IUH| values as factors.
2511
+
2512
+ Please read the documentation on class |ReplaceIUH| first, from which we take the
2513
+ following test configuration:
2514
+
2515
+ >>> from hydpy import Element, prepare_model, Selection
2516
+ >>> element1 = Element("element1", inlets="in1", outlets="out1")
2517
+ >>> element2 = Element("element2", inlets="in2", outlets="out2")
2518
+ >>> complete = Selection("complete", elements=[element1, element2])
2519
+ >>> element1.model = prepare_model("arma_rimorido")
2520
+ >>> element2.model = prepare_model("arma_rimorido")
2521
+
2522
+ >>> from hydpy import TranslationDiffusionEquation
2523
+ >>> tde1 = TranslationDiffusionEquation(u=5.0, d=15.0, x=1.0)
2524
+ >>> tde2 = TranslationDiffusionEquation(u=5.0, d=15.0, x=2.0)
2525
+ >>> element1.model.parameters.control.responses(tde1.arma.coefs)
2526
+ >>> element1.model.parameters.control.responses
2527
+ responses(th_0_0=((0.906536, -0.197555, 0.002128, 0.000276),
2528
+ (0.842788, -0.631499, 0.061685, 0.015639, 0.0, 0.0, 0.0,
2529
+ -0.000001, 0.0, 0.0, 0.0, 0.0)))
2530
+ >>> element2.model.parameters.control.responses(tde2.arma.coefs)
2531
+ >>> element2.model.parameters.control.responses
2532
+ responses(th_0_0=((1.298097, -0.536702, 0.072903, -0.001207, -0.00004),
2533
+ (0.699212, -0.663835, 0.093935, 0.046177, -0.00854)))
2534
+
2535
+ Initialising |MultiplyIUH| works exactly as for |ReplaceIUH|, except for the
2536
+ semantic difference that `value`, `lower`, and `upper` now represent factors:
2537
+
2538
+ >>> from hydpy import MultiplyIUH
2539
+ >>> u = MultiplyIUH(name="U",
2540
+ ... target="u",
2541
+ ... parameter="responses",
2542
+ ... value=2.0,
2543
+ ... lower=1.0,
2544
+ ... upper=4.0,
2545
+ ... selections=[complete])
2546
+ >>> d = MultiplyIUH(name="D",
2547
+ ... target="d",
2548
+ ... parameter="responses",
2549
+ ... value=0.5,
2550
+ ... lower=0.2,
2551
+ ... upper=2.0,
2552
+ ... selections=[complete])
2553
+
2554
+ >>> u.add_iuhs(element1=tde1, element2=tde2)
2555
+ >>> d.add_iuhs(element1=tde1, element2=tde2)
2556
+ >>> u.update_parameters = False
2557
+
2558
+ The following examples demonstrate that the current calibration values actually
2559
+ as factors, applied to the original values of the relevant |IUH| properties:
2560
+
2561
+ >>> u.value = 3.0
2562
+ >>> u.apply_value()
2563
+ >>> d.value = 1.0/3.0
2564
+ >>> d.apply_value()
2565
+ >>> tde1
2566
+ TranslationDiffusionEquation(d=5.0, u=15.0, x=1.0)
2567
+ >>> element1.model.parameters.control.responses
2568
+ responses(th_0_0=((0.0, 0.0),
2569
+ (0.933333, 0.066667)))
2570
+ >>> tde2
2571
+ TranslationDiffusionEquation(d=5.0, u=15.0, x=2.0)
2572
+ >>> element2.model.parameters.control.responses
2573
+ responses(th_0_0=((0.0, 0.0),
2574
+ (0.866667, 0.133333)))
2575
+
2576
+ >>> u.value = 1.0
2577
+ >>> u.apply_value()
2578
+ >>> d.value = 1.0
2579
+ >>> d.apply_value()
2580
+ >>> tde1
2581
+ TranslationDiffusionEquation(d=15.0, u=5.0, x=1.0)
2582
+ >>> element1.model.parameters.control.responses
2583
+ responses(th_0_0=((0.906536, -0.197555, 0.002128, 0.000276),
2584
+ (0.842788, -0.631499, 0.061685, 0.015639, 0.0, 0.0, 0.0,
2585
+ -0.000001, 0.0, 0.0, 0.0, 0.0)))
2586
+ >>> tde2
2587
+ TranslationDiffusionEquation(d=15.0, u=5.0, x=2.0)
2588
+ >>> element2.model.parameters.control.responses
2589
+ responses(th_0_0=((1.298097, -0.536702, 0.072903, -0.001207, -0.00004),
2590
+ (0.699212, -0.663835, 0.093935, 0.046177, -0.00854)))
2591
+ """
2592
+
2593
+ _original_iuh_values: list[float]
2594
+
2595
+ def add_iuhs(self, **iuhs: iuhtools.IUH) -> None:
2596
+ """Add one |IUH| object for each relevant |Element| object.
2597
+
2598
+ See the main documentation on class |ReplaceIUH| for further information.
2599
+ """
2600
+ super().add_iuhs(**iuhs)
2601
+ target = self.target
2602
+ original_iuh_values: list[float] = []
2603
+ assert self._element2iuh is not None # ensured by `RuleIUH.add_iuhs`
2604
+ for iuh in self._element2iuh.values():
2605
+ original_iuh_values.append(getattr(iuh, target))
2606
+ self._original_iuh_values = original_iuh_values
2607
+
2608
+ def apply_value(self) -> None:
2609
+ """Apply all current calibration parameter values to all relevant |IUH| objects
2610
+ and eventually update the related parameter's ARMA coefficients.
2611
+
2612
+ See the main documentation on class |MultiplyIUH| for further information.
2613
+ """
2614
+ target = self.target
2615
+ for parameter, iuh, orig in zip(self, self._iuhs, self._original_iuh_values):
2616
+ setattr(iuh, target, self.value * orig)
2617
+ if self.update_parameters:
2618
+ parameter(iuh.arma.coefs)
2619
+
2620
+
2621
+ class CalibSpec:
2622
+ """Helper class for specifying the properties of a single calibration parameter.
2623
+
2624
+ So far, class |CalibSpec| does not provide much functionality besides checking upon
2625
+ initialisation that the given default and boundary values are consistent:
2626
+
2627
+ >>> from hydpy import CalibSpec
2628
+ >>> CalibSpec(name="par1", default=1.0)
2629
+ CalibSpec(name="par1", default=1.0)
2630
+
2631
+ >>> CalibSpec(name="par1", default=1.0, keyword="key1")
2632
+ CalibSpec(name="par1", default=1.0, keyword="key1")
2633
+
2634
+ >>> CalibSpec(name="par1", default=1.0, lower=2.0)
2635
+ Traceback (most recent call last):
2636
+ ...
2637
+ ValueError: The following values given for calibration parameter `par1` are not \
2638
+ consistent: default=1.0, lower=2.0, upper=inf.
2639
+
2640
+ >>> CalibSpec(name="par1", default=1.0, upper=0.5)
2641
+ Traceback (most recent call last):
2642
+ ...
2643
+ ValueError: The following values given for calibration parameter `par1` are not \
2644
+ consistent: default=1.0, lower=-inf, upper=0.5.
2645
+
2646
+ >>> CalibSpec(name="par1", default=1.0, lower=0.0, upper=2.0)
2647
+ CalibSpec(name="par1", default=1.0, lower=0.0, upper=2.0)
2648
+
2649
+ Use the `parameterstep` argument for time-dependent calibration parameters:
2650
+
2651
+ >>> CalibSpec(name="par1", default=1.0/3.0, lower=1.0/3.0, upper=1.0/3.0,
2652
+ ... parameterstep="1d")
2653
+ CalibSpec(
2654
+ name="par1", default=0.333333, lower=0.333333, upper=0.333333, \
2655
+ parameterstep="1d"
2656
+ )
2657
+
2658
+ See the documentation on class |CalibSpecs| for further information.
2659
+ """
2660
+
2661
+ name: str
2662
+ """Name of the calibration parameter."""
2663
+ default: float
2664
+ """The default value of the calibration parameter."""
2665
+ keyword: str | None
2666
+ """The (optional) target keyword of the calibration parameter."""
2667
+ lower: float
2668
+ """Lower bound of the allowed calibration parameter value."""
2669
+ upper: float
2670
+ """Upper bound of the allowed calibration parameter value."""
2671
+ parameterstep: timetools.Period | None
2672
+ """The parameter step size to be set before applying the defined calibration
2673
+ parameter values."""
2674
+
2675
+ def __init__(
2676
+ self,
2677
+ *,
2678
+ name: str,
2679
+ default: float,
2680
+ keyword: None | None = None,
2681
+ lower: float = -numpy.inf,
2682
+ upper: float = numpy.inf,
2683
+ parameterstep: timetools.PeriodConstrArg | None = None,
2684
+ ) -> None:
2685
+ self.name = name
2686
+ if not lower <= default <= upper:
2687
+ raise ValueError(
2688
+ f"The following values given for calibration parameter `{self}` are "
2689
+ f"not consistent: default={objecttools.repr_(default)}, lower="
2690
+ f"{objecttools.repr_(lower)}, upper={objecttools.repr_(upper)}."
2691
+ )
2692
+ self.default = default
2693
+ self.keyword = keyword
2694
+ self.lower = lower
2695
+ self.upper = upper
2696
+ if parameterstep is None:
2697
+ self.parameterstep = None
2698
+ else:
2699
+ self.parameterstep = timetools.Period(parameterstep)
2700
+
2701
+ def __str__(self) -> str:
2702
+ return self.name
2703
+
2704
+ def __repr__(self) -> str:
2705
+ arguments = [
2706
+ f'name="{self.name}"',
2707
+ f"default={objecttools.repr_(self.default)}",
2708
+ ]
2709
+ if self.keyword is not None:
2710
+ arguments.append(f'keyword="{self.keyword}"')
2711
+ if not numpy.isinf(self.lower):
2712
+ arguments.append(f"lower={objecttools.repr_(self.lower)}")
2713
+ if not numpy.isinf(self.upper):
2714
+ arguments.append(f"upper={objecttools.repr_(self.upper)}")
2715
+ if self.parameterstep is not None:
2716
+ arguments.append(f'parameterstep="{self.parameterstep}"')
2717
+ return black.format_str(
2718
+ f"{type(self).__name__}({', '.join(arguments)})", mode=black.FileMode()
2719
+ )[:-1]
2720
+
2721
+
2722
+ class CalibSpecs:
2723
+ """Collection class for handling |CalibSpec| objects.
2724
+
2725
+ The primary purpose of class |CalibSpecs| is to handle multiple |CalibSpec| objects
2726
+ and to make all their attributes accessible in the same order. See property
2727
+ |CalibSpecs.names| as one example. Note that all such properties are sorted in the
2728
+ order or the attachment of the different |CalibSpec| objects:
2729
+
2730
+ >>> from hydpy import CalibSpec, CalibSpecs
2731
+ >>> calibspecs = CalibSpecs(
2732
+ ... CalibSpec(
2733
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d"
2734
+ ... ),
2735
+ ... CalibSpec(name="second", default=1.0, keyword="kw2", lower=0.0),
2736
+ ... CalibSpec(name="first",default=2.0, upper=2.0))
2737
+ >>> calibspecs
2738
+ CalibSpecs(
2739
+ CalibSpec(name="third", default=3.0, lower=-10.0, upper=10.0, \
2740
+ parameterstep="1d"),
2741
+ CalibSpec(name="second", default=1.0, keyword="kw2", lower=0.0),
2742
+ CalibSpec(name="first", default=2.0, upper=2.0),
2743
+ )
2744
+
2745
+ You can query and remove |CalibSpec| objects via keyword and attribute access:
2746
+
2747
+ >>> print(calibspecs)
2748
+ CalibSpecs("third", "second", "first")
2749
+
2750
+ >>> third = calibspecs["third"]
2751
+ >>> third in calibspecs
2752
+ True
2753
+ >>> del calibspecs["third"]
2754
+ >>> third in calibspecs
2755
+ False
2756
+ >>> calibspecs["third"]
2757
+ Traceback (most recent call last):
2758
+ ...
2759
+ KeyError: 'The current `CalibSpecs` object does not handle a `CalibSpec` object \
2760
+ named `third`.'
2761
+ >>> del calibspecs["third"]
2762
+ Traceback (most recent call last):
2763
+ ...
2764
+ KeyError: 'The current `CalibSpecs` object does not handle a `CalibSpec` object \
2765
+ named `third`.'
2766
+
2767
+ >>> second = calibspecs.second
2768
+ >>> "second" in calibspecs
2769
+ True
2770
+ >>> del calibspecs.second
2771
+ >>> "second" in calibspecs
2772
+ False
2773
+ >>> calibspecs.second
2774
+ Traceback (most recent call last):
2775
+ ...
2776
+ AttributeError: The current `CalibSpecs` object does neither handle a `CalibSpec` \
2777
+ object nor a normal attribute named `second`.
2778
+ >>> del calibspecs.second
2779
+ Traceback (most recent call last):
2780
+ ...
2781
+ AttributeError: The current `CalibSpecs` object does not handle a `CalibSpec` \
2782
+ object named `second`.
2783
+
2784
+ >>> len(calibspecs)
2785
+ 1
2786
+
2787
+ Now we can re-append the previously removed |CalibSpec| objects (and thereby bring
2788
+ the order of attachment in agreement with the |CalibSpec| names):
2789
+
2790
+ >>> calibspecs.append(second, third)
2791
+ >>> for calibspec in calibspecs:
2792
+ ... print(calibspec)
2793
+ first
2794
+ second
2795
+ third
2796
+ """
2797
+
2798
+ _name2parspec: dict[str, CalibSpec]
2799
+
2800
+ def __init__(self, *parspecs: CalibSpec) -> None:
2801
+ self._name2parspec = {parspec.name: parspec for parspec in parspecs}
2802
+
2803
+ def __getitem__(self, name: str) -> CalibSpec:
2804
+ try:
2805
+ return self._name2parspec[name]
2806
+ except KeyError:
2807
+ raise KeyError(
2808
+ f"The current `{type(self).__name__}` object does not handle a "
2809
+ f"`CalibSpec` object named `{name}`."
2810
+ ) from None
2811
+
2812
+ def __delitem__(self, name: str) -> None:
2813
+ try:
2814
+ del self._name2parspec[name]
2815
+ except KeyError:
2816
+ raise KeyError(
2817
+ f"The current `{type(self).__name__}` object does not handle a "
2818
+ f"`CalibSpec` object named `{name}`."
2819
+ ) from None
2820
+
2821
+ def __getattr__(self, name: str) -> CalibSpec:
2822
+ try:
2823
+ return self._name2parspec[name]
2824
+ except KeyError:
2825
+ raise AttributeError(
2826
+ f"The current `{type(self).__name__}` object does neither handle a "
2827
+ f"`CalibSpec` object nor a normal attribute named `{name}`."
2828
+ ) from None
2829
+
2830
+ def __delattr__(self, name: str) -> None:
2831
+ try:
2832
+ del self._name2parspec[name]
2833
+ except KeyError:
2834
+ raise AttributeError(
2835
+ f"The current `{type(self).__name__}` object does not handle a "
2836
+ f"`CalibSpec` object named `{name}`."
2837
+ ) from None
2838
+
2839
+ def __contains__(self, item: str | CalibSpec) -> bool:
2840
+ return (item in self._name2parspec) or (item in self._name2parspec.values())
2841
+
2842
+ def __len__(self) -> int:
2843
+ return len(self._name2parspec)
2844
+
2845
+ def __iter__(self) -> Iterator[CalibSpec]:
2846
+ yield from self._name2parspec.values()
2847
+
2848
+ def append(self, *calibspecs: CalibSpec) -> None:
2849
+ """Append one or more |CalibSpec| objects.
2850
+
2851
+ >>> from hydpy import CalibSpec, CalibSpecs
2852
+ >>> third = CalibSpec(
2853
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d")
2854
+ >>> first = CalibSpec(name="first", default=1.0, lower=0.0)
2855
+ >>> second = CalibSpec(name="second",default=2.0, keyword="kw2", upper=2.0)
2856
+ >>> calibspecs = CalibSpecs()
2857
+ >>> calibspecs.append(first)
2858
+ >>> calibspecs.append(second, third)
2859
+ >>> calibspecs
2860
+ CalibSpecs(
2861
+ CalibSpec(name="first", default=1.0, lower=0.0),
2862
+ CalibSpec(name="second", default=2.0, keyword="kw2", upper=2.0),
2863
+ CalibSpec(name="third", default=3.0, lower=-10.0, upper=10.0, \
2864
+ parameterstep="1d"),
2865
+ )
2866
+ """
2867
+ for calibspec in calibspecs:
2868
+ self._name2parspec[calibspec.name] = calibspec
2869
+
2870
+ @property
2871
+ def names(self) -> tuple[str, ...]:
2872
+ """The names of all |CalibSpec| objects in the order of attachment.
2873
+
2874
+ >>> from hydpy import CalibSpec, CalibSpecs
2875
+ >>> third = CalibSpec(
2876
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d")
2877
+ >>> calibspecs = CalibSpecs(CalibSpec(name="first", default=1.0, lower=0.0),
2878
+ ... CalibSpec(name="second",default=2.0, upper=2.0))
2879
+ >>> calibspecs.append(third)
2880
+ >>> calibspecs.names
2881
+ ('first', 'second', 'third')
2882
+ """
2883
+ return tuple(parspec.name for parspec in self._name2parspec.values())
2884
+
2885
+ @property
2886
+ def defaults(self) -> tuple[float, ...]:
2887
+ """The default values of all |CalibSpec| objects in the order of attachment.
2888
+
2889
+ >>> from hydpy import CalibSpec, CalibSpecs
2890
+ >>> third = CalibSpec(
2891
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d")
2892
+ >>> calibspecs = CalibSpecs(
2893
+ ... CalibSpec(name="first", default=1.0, lower=0.0),
2894
+ ... CalibSpec(name="second", default=2.0, keyword="kw2", upper=2.0))
2895
+ >>> calibspecs.append(third)
2896
+ >>> calibspecs.defaults
2897
+ (1.0, 2.0, 3.0)
2898
+ """
2899
+ return tuple(parspec.default for parspec in self._name2parspec.values())
2900
+
2901
+ @property
2902
+ def keywords(self) -> tuple[str | None, ...]:
2903
+ """The (optional) target keywords of all |CalibSpec| objects in the order of
2904
+ attachment.
2905
+
2906
+ >>> from hydpy import CalibSpec, CalibSpecs
2907
+ >>> third = CalibSpec(
2908
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d")
2909
+ >>> calibspecs = CalibSpecs(
2910
+ ... CalibSpec(name="first", default=1.0, lower=0.0),
2911
+ ... CalibSpec(name="second", default=2.0, keyword="kw2", upper=2.0))
2912
+ >>> calibspecs.append(third)
2913
+ >>> calibspecs.keywords
2914
+ (None, 'kw2', None)
2915
+ """
2916
+ return tuple(parspec.keyword for parspec in self._name2parspec.values())
2917
+
2918
+ @property
2919
+ def lowers(self) -> tuple[float, ...]:
2920
+ """The lower boundary values of all |CalibSpec| objects in the order of
2921
+ attachment.
2922
+
2923
+ >>> from hydpy import CalibSpec, CalibSpecs
2924
+ >>> third = CalibSpec(
2925
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d")
2926
+ >>> calibspecs = CalibSpecs(
2927
+ ... CalibSpec(name="first", default=1.0, lower=0.0),
2928
+ ... CalibSpec(name="second", default=2.0, keyword="kw2", upper=2.0))
2929
+ >>> calibspecs.append(third)
2930
+ >>> calibspecs.lowers
2931
+ (0.0, -inf, -10.0)
2932
+ """
2933
+ return tuple(parspec.lower for parspec in self._name2parspec.values())
2934
+
2935
+ @property
2936
+ def uppers(self) -> tuple[float, ...]:
2937
+ """The upper boundary values of all |CalibSpec| objects in the order of
2938
+ attachment.
2939
+
2940
+ >>> from hydpy import CalibSpec, CalibSpecs
2941
+ >>> third = CalibSpec(
2942
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d")
2943
+ >>> calibspecs = CalibSpecs(
2944
+ ... CalibSpec(name="first", default=1.0, lower=0.0),
2945
+ ... CalibSpec(name="second", default=2.0, keyword="kw2", upper=2.0))
2946
+ >>> calibspecs.append(third)
2947
+ >>> calibspecs.uppers
2948
+ (inf, 2.0, 10.0)
2949
+ """
2950
+ return tuple(parspec.upper for parspec in self._name2parspec.values())
2951
+
2952
+ @property
2953
+ def parametersteps(self) -> tuple[timetools.Period | None, ...]:
2954
+ """The parameter steps of all |CalibSpec| objects in the order of attachment.
2955
+
2956
+ >>> from hydpy import CalibSpec, CalibSpecs
2957
+ >>> third = CalibSpec(
2958
+ ... name="third", default=3.0, lower=-10.0, upper=10.0, parameterstep="1d")
2959
+ >>> calibspecs = CalibSpecs(
2960
+ ... CalibSpec(name="first", default=1.0, lower=0.0),
2961
+ ... CalibSpec(name="second", default=2.0, keyword="kw2", upper=2.0))
2962
+ >>> calibspecs.append(third)
2963
+ >>> calibspecs.parametersteps
2964
+ (None, None, Period("1d"))
2965
+ """
2966
+ return tuple(parspec.parameterstep for parspec in self._name2parspec.values())
2967
+
2968
+ def __str__(self) -> str:
2969
+ arguments = (f'"{name}"' for name in self._name2parspec.keys())
2970
+ return black.format_str(
2971
+ f"{type(self).__name__}({', '.join(arguments)})", mode=black.FileMode()
2972
+ )[:-1]
2973
+
2974
+ def __repr__(self) -> str:
2975
+ arguments = (repr(value) for value in self._name2parspec.values())
2976
+ return black.format_str(
2977
+ f"{type(self).__name__}({', '.join(arguments)})", mode=black.FileMode()
2978
+ )[:-1]
2979
+
2980
+ def __dir__(self) -> list[str]:
2981
+ """
2982
+ >>> from hydpy import CalibSpec, CalibSpecs, print_vector
2983
+ >>> calibspecs = CalibSpecs(CalibSpec(name="first", default=1.0),
2984
+ ... CalibSpec(name="second",default=2.0))
2985
+ >>> sorted(set(dir(calibspecs)) - set(object.__dir__(calibspecs)))
2986
+ ['first', 'second']
2987
+ """
2988
+ return list(super().__dir__()) + list(self.names)
2989
+
2990
+
2991
+ @overload
2992
+ def make_rules(
2993
+ *,
2994
+ rule: type[TypeRule],
2995
+ names: Sequence[str],
2996
+ parameters: Sequence[parametertools.Parameter | str],
2997
+ values: Sequence[float],
2998
+ lowers: Sequence[float],
2999
+ uppers: Sequence[float],
3000
+ parametersteps: Sequence1[timetools.PeriodConstrArg | None] = None,
3001
+ model: types.ModuleType | str | None = None,
3002
+ selections: Literal[None] = None,
3003
+ ) -> list[TypeRule]: ...
3004
+
3005
+
3006
+ @overload
3007
+ def make_rules(
3008
+ *,
3009
+ rule: type[TypeRule],
3010
+ names: Sequence[str],
3011
+ parameters: Sequence[parametertools.Parameter | str],
3012
+ values: Sequence[float],
3013
+ keywords: Sequence[str | None] | None = None,
3014
+ lowers: Sequence[float],
3015
+ uppers: Sequence[float],
3016
+ parametersteps: Sequence1[timetools.PeriodConstrArg | None] = None,
3017
+ model: types.ModuleType | str | None = None,
3018
+ selections: Iterable[selectiontools.Selection | str],
3019
+ product: bool = False,
3020
+ ) -> list[TypeRule]: ...
3021
+
3022
+
3023
+ @overload
3024
+ def make_rules(
3025
+ *,
3026
+ rule: type[TypeRule],
3027
+ calibspecs: CalibSpecs,
3028
+ names: Sequence[str] | None = None,
3029
+ parameters: Sequence[parametertools.Parameter | str] | None = None,
3030
+ values: Sequence[float] | None = None,
3031
+ keywords: Sequence[str | None] | None = None,
3032
+ lowers: Sequence[float] | None = None,
3033
+ uppers: Sequence[float] | None = None,
3034
+ model: types.ModuleType | str | None = None,
3035
+ selections: Literal[None] = None,
3036
+ ) -> list[TypeRule]: ...
3037
+
3038
+
3039
+ @overload
3040
+ def make_rules(
3041
+ *,
3042
+ rule: type[TypeRule],
3043
+ calibspecs: CalibSpecs,
3044
+ names: Sequence[str] | None = None,
3045
+ parameters: Sequence[parametertools.Parameter | str] | None = None,
3046
+ values: Sequence[float] | None = None,
3047
+ keywords: Sequence[str | None] | None = None,
3048
+ lowers: Sequence[float] | None = None,
3049
+ uppers: Sequence[float] | None = None,
3050
+ model: types.ModuleType | str | None = None,
3051
+ selections: Iterable[selectiontools.Selection | str],
3052
+ product: bool = False,
3053
+ ) -> list[TypeRule]: ...
3054
+
3055
+
3056
+ def make_rules(
3057
+ *,
3058
+ rule: type[TypeRule],
3059
+ calibspecs: CalibSpecs | None = None,
3060
+ names: Sequence[str] | None = None,
3061
+ parameters: Sequence[parametertools.Parameter | str] | None = None,
3062
+ values: Sequence[float] | None = None,
3063
+ keywords: Sequence[str | None] | None = None,
3064
+ lowers: Sequence[float] | None = None,
3065
+ uppers: Sequence[float] | None = None,
3066
+ parametersteps: Sequence1[timetools.PeriodConstrArg | None] = None,
3067
+ model: types.ModuleType | str | None = None,
3068
+ selections: Iterable[selectiontools.Selection | str] | None = None,
3069
+ product: bool = False,
3070
+ ) -> list[TypeRule]:
3071
+ """Conveniently create multiple |Rule| objects at once.
3072
+
3073
+ Please see the main documentation on class |CalibrationInterface| first, from
3074
+ which we borrow the general setup:
3075
+
3076
+ >>> from hydpy.core.testtools import prepare_full_example_2
3077
+ >>> hp, pub, TestIO = prepare_full_example_2()
3078
+ >>> from hydpy import CalibrationInterface, make_rules, nse
3079
+ >>> ci = CalibrationInterface(
3080
+ ... hp=hp,
3081
+ ... targetfunction=lambda: sum(nse(node=node) for node in hp.nodes))
3082
+
3083
+ Here, we show only the supplemental features of function |make_rules| in some
3084
+ brevity.
3085
+
3086
+ Function |make_rules| checks that all given sequences have the same length:
3087
+
3088
+ >>> from hydpy import Replace
3089
+ >>> make_rules(rule=Replace,
3090
+ ... names=["fc", "percmax"],
3091
+ ... parameters=["fc", "percmax"],
3092
+ ... values=[100.0, 5.0],
3093
+ ... keywords=["forest", None],
3094
+ ... lowers=[50.0, 1.0],
3095
+ ... uppers=[200.0],
3096
+ ... parametersteps="1d",
3097
+ ... model="hland_96")
3098
+ Traceback (most recent call last):
3099
+ ...
3100
+ ValueError: When creating rules via function `make_rules`, all given sequences \
3101
+ must be of equal length.
3102
+
3103
+ The separate handling of the specifications of all calibration parameters is
3104
+ error-prone. You can bundle all specifications within a |CalibSpecs| object
3105
+ instead and pass them at once for more safety and convenience:
3106
+
3107
+ >>> from hydpy import CalibSpec, CalibSpecs
3108
+ >>> calibspecs = CalibSpecs(
3109
+ ... CalibSpec(name="fc", default=100.0, keyword="forest", lower=50.0, \
3110
+ upper=200.0),
3111
+ ... CalibSpec(name="percmax", default=5.0, lower=1.0, upper=10.0, \
3112
+ parameterstep="1d"))
3113
+ >>> make_rules(rule=Replace,
3114
+ ... calibspecs=calibspecs,
3115
+ ... parametersteps="1d",
3116
+ ... model="hland_96")[1]
3117
+ Replace(
3118
+ name="percmax",
3119
+ parameter="percmax",
3120
+ value=5.0,
3121
+ lower=1.0,
3122
+ upper=10.0,
3123
+ keyword=None,
3124
+ parameterstep="1d",
3125
+ model="hland_96",
3126
+ selections=("complete",),
3127
+ )
3128
+
3129
+ You are free also to use the individual arguments (e.g. `names`) to override the
3130
+ related specifications defined by the |CalibSpecs| object:
3131
+
3132
+ >>> make_rules(rule=Replace,
3133
+ ... calibspecs=calibspecs,
3134
+ ... names=[name.upper() for name in calibspecs.names],
3135
+ ... parametersteps="1d",
3136
+ ... model="hland_96")[1]
3137
+ Replace(
3138
+ name="PERCMAX",
3139
+ parameter="percmax",
3140
+ value=5.0,
3141
+ lower=1.0,
3142
+ upper=10.0,
3143
+ keyword=None,
3144
+ parameterstep="1d",
3145
+ model="hland_96",
3146
+ selections=("complete",),
3147
+ )
3148
+
3149
+ Function |make_rules| raises the following error if you neither pass a |CalibSpecs|
3150
+ object nor the complete list of individual calibration parameter specifications:
3151
+
3152
+ >>> make_rules(rule=Replace,
3153
+ ... names=["fc", "percmax"],
3154
+ ... parameters=["fc", "percmax"],
3155
+ ... values=[100.0, 5.0],
3156
+ ... keywords=["forest", None],
3157
+ ... lowers=[50.0, 1.0],
3158
+ ... parametersteps="1d",
3159
+ ... model="hland_96")
3160
+ Traceback (most recent call last):
3161
+ ...
3162
+ TypeError: When creating rules via function `make_rules`, you must pass a \
3163
+ `CalibSpecs` object or provide complete information for the following arguments: \
3164
+ names, parameters, values, keywords, lowers, and uppers.
3165
+
3166
+ You can run function |make_rules| in "product mode", meaning that its execution
3167
+ results in distinct |Rule| objects for all combinations of the given calibration
3168
+ parameters and selections:
3169
+
3170
+ >>> make_rules(rule=Replace,
3171
+ ... calibspecs=calibspecs,
3172
+ ... model="hland_96",
3173
+ ... selections=("headwaters", "nonheadwaters"),
3174
+ ... product=True)
3175
+ [Replace(
3176
+ name="fc_headwaters",
3177
+ parameter="fc",
3178
+ value=100.0,
3179
+ lower=50.0,
3180
+ upper=200.0,
3181
+ keyword="forest",
3182
+ parameterstep=None,
3183
+ model="hland_96",
3184
+ selections=("headwaters",),
3185
+ ), Replace(
3186
+ name="percmax_headwaters",
3187
+ parameter="percmax",
3188
+ value=5.0,
3189
+ lower=1.0,
3190
+ upper=10.0,
3191
+ keyword=None,
3192
+ parameterstep="1d",
3193
+ model="hland_96",
3194
+ selections=("headwaters",),
3195
+ ), Replace(
3196
+ name="fc_nonheadwaters",
3197
+ parameter="fc",
3198
+ value=100.0,
3199
+ lower=50.0,
3200
+ upper=200.0,
3201
+ keyword="forest",
3202
+ parameterstep=None,
3203
+ model="hland_96",
3204
+ selections=("nonheadwaters",),
3205
+ ), Replace(
3206
+ name="percmax_nonheadwaters",
3207
+ parameter="percmax",
3208
+ value=5.0,
3209
+ lower=1.0,
3210
+ upper=10.0,
3211
+ keyword=None,
3212
+ parameterstep="1d",
3213
+ model="hland_96",
3214
+ selections=("nonheadwaters",),
3215
+ )]
3216
+
3217
+ Trying to run in "product mode" without defining the target selections results in
3218
+ the following error message:
3219
+
3220
+ >>> make_rules(rule=Replace,
3221
+ ... calibspecs=calibspecs,
3222
+ ... parametersteps="1d",
3223
+ ... model="hland_96",
3224
+ ... product=True)
3225
+ Traceback (most recent call last):
3226
+ ...
3227
+ TypeError: When creating rules via function `make_rules` in "product mode" (with \
3228
+ the argument `product` being `True`), you must supply all target selection objects \
3229
+ via argument `selections`.
3230
+ """
3231
+ if calibspecs is None:
3232
+ if (
3233
+ (names is None) # pylint: disable=too-many-boolean-expressions
3234
+ or (parameters is None)
3235
+ or (values is None)
3236
+ or (keywords is None)
3237
+ or (lowers is None)
3238
+ or (uppers is None)
3239
+ ):
3240
+ raise TypeError(
3241
+ "When creating rules via function `make_rules`, you must pass a "
3242
+ "`CalibSpecs` object or provide complete information for the "
3243
+ "following arguments: names, parameters, values, keywords, lowers, "
3244
+ "and uppers."
3245
+ )
3246
+ else:
3247
+ if names is None:
3248
+ names = calibspecs.names
3249
+ if parameters is None:
3250
+ parameters = calibspecs.names
3251
+ if values is None:
3252
+ values = calibspecs.defaults
3253
+ if keywords is None:
3254
+ keywords = calibspecs.keywords
3255
+ if lowers is None:
3256
+ lowers = calibspecs.lowers
3257
+ if uppers is None:
3258
+ uppers = calibspecs.uppers
3259
+ if parametersteps is None:
3260
+ parametersteps = calibspecs.parametersteps
3261
+ parameters_ = tuple(
3262
+ objecttools.extract(values=parameters, types_=(parametertools.Parameter, str))
3263
+ )
3264
+ if isinstance(parametersteps, str) or not isinstance(parametersteps, Sequence):
3265
+ parametersteps = len(names) * (parametersteps,)
3266
+ if not (
3267
+ len(names)
3268
+ == len(parameters_)
3269
+ == len(lowers)
3270
+ == len(uppers)
3271
+ == len(values)
3272
+ == len(keywords)
3273
+ == len(parametersteps)
3274
+ ):
3275
+ raise ValueError(
3276
+ "When creating rules via function `make_rules`, all given sequences must "
3277
+ "be of equal length."
3278
+ )
3279
+ nmb_parameters = len(parameters_)
3280
+ selections2: Iterable[Iterable[selectiontools.Selection | str] | None]
3281
+ if product:
3282
+ if selections is None:
3283
+ raise TypeError(
3284
+ 'When creating rules via function `make_rules` in "product mode" '
3285
+ "(with the argument `product` being `True`), you must supply all "
3286
+ "target selection objects via argument `selections`."
3287
+ )
3288
+ selections = tuple(selections)
3289
+ names = tuple(
3290
+ f"{par}_{sel}" for sel, par in itertools.product(selections, parameters_)
3291
+ )
3292
+ nmb_selections = len(selections)
3293
+ parameters_ = nmb_selections * tuple(parameters_)
3294
+ lowers = nmb_selections * tuple(lowers)
3295
+ uppers = nmb_selections * tuple(uppers)
3296
+ values = nmb_selections * tuple(values)
3297
+ keywords = nmb_selections * tuple(keywords)
3298
+ parametersteps = nmb_selections * tuple(parametersteps)
3299
+ selections2 = itertools.chain.from_iterable(
3300
+ itertools.repeat((sel,), nmb_parameters) for sel in selections
3301
+ )
3302
+ else:
3303
+ selections2 = itertools.repeat(selections, nmb_parameters)
3304
+ rules = []
3305
+ for (
3306
+ name,
3307
+ parameter,
3308
+ lower,
3309
+ upper,
3310
+ value,
3311
+ keyword,
3312
+ parameterstep,
3313
+ selections_,
3314
+ ) in zip(
3315
+ names,
3316
+ parameters_,
3317
+ lowers,
3318
+ uppers,
3319
+ values,
3320
+ keywords,
3321
+ parametersteps,
3322
+ selections2,
3323
+ ):
3324
+ rules.append(
3325
+ rule(
3326
+ name=name,
3327
+ parameter=parameter,
3328
+ value=value,
3329
+ keyword=keyword,
3330
+ lower=lower,
3331
+ upper=upper,
3332
+ parameterstep=parameterstep,
3333
+ selections=selections_,
3334
+ model=model,
3335
+ )
3336
+ )
3337
+ return rules