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