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