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,4674 @@
1
+ """This module provides tools for defining and handling different kinds of parameters
2
+ of hydrological models."""
3
+
4
+ # import...
5
+ # ...from standard library
6
+ from __future__ import annotations
7
+ import builtins
8
+ import contextlib
9
+ import copy
10
+ import inspect
11
+ import itertools
12
+ import textwrap
13
+ import types
14
+ import warnings
15
+
16
+ # ...from site-packages
17
+ import numpy
18
+
19
+ # ...from HydPy
20
+ import hydpy
21
+ from hydpy import config
22
+ from hydpy.core import exceptiontools
23
+ from hydpy.core import filetools
24
+ from hydpy.core import masktools
25
+ from hydpy.core import objecttools
26
+ from hydpy.core import propertytools
27
+ from hydpy.core import timetools
28
+ from hydpy.core import variabletools
29
+ from hydpy.core.typingtools import *
30
+
31
+ # from hydpy.cythons import modelutils # actual download below
32
+
33
+ if TYPE_CHECKING:
34
+ from hydpy.core import auxfiletools
35
+ from hydpy.core import devicetools
36
+ from hydpy.core import modeltools
37
+
38
+
39
+ def trim_kwarg(
40
+ parameter: Parameter,
41
+ name: str,
42
+ value: float,
43
+ lower: float = -numpy.inf,
44
+ upper: float = numpy.inf,
45
+ ) -> float:
46
+ """Helper function for model developers for trimming scalar keyword arguments of
47
+ type |float|.
48
+
49
+ Function |trim_kwarg| works similarly to function |trim| but targets defining
50
+ parameter values via parameter-specific keyword arguments. Due to the individual
51
+ nature of calculating parameter values from keyword arguments, using |trim_kwarg|
52
+ is less standardisable than using |trim|. Hence, model developers must include it
53
+ manually into the `__call__` methods of their |Parameter| subclasses when trimming
54
+ keyword arguments is required.
55
+
56
+ The following tests show that |trim_kwarg| returns the eventually trimmed value,
57
+ and, like |trim|, emits warnings only in case of boundary violations beyond the
58
+ size of pure precision-related artefacts:
59
+
60
+ >>> from hydpy.core.parametertools import Parameter, trim_kwarg
61
+ >>> parameter = Parameter(None)
62
+
63
+ >>> trim_kwarg(parameter, "x", 1.0) == 1.0
64
+ True
65
+
66
+ >>> from hydpy.core.testtools import warn_later
67
+ >>> with warn_later():
68
+ ... trim_kwarg(parameter, "x", 0.0, lower=1.0) == 1.0
69
+ True
70
+ UserWarning: For parameter `parameter` of element `?` the keyword argument `x` \
71
+ with value `0.0` needed to be trimmed to `1.0`.
72
+
73
+ >>> with warn_later():
74
+ ... trim_kwarg(parameter, "x", 2.0, upper=1.0) == 1.0
75
+ True
76
+ UserWarning: For parameter `parameter` of element `?` the keyword argument `x` \
77
+ with value `2.0` needed to be trimmed to `1.0`.
78
+
79
+ >>> x = 1.0 - 1e-15
80
+ >>> x == 1.0
81
+ False
82
+ >>> with warn_later():
83
+ ... trim_kwarg(parameter, "x", x, lower=1.0) == 1.0
84
+ True
85
+
86
+ >>> x = 1.0 + 1e-15
87
+ >>> x == 1.0
88
+ False
89
+ >>> with warn_later():
90
+ ... trim_kwarg(parameter, "x", x, upper=1.0) == 1.0
91
+ True
92
+ """
93
+ gt = variabletools.get_tolerance
94
+ if value < lower:
95
+ if (value + gt(value)) < (lower - gt(lower)):
96
+ _warn_trim_kwarg(parameter, name, value, lower)
97
+ return lower
98
+ if value > upper:
99
+ if (value - gt(value)) > (upper + gt(upper)):
100
+ _warn_trim_kwarg(parameter, name, value, upper)
101
+ return upper
102
+ return value
103
+
104
+
105
+ def _warn_trim_kwarg(
106
+ parameter: Parameter, name: str, oldvalue: float, newvalue: float
107
+ ) -> None:
108
+ warnings.warn(
109
+ f"For parameter {objecttools.elementphrase(parameter)} the keyword argument "
110
+ f"`{name}` with value `{objecttools.repr_(oldvalue)}` needed to be trimmed to "
111
+ f"`{objecttools.repr_(newvalue)}`."
112
+ )
113
+
114
+
115
+ class IntConstant(int):
116
+ """Class for |int| objects with individual docstrings."""
117
+
118
+ def __new__(cls, value):
119
+ const = int.__new__(cls, value)
120
+ const.__doc__ = None
121
+ frame = inspect.currentframe().f_back
122
+ const.__module__ = frame.f_locals.get("__name__")
123
+ return const
124
+
125
+
126
+ class Constants(dict[str, int]):
127
+ """Base class for defining integer constants for a specific model."""
128
+
129
+ value2name: dict[int, str]
130
+ """Mapping from the the values of the constants to their names."""
131
+
132
+ def __init__(self, *args, **kwargs) -> None:
133
+ if not (args or kwargs):
134
+ assert ((frame1 := inspect.currentframe()) is not None) and (
135
+ (frame := frame1.f_back) is not None
136
+ )
137
+ assert isinstance(modulename := frame.f_locals.get("__name__"), str)
138
+ self.__module__ = modulename
139
+ for key, value in frame.f_locals.items():
140
+ if key.isupper() and isinstance(value, IntConstant):
141
+ kwargs[key] = value
142
+ super().__init__(**kwargs)
143
+ self._prepare_docstrings(frame)
144
+ else:
145
+ super().__init__(*args, **kwargs)
146
+ self.value2name = {value: key for key, value in self.items()}
147
+
148
+ def _prepare_docstrings(self, frame: types.FrameType) -> None:
149
+ """Assign docstrings to the constants handled by |Constants| to make them
150
+ available in the interactive mode of Python."""
151
+ if config.USEAUTODOC:
152
+ assert (filename := inspect.getsourcefile(frame)) is not None
153
+ with open(filename, encoding=config.ENCODING) as file_:
154
+ sources = file_.read().split('"""')[2:]
155
+ for code, doc in zip(sources[::2], sources[1::2]):
156
+ code = code.strip()
157
+ key = code.split("\n")[-1].split()[0]
158
+ value = self.get(key)
159
+ if value:
160
+ value.__doc__ = doc
161
+
162
+ def get_sortednames(
163
+ self, *, relevant: Sequence[int] | None = None
164
+ ) -> tuple[str, ...]:
165
+ """Get the lowercase constants' names, sorted by the constants' values.
166
+
167
+ >>> from hydpy.core.parametertools import Constants
168
+ >>> Constants(GRASS=2, TREES=0, WATER=1).get_sortednames()
169
+ ('trees', 'water', 'grass')
170
+
171
+ You can pass the values of relevant constants to exclude the names of the
172
+ remaining constants:
173
+
174
+ >>> Constants(GRASS=2, TREES=0, WATER=1).get_sortednames(relevant=[0, 2])
175
+ ('trees', 'grass')
176
+ """
177
+ rel = set(self.values()) if relevant is None else set(relevant)
178
+ return tuple(n.lower() for v, n in sorted(self.value2name.items()) if v in rel)
179
+
180
+
181
+ class Parameters:
182
+ """Base class for handling all parameters of a specific model.
183
+
184
+ |Parameters| objects handle four subgroups as attributes: the `control`
185
+ subparameters, the `derived` subparameters, the `fixed` subparameters and the
186
+ `solver` subparameters:
187
+
188
+ >>> from hydpy.models.meteo_glob_fao56 import *
189
+ >>> parameterstep("1d")
190
+ >>> assert model.parameters
191
+ >>> assert model.parameters.control
192
+ >>> assert not model.parameters.solver
193
+
194
+ Iterations makes only the non-empty subgroups available, which are actually
195
+ handling |Parameter| objects:
196
+
197
+ >>> for subpars in model.parameters:
198
+ ... print(subpars.name)
199
+ control
200
+ derived
201
+ fixed
202
+ >>> len(model.parameters)
203
+ 3
204
+
205
+ Keyword access provides a type-safe way to query a subgroup via a string:
206
+
207
+ >>> type(model.parameters["control"]).__name__
208
+ 'ControlParameters'
209
+ >>> type(model.parameters["wrong"])
210
+ Traceback (most recent call last):
211
+ ...
212
+ TypeError: There is no parameter subgroup named `wrong`.
213
+ >>> model.parameters["model"]
214
+ Traceback (most recent call last):
215
+ ...
216
+ TypeError: Attribute `model` is of type `Model`, which is not a subtype of class \
217
+ `SubParameters`.
218
+ """
219
+
220
+ model: modeltools.Model
221
+ control: SubParameters
222
+ derived: SubParameters
223
+ fixed: SubParameters
224
+ solver: SubParameters
225
+
226
+ def __init__(self, kwargs):
227
+ self.model = kwargs.get("model")
228
+ self.control = self._prepare_subpars("control", kwargs)
229
+ self.derived = self._prepare_subpars("derived", kwargs)
230
+ self.fixed = self._prepare_subpars("fixed", kwargs)
231
+ self.solver = self._prepare_subpars("solver", kwargs)
232
+
233
+ def _prepare_subpars(self, shortname, kwargs):
234
+ fullname = f"{shortname.capitalize()}Parameters"
235
+ cls = kwargs.get(fullname, type(fullname, (SubParameters,), {"CLASSES": ()}))
236
+ return cls(
237
+ self,
238
+ getattr(kwargs.get("cythonmodule"), fullname, None),
239
+ kwargs.get("cymodel"),
240
+ )
241
+
242
+ def update(self, ignore_errors: bool = False) -> None:
243
+ """Call method |Parameter.update| of all "secondary" parameters.
244
+
245
+ Directly after initialisation, neither the primary (`control`) parameters nor
246
+ the secondary (`derived`) parameters of application model |meteo_glob_fao56|
247
+ are ready for usage:
248
+
249
+ >>> from hydpy.models.meteo_glob_fao56 import *
250
+ >>> parameterstep("1d")
251
+ >>> simulationstep("1d")
252
+ >>> derived
253
+ doy(?)
254
+ moy(?)
255
+ hours(?)
256
+ days(?)
257
+ sct(?)
258
+ utclongitude(?)
259
+ latituderad(?)
260
+
261
+ Trying to update the values of the secondary parameters while the primary ones
262
+ are still not defined raises errors like the following:
263
+
264
+ >>> model.parameters.update()
265
+ Traceback (most recent call last):
266
+ ...
267
+ hydpy.core.exceptiontools.AttributeNotReady: While trying to update parameter \
268
+ `doy` of element `?`, the following error occurred: An Indexer object has been asked \
269
+ for an `dayofyear` array. Such an array has neither been determined yet nor can it \
270
+ be determined automatically at the moment. Either define an `dayofyear` array \
271
+ manually and pass it to the Indexer object, or make a proper Timegrids object \
272
+ available within the pub module.
273
+
274
+ >>> from hydpy import pub
275
+ >>> pub.timegrids = "2000-01-30", "2000-02-04", "1d"
276
+ >>> model.parameters.update()
277
+ Traceback (most recent call last):
278
+ ...
279
+ hydpy.core.exceptiontools.AttributeNotReady: While trying to update parameter \
280
+ `latituderad` of element `?`, the following error occurred: While trying to multiply \
281
+ variable `latitude` and `float` instance `0.017453`, the following error occurred: \
282
+ For variable `latitude`, no value has been defined so far.
283
+
284
+ With a defined |Timegrids| object and proper values both for parameters
285
+ |meteo_control.Latitude| and |meteo_control.Longitude|, updating the derived
286
+ parameters succeeds:
287
+
288
+ >>> latitude(50.0)
289
+ >>> longitude(10.0)
290
+ >>> model.parameters.update()
291
+ >>> derived
292
+ doy(29, 30, 31, 32, 33)
293
+ moy(0, 0, 1, 1, 1)
294
+ hours(24.0)
295
+ days(1.0)
296
+ sct(12.0)
297
+ utclongitude(15)
298
+ latituderad(0.872665)
299
+
300
+ .. testsetup::
301
+
302
+ >>> del pub.timegrids
303
+ """
304
+ for subpars in self.secondary_subpars:
305
+ for par in subpars:
306
+ try:
307
+ par.update()
308
+ except BaseException:
309
+ if not ignore_errors:
310
+ objecttools.augment_excmessage(
311
+ f"While trying to update parameter "
312
+ f"{objecttools.elementphrase(par)}"
313
+ )
314
+
315
+ def verify(self) -> None:
316
+ """Call method |Variable.verify| of all |Parameter| objects handled by the
317
+ actual model.
318
+
319
+ When calling method |Parameters.verify| directly after initialising model
320
+ |meteo_glob_fao56| (without using default values), it raises a |RuntimeError|
321
+ due to the undefined value of control parameter |meteo_control.Latitude|:
322
+
323
+ >>> from hydpy.models.meteo_glob_fao56 import *
324
+ >>> parameterstep("1d")
325
+ >>> simulationstep("1d")
326
+ >>> model.parameters.verify()
327
+ Traceback (most recent call last):
328
+ ...
329
+ RuntimeError: For variable `latitude`, 1 required value has not been set yet: \
330
+ latitude(?).
331
+
332
+ Assigning a value to |meteo_control.Latitude| is not sufficient:
333
+
334
+ >>> model.parameters.control.latitude(50.0)
335
+ >>> model.parameters.verify()
336
+ Traceback (most recent call last):
337
+ ...
338
+ RuntimeError: For variable `longitude`, 1 required value has not been set \
339
+ yet: longitude(?).
340
+
341
+ After also defining suitable values for all remaining control parameters, the
342
+ derived parameters are still not ready:
343
+
344
+ >>> model.parameters.control.longitude(10.0)
345
+ >>> model.parameters.control.angstromconstant(0.25)
346
+ >>> model.parameters.control.angstromfactor(0.5)
347
+ >>> model.parameters.verify()
348
+ Traceback (most recent call last):
349
+ ...
350
+ hydpy.core.exceptiontools.AttributeNotReady: Shape information for variable \
351
+ `doy` can only be retrieved after it has been defined.
352
+
353
+ After updating the derived parameters (which requires preparing a |Timegrids|
354
+ object first), method |Parameters.verify| has no reason to complain anymore:
355
+
356
+ >>> from hydpy import pub
357
+ >>> pub.timegrids = "2000-01-30", "2000-02-04", "1d"
358
+ >>> model.parameters.update()
359
+ >>> model.parameters.verify()
360
+
361
+ .. testsetup::
362
+
363
+ >>> del pub.timegrids
364
+ """
365
+ for subpars in self:
366
+ for par in subpars:
367
+ par.verify()
368
+
369
+ @property
370
+ def secondary_subpars(self) -> Iterator[SubParameters]:
371
+ """Iterate through all subgroups of "secondary" parameters.
372
+
373
+ These secondary parameter subgroups are the `derived` parameters and the
374
+ `solver` parameters at the moment:
375
+
376
+ >>> from hydpy.models.meteo_glob_fao56 import *
377
+ >>> parameterstep("1d")
378
+ >>> for subpars in model.parameters.secondary_subpars:
379
+ ... print(subpars.name)
380
+ derived
381
+ solver
382
+ """
383
+ yield self.derived
384
+ yield self.solver
385
+
386
+ def __getitem__(self, item: str) -> SubParameters:
387
+ try:
388
+ subpars = getattr(self, item)
389
+ except AttributeError:
390
+ raise TypeError(f"There is no parameter subgroup named `{item}`.") from None
391
+ if isinstance(subpars, SubParameters):
392
+ return subpars
393
+ raise TypeError(
394
+ f"Attribute `{item}` is of type `{type(subpars).__name__}`, which is not "
395
+ f"a subtype of class `SubParameters`."
396
+ )
397
+
398
+ def __iter__(self) -> Iterator[SubParameters]:
399
+ for subpars in (self.control, self.derived, self.fixed, self.solver):
400
+ if subpars:
401
+ yield subpars
402
+
403
+ def __len__(self):
404
+ return sum(1 for _ in self)
405
+
406
+ def __bool__(self) -> bool:
407
+ return any(pars for pars in self)
408
+
409
+
410
+ class FastAccessParameter(variabletools.FastAccess):
411
+ """Used as a surrogate for typed Cython classes handling parameters
412
+ when working in pure Python mode."""
413
+
414
+
415
+ class SubParameters(
416
+ variabletools.SubVariables[Parameters, "Parameter", FastAccessParameter]
417
+ ):
418
+ '''Base class for handling subgroups of model parameters.
419
+
420
+ When trying to implement a new model, one has to define its
421
+ specific |Parameter| subclasses. Currently, the HydPy framework
422
+ distinguishes between control parameters, derived parameters,
423
+ fixed parameters, and solver parameters. Each |Parameter| subclass is
424
+ a member of a collection class derived from |SubParameters|, called
425
+ "ControlParameters", "DerivedParameters", "FixedParameters", or
426
+ "SolverParameters", respectively. Indicate membership by putting the
427
+ parameter subclasses into the |tuple| "CLASSES":
428
+
429
+ >>> from hydpy.core.parametertools import Parameter, SubParameters
430
+ >>> class Par2(Parameter):
431
+ ... """Parameter 2 [-]."""
432
+ ... NDIM = 1
433
+ ... TYPE = float
434
+ ... TIME = None
435
+ >>> class Par1(Parameter):
436
+ ... """Parameter 1 [-]."""
437
+ ... NDIM = 1
438
+ ... TYPE = float
439
+ ... TIME = None
440
+ >>> class ControlParameters(SubParameters):
441
+ ... """Control Parameters."""
442
+ ... CLASSES = (Par2,
443
+ ... Par1)
444
+
445
+ The order within the tuple determines the order of iteration:
446
+
447
+ >>> control = ControlParameters(None)
448
+ >>> control
449
+ par2(?)
450
+ par1(?)
451
+
452
+ Each |SubParameters| object has a `fastaccess` attribute. When
453
+ working in pure Python mode, this is an instance of class
454
+ |FastAccessParameter|:
455
+
456
+ >>> from hydpy import classname, prepare_model, pub
457
+ >>> with pub.options.usecython(False):
458
+ ... model = prepare_model("lland_dd")
459
+ >>> classname(model.parameters.control.fastaccess)
460
+ 'FastAccessParameter'
461
+
462
+ When working in Cython mode (which is the default mode and much
463
+ faster), `fastaccess` is an object of a Cython extension class
464
+ specialised for the respective model and sequence group:
465
+
466
+ >>> with pub.options.usecython(True):
467
+ ... model = prepare_model("lland_dd")
468
+ >>> classname(model.parameters.control.fastaccess)
469
+ 'ControlParameters'
470
+ '''
471
+
472
+ pars: Parameters
473
+ _cymodel: CyModelProtocol | None
474
+ _CLS_FASTACCESS_PYTHON = FastAccessParameter
475
+
476
+ def __init__(
477
+ self,
478
+ master: Parameters,
479
+ cls_fastaccess: type[FastAccessParameter] | None = None,
480
+ cymodel: CyModelProtocol | None = None,
481
+ ):
482
+ self.pars = master
483
+ self._cymodel = cymodel
484
+ super().__init__(master=master, cls_fastaccess=cls_fastaccess)
485
+
486
+ def _init_fastaccess(self) -> None:
487
+ super()._init_fastaccess()
488
+ if self._cls_fastaccess and self._cymodel:
489
+ setattr(self._cymodel.parameters, self.name, self.fastaccess)
490
+
491
+ @property
492
+ def name(self) -> str:
493
+ """The class name in lowercase letters omitting the last ten characters
494
+ ("parameters").
495
+
496
+ >>> from hydpy.core.parametertools import SubParameters
497
+ >>> class ControlParameters(SubParameters):
498
+ ... CLASSES = ()
499
+ >>> ControlParameters(None).name
500
+ 'control'
501
+ """
502
+ return type(self).__name__[:-10].lower()
503
+
504
+
505
+ class Keyword(NamedTuple):
506
+ """Helper class to describe parameter-specific keyword arguments for defining
507
+ values by "calling" a parameter object."""
508
+
509
+ name: str
510
+ """The keyword argument's name."""
511
+ type_: type[float | int] = float
512
+ """The keyword argument's type (equivalent to the |Variable.TYPE| attribute of
513
+ class |Variable|)."""
514
+ time: bool | None = None
515
+ """Type of the keyword argument's time dependency (equivalent to the
516
+ |Parameter.TIME| attribute of class |Parameter|).
517
+ """
518
+ span: tuple[float | None, float | None] = (None, None)
519
+ """The keyword argument's lower and upper boundary (equivalent to the
520
+ |Variable.SPAN| attribute of class |Variable|).
521
+ """
522
+
523
+
524
+ class KeywordArgumentsError(RuntimeError):
525
+ """A specialised |RuntimeError| raised by class |KeywordArguments|."""
526
+
527
+
528
+ class KeywordArguments(Generic[T]):
529
+ """A handler for the keyword arguments of the instances of specific |Parameter|
530
+ subclasses.
531
+
532
+ Class |KeywordArguments| is a rather elaborate feature of *HydPy* primarily
533
+ thought for framework developers. One possible use-case for (advanced)
534
+ *HydPy* users is writing polished auxiliary control files. When dealing with
535
+ such a problem, have a look on method |KeywordArguments.extend|.
536
+
537
+ The purpose of class |KeywordArguments| is to simplify handling instances of
538
+ |Parameter| subclasses which allow setting values by calling them with keyword
539
+ arguments. When useful, instances of |Parameter| subclasses should return a
540
+ valid |KeywordArguments| object via property |Parameter.keywordarguments|.
541
+ This object should contain the keyword arguments that, when passed to the same
542
+ parameter instance or another parameter instance of the same type, sets it into
543
+ an equal state. This is best explained by the following example based on
544
+ parameter |lland_control.TRefT| of application model |lland_dd| (see the
545
+ documentation on property |ZipParameter.keywordarguments| of class |ZipParameter|
546
+ for additional information):
547
+
548
+ >>> from hydpy.models.lland_dd import *
549
+ >>> parameterstep()
550
+ >>> nhru(4)
551
+ >>> lnk(ACKER, LAUBW, WASSER, ACKER)
552
+ >>> treft(acker=2.0, laubw=1.0)
553
+ >>> treft.keywordarguments
554
+ KeywordArguments(acker=2.0, laubw=1.0)
555
+
556
+ You can initialise a |KeywordArguments| object on your own:
557
+
558
+ >>> from hydpy import KeywordArguments
559
+ >>> kwargs1 = KeywordArguments(acker=3.0, laubw=2.0, nadelw=1.0)
560
+ >>> kwargs1
561
+ KeywordArguments(acker=3.0, laubw=2.0, nadelw=1.0)
562
+
563
+ After preparing a |KeywordArguments| object, it is "valid" by default:
564
+
565
+ >>> kwargs1.valid
566
+ True
567
+
568
+ Pass |False| as a positional argument to the constructor if you want your
569
+ |KeywordArguments| object to be invalid at first:
570
+
571
+ >>> kwargs2 = KeywordArguments(False)
572
+ >>> kwargs2
573
+ KeywordArguments()
574
+ >>> kwargs2.valid
575
+ False
576
+
577
+ Flag |KeywordArguments.valid|, for example, helps to distinguish between empty
578
+ objects that are okay to be empty and those that are not. When we, for example,
579
+ set all hydrological response units to land-use type |lland_constants.WASSER|
580
+ (water), parameter |lland_control.TRefT| returns the following valid
581
+ |KeywordArguments| object, as its values do not need to be defined for water areas:
582
+
583
+ >>> lnk(WASSER)
584
+ >>> treft
585
+ treft(nan)
586
+ >>> treft.keywordarguments
587
+ KeywordArguments()
588
+ >>> treft.keywordarguments.valid
589
+ True
590
+
591
+ Class |KeywordArguments| supports features like iteration but raises the
592
+ exception |KeywordArgumentsError| when trying to iterate an invalid object:
593
+
594
+ >>> for keyword, argument in kwargs1:
595
+ ... print(keyword, argument)
596
+ acker 3.0
597
+ laubw 2.0
598
+ nadelw 1.0
599
+
600
+ >>> for keyword, argument in kwargs2:
601
+ ... print(keyword, argument)
602
+ Traceback (most recent call last):
603
+ ...
604
+ hydpy.core.parametertools.KeywordArgumentsError: Cannot iterate an invalid \
605
+ `KeywordArguments` object.
606
+
607
+ The same holds when trying to check if a specific keyword-value item is available:
608
+
609
+ >>> ("acker", 3.0) in kwargs1
610
+ True
611
+ >>> ("laubw", 3.0) in kwargs1
612
+ False
613
+ >>> ("?", "???") in kwargs1
614
+ False
615
+ >>> ("laubw", 3.0) in kwargs2
616
+ Traceback (most recent call last):
617
+ ...
618
+ hydpy.core.parametertools.KeywordArgumentsError: Cannot check if an item is \
619
+ defined by an invalid `KeywordArguments` object.
620
+
621
+ However, keyword access is always possible:
622
+
623
+ >>> kwargs2["laubw"] = 3.0
624
+
625
+ >>> kwargs2["laubw"]
626
+ 3.0
627
+
628
+ >>> del kwargs2["laubw"]
629
+
630
+ >>> kwargs2["laubw"]
631
+ Traceback (most recent call last):
632
+ ...
633
+ KeyError: 'The current `KeywordArguments` object does not handle an argument \
634
+ under the keyword `laubw`.'
635
+
636
+ >>> del kwargs2["laubw"]
637
+ Traceback (most recent call last):
638
+ ...
639
+ KeyError: 'The current `KeywordArguments` object does not handle an argument \
640
+ under the keyword `laubw`.'
641
+
642
+ Two |KeywordArguments| objects are considered equal if they have the same
643
+ validity state, the same length, and if all items are equal:
644
+
645
+ >>> KeywordArguments(True) == KeywordArguments(False)
646
+ False
647
+ >>> KeywordArguments(x=1) == KeywordArguments(x=1, y=2)
648
+ False
649
+ >>> KeywordArguments(x=1, y=2) == KeywordArguments(x=1, y=3)
650
+ False
651
+ >>> KeywordArguments(x=1, y=2) == KeywordArguments(x=1, y=2)
652
+ True
653
+
654
+ You can also compare with other objects (always |False|) and use the
655
+ "!=" operator:
656
+
657
+ >>> KeywordArguments() == "test"
658
+ False
659
+ >>> KeywordArguments(x=1, y=2) != KeywordArguments(x=1, y=2)
660
+ False
661
+ """
662
+
663
+ valid: bool
664
+ """Flag indicating whether the actual |KeywordArguments| object is valid or not."""
665
+ _name2value: dict[str, T]
666
+
667
+ def __init__(self, __valid: bool = True, **keywordarguments: T) -> None:
668
+ self.valid = __valid
669
+ self._name2value = copy.deepcopy(keywordarguments)
670
+
671
+ def add(self, name: str, value: T) -> None:
672
+ """Add a keyword argument.
673
+
674
+ Method |KeywordArguments.add| works both for valid and invalid
675
+ |KeywordArguments| objects without changing their validity status:
676
+
677
+ >>> from hydpy import KeywordArguments
678
+ >>> kwargs = KeywordArguments()
679
+ >>> kwargs.add("one", 1)
680
+ >>> kwargs.valid = False
681
+ >>> kwargs.add("two", 2)
682
+ >>> kwargs
683
+ KeywordArguments(one=1, two=2)
684
+
685
+ It raises the following error when (possibly accidentally) trying to
686
+ overwrite an existing keyword argument:
687
+
688
+ >>> kwargs.add("one", 3)
689
+ Traceback (most recent call last):
690
+ ...
691
+ hydpy.core.parametertools.KeywordArgumentsError: Cannot add argument value \
692
+ `3` of type `int` to the current `KeywordArguments` object as it already handles \
693
+ the unequal argument `1` under the keyword `one`.
694
+
695
+ On the other hand, redefining the save value causes no harm and thus
696
+ does not trigger an exception:
697
+
698
+ >>> kwargs.add("one", 1)
699
+ >>> kwargs
700
+ KeywordArguments(one=1, two=2)
701
+ """
702
+ if name in self._name2value:
703
+ if self._name2value[name] != value:
704
+ raise KeywordArgumentsError(
705
+ f"Cannot add argument {objecttools.value_of_type(value)} to the "
706
+ f"current `{type(self).__name__}` object as it already handles "
707
+ f"the unequal argument `{self._name2value[name]}` under the "
708
+ f"keyword `{name}`."
709
+ )
710
+ else:
711
+ self._name2value[name] = value
712
+
713
+ def subset_of(self, other: KeywordArguments[T]) -> bool:
714
+ """Check if the actual |KeywordArguments| object is a subset of the given one.
715
+
716
+ First, we define the following (valid) |KeywordArguments| objects:
717
+
718
+ >>> from hydpy import KeywordArguments
719
+ >>> kwargs1 = KeywordArguments(a=1, b=2)
720
+ >>> kwargs2 = KeywordArguments(a= 1, b=2, c=3)
721
+ >>> kwargs3 = KeywordArguments(a= 1, b=3)
722
+
723
+ Method |KeywordArguments.subset_of| requires that the keywords handled by
724
+ the left |KeywordArguments| object form a subset of the keywords of the
725
+ right |KeywordArguments| object:
726
+
727
+ >>> kwargs1.subset_of(kwargs2)
728
+ True
729
+ >>> kwargs2.subset_of(kwargs1)
730
+ False
731
+
732
+ Additionally, all values corresponding to the union of the relevant keywords
733
+ must be equal:
734
+
735
+ >>> kwargs1.subset_of(kwargs3)
736
+ False
737
+
738
+ If at least one of both |KeywordArguments| is invalid, method
739
+ |KeywordArguments.subset_of| generally returns |False|:
740
+
741
+ >>> kwargs2.valid = False
742
+ >>> kwargs1.subset_of(kwargs2)
743
+ False
744
+ >>> kwargs2.subset_of(kwargs1)
745
+ False
746
+ >>> kwargs2.subset_of(kwargs2)
747
+ False
748
+ """
749
+ if self.valid and other.valid:
750
+ for item in self._name2value.items():
751
+ if item not in other:
752
+ return False
753
+ return True
754
+ return False
755
+
756
+ def extend(
757
+ self,
758
+ parametertype: type[Parameter],
759
+ elements: Iterable[devicetools.Element],
760
+ raise_exception: bool = True,
761
+ ) -> None:
762
+ """Extend the currently available keyword arguments based on the parameters
763
+ of the given type handled by the given elements.
764
+
765
+ Sometimes (for example, when writing auxiliary control files) one is
766
+ interested in a superset of all keyword arguments related to a specific
767
+ |Parameter| type relevant for certain |Element| objects. To show how
768
+ method |KeywordArguments.extend| can help in such cases, we make use of
769
+ the `HydPy-H-Lahn` example project:
770
+
771
+ >>> from hydpy.core.testtools import prepare_full_example_2
772
+ >>> hp, pub, TestIO = prepare_full_example_2()
773
+
774
+ First, we prepare an empty |KeywordArguments| object:
775
+
776
+ >>> from hydpy import KeywordArguments
777
+ >>> kwargs = KeywordArguments()
778
+ >>> kwargs
779
+ KeywordArguments()
780
+
781
+ When passing a |Parameter| subclass (in our example
782
+ |hland_control.IcMax|) and some |Element| objects (at first the headwater
783
+ elements, which handle instances of application model |hland_96|), method
784
+ |KeywordArguments.extend| collects their relevant keyword arguments:
785
+
786
+ >>> from hydpy.models.hland.hland_control import IcMax
787
+ >>> kwargs.extend(IcMax, pub.selections.headwaters.elements)
788
+ >>> kwargs
789
+ KeywordArguments(field=1.0, forest=1.5)
790
+
791
+ Applying method |KeywordArguments.extend| also on the non-headwaters does
792
+ not change anything, as the values of parameter |hland_control.IcMax| are
793
+ consistent for the whole Lahn river basin:
794
+
795
+ >>> kwargs.extend(IcMax, pub.selections.nonheadwaters.elements)
796
+ >>> kwargs
797
+ KeywordArguments(field=1.0, forest=1.5)
798
+
799
+ Next, we change the interception capacity of forests in one subcatchment:
800
+
801
+ >>> icmax = hp.elements.land_lahn_leun.model.parameters.control.icmax
802
+ >>> icmax(field=1.0, forest=2.0)
803
+
804
+ Re-applying method |KeywordArguments.extend| now raises the following error:
805
+
806
+ >>> kwargs.extend(IcMax, pub.selections.nonheadwaters.elements)
807
+ Traceback (most recent call last):
808
+ ...
809
+ hydpy.core.parametertools.KeywordArgumentsError: While trying to extend the \
810
+ keyword arguments based on the available `IcMax` parameter objects, the following \
811
+ error occurred: While trying to add the keyword arguments for element \
812
+ `land_lahn_leun`, the following error occurred: Cannot add argument value `2.0` of \
813
+ type `float64` to the current `KeywordArguments` object as it already handles the \
814
+ unequal argument `1.5` under the keyword `forest`.
815
+
816
+ The old keywords arguments and the validity status remain unchanged:
817
+
818
+ >>> kwargs
819
+ KeywordArguments(field=1.0, forest=1.5)
820
+ >>> kwargs.valid
821
+ True
822
+
823
+ When we modify the same |hland_control.IcMax| parameter object in a way
824
+ that it cannot return a valid |KeywordArguments| object anymore, we get
825
+ the following error message:
826
+
827
+ >>> icmax(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)
828
+ >>> kwargs.extend(IcMax, pub.selections.nonheadwaters.elements)
829
+ Traceback (most recent call last):
830
+ ...
831
+ hydpy.core.parametertools.KeywordArgumentsError: While trying to extend the \
832
+ keyword arguments based on the available `IcMax` parameter objects, the following \
833
+ error occurred: While trying to add the keyword arguments for element \
834
+ `land_lahn_leun`, the following error occurred: Cannot iterate an invalid \
835
+ `KeywordArguments` object.
836
+
837
+ When setting the `raise_exception` argument to |False|, method
838
+ |KeywordArguments.extend| handles such errors internally and, instead of
839
+ raising an error invalidates the actual |KeywordArguments| object:
840
+
841
+ >>> kwargs.extend(
842
+ ... IcMax, pub.selections.nonheadwaters.elements, raise_exception=False)
843
+ >>> kwargs
844
+ KeywordArguments()
845
+ >>> kwargs.valid
846
+ False
847
+
848
+ Trying to extend an invalid |KeywordArguments| object by default also
849
+ raises an exception of type |KeywordArgumentsError|:
850
+
851
+ >>> kwargs.extend(IcMax, pub.selections.headwaters.elements)
852
+ Traceback (most recent call last):
853
+ ...
854
+ hydpy.core.parametertools.KeywordArgumentsError: While trying to extend the \
855
+ keyword arguments based on the available `IcMax` parameter objects, the following \
856
+ error occurred: The `KeywordArguments` object is invalid.
857
+
858
+ When setting `raise_exception` to |False| instead, nothing happens:
859
+
860
+ >>> kwargs.extend(IcMax, pub.selections.headwaters.elements, \
861
+ raise_exception=False)
862
+ >>> kwargs
863
+ KeywordArguments()
864
+ >>> kwargs.valid
865
+ False
866
+ """
867
+ name_parameter = parametertype.name
868
+ try:
869
+ if not self.valid:
870
+ if raise_exception:
871
+ raise KeywordArgumentsError(
872
+ f"The `{type(self).__name__}` object is invalid."
873
+ )
874
+ return
875
+ for element in elements:
876
+ try:
877
+ control = element.model.parameters.control
878
+ other = control[name_parameter].keywordarguments
879
+ for name_keyword, value in other:
880
+ self.add(name_keyword, value)
881
+ except KeywordArgumentsError:
882
+ if raise_exception:
883
+ objecttools.augment_excmessage(
884
+ f"While trying to add the keyword arguments for "
885
+ f"element `{objecttools.devicename(element)}`"
886
+ )
887
+ self.valid = False
888
+ self._name2value.clear()
889
+ return
890
+ except BaseException:
891
+ objecttools.augment_excmessage(
892
+ f"While trying to extend the keyword arguments based on the "
893
+ f"available `{parametertype.__name__}` parameter objects"
894
+ )
895
+ return
896
+
897
+ def clear(self) -> None:
898
+ """Clear the current object contents and set attribute |KeywordArguments.valid|
899
+ to |False|.
900
+
901
+ >>> from hydpy import KeywordArguments
902
+ >>> kwa =KeywordArguments(x=1, y=2)
903
+ >>> kwa
904
+ KeywordArguments(x=1, y=2)
905
+ >>> kwa.valid
906
+ True
907
+ >>> kwa.clear()
908
+ >>> kwa
909
+ KeywordArguments()
910
+ >>> kwa.valid
911
+ True
912
+ """
913
+ self._name2value.clear()
914
+
915
+ def __getitem__(self, key: str) -> T:
916
+ try:
917
+ return self._name2value[key]
918
+ except KeyError:
919
+ raise KeyError(
920
+ f"The current `{type(self).__name__}` object does not handle an "
921
+ f"argument under the keyword `{key}`."
922
+ ) from None
923
+
924
+ def __setitem__(self, key: str, value: T) -> None:
925
+ self._name2value[key] = value
926
+
927
+ def __delitem__(self, key: str) -> None:
928
+ try:
929
+ del self._name2value[key]
930
+ except KeyError:
931
+ raise KeyError(
932
+ f"The current `{type(self).__name__}` object does not handle an "
933
+ f"argument under the keyword `{key}`."
934
+ ) from None
935
+
936
+ def __contains__(self, item: tuple[str, T]) -> bool:
937
+ if not self.valid:
938
+ raise KeywordArgumentsError(
939
+ f"Cannot check if an item is defined by an invalid "
940
+ f"`{type(self).__name__}` object."
941
+ )
942
+ if item[0] in self._name2value:
943
+ return self._name2value[item[0]] == item[1]
944
+ return False
945
+
946
+ def __len__(self) -> int:
947
+ return len(self._name2value)
948
+
949
+ def __iter__(self) -> Iterator[tuple[str, T]]:
950
+ if not self.valid:
951
+ raise KeywordArgumentsError(
952
+ f"Cannot iterate an invalid `{type(self).__name__}` object."
953
+ )
954
+ yield from self._name2value.items()
955
+
956
+ def __eq__(self, other: object) -> bool:
957
+ if isinstance(other, KeywordArguments):
958
+ if self.valid != other.valid:
959
+ return False
960
+ if len(self) != len(other):
961
+ return False
962
+ for item in self:
963
+ if item not in other:
964
+ return False
965
+ return True
966
+ return False
967
+
968
+ def __ne__(self, other: object) -> bool:
969
+ return not self.__eq__(other)
970
+
971
+ def __repr__(self) -> str:
972
+ return objecttools.apply_black(type(self).__name__, **self._name2value)
973
+
974
+
975
+ class Parameter(variabletools.Variable):
976
+ """Base class for model parameters.
977
+
978
+ In *HydPy*, each kind of model parameter is represented by a unique
979
+ class. In almost all cases, you should derive such a class,
980
+ directly or indirectly, from class |Parameter|, which provides
981
+ all necessary features that assure the new parameter class works
982
+ well when applied in different contexts.
983
+
984
+ In most cases, the functionalities of class |Parameter| are sufficient
985
+ for deriving new classes without doing any real coding (writing
986
+ new or extending existing methods). However, one sometimes prefers
987
+ to do some extensions to simplify the usage of the parameter class.
988
+ Before doing so on your own, have a look at the specialised
989
+ subclasses already available in module |parametertools|. One
990
+ example is class |SeasonalParameter|, which allows defining
991
+ parameter values that vary seasonally (e.g. the leaf area index).
992
+
993
+ Class |Parameter| itself extends class |Variable|. Hence, all
994
+ model-specific |Parameter| subclasses must define both a value
995
+ dimensionality and a type via class constants `NDIM` and `TYPE`,
996
+ respectively. Additionally, one has to define the class constant
997
+ `TIME`, telling how parameter values depend on the simulation
998
+ step size (see methods |Parameter.get_timefactor| and
999
+ |Parameter.apply_timefactor|). Also, model developers might find
1000
+ it useful to define initial default values via `INIT` or minimum
1001
+ and maximum values via `SPAN`:
1002
+
1003
+ .. testsetup::
1004
+
1005
+ >>> from hydpy import pub
1006
+ >>> del pub.options.simulationstep
1007
+
1008
+ Let us first prepare a new parameter class without time-dependency
1009
+ (indicated by assigning |None|) and initialise it:
1010
+
1011
+ >>> from hydpy.core.parametertools import Parameter
1012
+ >>> class Par(Parameter):
1013
+ ... NDIM = 0
1014
+ ... TYPE = float
1015
+ ... TIME = None
1016
+ ... SPAN = 0.0, 5.0
1017
+ >>> par = Par(None)
1018
+
1019
+ As described in the documentation on base class |Variable|, one
1020
+ can directly assign values via property |Variable.value|:
1021
+
1022
+ >>> par.value = 6.0
1023
+ >>> par
1024
+ par(6.0)
1025
+
1026
+ For |Parameter| objects, there is an alternative way to define
1027
+ new values, which should be preferred in most cases (especially
1028
+ when writing control files), by "calling" the parameter with
1029
+ suitable arguments. For example, this offers the advantage of
1030
+ automatical trimming. In the example above, the assigned value
1031
+ (6.0) violates the upper bound (5.0) of our test parameter. When
1032
+ using the "call" syntax, the wrong value is corrected immediately:
1033
+
1034
+ >>> from hydpy import pub
1035
+ >>> from hydpy.core.testtools import warn_later
1036
+ >>> with pub.options.warntrim(True), warn_later():
1037
+ ... par(7.0)
1038
+ UserWarning: For variable `par` at least one value needed to be trimmed. \
1039
+ The old and the new value(s) are `7.0` and `5.0`, respectively.
1040
+ >>> par
1041
+ par(5.0)
1042
+
1043
+ .. testsetup::
1044
+
1045
+ >>> try:
1046
+ ... del model
1047
+ ... except NameError:
1048
+ ... pass
1049
+
1050
+ The "call" syntax provides some additional features and related
1051
+ error messages. Use the `auxfile` keyword argument to tell that
1052
+ another control file defines the actual parameter value. Note
1053
+ that you cannot use this feature in the interactive mode:
1054
+
1055
+ >>> par(auxfile="test")
1056
+ Traceback (most recent call last):
1057
+ ...
1058
+ RuntimeError: While trying to extract information for parameter `par` \
1059
+ from file `test`, the following error occurred: Cannot determine the \
1060
+ corresponding model. Use the `auxfile` keyword in usual parameter \
1061
+ control files only.
1062
+
1063
+ Also, note that you cannot combine the `auxfile` keyword with any other keyword:
1064
+
1065
+ >>> par(auxfile="test", x1=1, x2=2, x3=3)
1066
+ Traceback (most recent call last):
1067
+ ...
1068
+ ValueError: It is not allowed to combine keyword `auxfile` with other \
1069
+ keywords, but for parameter `par` of element `?` also the following keywords \
1070
+ are used: x1, x2, and x3.
1071
+
1072
+ Some |Parameter| subclasses support other keyword arguments.
1073
+ The standard error message for unsupported arguments is the following:
1074
+
1075
+ >>> par(wrong=1.0)
1076
+ Traceback (most recent call last):
1077
+ ...
1078
+ NotImplementedError: The value(s) of parameter `par` of element `?` \
1079
+ could not be set based on the given keyword arguments.
1080
+
1081
+ Passing a wrong number of positional arguments results in the
1082
+ following error:
1083
+
1084
+ >>> par(1.0, 2.0)
1085
+ Traceback (most recent call last):
1086
+ ...
1087
+ TypeError: While trying to set the value(s) of variable `par`, the \
1088
+ following error occurred: The given value `[1. 2.]` cannot be converted \
1089
+ to type `float`.
1090
+
1091
+ Passing no argument or both positional and keyword arguments are also
1092
+ disallowed:
1093
+
1094
+ >>> par()
1095
+ Traceback (most recent call last):
1096
+ ...
1097
+ ValueError: For parameter `par` of element `?` neither a positional \
1098
+ nor a keyword argument is given.
1099
+
1100
+ >>> par(1.0, auxfile="test")
1101
+ Traceback (most recent call last):
1102
+ ...
1103
+ ValueError: For parameter `par` of element `?` both positional and \
1104
+ keyword arguments are given, which is ambiguous.
1105
+
1106
+ Our next |Parameter| test class is a little more complicated, as it
1107
+ handles a (1-dimensional) vector of time-dependent values (indicated
1108
+ by setting the class attribute `TIME` to |True|):
1109
+
1110
+ >>> from hydpy import print_vector
1111
+ >>> class Par(Parameter):
1112
+ ... NDIM = 1
1113
+ ... TYPE = float
1114
+ ... TIME = True
1115
+ ... SPAN = 0.0, None
1116
+
1117
+ We prepare a shape of length 2 and set different simulation and
1118
+ parameter step sizes, to see that the required time-related
1119
+ adjustments work correctly:
1120
+
1121
+ >>> par = Par(None)
1122
+ >>> par.shape = (2,)
1123
+ >>> pub.options.parameterstep = "1d"
1124
+ >>> pub.options.simulationstep = "2d"
1125
+
1126
+ Now you can pass one single value, an iterable containing two
1127
+ values, or two separate values as positional arguments, to set
1128
+ both required values. Note that the given values are assumed to
1129
+ agree with the actual parameter step size, and are converted
1130
+ internally to agree with the actual simulation step size. The
1131
+ string representation shows values that agree with the parameter
1132
+ step size; the |Variable.values| property shows the values that
1133
+ agree with the simulation step size, relevant during simulation
1134
+ runs. Also, the string representation shows only one value, in
1135
+ case all (relevant) values are identical:
1136
+
1137
+ >>> par(3.0)
1138
+ >>> par
1139
+ par(3.0)
1140
+ >>> print_vector(par.values)
1141
+ 6.0, 6.0
1142
+
1143
+ >>> par([0.0, 4.0])
1144
+ >>> par
1145
+ par(0.0, 4.0)
1146
+ >>> print_vector(par.values)
1147
+ 0.0, 8.0
1148
+
1149
+ >>> par(1.0, 2.0)
1150
+ >>> par
1151
+ par(1.0, 2.0)
1152
+ >>> print_vector(par.values)
1153
+ 2.0, 4.0
1154
+
1155
+ Using the `call` syntax to set parameter values triggers method
1156
+ |trim| automatically:
1157
+
1158
+ >>> with pub.options.warntrim(True), warn_later():
1159
+ ... par(-1.0, 3.0)
1160
+ UserWarning: For variable `par` at least one value needed to be trimmed. \
1161
+ The old and the new value(s) are `-2.0, 6.0` and `0.0, 6.0`, respectively.
1162
+ >>> par
1163
+ par(0.0, 3.0)
1164
+ >>> print_vector(par.values)
1165
+ 0.0, 6.0
1166
+
1167
+ You are free to change the parameter step size (temporarily) to change
1168
+ the string representation of |Parameter| handling time-dependent values
1169
+ without a risk to change the actual values relevant for simulation:
1170
+
1171
+ >>> with pub.options.parameterstep("2d"):
1172
+ ... print(par)
1173
+ ... print_vector(par.values)
1174
+ par(0.0, 6.0)
1175
+ 0.0, 6.0
1176
+ >>> par
1177
+ par(0.0, 3.0)
1178
+ >>> print_vector(par.values)
1179
+ 0.0, 6.0
1180
+
1181
+ The highest number of dimensions of |Parameter| subclasses supported
1182
+ is currently two. The following examples repeat some examples from
1183
+ above for a 2-dimensional parameter that handles that values inversely
1184
+ related to the simulation step size (indicated by setting the class
1185
+ attribute `TIME` to |False|):
1186
+
1187
+ >>> from hydpy import print_matrix
1188
+ >>> class Par(Parameter):
1189
+ ... NDIM = 2
1190
+ ... TYPE = float
1191
+ ... TIME = False
1192
+ ... SPAN = 0.0, 5.0
1193
+
1194
+ >>> par = Par(None)
1195
+ >>> par.shape = (2, 3)
1196
+
1197
+ >>> par(9.0)
1198
+ >>> par
1199
+ par(9.0)
1200
+ >>> print_matrix(par.values)
1201
+ | 4.5, 4.5, 4.5 |
1202
+ | 4.5, 4.5, 4.5 |
1203
+
1204
+ >>> par([[1.0, 2.0, 3.0],
1205
+ ... [4.0, 5.0, 6.0]])
1206
+ >>> print_matrix(par)
1207
+ | 0.5, 1.0, 1.5 |
1208
+ | 2.0, 2.5, 3.0 |
1209
+ >>> print_matrix(par.values)
1210
+ | 0.5, 1.0, 1.5 |
1211
+ | 2.0, 2.5, 3.0 |
1212
+
1213
+ >>> par(1.0, 2.0)
1214
+ Traceback (most recent call last):
1215
+ ...
1216
+ ValueError: While trying to set the value(s) of variable `par`, the following \
1217
+ error occurred: While trying to convert the value(s) `[0.5 1. ]` to a numpy ndarray \
1218
+ with shape `(2, 3)` and type `float`, the following error occurred: could not \
1219
+ broadcast input array from shape (2,) into shape (2,3)
1220
+ """
1221
+
1222
+ TIME: bool | None
1223
+ KEYWORDS: Mapping[str, Keyword] = {}
1224
+
1225
+ subvars: SubParameters
1226
+ """The subgroup to which the parameter belongs."""
1227
+ subpars: SubParameters
1228
+ """Alias for |Parameter.subvars|."""
1229
+
1230
+ _CLS_FASTACCESS_PYTHON = FastAccessParameter
1231
+
1232
+ _keywordarguments: KeywordArguments
1233
+
1234
+ def __init__(self, subvars: SubParameters) -> None:
1235
+ super().__init__(subvars)
1236
+ self.subpars = subvars
1237
+ self._keywordarguments = KeywordArguments(False)
1238
+
1239
+ def _raise_args_and_kwargs_error(self) -> NoReturn:
1240
+ raise ValueError(
1241
+ f"For parameter {objecttools.elementphrase(self)} both positional and "
1242
+ f"keyword arguments are given, which is ambiguous."
1243
+ )
1244
+
1245
+ def _raise_no_args_and_no_kwargs_error(self) -> NoReturn:
1246
+ raise ValueError(
1247
+ f"For parameter {objecttools.elementphrase(self)} neither a positional "
1248
+ f"nor a keyword argument is given."
1249
+ )
1250
+
1251
+ def _raise_kwargs_and_auxfile_error(self, kwargs: Mapping[str, object]) -> NoReturn:
1252
+ raise ValueError(
1253
+ f"It is not allowed to combine keyword `auxfile` with other keywords, but "
1254
+ f"for parameter {objecttools.elementphrase(self)} also the following "
1255
+ f"keywords are used: {objecttools.enumeration(kwargs.keys())}."
1256
+ )
1257
+
1258
+ def _raise_wrong_kwargs_error(self) -> NoReturn:
1259
+ # ToDo: we should stop using `NotImplementedError` this way
1260
+ # To trick pylint:
1261
+ raise getattr(builtins, "NotImplementedError")(
1262
+ f"The value(s) of parameter {objecttools.elementphrase(self)} could not "
1263
+ f"be set based on the given keyword arguments."
1264
+ )
1265
+
1266
+ def __call__(self, *args, **kwargs) -> None:
1267
+ if args and kwargs:
1268
+ self._raise_args_and_kwargs_error()
1269
+ if not args and not kwargs:
1270
+ self._raise_no_args_and_no_kwargs_error()
1271
+ auxfile = kwargs.pop("auxfile", None)
1272
+ if auxfile:
1273
+ if kwargs:
1274
+ self._raise_kwargs_and_auxfile_error(kwargs)
1275
+ self.values = self._get_values_from_auxiliaryfile(auxfile)
1276
+ elif args:
1277
+ if len(args) == 1:
1278
+ args = args[0]
1279
+ self.values = self.apply_timefactor(numpy.array(args))
1280
+ else:
1281
+ self._raise_wrong_kwargs_error()
1282
+ self.trim()
1283
+
1284
+ def _get_values_from_auxiliaryfile(self, auxfile: str):
1285
+ """Try to return the parameter values from the auxiliary control file with the
1286
+ given name.
1287
+
1288
+ Things are a little complicated here. To understand this method, you should
1289
+ first take a look at the |parameterstep| function.
1290
+ """
1291
+ try:
1292
+ assert (
1293
+ ((frame1 := inspect.currentframe()) is not None)
1294
+ and ((frame2 := frame1.f_back) is not None)
1295
+ and ((frame := frame2.f_back) is not None)
1296
+ )
1297
+ while frame:
1298
+ namespace = frame.f_locals
1299
+ try:
1300
+ subnamespace = {"model": namespace["model"], "focus": self}
1301
+ break
1302
+ except KeyError:
1303
+ frame = frame.f_back
1304
+ else:
1305
+ raise RuntimeError(
1306
+ "Cannot determine the corresponding model. Use the `auxfile` "
1307
+ "keyword in usual parameter control files only."
1308
+ )
1309
+ filetools.ControlManager.read2dict(auxfile, subnamespace)
1310
+ subself = subnamespace[self.name]
1311
+ try:
1312
+ return subself.value
1313
+ except exceptiontools.AttributeNotReady:
1314
+ raise RuntimeError(
1315
+ f"The selected auxiliary file does not define "
1316
+ f"value(s) for parameter `{self.name}`."
1317
+ ) from None
1318
+ except BaseException:
1319
+ objecttools.augment_excmessage(
1320
+ f"While trying to extract information for parameter "
1321
+ f"`{self.name}` from file `{auxfile}`"
1322
+ )
1323
+
1324
+ def _find_kwargscombination(
1325
+ self,
1326
+ given_args: Sequence[Any],
1327
+ given_kwargs: dict[str, Any],
1328
+ allowed_combinations: tuple[set[str], ...],
1329
+ ) -> int | None:
1330
+ if given_kwargs and ("auxfile" not in given_kwargs):
1331
+ if given_args:
1332
+ self._raise_args_and_kwargs_error()
1333
+ try:
1334
+ return allowed_combinations.index(set(given_kwargs))
1335
+ except ValueError:
1336
+ return None
1337
+ return None
1338
+
1339
+ def __hydpy__connect_variable2subgroup__(self) -> None:
1340
+ super().__hydpy__connect_variable2subgroup__()
1341
+ if self.NDIM:
1342
+ if exceptiontools.attrready(self, "shape"):
1343
+ return
1344
+ setattr(self.fastaccess, self.name, None)
1345
+ return
1346
+ initvalue, initflag = self.initinfo
1347
+ if initflag:
1348
+ setattr(self, "value", initvalue)
1349
+ return
1350
+ setattr(self.fastaccess, self.name, initvalue)
1351
+ return
1352
+
1353
+ @property
1354
+ def initinfo(self) -> tuple[float | int | bool, bool]:
1355
+ """A |tuple| containing the initial value and |True| or a missing
1356
+ value and |False|, depending on the actual |Parameter| subclass and
1357
+ the actual value of option |Options.usedefaultvalues|.
1358
+
1359
+ In the following we show how method the effects of property
1360
+ |Parameter.initinfo| when initiasing new |Parameter| objects.
1361
+ Let's define a parameter test class and prepare a function for
1362
+ initialising it and connecting the resulting instance to a
1363
+ |SubParameters| object:
1364
+
1365
+ >>> from hydpy.core.parametertools import Parameter, SubParameters
1366
+ >>> class Test(Parameter):
1367
+ ... NDIM = 0
1368
+ ... TYPE = float
1369
+ ... TIME = None
1370
+ ... INIT = 2.0
1371
+ >>> class SubGroup(SubParameters):
1372
+ ... CLASSES = (Test,)
1373
+ >>> def prepare():
1374
+ ... subpars = SubGroup(None)
1375
+ ... test = Test(subpars)
1376
+ ... test.__hydpy__connect_variable2subgroup__()
1377
+ ... return test
1378
+
1379
+ By default, making use of the `INIT` attribute is disabled:
1380
+
1381
+ >>> test = prepare()
1382
+ >>> test
1383
+ test(?)
1384
+
1385
+ Enable it through setting |Options.usedefaultvalues| to |True|:
1386
+
1387
+ >>> from hydpy import pub
1388
+ >>> pub.options.usedefaultvalues = True
1389
+ >>> test = prepare()
1390
+ >>> test
1391
+ test(2.0)
1392
+
1393
+ When no `INIT` attribute is defined (indicated by |None|), enabling
1394
+ |Options.usedefaultvalues| has no effect, of course:
1395
+
1396
+ >>> Test.INIT = None
1397
+ >>> test = prepare()
1398
+ >>> test
1399
+ test(?)
1400
+
1401
+ For time-dependent parameter values, the `INIT` attribute is assumed
1402
+ to be related to a |Parameterstep| of one day:
1403
+
1404
+ >>> pub.options.parameterstep = "2d"
1405
+ >>> pub.options.simulationstep = "12h"
1406
+ >>> Test.INIT = 2.0
1407
+ >>> Test.TIME = True
1408
+ >>> test = prepare()
1409
+ >>> test
1410
+ test(4.0)
1411
+ >>> test.value
1412
+ 1.0
1413
+ """
1414
+ init = self.INIT
1415
+ if (init is not None) and hydpy.pub.options.usedefaultvalues:
1416
+ with hydpy.pub.options.parameterstep("1d"):
1417
+ return self.apply_timefactor(init), True
1418
+ return variabletools.TYPE2MISSINGVALUE[self.TYPE], False
1419
+
1420
+ @classmethod
1421
+ def get_timefactor(cls) -> float:
1422
+ """Factor to adjust a new value of a time-dependent parameter.
1423
+
1424
+ For a time-dependent parameter, its effective value depends on the
1425
+ simulation step size. Method |Parameter.get_timefactor| returns
1426
+ the fraction between the current simulation step size and the
1427
+ current parameter step size.
1428
+
1429
+ .. testsetup::
1430
+
1431
+ >>> from hydpy import pub
1432
+ >>> del pub.options.simulationstep
1433
+ >>> del pub.options.parameterstep
1434
+
1435
+ Method |Parameter.get_timefactor| raises the following error
1436
+ when time information is not available:
1437
+
1438
+ >>> from hydpy.core.parametertools import Parameter
1439
+ >>> Parameter.get_timefactor()
1440
+ Traceback (most recent call last):
1441
+ ...
1442
+ RuntimeError: To calculate the conversion factor for adapting the \
1443
+ values of the time-dependent parameters, you need to define both a \
1444
+ parameter and a simulation time step size first.
1445
+
1446
+ One can define both time step sizes directly:
1447
+
1448
+ >>> from hydpy import pub
1449
+ >>> pub.options.parameterstep = "1d"
1450
+ >>> pub.options.simulationstep = "6h"
1451
+ >>> Parameter.get_timefactor()
1452
+ 0.25
1453
+
1454
+ As usual, the "global" simulation step size of the |Timegrids|
1455
+ object of module |pub| is prefered:
1456
+
1457
+ >>> from hydpy import pub
1458
+ >>> pub.timegrids = "2000-01-01", "2001-01-01", "12h"
1459
+ >>> Parameter.get_timefactor()
1460
+ 0.5
1461
+
1462
+ .. testsetup::
1463
+
1464
+ >>> del pub.timegrids
1465
+ """
1466
+ try:
1467
+ parameterstep = hydpy.pub.options.parameterstep
1468
+ parameterstep.check()
1469
+ parfactor = hydpy.pub.timegrids.parfactor
1470
+ except RuntimeError:
1471
+ options = hydpy.pub.options
1472
+ if not (options.parameterstep and options.simulationstep):
1473
+ raise RuntimeError(
1474
+ "To calculate the conversion factor for adapting "
1475
+ "the values of the time-dependent parameters, "
1476
+ "you need to define both a parameter and a simulation "
1477
+ "time step size first."
1478
+ ) from None
1479
+ date1 = timetools.Date("2000.01.01")
1480
+ date2 = date1 + options.simulationstep
1481
+ parfactor = timetools.Timegrids(
1482
+ timetools.Timegrid(
1483
+ firstdate=date1, lastdate=date2, stepsize=options.simulationstep
1484
+ )
1485
+ ).parfactor
1486
+ return parfactor(parameterstep)
1487
+
1488
+ def trim(self, lower=None, upper=None) -> bool:
1489
+ """Apply function |trim| of module |variabletools|."""
1490
+ return variabletools.trim(self, lower, upper)
1491
+
1492
+ @classmethod
1493
+ def apply_timefactor(cls, values: ArrayFloat) -> ArrayFloat:
1494
+ """Change and return the given value(s) in accordance with
1495
+ |Parameter.get_timefactor| and the type of time-dependence
1496
+ of the actual parameter subclass.
1497
+
1498
+ For the same conversion factor returned by method
1499
+ |Parameter.get_timefactor|, method |Parameter.apply_timefactor|
1500
+ behaves differently depending on the `TIME` attribute of the
1501
+ respective |Parameter| subclass. We first prepare a parameter
1502
+ test class and define both the parameter and simulation step size:
1503
+
1504
+ >>> from hydpy.core.parametertools import Parameter
1505
+ >>> class Par(Parameter):
1506
+ ... TIME = None
1507
+ >>> from hydpy import pub
1508
+ >>> pub.options.parameterstep = "1d"
1509
+ >>> pub.options.simulationstep = "6h"
1510
+
1511
+ |None| means the value(s) of the parameter are not time-dependent
1512
+ (e.g. maximum storage capacity). Hence, |Parameter.apply_timefactor|
1513
+ returns the original value(s):
1514
+
1515
+ >>> Par.apply_timefactor(4.0)
1516
+ 4.0
1517
+
1518
+ |True| means the effective parameter value is proportional to
1519
+ the simulation step size (e.g. travel time). Hence,
1520
+ |Parameter.apply_timefactor| returns a reduced value in the
1521
+ next example (where the simulation step size is smaller than
1522
+ the parameter step size):
1523
+
1524
+ >>> Par.TIME = True
1525
+ >>> Par.apply_timefactor(4.0)
1526
+ 1.0
1527
+
1528
+ |False| means the effective parameter value is inversely
1529
+ proportional to the simulation step size (e.g. storage
1530
+ coefficient). Hence, |Parameter.apply_timefactor| returns
1531
+ an increased value in the next example:
1532
+
1533
+ >>> Par.TIME = False
1534
+ >>> Par.apply_timefactor(4.0)
1535
+ 16.0
1536
+ """
1537
+ if cls.TIME is True:
1538
+ return values * cls.get_timefactor()
1539
+ if cls.TIME is False:
1540
+ return values / cls.get_timefactor()
1541
+ return values
1542
+
1543
+ @classmethod
1544
+ def revert_timefactor(cls, values: ArrayFloat) -> ArrayFloat:
1545
+ """The inverse version of method |Parameter.apply_timefactor|.
1546
+
1547
+ See the explanations on method Parameter.apply_timefactor| to
1548
+ understand the following examples:
1549
+
1550
+ >>> from hydpy.core.parametertools import Parameter
1551
+ >>> class Par(Parameter):
1552
+ ... TIME = None
1553
+ >>> Par.parameterstep = "1d"
1554
+ >>> Par.simulationstep = "6h"
1555
+ >>> Par.revert_timefactor(4.0)
1556
+ 4.0
1557
+
1558
+ >>> Par.TIME = True
1559
+ >>> Par.revert_timefactor(4.0)
1560
+ 16.0
1561
+
1562
+ >>> Par.TIME = False
1563
+ >>> Par.revert_timefactor(4.0)
1564
+ 1.0
1565
+ """
1566
+ if cls.TIME is True:
1567
+ return values / cls.get_timefactor()
1568
+ if cls.TIME is False:
1569
+ return values * cls.get_timefactor()
1570
+ return values
1571
+
1572
+ def update(self) -> None:
1573
+ """To be overridden by all "secondary" parameters.
1574
+
1575
+ |Parameter| subclasses to be used as "primary" parameters (control
1576
+ parameters) do not need to implement method |Parameter.update|.
1577
+ For such classes, invoking the method results in the following
1578
+ error message:
1579
+
1580
+ >>> from hydpy.core.parametertools import Parameter
1581
+ >>> class Par(Parameter):
1582
+ ... pass
1583
+ >>> Par(None).update()
1584
+ Traceback (most recent call last):
1585
+ ...
1586
+ RuntimeError: Parameter `par` of element `?` does not \
1587
+ implement method `update`.
1588
+ """
1589
+ raise RuntimeError(
1590
+ f"Parameter {objecttools.elementphrase(self)} does not "
1591
+ f"implement method `update`."
1592
+ )
1593
+
1594
+ @property
1595
+ def keywordarguments(self) -> KeywordArguments:
1596
+ """A |KeywordArguments| object.
1597
+
1598
+ By default, instances of |Parameter| subclasses return empty, invalid
1599
+ |KeywordArguments| objects:
1600
+
1601
+ >>> from hydpy.core.parametertools import Keyword, KeywordArguments, Parameter
1602
+ >>> par = Parameter(None)
1603
+ >>> kwa = par.keywordarguments
1604
+ >>> kwa
1605
+ KeywordArguments()
1606
+ >>> kwa.valid
1607
+ False
1608
+
1609
+ See class |ZipParameter| for an implementation example of a |Parameter|
1610
+ subclass overriding this behaviour. Another example is class
1611
+ |musk_control.NmbSegments|, which relies on the following mechanism.
1612
+
1613
+ Model developers can use the private `_keywordarguments` attribute. Property
1614
+ |Parameter.keywordarguments| returns a deep copy of the |KeywordArguments|
1615
+ object stored here:
1616
+
1617
+ >>> par._keywordarguments = KeywordArguments(x=1.0, y=2.0, z=3.0)
1618
+ >>> par.keywordarguments
1619
+ KeywordArguments(x=1.0, y=2.0, z=3.0)
1620
+ >>> par.keywordarguments is par._keywordarguments
1621
+ False
1622
+
1623
+ We assume that the values of time-dependent keyword arguments stored under the
1624
+ private attribute refer to the simulation step size. However, the values of
1625
+ the |KeywordArguments| object returned by |Parameter.keywordarguments| must
1626
+ refer to the current parameter step size. If the relevant |Parameter| class
1627
+ provides |Keyword| instances that describe the individual keyword arguments,
1628
+ |Parameter.keywordarguments| can perform the necessary adjustments
1629
+ automatically:
1630
+
1631
+ >>> par.KEYWORDS = {"x": Keyword(name="x", time=None),
1632
+ ... "y": Keyword(name="y", time=True),
1633
+ ... "z": Keyword(name="z", time=False)}
1634
+ >>> from hydpy import pub
1635
+ >>> with pub.options.simulationstep("1d"), pub.options.parameterstep("2d"):
1636
+ ... par.keywordarguments
1637
+ KeywordArguments(x=1.0, y=4.0, z=1.5)
1638
+ """
1639
+ keywordarguments = copy.deepcopy(self._keywordarguments)
1640
+ for name, keyword in self.KEYWORDS.items():
1641
+ if keyword.time is not None:
1642
+ try:
1643
+ value = keywordarguments[keyword.name]
1644
+ except KeyError:
1645
+ continue
1646
+ if keyword.time is True:
1647
+ keywordarguments[name] = value / self.get_timefactor()
1648
+ else:
1649
+ keywordarguments[name] = value * self.get_timefactor()
1650
+ return keywordarguments
1651
+
1652
+ def compress_repr(self) -> str | None:
1653
+ """Try to find a compressed parameter value representation and return it.
1654
+
1655
+ |Parameter.compress_repr| raises a |NotImplementedError| when failing to find a
1656
+ compressed representation.
1657
+
1658
+ For the following examples, we define a 1-dimensional sequence handling
1659
+ time-dependent floating-point values:
1660
+
1661
+ >>> from hydpy.core.parametertools import Parameter
1662
+ >>> class Test(Parameter):
1663
+ ... NDIM = 1
1664
+ ... TYPE = float
1665
+ ... TIME = True
1666
+ >>> test = Test(None)
1667
+
1668
+ Before and directly after defining the parameter shape, `nan` is returned:
1669
+
1670
+ >>> test.compress_repr()
1671
+ '?'
1672
+ >>> test
1673
+ test(?)
1674
+ >>> test.shape = 4
1675
+ >>> test
1676
+ test(?)
1677
+
1678
+ Due to the time-dependence of the values of our test class, we need to specify
1679
+ a parameter and a simulation time step:
1680
+
1681
+ >>> from hydpy import print_vector, pub
1682
+ >>> pub.options.parameterstep = "1d"
1683
+ >>> pub.options.simulationstep = "8h"
1684
+
1685
+ Compression succeeds when all required values are identical:
1686
+
1687
+ >>> test(3.0, 3.0, 3.0, 3.0)
1688
+ >>> print_vector(test.values)
1689
+ 1.0, 1.0, 1.0, 1.0
1690
+ >>> test.compress_repr()
1691
+ '3.0'
1692
+ >>> test
1693
+ test(3.0)
1694
+
1695
+ Method |Parameter.compress_repr| returns |None| in case the required values are
1696
+ not identical:
1697
+
1698
+ >>> test(1.0, 2.0, 3.0, 3.0)
1699
+ >>> test.compress_repr()
1700
+ >>> test
1701
+ test(1.0, 2.0, 3.0, 3.0)
1702
+
1703
+ If some values are not required, indicate this by the `mask` descriptor:
1704
+
1705
+ >>> import numpy
1706
+ >>> test(3.0, 3.0, 3.0, numpy.nan)
1707
+ >>> test
1708
+ test(3.0, 3.0, 3.0, nan)
1709
+ >>> Test.mask = numpy.array([True, True, True, False])
1710
+ >>> test
1711
+ test(3.0)
1712
+
1713
+ If trying to access the mask results in an error, |Parameter.compress_repr|
1714
+ behaves as if no mask were available:
1715
+
1716
+ >>> def getattribute(obj, name):
1717
+ ... if name == 'mask':
1718
+ ... raise BaseException
1719
+ ... return object.__getattribute__(obj, name)
1720
+ >>> Test.__getattribute__ = getattribute
1721
+ >>> test
1722
+ test(3.0, 3.0, 3.0, nan)
1723
+
1724
+ For a shape of zero, the string representing includes an empty list:
1725
+
1726
+ >>> test.shape = 0
1727
+ >>> test.compress_repr()
1728
+ '[]'
1729
+ >>> test
1730
+ test([])
1731
+
1732
+ Method |Parameter.compress_repr| works similarly for different |Parameter|
1733
+ subclasses. The following examples focus on a 2-dimensional parameter handling
1734
+ integer values:
1735
+
1736
+ >>> from hydpy.core.parametertools import Parameter
1737
+ >>> class Test(Parameter):
1738
+ ... NDIM = 2
1739
+ ... TYPE = int
1740
+ ... TIME = None
1741
+ >>> test = Test(None)
1742
+
1743
+ >>> test.compress_repr()
1744
+ '?'
1745
+ >>> test
1746
+ test(?)
1747
+ >>> test.shape = (2, 3)
1748
+ >>> test
1749
+ test(?)
1750
+
1751
+ >>> test([[3, 3, 3],
1752
+ ... [3, 3, 3]])
1753
+ >>> test
1754
+ test(3)
1755
+
1756
+ >>> test([[3, 3, -999999],
1757
+ ... [3, 3, 3]])
1758
+ >>> test
1759
+ test([[3, 3, -999999],
1760
+ [3, 3, 3]])
1761
+
1762
+ >>> Test.mask = numpy.array([[True, True, False],
1763
+ ... [True, True, True]])
1764
+ >>> test
1765
+ test(3)
1766
+
1767
+ >>> test.shape = (0, 0)
1768
+ >>> test
1769
+ test([[]])
1770
+ """
1771
+ if not exceptiontools.attrready(self, "value"):
1772
+ return "?"
1773
+ if not self:
1774
+ return f"{self.NDIM * '['}{self.NDIM * ']'}"
1775
+ try:
1776
+ unique = numpy.unique(self[self.mask])
1777
+ except BaseException:
1778
+ unique = numpy.unique(self.values)
1779
+ if sum(numpy.isnan(unique)) == len(unique.flatten()):
1780
+ unique = numpy.array([numpy.nan])
1781
+ else:
1782
+ unique = self.revert_timefactor(unique)
1783
+ if len(unique) == 1:
1784
+ return objecttools.repr_(unique[0])
1785
+ return None
1786
+
1787
+ def __repr__(self) -> str:
1788
+ if self.NDIM:
1789
+ values = self.compress_repr()
1790
+ if values is None:
1791
+ values = self.revert_timefactor(self.values)
1792
+ brackets = (isinstance(values, str) and (values == "?")) or (
1793
+ (self.NDIM == 2) and (self.shape[0] != 1)
1794
+ )
1795
+ return variabletools.to_repr(self, values, brackets)
1796
+ if exceptiontools.attrready(self, "value"):
1797
+ value = self.revert_timefactor(self.value)
1798
+ else:
1799
+ value = "?"
1800
+ return f"{self.name}({objecttools.repr_(value)})"
1801
+
1802
+
1803
+ class _MixinModifiableParameter(Parameter):
1804
+ @classmethod
1805
+ def _reset_after_modification(cls, name: str, value: object | None) -> None:
1806
+ if value is None:
1807
+ delattr(cls, name)
1808
+ else:
1809
+ setattr(cls, name, value)
1810
+
1811
+
1812
+ class NameParameter(_MixinModifiableParameter, Parameter):
1813
+ """Parameter displaying the names of constants instead of their values.
1814
+
1815
+ For demonstration, we define the test class `LandType`, covering three different
1816
+ types of land covering. For this purpose, we need to prepare a dictionary of type
1817
+ |Constants| (class attribute `constants`), mapping the land type names to identity
1818
+ values. The class attributes `NDIM`, `TYPE`, and `TIME` are already set to `1`,
1819
+ `int`, and `None` via base class |NameParameter|. Furthermore, both `SPAN` tuple
1820
+ entries are `None` because |NameParameter| can perform checks against the available
1821
+ constants, which is more precise than only checking against the lowest and highest
1822
+ constant value:
1823
+
1824
+ >>> from hydpy.core.parametertools import Constants, NameParameter
1825
+ >>> class LandType(NameParameter):
1826
+ ... __name__ = "temp.py"
1827
+ ... constants = Constants(SOIL=1, WATER=2, GLACIER=3)
1828
+
1829
+ Additionally, we make the constants available within the local namespace (which is
1830
+ usually done by importing the constants from the selected application model
1831
+ automatically):
1832
+
1833
+ >>> SOIL, WATER, GLACIER = 1, 2, 3
1834
+
1835
+ For parameters of zero length, unprepared values, and identical required values,
1836
+ the string representations of |NameParameter| subclasses equal those of other
1837
+ |Parameter| subclasses:
1838
+
1839
+ >>> landtype = LandType(None)
1840
+ >>> landtype.shape = 0
1841
+ >>> landtype
1842
+ landtype([])
1843
+ >>> landtype.shape = 5
1844
+ >>> landtype
1845
+ landtype(?)
1846
+ >>> landtype(SOIL)
1847
+ >>> landtype
1848
+ landtype(SOIL)
1849
+
1850
+ For non-identical required values, class |NameParameter| replaces the identity
1851
+ values with their names:
1852
+
1853
+ >>> landtype(SOIL, WATER, GLACIER, WATER, SOIL)
1854
+ >>> landtype
1855
+ landtype(SOIL, WATER, GLACIER, WATER, SOIL)
1856
+
1857
+ For high numbers of entries, string representations are wrapped:
1858
+
1859
+ >>> landtype.shape = 22
1860
+ >>> landtype(SOIL)
1861
+ >>> landtype.values[0] = WATER
1862
+ >>> landtype.values[-1] = GLACIER
1863
+ >>> landtype
1864
+ landtype(WATER, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL,
1865
+ SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL,
1866
+ SOIL, GLACIER)
1867
+ """
1868
+
1869
+ NDIM = 1
1870
+ TYPE = int
1871
+ TIME = None
1872
+ SPAN = (None, None)
1873
+ constants: Constants
1874
+ _possible_values: set[int]
1875
+
1876
+ def __init__(self, subvars: SubParameters) -> None:
1877
+ super().__init__(subvars)
1878
+ self.constants = type(self).constants
1879
+ self._possible_values = set(self.constants.values())
1880
+ self._possible_values.add(variabletools.INT_NAN)
1881
+
1882
+ @classmethod
1883
+ @contextlib.contextmanager
1884
+ def modify_constants(
1885
+ cls, constants: Constants | None
1886
+ ) -> Generator[None, None, None]:
1887
+ """Modify the relevant constants temporarily.
1888
+
1889
+ The constants for defining land-use types are fixed for typical "main models"
1890
+ like |hland|. However, some submodels must take over the constants defined
1891
+ by their current main model, which are only known at runtime. For example,
1892
+ consider the following simple `LandType` parameter that handles predefined
1893
+ constants as a class attribute:
1894
+
1895
+ >>> from hydpy.core.parametertools import Constants, NameParameter
1896
+ >>> class LandType(NameParameter):
1897
+ ... __name__ = "temp.py"
1898
+ ... constants = Constants(SOIL=1, WATER=2, GLACIER=3)
1899
+ >>> SOIL, WATER, GLACIER = 1, 2, 3
1900
+ >>> landtype1 = LandType(None)
1901
+ >>> landtype1.shape = 3
1902
+ >>> landtype1(SOIL, WATER, SOIL)
1903
+ >>> landtype1
1904
+ landtype(SOIL, WATER, SOIL)
1905
+
1906
+ We can use |NameParameter.modify_constants| to temporarily change these
1907
+ constants:
1908
+
1909
+ >>> FIELD, FOREST = 1, 4
1910
+ >>> with LandType.modify_constants(Constants(FIELD=1, FOREST=4)):
1911
+ ... landtype2 = LandType(None)
1912
+ ... landtype2.shape = 4
1913
+ ... landtype2(FOREST, FOREST, FIELD, FIELD)
1914
+ ... landtype2
1915
+ landtype(FOREST, FOREST, FIELD, FIELD)
1916
+
1917
+ During initialisation, these constants become an instance attribute, so the
1918
+ parameter instance does not forget them after leaving the `with` block (when
1919
+ the class attribute is reset to its previous value):
1920
+
1921
+ >>> landtype1.constants
1922
+ {'SOIL': 1, 'WATER': 2, 'GLACIER': 3}
1923
+ >>> landtype2.constants
1924
+ {'FIELD': 1, 'FOREST': 4}
1925
+ >>> LandType.constants
1926
+ {'SOIL': 1, 'WATER': 2, 'GLACIER': 3}
1927
+
1928
+ One can now use both parameter instances with their specific constants without
1929
+ the risk of impacting the other:
1930
+
1931
+ >>> landtype1(SOIL, WATER, WATER)
1932
+ >>> landtype1
1933
+ landtype(SOIL, WATER, WATER)
1934
+ >>> landtype2
1935
+ landtype(FOREST, FOREST, FIELD, FIELD)
1936
+
1937
+ >>> landtype2(FIELD, FOREST, FOREST, FIELD)
1938
+ >>> landtype2
1939
+ landtype(FIELD, FOREST, FOREST, FIELD)
1940
+ >>> landtype1
1941
+ landtype(SOIL, WATER, WATER)
1942
+
1943
+ Passing |None| does not overwrite the default or the previously set references:
1944
+
1945
+ >>> with LandType.modify_constants(None):
1946
+ ... LandType.constants
1947
+ ... landtype3 = LandType(None)
1948
+ ... landtype3.shape = 4
1949
+ ... landtype3(GLACIER, SOIL, GLACIER, WATER)
1950
+ ... landtype3
1951
+ {'SOIL': 1, 'WATER': 2, 'GLACIER': 3}
1952
+ landtype(GLACIER, SOIL, GLACIER, WATER)
1953
+ >>> LandType.constants
1954
+ {'SOIL': 1, 'WATER': 2, 'GLACIER': 3}
1955
+ >>> landtype3
1956
+ landtype(GLACIER, SOIL, GLACIER, WATER)
1957
+ """
1958
+ if constants is None:
1959
+ yield
1960
+ else:
1961
+ old = vars(cls).get("constants")
1962
+ try:
1963
+ cls.constants = constants
1964
+ yield
1965
+ finally:
1966
+ cls._reset_after_modification("constants", old)
1967
+
1968
+ def trim(self, lower=None, upper=None) -> bool:
1969
+ """Check if all previously set values comply with the supported constants.
1970
+
1971
+ >>> from hydpy.core.parametertools import Constants, NameParameter
1972
+ >>> class LandType(NameParameter):
1973
+ ... __name__ = "temp.py"
1974
+ ... constants = Constants(SOIL=1, WATER=2, GLACIER=4)
1975
+ >>> SOIL, WATER, ROCK, GLACIER = 1, 2, 3, 4
1976
+ >>> landtype = LandType(None)
1977
+ >>> landtype.shape = 4
1978
+ >>> landtype(SOIL, WATER, ROCK, GLACIER)
1979
+ Traceback (most recent call last):
1980
+ ..
1981
+ ValueError: At least one value of parameter `landtype` of element `?` is not \
1982
+ valid.
1983
+ >>> landtype
1984
+ landtype(SOIL, WATER, 3, GLACIER)
1985
+ """
1986
+ if hydpy.pub.options.trimvariables:
1987
+ if any(value not in self._possible_values for value in self._get_value()):
1988
+ raise ValueError(
1989
+ f"At least one value of parameter "
1990
+ f"{objecttools.elementphrase(self)} is not valid."
1991
+ )
1992
+ return False
1993
+
1994
+ def __repr__(self) -> str:
1995
+ string = super().compress_repr()
1996
+ if string in ("?", "[]"):
1997
+ return f"{self.name}({string})"
1998
+ if string is None:
1999
+ values = self.values
2000
+ else:
2001
+ values = [int(string)]
2002
+ get = self.constants.value2name.get
2003
+ repr_ = objecttools.repr_
2004
+ names = tuple(get(value, repr_(value)) for value in values)
2005
+ string = objecttools.assignrepr_values(
2006
+ values=names, prefix=f"{self.name}(", width=70
2007
+ )
2008
+ return f"{string})"
2009
+
2010
+
2011
+ class ZipParameter(_MixinModifiableParameter, Parameter):
2012
+ """Base class for 1-dimensional model parameters that offers an additional
2013
+ keyword-based zipping functionality.
2014
+
2015
+ Many models implemented in the *HydPy* framework realise the concept of hydrological
2016
+ response units via 1-dimensional |Parameter| objects, each entry corresponding with
2017
+ an individual unit. To allow for a maximum of flexibility, one can define their
2018
+ values independently, which allows, for example, for applying arbitrary
2019
+ relationships between the altitude of individual response units and a precipitation
2020
+ correction factor to be parameterised.
2021
+
2022
+ However, very often, hydrological modellers set identical values for different
2023
+ hydrological response units of the same type. One could, for example, set the same
2024
+ leaf area index for all units of the same land-use type. Class |ZipParameter|
2025
+ allows defining parameters, which conveniently support this parameterisation
2026
+ strategy.
2027
+
2028
+ .. testsetup::
2029
+
2030
+ >>> from hydpy import pub
2031
+ >>> del pub.options.simulationstep
2032
+
2033
+ To see how base class |ZipParameter| works, we need to create some additional
2034
+ subclasses. First, we need a parameter defining the type of the individual
2035
+ hydrological response units, which can be done by subclassing from |NameParameter|.
2036
+ We do so by taking the example from the documentation of the |NameParameter| class:
2037
+
2038
+ >>> from hydpy.core.parametertools import NameParameter
2039
+ >>> SOIL, WATER, GLACIER = 1, 2, 3
2040
+ >>> class LandType(NameParameter):
2041
+ ... SPAN = (1, 3)
2042
+ ... constants = {"SOIL": SOIL, "WATER": WATER, "GLACIER": GLACIER}
2043
+ >>> landtype = LandType(None)
2044
+
2045
+ Second, we need an |IndexMask| subclass. Our subclass `Land` references the
2046
+ respective `LandType` parameter object (we do this in a simplified manner, see class
2047
+ |hland_parameters.ParameterComplete| for a "real world" example) but is supposed to
2048
+ focus on the response units of type `soil` or `glacier` only:
2049
+
2050
+ >>> from hydpy.core.masktools import IndexMask
2051
+ >>> class Land(IndexMask):
2052
+ ... relevant = (SOIL, GLACIER)
2053
+ ... @staticmethod
2054
+ ... def get_refindices(variable):
2055
+ ... return variable.landtype
2056
+
2057
+ Third, we prepare the actual |ZipParameter| subclass, holding the same `constants`
2058
+ dictionary as the `LandType` parameter and the `Land` mask as attributes. We assume
2059
+ that the values of our test class `Par` are time-dependent and set different
2060
+ parameter and simulation step sizes, to show that the related value adjustments
2061
+ work. Also, we make the `LandType` object available via attribute access, which is
2062
+ a hack to make the above simplification work:
2063
+
2064
+ >>> from hydpy.core.parametertools import ZipParameter
2065
+ >>> class Par(ZipParameter):
2066
+ ... TYPE = float
2067
+ ... TIME = True
2068
+ ... SPAN = (0.0, None)
2069
+ ... constants = LandType.constants
2070
+ ... mask = Land()
2071
+ ... landtype = landtype
2072
+ >>> par = Par(None)
2073
+ >>> from hydpy import pub
2074
+ >>> pub.options.parameterstep = "1d"
2075
+ >>> pub.options.simulationstep = "12h"
2076
+
2077
+ For parameters with zero-length or with unprepared or identical parameter values,
2078
+ the string representation looks as usual:
2079
+
2080
+ >>> from hydpy import print_vector
2081
+ >>> landtype.shape = 0
2082
+ >>> par.shape = 0
2083
+ >>> par
2084
+ par([])
2085
+ >>> landtype.shape = 5
2086
+ >>> landtype(SOIL, WATER, GLACIER, WATER, SOIL)
2087
+ >>> par.shape = 5
2088
+ >>> par
2089
+ par(?)
2090
+ >>> par(2.0)
2091
+ >>> par
2092
+ par(2.0)
2093
+ >>> print_vector(par.values)
2094
+ 1.0, 1.0, 1.0, 1.0, 1.0
2095
+
2096
+ The extended feature of class |ZipParameter| is to allow passing values via
2097
+ keywords, each keyword corresponding to one of the relevant constants (in our
2098
+ example: `SOIL` and `GLACIER`) in lower case letters:
2099
+
2100
+ >>> par(soil=4.0, glacier=6.0)
2101
+ >>> par
2102
+ par(glacier=6.0, soil=4.0)
2103
+ >>> print_vector(par.values)
2104
+ 2.0, nan, 3.0, nan, 2.0
2105
+
2106
+ Use the `default` argument if you want to assign the same value to entries with
2107
+ different constants:
2108
+
2109
+ >>> par(soil=2.0, default=8.0)
2110
+ >>> par
2111
+ par(glacier=8.0, soil=2.0)
2112
+ >>> print_vector(par.values)
2113
+ 1.0, nan, 4.0, nan, 1.0
2114
+
2115
+ Using a keyword argument corresponding to an existing, but not relevant constant (in
2116
+ our example: `WATER`) is silently ignored:
2117
+
2118
+ >>> par(soil=4.0, glacier=6.0, water=8.0)
2119
+ >>> par
2120
+ par(glacier=6.0, soil=4.0)
2121
+ >>> print_vector(par.values)
2122
+ 2.0, nan, 3.0, nan, 2.0
2123
+
2124
+ However, using a keyword not corresponding to any constant raises an exception:
2125
+
2126
+ >>> par(soil=4.0, glacier=6.0, wrong=8.0)
2127
+ Traceback (most recent call last):
2128
+ ...
2129
+ TypeError: While trying to set the values of parameter `par` of element `?` based \
2130
+ on keyword arguments `soil, glacier, and wrong`, the following error occurred: Keyword \
2131
+ `wrong` is not among the available model constants.
2132
+
2133
+ The same is true when passing incomplete information:
2134
+
2135
+ >>> par(soil=4.0)
2136
+ Traceback (most recent call last):
2137
+ ...
2138
+ TypeError: While trying to set the values of parameter `par` of element `?` based \
2139
+ on keyword arguments `soil`, the following error occurred: The given keywords are \
2140
+ incomplete and no default value is available.
2141
+
2142
+ Values exceeding the bounds defined by class attribute `SPAN` are trimmed as usual:
2143
+
2144
+ >>> from hydpy import pub
2145
+ >>> with pub.options.warntrim(False):
2146
+ ... par(soil=-10.0, glacier=10.0)
2147
+ >>> par
2148
+ par(glacier=10.0, soil=0.0)
2149
+
2150
+ For convenience, you can get or set all values related to a specific constant via
2151
+ attribute access:
2152
+
2153
+ >>> print_vector(par.soil)
2154
+ 0.0, 0.0
2155
+ >>> par.soil = 2.5
2156
+ >>> par
2157
+ par(glacier=10.0, soil=5.0)
2158
+
2159
+ Improper use of these "special attributes" results in errors like the following:
2160
+
2161
+ >>> par.Soil # doctest: +ELLIPSIS
2162
+ Traceback (most recent call last):
2163
+ ...
2164
+ AttributeError: `Soil` is neither a normal attribute of parameter `par` of element \
2165
+ `?` nor among the following special attributes: soil, water, and glacier...
2166
+
2167
+ >>> par.soil = "test"
2168
+ Traceback (most recent call last):
2169
+ ...
2170
+ ValueError: While trying the set the value(s) of parameter `par` of element `?` \
2171
+ related to the special attribute `soil`, the following error occurred: could not \
2172
+ convert string to float: 'test'
2173
+ """
2174
+
2175
+ NDIM = 1
2176
+ constants: dict[str, int]
2177
+ """Mapping of the constants' names and values."""
2178
+ refindices: NameParameter | None = None
2179
+ """Optional reference to the relevant index parameter."""
2180
+ relevant: tuple[int, ...] | None = None
2181
+ """The values of all (potentially) relevant constants."""
2182
+ mask: masktools.IndexMask
2183
+
2184
+ @classmethod
2185
+ @contextlib.contextmanager
2186
+ def modify_refindices(
2187
+ cls, refindices: NameParameter | None
2188
+ ) -> Generator[None, None, None]:
2189
+ """Eventually, set or modify the reference to the required index parameter.
2190
+
2191
+ The following example demonstrates that changes affect the relevant class only
2192
+ temporarily, but its objects initialised within the "with" block persistently:
2193
+
2194
+
2195
+ >>> from hydpy.core.variabletools import FastAccess, Variable
2196
+ >>> GRASS, TREES, WATER = 0, 1, 2
2197
+ >>> class RefPar(Variable):
2198
+ ... NDIM = 1
2199
+ ... TYPE = int
2200
+ ... TIME = None
2201
+ ... initinfo = 0, True
2202
+ ... _CLS_FASTACCESS_PYTHON = FastAccess
2203
+ ... constants = {"GRASS": GRASS, "TREES": TREES, "WATER": WATER}
2204
+ ... relevant = (0, 1, 2)
2205
+ >>> from hydpy.core.parametertools import ZipParameter
2206
+ >>> from hydpy.core.masktools import SubmodelIndexMask
2207
+ >>> class ZipPar(ZipParameter):
2208
+ ... NDIM = 1
2209
+ ... TYPE = float
2210
+ ... TIME = None
2211
+ ... initinfo = 0.0, True
2212
+ ... _CLS_FASTACCESS_PYTHON = FastAccess
2213
+ ... constants = {"FIELD": 1, "FOREST": 3}
2214
+ ... relevant = (1, 3)
2215
+ ... mask = SubmodelIndexMask()
2216
+ >>> refpar = RefPar(None)
2217
+ >>> refpar.shape = 3
2218
+ >>> refpar(1, 2, 1)
2219
+ >>> with ZipPar.modify_refindices(refpar):
2220
+ ... ZipPar.refindices.name
2221
+ ... ZipPar.constants
2222
+ ... ZipPar.relevant
2223
+ ... zippar1 = ZipPar(None)
2224
+ ... zippar1.shape = 3
2225
+ ... zippar1(water=1.0, default=2.)
2226
+ ... zippar1
2227
+ 'refpar'
2228
+ {'GRASS': 0, 'TREES': 1, 'WATER': 2}
2229
+ (0, 1, 2)
2230
+ zippar(trees=2.0, water=1.0)
2231
+
2232
+ >>> ZipPar.refindices
2233
+ >>> ZipPar.constants
2234
+ {'FIELD': 1, 'FOREST': 3}
2235
+ >>> ZipPar.relevant
2236
+ (1, 3)
2237
+ >>> zippar1
2238
+ zippar(trees=2.0, water=1.0)
2239
+
2240
+ Passing |None| does not overwrite previously set references:
2241
+
2242
+ >>> with ZipPar.modify_refindices(None):
2243
+ ... ZipPar.refindices
2244
+ ... ZipPar.constants
2245
+ ... ZipPar.relevant
2246
+ {'FIELD': 1, 'FOREST': 3}
2247
+ (1, 3)
2248
+
2249
+ >>> with ZipPar.modify_refindices(None):
2250
+ ... zippar2 = ZipPar(None)
2251
+ ... zippar2.shape = 3
2252
+ ... zippar2(water=1.0, default=2.)
2253
+ Traceback (most recent call last):
2254
+ ...
2255
+ RuntimeError: While trying to set the values of parameter `zippar` of element \
2256
+ `?` based on keyword arguments `water and default`, the following error occurred: \
2257
+ Variable `zippar` of element `?` does currently not reference an instance-specific \
2258
+ index parameter.
2259
+
2260
+ >>> ZipPar.refindices
2261
+ >>> ZipPar.constants
2262
+ {'FIELD': 1, 'FOREST': 3}
2263
+ >>> ZipPar.relevant
2264
+ (1, 3)
2265
+ """
2266
+ if refindices is None:
2267
+ yield
2268
+ else:
2269
+ get = vars(cls).get
2270
+ old_refindices = get("refindices")
2271
+ old_constants = get("constants")
2272
+ old_relevant = get("relevant")
2273
+ try:
2274
+ cls.refindices = refindices
2275
+ cls.constants = refindices.constants
2276
+ cls.relevant = tuple(refindices.constants.values())
2277
+ yield
2278
+ finally:
2279
+ cls._reset_after_modification("refindices", old_refindices)
2280
+ cls._reset_after_modification("constants", old_constants)
2281
+ cls._reset_after_modification("relevant", old_relevant)
2282
+
2283
+ def __init__(self, subvars: SubParameters) -> None:
2284
+ super().__init__(subvars)
2285
+ self.refindices = type(self).refindices
2286
+ self.constants = type(self).constants
2287
+ self.relevant = type(self).relevant
2288
+
2289
+ def __call__(self, *args, **kwargs) -> None:
2290
+ try:
2291
+ super().__call__(*args, **kwargs)
2292
+ except NotImplementedError:
2293
+ try:
2294
+ self._own_call(kwargs)
2295
+ except BaseException:
2296
+ objecttools.augment_excmessage(
2297
+ f"While trying to set the values of parameter "
2298
+ f"{objecttools.elementphrase(self)} based on keyword arguments "
2299
+ f"`{objecttools.enumeration(kwargs)}`"
2300
+ )
2301
+
2302
+ def _own_call(self, kwargs: dict[str, Any]) -> None:
2303
+ mask = self.mask
2304
+ self._set_value(numpy.nan)
2305
+ values = self._get_value()
2306
+ allidxs = mask.refindices.values
2307
+ relidxs = mask.narrow_relevant(relevant=self.relevant)
2308
+ counter = 0
2309
+ if "default" in kwargs:
2310
+ check = False
2311
+ values[mask] = kwargs.pop("default")
2312
+ else:
2313
+ check = True
2314
+ for key, value in kwargs.items():
2315
+ try:
2316
+ selidx = self.constants[key.upper()]
2317
+ if selidx in relidxs:
2318
+ values[allidxs == selidx] = value
2319
+ counter += 1
2320
+ except KeyError:
2321
+ raise TypeError(
2322
+ f"Keyword `{key}` is not among the " f"available model constants."
2323
+ ) from None
2324
+ if check and (counter < len(relidxs)):
2325
+ raise TypeError(
2326
+ "The given keywords are incomplete and no default value is available."
2327
+ )
2328
+ values[:] = self.apply_timefactor(values)
2329
+ self.trim()
2330
+
2331
+ @property
2332
+ def keywordarguments(self) -> KeywordArguments[float]:
2333
+ """A |KeywordArguments| object providing the currently valid keyword arguments.
2334
+
2335
+ We take parameter |lland_control.TRefT| of application model |lland_dd| as an
2336
+ example and set its shape (the number of hydrological response units defined by
2337
+ parameter |lland_control.NHRU|) to four and prepare the land use types
2338
+ |lland_constants.ACKER| (acre), |lland_constants.LAUBW| (deciduous forest), and
2339
+ |lland_constants.WASSER| (water) via parameter |lland_control.Lnk|:
2340
+
2341
+ >>> from hydpy.models.lland_dd import *
2342
+ >>> parameterstep()
2343
+ >>> nhru(4)
2344
+ >>> lnk(ACKER, LAUBW, WASSER, ACKER)
2345
+
2346
+ After defining all required values via keyword arguments (note that parameter
2347
+ |lland_control.TRefT| does not need any values for response units of type
2348
+ |lland_constants.WASSER|), property |ZipParameter.keywordarguments| makes
2349
+ exactly these keywords arguments available:
2350
+
2351
+ >>> treft(acker=2.0, laubw=1.0)
2352
+ >>> treft.keywordarguments
2353
+ KeywordArguments(acker=2.0, laubw=1.0)
2354
+ >>> treft.keywordarguments.valid
2355
+ True
2356
+
2357
+ In the following example, both the first and the fourth response unit are of
2358
+ type |lland_constants.ACKER| but have different |lland_control.TRefT| values,
2359
+ which cannot be the result of defining values via keyword arguments. Hence, the
2360
+ returned |KeywordArguments| object is invalid:
2361
+
2362
+ >>> treft(1.0, 2.0, 3.0, 4.0)
2363
+ >>> treft.keywordarguments
2364
+ KeywordArguments()
2365
+ >>> treft.keywordarguments.valid
2366
+ False
2367
+
2368
+ This is different from the situation where all response units are of type
2369
+ |lland_constants.WASSER|, where one does not need to define any values for
2370
+ parameter |lland_control.TRefT|. Thus, the returned |KeywordArguments| object
2371
+ is also empty but valid:
2372
+
2373
+ >>> lnk(WASSER)
2374
+ >>> treft.keywordarguments
2375
+ KeywordArguments()
2376
+ >>> treft.keywordarguments.valid
2377
+ True
2378
+
2379
+ ToDo: document "refinement" asa lland_dd uses the AETModel_V1 interface
2380
+ """
2381
+ try:
2382
+ mask = self.mask
2383
+ except BaseException:
2384
+ return KeywordArguments(False)
2385
+ if (refinement := mask.refinement) is None:
2386
+ refindices = mask.refindices.values
2387
+ else:
2388
+ refindices = mask.refindices.values.copy()
2389
+ refindices[~refinement.values] = variabletools.INT_NAN
2390
+ name2unique = KeywordArguments[float]()
2391
+ if (relevant := self.relevant) is None:
2392
+ relevant = mask.relevant
2393
+ for key, value in self.constants.items():
2394
+ if value in relevant:
2395
+ unique = numpy.unique(self.values[refindices == value])
2396
+ unique = self.revert_timefactor(unique)
2397
+ length = len(unique)
2398
+ if length == 1:
2399
+ name2unique[key.lower()] = unique[0]
2400
+ elif length > 1:
2401
+ return KeywordArguments(False)
2402
+ return name2unique
2403
+
2404
+ def __getattr__(self, name: str):
2405
+ name_ = name.upper()
2406
+ if (not name.islower()) or (name_ not in self.constants):
2407
+ names = objecttools.enumeration(
2408
+ key.lower() for key in self.constants.keys()
2409
+ )
2410
+ raise AttributeError(
2411
+ f"`{name}` is neither a normal attribute of parameter "
2412
+ f"{objecttools.elementphrase(self)} nor among the following special "
2413
+ f"attributes: {names}."
2414
+ )
2415
+ sel_constant = self.constants[name_]
2416
+ used_constants = self.mask.refindices.values
2417
+ return self.values[used_constants == sel_constant]
2418
+
2419
+ def __setattr__(self, name: str, value):
2420
+ name_ = name.upper()
2421
+ if name.islower() and (name_ in (constants := self.constants)):
2422
+ try:
2423
+ sel_constant = constants[name_]
2424
+ used_constants = self.mask.refindices.values
2425
+ self.values[used_constants == sel_constant] = value
2426
+ except BaseException:
2427
+ objecttools.augment_excmessage(
2428
+ f"While trying the set the value(s) of parameter "
2429
+ f"{objecttools.elementphrase(self)} related to the special "
2430
+ f"attribute `{name}`"
2431
+ )
2432
+ else:
2433
+ super().__setattr__(name, value)
2434
+
2435
+ def __repr__(self) -> str:
2436
+ string = self.compress_repr()
2437
+ if string is not None:
2438
+ return f"{self.name}({string})"
2439
+ keywordarguments = self.keywordarguments
2440
+ if not keywordarguments.valid:
2441
+ return super().__repr__()
2442
+ results = [
2443
+ f"{name}={objecttools.repr_(value)}" for name, value in keywordarguments
2444
+ ]
2445
+ string = objecttools.assignrepr_values(
2446
+ values=sorted(results), prefix=f"{self.name}(", width=70
2447
+ )
2448
+ return f"{string})"
2449
+
2450
+ def __dir__(self) -> list[str]:
2451
+ """
2452
+ >>> from hydpy.models.lland_dd import *
2453
+ >>> parameterstep()
2454
+ >>> sorted(set(dir(treft)) - set(object.__dir__(treft)))
2455
+ ['acker', 'baumb', 'boden', 'feucht', 'fluss', 'glets', 'grue_e', 'grue_i', \
2456
+ 'laubw', 'mischw', 'nadelw', 'obstb', 'see', 'sied_d', 'sied_l', 'vers', 'wasser', \
2457
+ 'weinb']
2458
+ """
2459
+ names = itertools.chain(
2460
+ cast(list[str], super().__dir__()),
2461
+ (key.lower() for key in self.constants.keys()),
2462
+ )
2463
+ return list(names)
2464
+
2465
+
2466
+ class SeasonalParameter(Parameter):
2467
+ """Base class for parameters handling values showing a seasonal variation.
2468
+
2469
+ Quite a lot of model parameter values change on an annual basis. One example is
2470
+ the leaf area index. For deciduous forests within temperate climatic regions, it
2471
+ shows a clear peak during the summer season.
2472
+
2473
+ If you want to vary the parameter values on a fixed (for example, a monthly) basis,
2474
+ |KeywordParameter2D| might be the best starting point. See the
2475
+ |lland_parameters.LanduseMonthParameter| class of the |lland| base model as an
2476
+ example, which is used to define parameter |lland_control.LAI|, handling monthly
2477
+ leaf area index values for different land-use classes.
2478
+
2479
+ However, class |SeasonalParameter| offers more flexibility in defining seasonal
2480
+ patterns, which is often helpful for modelling technical control systems. One
2481
+ example is the parameter |dam_control.TargetVolume| of base model |dam| for
2482
+ determining a dam's desired volume throughout the year.
2483
+
2484
+ .. testsetup::
2485
+
2486
+ >>> from hydpy import pub
2487
+ >>> del pub.options.simulationstep
2488
+
2489
+ For the following examples, we assume a simulation step size of one day:
2490
+
2491
+ >>> from hydpy import pub
2492
+ >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
2493
+
2494
+ Let us prepare an empty 1-dimensional |SeasonalParameter| instance:
2495
+
2496
+ >>> from hydpy.core.parametertools import SeasonalParameter
2497
+ >>> class Par(SeasonalParameter):
2498
+ ... NDIM = 1
2499
+ ... TIME = None
2500
+ >>> par = Par(None)
2501
+ >>> par.NDIM = 1
2502
+ >>> par
2503
+ par()
2504
+
2505
+ The shape is determined automatically, as described in the documentation on
2506
+ property |SeasonalParameter.shape| in more detail:
2507
+
2508
+ >>> par.shape = (None,)
2509
+ >>> par.shape
2510
+ (366,)
2511
+
2512
+ Pairs of |TOY| objects and |float| values define the seasonal pattern. One can
2513
+ assign them all at once via keyword arguments:
2514
+
2515
+ >>> par(_1=2., _7_1=4., _3_1_0_0_0=5.)
2516
+
2517
+ Note that all keywords in the call above are proper |TOY| initialisation arguments.
2518
+ Misspelt keywords result in error messages like the following:
2519
+
2520
+ >>> Par(None)(_a=1.)
2521
+ Traceback (most recent call last):
2522
+ ...
2523
+ ValueError: While trying to define the seasonal parameter value `par` of element \
2524
+ `?` for time of year `_a`, the following error occurred: While trying to initialise a \
2525
+ TOY object based on argument value `_a` of type `str`, the following error occurred: \
2526
+ While trying to retrieve the month, the following error occurred: For TOY (time of \
2527
+ year) objects, all properties must be of type `int`, but the value `a` of type `str` \
2528
+ given for property `month` cannot be converted to `int`.
2529
+
2530
+ As the following string representation shows are the pairs of each
2531
+ |SeasonalParameter| instance automatically sorted:
2532
+
2533
+ >>> par
2534
+ par(toy_1_1_0_0_0=2.0,
2535
+ toy_3_1_0_0_0=5.0,
2536
+ toy_7_1_0_0_0=4.0)
2537
+
2538
+ By default, `toy` is used as a prefix string. Using this prefix string, one can
2539
+ change the toy-value pairs via attribute access:
2540
+
2541
+ >>> par.toy_1_1_0_0_0
2542
+ 2.0
2543
+ >>> del par.toy_1_1_0_0_0
2544
+ >>> par.toy_2_1_0_0_0 = 2.
2545
+ >>> par
2546
+ par(toy_2_1_0_0_0=2.0,
2547
+ toy_3_1_0_0_0=5.0,
2548
+ toy_7_1_0_0_0=4.0)
2549
+
2550
+ For attribute access, zero hours, minutes, or seconds can be left out:
2551
+
2552
+ >>> par.toy_2_1
2553
+ 2.0
2554
+
2555
+ When using functions |getattr| and |delattr|, one can also omit the "toy" prefix:
2556
+
2557
+ >>> getattr(par, "2_1")
2558
+ 2.0
2559
+ >>> delattr(par, "2_1")
2560
+ >>> getattr(par, "2_1")
2561
+ Traceback (most recent call last):
2562
+ ...
2563
+ AttributeError: Seasonal parameter `par` of element `?` has neither a normal \
2564
+ attribute nor does it handle a "time of year" named `2_1`.
2565
+ >>> delattr(par, "2_1")
2566
+ Traceback (most recent call last):
2567
+ ...
2568
+ AttributeError: Seasonal parameter `par` of element `?` has neither a normal \
2569
+ attribute nor does it handle a "time of year" named `2_1`.
2570
+
2571
+ Applying the |len| operator on |SeasonalParameter| objects returns the number of
2572
+ toy-value pairs.
2573
+
2574
+ >>> len(par)
2575
+ 2
2576
+
2577
+ New values are checked to be compatible with the predefined shape:
2578
+
2579
+ >>> par.toy_1_1_0_0_0 = [1., 2.] # doctest: +ELLIPSIS
2580
+ Traceback (most recent call last):
2581
+ ...
2582
+ TypeError: While trying to add a new or change an existing toy-value pair for the \
2583
+ seasonal parameter `par` of element `?`, the following error occurred: float() \
2584
+ argument must be a string or a... number...
2585
+ >>> par = Par(None)
2586
+ >>> par.NDIM = 2
2587
+ >>> par.shape = (None, 3)
2588
+ >>> par.toy_1_1_0_0_0 = [1., 2.]
2589
+ Traceback (most recent call last):
2590
+ ...
2591
+ ValueError: While trying to add a new or change an existing toy-value pair for \
2592
+ the seasonal parameter `par` of element `?`, the following error occurred: could not \
2593
+ broadcast input array from shape (2,) into shape (3,)
2594
+
2595
+ If you do not require seasonally varying parameter values in a specific situation,
2596
+ you can pass a single positional argument:
2597
+
2598
+ >>> par(5.0)
2599
+ >>> par
2600
+ par([5.0, 5.0, 5.0])
2601
+
2602
+ Note that class |SeasonalParameter| associates the given value(s) to the "first"
2603
+ time of the year internally:
2604
+
2605
+ >>> par.toys
2606
+ (TOY("1_1_0_0_0"),)
2607
+
2608
+ Incompatible positional arguments result in errors like the following:
2609
+
2610
+ >>> par(1.0, 2.0)
2611
+ Traceback (most recent call last):
2612
+ ...
2613
+ ValueError: While trying to set the value(s) of variable `par`, the following \
2614
+ error occurred: While trying to convert the value(s) `[1. 2.]` to a numpy ndarray \
2615
+ with shape `(366, 3)` and type `float`, the following error occurred: could not \
2616
+ broadcast input array from shape (2,) into shape (366,3)
2617
+
2618
+ .. testsetup::
2619
+
2620
+ >>> del pub.timegrids
2621
+ """
2622
+
2623
+ TYPE = float
2624
+
2625
+ strict_valuehandling: bool = False
2626
+
2627
+ _toy2values_unprotected: list[tuple[timetools.TOY, float | NDArrayFloat]]
2628
+ _trimmed_insufficiently: bool
2629
+ _trimming_disabled: bool
2630
+
2631
+ def __init__(self, subvars) -> None:
2632
+ super().__init__(subvars)
2633
+ self._toy2values_unprotected = []
2634
+ self._trimming_disabled = False
2635
+ self._trimmed_insufficiently = False
2636
+
2637
+ @property
2638
+ def _toy2values_protected(self) -> list[tuple[timetools.TOY, float | NDArrayFloat]]:
2639
+ if self._trimmed_insufficiently and hydpy.pub.options.warntrim:
2640
+ warnings.warn(
2641
+ f'The "background values" of parameter '
2642
+ f"{objecttools.elementphrase(self)} have been trimmed but not its "
2643
+ f"original time of year-specific values. Using the latter without "
2644
+ f"modification might result in inconsistencies."
2645
+ )
2646
+ return self._toy2values_unprotected
2647
+
2648
+ def __call__(self, *args, **kwargs) -> None:
2649
+ if self.NDIM == 1:
2650
+ self.shape = (-1,)
2651
+ try:
2652
+ try:
2653
+ self._trimming_disabled = True
2654
+ super().__call__(*args, **kwargs)
2655
+ finally:
2656
+ self._trimming_disabled = False
2657
+ self._toy2values_unprotected = [(timetools.TOY(), self.values[0])]
2658
+ self.trim()
2659
+ except BaseException as exc:
2660
+ self._toy2values_unprotected = []
2661
+ if args:
2662
+ raise exc
2663
+ for toystr, values in kwargs.items():
2664
+ try:
2665
+ self._add_toyvaluepair(toystr, values)
2666
+ except BaseException:
2667
+ objecttools.augment_excmessage(
2668
+ f"While trying to define the seasonal parameter value "
2669
+ f"{objecttools.elementphrase(self)} for time of year `{toystr}`"
2670
+ )
2671
+ self.refresh()
2672
+
2673
+ def _add_toyvaluepair(self, name: str, value: float | NDArrayFloat) -> None:
2674
+ if self.NDIM == 1:
2675
+ value = float(value)
2676
+ else:
2677
+ value = numpy.full(self.shape[1:], value)
2678
+ toy_new = timetools.TOY(name)
2679
+ toy2values = self._toy2values_unprotected
2680
+ if len(toy2values) == 0:
2681
+ toy2values.append((toy_new, value))
2682
+ secs_new = toy_new.seconds_passed
2683
+ if secs_new > toy2values[-1][0].seconds_passed:
2684
+ toy2values.append((toy_new, value))
2685
+ else:
2686
+ for idx, (toy_old, _) in enumerate(toy2values[:]):
2687
+ secs_old = toy_old.seconds_passed
2688
+ if secs_new == secs_old:
2689
+ toy2values[idx] = toy_new, value
2690
+ break
2691
+ if secs_new < secs_old:
2692
+ toy2values.insert(idx, (toy_new, value))
2693
+ break
2694
+
2695
+ def refresh(self) -> None:
2696
+ """Update the actual simulation values based on the toy-value pairs.
2697
+
2698
+ Usually, one does not need to call refresh explicitly. The "magic" methods
2699
+ `__call__`, `__setattr__`, and `__delattr__` invoke it automatically, when
2700
+ required.
2701
+
2702
+ Method |SeasonalParameter.refresh| calculates only those time variable
2703
+ parameter values required for the defined initialisation period. We start with
2704
+ an initialisation period covering a full year, making a complete calculation
2705
+ necessary:
2706
+
2707
+ >>> from hydpy import pub
2708
+ >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
2709
+
2710
+ Instantiate a 1-dimensional |SeasonalParameter| object:
2711
+
2712
+ >>> from hydpy.core.parametertools import SeasonalParameter
2713
+ >>> class Par(SeasonalParameter):
2714
+ ... NDIM = 1
2715
+ ... TYPE = float
2716
+ ... TIME = None
2717
+ >>> par = Par(None)
2718
+ >>> par.shape = (None,)
2719
+
2720
+ When a |SeasonalParameter| object does not contain any toy-value pairs yet, the
2721
+ method |SeasonalParameter.refresh| sets all actual simulation values to zero:
2722
+
2723
+ >>> par.values = 1.0
2724
+ >>> par.refresh()
2725
+ >>> from hydpy import round_
2726
+ >>> round_(par.values[0])
2727
+ 0.0
2728
+
2729
+ When there is only one toy-value pair, its values are relevant for all actual
2730
+ simulation values:
2731
+
2732
+ >>> par.toy_1 = 2.0 # calls refresh automatically
2733
+ >>> round_(par.values[0])
2734
+ 2.0
2735
+
2736
+ Method |SeasonalParameter.refresh| performs a linear interpolation for the
2737
+ central time points of each simulation time step. Hence, in the following
2738
+ example, the original values of the toy-value pairs do not show up:
2739
+
2740
+ >>> par.toy_12_31 = 4.0
2741
+ >>> round_(par.values[0])
2742
+ 2.00274
2743
+ >>> round_(par.values[-2])
2744
+ 3.99726
2745
+ >>> round_(par.values[-1])
2746
+ 3.0
2747
+
2748
+ If one wants to preserve the original values in this example, one must set the
2749
+ corresponding toy instances in the middle of some simulation step intervals:
2750
+
2751
+ >>> del par.toy_1
2752
+ >>> del par.toy_12_31
2753
+ >>> par.toy_1_1_12 = 2
2754
+ >>> par.toy_12_31_12 = 4.0
2755
+ >>> round_(par.values[0])
2756
+ 2.0
2757
+ >>> round_(par.values[1])
2758
+ 2.005479
2759
+ >>> round_(par.values[-2])
2760
+ 3.994521
2761
+ >>> round_(par.values[-1])
2762
+ 4.0
2763
+
2764
+ For short initialisation periods, method |SeasonalParameter.refresh| performs
2765
+ only the required interpolations for efficiency:
2766
+
2767
+ >>> pub.timegrids = "2000-01-02", "2000-01-05", "1d"
2768
+ >>> Par.NDIM = 2
2769
+ >>> par = Par(None)
2770
+ >>> par.shape = (None, 3)
2771
+ >>> par.toy_1_2_12 = 2.0
2772
+ >>> par.toy_1_6_12 = 0.0, 2.0, 4.0
2773
+ >>> from hydpy import print_matrix
2774
+ >>> print_matrix(par.values[:6])
2775
+ | nan, nan, nan |
2776
+ | 2.0, 2.0, 2.0 |
2777
+ | 1.5, 2.0, 2.5 |
2778
+ | 1.0, 2.0, 3.0 |
2779
+ | nan, nan, nan |
2780
+ | nan, nan, nan |
2781
+
2782
+ .. testsetup::
2783
+
2784
+ >>> del pub.timegrids
2785
+ """
2786
+ toy2values = self._toy2values_unprotected
2787
+ if not toy2values:
2788
+ self._set_value(0.0)
2789
+ elif len(self) == 1:
2790
+ self.values[:] = self.apply_timefactor(toy2values[0][1])
2791
+ else:
2792
+ centred = timetools.TOY.centred_timegrid()
2793
+ values = self._get_value()
2794
+ for idx, (date, rel) in enumerate(zip(*centred)):
2795
+ values[idx] = self.interp(date) if rel else numpy.nan
2796
+ values = self.apply_timefactor(values)
2797
+ self._set_value(values)
2798
+ self.trim()
2799
+
2800
+ def interp(self, date: timetools.Date) -> float:
2801
+ """Perform a linear value interpolation for the given `date` and return the
2802
+ result.
2803
+
2804
+ Instantiate a 1-dimensional |SeasonalParameter| object:
2805
+
2806
+ >>> from hydpy import pub
2807
+ >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
2808
+ >>> from hydpy.core.parametertools import SeasonalParameter
2809
+ >>> class Par(SeasonalParameter):
2810
+ ... NDIM = 1
2811
+ ... TYPE = float
2812
+ ... TIME = None
2813
+ >>> par = Par(None)
2814
+ >>> par.shape = (None,)
2815
+
2816
+ Define three toy-value pairs:
2817
+
2818
+ >>> par(_1=2.0, _2=5.0, _12_31=4.0)
2819
+
2820
+ Passing a |Date| object matching a |TOY| object exactly returns the
2821
+ corresponding |float| value:
2822
+
2823
+ >>> from hydpy import Date
2824
+ >>> par.interp(Date("2000.01.01"))
2825
+ 2.0
2826
+ >>> par.interp(Date("2000.02.01"))
2827
+ 5.0
2828
+ >>> par.interp(Date("2000.12.31"))
2829
+ 4.0
2830
+
2831
+ For all intermediate points, |SeasonalParameter.interp| performs a linear
2832
+ interpolation:
2833
+
2834
+ >>> from hydpy import round_
2835
+ >>> round_(par.interp(Date("2000.01.02")))
2836
+ 2.096774
2837
+ >>> round_(par.interp(Date("2000.01.31")))
2838
+ 4.903226
2839
+ >>> round_(par.interp(Date("2000.02.02")))
2840
+ 4.997006
2841
+ >>> round_(par.interp(Date("2000.12.30")))
2842
+ 4.002994
2843
+
2844
+ Linear interpolation is also allowed between the first and the last pair when
2845
+ they do not capture the endpoints of the year:
2846
+
2847
+ >>> par(_1_2=2.0, _12_30=4.0)
2848
+ >>> round_(par.interp(Date("2000.12.29")))
2849
+ 3.99449
2850
+ >>> par.interp(Date("2000.12.30"))
2851
+ 4.0
2852
+ >>> round_(par.interp(Date("2000.12.31")))
2853
+ 3.333333
2854
+ >>> round_(par.interp(Date("2000.01.01")))
2855
+ 2.666667
2856
+ >>> par.interp(Date("2000.01.02"))
2857
+ 2.0
2858
+ >>> round_(par.interp(Date("2000.01.03")))
2859
+ 2.00551
2860
+
2861
+ The following example briefly shows interpolation performed for a 2-dimensional
2862
+ parameter:
2863
+
2864
+ >>> Par.NDIM = 2
2865
+ >>> par = Par(None)
2866
+ >>> par.shape = (None, 2)
2867
+ >>> par(_1_1=[1., 2.], _1_3=[-3, 0.])
2868
+ >>> result = par.interp(Date("2000.01.02"))
2869
+ >>> round_(result[0])
2870
+ -1.0
2871
+ >>> round_(result[1])
2872
+ 1.0
2873
+
2874
+ .. testsetup::
2875
+
2876
+ >>> del pub.timegrids
2877
+ """
2878
+ xnew = timetools.TOY(date)
2879
+ xys = list(self)
2880
+ for idx, (x1, y1) in enumerate(xys):
2881
+ if x1 > xnew:
2882
+ x0, y0 = xys[idx - 1]
2883
+ break
2884
+ else:
2885
+ x0, y0 = xys[-1]
2886
+ x1, y1 = xys[0]
2887
+ return y0 + (y1 - y0) / (x1 - x0) * (xnew - x0)
2888
+
2889
+ @property
2890
+ def toys(self) -> tuple[timetools.TOY, ...]:
2891
+ """A sorted |tuple| of all contained |TOY| objects."""
2892
+ return tuple(toy for toy, _ in self._toy2values_unprotected)
2893
+
2894
+ def _get_shape(self) -> tuple[int, ...]:
2895
+ """A tuple containing the actual lengths of all dimensions.
2896
+
2897
+ .. testsetup::
2898
+
2899
+ >>> from hydpy import pub
2900
+ >>> del pub.options.simulationstep
2901
+
2902
+ Setting the shape of |SeasonalParameter| objects differs from setting the shape
2903
+ of other |Variable| subclasses, due to handling time on the first axis. The
2904
+ simulation step size determines the length of this axis. Hence, trying to set
2905
+ the shape before the simulation step size is known does not work:
2906
+
2907
+ >>> from hydpy.core.parametertools import SeasonalParameter
2908
+ >>> class Par(SeasonalParameter):
2909
+ ... NDIM = 1
2910
+ ... TYPE = float
2911
+ ... TIME = None
2912
+ >>> par = Par(None)
2913
+ >>> par.shape = (None,)
2914
+ Traceback (most recent call last):
2915
+ ...
2916
+ RuntimeError: It is not possible the set the shape of the seasonal parameter \
2917
+ `par` of element `?` at the moment. You need to define the simulation step size \
2918
+ first. However, in complete HydPy projects this stepsize is indirectly defined via \
2919
+ `pub.timegrids.stepsize` automatically.
2920
+
2921
+ After preparing the simulation step size, you can pass a tuple with a single
2922
+ entry of any value to define the shape of the defined 1-dimensional test class.
2923
+ Property |SeasonalParameter.shape| replaces this arbitrary value by the number
2924
+ of simulation steps fitting into a leap year:
2925
+
2926
+ >>> from hydpy import pub
2927
+ >>> pub.options.simulationstep = "1d"
2928
+ >>> par.shape = (123,)
2929
+ >>> par.shape
2930
+ (366,)
2931
+
2932
+ Assigning a single, arbitrary value also works well:
2933
+
2934
+ >>> par.shape = None
2935
+ >>> par.shape
2936
+ (366,)
2937
+
2938
+ For higher-dimensional parameters, property |SeasonalParameter.shape| replaces
2939
+ the first entry of the assigned iterable, accordingly:
2940
+
2941
+ >>> Par.NDIM = 2
2942
+ >>> par.shape = (None, 3)
2943
+ >>> par.shape
2944
+ (366, 3)
2945
+
2946
+ For simulation steps not cleanly fitting into a leap year, the ceil-operation
2947
+ determines the number of entries:
2948
+
2949
+ >>> pub.options.simulationstep = "100d"
2950
+ >>> par.shape = (None, 3)
2951
+ >>> par.shape
2952
+ (4, 3)
2953
+ """
2954
+ return super()._get_shape()
2955
+
2956
+ def _set_shape(self, shape: int | tuple[int, ...]) -> None:
2957
+ if isinstance(shape, tuple):
2958
+ shape_ = list(shape)
2959
+ else:
2960
+ shape_ = [-1]
2961
+ simulationstep = hydpy.pub.options.simulationstep
2962
+ if not simulationstep:
2963
+ raise RuntimeError(
2964
+ f"It is not possible the set the shape of the seasonal parameter "
2965
+ f"{objecttools.elementphrase(self)} at the moment. You need to "
2966
+ f"define the simulation step size first. However, in complete HydPy "
2967
+ f"projects this stepsize is indirectly defined via "
2968
+ f"`pub.timegrids.stepsize` automatically."
2969
+ )
2970
+ shape_[0] = int(numpy.ceil(timetools.Period("366d") / simulationstep))
2971
+ shape_[0] = int(numpy.ceil(round(shape_[0], 10)))
2972
+ super()._set_shape(tuple(shape_))
2973
+
2974
+ shape = propertytools.Property(fget=_get_shape, fset=_set_shape)
2975
+
2976
+ def trim(self, lower=None, upper=None) -> bool:
2977
+ """Extend the usual trim logic with a warning mechanism to account for that
2978
+ trimming only affects "background values" and not the "visible" time of year /
2979
+ value pairs.
2980
+
2981
+ The usual trimming process affects the simulation-relevant "background values",
2982
+ not the "visible" original values supplied by the user. Hence,
2983
+ |SeasonalParameter| tries to keep track of recent trimmings to warn if users
2984
+ try to used the unmodified "visible" values later:
2985
+
2986
+ >>> from hydpy import pub, round_
2987
+ >>> pub.timegrids = "2000-01-01", "2000-01-04", "1d"
2988
+ >>> from hydpy.models.sw1d import *
2989
+ >>> parameterstep()
2990
+ >>> upperlowwaterthreshold.shape = 1
2991
+ >>> upperlowwaterthreshold.values[:3] = 1.0, 2.0, 3.0
2992
+ >>> bottomlowwaterthreshold(2.0)
2993
+ >>> round_(bottomlowwaterthreshold.values[:3])
2994
+ 1.0, 2.0, 2.0
2995
+ >>> from hydpy.core.testtools import warn_later
2996
+ >>> with pub.options.warntrim(True), warn_later():
2997
+ ... bottomlowwaterthreshold
2998
+ bottomlowwaterthreshold(2.0)
2999
+ UserWarning: The "background values" of parameter `bottomlowwaterthreshold` \
3000
+ of element `?` have been trimmed but not its original time of year-specific values. \
3001
+ Using the latter without modification might result in inconsistencies.
3002
+
3003
+ In such cases, modify the values of the affected parameter or its boundaries
3004
+ and call |SeasonalParameter.refresh| manually. If successful, the warning
3005
+ disappears:
3006
+
3007
+ >>> upperlowwaterthreshold.values[0] = 2.0
3008
+ >>> bottomlowwaterthreshold.refresh()
3009
+ >>> with pub.options.warntrim(True):
3010
+ ... bottomlowwaterthreshold
3011
+ bottomlowwaterthreshold(2.0)
3012
+
3013
+ The explained mechanism equivalently works when defining seasonally varying
3014
+ parameter values and trying to access the unmodified "visible" values in other
3015
+ ways:
3016
+
3017
+ >>> bottomlowwaterthreshold(_1_1_12=3.0, _1_3_12=1.0)
3018
+ >>> round_(bottomlowwaterthreshold.values[:3])
3019
+ 2.0, 2.0, 1.0
3020
+ >>> with pub.options.warntrim(True), warn_later():
3021
+ ... round_(bottomlowwaterthreshold.toy_1_1_12) # doctest: +ELLIPSIS
3022
+ 3.0
3023
+ UserWarning: The "background values"...result in inconsistencies.
3024
+ >>> with pub.options.warntrim(True), warn_later():
3025
+ ... tuple(bottomlowwaterthreshold) # doctest: +ELLIPSIS
3026
+ ((TOY("1_1_12_0_0"), 3.0), (TOY("1_3_12_0_0"), 1.0))
3027
+ UserWarning: The "background values"...result in inconsistencies.
3028
+ >>> with pub.options.warntrim(True), warn_later():
3029
+ ... len(bottomlowwaterthreshold) # doctest: +ELLIPSIS
3030
+ 2
3031
+ UserWarning: The "background values"...result in inconsistencies.
3032
+
3033
+ .. testsetup::
3034
+
3035
+ >>> del pub.timegrids
3036
+ """
3037
+ if not self._trimming_disabled:
3038
+ self._trimmed_insufficiently = super().trim(lower, upper)
3039
+ return self._trimmed_insufficiently
3040
+
3041
+ def __iter__(self) -> Iterator[tuple[timetools.TOY, Any]]:
3042
+ return iter(self._toy2values_protected)
3043
+
3044
+ def __getattr__(self, name: str) -> float | NDArrayFloat:
3045
+ selected = timetools.TOY(name)
3046
+ for available, value in self._toy2values_protected:
3047
+ if selected == available:
3048
+ return value
3049
+ raise AttributeError(
3050
+ f"Seasonal parameter {objecttools.elementphrase(self)} has neither a "
3051
+ f'normal attribute nor does it handle a "time of year" named `{name}`.'
3052
+ )
3053
+
3054
+ def __setattr__(self, name: str, value: float | NDArrayFloat) -> None:
3055
+ if name.startswith("toy_"):
3056
+ try:
3057
+ self._add_toyvaluepair(name, value)
3058
+ self.refresh()
3059
+ except BaseException:
3060
+ objecttools.augment_excmessage(
3061
+ f"While trying to add a new or change an existing toy-value pair "
3062
+ f"for the seasonal parameter {objecttools.elementphrase(self)}"
3063
+ )
3064
+ else:
3065
+ super().__setattr__(name, value)
3066
+
3067
+ def __delattr__(self, name: str) -> None:
3068
+ try:
3069
+ super().__delattr__(name)
3070
+ except AttributeError:
3071
+ selected = timetools.TOY(name)
3072
+ for idx, (available, _) in enumerate(self._toy2values_unprotected):
3073
+ if selected == available:
3074
+ break
3075
+ else:
3076
+ raise AttributeError(
3077
+ f"Seasonal parameter {objecttools.elementphrase(self)} has "
3078
+ f'neither a normal attribute nor does it handle a "time of year" '
3079
+ f"named `{name}`."
3080
+ ) from None
3081
+ del self._toy2values_unprotected[idx]
3082
+ self.refresh()
3083
+
3084
+ def __repr__(self) -> str:
3085
+ def _assignrepr(value_, prefix_):
3086
+ if self.NDIM == 1:
3087
+ return objecttools.assignrepr_value(value_, prefix_)
3088
+ return objecttools.assignrepr_list(value_, prefix_, width=79)
3089
+
3090
+ toy2values = self._toy2values_protected
3091
+ if not toy2values:
3092
+ return f"{self.name}()"
3093
+ toy0 = timetools.TOY0
3094
+ if (len(self) == 1) and (toy0 == toy2values[0][0]):
3095
+ return f'{_assignrepr(toy2values[0][1], f"{self.name}(")})'
3096
+ lines = []
3097
+ blanks = " " * (len(self.name) + 1)
3098
+ for idx, (toy, value) in enumerate(self):
3099
+ if idx == 0:
3100
+ lines.append(_assignrepr(value, f"{self.name}({toy}="))
3101
+ else:
3102
+ lines.append(_assignrepr(value, f"{blanks}{toy}="))
3103
+ lines[-1] += ")"
3104
+ return ",\n".join(lines)
3105
+
3106
+ def __len__(self) -> int:
3107
+ return len(self._toy2values_protected)
3108
+
3109
+ def __dir__(self) -> list[str]:
3110
+ """
3111
+
3112
+ >>> from hydpy import pub
3113
+ >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
3114
+ >>> from hydpy.core.parametertools import SeasonalParameter
3115
+ >>> class Par(SeasonalParameter):
3116
+ ... NDIM = 1
3117
+ ... TIME = None
3118
+ >>> par = Par(None)
3119
+ >>> par.NDIM = 1
3120
+ >>> par.shape = (None,)
3121
+ >>> par(_1=2., _7_1=4., _3_1_0_0_0=5.)
3122
+ >>> sorted(set(dir(par)) - set(object.__dir__(par)))
3123
+ ['toy_1_1_0_0_0', 'toy_3_1_0_0_0', 'toy_7_1_0_0_0']
3124
+
3125
+ .. testsetup::
3126
+
3127
+ >>> del pub.timegrids
3128
+ """
3129
+ return cast(list[str], super().__dir__()) + [str(toy) for (toy, dummy) in self]
3130
+
3131
+
3132
+ class KeywordParameter1D(_MixinModifiableParameter, Parameter):
3133
+ """Base class for 1-dimensional model parameters with values depending on one
3134
+ factor.
3135
+
3136
+ When subclassing from |KeywordParameter1D|, one needs to define the class attribute
3137
+ `entrynames`. A typical use case is that `entrynames` defines seasons like the
3138
+ months or, as in our example, half-years:
3139
+
3140
+ >>> from hydpy.core.parametertools import KeywordParameter1D
3141
+ >>> class IsHot(KeywordParameter1D):
3142
+ ... TYPE = bool
3143
+ ... TIME = None
3144
+ ... entrynames = ("winter", "summer")
3145
+
3146
+ Usually, |KeywordParameter1D| objects prepare their shape automatically. However,
3147
+ to simplify this test case, we define it manually:
3148
+
3149
+ >>> ishot = IsHot(None)
3150
+ >>> ishot.shape = 2
3151
+
3152
+ You can pass all parameter values both via positional or keyword arguments:
3153
+
3154
+ >>> ishot(True)
3155
+ >>> ishot
3156
+ ishot(True)
3157
+
3158
+ >>> ishot(False, True)
3159
+ >>> ishot
3160
+ ishot(winter=False, summer=True)
3161
+
3162
+ >>> from hydpy import print_vector
3163
+ >>> ishot(winter=True, summer=False)
3164
+ >>> ishot
3165
+ ishot(winter=True, summer=False)
3166
+ >>> print_vector(ishot.values)
3167
+ True, False
3168
+
3169
+ We check the given keyword arguments for correctness and completeness:
3170
+
3171
+ >>> ishot(winter=True)
3172
+ Traceback (most recent call last):
3173
+ ...
3174
+ ValueError: When setting parameter `ishot` of element `?` via keyword arguments, \
3175
+ each string defined in `entrynames` must be used as a keyword, but the following \
3176
+ keywords are not: `summer`.
3177
+
3178
+ >>> ishot(winter=True, summer=False, spring=True, autumn=False)
3179
+ Traceback (most recent call last):
3180
+ ...
3181
+ ValueError: When setting parameter `ishot` of element `?` via keyword arguments, \
3182
+ each keyword must be defined in `entrynames`, but the following keywords are not: \
3183
+ `spring and autumn`.
3184
+
3185
+ Class |KeywordParameter1D| implements attribute access, including specialised error
3186
+ messages:
3187
+
3188
+ >>> ishot.winter, ishot.summer = ishot.summer, ishot.winter
3189
+ >>> ishot
3190
+ ishot(winter=False, summer=True)
3191
+
3192
+ >>> ishot.spring
3193
+ Traceback (most recent call last):
3194
+ ...
3195
+ AttributeError: Parameter `ishot` of element `?` does not handle an attribute \
3196
+ named `spring`.
3197
+
3198
+ >>> ishot.shape = 1
3199
+ >>> ishot.summer
3200
+ Traceback (most recent call last):
3201
+ ...
3202
+ IndexError: While trying to retrieve a value from parameter `ishot` of element \
3203
+ `?` via the attribute `summer`, the following error occurred: index 1 is out of \
3204
+ bounds for axis 0 with size 1
3205
+
3206
+ >>> ishot.summer = True
3207
+ Traceback (most recent call last):
3208
+ ...
3209
+ IndexError: While trying to assign a new value to parameter `ishot` of element \
3210
+ `?` via attribute `summer`, the following error occurred: index 1 is out of bounds \
3211
+ for axis 0 with size 1
3212
+ """
3213
+
3214
+ NDIM = 1
3215
+ entrynames: tuple[str, ...]
3216
+ entrymin: int = 0
3217
+
3218
+ strict_valuehandling: bool = False
3219
+
3220
+ def __init__(self, subvars: SubParameters) -> None:
3221
+ super().__init__(subvars)
3222
+ self.entrynames = type(self).entrynames
3223
+ self.entrymin = type(self).entrymin
3224
+
3225
+ @classmethod
3226
+ @contextlib.contextmanager
3227
+ def modify_entries(cls, constants: Constants | None) -> Generator[None, None, None]:
3228
+ """Modify the relevant entry names temporarily.
3229
+
3230
+ The entry names for defining properties like land-use types are fixed for
3231
+ typical "main models" like |hland|. However, some submodels must take over the
3232
+ entry names defined by their current main model, which are only known at
3233
+ runtime. For example, consider the following simple `LAI` parameter that
3234
+ handles predefined land use type names as a class attribute:
3235
+
3236
+ >>> from hydpy.core.parametertools import KeywordParameter1D
3237
+ >>> class LAI(KeywordParameter1D):
3238
+ ... TYPE = float
3239
+ ... TIME = None
3240
+ ... entrynames = ("field", "forest")
3241
+ >>> lai1 = LAI(None)
3242
+ >>> lai1.shape = 2
3243
+ >>> lai1(field=1.0, forest=2.0)
3244
+ >>> lai1
3245
+ lai(field=1.0, forest=2.0)
3246
+
3247
+ We can use |KeywordParameter1D.modify_entries| to temporarily change these
3248
+ names:
3249
+
3250
+ >>> from hydpy.core.parametertools import Constants
3251
+ >>> constants = Constants(GRASS=2, SOIL=1, TREES=3)
3252
+ >>> with LAI.modify_entries(constants):
3253
+ ... lai2 = LAI(None)
3254
+ ... lai2.shape = 3
3255
+ ... lai2(soil=0.0, grass=1.0, trees=2.0)
3256
+ ... lai2
3257
+ lai(soil=0.0, grass=1.0, trees=2.0)
3258
+
3259
+ During initialisation, the names and the lowest value of the given constants
3260
+ become instance attributes, so the parameter instance does not forget them
3261
+ after leaving the `with` block (when the class attribute is reset to its
3262
+ previous value):
3263
+
3264
+ >>> lai1.entrynames
3265
+ ('field', 'forest')
3266
+ >>> lai2.entrynames
3267
+ ('soil', 'grass', 'trees')
3268
+ >>> LAI.entrynames
3269
+ ('field', 'forest')
3270
+
3271
+ >>> lai1.entrymin
3272
+ 0
3273
+ >>> lai2.entrymin
3274
+ 1
3275
+ >>> LAI.entrymin
3276
+ 0
3277
+
3278
+ Passing |None| does not overwrite the default or the previously set references:
3279
+
3280
+ >>> LAI.entrymin = 3
3281
+ >>> with LAI.modify_entries(None):
3282
+ ... LAI.entrynames
3283
+ ... LAI.entrymin
3284
+ ... lai3 = LAI(None)
3285
+ ... lai3.shape = 2
3286
+ ... lai3(field=2.0, forest=1.0)
3287
+ ... lai3
3288
+ ('field', 'forest')
3289
+ 3
3290
+ lai(field=2.0, forest=1.0)
3291
+ >>> LAI.entrynames
3292
+ ('field', 'forest')
3293
+ >>> LAI.entrymin
3294
+ 3
3295
+ >>> lai3
3296
+ lai(field=2.0, forest=1.0)
3297
+ """
3298
+ if constants is None:
3299
+ yield
3300
+ else:
3301
+ get = vars(cls).get
3302
+ old_names = get("entrynames")
3303
+ old_min = get("entrymin")
3304
+ try:
3305
+ cls.entrynames = constants.get_sortednames()
3306
+ cls.entrymin = min(constants.values())
3307
+ yield
3308
+ finally:
3309
+ cls._reset_after_modification("entrynames", old_names)
3310
+ cls._reset_after_modification("entrymin", old_min)
3311
+
3312
+ def __hydpy__connect_variable2subgroup__(self) -> None:
3313
+ super().__hydpy__connect_variable2subgroup__()
3314
+ self.shape = len(self.entrynames)
3315
+ setattr(self.fastaccess, f"_{self.name}_entrymin", self.entrymin)
3316
+
3317
+ def __call__(self, *args, **kwargs) -> None:
3318
+ try:
3319
+ super().__call__(*args, **kwargs)
3320
+ except NotImplementedError:
3321
+ for idx, key in enumerate(self.entrynames):
3322
+ try:
3323
+ self.values[idx] = self.apply_timefactor(kwargs[key])
3324
+ except KeyError:
3325
+ err = (key for key in self.entrynames if key not in kwargs)
3326
+ raise ValueError(
3327
+ f"When setting parameter {objecttools.elementphrase(self)} "
3328
+ f"via keyword arguments, each string defined in `entrynames` "
3329
+ f"must be used as a keyword, but the following keywords are "
3330
+ f"not: `{objecttools.enumeration(err)}`."
3331
+ ) from None
3332
+ if len(kwargs) != len(self.entrynames):
3333
+ err = (key for key in kwargs if key not in self.entrynames)
3334
+ raise ValueError(
3335
+ f"When setting parameter {objecttools.elementphrase(self)} via "
3336
+ f"keyword arguments, each keyword must be defined in "
3337
+ f"`entrynames`, but the following keywords are not: "
3338
+ f"`{objecttools.enumeration(err)}`."
3339
+ ) from None
3340
+
3341
+ def __getattr__(self, key):
3342
+ if key in self.entrynames:
3343
+ try:
3344
+ return self.TYPE(self.values[self.entrynames.index(key)])
3345
+ except BaseException:
3346
+ objecttools.augment_excmessage(
3347
+ f"While trying to retrieve a value from parameter "
3348
+ f"{objecttools.elementphrase(self)} via the attribute `{key}`"
3349
+ )
3350
+ raise AttributeError(
3351
+ f"Parameter {objecttools.elementphrase(self)} does not handle an "
3352
+ f"attribute named `{key}`."
3353
+ )
3354
+
3355
+ def __setattr__(self, key, values):
3356
+ if key in self.entrynames:
3357
+ try:
3358
+ self.values[self.entrynames.index(key)] = values
3359
+ except BaseException:
3360
+ objecttools.augment_excmessage(
3361
+ f"While trying to assign a new value to parameter "
3362
+ f"{objecttools.elementphrase(self)} via attribute `{key}`"
3363
+ )
3364
+ else:
3365
+ super().__setattr__(key, values)
3366
+
3367
+ def __repr__(self):
3368
+ values = self.revert_timefactor(self.values)
3369
+ prefix = f"{self.name}("
3370
+ lines = []
3371
+ if len(numpy.unique(values)) == 1:
3372
+ lines.append(f"{prefix}{objecttools.repr_(values[0])})")
3373
+ else:
3374
+ blanks = " " * len(prefix)
3375
+ string = ", ".join(
3376
+ f"{key}={objecttools.repr_(value)}"
3377
+ for key, value in zip(self.entrynames, values)
3378
+ )
3379
+ for idx, substring in enumerate(
3380
+ textwrap.wrap(
3381
+ text=string, width=max(70 - len(prefix), 30), break_long_words=False
3382
+ )
3383
+ ):
3384
+ if idx:
3385
+ lines.append(f"{blanks}{substring}")
3386
+ else:
3387
+ lines.append(f"{prefix}{substring}")
3388
+ lines[-1] += ")"
3389
+ return "\n".join(lines)
3390
+
3391
+ def __dir__(self) -> list[str]:
3392
+ """
3393
+ >>> from hydpy.core.parametertools import KeywordParameter1D
3394
+ >>> class Season(KeywordParameter1D):
3395
+ ... TYPE = bool
3396
+ ... TIME = None
3397
+ ... entrynames = ("winter", "summer")
3398
+ >>> season = Season(None)
3399
+ >>> sorted(set(dir(season)) - set(object.__dir__(season)))
3400
+ ['summer', 'winter']
3401
+ """
3402
+ return cast(list[str], super().__dir__()) + list(self.entrynames)
3403
+
3404
+
3405
+ class MonthParameter(KeywordParameter1D):
3406
+ """Base class for parameters whose values depend on the actual month.
3407
+
3408
+ Please see the documentation on class |KeywordParameter1D| on how to use
3409
+ |MonthParameter| objects and class |lland_control.WG2Z| of base model |lland| as an
3410
+ example implementation:
3411
+
3412
+ >>> from hydpy.models.lland import *
3413
+ >>> simulationstep("12h")
3414
+ >>> parameterstep("1d")
3415
+ >>> wg2z(3.0, 2.0, 1.0, 0.0, -1.0, -2.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0)
3416
+ >>> wg2z
3417
+ wg2z(jan=3.0, feb=2.0, mar=1.0, apr=0.0, may=-1.0, jun=-2.0, jul=-3.0,
3418
+ aug=-2.0, sep=-1.0, oct=0.0, nov=1.0, dec=2.0)
3419
+
3420
+ Note that attribute access provides access to the "real" values related to the
3421
+ current simulation time step:
3422
+
3423
+ >>> wg2z.feb
3424
+ 2.0
3425
+ >>> wg2z.feb = 4.0
3426
+ >>> wg2z
3427
+ wg2z(jan=3.0, feb=4.0, mar=1.0, apr=0.0, may=-1.0, jun=-2.0, jul=-3.0,
3428
+ aug=-2.0, sep=-1.0, oct=0.0, nov=1.0, dec=2.0)
3429
+ """
3430
+
3431
+ entrynames = (
3432
+ "jan",
3433
+ "feb",
3434
+ "mar",
3435
+ "apr",
3436
+ "may",
3437
+ "jun",
3438
+ "jul",
3439
+ "aug",
3440
+ "sep",
3441
+ "oct",
3442
+ "nov",
3443
+ "dec",
3444
+ )
3445
+
3446
+
3447
+ class KeywordParameter2D(_MixinModifiableParameter, Parameter):
3448
+ """Base class for 2-dimensional model parameters with values depending on two
3449
+ factors.
3450
+
3451
+ When subclassing from |KeywordParameter2D| one needs to define the attributes
3452
+ `rownames` and `columnnames` (both of type |tuple|). A typical use case is that
3453
+ `rownames` defines some land-use classes, and `columnnames` defines seasons,
3454
+ months, etc. Here, we consider a simple corresponding example where the values of
3455
+ the boolean parameter `IsWarm` depend on the on the hemisphere and the half-year
3456
+ period:
3457
+
3458
+ >>> from hydpy.core.parametertools import KeywordParameter2D
3459
+ >>> class IsWarm(KeywordParameter2D):
3460
+ ... TYPE = bool
3461
+ ... TIME = None
3462
+ ... rownames = ("north", "south")
3463
+ ... columnnames = ("apr2sep", "oct2mar")
3464
+
3465
+ Instantiate the defined parameter class and define its shape:
3466
+
3467
+ >>> iswarm = IsWarm(None)
3468
+ >>> iswarm.shape = (2, 2)
3469
+
3470
+ |KeywordParameter2D| allows us to set the values of all rows via keyword arguments:
3471
+
3472
+ >>> from hydpy import print_matrix
3473
+ >>> iswarm(north=[True, False],
3474
+ ... south=[False, True])
3475
+ >>> iswarm
3476
+ iswarm(north=[True, False],
3477
+ south=[False, True])
3478
+ >>> print_matrix(iswarm.values)
3479
+ | True, False |
3480
+ | False, True |
3481
+
3482
+ If a keyword is missing, it raises a |ValueError|:
3483
+
3484
+ >>> iswarm(north=[True, False])
3485
+ Traceback (most recent call last):
3486
+ ...
3487
+ ValueError: While setting parameter `iswarm` of element `?` via row related \
3488
+ keyword arguments, each string defined in `rownames` must be used as a keyword, but \
3489
+ the following keywords are not: `south`.
3490
+
3491
+ One can modify single rows via attribute access:
3492
+
3493
+ >>> from hydpy import print_vector
3494
+ >>> iswarm.north = False, False
3495
+ >>> print_vector(iswarm.north)
3496
+ False, False
3497
+
3498
+ The same holds for the columns:
3499
+
3500
+ >>> iswarm.apr2sep = True, False
3501
+ >>> print_vector(iswarm.apr2sep)
3502
+ True, False
3503
+
3504
+ Also, combined row-column access is possible:
3505
+
3506
+ >>> iswarm.north_apr2sep
3507
+ True
3508
+ >>> iswarm.north_apr2sep = False
3509
+ >>> iswarm.north_apr2sep
3510
+ False
3511
+
3512
+ All three forms of attribute access define augmented exception messages in case
3513
+ anything goes wrong:
3514
+
3515
+ >>> iswarm.north = True, True, True
3516
+ Traceback (most recent call last):
3517
+ ...
3518
+ ValueError: While trying to assign new values to parameter `iswarm` of element \
3519
+ `?` via the row related attribute `north`, the following error occurred: could not \
3520
+ broadcast input array from shape (3,) into shape (2,)
3521
+ >>> iswarm.apr2sep = True, True, True
3522
+ Traceback (most recent call last):
3523
+ ...
3524
+ ValueError: While trying to assign new values to parameter `iswarm` of element \
3525
+ `?` via the column related attribute `apr2sep`, the following error occurred: could \
3526
+ not broadcast input array from shape (3,) into shape (2,)
3527
+
3528
+ >>> iswarm.shape = (1, 1)
3529
+
3530
+ >>> iswarm.south_apr2sep = False
3531
+ Traceback (most recent call last):
3532
+ ...
3533
+ IndexError: While trying to assign new values to parameter `iswarm` of element \
3534
+ `?` via the row and column related attribute `south_apr2sep`, the following error \
3535
+ occurred: index 1 is out of bounds for axis 0 with size 1
3536
+
3537
+ >>> iswarm.south
3538
+ Traceback (most recent call last):
3539
+ ...
3540
+ IndexError: While trying to retrieve values from parameter `iswarm` of element \
3541
+ `?` via the row related attribute `south`, the following error occurred: index 1 is \
3542
+ out of bounds for axis 0 with size 1
3543
+ >>> iswarm.oct2mar
3544
+ Traceback (most recent call last):
3545
+ ...
3546
+ IndexError: While trying to retrieve values from parameter `iswarm` of element \
3547
+ `?` via the column related attribute `oct2mar`, the following error occurred: index 1 \
3548
+ is out of bounds for axis 1 with size 1
3549
+ >>> iswarm.south_oct2mar
3550
+ Traceback (most recent call last):
3551
+ ...
3552
+ IndexError: While trying to retrieve values from parameter `iswarm` of element \
3553
+ `?` via the row and column related attribute `south_oct2mar`, the following error \
3554
+ occurred: index 1 is out of bounds for axis 0 with size 1
3555
+
3556
+ >>> iswarm.shape = (2, 2)
3557
+
3558
+ Unknown attribute names result in the following error:
3559
+
3560
+ >>> iswarm.wrong
3561
+ Traceback (most recent call last):
3562
+ ...
3563
+ AttributeError: Parameter `iswarm` of element `?` does neither handle a normal \
3564
+ attribute nor a row or column related attribute named `wrong`.
3565
+
3566
+ One can still define the parameter values via positional arguments:
3567
+
3568
+ >>> iswarm(True)
3569
+ >>> iswarm
3570
+ iswarm(north=[True, True],
3571
+ south=[True, True])
3572
+
3573
+ For parameters with many columns, string representations are adequately wrapped:
3574
+
3575
+ >>> iswarm.shape = (2, 10)
3576
+ >>> iswarm
3577
+ iswarm(north=[False, False, False, False, False, False, False, False,
3578
+ False, False],
3579
+ south=[False, False, False, False, False, False, False, False,
3580
+ False, False])
3581
+ """
3582
+
3583
+ NDIM = 2
3584
+ rownames: tuple[str, ...]
3585
+ columnnames: tuple[str, ...]
3586
+ rowmin: int = 0
3587
+ columnmin: int = 0
3588
+
3589
+ strict_valuehandling: bool = False
3590
+
3591
+ _rowcolumnmappings: dict[str, tuple[int, int]]
3592
+
3593
+ def __init__(self, subvars: SubParameters) -> None:
3594
+ super().__init__(subvars)
3595
+ self.rownames = type(self).rownames
3596
+ self.columnnames = type(self).columnnames
3597
+ self._rowcolumnmappings = self._make_rowcolumnmappings(
3598
+ rownames=self.rownames, columnnames=self.columnnames
3599
+ )
3600
+ self.rowmin = type(self).rowmin
3601
+ self.columnmin = type(self).columnmin
3602
+
3603
+ @classmethod
3604
+ @contextlib.contextmanager
3605
+ def modify_rows(cls, constants: Constants | None) -> Generator[None, None, None]:
3606
+ """Modify the relevant row names temporarily.
3607
+
3608
+ Methods |KeywordParameter2D.modify_rows| and |KeywordParameter2D.modify_columns|
3609
+ serve the same purpose and behave exactly on the respective axis
3610
+ |KeywordParameter2D| instances as method |KeywordParameter1D.modify_entries| on
3611
+ the single axis of |KeywordParameter1D| instances. Hence, we only test their
3612
+ implementation here. Please read the documentation on method
3613
+ |KeywordParameter1D.modify_entries| for more information:
3614
+
3615
+ >>> from hydpy import print_vector
3616
+ >>> from hydpy.core.parametertools import KeywordParameter2D
3617
+ >>> class IsWarm(KeywordParameter2D):
3618
+ ... TYPE = bool
3619
+ ... TIME = None
3620
+ ... rownames = ("north", "south")
3621
+ ... columnnames = ("apr2sep", "oct2mar")
3622
+ >>> iswarm1 = IsWarm(None)
3623
+ >>> iswarm1.shape = (2, 2)
3624
+ >>> iswarm1(north=[True, False],
3625
+ ... south=[False, True])
3626
+ >>> print_vector(iswarm1.north)
3627
+ True, False
3628
+ >>> print_vector(iswarm1.apr2sep)
3629
+ True, False
3630
+
3631
+ >>> from hydpy.core.parametertools import Constants
3632
+ >>> consts_row = Constants(N=1, S=2)
3633
+ >>> consts_column = Constants(APR2JUN=2, JUN2SEP=3, OCT2DEC=4, JAN2MAR=5)
3634
+ >>> with IsWarm.modify_rows(consts_row), IsWarm.modify_columns(consts_column):
3635
+ ... iswarm2 = IsWarm(None)
3636
+ ... iswarm2.shape = (2, 4)
3637
+ ... iswarm2(n=[True, True, False, False],
3638
+ ... s=[False, False, True, True])
3639
+ ... print_vector(iswarm2.n)
3640
+ ... print_vector(iswarm2.apr2jun)
3641
+ True, True, False, False
3642
+ True, False
3643
+
3644
+ >>> iswarm1.rownames
3645
+ ('north', 'south')
3646
+ >>> iswarm1.columnnames
3647
+ ('apr2sep', 'oct2mar')
3648
+ >>> iswarm1.rowmin
3649
+ 0
3650
+ >>> iswarm1.columnmin
3651
+ 0
3652
+
3653
+ >>> iswarm2.rownames
3654
+ ('n', 's')
3655
+ >>> iswarm2.columnnames
3656
+ ('apr2jun', 'jun2sep', 'oct2dec', 'jan2mar')
3657
+ >>> iswarm2.rowmin
3658
+ 1
3659
+ >>> iswarm2.columnmin
3660
+ 2
3661
+
3662
+ >>> IsWarm.rownames
3663
+ ('north', 'south')
3664
+ >>> IsWarm.columnnames
3665
+ ('apr2sep', 'oct2mar')
3666
+ >>> IsWarm.rowmin
3667
+ 0
3668
+ >>> IsWarm.columnmin
3669
+ 0
3670
+
3671
+ >>> IsWarm.rowmin = 2
3672
+ >>> IsWarm.columnmin = 3
3673
+ >>> with IsWarm.modify_rows(None), IsWarm.modify_columns(None):
3674
+ ... IsWarm.rownames
3675
+ ... IsWarm.rowmin
3676
+ ... IsWarm.columnnames
3677
+ ... IsWarm.columnmin
3678
+ ... iswarm3 = IsWarm(None)
3679
+ ... iswarm3.shape = (2, 2)
3680
+ ... iswarm3(north=[True, False],
3681
+ ... south=[False, True])
3682
+ ... iswarm3
3683
+ ... print_vector(iswarm1.north)
3684
+ ('north', 'south')
3685
+ 2
3686
+ ('apr2sep', 'oct2mar')
3687
+ 3
3688
+ iswarm(north=[True, False],
3689
+ south=[False, True])
3690
+ True, False
3691
+ >>> IsWarm.rownames
3692
+ ('north', 'south')
3693
+ >>> IsWarm.columnnames
3694
+ ('apr2sep', 'oct2mar')
3695
+ >>> IsWarm.rowmin
3696
+ 2
3697
+ >>> IsWarm.columnmin
3698
+ 3
3699
+ >>> iswarm3
3700
+ iswarm(north=[True, False],
3701
+ south=[False, True])
3702
+ >>> print_vector(iswarm1.north)
3703
+ True, False
3704
+ """
3705
+ if constants is None:
3706
+ yield
3707
+ else:
3708
+ get = vars(cls).get
3709
+ old_names = get("rownames")
3710
+ old_min = get("rowmin")
3711
+ try:
3712
+ cls.rownames = constants.get_sortednames()
3713
+ cls.rowmin = min(constants.values())
3714
+ yield
3715
+ finally:
3716
+ cls._reset_after_modification("rownames", old_names)
3717
+ cls._reset_after_modification("rowmin", old_min)
3718
+
3719
+ @classmethod
3720
+ @contextlib.contextmanager
3721
+ def modify_columns(cls, constants: Constants | None) -> Generator[None, None, None]:
3722
+ """Modify the relevant column names temporarily.
3723
+
3724
+ Please see the documentation on method |KeywordParameter2D.modify_rows| for
3725
+ further information.
3726
+ """
3727
+ if constants is None:
3728
+ yield
3729
+ else:
3730
+ get = vars(cls).get
3731
+ old_names = get("columnnames")
3732
+ old_min = get("columnmin")
3733
+ try:
3734
+ cls.columnnames = constants.get_sortednames()
3735
+ cls.columnmin = min(constants.values())
3736
+ yield
3737
+ finally:
3738
+ cls._reset_after_modification("columnnames", old_names)
3739
+ cls._reset_after_modification("columnmin", old_min)
3740
+
3741
+ @classmethod
3742
+ def _make_rowcolumnmappings(
3743
+ cls, rownames: tuple[str, ...], columnnames: tuple[str, ...]
3744
+ ) -> dict[str, tuple[int, int]]:
3745
+ rowcolmappings = {}
3746
+ for idx, rowname in enumerate(rownames):
3747
+ for jdx, colname in enumerate(columnnames):
3748
+ rowcolmappings["_".join((rowname, colname))] = (idx, jdx)
3749
+ return rowcolmappings
3750
+
3751
+ def __init_subclass__(cls) -> None:
3752
+ super().__init_subclass__()
3753
+ cls._rowcolumnmappings = cls._make_rowcolumnmappings(
3754
+ rownames=cls.rownames, columnnames=cls.columnnames
3755
+ )
3756
+
3757
+ def __hydpy__connect_variable2subgroup__(self) -> None:
3758
+ super().__hydpy__connect_variable2subgroup__()
3759
+ self.shape = (len(self.rownames), len(self.columnnames))
3760
+ setattr(self.fastaccess, f"_{self.name}_rowmin", self.rowmin)
3761
+ setattr(self.fastaccess, f"_{self.name}_columnmin", self.columnmin)
3762
+
3763
+ def __call__(self, *args, **kwargs) -> None:
3764
+ try:
3765
+ super().__call__(*args, **kwargs)
3766
+ except NotImplementedError:
3767
+ for idx, key in enumerate(self.rownames):
3768
+ try:
3769
+ self.values[idx, :] = self.apply_timefactor(kwargs[key])
3770
+ except KeyError:
3771
+ miss = [key for key in self.rownames if key not in kwargs]
3772
+ raise ValueError(
3773
+ f"While setting parameter "
3774
+ f"{objecttools.elementphrase(self)} via row related keyword "
3775
+ f"arguments, each string defined in `rownames` must be used "
3776
+ f"as a keyword, but the following keywords are not: "
3777
+ f"`{objecttools.enumeration(miss)}`."
3778
+ ) from None
3779
+ self.trim()
3780
+
3781
+ def __getattr__(self, key: str):
3782
+ if key in self.rownames:
3783
+ try:
3784
+ return self.values[self.rownames.index(key), :]
3785
+ except BaseException:
3786
+ objecttools.augment_excmessage(
3787
+ f"While trying to retrieve values from parameter "
3788
+ f"{objecttools.elementphrase(self)} via the row related attribute "
3789
+ f"`{key}`"
3790
+ )
3791
+ if key in self.columnnames:
3792
+ try:
3793
+ return self.values[:, self.columnnames.index(key)]
3794
+ except BaseException:
3795
+ objecttools.augment_excmessage(
3796
+ f"While trying to retrieve values from parameter "
3797
+ f"{objecttools.elementphrase(self)} via the column related "
3798
+ f"attribute `{key}`"
3799
+ )
3800
+ if key in self._rowcolumnmappings:
3801
+ idx, jdx = self._rowcolumnmappings[key]
3802
+ try:
3803
+ return self.TYPE(self.values[idx, jdx])
3804
+ except BaseException:
3805
+ objecttools.augment_excmessage(
3806
+ f"While trying to retrieve values from parameter "
3807
+ f"{objecttools.elementphrase(self)} via the row and column "
3808
+ f"related attribute `{key}`"
3809
+ )
3810
+ raise AttributeError(
3811
+ f"Parameter {objecttools.elementphrase(self)} does neither handle a "
3812
+ f"normal attribute nor a row or column related attribute named `{key}`."
3813
+ )
3814
+
3815
+ def __setattr__(self, key: str, values) -> None:
3816
+ if key in self.rownames:
3817
+ try:
3818
+ self.values[self.rownames.index(key), :] = values
3819
+ except BaseException:
3820
+ objecttools.augment_excmessage(
3821
+ f"While trying to assign new values to parameter "
3822
+ f"{objecttools.elementphrase(self)} via the row related attribute "
3823
+ f"`{key}`"
3824
+ )
3825
+ elif key in self.columnnames:
3826
+ try:
3827
+ self.values[:, self.columnnames.index(key)] = values
3828
+ except BaseException:
3829
+ objecttools.augment_excmessage(
3830
+ f"While trying to assign new values to parameter "
3831
+ f"{objecttools.elementphrase(self)} via the column related "
3832
+ f"attribute `{key}`"
3833
+ )
3834
+ elif key in self._rowcolumnmappings:
3835
+ idx, jdx = self._rowcolumnmappings[key]
3836
+ try:
3837
+ self.values[idx, jdx] = values
3838
+ except BaseException:
3839
+ objecttools.augment_excmessage(
3840
+ f"While trying to assign new values to parameter "
3841
+ f"{objecttools.elementphrase(self)} via the row and column "
3842
+ f"related attribute `{key}`"
3843
+ )
3844
+ else:
3845
+ super().__setattr__(key, values)
3846
+
3847
+ def __repr__(self) -> str:
3848
+ values = self.revert_timefactor(self.values)
3849
+ prefix = f"{self.name}("
3850
+ blanks = " " * len(prefix)
3851
+ lines = []
3852
+ for idx, key in enumerate(self.rownames):
3853
+ subprefix = f"{prefix}{key}=" if idx == 0 else f"{blanks}{key}="
3854
+ lines.append(
3855
+ objecttools.assignrepr_list(values[idx, :], subprefix, 75) + ","
3856
+ )
3857
+ lines[-1] = lines[-1][:-1] + ")"
3858
+ return "\n".join(lines)
3859
+
3860
+ def __dir__(self) -> list[str]:
3861
+ """
3862
+ >>> from hydpy.core.parametertools import KeywordParameter2D
3863
+ >>> class IsWarm(KeywordParameter2D):
3864
+ ... TYPE = bool
3865
+ ... TIME = None
3866
+ ... rownames = ("north", "south")
3867
+ ... columnnames = ("apr2sep", "oct2mar")
3868
+ >>> iswarm = IsWarm(None)
3869
+ >>> sorted(set(dir(iswarm)) - set(object.__dir__(iswarm)))
3870
+ ['apr2sep', 'north', 'north_apr2sep', 'north_oct2mar', 'oct2mar', 'south', \
3871
+ 'south_apr2sep', 'south_oct2mar']
3872
+ """
3873
+ assert (rowcolmappings := self._rowcolumnmappings) is not None
3874
+ return (
3875
+ cast(list[str], super().__dir__())
3876
+ + list(self.rownames)
3877
+ + list(self.columnnames)
3878
+ + list(rowcolmappings)
3879
+ )
3880
+
3881
+
3882
+ class LeftRightParameter(variabletools.MixinFixedShape, Parameter):
3883
+ """Base class for handling two values, a left one and a right one.
3884
+
3885
+ The original purpose of class |LeftRightParameter| is to make the
3886
+ handling of river channel related parameters with different values
3887
+ for both river banks a little more convenient.
3888
+
3889
+ As an example, we define a parameter class describing the width
3890
+ both of the left and the right flood plain of a river segment:
3891
+
3892
+ >>> from hydpy.core.parametertools import LeftRightParameter
3893
+ >>> class FloodPlainWidth(LeftRightParameter):
3894
+ ... TYPE = float
3895
+ ... TIME = None
3896
+ >>> floodplainwidth = FloodPlainWidth(None)
3897
+
3898
+ Here, we need to set the shape of the parameter to 2, which is an
3899
+ automated procedure in full model setups:
3900
+
3901
+ >>> floodplainwidth.shape = 2
3902
+
3903
+ Parameter values can be defined as usual:
3904
+
3905
+ >>> floodplainwidth(3.0)
3906
+ >>> floodplainwidth
3907
+ floodplainwidth(3.0)
3908
+
3909
+ Alternatively, use the keywords `left` or `l` and `right` or
3910
+ `r` to define both values individually:
3911
+
3912
+ >>> floodplainwidth(left=1.0, right=2.0)
3913
+ >>> floodplainwidth
3914
+ floodplainwidth(left=1.0, right=2.0)
3915
+ >>> floodplainwidth(l=2.0, r=1.0)
3916
+ >>> floodplainwidth
3917
+ floodplainwidth(left=2.0, right=1.0)
3918
+
3919
+ Incomplete information results in the following errors:
3920
+
3921
+ >>> floodplainwidth(left=2.0)
3922
+ Traceback (most recent call last):
3923
+ ...
3924
+ ValueError: When setting the values of parameter `floodplainwidth` of \
3925
+ element `?` via keyword arguments, either `right` or `r` for the "right" \
3926
+ parameter value must be given, but is not.
3927
+ >>> floodplainwidth(right=1.0)
3928
+ Traceback (most recent call last):
3929
+ ...
3930
+ ValueError: When setting the values of parameter `floodplainwidth` of \
3931
+ element `?` via keyword arguments, either `left` or `l` for the "left" \
3932
+ parameter value must be given, but is not.
3933
+
3934
+ Additionally, one can query and modify the individual values via the
3935
+ attribute names `left` and `right`:
3936
+
3937
+ >>> floodplainwidth.left
3938
+ 2.0
3939
+ >>> floodplainwidth.left = 3.0
3940
+ >>> floodplainwidth.right
3941
+ 1.0
3942
+ >>> floodplainwidth.right = 4.0
3943
+ >>> floodplainwidth
3944
+ floodplainwidth(left=3.0, right=4.0)
3945
+ """
3946
+
3947
+ NDIM = 1
3948
+ SHAPE = (2,)
3949
+ strict_valuehandling: bool = False
3950
+
3951
+ def __call__(self, *args, **kwargs) -> None:
3952
+ try:
3953
+ super().__call__(*args, **kwargs)
3954
+ except NotImplementedError:
3955
+ left = kwargs.get("left", kwargs.get("l"))
3956
+ if left is None:
3957
+ raise ValueError(
3958
+ f"When setting the values of parameter "
3959
+ f"{objecttools.elementphrase(self)} via keyword "
3960
+ f'arguments, either `left` or `l` for the "left" '
3961
+ f"parameter value must be given, but is not."
3962
+ ) from None
3963
+ self.left = left
3964
+ right = kwargs.get("right", kwargs.get("r"))
3965
+ if right is None:
3966
+ raise ValueError(
3967
+ f"When setting the values of parameter "
3968
+ f"{objecttools.elementphrase(self)} via keyword "
3969
+ f'arguments, either `right` or `r` for the "right" '
3970
+ f"parameter value must be given, but is not."
3971
+ ) from None
3972
+ self.right = right
3973
+
3974
+ @property
3975
+ def left(self) -> float:
3976
+ """The "left" value of the actual parameter object."""
3977
+ return self.TYPE(self.values[0])
3978
+
3979
+ @left.setter
3980
+ def left(self, value):
3981
+ self.values[0] = value
3982
+
3983
+ @property
3984
+ def right(self) -> float:
3985
+ """The "right" value of the actual parameter object."""
3986
+ return self.TYPE(self.values[1])
3987
+
3988
+ @right.setter
3989
+ def right(self, value):
3990
+ self.values[1] = value
3991
+
3992
+ def __repr__(self):
3993
+ values = [objecttools.repr_(value) for value in self.values]
3994
+ if values[0] == values[1]:
3995
+ return f"{self.name}({values[0]})"
3996
+ return f"{self.name}(left={values[0]}, right={values[1]})"
3997
+
3998
+
3999
+ class FixedParameter(Parameter):
4000
+ """Base class for defining parameters with fixed values.
4001
+
4002
+ Model model-users usually do not modify the values of |FixedParameter|
4003
+ objects. Hence, such objects prepare their "initial" values automatically
4004
+ whenever possible, even when option |Options.usedefaultvalues| is disabled.
4005
+ """
4006
+
4007
+ INIT: int | float | bool
4008
+
4009
+ @property
4010
+ def initinfo(self) -> tuple[float | int | bool, bool]:
4011
+ """A |tuple| always containing the fixed value and |True|, except
4012
+ for time-dependent parameters and incomplete time-information.
4013
+
4014
+ .. testsetup::
4015
+
4016
+ >>> from hydpy import pub
4017
+ >>> del pub.options.simulationstep
4018
+
4019
+ >>> from hydpy.core.parametertools import FixedParameter
4020
+ >>> class Par(FixedParameter):
4021
+ ... NDIM, TYPE, TIME, SPAN = 0, float, True, (0., None)
4022
+ ... INIT = 100.0
4023
+ >>> par = Par(None)
4024
+ >>> par.initinfo
4025
+ (nan, False)
4026
+ >>> from hydpy import pub
4027
+ >>> pub.options.parameterstep = "1d"
4028
+ >>> pub.options.simulationstep = "12h"
4029
+ >>> par.initinfo
4030
+ (50.0, True)
4031
+ """
4032
+ try:
4033
+ with hydpy.pub.options.parameterstep("1d"):
4034
+ return self.apply_timefactor(self.INIT), True
4035
+ except (AttributeError, RuntimeError):
4036
+ return variabletools.TYPE2MISSINGVALUE[self.TYPE], False
4037
+
4038
+ def restore(self) -> None:
4039
+ """Restore the original parameter value.
4040
+
4041
+ Method |FixedParameter.restore| is relevant for testing mainly. Note that it
4042
+ might be necessary to call it after changing the simulation step size, as shown
4043
+ in the following example using the parameter |evap_fixed.HeatOfCondensation| of
4044
+ base model |evap|:
4045
+
4046
+ >>> from hydpy.models.evap import *
4047
+ >>> simulationstep("1d")
4048
+ >>> parameterstep("1d")
4049
+ >>> from hydpy import round_
4050
+ >>> fixed.heatofcondensation
4051
+ heatofcondensation(28.5)
4052
+ >>> round_(fixed.heatofcondensation.value)
4053
+ 28.5
4054
+ >>> simulationstep("12h")
4055
+ >>> fixed.heatofcondensation
4056
+ heatofcondensation(14.25)
4057
+ >>> round_(fixed.heatofcondensation.value)
4058
+ 28.5
4059
+ >>> fixed.heatofcondensation.restore()
4060
+ >>> fixed.heatofcondensation
4061
+ heatofcondensation(28.5)
4062
+ >>> round_(fixed.heatofcondensation.value)
4063
+ 57.0
4064
+ """
4065
+ with hydpy.pub.options.parameterstep("1d"):
4066
+ self(self.INIT)
4067
+
4068
+
4069
+ class SolverParameter(Parameter):
4070
+ """Base class for defining parameters controlling numerical algorithms
4071
+ for solving model equations.
4072
+
4073
+ So far, the equation systems of most models implemented into *HydPy*
4074
+ are primarily coded as approximative solutions. However, there are also
4075
+ some models stating the original equations only. One example
4076
+ is the |dam| model. Its code consists of the original differential
4077
+ equations, which must be solved by a separate algorithm. Such
4078
+ algorithms, like the Runge Kutta schema implemented in class
4079
+ |ELSModel|, often come with some degrees of freedom, for example,
4080
+ to control the striven numerical accuracy.
4081
+
4082
+ On the one hand, the model developer should know best how to configure
4083
+ a numerical algorithm he selects for solving the model equations. On the
4084
+ other hand, there might be situations when the model user has diverging
4085
+ preferences. For example, he might favour higher numerical accuracies
4086
+ in one project and faster computation times in another one. Therefore,
4087
+ the |SolverParameter.update| method of class |SolverParameter| relies
4088
+ on an `INIT` value defined by the model developer as long as the user
4089
+ does not define an alternative value in his control files.
4090
+
4091
+ As an example, we derive the numerical tolerance parameter `Tol`:
4092
+
4093
+ >>> from hydpy.core.parametertools import SolverParameter
4094
+ >>> class Tol(SolverParameter):
4095
+ ... NDIM = 0
4096
+ ... TYPE = float
4097
+ ... TIME = None
4098
+ ... SPAN = (0.0, None)
4099
+ ... INIT = 0.1
4100
+
4101
+ Initially, the method |SolverParameter.update| applies the value of the
4102
+ class constant `INIT`:
4103
+
4104
+ >>> tol = Tol(None)
4105
+ >>> tol.update()
4106
+ >>> tol
4107
+ tol(0.1)
4108
+
4109
+ One can define an alternative value via "calling" the parameter as usual:
4110
+
4111
+ >>> tol(0.01)
4112
+ >>> tol
4113
+ tol(0.01)
4114
+
4115
+ Afterwards, |SolverParameter.update| reuses the alternative value
4116
+ instead of the value of class constant `INIT`:
4117
+
4118
+ >>> tol.update()
4119
+ >>> tol
4120
+ tol(0.01)
4121
+
4122
+ This alternative value is accessible and changeable via property
4123
+ |SolverParameter.alternative_initvalue|:
4124
+
4125
+ >>> tol.alternative_initvalue
4126
+ 0.01
4127
+
4128
+ >>> tol.alternative_initvalue = 0.001
4129
+ >>> tol.update()
4130
+ >>> tol
4131
+ tol(0.001)
4132
+
4133
+ One must delete the alternative value to make `INIT` relevant again:
4134
+
4135
+ >>> del tol.alternative_initvalue
4136
+ >>> tol.alternative_initvalue
4137
+ Traceback (most recent call last):
4138
+ ...
4139
+ hydpy.core.exceptiontools.AttributeNotReady: No alternative initial value for \
4140
+ solver parameter `tol` of element `?` has been defined so far.
4141
+ >>> tol.update()
4142
+ >>> tol
4143
+ tol(0.1)
4144
+
4145
+ Very often, solver parameters depend on other model settings as the
4146
+ simulation step size or the catchment size, but `INIT` is always
4147
+ constant. To allow for more flexibility, model developers can
4148
+ override the method |SolverParameter.modify_init|, which allows
4149
+ adapting the effective parameter value to the actual project settings.
4150
+
4151
+ As a most simple example, we extend our class `Tol` with a
4152
+ |SolverParameter.modify_init| method that doubles the original
4153
+ `INIT` value:
4154
+
4155
+ >>> class ModTol(Tol):
4156
+ ... def modify_init(self):
4157
+ ... return 2.0 * self.INIT
4158
+ >>> modtol = ModTol(None)
4159
+ >>> modtol.update()
4160
+ >>> modtol
4161
+ modtol(0.2)
4162
+
4163
+ Note that |SolverParameter.modify_init| changes the value of `INIT`
4164
+ only, not the value of |SolverParameter.alternative_initvalue|:
4165
+
4166
+ >>> modtol.alternative_initvalue = 0.01
4167
+ >>> modtol.update()
4168
+ >>> modtol
4169
+ modtol(0.01)
4170
+ """
4171
+
4172
+ INIT: int | float | bool
4173
+ _alternative_initvalue: float | None
4174
+
4175
+ def __init__(self, subvars):
4176
+ super().__init__(subvars)
4177
+ self._alternative_initvalue = None
4178
+
4179
+ def __call__(self, *args, **kwargs) -> None:
4180
+ super().__call__(*args, **kwargs)
4181
+ self.alternative_initvalue = self.value
4182
+
4183
+ def update(self) -> None:
4184
+ """Update the actual parameter value based on `INIT` or, if
4185
+ available, on |SolverParameter.alternative_initvalue|.
4186
+
4187
+ See the main documentation on class |SolverParameter| for more
4188
+ information.
4189
+ """
4190
+ if self._alternative_initvalue:
4191
+ self.value = self.alternative_initvalue
4192
+ else:
4193
+ self.value = self.modify_init()
4194
+
4195
+ def modify_init(self) -> bool | int | float:
4196
+ """Return the value of class constant `INIT`.
4197
+
4198
+ Override this method to support project-specific solver parameters.
4199
+ See the main documentation on class |SolverParameter| for more
4200
+ information.
4201
+ """
4202
+ return self.INIT
4203
+
4204
+ @property
4205
+ def alternative_initvalue(self) -> bool | int | float:
4206
+ """A user-defined value to be used instead of the value of class
4207
+ constant `INIT`.
4208
+
4209
+ See the main documentation on class |SolverParameter| for more
4210
+ information.
4211
+ """
4212
+ if self._alternative_initvalue is None:
4213
+ raise exceptiontools.AttributeNotReady(
4214
+ f"No alternative initial value for solver parameter "
4215
+ f"{objecttools.elementphrase(self)} has been defined so far."
4216
+ )
4217
+ return self._alternative_initvalue
4218
+
4219
+ @alternative_initvalue.setter
4220
+ def alternative_initvalue(self, value):
4221
+ self._alternative_initvalue = value
4222
+
4223
+ @alternative_initvalue.deleter
4224
+ def alternative_initvalue(self):
4225
+ self._alternative_initvalue = None
4226
+
4227
+
4228
+ class SecondsParameter(Parameter):
4229
+ """The length of the actual simulation step size in seconds [s]."""
4230
+
4231
+ NDIM = 0
4232
+ TYPE = float
4233
+ TIME = None
4234
+ SPAN = (0.0, None)
4235
+
4236
+ def update(self) -> None:
4237
+ """Take the number of seconds from the current simulation time step.
4238
+
4239
+ >>> from hydpy import pub
4240
+ >>> from hydpy.core.parametertools import SecondsParameter
4241
+ >>> secondsparameter = SecondsParameter(None)
4242
+ >>> with pub.options.parameterstep("1d"):
4243
+ ... with pub.options.simulationstep("12h"):
4244
+ ... secondsparameter.update()
4245
+ ... secondsparameter
4246
+ secondsparameter(43200.0)
4247
+ """
4248
+ self.value = hydpy.pub.options.simulationstep.seconds
4249
+
4250
+
4251
+ class HoursParameter(Parameter):
4252
+ """The length of the actual simulation step size in hours [h]."""
4253
+
4254
+ NDIM = 0
4255
+ TYPE = float
4256
+ TIME = None
4257
+ SPAN = (0.0, None)
4258
+
4259
+ def update(self) -> None:
4260
+ """Take the number of hours from the current simulation time step.
4261
+
4262
+ >>> from hydpy import pub
4263
+ >>> from hydpy.core.parametertools import HoursParameter
4264
+ >>> hoursparameter = HoursParameter(None)
4265
+ >>> with pub.options.parameterstep("1d"):
4266
+ ... with pub.options.simulationstep("12h"):
4267
+ ... hoursparameter.update()
4268
+ >>> hoursparameter
4269
+ hoursparameter(12.0)
4270
+ """
4271
+ self.value = hydpy.pub.options.simulationstep.hours
4272
+
4273
+
4274
+ class DaysParameter(Parameter):
4275
+ """The length of the actual simulation step size in days [d]."""
4276
+
4277
+ NDIM = 0
4278
+ TYPE = float
4279
+ TIME = None
4280
+ SPAN = (0.0, None)
4281
+
4282
+ def update(self) -> None:
4283
+ """Take the number of days from the current simulation time step.
4284
+
4285
+ >>> from hydpy import pub
4286
+ >>> from hydpy.core.parametertools import DaysParameter
4287
+ >>> daysparameter = DaysParameter(None)
4288
+ >>> with pub.options.parameterstep("1d"):
4289
+ ... with pub.options.simulationstep("12h"):
4290
+ ... daysparameter.update()
4291
+ >>> daysparameter
4292
+ daysparameter(0.5)
4293
+ """
4294
+ self.value = hydpy.pub.options.simulationstep.days
4295
+
4296
+
4297
+ class TOYParameter(Parameter):
4298
+ """References the |Indexer.timeofyear| index array provided by the
4299
+ instance of class |Indexer| available in module |pub|. [-]."""
4300
+
4301
+ NDIM = 1
4302
+ TYPE = int
4303
+ TIME = None
4304
+ SPAN = (0, None)
4305
+
4306
+ def update(self) -> None:
4307
+ """Reference the actual |Indexer.timeofyear| array of the
4308
+ |Indexer| object available in module |pub|.
4309
+
4310
+ >>> from hydpy import pub
4311
+ >>> pub.timegrids = "27.02.2004", "3.03.2004", "1d"
4312
+ >>> from hydpy.core.parametertools import TOYParameter
4313
+ >>> toyparameter = TOYParameter(None)
4314
+ >>> toyparameter.update()
4315
+ >>> toyparameter
4316
+ toyparameter(57, 58, 59, 60, 61)
4317
+
4318
+ .. testsetup::
4319
+
4320
+ >>> del pub.timegrids
4321
+ """
4322
+ indexarray = hydpy.pub.indexer.timeofyear
4323
+ self._set_shape(indexarray.shape)
4324
+ self._set_value(indexarray)
4325
+
4326
+
4327
+ class MOYParameter(Parameter):
4328
+ """References the |Indexer.monthofyear| index array provided by the
4329
+ instance of class |Indexer| available in module |pub| [-]."""
4330
+
4331
+ NDIM = 1
4332
+ TYPE = int
4333
+ TIME = None
4334
+ SPAN = (0, 11)
4335
+
4336
+ def update(self) -> None:
4337
+ """Reference the actual |Indexer.monthofyear| array of the
4338
+ |Indexer| object available in module |pub|.
4339
+
4340
+ >>> from hydpy import pub
4341
+ >>> pub.timegrids = "27.02.2004", "3.03.2004", "1d"
4342
+ >>> from hydpy.core.parametertools import MOYParameter
4343
+ >>> moyparameter = MOYParameter(None)
4344
+ >>> moyparameter.update()
4345
+ >>> moyparameter
4346
+ moyparameter(1, 1, 1, 2, 2)
4347
+
4348
+ .. testsetup::
4349
+
4350
+ >>> del pub.timegrids
4351
+ """
4352
+ indexarray = hydpy.pub.indexer.monthofyear
4353
+ self._set_shape(indexarray.shape)
4354
+ self._set_value(indexarray)
4355
+
4356
+
4357
+ class DOYParameter(Parameter):
4358
+ """References the |Indexer.dayofyear| index array provided by the
4359
+ instance of class |Indexer| available in module |pub| [-]."""
4360
+
4361
+ NDIM = 1
4362
+ TYPE = int
4363
+ TIME = None
4364
+ SPAN = (0, 365)
4365
+
4366
+ def update(self) -> None:
4367
+ """Reference the actual |Indexer.dayofyear| array of the
4368
+ |Indexer| object available in module |pub|.
4369
+
4370
+ >>> from hydpy import pub
4371
+ >>> pub.timegrids = "27.02.2004", "3.03.2004", "1d"
4372
+ >>> from hydpy.core.parametertools import DOYParameter
4373
+ >>> doyparameter = DOYParameter(None)
4374
+ >>> doyparameter.update()
4375
+ >>> doyparameter
4376
+ doyparameter(57, 58, 59, 60, 61)
4377
+
4378
+ .. testsetup::
4379
+
4380
+ >>> del pub.timegrids
4381
+ """
4382
+ indexarray = hydpy.pub.indexer.dayofyear
4383
+ self._set_shape(indexarray.shape)
4384
+ self._set_value(indexarray)
4385
+
4386
+
4387
+ class SCTParameter(Parameter):
4388
+ """References the |Indexer.standardclocktime| array provided by the
4389
+ instance of class |Indexer| available in module |pub| [h]."""
4390
+
4391
+ NDIM = 1
4392
+ TYPE = float
4393
+ TIME = None
4394
+ SPAN = (0.0, 86400.0)
4395
+
4396
+ def update(self) -> None:
4397
+ """Reference the actual |Indexer.standardclocktime| array of the
4398
+ |Indexer| object available in module |pub|.
4399
+
4400
+ >>> from hydpy import pub
4401
+ >>> pub.timegrids = "27.02.2004 21:00", "28.02.2004 03:00", "1h"
4402
+ >>> from hydpy.core.parametertools import SCTParameter
4403
+ >>> sctparameter = SCTParameter(None)
4404
+ >>> sctparameter.update()
4405
+ >>> sctparameter
4406
+ sctparameter(21.5, 22.5, 23.5, 0.5, 1.5, 2.5)
4407
+
4408
+ .. testsetup::
4409
+
4410
+ >>> del pub.timegrids
4411
+ """
4412
+ array = hydpy.pub.indexer.standardclocktime
4413
+ self._set_shape(array.shape)
4414
+ self._set_value(array)
4415
+
4416
+
4417
+ class UTCLongitudeParameter(Parameter):
4418
+ """References the current "UTC longitude" defined by option
4419
+ |Options.utclongitude|."""
4420
+
4421
+ NDIM = 0
4422
+ TYPE = int
4423
+ TIME = None
4424
+ SPAN = (-180, 180)
4425
+
4426
+ def update(self):
4427
+ """Apply the current value of option |Options.utclongitude|.
4428
+
4429
+ >>> from hydpy import pub
4430
+ >>> pub.options.utclongitude
4431
+ 15
4432
+ >>> from hydpy.core.parametertools import UTCLongitudeParameter
4433
+ >>> utclongitudeparameter = UTCLongitudeParameter(None)
4434
+ >>> utclongitudeparameter.update()
4435
+ >>> utclongitudeparameter
4436
+ utclongitudeparameter(15)
4437
+
4438
+ Note that changing the value of option |Options.utclongitude|
4439
+ might makes re-calling method |UTCLongitudeParameter.update| necessary:
4440
+
4441
+ >>> pub.options.utclongitude = 0
4442
+ >>> utclongitudeparameter
4443
+ utclongitudeparameter(15)
4444
+ >>> utclongitudeparameter.update()
4445
+ >>> utclongitudeparameter
4446
+ utclongitudeparameter(0)
4447
+ """
4448
+ self(hydpy.pub.options.utclongitude)
4449
+
4450
+
4451
+ def do_nothing(model: modeltools.Model) -> None: # pylint: disable=unused-argument
4452
+ """The default Python version of the |CallbackParameter|
4453
+ |CallbackParameter.callback| function, which does nothing."""
4454
+
4455
+
4456
+ class CallbackParameter(Parameter):
4457
+ """Base class for parameters that support calculating their values via user-defined
4458
+ callback functions alternatively of sticking to the same values during a simulation
4459
+ run.
4460
+
4461
+ We use the callback parameter |sw1d_control.GateHeight| of application model
4462
+ |sw1d_gate_out| for the following technical explanations (for a more
4463
+ application-oriented example, see the |sw1d_model.Calc_Discharge_V3|
4464
+ documentation):
4465
+
4466
+ >>> from hydpy.models.sw1d_gate_out import *
4467
+ >>> parameterstep()
4468
+
4469
+ You can define a fixed gate height as usual:
4470
+
4471
+ >>> gateheight
4472
+ gateheight(?)
4473
+ >>> gateheight(3.0)
4474
+ >>> gateheight
4475
+ gateheight(3.0)
4476
+
4477
+ Alternatively, you can write an individual callback function. Its only argument
4478
+ accepts the model under consideration (here, |sw1d_gate_out|). Principally, you
4479
+ are free to modify the model in any way you like, but the expected behaviour is
4480
+ to set the considered parameter's value only:
4481
+
4482
+ >>> def adjust(model) -> None:
4483
+ ... con = model.parameters.control.fastaccess
4484
+ ... my_gateheight: float = 2.0 + 3.0
4485
+ ... con.gateheight = my_gateheight
4486
+
4487
+ However, when working in Cython mode, *HydPy* converts the pure Python function to
4488
+ a Cython function and compiles it to C in the background, similar to how it handles
4489
+ "normal" model methods. This background conversion is crucial for efficiency but
4490
+ restricts the allowed syntax and functionality. Generally, you should work with
4491
+ the usual "fast access shortcuts", be explicit about the |None| return type, and
4492
+ cannot import any Python library, but are free to use Cython-functionalities
4493
+ implemented for and used by other model methods instead. A trivial example is using
4494
+ |fabs| for calculating absolute values.
4495
+
4496
+ Next, we hand the callback function over to the parameter. Here, we do this a
4497
+ little strangely between the creation of two tuples for hiding potential
4498
+ information printed by Cython or the used C compiler:
4499
+
4500
+ >>> ();gateheight(callback=adjust);() # doctest: +ELLIPSIS
4501
+ (...)
4502
+
4503
+ The string representation now includes the callback's source code:
4504
+
4505
+ >>> gateheight
4506
+ def adjust(model) -> None:
4507
+ con = model.parameters.control.fastaccess
4508
+ my_gateheight: float = 2.0 + 3.0
4509
+ con.gateheight = my_gateheight
4510
+ gateheight(callback=adjust)
4511
+
4512
+ When interested in the parameter's value, request it via the
4513
+ |CallbackParameter.value| property. Note that this property applies the callback
4514
+ automatically before returning the (then updated) value:
4515
+
4516
+ >>> from hydpy import round_
4517
+ >>> round_(gateheight.value)
4518
+ 5.0
4519
+
4520
+ You can return the parameter to "normal behaviour" by assigning a fixed value:
4521
+
4522
+ >>> gateheight(7.0)
4523
+ >>> gateheight
4524
+ gateheight(7.0)
4525
+
4526
+ Alternatively, one can assign a function via the |CallbackParameter.callback|
4527
+ property. We do not need to hide potential compiler output this time because the
4528
+ Python function has already been converted to a reusable Cython function:
4529
+
4530
+ >>> gateheight.callback = adjust
4531
+ >>> gateheight
4532
+ def adjust(model) -> None:
4533
+ con = model.parameters.control.fastaccess
4534
+ my_gateheight: float = 2.0 + 3.0
4535
+ con.gateheight = my_gateheight
4536
+ gateheight(callback=adjust)
4537
+ >>> round_(gateheight.value)
4538
+ 5.0
4539
+
4540
+ Note that *HydPy* stores the Cython callbacks persistently on disk, using the
4541
+ Python function name as a part of the Cython module name. Hence, you cannot use
4542
+ two equally named callback functions for the same parameter of the same application
4543
+ model within one project.
4544
+
4545
+ Use the `del` statement to remove the callback function:
4546
+
4547
+ >>> assert gateheight.callback is not None
4548
+ >>> del gateheight.callback
4549
+ >>> assert gateheight.callback is None
4550
+ >>> gateheight
4551
+ gateheight(5.0)
4552
+ >>> round_(gateheight.value)
4553
+ 5.0
4554
+
4555
+ Failing attempts to pass a callback function might result in the following errors:
4556
+
4557
+ >>> gateheight(Callback=adjust)
4558
+ Traceback (most recent call last):
4559
+ ...
4560
+ ValueError: When trying to prepare parameter `gateheight` of element `?` via a \
4561
+ keyword argument, it must be `callback`, and you need to pass a callback function.
4562
+
4563
+ >>> gateheight(value=1.0, callback=adjust)
4564
+ Traceback (most recent call last):
4565
+ ...
4566
+ ValueError: Parameter `gateheight` of element `?` does not allow to combine the \
4567
+ `callback` argument with other arguments.
4568
+
4569
+ The conversion from Python to Cython also works when defining the original function
4570
+ in an indentated block:
4571
+
4572
+ >>> try:
4573
+ ... def adjust_2(model) -> None:
4574
+ ... con = model.parameters.control.fastaccess
4575
+ ... my_gateheight: float = 2.0 * 3.0
4576
+ ... con.gateheight = my_gateheight
4577
+ ... finally:
4578
+ ... ();gateheight(callback=adjust_2);() # doctest: +ELLIPSIS
4579
+ (...)
4580
+ >>> gateheight.callback = adjust_2
4581
+ >>> gateheight
4582
+ def adjust_2(model) -> None:
4583
+ con = model.parameters.control.fastaccess
4584
+ my_gateheight: float = 2.0 * 3.0
4585
+ con.gateheight = my_gateheight
4586
+ gateheight(callback=adjust_2)
4587
+ >>> round_(gateheight.value)
4588
+ 6.0
4589
+ """
4590
+
4591
+ _has_callback: bool = False
4592
+
4593
+ def __call__(self, *args, **kwargs) -> None:
4594
+ try:
4595
+ super().__call__(*args, **kwargs)
4596
+ except NotImplementedError as exc:
4597
+ if (callback := kwargs.get("callback", None)) is None:
4598
+ raise ValueError(
4599
+ f"When trying to prepare parameter "
4600
+ f"{objecttools.elementphrase(self)} via a keyword argument, it "
4601
+ f"must be `callback`, and you need to pass a callback function."
4602
+ ) from exc
4603
+ if (len(args) > 0) or (len(kwargs) > 1):
4604
+ raise ValueError(
4605
+ f"Parameter {objecttools.elementphrase(self)} does not allow to "
4606
+ f"combine the `callback` argument with other arguments."
4607
+ ) from exc
4608
+ self.callback = callback
4609
+
4610
+ def _init_callback(self):
4611
+ if init := getattr(self.fastaccess, f"init_{self.name}_callback", None):
4612
+ init()
4613
+ else:
4614
+ setattr(self.fastaccess, f"{self.name}_callback", do_nothing)
4615
+
4616
+ def __hydpy__connect_variable2subgroup__(self) -> None:
4617
+ super().__hydpy__connect_variable2subgroup__()
4618
+ self._init_callback()
4619
+
4620
+ @property
4621
+ def callback(self) -> Callable[[modeltools.Model], None] | None:
4622
+ """The currently handled callback function for updating the parameter value."""
4623
+ if self._has_callback:
4624
+ if get := getattr(self.fastaccess, f"get_{self.name}_callback", None):
4625
+ return get()
4626
+ return getattr(self.fastaccess, f"{self.name}_callback")
4627
+ return None
4628
+
4629
+ @callback.setter
4630
+ def callback(self, callback: Callable[[modeltools.Model], None]) -> None:
4631
+ from hydpy.cythons import modelutils # pylint: disable=import-outside-toplevel
4632
+
4633
+ if set_ := getattr(self.fastaccess, f"set_{self.name}_callback", None):
4634
+ cymodule = modelutils.get_callbackcymodule(
4635
+ model=self.subpars.pars.model, parameter=self, callback=callback
4636
+ )
4637
+ set_(cymodule.get_wrapper())
4638
+ else:
4639
+ setattr(self.fastaccess, f"{self.name}_callback", callback)
4640
+ self._has_callback = True
4641
+
4642
+ @callback.deleter
4643
+ def callback(self) -> None:
4644
+ self._has_callback = False
4645
+ self._init_callback()
4646
+
4647
+ def _get_value(self):
4648
+ """The fixed value or the value last updated by the callback function."""
4649
+ if self._has_callback:
4650
+ self.callback(self.subpars.pars.model)
4651
+ self._valueready = True
4652
+ return super()._get_value()
4653
+
4654
+ def _set_value(self, value) -> None:
4655
+ self._init_callback()
4656
+ self._has_callback = False
4657
+ super()._set_value(value)
4658
+
4659
+ value = property(fget=_get_value, fset=_set_value)
4660
+
4661
+ def __repr__(self) -> str:
4662
+ if self._has_callback:
4663
+ callback: Any = self.callback
4664
+ if isinstance(callback, types.FunctionType):
4665
+ lines = inspect.getsource(callback).split("\n")
4666
+ indent = len(lines[0]) - len(lines[0].lstrip())
4667
+ pycode = "\n".join(line[indent:] for line in lines).rstrip()
4668
+ funcname = callback.__name__
4669
+ else:
4670
+ pycode = callback.get_sourcecode()
4671
+ funcname = callback.get_name()
4672
+ varrepr = f"{self.name}(callback={funcname})"
4673
+ return "\n".join((pycode, varrepr))
4674
+ return super().__repr__()