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,2990 @@
1
+ """ This module provides utilities to build Cython models based on Python models
2
+ automatically.
3
+
4
+ .. _`issue`: https://github.com/hydpy-dev/hydpy/issues
5
+
6
+ Most model developers do not need to be aware of the features implemented in module
7
+ |modelutils|, except that they need to initialise class |Cythonizer| within the main
8
+ modules of their base and application models (see, for example, the source code of base
9
+ model |hland| and application model |hland_96|).
10
+
11
+ However, when implementing models with functionalities not envisaged so far, problems
12
+ might arise. Please contact the *HydPy* developer team then, preferably by opening an
13
+ `issue`_ on GitHub. Potentially, problems could occur when defining parameters or
14
+ sequences with larger dimensionality than anticipated. The following example shows the
15
+ Cython code lines for the |ELSModel.get_point_states| method of class |ELSModel|, used
16
+ for deriving the |test| model. By now, we did only implement 0-dimensional and
17
+ 1-dimensional sequences requiring this method. After hackishly changing the
18
+ dimensionality of sequences |test_states.S|, we still seem to get plausible results,
19
+ but these are untested in model applications:
20
+
21
+ >>> from hydpy.models.test import cythonizer
22
+ >>> pyxwriter = cythonizer.pyxwriter
23
+ >>> from hydpy.cythons.modelutils import PyxPxdLines
24
+ >>> lines = PyxPxdLines()
25
+ >>> pyxwriter.get_point_states(lines)
26
+ . get_point_states
27
+ >>> lines.pyx # doctest: +ELLIPSIS
28
+ cpdef inline void get_point_states(self) noexcept nogil:
29
+ cdef ...int... idx0
30
+ self.sequences.states.s = \
31
+ self.sequences.states._s_points[self.numvars.idx_stage]
32
+ for idx0 in range(self.sequences.states._sv_length):
33
+ self.sequences.states.sv[idx0] = \
34
+ self.sequences.states._sv_points[self.numvars.idx_stage][idx0]
35
+ <BLANKLINE>
36
+
37
+ >>> pyxwriter.model.sequences.states.s.NDIM = 2
38
+ >>> lines.pyx.clear()
39
+ >>> pyxwriter.get_point_states(lines)
40
+ . get_point_states
41
+ >>> lines.pyx # doctest: +ELLIPSIS
42
+ cpdef inline void get_point_states(self) noexcept nogil:
43
+ cdef ...int... idx0, idx1
44
+ for idx0 in range(self.sequences.states._s_length0):
45
+ for idx1 in range(self.sequences.states._s_length1):
46
+ self.sequences.states.s[idx0, idx1] = \
47
+ self.sequences.states._s_points[self.numvars.idx_stage][idx0, idx1]
48
+ for idx0 in range(self.sequences.states._sv_length):
49
+ self.sequences.states.sv[idx0] = \
50
+ self.sequences.states._sv_points[self.numvars.idx_stage][idx0]
51
+ <BLANKLINE>
52
+
53
+ >>> pyxwriter.model.sequences.states.s.NDIM = 3
54
+ >>> pyxwriter.get_point_states(lines)
55
+ Traceback (most recent call last):
56
+ ...
57
+ NotImplementedError: NDIM of sequence `s` is higher than expected.
58
+
59
+ The following examples show the results for some methods which are also related to
60
+ numerical integration but deal with |FluxSequence| objects. We start with the method
61
+ |ELSModel.integrate_fluxes|:
62
+
63
+ >>> lines.pyx.clear()
64
+ >>> pyxwriter.integrate_fluxes(lines)
65
+ . integrate_fluxes
66
+ >>> lines.pyx # doctest: +ELLIPSIS
67
+ cpdef inline void integrate_fluxes(self) noexcept nogil:
68
+ cdef ...int... jdx, idx0
69
+ self.sequences.fluxes.q = 0.
70
+ for jdx in range(self.numvars.idx_method):
71
+ self.sequences.fluxes.q = \
72
+ self.sequences.fluxes.q +self.numvars.dt * \
73
+ self.numconsts.a_coefs[self.numvars.idx_method-1, \
74
+ self.numvars.idx_stage, jdx]*self.sequences.fluxes._q_points[jdx]
75
+ for idx0 in range(self.sequences.fluxes._qv_length):
76
+ self.sequences.fluxes.qv[idx0] = 0.
77
+ for jdx in range(self.numvars.idx_method):
78
+ self.sequences.fluxes.qv[idx0] = \
79
+ self.sequences.fluxes.qv[idx0] + self.numvars.dt * \
80
+ self.numconsts.a_coefs[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]*\
81
+ self.sequences.fluxes._qv_points[jdx, idx0]
82
+ <BLANKLINE>
83
+
84
+
85
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
86
+ >>> lines.pyx.clear()
87
+ >>> pyxwriter.integrate_fluxes(lines)
88
+ . integrate_fluxes
89
+ >>> lines.pyx # doctest: +ELLIPSIS
90
+ cpdef inline void integrate_fluxes(self) noexcept nogil:
91
+ cdef ...int... jdx, idx0, idx1
92
+ for idx0 in range(self.sequences.fluxes._q_length0):
93
+ for idx1 in range(self.sequences.fluxes._q_length1):
94
+ self.sequences.fluxes.q[idx0, idx1] = 0.
95
+ for jdx in range(self.numvars.idx_method):
96
+ self.sequences.fluxes.q[idx0, idx1] = \
97
+ self.sequences.fluxes.q[idx0, idx1] + self.numvars.dt * \
98
+ self.numconsts.a_coefs[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]*\
99
+ self.sequences.fluxes._q_points[jdx, idx0, idx1]
100
+ for idx0 in range(self.sequences.fluxes._qv_length):
101
+ self.sequences.fluxes.qv[idx0] = 0.
102
+ for jdx in range(self.numvars.idx_method):
103
+ self.sequences.fluxes.qv[idx0] = \
104
+ self.sequences.fluxes.qv[idx0] + self.numvars.dt * \
105
+ self.numconsts.a_coefs[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]\
106
+ *self.sequences.fluxes._qv_points[jdx, idx0]
107
+ <BLANKLINE>
108
+
109
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
110
+ >>> pyxwriter.integrate_fluxes(lines)
111
+ Traceback (most recent call last):
112
+ ...
113
+ NotImplementedError: NDIM of sequence `q` is higher than expected.
114
+
115
+ Method |ELSModel.reset_sum_fluxes|:
116
+
117
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 0
118
+ >>> lines.pyx.clear()
119
+ >>> pyxwriter.reset_sum_fluxes(lines)
120
+ . reset_sum_fluxes
121
+ >>> lines.pyx # doctest: +ELLIPSIS
122
+ cpdef inline void reset_sum_fluxes(self) noexcept nogil:
123
+ cdef ...int... idx0
124
+ self.sequences.fluxes._q_sum = 0.
125
+ for idx0 in range(self.sequences.fluxes._qv_length):
126
+ self.sequences.fluxes._qv_sum[idx0] = 0.
127
+ <BLANKLINE>
128
+
129
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
130
+ >>> lines.pyx.clear()
131
+ >>> pyxwriter.reset_sum_fluxes(lines)
132
+ . reset_sum_fluxes
133
+ >>> lines.pyx # doctest: +ELLIPSIS
134
+ cpdef inline void reset_sum_fluxes(self) noexcept nogil:
135
+ cdef ...int... idx0, idx1
136
+ for idx0 in range(self.sequences.fluxes._q_length0):
137
+ for idx1 in range(self.sequences.fluxes._q_length1):
138
+ self.sequences.fluxes._q_sum[idx0, idx1] = 0.
139
+ for idx0 in range(self.sequences.fluxes._qv_length):
140
+ self.sequences.fluxes._qv_sum[idx0] = 0.
141
+ <BLANKLINE>
142
+
143
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
144
+ >>> pyxwriter.reset_sum_fluxes(lines)
145
+ Traceback (most recent call last):
146
+ ...
147
+ NotImplementedError: NDIM of sequence `q` is higher than expected.
148
+
149
+ Method |ELSModel.addup_fluxes|:
150
+
151
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 0
152
+ >>> lines.pyx.clear()
153
+ >>> pyxwriter.addup_fluxes(lines)
154
+ . addup_fluxes
155
+ >>> lines.pyx # doctest: +ELLIPSIS
156
+ cpdef inline void addup_fluxes(self) noexcept nogil:
157
+ cdef ...int... idx0
158
+ self.sequences.fluxes._q_sum = \
159
+ self.sequences.fluxes._q_sum + self.sequences.fluxes.q
160
+ for idx0 in range(self.sequences.fluxes._qv_length):
161
+ self.sequences.fluxes._qv_sum[idx0] = \
162
+ self.sequences.fluxes._qv_sum[idx0] + self.sequences.fluxes.qv[idx0]
163
+ <BLANKLINE>
164
+
165
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
166
+ >>> lines.pyx.clear()
167
+ >>> pyxwriter.addup_fluxes(lines)
168
+ . addup_fluxes
169
+ >>> lines.pyx # doctest: +ELLIPSIS
170
+ cpdef inline void addup_fluxes(self) noexcept nogil:
171
+ cdef ...int... idx0, idx1
172
+ for idx0 in range(self.sequences.fluxes._q_length0):
173
+ for idx1 in range(self.sequences.fluxes._q_length1):
174
+ self.sequences.fluxes._q_sum[idx0, idx1] = \
175
+ self.sequences.fluxes._q_sum[idx0, idx1] + self.sequences.fluxes.q[idx0, idx1]
176
+ for idx0 in range(self.sequences.fluxes._qv_length):
177
+ self.sequences.fluxes._qv_sum[idx0] = \
178
+ self.sequences.fluxes._qv_sum[idx0] + self.sequences.fluxes.qv[idx0]
179
+ <BLANKLINE>
180
+
181
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
182
+ >>> pyxwriter.addup_fluxes(lines)
183
+ Traceback (most recent call last):
184
+ ...
185
+ NotImplementedError: NDIM of sequence `q` is higher than expected.
186
+
187
+ Method |ELSModel.calculate_error|:
188
+
189
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 0
190
+ >>> lines.pyx.clear()
191
+ >>> pyxwriter.calculate_error(lines)
192
+ . calculate_error
193
+ >>> lines.pyx # doctest: +ELLIPSIS
194
+ cpdef inline void calculate_error(self) noexcept nogil:
195
+ cdef ...int... idx0
196
+ cdef double abserror
197
+ self.numvars.abserror = 0.
198
+ if self.numvars.use_relerror:
199
+ self.numvars.relerror = 0.
200
+ else:
201
+ self.numvars.relerror = inf
202
+ abserror = fabs(\
203
+ self.sequences.fluxes._q_results[self.numvars.idx_method]-\
204
+ self.sequences.fluxes._q_results[self.numvars.idx_method-1])
205
+ self.numvars.abserror = max(self.numvars.abserror, abserror)
206
+ if self.numvars.use_relerror:
207
+ if self.sequences.fluxes._q_results[self.numvars.idx_method] == 0.:
208
+ self.numvars.relerror = inf
209
+ else:
210
+ self.numvars.relerror = max(self.numvars.relerror, \
211
+ fabs(abserror/self.sequences.fluxes._q_results[self.numvars.idx_method]))
212
+ for idx0 in range(self.sequences.fluxes._qv_length):
213
+ abserror = fabs(\
214
+ self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]-\
215
+ self.sequences.fluxes._qv_results[self.numvars.idx_method-1, idx0])
216
+ self.numvars.abserror = max(self.numvars.abserror, abserror)
217
+ if self.numvars.use_relerror:
218
+ if self.sequences.fluxes._qv_results\
219
+ [self.numvars.idx_method, idx0] == 0.:
220
+ self.numvars.relerror = inf
221
+ else:
222
+ self.numvars.relerror = max(self.numvars.relerror, \
223
+ fabs(abserror/self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]))
224
+ <BLANKLINE>
225
+
226
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
227
+ >>> lines.pyx.clear()
228
+ >>> pyxwriter.calculate_error(lines)
229
+ . calculate_error
230
+ >>> lines.pyx # doctest: +ELLIPSIS
231
+ cpdef inline void calculate_error(self) noexcept nogil:
232
+ cdef ...int... idx0, idx1
233
+ cdef double abserror
234
+ self.numvars.abserror = 0.
235
+ if self.numvars.use_relerror:
236
+ self.numvars.relerror = 0.
237
+ else:
238
+ self.numvars.relerror = inf
239
+ for idx0 in range(self.sequences.fluxes._q_length0):
240
+ for idx1 in range(self.sequences.fluxes._q_length1):
241
+ abserror = fabs(\
242
+ self.sequences.fluxes._q_results[self.numvars.idx_method, idx0, idx1]-\
243
+ self.sequences.fluxes._q_results[self.numvars.idx_method-1, idx0, idx1])
244
+ self.numvars.abserror = max(self.numvars.abserror, abserror)
245
+ if self.numvars.use_relerror:
246
+ if self.sequences.fluxes._q_results\
247
+ [self.numvars.idx_method, idx0, idx1] == 0.:
248
+ self.numvars.relerror = inf
249
+ else:
250
+ self.numvars.relerror = max(self.numvars.relerror, fabs(\
251
+ abserror/self.sequences.fluxes._q_results[self.numvars.idx_method, idx0, idx1]))
252
+ for idx0 in range(self.sequences.fluxes._qv_length):
253
+ abserror = fabs(\
254
+ self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]-\
255
+ self.sequences.fluxes._qv_results[self.numvars.idx_method-1, idx0])
256
+ self.numvars.abserror = max(self.numvars.abserror, abserror)
257
+ if self.numvars.use_relerror:
258
+ if self.sequences.fluxes._qv_results\
259
+ [self.numvars.idx_method, idx0] == 0.:
260
+ self.numvars.relerror = inf
261
+ else:
262
+ self.numvars.relerror = max(\
263
+ self.numvars.relerror, \
264
+ fabs(abserror/self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]))
265
+ <BLANKLINE>
266
+
267
+ >>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
268
+ >>> pyxwriter.calculate_error(lines)
269
+ Traceback (most recent call last):
270
+ ...
271
+ NotImplementedError: NDIM of sequence `q` is higher than expected.
272
+ """
273
+
274
+ # import...
275
+ # ...from standard library
276
+ from __future__ import annotations
277
+ import copy
278
+
279
+ # pylint: enable=no-name-in-module
280
+ # pylint: enable=import-error
281
+ import functools
282
+ import importlib
283
+ import inspect
284
+ import math
285
+ import os
286
+ import platform
287
+ import shutil
288
+ import sys
289
+ import types
290
+
291
+ # ...third party modules
292
+ import numpy
293
+ from numpy import inf # pylint: disable=unused-import
294
+ from numpy import nan # pylint: disable=unused-import
295
+ import setuptools
296
+
297
+ # ...from HydPy
298
+ import hydpy
299
+ from hydpy import config
300
+ from hydpy import cythons
301
+ from hydpy.core import exceptiontools
302
+ from hydpy.core import importtools
303
+ from hydpy.core import modeltools
304
+ from hydpy.core import objecttools
305
+ from hydpy.core import parametertools
306
+ from hydpy.core import sequencetools
307
+ from hydpy.core import testtools
308
+ from hydpy.core.typingtools import *
309
+ from hydpy.cythons import autogenpath
310
+
311
+
312
+ if TYPE_CHECKING:
313
+ import Cython.Build as build
314
+ else:
315
+ build = exceptiontools.OptionalImport("build", ["Cython.Build"], locals())
316
+
317
+
318
+ def get_dllextension() -> str:
319
+ """Return the DLL file extension for the current operating system.
320
+
321
+ The returned value depends on the response of function |platform.system| of module
322
+ |platform|. |get_dllextension| returns `.pyd` if |platform.system| returns the
323
+ string "windows" and `.so` for all other strings:
324
+
325
+ >>> from hydpy.cythons.modelutils import get_dllextension
326
+ >>> import platform
327
+ >>> from unittest import mock
328
+ >>> with mock.patch.object(
329
+ ... platform, "system", side_effect=lambda: "Windows") as mocked:
330
+ ... get_dllextension()
331
+ '.pyd'
332
+ >>> with mock.patch.object(
333
+ ... platform, "system", side_effect=lambda: "Linux") as mocked:
334
+ ... get_dllextension()
335
+ '.so'
336
+ """
337
+ if platform.system().lower() == "windows":
338
+ return ".pyd"
339
+ return ".so"
340
+
341
+
342
+ _dllextension = get_dllextension()
343
+
344
+ INT = "numpy.int64_t"
345
+
346
+ TYPE2STR: dict[type[Any] | str | None, str] = { # pylint: disable=duplicate-key
347
+ bool: "numpy.npy_bool",
348
+ "bool": "numpy.npy_bool",
349
+ int: INT,
350
+ "int": INT,
351
+ parametertools.IntConstant: INT,
352
+ "parametertools.IntConstant": INT,
353
+ "IntConstant": INT,
354
+ float: "double",
355
+ "float": "double",
356
+ str: "str",
357
+ "str": "str",
358
+ None: "void",
359
+ "None": "void",
360
+ type(None): "void",
361
+ Vector: "double[:]", # to be removed as soon as possible
362
+ "Vector": "double[:]",
363
+ "Vector": "double[:]",
364
+ VectorFloat: "double[:]", # This works because the `__getitem__`
365
+ # of `_ProtocolMeta` is decorated by `_tp_cache`. I don't know if this caching
366
+ # is documented behaviour, so this might cause (little) trouble in the future.
367
+ "VectorFloat": "double[:]",
368
+ "VectorFloat": "double[:]",
369
+ }
370
+ """Maps Python types to Cython compatible type declarations.
371
+
372
+ The Cython type belonging to Python's |int| is selected to agree with numpy's default
373
+ integer type on the current platform/system.
374
+ """
375
+
376
+ _checkable_types: list[type[Any]] = []
377
+ for maybe_a_type in TYPE2STR:
378
+ try:
379
+ isinstance(1, maybe_a_type) # type: ignore[arg-type]
380
+ except TypeError:
381
+ continue
382
+ assert isinstance(maybe_a_type, type)
383
+ _checkable_types.append(maybe_a_type)
384
+ CHECKABLE_TYPES: tuple[type[Any], ...] = tuple(_checkable_types)
385
+ """"Real types" of |TYPE2STR| allowed as second arguments of function |isinstance|."""
386
+ del _checkable_types
387
+
388
+ NDIM2STR = {0: "", 1: "[:]", 2: "[:,:]", 3: "[:,:,:]"}
389
+
390
+ _nogil = " noexcept nogil" if config.FASTCYTHON else ""
391
+
392
+
393
+ class Lines(list[str]):
394
+ """Handles the code lines for a `.pyx` or a `pxd` file."""
395
+
396
+ def __init__(self, *args: str) -> None:
397
+ super().__init__(args)
398
+
399
+ def add(self, indent: int, line: Mayberable1[str]) -> None:
400
+ """Append the given text line with prefixed spaces following the given number
401
+ of indentation levels."""
402
+ if isinstance(line, str):
403
+ self.append(indent * 4 * " " + line)
404
+ else:
405
+ for subline in line:
406
+ self.append(indent * 4 * " " + subline)
407
+
408
+ def __repr__(self) -> str:
409
+ return "\n".join(self) + "\n"
410
+
411
+
412
+ class PyxPxdLines:
413
+ """Handles the code lines for a `.pyx` and a `pxd` file."""
414
+
415
+ pyx: Lines
416
+ pxd: Lines
417
+
418
+ def __init__(self) -> None:
419
+ self.pyx = Lines()
420
+ self.pxd = Lines()
421
+
422
+ def add(self, indent: int, line: str) -> None:
423
+ """Pass the given data to method |Lines.add| of the `pyx` and `pxd` |Lines|
424
+ instances."""
425
+ self.pyx.add(indent, line)
426
+ if line.endswith(":") and (" class " not in line):
427
+ line = line[:-1]
428
+ self.pxd.add(indent, line)
429
+
430
+
431
+ def get_methodheader(
432
+ methodname: str, nogil: bool = False, idxarg: bool = False, inline: bool = True
433
+ ) -> str:
434
+ """Returns the Cython method header for methods without arguments except`self`.
435
+
436
+ Note the influence of the configuration flag `FASTCYTHON`:
437
+
438
+ >>> from hydpy.cythons.modelutils import get_methodheader
439
+ >>> from hydpy import config
440
+ >>> config.FASTCYTHON = False
441
+ >>> print(get_methodheader("test", nogil=True, idxarg=False, inline=True))
442
+ cpdef inline void test(self):
443
+ >>> config.FASTCYTHON = True
444
+ >>> methodheader = get_methodheader("test", nogil=True, idxarg=True, inline=False)
445
+ >>> print(methodheader) # doctest: +ELLIPSIS
446
+ cpdef void test(self, ...int... idx) noexcept nogil:
447
+ """
448
+ if not config.FASTCYTHON:
449
+ nogil = False
450
+ nogil_ = " noexcept nogil" if nogil else ""
451
+ idxarg_ = f", {INT} idx" if idxarg else ""
452
+ inline_ = " inline" if inline else ""
453
+ return f"cpdef{inline_} void {methodname}(self{idxarg_}){nogil_}:"
454
+
455
+
456
+ def decorate_method(
457
+ wrapped: Callable[[PyxWriter], Iterator[str]],
458
+ ) -> Callable[[PyxWriter, PyxPxdLines], None]:
459
+ """The decorated method returns a |Lines| object including a method header.
460
+ However, the |Lines| object is empty if the respective model does not implement a
461
+ method with the same name as the wrapped method.
462
+ """
463
+
464
+ def wrapper(self: PyxWriter, lines: PyxPxdLines) -> None:
465
+ if hasattr(self.model, wrapped.__name__):
466
+ print(f" . {wrapped.__name__}")
467
+ pyx, both = lines.pyx.add, lines.add
468
+ both(1, get_methodheader(wrapped.__name__, nogil=True))
469
+ for line in wrapped(self):
470
+ pyx(2, line)
471
+
472
+ functools.update_wrapper(wrapper, wrapped)
473
+ return wrapper
474
+
475
+
476
+ def compile_(cyname: str, pyxfilepath: str, buildpath: str) -> None:
477
+ """Translate Cython code to C code and compile it."""
478
+ argv = copy.deepcopy(sys.argv)
479
+ try:
480
+ sys.argv = [
481
+ sys.argv[0],
482
+ "build_ext",
483
+ f"--build-lib={buildpath}",
484
+ f"--build-temp={buildpath}",
485
+ ]
486
+ print(sys.argv)
487
+ exc_modules = [
488
+ setuptools.Extension(
489
+ name=f"hydpy.cythons.autogen.{cyname}",
490
+ sources=[pyxfilepath],
491
+ extra_compile_args=["-O2"],
492
+ )
493
+ ]
494
+ setuptools.setup(
495
+ ext_modules=build.cythonize(exc_modules), include_dirs=[numpy.get_include()]
496
+ )
497
+ finally:
498
+ sys.argv = argv
499
+
500
+
501
+ def move_dll(pyname: str, cyname: str, cydirpath: str, buildpath: str) -> None:
502
+ """Try to find the DLL file created by function |compile_| and try to move it to
503
+ the `autogen` folder of the `cythons` subpackage.
504
+
505
+ Usually, one does not need to apply |move_dll| directly. However, if you are a
506
+ model developer, you might see one of the following error messages from time to
507
+ time:
508
+
509
+ >>> from hydpy.cythons.modelutils import move_dll
510
+ >>> from hydpy.models.hland_96 import cythonizer as c
511
+ >>> move_dll(pyname=c.pyname, cyname=c.cyname,
512
+ ... cydirpath=c.cydirpath, buildpath=c.buildpath) # doctest: +ELLIPSIS
513
+ Traceback (most recent call last):
514
+ ...
515
+ OSError: After trying to cythonize `hland_96`, the resulting file `c_hland_96...` \
516
+ could not be found in directory `.../hydpy/cythons/autogen/_build` nor any of its \
517
+ subdirectories. The distutil report should tell whether the file has been stored \
518
+ somewhere else, is named somehow else, or could not be build at all.
519
+
520
+ >>> import os
521
+ >>> from unittest import mock
522
+ >>> from hydpy import TestIO
523
+ >>> with TestIO(): # doctest: +ELLIPSIS
524
+ ... with mock.patch.object(
525
+ ... type(c), "buildpath", new_callable=mock.PropertyMock
526
+ ... ) as mocked_buildpath:
527
+ ... mocked_buildpath.return_value = "_build"
528
+ ... os.makedirs("_build/subdir", exist_ok=True)
529
+ ... filepath = f"_build/subdir/c_hland_96{get_dllextension()}"
530
+ ... with open(filepath, "w"):
531
+ ... pass
532
+ ... with mock.patch(
533
+ ... "shutil.move",
534
+ ... side_effect=PermissionError("Denied!")):
535
+ ... move_dll(pyname=c.pyname, cyname=c.cyname,
536
+ ... cydirpath=c.cydirpath, buildpath=c.buildpath)
537
+ Traceback (most recent call last):
538
+ ...
539
+ PermissionError: After trying to cythonize `hland_96`, when trying to move the \
540
+ final cython module `c_hland_96...` from directory `_build` to directory \
541
+ `.../hydpy/cythons/autogen`, the following error occurred: Denied! A likely error \
542
+ cause is that the cython module `c_hland_96...` does already exist in this directory \
543
+ and is currently blocked by another Python process. Maybe it helps to close all \
544
+ Python processes and restart the cythonization afterwards.
545
+ """
546
+ dirinfos = os.walk(buildpath)
547
+ system_dependent_filename = None
548
+ for dirinfo in dirinfos:
549
+ for filename in dirinfo[2]:
550
+ if filename.startswith(cyname) and filename.endswith(_dllextension):
551
+ system_dependent_filename = filename
552
+ break
553
+ if system_dependent_filename:
554
+ try:
555
+ shutil.move(
556
+ os.path.join(dirinfo[0], system_dependent_filename),
557
+ os.path.join(cydirpath, cyname + _dllextension),
558
+ )
559
+ break
560
+ except BaseException:
561
+ objecttools.augment_excmessage(
562
+ f"After trying to cythonize `{pyname}`, when trying to move the "
563
+ f"final cython module `{system_dependent_filename}` from "
564
+ f"directory `{buildpath}` to directory "
565
+ f"`{objecttools.repr_(cydirpath)}`",
566
+ f"A likely error cause is that the cython module "
567
+ f"`{cyname}{_dllextension}` does already exist in this directory "
568
+ f"and is currently blocked by another Python process. Maybe it "
569
+ f"helps to close all Python processes and restart the "
570
+ f"cythonization afterwards.",
571
+ )
572
+ else:
573
+ raise OSError(
574
+ f"After trying to cythonize `{pyname}`, the resulting file "
575
+ f"`{cyname}{_dllextension}` could not be found in directory "
576
+ f"`{objecttools.repr_(buildpath)}` nor any of its subdirectories. The "
577
+ f"distutil report should tell whether the file has been stored somewhere "
578
+ f"else, is named somehow else, or could not be build at all."
579
+ )
580
+
581
+
582
+ class Cythonizer:
583
+ """Handles the writing, compiling and initialisation of Cython models."""
584
+
585
+ Model: type[modeltools.Model]
586
+ Parameters: type[parametertools.Parameters]
587
+ Sequences: type[sequencetools.Sequences]
588
+ tester: testtools.Tester
589
+ pymodule: str
590
+ _cymodule: types.ModuleType | None
591
+
592
+ def __init__(self) -> None:
593
+ self._cymodule = None
594
+ frame = inspect.currentframe()
595
+ assert frame is not None
596
+ frame = frame.f_back
597
+ assert frame is not None
598
+ self.pymodule = frame.f_globals["__name__"]
599
+ for key, value in frame.f_locals.items():
600
+ setattr(self, key, value)
601
+
602
+ def cythonize(self) -> None:
603
+ """Translate Python source code of the relevant model first into Cython and
604
+ then into C, compile it, and move the resulting dll file to the `autogen`
605
+ subfolder of subpackage `cythons`."""
606
+ print(f"Translate module/package {self.pyname}.")
607
+ self.pyxwriter.write()
608
+ print(f"Compile module {self.cyname}.")
609
+ compile_(
610
+ cyname=self.cyname, pyxfilepath=self.pyxfilepath, buildpath=self.buildpath
611
+ )
612
+ move_dll(
613
+ pyname=self.pyname,
614
+ cyname=self.cyname,
615
+ cydirpath=self.cydirpath,
616
+ buildpath=self.buildpath,
617
+ )
618
+
619
+ @property
620
+ def pyname(self) -> str:
621
+ """Name of the original Python module or package.
622
+
623
+ >>> from hydpy.models.hland import cythonizer
624
+ >>> cythonizer.pyname
625
+ 'hland'
626
+ >>> from hydpy.models.hland_96 import cythonizer
627
+ >>> cythonizer.pyname
628
+ 'hland_96'
629
+ """
630
+ return self.pymodule.split(".")[-1]
631
+
632
+ @property
633
+ def cyname(self) -> str:
634
+ """Name of the compiled module.
635
+
636
+ >>> from hydpy.models.hland import cythonizer
637
+ >>> cythonizer.cyname
638
+ 'c_hland'
639
+ >>> from hydpy.models.hland_96 import cythonizer
640
+ >>> cythonizer.cyname
641
+ 'c_hland_96'
642
+ """
643
+ return "c_" + self.pyname
644
+
645
+ @property
646
+ def cydirpath(self) -> str:
647
+ """The absolute path of the directory containing the compiled modules.
648
+
649
+ >>> from hydpy.models.hland import cythonizer
650
+ >>> from hydpy import repr_
651
+ >>> repr_(cythonizer.cydirpath) # doctest: +ELLIPSIS
652
+ '.../hydpy/cythons/autogen'
653
+ >>> import os
654
+ >>> os.path.exists(cythonizer.cydirpath)
655
+ True
656
+ """
657
+ return cythons.autogen.__path__[0]
658
+
659
+ @property
660
+ def cymodule(self) -> types.ModuleType:
661
+ """The compiled module.
662
+
663
+ Property |Cythonizer.cymodule| returns the relevant DLL module:
664
+
665
+ >>> from hydpy.models.hland_96 import cythonizer
666
+ >>> from hydpy.cythons.autogen import c_hland_96
667
+ >>> c_hland_96 is cythonizer.cymodule
668
+ True
669
+
670
+ However, if this module is missing for some reasons, it tries to create the
671
+ module first and returns it afterwards. For demonstration purposes, we define
672
+ a wrong |Cythonizer.cyname|:
673
+
674
+ >>> from hydpy.cythons.modelutils import Cythonizer
675
+ >>> cyname = Cythonizer.cyname
676
+ >>> Cythonizer.cyname = "wrong"
677
+ >>> cythonizer._cymodule = None
678
+ >>> from unittest import mock
679
+ >>> with mock.patch.object(Cythonizer, "cythonize") as mock:
680
+ ... cythonizer.cymodule
681
+ Traceback (most recent call last):
682
+ ...
683
+ ModuleNotFoundError: No module named 'hydpy.cythons.autogen.wrong'
684
+ >>> mock.call_args_list
685
+ [call()]
686
+
687
+ >>> Cythonizer.cyname = cyname
688
+ """
689
+ cymodule = self._cymodule
690
+ if cymodule:
691
+ return cymodule
692
+ modulepath = f"hydpy.cythons.autogen.{self.cyname}"
693
+ try:
694
+ self._cymodule = importlib.import_module(modulepath)
695
+ except ModuleNotFoundError:
696
+ self.cythonize()
697
+ self._cymodule = importlib.import_module(modulepath)
698
+ return self._cymodule
699
+
700
+ @property
701
+ def pyxfilepath(self) -> str:
702
+ """The absolute path of the compiled module.
703
+
704
+ >>> from hydpy.models.hland_96 import cythonizer
705
+ >>> from hydpy import repr_
706
+ >>> repr_(cythonizer.pyxfilepath) # doctest: +ELLIPSIS
707
+ '.../hydpy/cythons/autogen/c_hland_96.pyx'
708
+ >>> import os
709
+ >>> os.path.exists(cythonizer.pyxfilepath)
710
+ True
711
+ """
712
+ return os.path.join(self.cydirpath, f"{self.cyname}.pyx")
713
+
714
+ @property
715
+ def dllfilepath(self) -> str:
716
+ """The absolute path of the compiled module.
717
+
718
+ >>> from hydpy.models.hland_96 import cythonizer
719
+ >>> from hydpy import repr_
720
+ >>> repr_(cythonizer.dllfilepath) # doctest: +ELLIPSIS
721
+ '.../hydpy/cythons/autogen/c_hland_96...'
722
+ >>> import os
723
+ >>> os.path.exists(os.path.split(cythonizer.dllfilepath)[0])
724
+ True
725
+ """
726
+ return os.path.join(self.cydirpath, f"{self.cyname}{_dllextension}")
727
+
728
+ @property
729
+ def buildpath(self) -> str:
730
+ """The absolute path for temporarily build files.
731
+
732
+ >>> from hydpy.models.hland_96 import cythonizer
733
+ >>> from hydpy import repr_
734
+ >>> repr_(cythonizer.buildpath) # doctest: +ELLIPSIS
735
+ '.../hydpy/cythons/autogen/_build'
736
+ """
737
+ return os.path.join(self.cydirpath, "_build")
738
+
739
+ @property
740
+ def pyxwriter(self) -> PyxWriter:
741
+ """A new |PyxWriter| instance.
742
+
743
+ >>> from hydpy.models.hland_96 import cythonizer
744
+ >>> pyxwriter = cythonizer.pyxwriter
745
+ >>> from hydpy import classname
746
+ >>> classname(pyxwriter)
747
+ 'PyxWriter'
748
+ >>> cythonizer.pyxwriter is pyxwriter
749
+ False
750
+ """
751
+ model = self.Model()
752
+ dict_ = vars(self)
753
+ dict_["model"] = model
754
+ model.parameters = importtools.prepare_parameters(dict_)
755
+ model.sequences = importtools.prepare_sequences(dict_)
756
+ return PyxWriter(self, model, self.pyxfilepath)
757
+
758
+
759
+ class PyxWriter:
760
+ """Translates the source code of Python models into Cython source code.
761
+
762
+ Method |PyxWriter| serves as a master method, which triggers the complete writing
763
+ process. The other properties and methods supply the required code lines. Their
764
+ names are selected to match the names of the original Python models as closely as
765
+ possible.
766
+ """
767
+
768
+ cythonizer: Cythonizer
769
+ model: modeltools.Model
770
+ pyxpath: str
771
+ pxdpath: str
772
+
773
+ def __init__(
774
+ self, cythonizer: Cythonizer, model: modeltools.Model, pyxpath: str
775
+ ) -> None:
776
+ self.cythonizer = cythonizer
777
+ self.model = model
778
+ self.pyxpath = pyxpath
779
+ self.pxdpath = pyxpath.replace(".pyx", ".pxd")
780
+
781
+ def write(self) -> None:
782
+ """Collect the source code and write it into a Cython extension file ("pyx")
783
+ and its definition file ("pxd")."""
784
+
785
+ lines = PyxPxdLines()
786
+
787
+ print(" * cython options")
788
+ self.cythondistutilsoptions(lines)
789
+ print(" * C imports")
790
+ self.cimports(lines)
791
+ print(" - callback features")
792
+ self.callbackfeatures(lines)
793
+ print(" * constants (if defined)")
794
+ self.constants(lines)
795
+ print(" * parameter classes")
796
+ self.parameters(lines)
797
+ print(" * sequence classes")
798
+ self.sequences(lines)
799
+ print(" * numerical parameters")
800
+ self.numericalparameters(lines)
801
+ print(" * submodel classes")
802
+ self.submodels(lines)
803
+ print(" * model class")
804
+ print(" - model attributes")
805
+ self.modeldeclarations(lines)
806
+ print(" - standard functions")
807
+ self.modelstandardfunctions(lines)
808
+ print(" - numeric functions")
809
+ self.modelnumericfunctions(lines)
810
+ print(" - additional functions")
811
+ self.modeluserfunctions(lines)
812
+
813
+ with open(self.pyxpath, "w", encoding=config.ENCODING) as pyxfile:
814
+ pyxfile.write(repr(lines.pyx))
815
+ with open(self.pxdpath, "w", encoding=config.ENCODING) as pxdfile:
816
+ pxdfile.write(repr(lines.pxd))
817
+
818
+ def cythondistutilsoptions(self, lines: PyxPxdLines) -> None:
819
+ """Cython and Distutils option lines.
820
+
821
+ Use the configuration options "FASTCYTHON" and "PROFILECYTHON" to configure the
822
+ cythonization processes as follows:
823
+
824
+ >>> from hydpy.cythons.modelutils import PyxWriter
825
+ >>> pyxwriter = PyxWriter(None, None, "file.pyx")
826
+ >>> from hydpy.cythons.modelutils import PyxPxdLines
827
+ >>> lines = PyxPxdLines()
828
+ >>> pyxwriter.cythondistutilsoptions(lines)
829
+ >>> lines.pyx # doctest: +ELLIPSIS
830
+ #!python
831
+ # distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION
832
+ # cython: language_level=3
833
+ # cython: cpow=True
834
+ # cython: boundscheck=False
835
+ # cython: wraparound=False
836
+ # cython: initializedcheck=False
837
+ # cython: cdivision=True
838
+ <BLANKLINE>
839
+
840
+ >>> from hydpy import config
841
+ >>> config.FASTCYTHON = False
842
+ >>> config.PROFILECYTHON = True
843
+ >>> lines.pyx.clear()
844
+ >>> pyxwriter.cythondistutilsoptions(lines)
845
+ >>> lines.pyx # doctest: +ELLIPSIS
846
+ #!python
847
+ # distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION
848
+ # cython: language_level=3
849
+ # cython: cpow=True
850
+ # cython: boundscheck=True
851
+ # cython: wraparound=True
852
+ # cython: initializedcheck=True
853
+ # cython: cdivision=False
854
+ # cython: linetrace=True
855
+ # distutils: define_macros=CYTHON_TRACE=1
856
+ # distutils: define_macros=CYTHON_TRACE_NOGIL=1
857
+ <BLANKLINE>
858
+
859
+ >>> config.FASTCYTHON = True
860
+ >>> config.PROFILECYTHON = False
861
+ """
862
+
863
+ # ToDo: do not share code with prepare.__prepare_cythonoptions
864
+
865
+ both = lines.add
866
+
867
+ both(0, "#!python")
868
+ both(0, "# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION")
869
+ both(0, "# cython: language_level=3")
870
+ both(0, "# cython: cpow=True")
871
+
872
+ if config.FASTCYTHON:
873
+ both(0, "# cython: boundscheck=False")
874
+ both(0, "# cython: wraparound=False")
875
+ both(0, "# cython: initializedcheck=False")
876
+ both(0, "# cython: cdivision=True")
877
+ else:
878
+ both(0, "# cython: boundscheck=True")
879
+ both(0, "# cython: wraparound=True")
880
+ both(0, "# cython: initializedcheck=True")
881
+ both(0, "# cython: cdivision=False")
882
+
883
+ if config.PROFILECYTHON:
884
+ both(0, "# cython: linetrace=True")
885
+ both(0, "# distutils: define_macros=CYTHON_TRACE=1")
886
+ both(0, "# distutils: define_macros=CYTHON_TRACE_NOGIL=1")
887
+
888
+ def cimports(self, lines: PyxPxdLines) -> None:
889
+ """Import command lines."""
890
+ add = lines.add
891
+ add(0, "from typing import Optional")
892
+ add(0, "import numpy")
893
+ add(0, "cimport numpy")
894
+ add(
895
+ 0,
896
+ "from libc.math cimport exp, fabs, log, sin, cos, tan, tanh, asin, acos, "
897
+ "atan, isnan, isinf",
898
+ )
899
+ add(0, "from libc.math cimport NAN as nan")
900
+ add(0, "from libc.math cimport INFINITY as inf")
901
+ add(0, "import cython")
902
+ add(0, "from cpython.mem cimport PyMem_Malloc")
903
+ add(0, "from cpython.mem cimport PyMem_Realloc")
904
+ add(0, "from cpython.mem cimport PyMem_Free")
905
+ add(0, "from hydpy.cythons.autogen cimport configutils")
906
+ add(0, "from hydpy.cythons.autogen cimport interfaceutils")
907
+ add(0, "from hydpy.cythons.autogen cimport interputils")
908
+ add(0, "from hydpy.cythons.autogen import pointerutils")
909
+ add(0, "from hydpy.cythons.autogen cimport pointerutils")
910
+ add(0, "from hydpy.cythons.autogen cimport quadutils")
911
+ add(0, "from hydpy.cythons.autogen cimport rootutils")
912
+ add(0, "from hydpy.cythons.autogen cimport smoothutils")
913
+ add(0, "from hydpy.cythons.autogen cimport masterinterface")
914
+
915
+ def constants(self, lines: PyxPxdLines) -> None:
916
+ """Constants declaration lines."""
917
+ both = lines.add
918
+ for name, member in vars(self.cythonizer).items():
919
+ if (
920
+ name.isupper()
921
+ and not inspect.isclass(member)
922
+ and isinstance(member, CHECKABLE_TYPES)
923
+ ):
924
+ ndim = numpy.array(member).ndim
925
+ ctype = TYPE2STR[type(member)] + NDIM2STR[ndim]
926
+ both(0, f"cdef public {ctype} {name} = {member}")
927
+
928
+ def parameters(self, lines: PyxPxdLines) -> None:
929
+ """Parameter declaration lines."""
930
+ if pars := self.model.parameters:
931
+ pt = parametertools
932
+ pyx, pxd, both = lines.pyx.add, lines.pxd.add, lines.add
933
+ both(0, "@cython.final")
934
+ both(0, "cdef class Parameters:")
935
+ pyx(1, "pass")
936
+ if not self.model.parameters:
937
+ pxd(1, "pass")
938
+ for subpars in pars:
939
+ pxd(1, f"cdef public {type(subpars).__name__} {subpars.name}")
940
+ for subpars in pars:
941
+ print(f" - {subpars.name}")
942
+ both(0, "@cython.final")
943
+ both(0, f"cdef class {type(subpars).__name__}:")
944
+ for par in subpars:
945
+ try:
946
+ ctype = TYPE2STR[par.TYPE] + NDIM2STR[par.NDIM]
947
+ except KeyError:
948
+ ctype = par.TYPE + NDIM2STR[par.NDIM] # type: ignore[operator]
949
+ pxd(1, f"cdef public {ctype} {par.name}")
950
+ if isinstance(par, pt.KeywordParameter1D):
951
+ pxd(1, f"cdef public {TYPE2STR[int]} _{par.name}_entrymin")
952
+ elif isinstance(par, pt.KeywordParameter2D):
953
+ prefix = f"cdef public {TYPE2STR[int]} _{par.name}"
954
+ for suffix in ("rowmin", "columnmin"):
955
+ pxd(1, f"{prefix}_{suffix}")
956
+ elif isinstance(par, pt.CallbackParameter):
957
+ pxd(1, f"cdef CallbackType {par.name}_callback")
958
+ pxd(1, f"cdef CallbackWrapper _{par.name}_wrapper")
959
+ if callbackpars := tuple(
960
+ p for p in subpars if isinstance(p, pt.CallbackParameter)
961
+ ):
962
+ pyx(0, "")
963
+ for par in callbackpars:
964
+ cn = f"{par.name}_callback"
965
+ both(1, f"cpdef void init_{cn}(self):")
966
+ pyx(2, f"self.{cn} = do_nothing")
967
+ pyx(2, "cdef CallbackWrapper wrapper = CallbackWrapper()")
968
+ pyx(2, "wrapper.callback = do_nothing")
969
+ pyx(2, f"self._{par.name}_wrapper = wrapper")
970
+ pyx(0, "")
971
+ both(1, f"cpdef CallbackWrapper get_{cn}(self):")
972
+ pyx(2, f"return self._{par.name}_wrapper")
973
+ pyx(0, "")
974
+ both(1, f"cpdef void set_{cn}(self, CallbackWrapper wrapper):")
975
+ pyx(2, f"self.{cn} = wrapper.callback")
976
+ pyx(2, f"self._{par.name}_wrapper = wrapper")
977
+ pyx(0, "")
978
+ else:
979
+ pyx(1, "pass")
980
+
981
+ def sequences(self, lines: PyxPxdLines) -> None:
982
+ """Sequence declaration lines."""
983
+ sqt = sequencetools
984
+ pyx, pxd, both = lines.pyx.add, lines.pxd.add, lines.add
985
+ both(0, "@cython.final")
986
+ both(0, "cdef class Sequences:")
987
+ pyx(1, "pass")
988
+ if not self.model.sequences:
989
+ pxd(1, "pass")
990
+ for subseqs in self.model.sequences:
991
+ pxd(1, f"cdef public {type(subseqs).__name__} {subseqs.name}")
992
+ if self.model.sequences.states:
993
+ pxd(1, "cdef public StateSequences old_states")
994
+ pxd(1, "cdef public StateSequences new_states")
995
+ for subseqs in self.model.sequences:
996
+ print(f" - {subseqs.name}")
997
+ both(0, "@cython.final")
998
+ both(0, f"cdef class {type(subseqs).__name__}:")
999
+ if isinstance(subseqs, (sqt.LogSequences, sqt.AideSequences)):
1000
+ pyx(1, "pass")
1001
+ for seq in subseqs:
1002
+ ctype = f"double{NDIM2STR[seq.NDIM]}"
1003
+ if isinstance(subseqs, sqt.LinkSequences):
1004
+ if seq.NDIM == 0:
1005
+ pxd(1, f"cdef double *{seq.name}")
1006
+ elif seq.NDIM == 1:
1007
+ pxd(1, f"cdef double **{seq.name}")
1008
+ pxd(1, f"cdef public {INT} len_{seq.name}")
1009
+ pxd(1, f"cdef public {TYPE2STR[int]}[:] _{seq.name}_ready")
1010
+ else:
1011
+ pxd(1, f"cdef public {ctype} {seq.name}")
1012
+ pxd(1, f"cdef public {INT} _{seq.name}_ndim")
1013
+ pxd(1, f"cdef public {INT} _{seq.name}_length")
1014
+ for idx in range(seq.NDIM):
1015
+ pxd(1, f"cdef public {INT} _{seq.name}_length_{idx}")
1016
+ if seq.NUMERIC:
1017
+ ctype_numeric = "double" + NDIM2STR[seq.NDIM + 1]
1018
+ pxd(1, f"cdef public {ctype_numeric} _{seq.name}_points")
1019
+ pxd(1, f"cdef public {ctype_numeric} _{seq.name}_results")
1020
+ if isinstance(subseqs, sqt.FluxSequences):
1021
+ pxd(1, f"cdef public {ctype_numeric} " f"_{seq.name}_integrals")
1022
+ pxd(1, f"cdef public {ctype} _{seq.name}_sum")
1023
+ if isinstance(seq, sqt.IOSequence):
1024
+ self.iosequence(lines, seq)
1025
+ if isinstance(subseqs, sqt.IOSequences):
1026
+ self.load_data(lines, subseqs)
1027
+ self.save_data(lines, subseqs)
1028
+ if isinstance(subseqs, sqt.LinkSequences):
1029
+ self.set_pointer(lines, subseqs)
1030
+ self.get_value(lines, subseqs)
1031
+ self.set_value(lines, subseqs)
1032
+ if isinstance(subseqs, (sqt.InputSequences, sqt.OutputSequences)):
1033
+ self.set_pointer(lines, subseqs)
1034
+ if isinstance(subseqs, sqt.OutputSequences):
1035
+ self.update_outputs(lines, subseqs)
1036
+
1037
+ @staticmethod
1038
+ def iosequence(lines: PyxPxdLines, seq: sequencetools.IOSequence) -> None:
1039
+ """Declaration lines for the given |IOSequence| object."""
1040
+ ctype = f"double{NDIM2STR[seq.NDIM+1]}"
1041
+ add = lines.pxd.add
1042
+ add(1, f"cdef public bint _{seq.name}_ramflag")
1043
+ add(1, f"cdef public {ctype} _{seq.name}_array")
1044
+ add(1, f"cdef public bint _{seq.name}_diskflag_reading")
1045
+ add(1, f"cdef public bint _{seq.name}_diskflag_writing")
1046
+ add(1, f"cdef public double[:] _{seq.name}_ncarray")
1047
+ if isinstance(seq, sequencetools.InputSequence) and (seq.NDIM == 0):
1048
+ add(1, f"cdef public bint _{seq.name}_inputflag")
1049
+ add(1, f"cdef double *_{seq.name}_inputpointer")
1050
+ elif isinstance(seq, sequencetools.OutputSequence) and (seq.NDIM == 0):
1051
+ add(1, f"cdef public bint _{seq.name}_outputflag")
1052
+ add(1, f"cdef double *_{seq.name}_outputpointer")
1053
+
1054
+ @staticmethod
1055
+ def _get_index(ndim: int) -> str:
1056
+ return ", ".join(f"jdx{idx}" for idx in range(ndim))
1057
+
1058
+ @staticmethod
1059
+ def _add_cdef_jdxs(
1060
+ lines: PyxPxdLines, subseqs: sequencetools.IOSequences[Any, Any, Any]
1061
+ ) -> None:
1062
+ maxndim = max(seq.NDIM for seq in subseqs)
1063
+ if maxndim:
1064
+ jdxs = ", ".join(f"jdx{ndim}" for ndim in range(maxndim))
1065
+ lines.pyx.add(2, f"cdef {INT} {jdxs}")
1066
+
1067
+ def reset_reuseflags(self, lines: PyxPxdLines) -> None:
1068
+ """Reset reuse flag statements."""
1069
+ print(" . reset_reuseflags")
1070
+ pyx, both = lines.pyx.add, lines.add
1071
+ both(1, f"cpdef void reset_reuseflags(self){_nogil}:")
1072
+ if (methods := self.model.REUSABLE_METHODS) or self.model.find_submodels(
1073
+ include_subsubmodels=False, include_optional=True, repeat_sharedmodels=True
1074
+ ):
1075
+ for method in methods:
1076
+ pyx(2, f"self.{method.REUSEMARKER} = False")
1077
+ self._call_submodel_method(lines, "reset_reuseflags()")
1078
+ else:
1079
+ pyx(2, "pass")
1080
+
1081
+ @classmethod
1082
+ def load_data(
1083
+ cls, lines: PyxPxdLines, subseqs: sequencetools.IOSequences[Any, Any, Any]
1084
+ ) -> None:
1085
+ """Load data statements."""
1086
+ print(" . load_data")
1087
+ pyx, both = lines.pyx.add, lines.add
1088
+ both(1, f"cpdef inline void load_data(self, {INT} idx) {_nogil}:")
1089
+ cls._add_cdef_jdxs(lines, subseqs)
1090
+ pyx(2, f"cdef {INT} k")
1091
+ for seq in subseqs:
1092
+ if isinstance(seq, sequencetools.InputSequence) and (seq.NDIM == 0):
1093
+ pyx(2, f"if self._{seq.name}_inputflag:")
1094
+ pyx(3, f"self.{seq.name} = self._{seq.name}_inputpointer[0]")
1095
+ if_or_elif = "elif"
1096
+ else:
1097
+ if_or_elif = "if"
1098
+ pyx(2, f"{if_or_elif} self._{seq.name}_diskflag_reading:")
1099
+ if seq.NDIM == 0:
1100
+ pyx(3, f"self.{seq.name} = self._{seq.name}_ncarray[0]")
1101
+ else:
1102
+ pyx(3, "k = 0")
1103
+ for idx in range(seq.NDIM):
1104
+ pyx(
1105
+ 3 + idx,
1106
+ f"for jdx{idx} in range(self._{seq.name}_length_{idx}):",
1107
+ )
1108
+ pyx(
1109
+ 3 + seq.NDIM,
1110
+ f"self.{seq.name}[{cls._get_index(seq.NDIM)}] "
1111
+ f"= self._{seq.name}_ncarray[k]",
1112
+ )
1113
+ pyx(3 + seq.NDIM, "k += 1")
1114
+ pyx(2, f"elif self._{seq.name}_ramflag:")
1115
+ if seq.NDIM == 0:
1116
+ pyx(3, f"self.{seq.name} = self._{seq.name}_array[idx]")
1117
+ else:
1118
+ for idx in range(seq.NDIM):
1119
+ pyx(
1120
+ 3 + idx,
1121
+ f"for jdx{idx} in " f"range(self._{seq.name}_length_{idx}):",
1122
+ )
1123
+ index = cls._get_index(seq.NDIM)
1124
+ pyx(
1125
+ 3 + seq.NDIM,
1126
+ f"self.{seq.name}[{index}] = self._{seq.name}_array[idx, {index}]",
1127
+ )
1128
+
1129
+ @classmethod
1130
+ def save_data(
1131
+ cls, lines: PyxPxdLines, subseqs: sequencetools.IOSequences[Any, Any, Any]
1132
+ ) -> None:
1133
+ """Save data statements."""
1134
+ print(" . save_data")
1135
+ pyx, both = lines.pyx.add, lines.add
1136
+ both(1, f"cpdef inline void save_data(self, {INT} idx) {_nogil}:")
1137
+ cls._add_cdef_jdxs(lines, subseqs)
1138
+ pyx(2, f"cdef {INT} k")
1139
+ for seq in subseqs:
1140
+ pyx(2, f"if self._{seq.name}_diskflag_writing:")
1141
+ if seq.NDIM == 0:
1142
+ pyx(3, f"self._{seq.name}_ncarray[0] = self.{seq.name}")
1143
+ else:
1144
+ pyx(3, "k = 0")
1145
+ for idx in range(seq.NDIM):
1146
+ pyx(
1147
+ 3 + idx,
1148
+ f"for jdx{idx} in " f"range(self._{seq.name}_length_{idx}):",
1149
+ )
1150
+ index = cls._get_index(seq.NDIM)
1151
+ pyx(
1152
+ 3 + seq.NDIM,
1153
+ f"self._{seq.name}_ncarray[k] = self.{seq.name}[{index}]",
1154
+ )
1155
+ pyx(3 + seq.NDIM, "k += 1")
1156
+ pyx(2, f"if self._{seq.name}_ramflag:")
1157
+ if seq.NDIM == 0:
1158
+ pyx(3, f"self._{seq.name}_array[idx] = self.{seq.name}")
1159
+ else:
1160
+ for idx in range(seq.NDIM):
1161
+ pyx(
1162
+ 3 + idx,
1163
+ f"for jdx{idx} in " f"range(self._{seq.name}_length_{idx}):",
1164
+ )
1165
+ index = cls._get_index(seq.NDIM)
1166
+ pyx(
1167
+ 3 + seq.NDIM,
1168
+ f"self._{seq.name}_array[idx, {index}] = self.{seq.name}[{index}]",
1169
+ )
1170
+
1171
+ def set_pointer(
1172
+ self,
1173
+ lines: PyxPxdLines,
1174
+ subseqs: (
1175
+ sequencetools.InputSequences
1176
+ | sequencetools.OutputSequences[Any]
1177
+ | sequencetools.LinkSequences[Any]
1178
+ ),
1179
+ ) -> None:
1180
+ """Set pointer statements for all input, output, and link sequences."""
1181
+ if isinstance(subseqs, sequencetools.InputSequences):
1182
+ self.set_pointerinput(lines, subseqs)
1183
+ elif isinstance(subseqs, sequencetools.OutputSequences):
1184
+ self.set_pointeroutput(lines, subseqs)
1185
+ else:
1186
+ if any(seq.NDIM == 0 for seq in subseqs):
1187
+ self.set_pointer0d(lines, subseqs)
1188
+ if any(seq.NDIM == 1 for seq in subseqs):
1189
+ self.alloc(lines, subseqs)
1190
+ self.dealloc(lines, subseqs)
1191
+ self.set_pointer1d(lines, subseqs)
1192
+
1193
+ @staticmethod
1194
+ def set_pointer0d(
1195
+ lines: PyxPxdLines, subseqs: sequencetools.LinkSequences[Any]
1196
+ ) -> None:
1197
+ """Set pointer statements for 0-dimensional link sequences."""
1198
+ print(" . set_pointer0d")
1199
+ pyx, both = lines.pyx.add, lines.add
1200
+ both(
1201
+ 1, "cpdef inline set_pointer0d(self, str name, pointerutils.Double value):"
1202
+ )
1203
+ pyx(2, "cdef pointerutils.PDouble pointer = pointerutils.PDouble(value)")
1204
+ for seq in (seq for seq in subseqs if seq.NDIM == 0):
1205
+ pyx(2, f'if name == "{seq.name}":')
1206
+ pyx(3, f"self.{seq.name} = pointer.p_value")
1207
+
1208
+ @staticmethod
1209
+ def get_value(
1210
+ lines: PyxPxdLines, subseqs: sequencetools.LinkSequences[Any]
1211
+ ) -> None:
1212
+ """Get value statements for link sequences."""
1213
+ print(" . get_value")
1214
+ pyx, both = lines.pyx.add, lines.add
1215
+ both(1, "cpdef get_value(self, str name):")
1216
+ pyx(2, f"cdef {INT} idx")
1217
+ for seq in subseqs:
1218
+ pyx(2, f'if name == "{seq.name}":')
1219
+ if seq.NDIM == 0:
1220
+ pyx(3, f"return self.{seq.name}[0]")
1221
+ elif seq.NDIM == 1:
1222
+ pyx(3, f"values = numpy.empty(self.len_{seq.name})")
1223
+ pyx(3, f"for idx in range(self.len_{seq.name}):")
1224
+ PyxWriter._check_pointer(lines, seq)
1225
+ pyx(4, f"values[idx] = self.{seq.name}[idx][0]")
1226
+ pyx(3, "return values")
1227
+
1228
+ @staticmethod
1229
+ def set_value(
1230
+ lines: PyxPxdLines, subseqs: sequencetools.LinkSequences[Any]
1231
+ ) -> None:
1232
+ """Set value statements for link sequences."""
1233
+ print(" . set_value")
1234
+ pyx, both = lines.pyx.add, lines.add
1235
+ both(1, "cpdef set_value(self, str name, value):")
1236
+ for seq in subseqs:
1237
+ pyx(2, f'if name == "{seq.name}":')
1238
+ if seq.NDIM == 0:
1239
+ pyx(3, f"self.{seq.name}[0] = value")
1240
+ elif seq.NDIM == 1:
1241
+ pyx(3, f"for idx in range(self.len_{seq.name}):")
1242
+ PyxWriter._check_pointer(lines, seq)
1243
+ pyx(4, f"self.{seq.name}[idx][0] = value[idx]")
1244
+
1245
+ @staticmethod
1246
+ def _check_pointer(lines: PyxPxdLines, seq: sequencetools.LinkSequence) -> None:
1247
+ pyx = lines.pyx.add
1248
+ pyx(4, f"pointerutils.check0(self._{seq.name}_length_0)")
1249
+ pyx(4, f"if self._{seq.name}_ready[idx] == 0:")
1250
+ pyx(5, f"pointerutils.check1(self._{seq.name}_length_0, idx)")
1251
+ pyx(5, f"pointerutils.check2(self._{seq.name}_ready, idx)")
1252
+
1253
+ @staticmethod
1254
+ def alloc(lines: PyxPxdLines, subseqs: sequencetools.LinkSequences[Any]) -> None:
1255
+ """Allocate memory statements for 1-dimensional link sequences."""
1256
+ print(" . setlength")
1257
+ pyx, both = lines.pyx.add, lines.add
1258
+ both(1, f"cpdef inline alloc(self, name, {TYPE2STR[int]} length):")
1259
+ for seq in (seq for seq in subseqs if seq.NDIM == 1):
1260
+ pyx(2, f'if name == "{seq.name}":')
1261
+ pyx(3, f"self._{seq.name}_length_0 = length")
1262
+ pyx(
1263
+ 3,
1264
+ f"self._{seq.name}_ready = "
1265
+ f"numpy.full(length, 0, dtype={ TYPE2STR[int].split('_')[0]})",
1266
+ )
1267
+ pyx(
1268
+ 3,
1269
+ f"self.{seq.name} = "
1270
+ f"<double**> PyMem_Malloc(length * sizeof(double*))",
1271
+ )
1272
+
1273
+ @staticmethod
1274
+ def dealloc(lines: PyxPxdLines, subseqs: sequencetools.LinkSequences[Any]) -> None:
1275
+ """Deallocate memory statements for 1-dimensional link sequences."""
1276
+ print(" . dealloc")
1277
+ pyx, both = lines.pyx.add, lines.add
1278
+ both(1, "cpdef inline dealloc(self, name):")
1279
+ for seq in (seq for seq in subseqs if seq.NDIM == 1):
1280
+ pyx(2, f'if name == "{seq.name}":')
1281
+ pyx(3, f"PyMem_Free(self.{seq.name})")
1282
+
1283
+ @staticmethod
1284
+ def set_pointer1d(
1285
+ lines: PyxPxdLines, subseqs: sequencetools.LinkSequences[Any]
1286
+ ) -> None:
1287
+ """Set_pointer statements for 1-dimensional link sequences."""
1288
+ print(" . set_pointer1d")
1289
+ pyx, both = lines.pyx.add, lines.add
1290
+ both(
1291
+ 1,
1292
+ "cpdef inline set_pointer1d"
1293
+ f"(self, str name, pointerutils.Double value, {INT} idx):",
1294
+ )
1295
+ pyx(2, "cdef pointerutils.PDouble pointer = pointerutils.PDouble(value)")
1296
+ for seq in (seq for seq in subseqs if seq.NDIM == 1):
1297
+ pyx(2, f'if name == "{seq.name}":')
1298
+ pyx(3, f"self.{seq.name}[idx] = pointer.p_value")
1299
+ pyx(3, f"self._{seq.name}_ready[idx] = 1")
1300
+
1301
+ @classmethod
1302
+ def set_pointerinput(
1303
+ cls, lines: PyxPxdLines, subseqs: sequencetools.InputSequences
1304
+ ) -> None:
1305
+ """Set pointer statements for input sequences."""
1306
+ print(" . set_pointerinput")
1307
+ pyx, both = lines.pyx.add, lines.add
1308
+ both(
1309
+ 1,
1310
+ "cpdef inline set_pointerinput"
1311
+ "(self, str name, pointerutils.PDouble value):",
1312
+ )
1313
+ subseqs_ = cls._filter_inputsequences(subseqs)
1314
+ if subseqs_:
1315
+ for seq in subseqs_:
1316
+ pyx(2, f'if name == "{seq.name}":')
1317
+ pyx(3, f"self._{seq.name}_inputpointer = value.p_value")
1318
+ else:
1319
+ pyx(2, "pass")
1320
+
1321
+ @classmethod
1322
+ def set_pointeroutput(
1323
+ cls, lines: PyxPxdLines, subseqs: sequencetools.OutputSequences[Any]
1324
+ ) -> None:
1325
+ """Set pointer statements for output sequences."""
1326
+ print(" . set_pointeroutput")
1327
+ pyx, both = lines.pyx.add, lines.add
1328
+ both(
1329
+ 1,
1330
+ "cpdef inline set_pointeroutput"
1331
+ "(self, str name, pointerutils.PDouble value):",
1332
+ )
1333
+ subseqs_ = cls._filter_outputsequences(subseqs)
1334
+ if subseqs_:
1335
+ for seq in subseqs_:
1336
+ pyx(2, f'if name == "{seq.name}":')
1337
+ pyx(3, f"self._{seq.name}_outputpointer = value.p_value")
1338
+ else:
1339
+ pyx(2, "pass")
1340
+
1341
+ @staticmethod
1342
+ def _filter_inputsequences(
1343
+ subseqs: sequencetools.InputSequences,
1344
+ ) -> list[sequencetools.InputSequence]:
1345
+ return [subseq for subseq in subseqs if not subseq.NDIM]
1346
+
1347
+ @staticmethod
1348
+ def _filter_outputsequences(
1349
+ subseqs: sequencetools.OutputSequences[Any],
1350
+ ) -> list[sequencetools.OutputSequence]:
1351
+ return [subseq for subseq in subseqs if not subseq.NDIM]
1352
+
1353
+ def numericalparameters(self, lines: PyxPxdLines) -> None:
1354
+ """Numeric parameter declaration lines."""
1355
+ if isinstance(self.model, modeltools.SolverModel):
1356
+ pyx, pxd, both = lines.pyx.add, lines.pxd.add, lines.add
1357
+ both(0, "@cython.final")
1358
+ both(0, "cdef class NumConsts:")
1359
+ pyx(1, "pass")
1360
+ for name in ("nmb_methods", "nmb_stages"):
1361
+ pxd(1, f"cdef public {TYPE2STR[int]} {name}")
1362
+ for name in ("dt_increase", "dt_decrease"):
1363
+ pxd(1, f"cdef public {TYPE2STR[float]} {name}")
1364
+ pxd(1, "cdef public configutils.Config pub")
1365
+ pxd(1, "cdef public double[:, :, :] a_coefs")
1366
+
1367
+ both(0, "@cython.final")
1368
+ both(0, "cdef class NumVars:")
1369
+ pyx(1, "pass")
1370
+ pxd(1, "cdef public bint use_relerror")
1371
+ for name in ("nmb_calls", "idx_method", "idx_stage"):
1372
+ pxd(1, f"cdef public {TYPE2STR[int]} {name}")
1373
+ for name in (
1374
+ "t0",
1375
+ "t1",
1376
+ "dt",
1377
+ "dt_est",
1378
+ "abserror",
1379
+ "relerror",
1380
+ "last_abserror",
1381
+ "last_relerror",
1382
+ "extrapolated_abserror",
1383
+ "extrapolated_relerror",
1384
+ ):
1385
+ pxd(1, f"cdef public {TYPE2STR[float]} {name}")
1386
+ pxd(1, f"cdef public {TYPE2STR[bool]} f0_ready")
1387
+
1388
+ def submodels(self, lines: PyxPxdLines) -> None:
1389
+ """Submodel declaration lines."""
1390
+ for submodel in self.model.SUBMODELS:
1391
+ pyx, pxd, both = lines.pyx.add, lines.pxd.add, lines.add
1392
+ both(0, "@cython.final")
1393
+ cls = submodel.CYTHONBASECLASS
1394
+ both(
1395
+ 0,
1396
+ f"cdef class {submodel.__name__}("
1397
+ f"{cls.__module__.split('.')[-1]}.{cls.__name__}):",
1398
+ )
1399
+ pxd(1, "cdef public Model model")
1400
+ pyx(1, "def __init__(self, Model model):")
1401
+ pyx(2, "self.model = model")
1402
+ for idx, method in enumerate(submodel.METHODS):
1403
+ both(1, f"cpdef double apply_method{idx}(self, double x) {_nogil}:")
1404
+ pyx(2, f"return self.model.{method.__name__.lower()}(x)")
1405
+
1406
+ def modeldeclarations(self, lines: PyxPxdLines) -> None:
1407
+ """The attribute declarations of the model class."""
1408
+ # pylint: disable=too-many-branches
1409
+ submodeltypes_old = getattr(self.model, "SUBMODELS", ())
1410
+ submodelnames_new = [
1411
+ n.split(".")[-1]
1412
+ for n in self.model.find_submodels(
1413
+ include_subsubmodels=False,
1414
+ include_sidemodels=True,
1415
+ include_optional=True,
1416
+ aggregate_vectors=True,
1417
+ repeat_sharedmodels=True,
1418
+ )
1419
+ ]
1420
+ pyx, pxd, both = lines.pyx.add, lines.pxd.add, lines.add
1421
+ both(0, "@cython.final")
1422
+ follows_interface = any(
1423
+ base
1424
+ for base in inspect.getmro(type(self.model))
1425
+ if issubclass(base, modeltools.SubmodelInterface)
1426
+ and base.__module__.startswith("hydpy.interfaces.")
1427
+ )
1428
+ if follows_interface:
1429
+ both(0, "cdef class Model(masterinterface.MasterInterface):")
1430
+ else:
1431
+ both(0, "cdef class Model:")
1432
+ for cls in inspect.getmro(type(self.model)):
1433
+ for name, member in vars(cls).items():
1434
+ if isinstance(member, modeltools.IndexProperty):
1435
+ if (name != "idx_sim") or not follows_interface:
1436
+ pxd(1, f"cdef public {INT} {name}")
1437
+ if isinstance(self.model, modeltools.SubstepModel):
1438
+ pxd(1, f"cdef public {TYPE2STR[float]} timeleft")
1439
+ if self.model.parameters:
1440
+ pxd(1, "cdef public Parameters parameters")
1441
+ pxd(1, "cdef public Sequences sequences")
1442
+ for name in submodelnames_new:
1443
+ if name.endswith("_*"):
1444
+ name = name[:-2]
1445
+ pxd(1, f"cdef public interfaceutils.SubmodelsProperty {name}")
1446
+ else:
1447
+ pxd(1, f"cdef public masterinterface.MasterInterface {name}")
1448
+ pxd(1, f"cdef public {TYPE2STR[bool]} {name}_is_mainmodel")
1449
+ pxd(1, f"cdef public {TYPE2STR[int]} {name}_typeid")
1450
+ for submodel in submodeltypes_old:
1451
+ pxd(1, f"cdef public {submodel.__name__} {submodel.name}")
1452
+ if hasattr(self.model, "numconsts"):
1453
+ pxd(1, "cdef public NumConsts numconsts")
1454
+ if hasattr(self.model, "numvars"):
1455
+ pxd(1, "cdef public NumVars numvars")
1456
+ if submodeltypes_old or submodelnames_new:
1457
+ pyx(1, "def __init__(self):")
1458
+ pyx(2, "super().__init__()")
1459
+ for name in submodelnames_new:
1460
+ if name.endswith("_*"):
1461
+ name = name[:-2]
1462
+ pyx(2, f"self.{name} = interfaceutils.SubmodelsProperty()")
1463
+ else:
1464
+ pyx(2, f"self.{name} = None")
1465
+ pyx(2, f"self.{name}_is_mainmodel = False")
1466
+ for submodel in submodeltypes_old:
1467
+ pyx(2, f"self.{submodel.name} = {submodel.__name__}(self)")
1468
+ baseinterface = "masterinterface.MasterInterface | None"
1469
+ for name in submodelnames_new:
1470
+ if not name.endswith("_*"):
1471
+ pyx(1, f"def get_{name}(self) -> {baseinterface}:")
1472
+ pyx(2, f"return self.{name}")
1473
+ pyx(1, f"def set_{name}(self, {name}: {baseinterface}) -> None:")
1474
+ pyx(2, f"self.{name} = {name}")
1475
+ for method in self.model.REUSABLE_METHODS:
1476
+ pxd(1, f"cdef bint {method.REUSEMARKER}")
1477
+
1478
+ def modelstandardfunctions(self, lines: PyxPxdLines) -> None:
1479
+ """The standard functions of the model class."""
1480
+ self.simulate(lines)
1481
+ self.reset_reuseflags(lines)
1482
+ self.iofunctions(lines)
1483
+ self.new2old(lines)
1484
+ if isinstance(self.model, modeltools.RunModel):
1485
+ self.run(lines, self.model)
1486
+ self.update_inlets(lines)
1487
+ self.update_outlets(lines)
1488
+ self.update_receivers(lines)
1489
+ self.update_senders(lines)
1490
+ self.update_outputs_model(lines)
1491
+
1492
+ def modelnumericfunctions(self, lines: PyxPxdLines) -> None:
1493
+ """Numerical integration functions of the model class."""
1494
+ if isinstance(self.model, modeltools.SolverModel):
1495
+ self.solve(lines)
1496
+ self.calculate_single_terms(lines, self.model)
1497
+ self.calculate_full_terms(lines, self.model)
1498
+ self.get_point_states(lines)
1499
+ self.set_point_states(lines)
1500
+ self.set_result_states(lines)
1501
+ self.get_sum_fluxes(lines)
1502
+ self.set_point_fluxes(lines)
1503
+ self.set_result_fluxes(lines)
1504
+ self.integrate_fluxes(lines)
1505
+ self.reset_sum_fluxes(lines)
1506
+ self.addup_fluxes(lines)
1507
+ self.calculate_error(lines)
1508
+ self.extrapolate_error(lines)
1509
+
1510
+ def simulate(self, lines: PyxPxdLines) -> None:
1511
+ """Simulation statements."""
1512
+ print(" . simulate")
1513
+ pyx, both = lines.pyx.add, lines.add
1514
+ both(1, f"cpdef inline void simulate(self, {INT} idx) {_nogil}:")
1515
+ pyx(2, "self.idx_sim = idx")
1516
+ if self.model.REUSABLE_METHODS or self.model.find_submodels(
1517
+ include_optional=True, include_subsubmodels=False, repeat_sharedmodels=True
1518
+ ):
1519
+ pyx(2, "self.reset_reuseflags()")
1520
+ seqs = self.model.sequences
1521
+ if seqs.inputs or self.model.SUBMODELINTERFACES:
1522
+ pyx(2, "self.load_data(idx)")
1523
+ if self.model.INLET_METHODS:
1524
+ pyx(2, "self.update_inlets()")
1525
+ if isinstance(self.model, modeltools.SolverModel):
1526
+ pyx(2, "self.solve()")
1527
+ else:
1528
+ pyx(2, "self.run()")
1529
+ if seqs.states:
1530
+ pyx(2, "self.new2old()")
1531
+ if self.model.OUTLET_METHODS:
1532
+ pyx(2, "self.update_outlets()")
1533
+ if seqs.factors or seqs.fluxes or seqs.states:
1534
+ pyx(2, "self.update_outputs()")
1535
+
1536
+ def _call_submodel_method(self, lines: PyxPxdLines, methodcall: str) -> None:
1537
+ name2submodel = self.model.find_submodels(
1538
+ include_subsubmodels=False,
1539
+ include_optional=True,
1540
+ aggregate_vectors=True,
1541
+ repeat_sharedmodels=True,
1542
+ )
1543
+ pyx = lines.pyx.add
1544
+ if any(name.endswith("_*") for name in name2submodel):
1545
+ pyx(2, f"cdef {INT} i_submodel")
1546
+ for fullname in name2submodel:
1547
+ name = fullname.rpartition(".")[2]
1548
+ if name.endswith("_*"):
1549
+ name = name[:-2]
1550
+ pyx(2, f"for i_submodel in range(self.{name}.number):")
1551
+ pyx(3, f"if self.{name}.typeids[i_submodel] > 0:")
1552
+ pyx(
1553
+ 4,
1554
+ f"(<masterinterface.MasterInterface>"
1555
+ f"self.{name}.submodels[i_submodel]).{methodcall}",
1556
+ )
1557
+ else:
1558
+ pyx(
1559
+ 2, f"if (self.{name} is not None) and not self.{name}_is_mainmodel:"
1560
+ )
1561
+ pyx(3, f"self.{name}.{methodcall}")
1562
+
1563
+ def iofunctions(self, lines: PyxPxdLines) -> None:
1564
+ """Input/output functions of the model class.
1565
+
1566
+ The result of property |PyxWriter.iofunctions| depends on the availability of
1567
+ different types of sequences. So far, the models implemented in *HydPy* do not
1568
+ reflect all possible combinations, which is why we modify the |hland_96|
1569
+ application model in the following examples:
1570
+
1571
+ >>> from hydpy.models.hland_96 import cythonizer
1572
+ >>> pyxwriter = cythonizer.pyxwriter
1573
+ >>> from hydpy.cythons.modelutils import PyxPxdLines
1574
+ >>> lines = PyxPxdLines()
1575
+ >>> pyxwriter.iofunctions(lines)
1576
+ . load_data
1577
+ . save_data
1578
+ >>> lines.pyx # doctest: +ELLIPSIS
1579
+ cpdef void load_data(self, ...int... idx) noexcept nogil:
1580
+ self.idx_sim = idx
1581
+ self.sequences.inputs.load_data(idx)
1582
+ if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel:
1583
+ self.aetmodel.load_data(idx)
1584
+ if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel:
1585
+ self.rconcmodel.load_data(idx)
1586
+ cpdef void save_data(self, ...int... idx) noexcept nogil:
1587
+ self.idx_sim = idx
1588
+ self.sequences.inputs.save_data(idx)
1589
+ self.sequences.factors.save_data(idx)
1590
+ self.sequences.fluxes.save_data(idx)
1591
+ self.sequences.states.save_data(idx)
1592
+ if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel:
1593
+ self.aetmodel.save_data(idx)
1594
+ if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel:
1595
+ self.rconcmodel.save_data(idx)
1596
+ <BLANKLINE>
1597
+
1598
+ >>> pyxwriter.model.sequences.factors = None
1599
+ >>> pyxwriter.model.sequences.fluxes = None
1600
+ >>> pyxwriter.model.sequences.states = None
1601
+ >>> lines.pyx.clear()
1602
+ >>> pyxwriter.iofunctions(lines)
1603
+ . load_data
1604
+ . save_data
1605
+ >>> lines.pyx # doctest: +ELLIPSIS
1606
+ cpdef void load_data(self, ...int... idx) noexcept nogil:
1607
+ self.idx_sim = idx
1608
+ self.sequences.inputs.load_data(idx)
1609
+ if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel:
1610
+ self.aetmodel.load_data(idx)
1611
+ if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel:
1612
+ self.rconcmodel.load_data(idx)
1613
+ cpdef void save_data(self, ...int... idx) noexcept nogil:
1614
+ self.idx_sim = idx
1615
+ self.sequences.inputs.save_data(idx)
1616
+ if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel:
1617
+ self.aetmodel.save_data(idx)
1618
+ if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel:
1619
+ self.rconcmodel.save_data(idx)
1620
+ <BLANKLINE>
1621
+
1622
+ >>> pyxwriter.model.sequences.inputs = None
1623
+ >>> lines.pyx.clear()
1624
+ >>> pyxwriter.iofunctions(lines)
1625
+ >>> lines.pyx # doctest: +ELLIPSIS
1626
+ <BLANKLINE>
1627
+ <BLANKLINE>
1628
+ """
1629
+ seqs = self.model.sequences
1630
+ if not (seqs.inputs or seqs.factors or seqs.fluxes or seqs.states):
1631
+ return
1632
+ pyx, both = lines.pyx.add, lines.add
1633
+ for func in ("load_data", "save_data"):
1634
+ if (func == "load_data") and not (
1635
+ seqs.inputs or self.model.SUBMODELINTERFACES
1636
+ ):
1637
+ continue
1638
+ print(f" . {func}")
1639
+ nogil = func in ("load_data", "save_data")
1640
+ both(1, get_methodheader(func, nogil=nogil, idxarg=True, inline=False))
1641
+ pyx(2, "self.idx_sim = idx")
1642
+ for subseqs in seqs:
1643
+ if func == "load_data":
1644
+ applyfuncs: tuple[str, ...] = ("inputs",)
1645
+ else:
1646
+ applyfuncs = ("inputs", "factors", "fluxes", "states")
1647
+ if subseqs.name in applyfuncs:
1648
+ pyx(2, f"self.sequences.{subseqs.name}." f"{func}(idx)")
1649
+ self._call_submodel_method(lines, f"{func}(idx)")
1650
+
1651
+ def new2old(self, lines: PyxPxdLines) -> None:
1652
+ """Old states to new states statements."""
1653
+ name2submodel = self.model.find_submodels(
1654
+ include_subsubmodels=False,
1655
+ include_optional=True,
1656
+ aggregate_vectors=True,
1657
+ repeat_sharedmodels=True,
1658
+ )
1659
+ pyx, both = lines.pyx.add, lines.add
1660
+ if self.model.sequences.states or name2submodel:
1661
+ print(" . new2old")
1662
+ both(1, get_methodheader("new2old", nogil=True, inline=False))
1663
+ if self.model.sequences.states:
1664
+ self._add_cdef_jdxs(lines, self.model.sequences.states)
1665
+ for seq in self.model.sequences.states:
1666
+ if seq.NDIM == 0:
1667
+ pyx(
1668
+ 2,
1669
+ f"self.sequences.old_states.{seq.name} = "
1670
+ f"self.sequences.new_states.{seq.name}",
1671
+ )
1672
+ else:
1673
+ indexing = ""
1674
+ for idx in range(seq.NDIM):
1675
+ pyx(
1676
+ 2 + idx,
1677
+ f"for jdx{idx} in range(self.sequences.states."
1678
+ f"_{seq.name}_length_{idx}):",
1679
+ )
1680
+ indexing += f"jdx{idx},"
1681
+ indexing = indexing[:-1]
1682
+ pyx(
1683
+ 2 + seq.NDIM,
1684
+ f"self.sequences.old_states.{seq.name}[{indexing}] = "
1685
+ f"self.sequences.new_states.{seq.name}[{indexing}]",
1686
+ )
1687
+ self._call_submodel_method(lines, "new2old()")
1688
+
1689
+ def _call_methods(
1690
+ self,
1691
+ lines: PyxPxdLines,
1692
+ name: str,
1693
+ methods: tuple[type[modeltools.Method], ...],
1694
+ idx_as_arg: bool = False,
1695
+ ) -> None:
1696
+ if hasattr(self.model, name):
1697
+ pyx, both = lines.pyx.add, lines.add
1698
+ both(1, get_methodheader(name, nogil=True, idxarg=idx_as_arg))
1699
+ if idx_as_arg:
1700
+ pyx(2, "self.idx_sim = idx")
1701
+ anything = False
1702
+ for method in methods:
1703
+ pyx(2, f"self.{method.__name__.lower()}()")
1704
+ anything = True
1705
+ if not anything:
1706
+ pyx(2, "pass")
1707
+
1708
+ def _call_runmethods_segmentwise(
1709
+ self, lines: PyxPxdLines, methods: tuple[type[modeltools.Method], ...]
1710
+ ) -> None:
1711
+ if hasattr(self.model, "run"):
1712
+ pyx, both = lines.pyx.add, lines.add
1713
+ both(1, get_methodheader("run", nogil=True, idxarg=False))
1714
+ pyx(2, f"cdef {TYPE2STR[int]} idx_segment, idx_run")
1715
+ pyx(2, "for idx_segment in range(self.parameters.control.nmbsegments):")
1716
+ pyx(3, "self.idx_segment = idx_segment")
1717
+ pyx(3, "for idx_run in range(self.parameters.solver.nmbruns):")
1718
+ pyx(4, "self.idx_run = idx_run")
1719
+ for method in methods:
1720
+ pyx(4, f"self.{method.__name__.lower()}()")
1721
+
1722
+ def update_receivers(self, lines: PyxPxdLines) -> None:
1723
+ """Lines of the model method with the same name."""
1724
+ self._call_methods(lines, "update_receivers", self.model.RECEIVER_METHODS, True)
1725
+
1726
+ def update_inlets(self, lines: PyxPxdLines) -> None:
1727
+ """Lines of the model method with the same name."""
1728
+ self._call_methods(lines, "update_inlets", self.model.INLET_METHODS)
1729
+
1730
+ def run(self, lines: PyxPxdLines, model: modeltools.RunModel) -> None:
1731
+ """Return the lines of the model method with the same name."""
1732
+ if isinstance(model, modeltools.SegmentModel):
1733
+ self._call_runmethods_segmentwise(lines, model.RUN_METHODS)
1734
+ else:
1735
+ nmb = len(lines.pyx)
1736
+ self._call_methods(lines, "run", model.RUN_METHODS)
1737
+ if isinstance(model, modeltools.SubstepModel):
1738
+ pyx = Lines()
1739
+ pyx.extend(lines.pyx[: nmb + 1])
1740
+ add = pyx.add
1741
+ add(2, "self.timeleft = self.parameters.derived.seconds")
1742
+ add(2, "while True:")
1743
+ for line in lines.pyx[nmb + 1 :]:
1744
+ add(1, line)
1745
+ add(3, "if self.timeleft <= 0.0:")
1746
+ add(4, "break")
1747
+ add(3, "self.new2old()")
1748
+ lines.pyx = pyx
1749
+
1750
+ def update_outlets(self, lines: PyxPxdLines) -> None:
1751
+ """Lines of the model method with the same name."""
1752
+ self._call_methods(lines, "update_outlets", self.model.OUTLET_METHODS)
1753
+
1754
+ def update_senders(self, lines: PyxPxdLines) -> None:
1755
+ """Lines of the model method with the same name."""
1756
+ self._call_methods(lines, "update_senders", self.model.SENDER_METHODS, True)
1757
+
1758
+ def update_outputs_model(self, lines: PyxPxdLines) -> None:
1759
+ """Lines of the model method with the same name (except the `_model` suffix)."""
1760
+ pyx, both = lines.pyx.add, lines.add
1761
+ both(1, get_methodheader("update_outputs", nogil=True, idxarg=False))
1762
+ factors = self._filter_outputsequences(self.model.sequences.factors)
1763
+ fluxes = self._filter_outputsequences(self.model.sequences.fluxes)
1764
+ states = self._filter_outputsequences(self.model.sequences.states)
1765
+ if factors:
1766
+ pyx(2, "self.sequences.factors.update_outputs()")
1767
+ if fluxes:
1768
+ pyx(2, "self.sequences.fluxes.update_outputs()")
1769
+ if states:
1770
+ pyx(2, "self.sequences.states.update_outputs()")
1771
+ if not (factors or fluxes or states):
1772
+ pyx(2, "pass")
1773
+
1774
+ def update_outputs(
1775
+ self, lines: PyxPxdLines, subseqs: sequencetools.OutputSequences[Any]
1776
+ ) -> None:
1777
+ """Lines of the subsequences method with the same name."""
1778
+ pyx, both = lines.pyx.add, lines.add
1779
+ both(1, get_methodheader("update_outputs", nogil=True, idxarg=False))
1780
+ subseqs_ = self._filter_outputsequences(subseqs)
1781
+ if subseqs_:
1782
+ for seq in subseqs_:
1783
+ name = seq.name
1784
+ pyx(2, f"if self._{name}_outputflag:")
1785
+ pyx(3, f"self._{name}_outputpointer[0] = self.{name}")
1786
+ else:
1787
+ pyx(2, "pass")
1788
+
1789
+ def calculate_single_terms(
1790
+ self, lines: PyxPxdLines, model: modeltools.SolverModel
1791
+ ) -> None:
1792
+ """Return the lines of the model method with the same name."""
1793
+ nmb = len(lines.pyx)
1794
+ self._call_methods(lines, "calculate_single_terms", model.PART_ODE_METHODS)
1795
+ if len(lines.pyx) > nmb:
1796
+ lines.pyx.insert(
1797
+ nmb + 1, (" self.numvars.nmb_calls = self.numvars.nmb_calls + 1")
1798
+ )
1799
+
1800
+ def calculate_full_terms(
1801
+ self, lines: PyxPxdLines, model: modeltools.SolverModel
1802
+ ) -> None:
1803
+ """Return the lines of the model method with the same name."""
1804
+ self._call_methods(lines, "calculate_full_terms", model.FULL_ODE_METHODS)
1805
+
1806
+ @property
1807
+ def name2function_method(self) -> dict[str, types.MethodType]:
1808
+ """Functions defined by |Method| subclasses."""
1809
+ name2function = {}
1810
+ for name, member in vars(self.model).items():
1811
+ if (getattr(member, "__name__", None) == "call_reusablemethod") or getattr(
1812
+ getattr(member, "__func__", None), "__HYDPY_METHOD__", False
1813
+ ):
1814
+ name2function[name] = member
1815
+ return name2function
1816
+
1817
+ @property
1818
+ def automethod2name(self) -> dict[str, tuple[type[modeltools.Method], ...]]:
1819
+ """Submethods selected by |AutoMethod| and |SetAutoMethod| subclasses."""
1820
+ automethod2name: dict[str, tuple[type[modeltools.Method], ...]] = {}
1821
+ for name, member in vars(self.model).items():
1822
+ if (
1823
+ isinstance(member, types.MethodType)
1824
+ and isinstance(call := member.__func__, types.MethodType)
1825
+ and inspect.isclass(method := call.__self__)
1826
+ and issubclass(
1827
+ automethod := method,
1828
+ (modeltools.AutoMethod, modeltools.SetAutoMethod),
1829
+ )
1830
+ ):
1831
+ automethod2name[name] = automethod.SUBMETHODS
1832
+ return automethod2name
1833
+
1834
+ @property
1835
+ def interfacemethods(self) -> set[str]:
1836
+ """The full and abbreviated names of the selected model's interface methods."""
1837
+ if hasattr(self.model, "INTERFACE_METHODS"):
1838
+ interfaces = {m.__name__.lower() for m in self.model.INTERFACE_METHODS}
1839
+ interfaces.update({i.rpartition("_")[0] for i in interfaces})
1840
+ return interfaces
1841
+ return set()
1842
+
1843
+ def modeluserfunctions(self, lines: PyxPxdLines) -> None:
1844
+ """Model-specific functions."""
1845
+ for name, func in self.name2function_method.items():
1846
+ print(f" . {name}")
1847
+ inline = name not in self.interfacemethods
1848
+ funcconverter = FuncConverter(
1849
+ model=self.model, funcname=name, func=func, inline=inline
1850
+ )
1851
+ pyxlines = tuple(f" {line}" for line in funcconverter.pyxlines)
1852
+ lines.pyx.extend(pyxlines)
1853
+ lines.pxd.append(pyxlines[0][:-1])
1854
+ for name, submethods in self.automethod2name.items():
1855
+ print(f" . {name}")
1856
+ self.automethod(lines, name=name, submethods=submethods)
1857
+
1858
+ def callbackfeatures(self, lines: PyxPxdLines) -> None:
1859
+ """Features to let users define callback functions."""
1860
+
1861
+ pyx, pxd = lines.pyx.add, lines.pxd.add
1862
+
1863
+ pxd(0, f"ctypedef void (*CallbackType) (Model) {_nogil}")
1864
+ pyx(0, "")
1865
+ pxd(0, "cdef class CallbackWrapper:")
1866
+ pxd(1, "cdef CallbackType callback")
1867
+ pyx(0, "")
1868
+ pyx(0, f"cdef void do_nothing(Model model) {_nogil}:")
1869
+ pyx(1, "pass")
1870
+ pyx(0, "")
1871
+ pyx(0, "cpdef get_wrapper():")
1872
+ pyx(1, "cdef CallbackWrapper wrapper = CallbackWrapper()")
1873
+ pyx(1, "wrapper.callback = do_nothing")
1874
+ pyx(1, "return wrapper")
1875
+ pyx(0, "")
1876
+
1877
+ def automethod(
1878
+ self,
1879
+ lines: PyxPxdLines,
1880
+ name: str,
1881
+ submethods: tuple[type[modeltools.Method], ...],
1882
+ ) -> None:
1883
+ """Lines of a method defined by a |AutoMethod| or |SetAutoMethod| subclass."""
1884
+ pyx, both = lines.pyx.add, lines.add
1885
+ inline = name not in self.interfacemethods
1886
+
1887
+ submethod2arg, subsignatures = {}, []
1888
+ for submethod in submethods:
1889
+ if len(args := inspect.getargs(submethod.__call__.__code__).args) == 2:
1890
+ submethod2arg[submethod] = args[1]
1891
+ type_ = get_type_hints(submethod.__call__)[args[1]]
1892
+ subsignatures.append(f"{TYPE2STR[type_]} {args[1]}")
1893
+ else:
1894
+ assert len(args) == 1
1895
+
1896
+ header = get_methodheader(methodname=name, nogil=True, inline=inline)
1897
+ if subsignatures:
1898
+ subheaders = list(header.partition(")"))
1899
+ subheaders.insert(1, ", ".join([""] + subsignatures))
1900
+ header = "".join(subheaders)
1901
+ both(1, header)
1902
+
1903
+ for submethod in submethods:
1904
+ arg = submethod2arg.get(submethod, "")
1905
+ pyx(2, f"self.{submethod.__name__.lower()}({arg})")
1906
+
1907
+ def solve(self, lines: PyxPxdLines) -> None:
1908
+ """Lines of the model method with the same name."""
1909
+ if solve := getattr(self.model, "solve", None):
1910
+ print(" . solve")
1911
+ funcconverter = FuncConverter(self.model, "solve", solve)
1912
+ pyxlines = tuple(f" {line}" for line in funcconverter.pyxlines)
1913
+ lines.pyx.extend(pyxlines)
1914
+ lines.pxd.append(pyxlines[0][:-1])
1915
+
1916
+ @classmethod
1917
+ def _assign_seqvalues(
1918
+ cls,
1919
+ *,
1920
+ subseqs: Iterable[sequencetools.IOSequence],
1921
+ subseqs_name: str,
1922
+ target: str,
1923
+ index: str | None,
1924
+ load: bool,
1925
+ ) -> Iterator[str]:
1926
+ subseqs = list(subseqs)
1927
+ from1 = f"self.sequences.{subseqs_name}.%s"
1928
+ to1 = f"self.sequences.{subseqs_name}._%s_{target}"
1929
+ if index is not None:
1930
+ to1 += f"[self.numvars.{index}]"
1931
+ if load:
1932
+ from1, to1 = to1, from1
1933
+ yield from cls._declare_idxs(subseqs)
1934
+ for seq in subseqs:
1935
+ from2 = from1 % seq.name
1936
+ to2 = to1 % seq.name
1937
+ if seq.NDIM == 0:
1938
+ yield f"{to2} = {from2}"
1939
+ elif seq.NDIM == 1:
1940
+ yield (
1941
+ f"for idx0 in range(self.sequences."
1942
+ f"{subseqs_name}._{seq.name}_length):"
1943
+ )
1944
+ yield f" {to2}[idx0] = {from2}[idx0]"
1945
+ elif seq.NDIM == 2:
1946
+ yield (
1947
+ f"for idx0 in range(self.sequences."
1948
+ f"{subseqs_name}._{seq.name}_length0):"
1949
+ )
1950
+ yield (
1951
+ f" for idx1 in range(self.sequences."
1952
+ f"{subseqs_name}._{seq.name}_length1):"
1953
+ )
1954
+ yield f" {to2}[idx0, idx1] = {from2}[idx0, idx1]"
1955
+ else:
1956
+ raise NotImplementedError(
1957
+ f"NDIM of sequence `{seq.name}` is higher than expected."
1958
+ )
1959
+
1960
+ @staticmethod
1961
+ def _declare_idxs(subseqs: Iterable[sequencetools.IOSequence]) -> Iterator[str]:
1962
+ maxdim = 0
1963
+ for seq in subseqs:
1964
+ maxdim = max(maxdim, seq.NDIM)
1965
+ if maxdim == 1:
1966
+ yield f"cdef {INT} idx0"
1967
+ elif maxdim == 2:
1968
+ yield f"cdef {INT} idx0, idx1"
1969
+
1970
+ @decorate_method
1971
+ def get_point_states(self) -> Iterator[str]:
1972
+ """Get point statements for state sequences."""
1973
+ return self._assign_seqvalues(
1974
+ subseqs=self.model.sequences.states,
1975
+ subseqs_name="states",
1976
+ target="points",
1977
+ index="idx_stage",
1978
+ load=True,
1979
+ )
1980
+
1981
+ @decorate_method
1982
+ def set_point_states(self) -> Iterator[str]:
1983
+ """Set point statements for state sequences."""
1984
+ return self._assign_seqvalues(
1985
+ subseqs=self.model.sequences.states,
1986
+ subseqs_name="states",
1987
+ target="points",
1988
+ index="idx_stage",
1989
+ load=False,
1990
+ )
1991
+
1992
+ @decorate_method
1993
+ def set_result_states(self) -> Iterator[str]:
1994
+ """Get results statements for state sequences."""
1995
+ return self._assign_seqvalues(
1996
+ subseqs=self.model.sequences.states,
1997
+ subseqs_name="states",
1998
+ target="results",
1999
+ index="idx_method",
2000
+ load=False,
2001
+ )
2002
+
2003
+ @decorate_method
2004
+ def get_sum_fluxes(self) -> Iterator[str]:
2005
+ """Get sum statements for flux sequences."""
2006
+ return self._assign_seqvalues(
2007
+ subseqs=self.model.sequences.fluxes.numericsequences,
2008
+ subseqs_name="fluxes",
2009
+ target="sum",
2010
+ index=None,
2011
+ load=True,
2012
+ )
2013
+
2014
+ @decorate_method
2015
+ def set_point_fluxes(self) -> Iterator[str]:
2016
+ """Set point statements for flux sequences."""
2017
+ return self._assign_seqvalues(
2018
+ subseqs=self.model.sequences.fluxes.numericsequences,
2019
+ subseqs_name="fluxes",
2020
+ target="points",
2021
+ index="idx_stage",
2022
+ load=False,
2023
+ )
2024
+
2025
+ @decorate_method
2026
+ def set_result_fluxes(self) -> Iterator[str]:
2027
+ """Set result statements for flux sequences."""
2028
+ return self._assign_seqvalues(
2029
+ subseqs=self.model.sequences.fluxes.numericsequences,
2030
+ subseqs_name="fluxes",
2031
+ target="results",
2032
+ index="idx_method",
2033
+ load=False,
2034
+ )
2035
+
2036
+ @decorate_method
2037
+ def integrate_fluxes(self) -> Iterator[str]:
2038
+ """Integrate statements for flux sequences."""
2039
+ max_ndim = -1
2040
+ for seq in self.model.sequences.fluxes.numericsequences:
2041
+ max_ndim = max(max_ndim, seq.NDIM)
2042
+ if max_ndim == 0:
2043
+ yield f"cdef {INT} jdx"
2044
+ elif max_ndim == 1:
2045
+ yield f"cdef {INT} jdx, idx0"
2046
+ elif max_ndim == 2:
2047
+ yield f"cdef {INT} jdx, idx0, idx1"
2048
+ for seq in self.model.sequences.fluxes.numericsequences:
2049
+ to_ = f"self.sequences.fluxes.{seq.name}"
2050
+ from_ = f"self.sequences.fluxes._{seq.name}_points"
2051
+ coefs = (
2052
+ "self.numvars.dt * self.numconsts.a_coefs"
2053
+ "[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]"
2054
+ )
2055
+ if seq.NDIM == 0:
2056
+ yield f"{to_} = 0."
2057
+ yield "for jdx in range(self.numvars.idx_method):"
2058
+ yield f" {to_} = {to_} +{coefs}*{from_}[jdx]"
2059
+ elif seq.NDIM == 1:
2060
+ yield (
2061
+ f"for idx0 in " f"range(self.sequences.fluxes._{seq.name}_length):"
2062
+ )
2063
+ yield f" {to_}[idx0] = 0."
2064
+ yield " for jdx in range(self.numvars.idx_method):"
2065
+ yield (
2066
+ f" {to_}[idx0] = "
2067
+ f"{to_}[idx0] + {coefs}*{from_}[jdx, idx0]"
2068
+ )
2069
+ elif seq.NDIM == 2:
2070
+ yield (
2071
+ f"for idx0 in " f"range(self.sequences.fluxes._{seq.name}_length0):"
2072
+ )
2073
+ yield (
2074
+ f" for idx1 in range("
2075
+ f"self.sequences.fluxes._{seq.name}_length1):"
2076
+ )
2077
+ yield f" {to_}[idx0, idx1] = 0."
2078
+ yield " for jdx in range(self.numvars.idx_method):"
2079
+ yield (
2080
+ f" {to_}[idx0, idx1] = "
2081
+ f"{to_}[idx0, idx1] + {coefs}*{from_}[jdx, idx0, idx1]"
2082
+ )
2083
+ else:
2084
+ raise NotImplementedError(
2085
+ f"NDIM of sequence `{seq.name}` is higher than expected."
2086
+ )
2087
+
2088
+ @decorate_method
2089
+ def reset_sum_fluxes(self) -> Iterator[str]:
2090
+ """Reset sum statements for flux sequences."""
2091
+ subseqs = list(self.model.sequences.fluxes.numericsequences)
2092
+ yield from PyxWriter._declare_idxs(subseqs)
2093
+ for seq in subseqs:
2094
+ to_ = f"self.sequences.fluxes._{seq.name}_sum"
2095
+ if seq.NDIM == 0:
2096
+ yield f"{to_} = 0."
2097
+ elif seq.NDIM == 1:
2098
+ yield (
2099
+ f"for idx0 in " f"range(self.sequences.fluxes._{seq.name}_length):"
2100
+ )
2101
+ yield f" {to_}[idx0] = 0."
2102
+ elif seq.NDIM == 2:
2103
+ yield (
2104
+ f"for idx0 in " f"range(self.sequences.fluxes._{seq.name}_length0):"
2105
+ )
2106
+ yield (
2107
+ f" for idx1 in "
2108
+ f"range(self.sequences.fluxes._{seq.name}_length1):"
2109
+ )
2110
+ yield f" {to_}[idx0, idx1] = 0."
2111
+ else:
2112
+ raise NotImplementedError(
2113
+ f"NDIM of sequence `{seq.name}` is higher than expected."
2114
+ )
2115
+
2116
+ @decorate_method
2117
+ def addup_fluxes(self) -> Iterator[str]:
2118
+ """Add up statements for flux sequences."""
2119
+ subseqs = list(self.model.sequences.fluxes.numericsequences)
2120
+ yield from PyxWriter._declare_idxs(subseqs)
2121
+ for seq in subseqs:
2122
+ to_ = f"self.sequences.fluxes._{seq.name}_sum"
2123
+ from_ = f"self.sequences.fluxes.{seq.name}"
2124
+ if seq.NDIM == 0:
2125
+ yield f"{to_} = {to_} + {from_}"
2126
+ elif seq.NDIM == 1:
2127
+ yield (
2128
+ f"for idx0 in " f"range(self.sequences.fluxes._{seq.name}_length):"
2129
+ )
2130
+ yield f" {to_}[idx0] = {to_}[idx0] + {from_}[idx0]"
2131
+ elif seq.NDIM == 2:
2132
+ yield (
2133
+ f"for idx0 in " f"range(self.sequences.fluxes._{seq.name}_length0):"
2134
+ )
2135
+ yield (
2136
+ f" for idx1 in "
2137
+ f"range(self.sequences.fluxes._{seq.name}_length1):"
2138
+ )
2139
+ yield (
2140
+ f" {to_}[idx0, idx1] = "
2141
+ f"{to_}[idx0, idx1] + {from_}[idx0, idx1]"
2142
+ )
2143
+ else:
2144
+ raise NotImplementedError(
2145
+ f"NDIM of sequence `{seq.name}` is higher than expected."
2146
+ )
2147
+
2148
+ @decorate_method
2149
+ def calculate_error(self) -> Iterator[str]:
2150
+ """Calculate error statements."""
2151
+ subseqs = list(self.model.sequences.fluxes.numericsequences)
2152
+ assert isinstance(self.model, modeltools.ELSModel)
2153
+ if self.model.SOLVERSEQUENCES:
2154
+ subseqs = [
2155
+ seq for seq in subseqs if isinstance(seq, self.model.SOLVERSEQUENCES)
2156
+ ]
2157
+ yield from self._declare_idxs(subseqs)
2158
+ userel = "self.numvars.use_relerror:"
2159
+ abserror = "self.numvars.abserror"
2160
+ relerror = "self.numvars.relerror"
2161
+ index = "self.numvars.idx_method"
2162
+ yield "cdef double abserror"
2163
+ yield f"{abserror} = 0."
2164
+ yield f"if {userel}"
2165
+ yield f" {relerror} = 0."
2166
+ yield "else:"
2167
+ yield f" {relerror} = inf"
2168
+ for seq in subseqs:
2169
+ results = f"self.sequences.fluxes._{seq.name}_results"
2170
+ if seq.NDIM == 0:
2171
+ yield f"abserror = fabs(" f"{results}[{index}]-{results}[{index}-1])"
2172
+ yield f"{abserror} = max({abserror}, abserror)"
2173
+ yield f"if {userel}"
2174
+ yield f" if {results}[{index}] == 0.:"
2175
+ yield f" {relerror} = inf"
2176
+ yield " else:"
2177
+ yield (
2178
+ f" {relerror} = max("
2179
+ f"{relerror}, fabs(abserror/{results}[{index}]))"
2180
+ )
2181
+ elif seq.NDIM == 1:
2182
+ yield (
2183
+ f"for idx0 in range(" f"self.sequences.fluxes._{seq.name}_length):"
2184
+ )
2185
+ yield (
2186
+ f" abserror = fabs("
2187
+ f"{results}[{index}, idx0]-{results}[{index}-1, idx0])"
2188
+ )
2189
+ yield f" {abserror} = max({abserror}, abserror)"
2190
+ yield f" if {userel}"
2191
+ yield f" if {results}[{index}, idx0] == 0.:"
2192
+ yield f" {relerror} = inf"
2193
+ yield " else:"
2194
+ yield (
2195
+ f" {relerror} = max("
2196
+ f"{relerror}, fabs(abserror/{results}[{index}, idx0]))"
2197
+ )
2198
+ elif seq.NDIM == 2:
2199
+ yield (
2200
+ f"for idx0 in range(" f"self.sequences.fluxes._{seq.name}_length0):"
2201
+ )
2202
+ yield (
2203
+ f" for idx1 in range("
2204
+ f"self.sequences.fluxes._{seq.name}_length1):"
2205
+ )
2206
+
2207
+ yield (
2208
+ f" abserror = fabs({results}[{index}, "
2209
+ f"idx0, idx1]-{results}[{index}-1, idx0, idx1])"
2210
+ )
2211
+ yield f" {abserror} = max({abserror}, abserror)"
2212
+ yield f" if {userel}"
2213
+ yield f" if {results}[{index}, idx0, idx1] == 0.:"
2214
+ yield f" {relerror} = inf"
2215
+ yield " else:"
2216
+ yield (
2217
+ f" {relerror} = max("
2218
+ f"{relerror}, "
2219
+ f"fabs(abserror/{results}[{index}, idx0, idx1]))"
2220
+ )
2221
+ else:
2222
+ raise NotImplementedError(
2223
+ f"NDIM of sequence `{seq.name}` is higher than expected."
2224
+ )
2225
+
2226
+ def extrapolate_error(self, lines: PyxPxdLines) -> None:
2227
+ """Extrapolate error statements."""
2228
+ extrapolate_error = getattr(self.model, "extrapolate_error", None)
2229
+ if extrapolate_error:
2230
+ print(" . extrapolate_error")
2231
+ funcconverter = FuncConverter(
2232
+ self.model, "extrapolate_error", extrapolate_error
2233
+ )
2234
+ pyxlines = tuple(f" {line}" for line in funcconverter.pyxlines)
2235
+ lines.pyx.extend(pyxlines)
2236
+ lines.pxd.append(pyxlines[0][:-1])
2237
+
2238
+ def write_stubfile(self) -> None:
2239
+ """Write a stub file for the actual base or application model.
2240
+
2241
+ At the moment, *HydPy* creates model objects quite dynamically. In many
2242
+ regards, this comes with lots of conveniences. However, there two critical
2243
+ drawbacks compared to more static approaches: some amount of additional
2244
+ initialisation time and, more important, much opaqueness for code inspection
2245
+ tools. In this context, we experiment with "stub files" at the moment. These
2246
+ could either contain typing information only or define statically predefined
2247
+ model classes. The following example uses method |PyxWriter.write_stubfile| to
2248
+ write a (far from perfect) prototype stub file for base model |hland|:
2249
+
2250
+ >>> from hydpy.models.hland import *
2251
+ >>> cythonizer.pyxwriter.write_stubfile()
2252
+
2253
+ This is the path to the written file:
2254
+
2255
+ >>> import os
2256
+ >>> import hydpy
2257
+ >>> filepath = os.path.join(hydpy.__path__[0], "hland.py")
2258
+ >>> os.path.exists(filepath)
2259
+ True
2260
+
2261
+ However, it's just an experimental prototype, so we better remove it:
2262
+
2263
+ >>> os.remove(filepath)
2264
+ >>> os.path.exists(filepath)
2265
+ False
2266
+ """
2267
+ hydpypath: str = hydpy.__path__[0]
2268
+ filepath = os.path.join(hydpypath, f"{self.model.name}.py")
2269
+ base = ".".join(self.model.__module__.split(".")[:3])
2270
+ with open(filepath, "w", encoding=config.ENCODING) as stubfile:
2271
+ stubfile.write(
2272
+ f"import hydpy\n"
2273
+ f"from {base} import *\n"
2274
+ f"from hydpy.core.parametertools import (\n"
2275
+ f" FastAccess,)\n"
2276
+ f"from hydpy.core.parametertools import (\n"
2277
+ f" Parameters, FastAccessParameter)\n"
2278
+ f"from hydpy.core.sequencetools import (\n"
2279
+ f" Sequences,)\n\n"
2280
+ )
2281
+ for subpars in self.model.parameters:
2282
+ classname = f"FastAccess{subpars.name.capitalize()}Parameters"
2283
+ stubfile.write(f"\n\nclass {classname}(FastAccessParameter):\n")
2284
+ for partype in subpars.CLASSES:
2285
+ stubfile.write(
2286
+ f" {partype.__name__.lower()}: "
2287
+ f"{partype.__module__}.{partype.__name__}\n"
2288
+ )
2289
+ for subpars in self.model.parameters:
2290
+ classname = f"{subpars.name.capitalize()}Parameters"
2291
+ stubfile.write(f"\n\nclass {classname}({classname}):\n")
2292
+ stubfile.write(f" fastaccess: FastAccess{classname}\n")
2293
+ for partype in subpars.CLASSES:
2294
+ stubfile.write(
2295
+ f" {partype.__name__.lower()}: "
2296
+ f"{partype.__module__}.{partype.__name__}\n"
2297
+ )
2298
+ stubfile.write("\n\nclass Parameters(Parameters):\n")
2299
+ for subpars in self.model.parameters:
2300
+ classname = f"{subpars.name.capitalize()}Parameters"
2301
+ stubfile.write(f" {subpars.name}: {classname}\n")
2302
+
2303
+ for subseqs in self.model.sequences:
2304
+ classname = f"FastAccess{type(subseqs).__name__}"
2305
+ stubfile.write(f"\n\nclass {classname}(FastAccess):\n")
2306
+ for seqtype in subseqs.CLASSES:
2307
+ stubfile.write(
2308
+ f" {seqtype.__name__.lower()}: "
2309
+ f"{seqtype.__module__}.{seqtype.__name__}\n"
2310
+ )
2311
+ for subseqs in self.model.sequences:
2312
+ classname = type(subseqs).__name__
2313
+ stubfile.write(f"\n\nclass {classname}({classname}):\n")
2314
+ stubfile.write(f" fastaccess: FastAccess{classname}\n")
2315
+ if classname == "StateSequences":
2316
+ stubfile.write(f" fastaccess_old: FastAccess{classname}\n")
2317
+ stubfile.write(f" fastaccess_new: FastAccess{classname}\n")
2318
+ for seqtype in subseqs.CLASSES:
2319
+ stubfile.write(
2320
+ f" {seqtype.__name__.lower()}: "
2321
+ f"{seqtype.__module__}.{seqtype.__name__}\n"
2322
+ )
2323
+ stubfile.write("\n\nclass Sequences(Sequences):\n")
2324
+ for group in self.model.sequences:
2325
+ classname = type(group).__name__
2326
+ stubfile.write(f" {group.name}: {classname}\n")
2327
+
2328
+ stubfile.write(
2329
+ "\n\nclass Model(Model):\n"
2330
+ " parameters: Parameters\n"
2331
+ " sequences: Sequences\n"
2332
+ )
2333
+ for method in self.model.get_methods():
2334
+ stubfile.write(
2335
+ f" {method.__name__.lower()}: hydpy.core.modeltools.Method\n"
2336
+ )
2337
+
2338
+ stubfile.write("\n\nmodel: Model\n")
2339
+ stubfile.write("parameters: Parameters\n")
2340
+ stubfile.write("sequences: Sequences\n")
2341
+ for subpars in self.model.parameters:
2342
+ classname = f"{subpars.name.capitalize()}Parameters"
2343
+ stubfile.write(f"{subpars.name}: {classname}\n")
2344
+ for subseqs in self.model.sequences:
2345
+ classname = type(subseqs).__name__
2346
+ stubfile.write(f"{subseqs.name}: {classname}\n")
2347
+ if self.model.parameters.control:
2348
+ for partype in self.model.parameters.control.CLASSES:
2349
+ stubfile.write(
2350
+ f"{partype.__name__.lower()}: "
2351
+ f"{partype.__module__}.{partype.__name__}\n"
2352
+ )
2353
+
2354
+
2355
+ class FuncConverter:
2356
+ """Helper class for class |PyxWriter| that analyses Python functions and provides
2357
+ the required Cython code via property |FuncConverter.pyxlines|."""
2358
+
2359
+ model: modeltools.Model
2360
+ funcname: str
2361
+ func: types.MethodType | Callable[[modeltools.Model], None]
2362
+ inline: bool
2363
+
2364
+ def __init__(
2365
+ self,
2366
+ model: modeltools.Model,
2367
+ funcname: str,
2368
+ func: types.MethodType | Callable[[modeltools.Model], None],
2369
+ inline: bool = True,
2370
+ ) -> None:
2371
+ self.model = model
2372
+ self.funcname = funcname
2373
+ self.func = func
2374
+ self.inline = inline
2375
+
2376
+ @property
2377
+ def realfunc(self) -> Callable:
2378
+ """The "real" function, as as defined by the model developer or user."""
2379
+ if (reusablemethod := self.reusablemethod) is not None:
2380
+ return reusablemethod.__call__
2381
+ return self.func
2382
+
2383
+ @property
2384
+ def argnames(self) -> list[str]:
2385
+ """The argument names of the current function.
2386
+
2387
+ >>> from hydpy.cythons.modelutils import FuncConverter
2388
+ >>> from hydpy import prepare_model, pub
2389
+ >>> with pub.options.usecython(False):
2390
+ ... model = prepare_model("hland_96")
2391
+ >>> FuncConverter(model, None, model.calc_tc_v1).argnames
2392
+ ['model']
2393
+ """
2394
+ return inspect.getargs(self.realfunc.__code__)[0]
2395
+
2396
+ @property
2397
+ def varnames(self) -> tuple[str, ...]:
2398
+ """The variable names of the current function.
2399
+
2400
+ >>> from hydpy.cythons.modelutils import FuncConverter
2401
+ >>> from hydpy import prepare_model, pub
2402
+ >>> with pub.options.usecython(False):
2403
+ ... model = prepare_model("hland_96")
2404
+ >>> FuncConverter(model, None, model.calc_tc_v1).varnames
2405
+ ('self', 'con', 'der', 'inp', 'fac', 'k')
2406
+ """
2407
+ return tuple(
2408
+ vn if vn != "model" else "self" for vn in self.realfunc.__code__.co_varnames
2409
+ )
2410
+
2411
+ @property
2412
+ def locnames(self) -> list[str]:
2413
+ """The variable names of the handled function except for the argument names.
2414
+
2415
+ >>> from hydpy.cythons.modelutils import FuncConverter
2416
+ >>> from hydpy import prepare_model, pub
2417
+ >>> with pub.options.usecython(False):
2418
+ ... model = prepare_model("hland_96")
2419
+ >>> FuncConverter(model, None, model.calc_tc_v1).locnames
2420
+ ['self', 'con', 'der', 'inp', 'fac', 'k']
2421
+ """
2422
+ return [vn for vn in self.varnames if vn not in self.argnames]
2423
+
2424
+ @property
2425
+ def subgroupnames(self) -> list[str]:
2426
+ """The complete names of the subgroups relevant for the current function.
2427
+
2428
+ >>> from hydpy.cythons.modelutils import FuncConverter
2429
+ >>> from hydpy import prepare_model, pub
2430
+ >>> with pub.options.usecython(False):
2431
+ ... model = prepare_model("hland_96")
2432
+ >>> FuncConverter(model, None, model.calc_tc_v1).subgroupnames
2433
+ ['parameters.control', 'parameters.derived', 'sequences.inputs', \
2434
+ 'sequences.factors']
2435
+ """
2436
+ names = []
2437
+ for groupname in ("parameters", "sequences"):
2438
+ for subgroup in getattr(self.model, groupname):
2439
+ if subgroup.name[:3] in self.varnames:
2440
+ names.append(groupname + "." + subgroup.name)
2441
+ if "old" in self.varnames:
2442
+ names.append("sequences.old_states")
2443
+ if "new" in self.varnames:
2444
+ names.append("sequences.new_states")
2445
+ return names
2446
+
2447
+ @property
2448
+ def subgroupshortcuts(self) -> list[str]:
2449
+ """The abbreviated names of the subgroups relevant for the current function.
2450
+
2451
+ >>> from hydpy.cythons.modelutils import FuncConverter
2452
+ >>> from hydpy import prepare_model, pub
2453
+ >>> with pub.options.usecython(False):
2454
+ ... model = prepare_model("hland_96")
2455
+ >>> FuncConverter(model, None, model.calc_tc_v1).subgroupshortcuts
2456
+ ['con', 'der', 'inp', 'fac']
2457
+ """
2458
+ return [name.split(".")[-1][:3] for name in self.subgroupnames]
2459
+
2460
+ @property
2461
+ def untypedvarnames(self) -> list[str]:
2462
+ """The names of the untyped variables used in the current function.
2463
+
2464
+ >>> from hydpy.cythons.modelutils import FuncConverter
2465
+ >>> from hydpy import prepare_model, pub
2466
+ >>> with pub.options.usecython(False):
2467
+ ... model = prepare_model("hland_96")
2468
+ >>> FuncConverter(model, None, model.calc_tc_v1).untypedvarnames
2469
+ ['k']
2470
+ """
2471
+ return [
2472
+ name
2473
+ for name in self.varnames
2474
+ if name not in self.subgroupshortcuts + ["self"]
2475
+ ]
2476
+
2477
+ @property
2478
+ def untypedarguments(self) -> list[str]:
2479
+ """The names of the untyped arguments used by the current function.
2480
+
2481
+ >>> from hydpy.cythons.modelutils import FuncConverter
2482
+ >>> from hydpy import prepare_model, pub
2483
+ >>> with pub.options.usecython(False):
2484
+ ... model = prepare_model("hland_96")
2485
+ >>> FuncConverter(model, None, model.calc_tc_v1).untypedarguments
2486
+ []
2487
+ """
2488
+ defline = self.cleanlines[0]
2489
+ return [
2490
+ name
2491
+ for name in self.untypedvarnames
2492
+ if ((f", {name}," in defline) or (f", {name})" in defline))
2493
+ ]
2494
+
2495
+ @property
2496
+ def untypedinternalvarnames(self) -> list[str]:
2497
+ """The names of the untyped variables used in the current function except for
2498
+ those of the arguments.
2499
+
2500
+ >>> from hydpy.cythons.modelutils import FuncConverter
2501
+ >>> from hydpy import prepare_model, pub
2502
+ >>> with pub.options.usecython(False):
2503
+ ... model = prepare_model("hland_96")
2504
+ >>> FuncConverter(model, None, model.calc_tc_v1).untypedinternalvarnames
2505
+ ['k']
2506
+ """
2507
+ return [
2508
+ name for name in self.untypedvarnames if name not in self.untypedarguments
2509
+ ]
2510
+
2511
+ @property
2512
+ def reusablemethod(self) -> type[modeltools.ReusableMethod] | None:
2513
+ """If the currently handled function object is a reusable method, return the
2514
+ corresponding subclass of |ReusableMethod|."""
2515
+ if isinstance(method_of_model := self.func, types.MethodType):
2516
+ maybe_method_of_reusablemethod = method_of_model.__func__
2517
+ if isinstance(maybe_method_of_reusablemethod, types.MethodType):
2518
+ maybe_reusablemethod = maybe_method_of_reusablemethod.__self__
2519
+ if isinstance(maybe_reusablemethod, type) and issubclass(
2520
+ maybe_reusablemethod, modeltools.ReusableMethod
2521
+ ):
2522
+ return maybe_reusablemethod
2523
+ return None
2524
+
2525
+ @property
2526
+ def cleanlines(self) -> list[str]:
2527
+ """The leaned code lines of the current function.
2528
+
2529
+ The implemented cleanups:
2530
+ * eventually, remove method version
2531
+ * remove all docstrings
2532
+ * remove all comments
2533
+ * remove all empty lines
2534
+ * remove line bracks within brackets
2535
+ * remove the phrase `modelutils`
2536
+ * remove all lines containing the phrase `fastaccess`
2537
+ * replace all shortcuts with complete reference names
2538
+ * replace " model." with " self."
2539
+ * remove ".values" and "value"
2540
+ * remove the ": float" annotation
2541
+ """
2542
+ code = inspect.getsource(self.realfunc)
2543
+ code = "\n".join(code.split('"""')[::2])
2544
+ code = code.replace("modelutils.", "")
2545
+ code = code.replace(" model.", " self.")
2546
+ code = code.replace("[model.", "[self.")
2547
+ code = code.replace("(model.", "(self.")
2548
+ code = code.replace("(model)", "(self)")
2549
+ code = code.replace(".values", "")
2550
+ code = code.replace(".value", "")
2551
+ code = code.replace(": float", "")
2552
+ for name, shortcut in zip(self.subgroupnames, self.subgroupshortcuts):
2553
+ code = code.replace(f"{shortcut}.", f"self.{name}.")
2554
+ code = self.remove_linebreaks_within_equations(code)
2555
+ lines = code.splitlines()
2556
+ self.remove_imath_operators(lines)
2557
+ if lines[0].lstrip().startswith("@"):
2558
+ del lines[0] # remove @staticmethod
2559
+ indent = len(lines[0]) - len(lines[0].lstrip())
2560
+ lines = [line[indent:] for line in lines] # normalise indentation
2561
+ argnames = self.argnames
2562
+ argnames[0] = "self"
2563
+ lines[0] = f"def {self.funcname}({', '.join(argnames)}):"
2564
+ lines = [line.split("#")[0] for line in lines]
2565
+ lines = [line for line in lines if "fastaccess" not in line]
2566
+ lines = [line.rstrip() for line in lines if line.rstrip()]
2567
+ return Lines(*lines)
2568
+
2569
+ @staticmethod
2570
+ def remove_linebreaks_within_equations(code: str) -> str:
2571
+ r"""Remove line breaks within equations.
2572
+
2573
+ The following example is not an exhaustive test but shows how the method works
2574
+ in principle:
2575
+
2576
+ >>> code = "asdf = \\\n(a\n+b)"
2577
+ >>> from hydpy.cythons.modelutils import FuncConverter
2578
+ >>> FuncConverter.remove_linebreaks_within_equations(code)
2579
+ 'asdf = (a+b)'
2580
+ """
2581
+ code = code.replace("\\\n", "")
2582
+ chars = []
2583
+ counter = 0
2584
+ for char in code:
2585
+ if char in ("(", "[", "{"):
2586
+ counter += 1
2587
+ elif char in (")", "]", "}"):
2588
+ counter -= 1
2589
+ if not (counter and (char == "\n")):
2590
+ chars.append(char)
2591
+ return "".join(chars)
2592
+
2593
+ @staticmethod
2594
+ def remove_imath_operators(lines: list[str]) -> None:
2595
+ """Remove mathematical expressions that require Pythons global interpreter
2596
+ locking mechanism.
2597
+
2598
+ The following example is not an exhaustive test but shows how the method works
2599
+ in principle:
2600
+
2601
+ >>> lines = [" x += 1*1"]
2602
+ >>> from hydpy.cythons.modelutils import FuncConverter
2603
+ >>> FuncConverter.remove_imath_operators(lines)
2604
+ >>> lines
2605
+ [' x = x + (1*1)']
2606
+ """
2607
+ for idx, line in enumerate(lines):
2608
+ for operator_ in ("+=", "-=", "**=", "*=", "//=", "/=", "%="):
2609
+ sublines = line.split(operator_)
2610
+ if len(sublines) > 1:
2611
+ indent = line.count(" ") - line.lstrip().count(" ")
2612
+ sublines = [sl.strip() for sl in sublines]
2613
+ line = (
2614
+ f"{indent*' '}{sublines[0]} = "
2615
+ f"{sublines[0]} {operator_[:-1]} ({sublines[1]})"
2616
+ )
2617
+ lines[idx] = line
2618
+
2619
+ @property
2620
+ def pyxlines(self) -> Lines:
2621
+ """Cython code lines of the current function.
2622
+
2623
+ Assumptions:
2624
+ * The function shall be a method.
2625
+ * Annotations specify all argument and return types.
2626
+ * Non-default argument and return types are translate to
2627
+ "modulename.classname" strings.
2628
+ * Local variables are generally of type `int` but of type `double` when their
2629
+ name starts with `d_`.
2630
+ * Identical type names in Python and Cython when casting.
2631
+
2632
+ We import some classes and prepare a pure-Python instance of application model
2633
+ |hland_96|:
2634
+
2635
+ >>> from types import MethodType
2636
+ >>> from hydpy.core.modeltools import Method, Model
2637
+ >>> from hydpy.core.typingtools import Vector
2638
+ >>> from hydpy.cythons.modelutils import FuncConverter
2639
+ >>> from hydpy import prepare_model, pub
2640
+ >>> with pub.options.usecython(False):
2641
+ ... model = prepare_model("hland_96")
2642
+
2643
+ First, we show an example of a standard method without additional arguments and
2644
+ returning nothing but requiring two local variables:
2645
+
2646
+ >>> class Calc_Test_V1(Method):
2647
+ ... @staticmethod
2648
+ ... def __call__(model: Model) -> None:
2649
+ ... con = model.parameters.control.fastaccess
2650
+ ... flu = model.sequences.fluxes.fastaccess
2651
+ ... inp = model.sequences.inputs.fastaccess
2652
+ ... for k in range(con.nmbzones):
2653
+ ... d_pc = con.kg[k]*inp.p[k]
2654
+ ... flu.pc[k] = d_pc
2655
+ >>> model.calc_test_v1 = MethodType(Calc_Test_V1.__call__, model)
2656
+ >>> FuncConverter(
2657
+ ... model, "calc_test_v1", model.calc_test_v1
2658
+ ... ).pyxlines # doctest: +ELLIPSIS
2659
+ cpdef inline void calc_test_v1(self) noexcept nogil:
2660
+ cdef double d_pc
2661
+ cdef ...int... k
2662
+ for k in range(self.parameters.control.nmbzones):
2663
+ d_pc = self.parameters.control.kg[k]*self.sequences.inputs.p[k]
2664
+ self.sequences.fluxes.pc[k] = d_pc
2665
+ <BLANKLINE>
2666
+
2667
+ The second example shows that `float` and `Vector` annotations translate into
2668
+ `double` and `double[:]` types, respectively:
2669
+
2670
+ >>> class Calc_Test_V2(Method):
2671
+ ... @staticmethod
2672
+ ... def __call__(model: Model, value: float, values: Vector) -> float:
2673
+ ... con = model.parameters.control.fastaccess
2674
+ ... return con.kg[0]*value*values[1]
2675
+ >>> model.calc_test_discontinous = MethodType(Calc_Test_V2.__call__, model)
2676
+ >>> FuncConverter(
2677
+ ... model, "calc_test_discontinous", model.calc_test_discontinous
2678
+ ... ).pyxlines
2679
+ cpdef inline double calc_test_discontinous(self, double value, double[:] \
2680
+ values) noexcept nogil:
2681
+ return self.parameters.control.kg[0]*value*values[1]
2682
+ <BLANKLINE>
2683
+
2684
+ Third, Python's standard cast function translates into Cython's cast syntax:
2685
+
2686
+ >>> from hydpy.interfaces import routinginterfaces
2687
+ >>> class Calc_Test_V3(Method):
2688
+ ... @staticmethod
2689
+ ... def __call__(model: Model) -> routinginterfaces.StorageModel_V1:
2690
+ ... return cast(routinginterfaces.StorageModel_V1, model.soilmodel)
2691
+ >>> model.calc_test_stiff1d = MethodType(Calc_Test_V3.__call__, model)
2692
+ >>> FuncConverter(model, "calc_test_stiff1d", model.calc_test_stiff1d).pyxlines
2693
+ cpdef inline masterinterface.MasterInterface calc_test_stiff1d(self) noexcept \
2694
+ nogil:
2695
+ return (<masterinterface.MasterInterface>self.soilmodel)
2696
+ <BLANKLINE>
2697
+
2698
+ >>> class Calc_Test_V4(Method):
2699
+ ... @staticmethod
2700
+ ... def __call__(model: Model) -> None:
2701
+ ... cast(
2702
+ ... routinginterfaces.RoutingModel_V1
2703
+ ... | routinginterfaces.RoutingModel_V2,
2704
+ ... model.routingmodels[0],
2705
+ ... ).get_partialdischargedownstream()
2706
+ >>> model.calc_test_v4 = MethodType(Calc_Test_V4.__call__, model)
2707
+ >>> FuncConverter(model, "calc_test_v4", model.calc_test_v4).pyxlines
2708
+ cpdef inline void calc_test_v4(self) noexcept nogil:
2709
+ (<masterinterface.MasterInterface>self.routingmodels[0]).\
2710
+ get_partialdischargedownstream()
2711
+ <BLANKLINE>
2712
+ """
2713
+
2714
+ def _get_cytype(name_: str) -> str:
2715
+ pytype = annotations_[name_]
2716
+ if pytype in TYPE2STR:
2717
+ return TYPE2STR[pytype]
2718
+ if pytype.__module__.startswith("hydpy.interfaces."):
2719
+ return "masterinterface.MasterInterface"
2720
+ return f"{pytype.__module__.split('.')[-1]}.{pytype.__name__}"
2721
+
2722
+ annotations_ = get_type_hints(self.realfunc)
2723
+ lines = self.cleanlines
2724
+ lines[0] = lines[0].lower()
2725
+ inline = " inline" if self.inline else ""
2726
+ lines[0] = lines[0].replace("def ", f"cpdef{inline} {_get_cytype('return')} ")
2727
+ lines[0] = lines[0].replace("):", f"){_nogil}:")
2728
+ if (reusablemethod := self.reusablemethod) is not None:
2729
+ lines[0] = lines[0].replace("(self, model", "(self")
2730
+ for i in range(1, len(lines)):
2731
+ lines[i] = f" {lines[i]}"
2732
+ lines.insert(1, f" if not self.{reusablemethod.REUSEMARKER}:")
2733
+ lines.append(f" self.{reusablemethod.REUSEMARKER} = True")
2734
+ for name in self.untypedarguments:
2735
+ cytype = _get_cytype(name)
2736
+ lines[0] = lines[0].replace(f", {name},", f", {cytype} {name},")
2737
+ lines[0] = lines[0].replace(f", {name})", f", {cytype} {name})")
2738
+ code = inspect.getsource(self.realfunc)
2739
+ for name in self.untypedinternalvarnames:
2740
+ if (f" {name}: float" in code) or name.startswith("d_"):
2741
+ cytype = TYPE2STR[float]
2742
+ else:
2743
+ cytype = TYPE2STR[int]
2744
+ lines.insert(1, f" cdef {cytype} {name}")
2745
+ for idx, line in enumerate(lines):
2746
+ if "cast(" in line:
2747
+ part1, _, part23 = line.partition("cast(")
2748
+ part2, _, part3 = part23.partition(")")
2749
+ part2 = part2.strip().strip(",").rpartition(",")[2].strip()
2750
+ lines[idx] = f"{part1}(<masterinterface.MasterInterface>{part2}){part3}"
2751
+ return Lines(*lines)
2752
+
2753
+
2754
+ def get_callbackcymodule(
2755
+ model: modeltools.Model,
2756
+ parameter: parametertools.CallbackParameter,
2757
+ callback: Callable[[modeltools.Model], None],
2758
+ ) -> types.ModuleType:
2759
+ """Return the cython module containing the required callback module after, if
2760
+ necessary, creating or updating."""
2761
+
2762
+ basename = f"callback_{model}_{parameter.name}_{callback.__name__}"
2763
+ pyfilepath = os.path.join(autogenpath, f"{basename}.pysource")
2764
+ pycode = inspect.getsource(callback)
2765
+
2766
+ lines = pycode.split("\n")
2767
+ indent = len(lines[0]) - len(lines[0].lstrip())
2768
+ pycode = "\n".join(line[indent:] for line in lines)
2769
+
2770
+ refresh = True
2771
+ if os.path.exists(pyfilepath):
2772
+ with open(pyfilepath, encoding=config.ENCODING) as sf:
2773
+ refresh = pycode != sf.read()
2774
+
2775
+ if refresh:
2776
+ if (module := model.__module__).count(".") == 3:
2777
+ module = module.rpartition(".")[0]
2778
+ cythonizer = importlib.import_module(module).cythonizer
2779
+ assert isinstance(cythonizer, Cythonizer)
2780
+ preamble = PyxPxdLines()
2781
+ cythonizer.pyxwriter.cythondistutilsoptions(preamble)
2782
+ cythonizer.pyxwriter.cimports(preamble)
2783
+
2784
+ pyx = FuncConverter(
2785
+ model=model, funcname=callback.__name__, func=callback, inline=False
2786
+ ).pyxlines
2787
+ pyx.insert(
2788
+ 0, f"from hydpy.cythons.autogen.c_{model} cimport Model, CallbackWrapper\n"
2789
+ )
2790
+ pyx[1] = pyx[1].replace("cpdef ", "cdef ").replace("(self)", "(Model self)")
2791
+ pyx.add(0, "")
2792
+ pyx.add(0, "cdef class MyCallbackWrapper(CallbackWrapper):")
2793
+ pyx.add(1, "def __call__(self, model):")
2794
+ pyx.add(2, "self.callback(model.cymodel)")
2795
+ pyx.add(1, "def get_name(self):")
2796
+ pyx.add(2, f'return "{callback.__name__}"')
2797
+ pyx.add(1, "def get_sourcecode(self):")
2798
+ pyx.add(2, f"return {repr(pycode.strip())}")
2799
+ pyx.add(0, "")
2800
+ pyx.add(0, "cpdef get_wrapper():")
2801
+ pyx.add(1, "cdef MyCallbackWrapper wrapper = MyCallbackWrapper()")
2802
+ pyx.add(1, f"wrapper.callback = {callback.__name__}")
2803
+ pyx.add(1, "return wrapper")
2804
+
2805
+ pyxfilepath = os.path.join(autogenpath, f"{basename}.pyx")
2806
+ with open(pyxfilepath, "w", encoding=config.ENCODING) as file_:
2807
+ file_.write("\n".join(preamble.pyx + pyx))
2808
+ compile_(
2809
+ cyname=basename,
2810
+ pyxfilepath=pyxfilepath,
2811
+ buildpath=os.path.join(autogenpath, "_build"),
2812
+ )
2813
+ move_dll(
2814
+ pyname=model.name,
2815
+ cyname=basename,
2816
+ cydirpath=autogenpath,
2817
+ buildpath=os.path.join(autogenpath, "_build"),
2818
+ )
2819
+ with open(pyfilepath, "w", encoding=config.ENCODING) as sf:
2820
+ sf.write(pycode)
2821
+
2822
+ return importlib.import_module(f"hydpy.cythons.autogen.{basename}")
2823
+
2824
+
2825
+ def exp(double: float) -> float:
2826
+ """Cython wrapper for the |numpy.exp| function of module |numpy| applied on a
2827
+ single |float| object.
2828
+
2829
+ >>> from hydpy.cythons.modelutils import exp
2830
+ >>> from unittest import mock
2831
+ >>> with mock.patch("numpy.exp") as func:
2832
+ ... _ = exp(123.4)
2833
+ >>> func.call_args
2834
+ call(123.4)
2835
+ """
2836
+ return numpy.exp(double)
2837
+
2838
+
2839
+ def log(double: float) -> float:
2840
+ """Cython wrapper for the |numpy.log| function of module |numpy| applied on a
2841
+ single |float| object.
2842
+
2843
+ >>> from hydpy.cythons.modelutils import log
2844
+ >>> from unittest import mock
2845
+ >>> with mock.patch("numpy.log") as func:
2846
+ ... _ = log(123.4)
2847
+ >>> func.call_args
2848
+ call(123.4)
2849
+ """
2850
+ return numpy.log(double)
2851
+
2852
+
2853
+ def fabs(double: float) -> float:
2854
+ """Cython wrapper for the |math.fabs| function of module |math| applied on a single
2855
+ |float| object.
2856
+
2857
+ >>> from hydpy.cythons.modelutils import fabs
2858
+ >>> from unittest import mock
2859
+ >>> with mock.patch("math.fabs") as func:
2860
+ ... _ = fabs(123.4)
2861
+ >>> func.call_args
2862
+ call(123.4)
2863
+ """
2864
+ return math.fabs(double)
2865
+
2866
+
2867
+ def sin(double: float) -> float:
2868
+ """Cython wrapper for the |numpy.sin| function of module |numpy| applied on a
2869
+ single |float| object.
2870
+
2871
+ >>> from hydpy.cythons.modelutils import sin
2872
+ >>> from unittest import mock
2873
+ >>> with mock.patch("numpy.sin") as func:
2874
+ ... _ = sin(123.4)
2875
+ >>> func.call_args
2876
+ call(123.4)
2877
+ """
2878
+ return numpy.sin(double)
2879
+
2880
+
2881
+ def cos(double: float) -> float:
2882
+ """Cython wrapper for the |numpy.cos| function of module |numpy| applied on a
2883
+ single |float| object.
2884
+
2885
+ >>> from hydpy.cythons.modelutils import cos
2886
+ >>> from unittest import mock
2887
+ >>> with mock.patch("numpy.cos") as func:
2888
+ ... _ = cos(123.4)
2889
+ >>> func.call_args
2890
+ call(123.4)
2891
+ """
2892
+ return numpy.cos(double)
2893
+
2894
+
2895
+ def tan(double: float) -> float:
2896
+ """Cython wrapper for the |numpy.tan| function of module |numpy| applied on a
2897
+ single |float| object.
2898
+
2899
+ >>> from hydpy.cythons.modelutils import tan
2900
+ >>> from unittest import mock
2901
+ >>> with mock.patch("numpy.tan") as func:
2902
+ ... _ = tan(123.4)
2903
+ >>> func.call_args
2904
+ call(123.4)
2905
+ """
2906
+ return numpy.tan(double)
2907
+
2908
+
2909
+ def asin(double: float) -> float:
2910
+ """Cython wrapper for the |numpy.arcsin| function of module |numpy| applied on a
2911
+ single |float| object.
2912
+
2913
+ >>> from hydpy.cythons.modelutils import asin
2914
+ >>> from unittest import mock
2915
+ >>> with mock.patch("numpy.arcsin") as func:
2916
+ ... _ = asin(123.4)
2917
+ >>> func.call_args
2918
+ call(123.4)
2919
+ """
2920
+ return numpy.arcsin(double)
2921
+
2922
+
2923
+ def acos(double: float) -> float:
2924
+ """Cython wrapper for the |numpy.arccos| function of module |numpy| applied on a
2925
+ single |float| object.
2926
+
2927
+ >>> from hydpy.cythons.modelutils import acos
2928
+ >>> from unittest import mock
2929
+ >>> with mock.patch("numpy.arccos") as func:
2930
+ ... _ = acos(123.4)
2931
+ >>> func.call_args
2932
+ call(123.4)
2933
+ """
2934
+ return numpy.arccos(double)
2935
+
2936
+
2937
+ def atan(double: float) -> float:
2938
+ """Cython wrapper for the |numpy.arctan| function of module |numpy| applied on a
2939
+ single |float| object.
2940
+
2941
+ >>> from hydpy.cythons.modelutils import atan
2942
+ >>> from unittest import mock
2943
+ >>> with mock.patch("numpy.arctan") as func:
2944
+ ... _ = atan(123.4)
2945
+ >>> func.call_args
2946
+ call(123.4)
2947
+ """
2948
+ return numpy.arctan(double)
2949
+
2950
+
2951
+ def tanh(double: float) -> float:
2952
+ """Cython wrapper for the |numpy.tanh| function of module |numpy| applied
2953
+ on a single |float| object.
2954
+
2955
+ >>> from hydpy.cythons.modelutils import tanh
2956
+ >>> from unittest import mock
2957
+ >>> with mock.patch('numpy.tanh') as func:
2958
+ ... _ = tanh(123.4)
2959
+ >>> func.call_args
2960
+ call(123.4)
2961
+ """
2962
+ return numpy.tanh(double)
2963
+
2964
+
2965
+ def isnan(double: float) -> float:
2966
+ """Cython wrapper for the |numpy.isnan| function of module |numpy| applied on a
2967
+ single |float| object.
2968
+
2969
+ >>> from hydpy.cythons.modelutils import isnan
2970
+ >>> from unittest import mock
2971
+ >>> with mock.patch("numpy.isnan") as func:
2972
+ ... _ = isnan(123.4)
2973
+ >>> func.call_args
2974
+ call(123.4)
2975
+ """
2976
+ return numpy.isnan(double)
2977
+
2978
+
2979
+ def isinf(double: float) -> float:
2980
+ """Cython wrapper for the |numpy.isinf| function of module |numpy| applied on a
2981
+ single |float| object.
2982
+
2983
+ >>> from hydpy.cythons.modelutils import isnan
2984
+ >>> from unittest import mock
2985
+ >>> with mock.patch("numpy.isinf") as func:
2986
+ ... _ = isinf(123.4)
2987
+ >>> func.call_args
2988
+ call(123.4)
2989
+ """
2990
+ return numpy.isinf(double)