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