Source code for hydpy.models.lland.lland_model

# -*- coding: utf-8 -*-
# pylint: disable=missing-module-docstring

# imports...
# ...from HydPy
from hydpy.core import modeltools
from hydpy.auxs import roottools
from hydpy.cythons import modelutils

# ...from lland
from hydpy.models.lland import lland_control
from hydpy.models.lland import lland_derived
from hydpy.models.lland import lland_fixed
from hydpy.models.lland import lland_inlets
from hydpy.models.lland import lland_inputs
from hydpy.models.lland import lland_fluxes
from hydpy.models.lland import lland_states
from hydpy.models.lland import lland_logs
from hydpy.models.lland import lland_aides
from hydpy.models.lland import lland_outlets
from hydpy.models.lland.lland_constants import (
    WASSER,
    FLUSS,
    SEE,
    VERS,
    LAUBW,
    MISCHW,
    NADELW,
)


[docs] class Pick_QZ_V1(modeltools.Method): """Query the current inflow from all inlet nodes. Basic equation: :math:`QZ = \\sum Q_{inlets}` """ REQUIREDSEQUENCES = (lland_inlets.Q,) RESULTSEQUENCES = (lland_fluxes.QZ,) @staticmethod def __call__(model: modeltools.Model) -> None: flu = model.sequences.fluxes.fastaccess inl = model.sequences.inlets.fastaccess flu.qz = 0.0 for idx in range(inl.len_q): flu.qz += inl.q[idx][0]
[docs] class Calc_QZH_V1(modeltools.Method): """Calculate the inflow in mm. Basic equation: :math:`QZH = QZ / QFactor` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.qfactor(2.0) >>> fluxes.qz = 6.0 >>> model.calc_qzh_v1() >>> fluxes.qzh qzh(3.0) """ DERIVEDPARAMETERS = (lland_derived.QFactor,) REQUIREDSEQUENCES = (lland_fluxes.QZ,) RESULTSEQUENCES = (lland_fluxes.QZH,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess flu.qzh = flu.qz / der.qfactor
[docs] class Update_LoggedTemL_V1(modeltools.Method): """Log the air temperature values of the last 24 hours. Example: The following example shows that each new method call successively moves the three memorised values to the right and stores the respective new value on the most left position: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedteml.shape = 3 >>> logs.loggedteml = 0.0 >>> from hydpy import UnitTest >>> test = UnitTest(model, ... model.update_loggedteml_v1, ... last_example=4, ... parseqs=(inputs.teml, ... logs.loggedteml)) >>> test.nexts.teml = 0.0, 6.0, 3.0, 3.0 >>> del test.inits.loggedteml >>> test() | ex. | teml | loggedteml | ------------------------------------- | 1 | 0.0 | 0.0 0.0 0.0 | | 2 | 6.0 | 6.0 0.0 0.0 | | 3 | 3.0 | 3.0 6.0 0.0 | | 4 | 3.0 | 3.0 3.0 6.0 | """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_inputs.TemL,) UPDATEDSEQUENCES = (lland_logs.LoggedTemL,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess log = model.sequences.logs.fastaccess for idx in range(der.nmblogentries - 1, 0, -1): log.loggedteml[idx] = log.loggedteml[idx - 1] log.loggedteml[0] = inp.teml
[docs] class Calc_TemLTag_V1(modeltools.Method): """Calculate the average air temperature of the last 24 hours. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedteml.shape = 3 >>> logs.loggedteml = 1.0, 5.0, 3.0 >>> model.calc_temltag_v1() >>> fluxes.temltag temltag(3.0) """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_logs.LoggedTemL,) UPDATEDSEQUENCES = (lland_fluxes.TemLTag,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess log = model.sequences.logs.fastaccess flu = model.sequences.fluxes.fastaccess flu.temltag = 0.0 for idx in range(der.nmblogentries): flu.temltag += log.loggedteml[idx] flu.temltag /= der.nmblogentries
[docs] class Update_LoggedRelativeHumidity_V1(modeltools.Method): """Log the sunshine duration values of the last 24 hours. Example: The following example shows that each new method call successively moves the three memorised values to the right and stores the respective new value on the most left position: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedrelativehumidity.shape = 3 >>> logs.loggedrelativehumidity = 0.0 >>> from hydpy import UnitTest >>> method = ( ... model.update_loggedrelativehumidity_v1) >>> test = UnitTest(model, ... method, ... last_example=4, ... parseqs=(inputs.relativehumidity, ... logs.loggedrelativehumidity)) >>> test.nexts.relativehumidity = 0.0, 6.0, 3.0, 3.0 >>> del test.inits.loggedrelativehumidity >>> test() | ex. | relativehumidity | loggedrelativehumidity | ------------------------------------------------------------- | 1 | 0.0 | 0.0 0.0 0.0 | | 2 | 6.0 | 6.0 0.0 0.0 | | 3 | 3.0 | 3.0 6.0 0.0 | | 4 | 3.0 | 3.0 3.0 6.0 | """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_inputs.RelativeHumidity,) UPDATEDSEQUENCES = (lland_logs.LoggedRelativeHumidity,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess log = model.sequences.logs.fastaccess for idx in range(der.nmblogentries - 1, 0, -1): log.loggedrelativehumidity[idx] = log.loggedrelativehumidity[idx - 1] log.loggedrelativehumidity[0] = inp.relativehumidity
[docs] class Calc_DailyRelativeHumidity_V1(modeltools.Method): """Calculate the average relative humidity of the last 24 hours. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedrelativehumidity.shape = 3 >>> logs.loggedrelativehumidity = 1.0, 5.0, 3.0 >>> model.calc_dailyrelativehumidity_v1() >>> fluxes.dailyrelativehumidity dailyrelativehumidity(3.0) """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_logs.LoggedRelativeHumidity,) UPDATEDSEQUENCES = (lland_fluxes.DailyRelativeHumidity,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess log = model.sequences.logs.fastaccess flu = model.sequences.fluxes.fastaccess flu.dailyrelativehumidity = 0.0 for idx in range(der.nmblogentries): flu.dailyrelativehumidity += log.loggedrelativehumidity[idx] flu.dailyrelativehumidity /= der.nmblogentries
[docs] class Update_LoggedWindSpeed2m_V1(modeltools.Method): """Log the wind speed values 2 meters above ground of the last 24 hours. Example: The following example shows that each new method call successively moves the three memorised values to the right and stores the respective new value on the most left position: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedwindspeed2m.shape = 3 >>> logs.loggedwindspeed2m = 0.0 >>> from hydpy import UnitTest >>> test = UnitTest(model, ... model.update_loggedwindspeed2m_v1, ... last_example=4, ... parseqs=(fluxes.windspeed2m, ... logs.loggedwindspeed2m)) >>> test.nexts.windspeed2m = 1.0, 3.0, 2.0, 4.0 >>> del test.inits.loggedwindspeed2m >>> test() | ex. | windspeed2m | loggedwindspeed2m | --------------------------------------------------- | 1 | 1.0 | 1.0 0.0 0.0 | | 2 | 3.0 | 3.0 1.0 0.0 | | 3 | 2.0 | 2.0 3.0 1.0 | | 4 | 4.0 | 4.0 2.0 3.0 | """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_fluxes.WindSpeed2m,) UPDATEDSEQUENCES = (lland_logs.LoggedWindSpeed2m,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess log = model.sequences.logs.fastaccess for idx in range(der.nmblogentries - 1, 0, -1): log.loggedwindspeed2m[idx] = log.loggedwindspeed2m[idx - 1] log.loggedwindspeed2m[0] = flu.windspeed2m
[docs] class Calc_DailyWindSpeed2m_V1(modeltools.Method): """Calculate the average wind speed 2 meters above ground of the last 24 hours. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedwindspeed2m.shape = 3 >>> logs.loggedwindspeed2m = 1.0, 5.0, 3.0 >>> model.calc_dailywindspeed2m_v1() >>> fluxes.dailywindspeed2m dailywindspeed2m(3.0) """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_logs.LoggedWindSpeed2m,) UPDATEDSEQUENCES = (lland_fluxes.DailyWindSpeed2m,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess log = model.sequences.logs.fastaccess flu = model.sequences.fluxes.fastaccess flu.dailywindspeed2m = 0.0 for idx in range(der.nmblogentries): flu.dailywindspeed2m += log.loggedwindspeed2m[idx] flu.dailywindspeed2m /= der.nmblogentries
[docs] class Update_LoggedSunshineDuration_V1(modeltools.Method): """Log the sunshine duration values of the last 24 hours. Example: The following example shows that each new method call successively moves the three memorised values to the right and stores the respective new value on the most left position: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedsunshineduration.shape = 3 >>> logs.loggedsunshineduration = 0.0 >>> from hydpy import UnitTest >>> test = UnitTest(model, ... model.update_loggedsunshineduration_v1, ... last_example=4, ... parseqs=(inputs.sunshineduration, ... logs.loggedsunshineduration)) >>> test.nexts.sunshineduration = 1.0, 3.0, 2.0, 4.0 >>> del test.inits.loggedsunshineduration >>> test() | ex. | sunshineduration | loggedsunshineduration | ------------------------------------------------------------- | 1 | 1.0 | 1.0 0.0 0.0 | | 2 | 3.0 | 3.0 1.0 0.0 | | 3 | 2.0 | 2.0 3.0 1.0 | | 4 | 4.0 | 4.0 2.0 3.0 | """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_inputs.SunshineDuration,) UPDATEDSEQUENCES = (lland_logs.LoggedSunshineDuration,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess log = model.sequences.logs.fastaccess for idx in range(der.nmblogentries - 1, 0, -1): log.loggedsunshineduration[idx] = log.loggedsunshineduration[idx - 1] log.loggedsunshineduration[0] = inp.sunshineduration
[docs] class Calc_DailySunshineDuration_V1(modeltools.Method): """Calculate the sunshine duration sum of the last 24 hours. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedsunshineduration.shape = 3 >>> logs.loggedsunshineduration = 1.0, 5.0, 3.0 >>> model.calc_dailysunshineduration_v1() >>> fluxes.dailysunshineduration dailysunshineduration(9.0) """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_logs.LoggedSunshineDuration,) UPDATEDSEQUENCES = (lland_fluxes.DailySunshineDuration,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess log = model.sequences.logs.fastaccess flu.dailysunshineduration = 0.0 for idx in range(der.nmblogentries): flu.dailysunshineduration += log.loggedsunshineduration[idx]
[docs] class Update_LoggedPossibleSunshineDuration_V1(modeltools.Method): """Log the sunshine duration values of the last 24 hours. Example: The following example shows that each new method call successively moves the three memorised values to the right and stores the respective new value on the most left position: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedpossiblesunshineduration.shape = 3 >>> logs.loggedpossiblesunshineduration = 0.0 >>> from hydpy import UnitTest >>> test = UnitTest(model, ... model.update_loggedpossiblesunshineduration_v1, ... last_example=4, ... parseqs=(inputs.possiblesunshineduration, ... logs.loggedpossiblesunshineduration)) >>> test.nexts.possiblesunshineduration = 1.0, 3.0, 2.0, 4.0 >>> del test.inits.loggedpossiblesunshineduration >>> test() | ex. | possiblesunshineduration | loggedpossiblesunshineduration | ----------------------------------------------------------------------------- | 1 | 1.0 | 1.0 0.0 0.0 | | 2 | 3.0 | 3.0 1.0 0.0 | | 3 | 2.0 | 2.0 3.0 1.0 | | 4 | 4.0 | 4.0 2.0 3.0 | """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_inputs.PossibleSunshineDuration,) UPDATEDSEQUENCES = (lland_logs.LoggedPossibleSunshineDuration,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess log = model.sequences.logs.fastaccess for idx in range(der.nmblogentries - 1, 0, -1): log.loggedpossiblesunshineduration[ idx ] = log.loggedpossiblesunshineduration[idx - 1] log.loggedpossiblesunshineduration[0] = inp.possiblesunshineduration
[docs] class Calc_DailyPossibleSunshineDuration_V1(modeltools.Method): """Calculate the sunshine duration sum of the last 24 hours. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedpossiblesunshineduration.shape = 3 >>> logs.loggedpossiblesunshineduration = 1.0, 5.0, 3.0 >>> model.calc_dailypossiblesunshineduration_v1() >>> fluxes.dailypossiblesunshineduration dailypossiblesunshineduration(9.0) """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_logs.LoggedPossibleSunshineDuration,) UPDATEDSEQUENCES = (lland_fluxes.DailyPossibleSunshineDuration,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess log = model.sequences.logs.fastaccess flu = model.sequences.fluxes.fastaccess flu.dailypossiblesunshineduration = 0.0 for idx in range(der.nmblogentries): flu.dailypossiblesunshineduration += log.loggedpossiblesunshineduration[idx]
[docs] class Calc_NKor_V1(modeltools.Method): """Adjust the given precipitation value according to :cite:t:`ref-LARSIM`. Basic equation: :math:`NKor = KG \\cdot Nied` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> kg(0.8, 1.0, 1.2) >>> inputs.nied = 10.0 >>> model.calc_nkor_v1() >>> fluxes.nkor nkor(8.0, 10.0, 12.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.KG, ) REQUIREDSEQUENCES = (lland_inputs.Nied,) RESULTSEQUENCES = (lland_fluxes.NKor,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.nkor[k] = con.kg[k] * inp.nied
[docs] class Calc_TKor_V1(modeltools.Method): """Adjust the given air temperature value. Basic equation: :math:`TKor = KT + TemL` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> kt(-2.0, 0.0, 2.0) >>> inputs.teml(1.0) >>> model.calc_tkor_v1() >>> fluxes.tkor tkor(-1.0, 1.0, 3.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.KT, ) REQUIREDSEQUENCES = (lland_inputs.TemL,) RESULTSEQUENCES = (lland_fluxes.TKor,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.tkor[k] = con.kt[k] + inp.teml
[docs] class Calc_TKorTag_V1(modeltools.Method): """Adjust the given daily air temperature value. Basic equation: :math:`TKorTag = KT + TemLTag` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> kt(-2.0, 0.0, 2.0) >>> fluxes.temltag(1.0) >>> model.calc_tkortag_v1() >>> fluxes.tkortag tkortag(-1.0, 1.0, 3.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.KT, ) REQUIREDSEQUENCES = (lland_fluxes.TemLTag,) RESULTSEQUENCES = (lland_fluxes.TKorTag,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.tkortag[k] = con.kt[k] + flu.temltag
[docs] class Return_AdjustedWindSpeed_V1(modeltools.Method): """Adjust and return the measured wind speed to the given defined height above the ground according to :cite:t:`ref-LARSIM`. Basic equation: :math:`WindSpeed \\cdot \\frac{ln(newheight/Z0)}{ln(MeasuringHeightWindSpeed/Z0)}` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(1) >>> measuringheightwindspeed(10.0) >>> inputs.windspeed = 5.0 >>> from hydpy import round_ >>> round_(model.return_adjustedwindspeed_v1(2.0)) 4.007956 >>> round_(model.return_adjustedwindspeed_v1(0.5)) 3.153456 """ CONTROLPARAMETERS = (lland_control.MeasuringHeightWindSpeed,) FIXEDPARAMETERS = (lland_fixed.Z0,) REQUIREDSEQUENCES = (lland_inputs.WindSpeed,) @staticmethod def __call__( model: modeltools.Model, newheight: float, ) -> float: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess inp = model.sequences.inputs.fastaccess return inp.windspeed * ( modelutils.log(newheight / fix.z0) / modelutils.log(con.measuringheightwindspeed / fix.z0) )
[docs] class Calc_WindSpeed2m_V1(modeltools.Method): """Adjust the measured wind speed to a height of 2 meters above the ground. Method |Calc_WindSpeed2m_V1| uses method |Return_AdjustedWindSpeed_V1| to adjust the wind speed of all hydrological response units. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(1) >>> measuringheightwindspeed(10.0) >>> inputs.windspeed = 5.0 >>> model.calc_windspeed2m_v1() >>> fluxes.windspeed2m windspeed2m(4.007956) """ SUBMETHODS = (Return_AdjustedWindSpeed_V1,) CONTROLPARAMETERS = (lland_control.MeasuringHeightWindSpeed,) FIXEDPARAMETERS = (lland_fixed.Z0,) REQUIREDSEQUENCES = (lland_inputs.WindSpeed,) RESULTSEQUENCES = (lland_fluxes.WindSpeed2m,) @staticmethod def __call__(model: modeltools.Model) -> None: flu = model.sequences.fluxes.fastaccess flu.windspeed2m = model.return_adjustedwindspeed_v1(2.0)
[docs] class Calc_ReducedWindSpeed2m_V1(modeltools.Method): """Calculate the landuse-use-specific wind speed at height of 2 meters according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-LUBW2006a`, :cite:t:`ref-LUBWLUWG2015`). Basic equation (for forests): :math:`ReducedWindSpeed2m = \ max(P1Wind - P2Wind \\cdot LAI, 0) \\cdot WindSpeed2m` Example: The basic equation given above holds for forests (hydrological response units of type |LAUBW|, |MISCHW|, and |NADELW| only. For all other landuse-use types method |Calc_ReducedWindSpeed2m_V1| maintains the given wind speed for grass: >>> from hydpy import pub >>> pub.timegrids = "2019-05-30", "2019-06-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(OBSTB, LAUBW, MISCHW, NADELW) >>> lai.obstb_mai = 0.0 >>> lai.laubw_mai = 2.0 >>> lai.mischw_mai = 4.0 >>> lai.nadelw_mai = 7.0 >>> p1wind(0.5) >>> p2wind(0.1) >>> derived.moy.update() >>> fluxes.windspeed2m = 2.0 >>> model.idx_sim = pub.timegrids.init["2019-05-31"] >>> model.calc_reducedwindspeed2m_v1() >>> fluxes.reducedwindspeed2m reducedwindspeed2m(2.0, 0.6, 0.2, 0.0) >>> lai.obstb_jun = 0.0 >>> lai.laubw_jun = 3.0 >>> lai.mischw_jun = 6.0 >>> lai.nadelw_jun = 10.0 >>> model.idx_sim = pub.timegrids.init["2019-06-01"] >>> model.calc_reducedwindspeed2m_v1() >>> fluxes.reducedwindspeed2m reducedwindspeed2m(2.0, 0.4, 0.0, 0.0) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.LAI, lland_control.P1Wind, lland_control.P2Wind, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = (lland_fluxes.WindSpeed2m,) RESULTSEQUENCES = (lland_fluxes.ReducedWindSpeed2m,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): d_lai = con.lai[con.lnk[k] - 1, der.moy[model.idx_sim]] flu.reducedwindspeed2m[k] = ( max(con.p1wind - con.p2wind * d_lai, 0.0) * flu.windspeed2m ) else: flu.reducedwindspeed2m[k] = flu.windspeed2m
[docs] class Calc_WindSpeed10m_V1(modeltools.Method): """Adjust the measured wind speed to a height of 10 meters above the ground. Method |Calc_WindSpeed10m_V1| uses method |Return_AdjustedWindSpeed_V1| to adjust the wind speed of all hydrological response units. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(1) >>> measuringheightwindspeed(3.0) >>> inputs.windspeed = 5.0 >>> model.calc_windspeed10m_v1() >>> fluxes.windspeed10m windspeed10m(5.871465) """ SUBMETHODS = (Return_AdjustedWindSpeed_V1,) CONTROLPARAMETERS = (lland_control.MeasuringHeightWindSpeed,) FIXEDPARAMETERS = (lland_fixed.Z0,) REQUIREDSEQUENCES = (lland_inputs.WindSpeed,) RESULTSEQUENCES = (lland_fluxes.WindSpeed10m,) @staticmethod def __call__(model: modeltools.Model) -> None: flu = model.sequences.fluxes.fastaccess flu.windspeed10m = model.return_adjustedwindspeed_v1(10.0)
[docs] class Update_LoggedGlobalRadiation_V1(modeltools.Method): """Log the global radiation values of the last 24 hours. Example: The following example shows that each new method call successively moves the three memorised values to the right and stores the respective new value on the most left position: >>> from hydpy.models.lland import * >>> simulationstep("8h") >>> parameterstep() >>> derived.nmblogentries.update() >>> logs.loggedglobalradiation = 0.0 >>> from hydpy import UnitTest >>> test = UnitTest(model, ... model.update_loggedglobalradiation_v1, ... last_example=4, ... parseqs=(inputs.globalradiation, ... logs.loggedglobalradiation)) >>> test.nexts.globalradiation = 1.0, 3.0, 2.0, 4.0 >>> del test.inits.loggedglobalradiation >>> test() | ex. | globalradiation | loggedglobalradiation | ----------------------------------------------------------- | 1 | 1.0 | 1.0 0.0 0.0 | | 2 | 3.0 | 3.0 1.0 0.0 | | 3 | 2.0 | 2.0 3.0 1.0 | | 4 | 4.0 | 4.0 2.0 3.0 | """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_inputs.GlobalRadiation,) UPDATEDSEQUENCES = (lland_logs.LoggedGlobalRadiation,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess log = model.sequences.logs.fastaccess for idx in range(der.nmblogentries - 1, 0, -1): log.loggedglobalradiation[idx] = log.loggedglobalradiation[idx - 1] log.loggedglobalradiation[0] = inp.globalradiation
[docs] class Calc_DailyGlobalRadiation_V1(modeltools.Method): """Calculate the global radiation sum of the last 24 hours. Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.nmblogentries(3) >>> logs.loggedglobalradiation.shape = 3 >>> logs.loggedglobalradiation = 100.0, 800.0, 600.0 >>> model.calc_dailyglobalradiation_v1() >>> fluxes.dailyglobalradiation dailyglobalradiation(500.0) """ DERIVEDPARAMETERS = (lland_derived.NmbLogEntries,) REQUIREDSEQUENCES = (lland_logs.LoggedGlobalRadiation,) UPDATEDSEQUENCES = (lland_fluxes.DailyGlobalRadiation,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess log = model.sequences.logs.fastaccess flu = model.sequences.fluxes.fastaccess flu.dailyglobalradiation = 0.0 for idx in range(der.nmblogentries): flu.dailyglobalradiation += log.loggedglobalradiation[idx] flu.dailyglobalradiation /= der.nmblogentries
[docs] class Calc_ET0_V1(modeltools.Method): """Calculate reference evapotranspiration after Turc-Wendling. Basic equation: :math:`ET0 = KE \\cdot \\frac{(8.64 \\cdot GlobalRadiation + 93 \\cdot KF) \\cdot (TKor+22)} {165 \\cdot (TKor+123) \\cdot (1 + 0.00019 \\cdot min(HNN, 600))}` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> ke(1.1) >>> kf(0.6) >>> hnn(200.0, 600.0, 1000.0) >>> inputs.globalradiation = 200.0 >>> fluxes.tkor = 15.0 >>> model.calc_et0_v1() >>> fluxes.et0 et0(3.07171, 2.86215, 2.86215) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.KE, lland_control.KF, lland_control.HNN, ) REQUIREDSEQUENCES = ( lland_inputs.GlobalRadiation, lland_fluxes.TKor, ) RESULTSEQUENCES = (lland_fluxes.ET0,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.et0[k] = con.ke[k] * ( ((8.64 * inp.globalradiation + 93.0 * con.kf[k]) * (flu.tkor[k] + 22.0)) / ( 165.0 * (flu.tkor[k] + 123.0) * (1.0 + 0.00019 * min(con.hnn[k], 600.0)) ) )
[docs] class Calc_ET0_WET0_V1(modeltools.Method): """Correct the given reference evapotranspiration and update the corresponding log sequence. Basic equation: :math:`ET0_{new} = WfET0 \\cdot KE \\cdot PET + (1-WfET0) \\cdot ET0_{old}` Example: Prepare four hydrological response units with different value combinations of parameters |KE| and |WfET0|: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> simulationstep("12h") >>> nhru(4) >>> ke(0.8, 1.2, 0.8, 1.2) >>> wfet0(2.0, 2.0, 0.2, 0.2) Note that the actual value of time dependend parameter |WfET0| is reduced due the difference between the given parameter and simulation time steps: >>> from hydpy import round_ >>> round_(wfet0.values) 1.0, 1.0, 0.1, 0.1 For the first two hydrological response units, the given |PET| value is modified by -0.4 mm and +0.4 mm, respectively. For the other two response units, which weight the "new" evapotranspiration value with 10 %, |ET0| does deviate from the old value of |WET0| by -0.04 mm and +0.04 mm only: >>> inputs.pet = 2.0 >>> logs.wet0 = 2.0 >>> model.calc_et0_wet0_v1() >>> fluxes.et0 et0(1.6, 2.4, 1.96, 2.04) >>> logs.wet0 wet0(1.6, 2.4, 1.96, 2.04) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.WfET0, lland_control.KE, ) REQUIREDSEQUENCES = (lland_inputs.PET,) UPDATEDSEQUENCES = (lland_logs.WET0,) RESULTSEQUENCES = (lland_fluxes.ET0,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess log = model.sequences.logs.fastaccess for k in range(con.nhru): flu.et0[k] = ( con.wfet0[k] * con.ke[k] * inp.pet + (1.0 - con.wfet0[k]) * log.wet0[0, k] ) log.wet0[0, k] = flu.et0[k]
[docs] class Calc_EvPo_V1(modeltools.Method): """Calculate the potential evapotranspiration for the relevant land use and month. Additional requirements: |Model.idx_sim| Basic equation: :math:`EvPo = FLn \\cdot ET0` Example: For clarity, this is more of a kind of an integration example. Parameter |FLn| both depends on time (the actual month) and space (the actual land use). Firstly, let us define a initialization time period spanning the transition from June to July: >>> from hydpy import pub >>> pub.timegrids = "30.06.2000", "02.07.2000", "1d" Secondly, assume that the considered subbasin is differenciated in two HRUs, one of primarily consisting of arable land and the other one of deciduous forests: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(2) >>> lnk(ACKER, LAUBW) Thirdly, set the |FLn| values, one for the relevant months and land use classes: >>> fln.acker_jun = 1.299 >>> fln.acker_jul = 1.304 >>> fln.laubw_jun = 1.350 >>> fln.laubw_jul = 1.365 Fourthly, the index array connecting the simulation time steps defined above and the month indexes (0...11) can be retrieved from the |pub| module. This can be done manually more conveniently via its update method: >>> derived.moy.update() >>> derived.moy moy(5, 6) Finally, the actual method (with its simple equation) is applied as usual: >>> fluxes.et0 = 2.0 >>> model.idx_sim = 0 >>> model.calc_evpo_v1() >>> fluxes.evpo evpo(2.598, 2.7) >>> model.idx_sim = 1 >>> model.calc_evpo_v1() >>> fluxes.evpo evpo(2.608, 2.73) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.FLn, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = (lland_fluxes.ET0,) RESULTSEQUENCES = (lland_fluxes.EvPo,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.evpo[k] = con.fln[con.lnk[k] - 1, der.moy[model.idx_sim]] * flu.et0[k]
[docs] class Calc_NBes_Inzp_V1(modeltools.Method): """Calculate stand precipitation and update the interception storage accordingly. Additional requirements: |Model.idx_sim| Basic equation: .. math:: NBes = \\begin{cases} PKor &|\\ Inzp = KInz \\\\ 0 &|\\ Inzp < KInz \\end{cases} Examples: Initialise five HRUs with different land usages: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(5) >>> lnk(SIED_D, FEUCHT, GLETS, FLUSS, SEE) Define |KInz| values for July the selected land usages directly: >>> derived.kinz.sied_d_jul = 2.0 >>> derived.kinz.feucht_jul = 1.0 >>> derived.kinz.glets_jul = 0.0 >>> derived.kinz.fluss_jul = 1.0 >>> derived.kinz.see_jul = 1.0 Now we prepare a |MOY| object, that assumes that the first, second, and third simulation time steps are in June, July, and August respectively (we make use of the value defined above for July, but setting the values of parameter |MOY| this way allows for a more rigorous testing of proper indexing): >>> derived.moy.shape = 3 >>> derived.moy = 5, 6, 7 >>> model.idx_sim = 1 The dense settlement (|SIED_D|), the wetland area (|FEUCHT|), and both water areas (|FLUSS| and |SEE|) start with a initial interception storage of 1/2 mm, the glacier (|GLETS|) and water areas (|FLUSS| and |SEE|) start with 0 mm. In the first example, actual precipition is 1 mm: >>> states.inzp = 0.5, 0.5, 0.0, 1.0, 1.0 >>> fluxes.nkor = 1.0 >>> model.calc_nbes_inzp_v1() >>> states.inzp inzp(1.5, 1.0, 0.0, 0.0, 0.0) >>> fluxes.nbes nbes(0.0, 0.5, 1.0, 0.0, 0.0) Only for the settled area, interception capacity is not exceeded, meaning no stand precipitation occurs. Note that it is common in define zero interception capacities for glacier areas, but not mandatory. Also note that the |KInz|, |Inzp| and |NKor| values given for both water areas are ignored completely, and |Inzp| and |NBes| are simply set to zero. If there is no precipitation, there is of course also no stand precipitation and interception storage remains unchanged: >>> states.inzp = 0.5, 0.5, 0.0, 0.0, 0.0 >>> fluxes.nkor = 0. >>> model.calc_nbes_inzp_v1() >>> states.inzp inzp(0.5, 0.5, 0.0, 0.0, 0.0) >>> fluxes.nbes nbes(0.0, 0.0, 0.0, 0.0, 0.0) Interception capacities change discontinuously between consecutive months. This can result in little stand precipitation events in periods without precipitation: >>> states.inzp = 1.0, 0.0, 0.0, 0.0, 0.0 >>> derived.kinz.sied_d_jul = 0.6 >>> fluxes.nkor = 0.0 >>> model.calc_nbes_inzp_v1() >>> states.inzp inzp(0.6, 0.0, 0.0, 0.0, 0.0) >>> fluxes.nbes nbes(0.4, 0.0, 0.0, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.KInz, ) REQUIREDSEQUENCES = (lland_fluxes.NKor,) UPDATEDSEQUENCES = (lland_states.Inzp,) RESULTSEQUENCES = (lland_fluxes.NBes,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.nbes[k] = 0.0 sta.inzp[k] = 0.0 else: flu.nbes[k] = max( flu.nkor[k] + sta.inzp[k] - der.kinz[con.lnk[k] - 1, der.moy[model.idx_sim]], 0.0, ) sta.inzp[k] += flu.nkor[k] - flu.nbes[k]
[docs] class Calc_SNRatio_V1(modeltools.Method): """Calculate the ratio of frozen to total precipitation according to :cite:t:`ref-LARSIM`. Basic equation: :math:`SNRatio = min\\left(max\\left(\\frac{(TGr-TSp/2-TKor)}{TSp}, 0\\right), 1\\right)` Examples: In the first example, the threshold temperature of seven hydrological response units is 0 °C and the temperature interval of mixed precipitation 2 °C: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(7) >>> tgr(1.0) >>> tsp(2.0) The value of |SNRatio| is zero above 0 °C and 1 below 2 °C. Between these temperature values |SNRatio| decreases linearly: >>> fluxes.nkor = 4.0 >>> fluxes.tkor = -1.0, 0.0, 0.5, 1.0, 1.5, 2.0, 3.0 >>> model.calc_snratio_v1() >>> aides.snratio snratio(1.0, 1.0, 0.75, 0.5, 0.25, 0.0, 0.0) Note the special case of a zero temperature interval. With the actual temperature being equal to the threshold temperature, the the value of |SNRatio| is zero: >>> tsp(0.0) >>> model.calc_snratio_v1() >>> aides.snratio snratio(1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.TGr, lland_control.TSp, ) REQUIREDSEQUENCES = (lland_fluxes.TKor,) RESULTSEQUENCES = (lland_aides.SNRatio,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): if flu.tkor[k] >= (con.tgr[k] + con.tsp[k] / 2.0): aid.snratio[k] = 0.0 elif flu.tkor[k] <= (con.tgr[k] - con.tsp[k] / 2.0): aid.snratio[k] = 1.0 else: aid.snratio[k] = ( (con.tgr[k] + con.tsp[k] / 2.0) - flu.tkor[k] ) / con.tsp[k]
[docs] class Calc_SBes_V1(modeltools.Method): """Calculate the frozen part of stand precipitation. Basic equation: :math:`SBes = SNRatio \\cdot NBes` Example: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(2) >>> fluxes.nbes = 10.0 >>> aides.snratio = 0.2, 0.8 >>> model.calc_sbes_v1() >>> fluxes.sbes sbes(2.0, 8.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_aides.SNRatio, lland_fluxes.NBes, ) RESULTSEQUENCES = (lland_fluxes.SBes,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): flu.sbes[k] = aid.snratio[k] * flu.nbes[k]
[docs] class Calc_SnowIntMax_V1(modeltools.Method): r"""Calculate the current capacity of the snow interception storage according to :cite:t:`ref-LARSIM`. Basic equation: .. math:: SnowIntMax = \bigg( P1SIMax + P2SIMax \cdot LAI \bigg) \cdot \begin{cases} 1 &|\ TKor \leq -3 \\ (5 + TKor ) / 2 &|\ -3 < TKor < -1 \\ 2 &|\ -1 \leq TKor \end{cases} Examples: >>> from hydpy import pub >>> pub.timegrids = "2000-04-29", "2000-05-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(8) >>> lnk(ACKER, LAUBW, LAUBW, LAUBW, LAUBW, LAUBW, MISCHW, NADELW) >>> lai.laubw_apr = 4.0 >>> lai.laubw_mai = 7.0 >>> lai.mischw_apr = 6.0 >>> lai.mischw_mai = 8.0 >>> lai.nadelw = 11.0 >>> p1simax(8.0) >>> p2simax(1.5) >>> derived.moy.update() >>> fluxes.tkor = 0.0, 0.0, -1.0, -2.0, -3.0, -4.0, -4.0, -4.0 >>> model.idx_sim = pub.timegrids.init["2000-04-30"] >>> model.calc_snowintmax_v1() >>> fluxes.snowintmax snowintmax(0.0, 28.0, 28.0, 21.0, 14.0, 14.0, 17.0, 24.5) >>> model.idx_sim = pub.timegrids.init["2000-05-01"] >>> model.calc_snowintmax_v1() >>> fluxes.snowintmax snowintmax(0.0, 37.0, 37.0, 27.75, 18.5, 18.5, 20.0, 24.5) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.LAI, lland_control.P1SIMax, lland_control.P2SIMax, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = (lland_fluxes.TKor,) RESULTSEQUENCES = (lland_fluxes.SnowIntMax,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess idx = der.moy[model.idx_sim] for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): d_lai = con.lai[con.lnk[k] - 1, idx] flu.snowintmax[k] = con.p1simax + con.p2simax * d_lai if flu.tkor[k] >= -1.0: flu.snowintmax[k] *= 2 elif flu.tkor[k] > -3.0: flu.snowintmax[k] *= 2.5 + 0.5 * flu.tkor[k] else: flu.snowintmax[k] = 0.0
[docs] class Calc_SnowIntRate_V1(modeltools.Method): r"""Calculate the ratio between the snow interception rate and precipitation intensity according to :cite:t:`ref-LARSIM`. Basic equation: :math:`SnowIntRate = min\big( P1SIRate + P2SIRate \cdot LAI + P3SIRate \cdot SInz, 1 \big))` Example: >>> from hydpy import pub >>> pub.timegrids = "2000-04-29", "2000-05-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(7) >>> lnk(ACKER, LAUBW, LAUBW, LAUBW, LAUBW, MISCHW, NADELW) >>> lai.laubw_apr = 4.0 >>> lai.laubw_mai = 7.0 >>> lai.mischw_apr = 6.0 >>> lai.mischw_mai = 8.0 >>> lai.nadelw = 11.0 >>> p1sirate(0.2) >>> p2sirate(0.02) >>> p3sirate(0.003) >>> derived.moy.update() >>> states.sinz = 500.0, 500.0, 50.0, 5.0, 0.0, 0.0, 0.0 >>> model.idx_sim = pub.timegrids.init["2000-04-30"] >>> model.calc_snowintrate_v1() >>> fluxes.snowintrate snowintrate(0.0, 1.0, 0.43, 0.295, 0.28, 0.32, 0.42) >>> model.idx_sim = pub.timegrids.init["2000-05-01"] >>> model.calc_snowintrate_v1() >>> fluxes.snowintrate snowintrate(0.0, 1.0, 0.49, 0.355, 0.34, 0.36, 0.42) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.LAI, lland_control.P1SIRate, lland_control.P2SIRate, lland_control.P3SIRate, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = (lland_states.SInz,) RESULTSEQUENCES = (lland_fluxes.SnowIntRate,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess idx = der.moy[model.idx_sim] for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): d_lai = con.lai[con.lnk[k] - 1, idx] flu.snowintrate[k] = min( con.p1sirate + con.p2sirate * d_lai + con.p3sirate * sta.sinz[k], 1.0, ) else: flu.snowintrate[k] = 0.0
[docs] class Calc_NBesInz_V1(modeltools.Method): r"""Calculate the total amount of stand precipitation reaching the snow interception storage. Basic equation (does this comply with `LARSIM`_?): :math:`NBesInz = min \big( SnowIntRate \cdot NBes, max (SnowIntMax - SInz, 0) \big)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(6) >>> lnk(ACKER, LAUBW, MISCHW, NADELW, NADELW, NADELW) >>> states.sinz = 0.0, 0.0, 5.0, 10.0, 15.0, 20.0 >>> fluxes.nbes = 20.0 >>> fluxes.snowintmax = 15.0 >>> fluxes.snowintrate = 0.5 >>> model.calc_nbesinz_v1() >>> fluxes.nbesinz nbesinz(0.0, 10.0, 10.0, 5.0, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = ( lland_fluxes.NBes, lland_fluxes.SnowIntMax, lland_fluxes.SnowIntRate, lland_states.SInz, ) RESULTSEQUENCES = (lland_fluxes.NBesInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): flu.nbesinz[k] = min( flu.snowintrate[k] * flu.nbes[k], max(flu.snowintmax[k] - sta.sinz[k], 0.0), ) else: flu.nbesinz[k] = 0.0
[docs] class Calc_SBesInz_V1(modeltools.Method): r"""Calculate the frozen amount of stand precipitation reaching the snow interception storage. Basic equations (does this comply with `LARSIM`_?): :math:`SBesInz = SNRatio \cdot NBesInz` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(ACKER, LAUBW, MISCHW, NADELW) >>> fluxes.nbesinz = 10.0, 10.0, 5.0, 2.5 >>> aides.snratio = 0.8 >>> model.calc_sbesinz_v1() >>> fluxes.sbesinz sbesinz(0.0, 8.0, 4.0, 2.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = ( lland_fluxes.NBesInz, lland_aides.SNRatio, ) RESULTSEQUENCES = (lland_fluxes.SBesInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): flu.sbesinz[k] = aid.snratio[k] * flu.nbesinz[k] else: flu.sbesinz[k] = 0.0
[docs] class Calc_STInz_V1(modeltools.Method): r"""Add the relevant fraction of the frozen amount of stand precipitation to the frozen water equivalent of the interception snow storage. Basic equation: :math:`\frac{STInz}{dt} = SBesInz` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(ACKER, LAUBW, MISCHW, NADELW) >>> states.stinz = 0.0, 1.0, 2.0, 3.0 >>> fluxes.sbesinz = 1.0 >>> model.calc_stinz_v1() >>> states.stinz stinz(0.0, 2.0, 3.0, 4.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = (lland_fluxes.SBesInz,) UPDATEDSEQUENCES = (lland_states.STInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): sta.stinz[k] += flu.sbesinz[k] else: sta.stinz[k] = 0.0
[docs] class Calc_WaDaInz_SInz_V1(modeltools.Method): r"""Add as much liquid precipitation to the snow interception storage as it is able to hold. Basic equations: :math:`\frac{dSInz}{dt} = NBesInz - WaDaInz` :math:`SInz \leq PWMax \cdot STInz` Example: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(5) >>> lnk(ACKER, LAUBW, MISCHW, NADELW, NADELW) >>> pwmax(2.0) >>> fluxes.nbesinz = 1.0 >>> states.stinz = 0.0, 0.0, 1.0, 1.0, 1.0 >>> states.sinz = 1.0, 0.0, 1.0, 1.5, 2.0 >>> model.calc_wadainz_sinz_v1() >>> states.sinz sinz(0.0, 0.0, 2.0, 2.0, 2.0) >>> fluxes.wadainz wadainz(0.0, 1.0, 0.0, 0.5, 1.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.PWMax, ) REQUIREDSEQUENCES = ( lland_fluxes.NBesInz, lland_states.STInz, ) UPDATEDSEQUENCES = (lland_states.SInz,) RESULTSEQUENCES = (lland_fluxes.WaDaInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): sta.sinz[k] += flu.nbesinz[k] flu.wadainz[k] = max(sta.sinz[k] - con.pwmax[k] * sta.stinz[k], 0.0) sta.sinz[k] -= flu.wadainz[k] else: flu.wadainz[k] = 0.0 sta.sinz[k] = 0.0
[docs] class Calc_WNiedInz_ESnowInz_V1(modeltools.Method): r"""Calculate the heat flux into the snow interception storage due to the amount of precipitation actually hold by the snow layer. Basic equation: :math:`\frac{dESnowInz}{dt} = W\!NiedInz` :math:`W\!NiedInz = (TKor-TRefN) \cdot (CPEis \cdot SBesInz + CPWasser \cdot (NBesInz - SBesInz - WaDaInz))` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(6) >>> lnk(LAUBW, MISCHW, NADELW, NADELW, NADELW, ACKER) >>> trefn(1.0) >>> states.esnowinz = 0.5 >>> fluxes.tkor = 4.0, 0.0, 0.0, 0.0, 0.0, 0.0 >>> fluxes.nbesinz = 10.0 >>> fluxes.sbesinz = 0.0, 0.0, 5.0, 10.0, 5.0, 5.0 >>> fluxes.wadainz = 0.0, 0.0, 0.0, 0.0, 5.0, 5.0 >>> model.calc_wniedinz_esnowinz_v1() >>> fluxes.wniedinz wniedinz(2.9075, -0.969167, -0.726481, -0.483796, -0.241898, 0.0) >>> states.esnowinz esnowinz(3.4075, -0.469167, -0.226481, 0.016204, 0.258102, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.TRefN, ) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_fluxes.TKor, lland_fluxes.NBesInz, lland_fluxes.SBesInz, lland_fluxes.WaDaInz, ) UPDATEDSEQUENCES = (lland_states.ESnowInz,) RESULTSEQUENCES = (lland_fluxes.WNiedInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): d_ice = fix.cpeis * flu.sbesinz[k] d_water = fix.cpwasser * ( flu.nbesinz[k] - flu.sbesinz[k] - flu.wadainz[k] ) flu.wniedinz[k] = (flu.tkor[k] - con.trefn[k]) * (d_ice + d_water) sta.esnowinz[k] += flu.wniedinz[k] else: flu.wniedinz[k] = 0.0 sta.esnowinz[k] = 0.0
[docs] class Return_TempSInz_V1(modeltools.Method): r"""Calculate and return the average temperature of the intercepted snow. Basic equation: :math:`max \left( \frac{ESnowInz}{STInz \cdot CPEis + (SInz - STInz) \cdot CPWasser}, -273 \right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(5) >>> states.stinz = 0.0, 0.5, 5.0, 0.5, 0.5 >>> states.sinz = 0.0, 1.0, 10.0, 1.0, 1.0 >>> states.esnowinz = 0.0, 0.0, -1.0, -10.0, -100.0 >>> from hydpy import round_ >>> round_(model.return_tempsinz_v1(0)) nan >>> round_(model.return_tempsinz_v1(1)) 0.0 >>> round_(model.return_tempsinz_v1(2)) -1.376498 >>> round_(model.return_tempsinz_v1(3)) -137.649758 >>> round_(model.return_tempsinz_v1(4)) -273.0 """ FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_states.STInz, lland_states.SInz, lland_states.ESnowInz, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: fix = model.parameters.fixed.fastaccess sta = model.sequences.states.fastaccess if sta.sinz[k] > 0.0: d_ice = fix.cpeis * sta.stinz[k] d_water = fix.cpwasser * (sta.sinz[k] - sta.stinz[k]) return max(sta.esnowinz[k] / (d_ice + d_water), -273.0) return modelutils.nan
[docs] class Calc_TempSInz_V1(modeltools.Method): """Calculate the average temperature of the intercepted snow. Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep() >>> nhru(6) >>> lnk(ACKER, LAUBW, MISCHW, NADELW, NADELW, NADELW) >>> pwmax(2.0) >>> fluxes.tkor = -1.0 >>> states.stinz = 0.0, 0.5, 5.0, 5.0, 5.0, 5.0 >>> states.sinz = 0.0, 1.0, 10.0, 10.0, 10.0, 10.0 >>> states.esnowinz = 0.0, 0.0, -2.0, -2.0, -2.0, -2.0 >>> model.calc_tempsinz_v1() >>> aides.tempsinz tempsinz(nan, 0.0, -2.752995, -2.752995, -2.752995, -2.752995) """ SUBMETHODS = (Return_TempSInz_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_states.STInz, lland_states.SInz, lland_states.ESnowInz, ) RESULTSEQUENCES = (lland_aides.TempSInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): aid.tempsinz[k] = model.return_tempsinz_v1(k)
[docs] class Update_ASInz_V1(modeltools.Method): r"""Calculate the dimensionless age of the intercepted snow. Basic equations: :math:`TauSInz_{new} = TauSInz_{old} \cdot max(1 - 0.1 \cdot SBesInz), 0) + \frac{r_1+r_2+r_3}{10^6} \cdot Seconds` :math:`r_1 = exp \left( 5000 \cdot \left( \frac{1}{273.15} - \frac{1}{273.15 + TempSInz} \right) \right)` :math:`r_2 = min \left(r_1^{10}, 1 \right)` :math:`r_3 = 0.03` Example: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(10) >>> derived.seconds(24*60*60) >>> fluxes.sbesinz = 0.0, 1.0, 5.0, 9.0, 10.0, 11.0, 11.0, 11.0, 11.0, 11.0 >>> states.sinz = 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 >>> states.asinz = 1.0, nan, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 >>> aides.tempsinz = 0.0, 0.0, -10.0, -10.0, -10.0, -10.0, -1.0, 0.0, 1.0, 10.0 >>> model.update_asinz_v1() >>> states.asinz asinz(nan, 0.175392, 0.545768, 0.145768, 0.045768, 0.045768, 0.127468, 0.175392, 0.181358, 0.253912) """ CONTROLPARAMETERS = (lland_control.NHRU,) DERIVEDPARAMETERS = (lland_derived.Seconds,) REQUIREDSEQUENCES = ( lland_fluxes.SBesInz, lland_states.SInz, lland_aides.TempSInz, ) RESULTSEQUENCES = (lland_states.ASInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): if sta.sinz[k] > 0: if modelutils.isnan(sta.asinz[k]): sta.asinz[k] = 0.0 d_r1 = modelutils.exp( 5000.0 * (1 / 273.15 - 1.0 / (273.15 + aid.tempsinz[k])) ) d_r2 = min(d_r1**10, 1.0) sta.asinz[k] *= max(1 - 0.1 * flu.sbesinz[k], 0.0) sta.asinz[k] += (d_r1 + d_r2 + 0.03) / 1e6 * der.seconds else: sta.asinz[k] = modelutils.nan
[docs] class Calc_ActualAlbedoInz_V1(modeltools.Method): r"""Calculate the current albedo of the intercepted snow. Basic equation: :math:`AlbedoSnowInz = Albedo0Snow \cdot \left( 1 - SnowAgingFactor \cdot \frac{ASInz}{1 + ASInz} \right)` Examples: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(5) >>> albedo0snow(0.8) >>> snowagingfactor(0.35) >>> states.asinz = nan, nan, 0.0, 1.0, 3.0 >>> states.sinz = 0.0, 0.0, 1.0, 1.0, 1.0 >>> model.calc_actualalbedoinz_v1() >>> fluxes.actualalbedoinz actualalbedoinz(nan, nan, 0.8, 0.66, 0.59) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.SnowAgingFactor, lland_control.Albedo0Snow, ) REQUIREDSEQUENCES = ( lland_states.ASInz, lland_states.SInz, ) RESULTSEQUENCES = (lland_fluxes.ActualAlbedoInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if sta.sinz[k] > 0.0: flu.actualalbedoinz[k] = con.albedo0snow * ( 1.0 - con.snowagingfactor * sta.asinz[k] / (1.0 + sta.asinz[k]) ) else: flu.actualalbedoinz[k] = modelutils.nan
[docs] class Calc_NetShortwaveRadiationInz_V1(modeltools.Method): r"""Calculate the net shortwave radiation for the intercepted snow. Basic equation: :math:`NetShortwaveRadiationInz = (1 - Fr) \cdot (1.0 - ActualAlbedoInz) \cdot AdjustedGlobalRadiation` Examples: >>> from hydpy import pub >>> pub.timegrids = "2000-01-30", "2000-02-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> lnk(ACKER, LAUBW) >>> derived.fr.acker_jan = 1.0 >>> derived.fr.acker_feb = 1.0 >>> derived.fr.laubw_jan = 0.5 >>> derived.fr.laubw_feb = 0.1 >>> derived.moy.update() >>> inputs.globalradiation = 40.0 >>> fluxes.actualalbedoinz = 0.5 >>> model.idx_sim = pub.timegrids.init["2000-01-31"] >>> model.calc_netshortwaveradiationinz_v1() >>> fluxes.netshortwaveradiationinz netshortwaveradiationinz(0.0, 10.0) >>> model.idx_sim = pub.timegrids.init["2000-02-01"] >>> model.calc_netshortwaveradiationinz_v1() >>> fluxes.netshortwaveradiationinz netshortwaveradiationinz(0.0, 18.0) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) DERIVEDPARAMETERS = ( lland_derived.Fr, lland_derived.MOY, ) REQUIREDSEQUENCES = ( lland_inputs.GlobalRadiation, lland_fluxes.ActualAlbedoInz, ) RESULTSEQUENCES = (lland_fluxes.NetShortwaveRadiationInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): flu.netshortwaveradiationinz[k] = ( (1.0 - der.fr[con.lnk[k] - 1, der.moy[model.idx_sim]]) * (1.0 - flu.actualalbedoinz[k]) * inp.globalradiation ) else: flu.netshortwaveradiationinz[k] = 0.0
[docs] class Calc_SchmPotInz_V1(modeltools.Method): r"""Calculate the potential melt of the intercepted snow according to its heat content. Basic equation: :math:`SchmPotInz = max \left( \frac{ESnowInz}{RSchmelz}, 0 \right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(4) >>> states.sinz = 0.0, 1.0, 1.0, 1.0 >>> states.esnowinz = nan, 10.0, 5.0, -5.0 >>> model.calc_schmpotinz_v1() >>> fluxes.schmpotinz schmpotinz(0.0, 1.293413, 0.646707, 0.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = ( lland_states.ESnowInz, lland_states.SInz, ) RESULTSEQUENCES = (lland_fluxes.SchmPotInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if sta.sinz[k] > 0.0: flu.schmpotinz[k] = max(sta.esnowinz[k] / fix.rschmelz, 0.0) else: flu.schmpotinz[k] = 0.0
[docs] class Calc_SchmInz_STInz_V1(modeltools.Method): r"""Calculate the actual amount of snow melting within the snow interception storage. Basic equations: :math:`\frac{dSTInz}{dt} = -SchmInz` .. math:: SchmInz = \begin{cases} SchmPotInz &|\ STInz> 0 \\ 0 &|\ STInz = 0 \end{cases} Examples: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(6) >>> lnk(ACKER, LAUBW, MISCHW, NADELW, NADELW, NADELW) >>> states.stinz = 0.0, 0.0, 2.0, 2.0, 2.0, 2.0 >>> fluxes.schmpotinz = 1.0, 1.0, 0.0, 1.0, 3.0, 5.0 >>> model.calc_schminz_stinz_v1() >>> states.stinz stinz(0.0, 0.0, 2.0, 1.0, 0.0, 0.0) >>> fluxes.schminz schminz(0.0, 0.0, 0.0, 1.0, 2.0, 2.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = (lland_fluxes.SchmPotInz,) RESULTSEQUENCES = (lland_fluxes.SchmInz,) UPDATEDSEQUENCES = (lland_states.STInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): flu.schminz[k] = min(flu.schmpotinz[k], sta.stinz[k]) sta.stinz[k] -= flu.schminz[k] else: flu.schminz[k] = 0.0
[docs] class Calc_GefrPotInz_V1(modeltools.Method): r"""Calculate the potential refreezing within the snow interception storage according to its thermal energy. Basic equation: :math:`GefrPotInz = max \left( -\frac{ESnowInz}{RSchmelz}, 0 \right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(4) >>> states.sinz = 0.0, 1.0, 1.0, 1.0 >>> states.esnowinz = nan, -10.0, -5.0, 5.0 >>> model.calc_gefrpotinz_v1() >>> fluxes.gefrpotinz gefrpotinz(0.0, 1.293413, 0.646707, 0.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = ( lland_states.ESnowInz, lland_states.SInz, ) RESULTSEQUENCES = (lland_fluxes.GefrPotInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if sta.sinz[k] > 0: flu.gefrpotinz[k] = max(-sta.esnowinz[k] / fix.rschmelz, 0) else: flu.gefrpotinz[k] = 0.0
[docs] class Calc_GefrInz_STInz_V1(modeltools.Method): r"""Calculate the actual amount of snow melting within the snow interception storage. Basic equations: :math:`\frac{dGefrInz}{dt} = -GefrInz` .. math:: GefrInz = \begin{cases} GefrPotInz &|\ SInz - STInz > 0 \\ 0 &|\ SInz - STInz = 0 \end{cases} Examples: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(6) >>> lnk(ACKER, LAUBW, LAUBW, LAUBW, MISCHW, NADELW) >>> refreezeflag(True) >>> states.stinz = 0.0, 0.0, 2.0, 2.0, 2.0, 2.0 >>> states.sinz = 0.0, 0.0, 4.0, 4.0, 4.0, 4.0 >>> fluxes.gefrpotinz = 1.0, 1.0, 0.0, 1.0, 3.0, 5.0 >>> model.calc_gefrinz_stinz_v1() >>> states.stinz stinz(0.0, 0.0, 2.0, 3.0, 4.0, 4.0) >>> fluxes.gefrinz gefrinz(0.0, 0.0, 0.0, 1.0, 2.0, 2.0) >>> refreezeflag(False) >>> model.calc_gefrinz_stinz_v1() >>> states.stinz stinz(0.0, 0.0, 2.0, 3.0, 4.0, 4.0) >>> fluxes.gefrinz gefrinz(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.RefreezeFlag, ) REQUIREDSEQUENCES = ( lland_fluxes.GefrPotInz, lland_states.SInz, ) RESULTSEQUENCES = (lland_fluxes.GefrInz,) UPDATEDSEQUENCES = (lland_states.STInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW) and con.refreezeflag: flu.gefrinz[k] = min(flu.gefrpotinz[k], (sta.sinz[k] - sta.stinz[k])) sta.stinz[k] += flu.gefrinz[k] else: flu.gefrinz[k] = 0.0
[docs] class Calc_EvSInz_SInz_STInz_V1(modeltools.Method): r"""Calculate the evaporation from the snow interception storage, if any exists, and its content accordingly. Basic equations: :math:`SInz_{new} = f \cdot SInz_{old}` :math:`STInz_{new} = f \cdot STInz_{old}` :math:`f = \frac{SInz - EvSInz}{SInz}` :math:`EvSInz = max \left( \frac{WLatInz}{LWE}, SInz \right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(8) >>> lnk(LAUBW, MISCHW, NADELW, NADELW, NADELW, NADELW, NADELW, ACKER) >>> fluxes.wlatinz = -20.0, 0.0, 50.0, 100.0, 150.0, 150.0, 150.0, 150.0 >>> states.sinz = 2.0, 2.0, 2.0, 2.0, 2.0, 1.5, 0.0, 2.0 >>> states.stinz = 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.0, 1.0 >>> model.calc_evsinz_sinz_stinz_v1() >>> fluxes.evsinz evsinz(-0.323905, 0.0, 0.809762, 1.619524, 2.0, 1.5, 0.0, 0.0) >>> states.sinz sinz(2.323905, 2.0, 1.190238, 0.380476, 0.0, 0.0, 0.0, 0.0) >>> states.stinz stinz(1.161952, 1.0, 0.595119, 0.190238, 0.0, 0.0, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) FIXEDPARAMETERS = (lland_fixed.LWE,) REQUIREDSEQUENCES = (lland_fluxes.WLatInz,) RESULTSEQUENCES = (lland_fluxes.EvSInz,) UPDATEDSEQUENCES = ( lland_states.SInz, lland_states.STInz, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW) and (sta.sinz[k] > 0.0): flu.evsinz[k] = min(flu.wlatinz[k] / fix.lwe, sta.sinz[k]) d_frac = (sta.sinz[k] - flu.evsinz[k]) / sta.sinz[k] sta.sinz[k] *= d_frac sta.stinz[k] *= d_frac else: flu.evsinz[k] = 0.0 sta.sinz[k] = 0.0 sta.stinz[k] = 0.0
[docs] class Update_WaDaInz_SInz_V1(modeltools.Method): r"""Update the actual water release from and the liquid water content of the snow interception storage due to melting. Basic equations: :math:`WaDaInz_{new} = WaDaInz_{old} + \Delta` :math:`SInz_{new} = SInz_{old} - \Delta` :math:`\Delta = max(SInz - PWMax \cdot STInz, 0)` Examples: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(5) >>> lnk(ACKER, LAUBW, LAUBW, MISCHW, NADELW) >>> pwmax(2.0) >>> states.stinz = 0.0, 0.0, 1.0, 1.0, 1.0 >>> states.sinz = 1.0, 0.0, 1.0, 2.0, 3.0 >>> fluxes.wadainz = 1.0 >>> model.update_wadainz_sinz_v1() >>> states.sinz sinz(1.0, 0.0, 1.0, 2.0, 2.0) >>> fluxes.wadainz wadainz(1.0, 1.0, 1.0, 1.0, 2.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.PWMax, ) REQUIREDSEQUENCES = (lland_states.STInz,) UPDATEDSEQUENCES = ( lland_states.SInz, lland_fluxes.WaDaInz, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW): d_wadainz_corr = max(sta.sinz[k] - con.pwmax[k] * sta.stinz[k], 0.0) flu.wadainz[k] += d_wadainz_corr sta.sinz[k] -= d_wadainz_corr
[docs] class Update_ESnowInz_V2(modeltools.Method): r"""Update the thermal energy content of the intercepted snow regarding snow melt and refreezing. Basic equation: :math:`\frac{dESnowInz}{dt} = RSchmelz \cdot (GefrInz - SchmInz)` Examples: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(5) >>> lnk(ACKER, NADELW, NADELW, NADELW, NADELW) >>> fluxes.gefrinz = 0.0, 0.0, 4.0, 0.0, 4.0 >>> fluxes.schminz = 0.0, 0.0, 0.0, 4.0, 4.0 >>> states.esnowinz = 20.0, 20.0, -40.0, 30.925926, 0.0 >>> states.sinz = 1.0, 0.0, 5.0, 5.0, 10.0 >>> model.update_esnowinz_v2() >>> states.esnowinz esnowinz(0.0, 0.0, -9.074074, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = ( lland_fluxes.GefrInz, lland_fluxes.SchmInz, lland_states.SInz, ) UPDATEDSEQUENCES = (lland_states.ESnowInz,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess sta = model.sequences.states.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (LAUBW, MISCHW, NADELW) and sta.sinz[k] > 0.0: sta.esnowinz[k] += fix.rschmelz * (flu.gefrinz[k] - flu.schminz[k]) else: sta.esnowinz[k] = 0.0
[docs] class Calc_WATS_V1(modeltools.Method): """Add the snow fall to the frozen water equivalent of the snow cover. Basic equation: :math:`\\frac{dW\\!ATS}{dt} = SBes` Example: On water surfaces, method |Calc_WATS_V1| never builds up a snow layer: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(WASSER, FLUSS, SEE, ACKER) >>> fluxes.sbes = 2.0 >>> states.wats = 5.5 >>> model.calc_wats_v1() >>> states.wats wats(0.0, 0.0, 0.0, 7.5) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = (lland_fluxes.SBes,) UPDATEDSEQUENCES = (lland_states.WATS,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): sta.wats[k] = 0.0 else: sta.wats[k] += flu.sbes[k]
[docs] class Calc_WATS_V2(modeltools.Method): r"""Add the not intercepted snow fall to the frozen water equivalent of the snow cover. Basic equation: :math:`\frac{dW\!\!ATS}{dt} = SBes - SBesInz` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> lnk(WASSER, ACKER, LAUBW) >>> states.wats = 0.0, 1.0, 2.0 >>> fluxes.sbes = 2.0 >>> fluxes.sbesinz = 1.0 >>> model.calc_wats_v2() >>> states.wats wats(0.0, 3.0, 3.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = ( lland_fluxes.SBes, lland_fluxes.SBesInz, ) UPDATEDSEQUENCES = (lland_states.WATS,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): sta.wats[k] = 0.0 elif con.lnk[k] in (LAUBW, MISCHW, NADELW): sta.wats[k] += flu.sbes[k] - flu.sbesinz[k] else: sta.wats[k] += flu.sbes[k]
[docs] class Calc_WaDa_WAeS_V1(modeltools.Method): """Add as much liquid precipitation to the snow cover as it is able to hold. Basic equations: :math:`\\frac{dW\\!AeS}{dt} = NBes - WaDa` :math:`W\\!AeS \\leq PWMax \\cdot W\\!ATS` Example: For simplicity, we set the threshold parameter |PWMax| to a value of two for each of the six initialised hydrological response units. Thus, the snow layer can hold as much liquid water as it contains frozen water. Stand precipitation is also always set to the same value, but we vary the initial conditions of the snow cover: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(6) >>> lnk(FLUSS, SEE, ACKER, ACKER, ACKER, ACKER) >>> pwmax(2.0) >>> fluxes.nbes = 1.0 >>> states.wats = 0.0, 0.0, 0.0, 1.0, 1.0, 1.0 >>> states.waes = 1.0, 1.0, 0.0, 1.0, 1.5, 2.0 >>> model.calc_wada_waes_v1() >>> states.waes waes(0.0, 0.0, 0.0, 2.0, 2.0, 2.0) >>> fluxes.wada wada(1.0, 1.0, 1.0, 0.0, 0.5, 1.0) Note the special cases of the first two response units of type |FLUSS| and |SEE|. For water areas, stand precipitaton |NBes| is generally passed to |WaDa| and |WAeS| is set to zero. For all other land use classes (of which only |ACKER| is selected), method |Calc_WaDa_WAeS_V1| only passes the part of |NBes| exceeding the actual snow holding capacity to |WaDa|. """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.PWMax, ) REQUIREDSEQUENCES = ( lland_fluxes.NBes, lland_states.WATS, ) UPDATEDSEQUENCES = (lland_states.WAeS,) RESULTSEQUENCES = (lland_fluxes.WaDa,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): sta.waes[k] = 0.0 flu.wada[k] = flu.nbes[k] else: sta.waes[k] += flu.nbes[k] flu.wada[k] = max(sta.waes[k] - con.pwmax[k] * sta.wats[k], 0.0) sta.waes[k] -= flu.wada[k]
[docs] class Calc_WaDa_WAeS_V2(modeltools.Method): r"""Add as much liquid precipitation and interception melt to the snow cover as it is able to hold. Basic equations: :math:`\frac{dW\!\!AeS}{dt} = NBes - NBesInz + WaDaInz - WaDa` :math:`W\!\!AeS \leq PWMax \cdot W\!\!AT\!S` Example: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(10) >>> lnk(FLUSS, SEE, ACKER, ACKER, ACKER, ACKER, LAUBW, LAUBW, LAUBW, LAUBW) >>> pwmax(2.0) >>> fluxes.nbes = 1.5 >>> fluxes.nbesinz = 1.0 >>> fluxes.wadainz = 0.5 >>> states.wats = 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0 >>> states.waes = 1.0, 1.0, 0.0, 1.0, 1.5, 2.0, 0.0, 1.0, 1.5, 2.0 >>> model.calc_wada_waes_v2() >>> states.waes waes(0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 0.0, 2.0, 2.0, 2.0) >>> fluxes.wada wada(1.5, 1.5, 1.5, 0.5, 1.0, 1.5, 1.0, 0.0, 0.5, 1.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.PWMax, ) REQUIREDSEQUENCES = ( lland_fluxes.NBes, lland_fluxes.NBesInz, lland_fluxes.WaDaInz, lland_states.WATS, ) UPDATEDSEQUENCES = (lland_states.WAeS,) RESULTSEQUENCES = (lland_fluxes.WaDa,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): sta.waes[k] = 0.0 flu.wada[k] = flu.nbes[k] if con.lnk[k] in (LAUBW, MISCHW, NADELW): sta.waes[k] += flu.nbes[k] - flu.nbesinz[k] + flu.wadainz[k] flu.wada[k] = max(sta.waes[k] - con.pwmax[k] * sta.wats[k], 0.0) sta.waes[k] -= flu.wada[k] else: sta.waes[k] += flu.nbes[k] flu.wada[k] = max(sta.waes[k] - con.pwmax[k] * sta.wats[k], 0.0) sta.waes[k] -= flu.wada[k]
[docs] class Calc_WGTF_V1(modeltools.Method): """Calculate the heat flux according to the degree-day method according to :cite:t:`ref-LARSIM`. Basic equation: :math:`WGTF = GTF \\cdot (TKor - TRefT) \\cdot RSchmelz` Examples: Initialise six hydrological response units with identical degree-day factors and temperature thresholds, but different combinations of land use and air temperature: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(6) >>> lnk(FLUSS, SEE, LAUBW, ACKER, ACKER, LAUBW) >>> gtf(5.0) >>> treft(0.0) >>> trefn(1.0) >>> fluxes.tkor = 1.0, 1.0, 1.0, 1.0, 0.0, -1.0 Note that the values of the degree-day factor are only half as much as the given value, due to the simulation step size being only half as long as the parameter step size: >>> gtf gtf(5.0) >>> from hydpy import print_values >>> print_values(gtf.values) 2.5, 2.5, 2.5, 2.5, 2.5, 2.5 The results of the first four hydrological response units show that |WGTF| is generally zero for water areas (here, |FLUSS| and |SEE|) in principally identical for all other landuse-use type (here, |ACKER| and |LAUBW|). The results of the last three response units show the expectable linear relationship. However, note that that |WGTF| is allowed to be negative: >>> model.calc_wgtf_v1() >>> fluxes.wgtf wgtf(0.0, 0.0, 19.328704, 19.328704, 0.0, -19.328704) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.GTF, lland_control.TRefT, ) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = (lland_fluxes.TKor,) RESULTSEQUENCES = (lland_fluxes.WGTF,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.wgtf[k] = 0.0 else: flu.wgtf[k] = con.gtf[k] * (flu.tkor[k] - con.treft[k]) * fix.rschmelz
[docs] class Calc_WNied_V1(modeltools.Method): """Calculate the heat flux into the snow layer due to the total amount of ingoing precipitation (:cite:t:`ref-LARSIM`, modified). Method |Calc_WNied_V1| assumes that the temperature of precipitation equals air temperature minus |TRefN|. Basic equation: :math:`WNied = (TKor - TRefN) \\cdot (CPEis \\cdot SBes + CPWasser \\cdot (NBes - SBes))` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(5) >>> lnk(ACKER, ACKER, ACKER, ACKER, WASSER) >>> trefn(-2.0, 2.0, 2.0, 2.0, 2.0) >>> fluxes.tkor(1.0) >>> fluxes.nbes = 10.0 >>> fluxes.sbes = 0.0, 0.0, 5.0, 10.0, 10.0 >>> model.calc_wnied_v1() >>> fluxes.wnied wnied(2.9075, -0.969167, -0.726481, -0.483796, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.TRefN, ) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_fluxes.TKor, lland_fluxes.NBes, lland_fluxes.SBes, ) RESULTSEQUENCES = (lland_fluxes.WNied,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.wnied[k] = 0.0 else: d_ice = fix.cpeis * flu.sbes[k] d_water = fix.cpwasser * (flu.nbes[k] - flu.sbes[k]) flu.wnied[k] = (flu.tkor[k] - con.trefn[k]) * (d_ice + d_water)
[docs] class Calc_WNied_ESnow_V1(modeltools.Method): """Calculate the heat flux into the snow layer due to the amount of precipitation actually hold by the snow layer. Method |Calc_WNied_V1| assumes that the temperature of precipitation equals air temperature minus |TRefN|. The same holds for the temperature of the fraction of precipitation not hold by the snow layer (in other words: no temperature mixing occurs). Basic equation: :math:`\\frac{dESnow}{dt} = WNied` :math:`WNied = (TKor-TRefN) \\cdot (CPEis \\cdot SBes + CPWasser \\cdot (NBes - SBes - WaDa))` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(6) >>> lnk(ACKER, ACKER, ACKER, ACKER, ACKER, WASSER) >>> trefn(1.0) >>> states.esnow = 0.5 >>> fluxes.tkor = 4.0, 0.0, 0.0, 0.0, 0.0, 0.0 >>> fluxes.nbes = 10.0 >>> fluxes.sbes = 0.0, 0.0, 5.0, 10.0, 5.0, 5.0 >>> fluxes.wada = 0.0, 0.0, 0.0, 0.0, 5.0, 5.0 >>> model.calc_wnied_esnow_v1() >>> fluxes.wnied wnied(2.9075, -0.969167, -0.726481, -0.483796, -0.241898, 0.0) >>> states.esnow esnow(3.4075, -0.469167, -0.226481, 0.016204, 0.258102, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.TRefN, ) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_fluxes.TKor, lland_fluxes.NBes, lland_fluxes.SBes, lland_fluxes.WaDa, ) UPDATEDSEQUENCES = (lland_states.ESnow,) RESULTSEQUENCES = (lland_fluxes.WNied,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.wnied[k] = 0.0 sta.esnow[k] = 0.0 else: d_ice = fix.cpeis * flu.sbes[k] d_water = fix.cpwasser * (flu.nbes[k] - flu.sbes[k] - flu.wada[k]) flu.wnied[k] = (flu.tkor[k] - con.trefn[k]) * (d_ice + d_water) sta.esnow[k] += flu.wnied[k]
[docs] class Return_SaturationVapourPressure_V1(modeltools.Method): r"""Calculate and return the saturation vapour pressure over an arbitrary surface for the given temperature according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Weischet1983`). Basic equation: :math:`6.1078 \cdot 2.71828^{\frac{17.08085 \cdot temperature}{temperature + 234.175}}` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> from hydpy import round_ >>> round_(model.return_saturationvapourpressure_v1(10.0)) 12.293852 """ @staticmethod def __call__( model: modeltools.Model, temperature: float, ) -> float: return 6.1078 * 2.71828 ** (17.08085 * temperature / (temperature + 234.175))
[docs] class Return_SaturationVapourPressureSlope_V1(modeltools.Method): r"""Calculate and return the saturation vapour pressure slope over an arbitrary surface for the given temperature. Basic equation (derivative of |Return_SaturationVapourPressure_V1|: :math:`\frac{24430.6 \cdot exp((17.08085 \cdot temperature) / (temperature + 234.175)} {(temperature+234.175)^2}` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> from hydpy import round_ >>> round_(model.return_saturationvapourpressureslope_v1(10.0)) 0.824774 >>> dx = 1e-6 >>> round_((model.return_saturationvapourpressure_v1(10.0+dx) - ... model.return_saturationvapourpressure_v1(10.0-dx))/2/dx) 0.824775 """ @staticmethod def __call__( model: modeltools.Model, temperature: float, ) -> float: return ( 24430.6 * modelutils.exp(17.08085 * temperature / (temperature + 234.175)) / (temperature + 234.175) ** 2 )
[docs] class Calc_SaturationVapourPressure_V1(modeltools.Method): """Calculate the saturation vapour pressure over arbitrary surfaces. Basic equation: :math:`SaturationVapourPressure = Return\\_SaturationVapourPressure\\_V1(TKor)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> fluxes.tkor = -10.0, 0.0, 10.0 >>> model.calc_saturationvapourpressure_v1() >>> fluxes.saturationvapourpressure saturationvapourpressure(2.850871, 6.1078, 12.293852) """ SUBMETHODS = (Return_SaturationVapourPressure_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = (lland_fluxes.TKor,) RESULTSEQUENCES = (lland_fluxes.SaturationVapourPressure,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.saturationvapourpressure[k] = model.return_saturationvapourpressure_v1( flu.tkor[k] )
[docs] class Calc_DailySaturationVapourPressure_V1(modeltools.Method): """Calculate the daily saturation vapour pressure over arbitrary surfaces. Basic equation: :math:`DailySaturationVapourPressure = Return\\_SaturationVapourPressure\\_V1(TKorTag)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> fluxes.tkortag = -10.0, 0.0, 10.0 >>> model.calc_dailysaturationvapourpressure_v1() >>> fluxes.dailysaturationvapourpressure dailysaturationvapourpressure(2.850871, 6.1078, 12.293852) """ SUBMETHODS = (Return_SaturationVapourPressure_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = (lland_fluxes.TKorTag,) RESULTSEQUENCES = (lland_fluxes.DailySaturationVapourPressure,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.dailysaturationvapourpressure[ k ] = model.return_saturationvapourpressure_v1(flu.tkortag[k])
[docs] class Calc_SaturationVapourPressureSlope_V1(modeltools.Method): """Calculate the daily slope of the saturation vapour pressure curve. Basic equation: :math:`SaturationVapourPressureSlope = Return\\_SaturationVapourPressureSlope\\_V1(TKor)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> fluxes.tkor = -10.0, 0.0, 10.0 >>> model.calc_saturationvapourpressureslope_v1() >>> fluxes.saturationvapourpressureslope saturationvapourpressureslope(0.226909, 0.445506, 0.824774) """ SUBMETHODS = (Return_SaturationVapourPressureSlope_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = (lland_fluxes.TKor,) RESULTSEQUENCES = (lland_fluxes.SaturationVapourPressureSlope,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.saturationvapourpressureslope[ k ] = model.return_saturationvapourpressureslope_v1(flu.tkor[k])
[docs] class Calc_DailySaturationVapourPressureSlope_V1(modeltools.Method): """Calculate the daily slope of the saturation vapour pressure curve. Basic equation: :math:`DailySaturationVapourPressureSlope = Return\\_SaturationVapourPressureSlope\\_V1(TKorTag)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> fluxes.tkortag = 10.0, 0.0, -10.0 >>> model.calc_dailysaturationvapourpressureslope_v1() >>> fluxes.dailysaturationvapourpressureslope dailysaturationvapourpressureslope(0.824774, 0.445506, 0.226909) """ SUBMETHODS = (Return_SaturationVapourPressureSlope_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = (lland_fluxes.TKorTag,) RESULTSEQUENCES = (lland_fluxes.DailySaturationVapourPressureSlope,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.dailysaturationvapourpressureslope[ k ] = model.return_saturationvapourpressureslope_v1(flu.tkortag[k])
[docs] class Return_ActualVapourPressure_V1(modeltools.Method): """Calculate and return the actual vapour pressure for the given saturation vapour pressure and relative humidity according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Weischet1983`). Basic equation: :math:`saturationvapourpressure \\cdot relativehumidity / 100` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> from hydpy import round_ >>> round_(model.return_actualvapourpressure_v1(20.0, 60.0)) 12.0 """ @staticmethod def __call__( model: modeltools.Model, saturationvapourpressure: float, relativehumidity: float, ) -> float: return saturationvapourpressure * relativehumidity / 100.0
[docs] class Calc_ActualVapourPressure_V1(modeltools.Method): """Calculate the actual vapour pressure. Basic equation: :math:`ActualVapourPressure = Return\\_ActualVapourPressure\\_V1( SaturationVapourPressure, RelativeHumidity)` Example: >>> from hydpy.models.lland import * >>> simulationstep("1h") >>> parameterstep() >>> nhru(1) >>> derived.nmblogentries.update() >>> inputs.relativehumidity = 60.0 >>> fluxes.saturationvapourpressure = 20.0 >>> model.calc_actualvapourpressure_v1() >>> fluxes.actualvapourpressure actualvapourpressure(12.0) """ SUBMETHODS = (Return_ActualVapourPressure_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_inputs.RelativeHumidity, lland_fluxes.SaturationVapourPressure, ) RESULTSEQUENCES = (lland_fluxes.ActualVapourPressure,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.actualvapourpressure[k] = model.return_actualvapourpressure_v1( flu.saturationvapourpressure[k], inp.relativehumidity )
[docs] class Calc_DailyActualVapourPressure_V1(modeltools.Method): """Calculate the daily actual vapour pressure. Basic equation: :math:`DailyActualVapourPressure = Return\\_ActualVapourPressure\\_V1( DailySaturationVapourPressure, DailyRelativeHumidity)` Example: >>> from hydpy.models.lland import * >>> simulationstep("1h") >>> parameterstep() >>> nhru(1) >>> derived.nmblogentries.update() >>> fluxes.dailyrelativehumidity = 40.0 >>> fluxes.dailysaturationvapourpressure = 40.0 >>> model.calc_dailyactualvapourpressure_v1() >>> fluxes.dailyactualvapourpressure dailyactualvapourpressure(16.0) """ SUBMETHODS = (Return_ActualVapourPressure_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_fluxes.DailyRelativeHumidity, lland_fluxes.DailySaturationVapourPressure, ) RESULTSEQUENCES = (lland_fluxes.DailyActualVapourPressure,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.dailyactualvapourpressure[k] = model.return_actualvapourpressure_v1( flu.dailysaturationvapourpressure[k], flu.dailyrelativehumidity, )
[docs] class Calc_DailyNetLongwaveRadiation_V1(modeltools.Method): r"""Calculate the daily net longwave radiation. Basic equation above a snow-free surface: :math:`DailyNetLongwaveRadiation = Sigma \cdot (TKorTag + 273.15)^4 \cdot \left(Emissivity - FrAtm \cdot \left(\frac{DailyActualVapourPressure} {TKorTag + 273.15}\right)^{1/7}\right) \cdot \left(0.2 + 0.8 \cdot \frac{DailySunshineDuration}{DailyPossibleSunshineDuration}\right)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> emissivity(0.95) >>> fluxes.tkortag = 22.1, 0.0 >>> fluxes.dailyactualvapourpressure = 16.0, 6.0 >>> fluxes.dailysunshineduration = 12.0 >>> fluxes.dailypossiblesunshineduration = 14.0 >>> model.calc_dailynetlongwaveradiation_v1() >>> fluxes.dailynetlongwaveradiation dailynetlongwaveradiation(40.451915, 58.190798) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Emissivity, ) FIXEDPARAMETERS = ( lland_fixed.Sigma, lland_fixed.FrAtm, ) REQUIREDSEQUENCES = ( lland_fluxes.TKorTag, lland_fluxes.DailyActualVapourPressure, lland_fluxes.DailySunshineDuration, lland_fluxes.DailyPossibleSunshineDuration, ) RESULTSEQUENCES = (lland_fluxes.DailyNetLongwaveRadiation,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess d_relsunshine = flu.dailysunshineduration / flu.dailypossiblesunshineduration for k in range(con.nhru): d_temp = flu.tkortag[k] + 273.15 flu.dailynetlongwaveradiation[k] = ( (0.2 + 0.8 * d_relsunshine) * (fix.sigma * d_temp**4) * ( con.emissivity - fix.fratm * (flu.dailyactualvapourpressure[k] / d_temp) ** (1.0 / 7.0) ) )
[docs] class Calc_RLAtm_V1(modeltools.Method): r"""Calculate the longwave radiation emitted from the atmosphere according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Thompson1981`). Basic equation: :math:`RLAtm = FrAtm \cdot Sigma \cdot (Tkor + 273.15)^4 \cdot \left( \frac{ActualVapourPressure}{TKor + 273.15} \right) ^{1/7} \cdot \left(1 + 0.22 \cdot \left( 1 - \frac{DailySunshineDuration}{DailyPossibleSunshineDuration} \right)^2 \right)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> emissivity(0.95) >>> fluxes.tkor = 0.0, 10.0 >>> fluxes.actualvapourpressure = 6.0 >>> fluxes.dailysunshineduration = 12.0 >>> fluxes.dailypossiblesunshineduration = 14.0 >>> model.calc_rlatm_v1() >>> aides.rlatm rlatm(235.207154, 270.197425) """ CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = ( lland_fixed.Sigma, lland_fixed.FrAtm, ) REQUIREDSEQUENCES = ( lland_fluxes.TKor, lland_fluxes.ActualVapourPressure, lland_fluxes.DailySunshineDuration, lland_fluxes.DailyPossibleSunshineDuration, ) RESULTSEQUENCES = (lland_aides.RLAtm,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess d_rs = flu.dailysunshineduration / flu.dailypossiblesunshineduration d_common = fix.fratm * fix.sigma * (1.0 + 0.22 * (1.0 - d_rs) ** 2) for k in range(con.nhru): d_t = flu.tkor[k] + 273.15 aid.rlatm[k] = d_common * ( d_t**4 * (flu.actualvapourpressure[k] / d_t) ** (1.0 / 7.0) )
[docs] class Return_NetLongwaveRadiationInz_V1(modeltools.Method): r"""Calculate and return the net longwave radiation for intercepted snow. The following equation is not part of the official `LARSIM`_ documentation. We think, it fits to the geometric assumtions underlying method |Return_NetLongwaveRadiationSnow_V1| but so far have not thoroughly checked if it results in realistic net longwave radiations. Basic equation: :math:`\big( 1 - Fr \big) \cdot \big( 0.97 \cdot Sigma \cdot (TempS + 273.15)^4 - RLAtm \big)` Examples: >>> from hydpy import pub >>> pub.timegrids = "2000-01-30", "2000-02-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> lnk(LAUBW, NADELW) >>> derived.moy.update() >>> derived.fr.laubw_jan = 0.4 >>> derived.fr.laubw_feb = 0.5 >>> derived.fr.nadelw_jan = 0.6 >>> derived.fr.nadelw_feb = 0.7 >>> aides.tempsinz = -1.0 >>> aides.rlatm = 100.0 >>> model.idx_sim = pub.timegrids.init["2000-01-31"] >>> from hydpy import print_values >>> for hru in range(2): ... print_values([hru, model.return_netlongwaveradiationinz_v1(hru)]) 0, 126.624073 1, 84.416049 >>> model.idx_sim = pub.timegrids.init["2000-02-01"] >>> from hydpy import print_values >>> for hru in range(2): ... print_values([hru, model.return_netlongwaveradiationinz_v1(hru)]) 0, 105.520061 1, 63.312037 .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = (lland_control.Lnk,) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = (lland_fixed.Sigma,) REQUIREDSEQUENCES = ( lland_aides.TempSInz, lland_aides.RLAtm, ) @staticmethod def __call__(model: modeltools.Model, k: int) -> float: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess fix = model.parameters.fixed.fastaccess aid = model.sequences.aides.fastaccess d_fr = der.fr[con.lnk[k] - 1, der.moy[model.idx_sim]] d_rlsnow = fix.sigma * (aid.tempsinz[k] + 273.15) ** 4 return (1.0 - d_fr) * (d_rlsnow - aid.rlatm[k])
[docs] class Return_NetLongwaveRadiationSnow_V1(modeltools.Method): r"""Calculate and return the net longwave radiation for snow-covered areas. Method |Return_NetLongwaveRadiationSnow_V1| differentiates between forests (|LAUBW|, |MISCHW|, and |NADELW|) and open areas. The outgoing longwave radiation always depends on the snow surface temperature. For open areas, the ingoing counter radiation stems from the atmosphere only. This atmospheric radiation is partly replaced by the longwave radiation emitted by the tree canopies for forests. The lower the value of parameter |Fr|, the higher this shading effect of the canopies. See :cite:t:`ref-LARSIM` and :cite:t:`ref-Thompson1981` for further information. Basic equation: .. math:: Sigma \cdot (TempSSurface + 273.15)^4 - \begin{cases} Fr \cdot RLAtm + \big( 1 - Fr \big) \cdot \big( 0.97 \cdot Sigma \cdot (TKor + 273.15)^4 \big) &|\ Lnk \in \{LAUBW, MISCHW, NADELW\} \\ RLAtm &|\ Lnk \notin \{LAUBW, MISCHW, NADELW\} \end{cases} Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> lnk(ACKER, LAUBW) >>> derived.moy.shape = 1 >>> derived.moy(5) >>> derived.fr(0.3) >>> fluxes.tempssurface = -5.0 >>> fluxes.tkor = 0.0 >>> aides.rlatm = 235.207154 >>> from hydpy import print_values >>> for hru in range(2): ... print_values([hru, model.return_netlongwaveradiationsnow_v1(hru)]) 0, 57.945793 1, 8.273292 """ CONTROLPARAMETERS = (lland_control.Lnk,) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = (lland_fixed.Sigma,) REQUIREDSEQUENCES = ( lland_fluxes.TempSSurface, lland_fluxes.TKor, lland_aides.RLAtm, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess d_temp = flu.tkor[k] + 273.15 d_counter = aid.rlatm[k] if con.lnk[k] in (LAUBW, MISCHW, NADELW): d_fr = der.fr[con.lnk[k] - 1, der.moy[model.idx_sim]] d_counter = d_fr * d_counter + (1.0 - d_fr) * 0.97 * fix.sigma * d_temp**4 return fix.sigma * (flu.tempssurface[k] + 273.15) ** 4 - d_counter
[docs] class Update_TauS_V1(modeltools.Method): """Calculate the dimensionless age of the snow layer according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-LUBW2006b`). Basic equations: :math:`TauS_{new} = TauS_{old} \\cdot max(1 - 0.1 \\cdot SBes), 0) + \\frac{r_1+r_2+r_3}{10^6} \\cdot Seconds` :math:`r_1 = exp\\left(5000 \\cdot \\left(\\frac{1}{273.15} - \\frac{1}{273.15+TempS}\\right)\\right)` :math:`r_2 = min\\left(r_1^{10}, 1\\right)` :math:`r_3 = 0.03` Example: For snow-free surfaces, |TauS| is not defined; snowfall rejuvenates an existing snow layer; high temperatures increase the speed of aging: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(10) >>> derived.seconds(24*60*60) >>> fluxes.sbes = 0.0, 1.0, 5.0, 9.0, 10.0, 11.0, 11.0, 11.0, 11.0, 11.0 >>> states.waes = 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 >>> states.taus = 1.0, nan, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 >>> aides.temps = ( ... 0.0, 0.0, -10.0, -10.0, -10.0, -10.0, -1.0, 0.0, 1.0, 10.0) >>> model.update_taus() >>> states.taus taus(nan, 0.175392, 0.545768, 0.145768, 0.045768, 0.045768, 0.127468, 0.175392, 0.181358, 0.253912) Note that an initial |numpy.nan| value serves as an indication that there has been no snow-layer in the last simulation step, meaning the snow is fresh and starts with an age of zero (see the first two hydrological response units). """ CONTROLPARAMETERS = (lland_control.NHRU,) DERIVEDPARAMETERS = (lland_derived.Seconds,) REQUIREDSEQUENCES = ( lland_fluxes.SBes, lland_states.WAeS, lland_aides.TempS, ) RESULTSEQUENCES = (lland_states.TauS,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): if sta.waes[k] > 0: if modelutils.isnan(sta.taus[k]): sta.taus[k] = 0.0 d_r1 = modelutils.exp( 5000.0 * (1 / 273.15 - 1.0 / (273.15 + aid.temps[k])) ) d_r2 = min(d_r1**10, 1.0) sta.taus[k] *= max(1 - 0.1 * flu.sbes[k], 0.0) sta.taus[k] += (d_r1 + d_r2 + 0.03) / 1e6 * der.seconds else: sta.taus[k] = modelutils.nan
[docs] class Calc_ActualAlbedo_V1(modeltools.Method): """Calculate the current albedo value according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-LUBW2006b`). For snow-free surfaces, method |Calc_ActualAlbedo_V1| takes the value of parameter |Albedo| relevant for the given landuse and month. For snow conditions, it estimates the albedo based on the snow age, as shown by the following equation. Basic equation: :math:`AlbedoSnow = Albedo0Snow \\cdot \\left(1-SnowAgingFactor \\cdot \\frac{TauS}{1 + TauS}\\right)` Examples: >>> from hydpy import pub >>> pub.timegrids = "2000-01-30", "2000-02-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(5) >>> lnk(ACKER, VERS, VERS, VERS, VERS) >>> albedo0snow(0.8) >>> snowagingfactor(0.35) >>> albedo.acker_jan = 0.2 >>> albedo.vers_jan = 0.3 >>> albedo.acker_feb = 0.4 >>> albedo.vers_feb = 0.5 >>> derived.moy.update() >>> states.taus = nan, nan, 0.0, 1.0, 3.0 >>> states.waes = 0.0, 0.0, 1.0, 1.0, 1.0 >>> model.idx_sim = 1 >>> model.calc_actualalbedo_v1() >>> fluxes.actualalbedo actualalbedo(0.2, 0.3, 0.8, 0.66, 0.59) >>> model.idx_sim = 2 >>> model.calc_actualalbedo_v1() >>> fluxes.actualalbedo actualalbedo(0.4, 0.5, 0.8, 0.66, 0.59) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.SnowAgingFactor, lland_control.Albedo0Snow, lland_control.Albedo, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = ( lland_states.TauS, lland_states.WAeS, ) RESULTSEQUENCES = (lland_fluxes.ActualAlbedo,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if sta.waes[k] > 0.0: flu.actualalbedo[k] = con.albedo0snow * ( 1.0 - con.snowagingfactor * sta.taus[k] / (1.0 + sta.taus[k]) ) else: flu.actualalbedo[k] = con.albedo[con.lnk[k] - 1, der.moy[model.idx_sim]]
[docs] class Calc_NetShortwaveRadiation_V1(modeltools.Method): """Calculate the net shortwave radiation. Basic equation: :math:`NetShortwaveRadiation = (1.0 - ActualAlbedo) \\cdot AdjustedGlobalRadiation` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(1) >>> inputs.globalradiation = 100.0 >>> fluxes.actualalbedo = 0.25 >>> model.calc_netshortwaveradiation_v1() >>> fluxes.netshortwaveradiation netshortwaveradiation(75.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_inputs.GlobalRadiation, lland_fluxes.ActualAlbedo, ) RESULTSEQUENCES = (lland_fluxes.NetShortwaveRadiation,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.netshortwaveradiation[k] = ( 1.0 - flu.actualalbedo[k] ) * inp.globalradiation
[docs] class Calc_NetShortwaveRadiationSnow_V1(modeltools.Method): """Calculate the net shortwave radiation for snow-surfaces. Basic equation: :math:`NetShortwaveRadiationSnow = Fr \\cdot (1.0 - ActualAlbedo) \\cdot AdjustedGlobalRadiation` Examples: >>> from hydpy import pub >>> pub.timegrids = "2000-01-30", "2000-02-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> lnk(ACKER, LAUBW) >>> derived.fr.acker_jan = 1.0 >>> derived.fr.acker_feb = 1.0 >>> derived.fr.laubw_jan = 0.5 >>> derived.fr.laubw_feb = 0.1 >>> derived.moy.update() >>> inputs.globalradiation = 40.0 >>> fluxes.actualalbedo = 0.5 >>> model.idx_sim = pub.timegrids.init["2000-01-31"] >>> model.calc_netshortwaveradiationsnow_v1() >>> fluxes.netshortwaveradiationsnow netshortwaveradiationsnow(20.0, 10.0) >>> model.idx_sim = pub.timegrids.init["2000-02-01"] >>> model.calc_netshortwaveradiationsnow_v1() >>> fluxes.netshortwaveradiationsnow netshortwaveradiationsnow(20.0, 2.0) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) REQUIREDSEQUENCES = ( lland_inputs.GlobalRadiation, lland_fluxes.ActualAlbedo, ) RESULTSEQUENCES = (lland_fluxes.NetShortwaveRadiationSnow,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.netshortwaveradiationsnow[k] = ( der.fr[con.lnk[k] - 1, der.moy[model.idx_sim]] * (1.0 - flu.actualalbedo[k]) * inp.globalradiation )
[docs] class Calc_DailyNetShortwaveRadiation_V1(modeltools.Method): """Calculate the daily net shortwave radiation. Basic equation: :math:`DailyNetShortwaveRadiation = (1.0 - ActualAlbedo) \\cdot DailyGlobalRadiation` Example: >>> from hydpy.models.lland import * >>> simulationstep("1h") >>> parameterstep() >>> nhru(1) >>> fluxes.actualalbedo(0.25) >>> fluxes.dailyglobalradiation = 100.0 >>> model.calc_dailynetshortwaveradiation_v1() >>> fluxes.dailynetshortwaveradiation dailynetshortwaveradiation(75.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_fluxes.DailyGlobalRadiation, lland_fluxes.ActualAlbedo, ) RESULTSEQUENCES = (lland_fluxes.DailyNetShortwaveRadiation,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.dailynetshortwaveradiation[k] = ( 1.0 - flu.actualalbedo[k] ) * flu.dailyglobalradiation
[docs] class Return_TempS_V1(modeltools.Method): """Calculate and return the average temperature of the snow layer according to :cite:t:`ref-LARSIM`. Basic equation: :math:`max\\left( \\frac{ESnow}{WATS \\cdot CPEis + (WAeS-WATS) \\cdot CPWasser}, -273\\right)` Example: Note that we use |numpy.nan| values for snow-free surfaces (see the result of the first hydrological response unit) and that method |Return_TempS_V1| never returns temperature values lower than -273 °C (see the result of the fifth response unit): >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(5) >>> states.wats = 0.0, 0.5, 5.0, 0.5, 0.5 >>> states.waes = 0.0, 1.0, 10.0, 1.0, 1.0 >>> states.esnow = 0.0, 0.0, -1.0, -10.0, -100.0 >>> from hydpy import round_ >>> round_(model.return_temps_v1(0)) nan >>> round_(model.return_temps_v1(1)) 0.0 >>> round_(model.return_temps_v1(2)) -1.376498 >>> round_(model.return_temps_v1(3)) -137.649758 >>> round_(model.return_temps_v1(4)) -273.0 """ FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_states.WATS, lland_states.WAeS, lland_states.ESnow, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: fix = model.parameters.fixed.fastaccess sta = model.sequences.states.fastaccess if sta.waes[k] > 0.0: d_ice = fix.cpeis * sta.wats[k] d_water = fix.cpwasser * (sta.waes[k] - sta.wats[k]) return max(sta.esnow[k] / (d_ice + d_water), -273.0) return modelutils.nan
[docs] class Return_ESnowInz_V1(modeltools.Method): r"""Calculate and return the thermal energy content of the intercepted snow for its given bulk temperature. Basic equation: :math:`temps \cdot \big( STInz \cdot CPEis + (SInz - STInz) \cdot CPWasser \big)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(3) >>> states.stinz = 0.0, 0.5, 5.0 >>> states.sinz = 0.0, 1.0, 10.0 >>> states.esnowinz = 0.0, 0.0, -2.0 >>> from hydpy import round_ >>> round_(model.return_esnowinz_v1(0, 1.0)) 0.0 >>> round_(model.return_esnowinz_v1(1, 0.0)) 0.0 >>> round_(model.return_esnowinz_v1(2, -2.752995)) -2.0 """ FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_states.STInz, lland_states.SInz, ) @staticmethod def __call__( model: modeltools.Model, k: int, temps: float, ) -> float: fix = model.parameters.fixed.fastaccess sta = model.sequences.states.fastaccess d_ice = fix.cpeis * sta.stinz[k] d_water = fix.cpwasser * (sta.sinz[k] - sta.stinz[k]) return temps * (d_ice + d_water)
[docs] class Return_ESnow_V1(modeltools.Method): """Calculate and return the thermal energy content of the snow layer for the given bulk temperature according to :cite:t:`ref-LARSIM`. Basic equation: :math:`temps \\cdot (WATS \\cdot CPEis + (WAeS-WATS)\\cdot CPWasser)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(3) >>> states.wats = 0.0, 0.5, 5.0 >>> states.waes = 0.0, 1.0, 10.0 >>> states.esnow = 0.0, 0.0, -2.0 >>> from hydpy import round_ >>> round_(model.return_esnow_v1(0, 1.0)) 0.0 >>> round_(model.return_esnow_v1(1, 0.0)) 0.0 >>> round_(model.return_esnow_v1(2, -2.752995)) -2.0 """ FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_states.WATS, lland_states.WAeS, ) @staticmethod def __call__( model: modeltools.Model, k: int, temps: float, ) -> float: fix = model.parameters.fixed.fastaccess sta = model.sequences.states.fastaccess d_ice = fix.cpeis * sta.wats[k] d_water = fix.cpwasser * (sta.waes[k] - sta.wats[k]) return temps * (d_ice + d_water)
[docs] class Calc_TempS_V1(modeltools.Method): """Calculate the average temperature of the snow layer. Method |Calc_TempS_V1| uses method |Return_TempS_V1| to calculate the snow temperature of all hydrological response units. Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(3) >>> pwmax(2.0) >>> fluxes.tkor = -1.0 >>> states.wats = 0.0, 0.5, 5 >>> states.waes = 0.0, 1.0, 10 >>> states.esnow = 0.0, 0.0, -2.0 >>> model.calc_temps_v1() >>> aides.temps temps(nan, 0.0, -2.752995) """ SUBMETHODS = (Return_TempS_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, ) REQUIREDSEQUENCES = ( lland_states.WATS, lland_states.WAeS, lland_states.ESnow, ) RESULTSEQUENCES = (lland_aides.TempS,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): aid.temps[k] = model.return_temps_v1(k)
[docs] class Calc_TZ_V1(modeltools.Method): """Calculate the mean temperature in the top-layer of the soil according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-LUBW2006b`). Basic equation: .. math:: TZ = \\begin{cases} \\frac{EBdn}{2 \\cdot z \\cdot CG} &|\\ EBdn \\leq 0 \\\\ 0 &|\\ 0 < EBdn \\leq HeatOfFusion \\\\ \\frac{EBdn - HeatOfFusion}{2 \\cdot z \\cdot CG} &|\\ HeatOfFusion < EBdn \\end{cases} Examples: We set |TZ| to |numpy.nan| for all types of water areas: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(7) >>> lnk(WASSER, FLUSS, SEE, WASSER, SEE, FLUSS, WASSER) >>> model.calc_tz_v1() >>> fluxes.tz tz(nan, nan, nan, nan, nan, nan, nan) For all other land use types, the above basic equation applies: >>> lnk(ACKER) >>> derived.heatoffusion(310.0) >>> states.ebdn(-20.0, -10.0, 0.0, 310.0, 620.0, 630, 640.0) >>> model.calc_tz_v1() >>> fluxes.tz tz(-2.88, -1.44, 0.0, 0.0, 0.0, 1.44, 2.88) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) DERIVEDPARAMETERS = (lland_derived.HeatOfFusion,) FIXEDPARAMETERS = ( lland_fixed.Z, lland_fixed.CG, ) REQUIREDSEQUENCES = (lland_states.EBdn,) RESULTSEQUENCES = (lland_fluxes.TZ,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.tz[k] = modelutils.nan elif sta.ebdn[k] < 0.0: flu.tz[k] = sta.ebdn[k] / (2.0 * fix.z * fix.cg) elif sta.ebdn[k] < der.heatoffusion[k]: flu.tz[k] = 0.0 else: flu.tz[k] = (sta.ebdn[k] - der.heatoffusion[k]) / (2.0 * fix.z * fix.cg)
[docs] class Calc_G_V1(modeltools.Method): """Calculate and return the "MORECS" soil heat flux (modified :cite:t:`ref-LARSIM`, based on :cite:t:`ref-Thompson1981`). Basic equations: :math:`G = \\frac{PossibleSunshineDuration} {DailyPossibleSunshineDuration} \\cdot G_D + \\frac{(Hours - PossibleSunshineDuration)} {24-DailyPossibleSunshineDuration} \\cdot G_N` :math:`G_N = WG2Z - G_D` :math:`G_D = (0.3 - 0.03 \\cdot LAI) \\cdot DailyNetRadiation` The above equations are our interpretation on the relevant equations and explanations given in the LARSIM user manual. :math:`G_D` is the daytime sum of the soil heat flux, which depends on the daily sum of net radiation and the leaf area index. Curiously, |DailyNetRadiation| and :math:`G_D` become inversely related for leaf area index values larger than 10. |WG2Z| defines the daily energy change of the soil layer, which is why we use its difference to :math:`G_D` for estimating the nighttime sum of the soil heat flux (:math:`G_N`). .. note:: The sudden jumps of the soil heat flux calculated by method |Calc_G_V1| sometimes result in strange-looking evapotranspiration time-series. Hence, we do not use |Calc_G_V1| in any application model for now but leave it for further discussions and use the simplified method |Calc_G_V2| instead. .. warning:: This method was implemented when |lland| handled energy fluxes in MJ/m²/T. Before using it in an application model, one probably needs to adjust it to the new energy flux unit W/m². Examples: To start as simple as possible, we perform the first test calculation for a daily simulation step size: >>> from hydpy import pub >>> pub.timegrids = "2000-05-30", "2000-06-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() We define five hydrological respose units, of which only the first four need further parameterisation, as method |Calc_G_V1| generally sets |G| to zero for all kinds of water areas: >>> nhru(6) >>> lnk(BODEN, OBSTB, OBSTB, LAUBW, NADELW, WASSER) We assign leaf area indices covering the usual range of values: >>> lai.boden = 0.0 >>> lai.obstb = 5.0 >>> lai.laubw = 10.0 >>> lai.nadelw = 15.0 We define a positive |WG2Z| value for May (on average, energy moves from the soil body to the soil surface) and a negative one for June (enery moves for the surface to the body): >>> wg2z.may = 10.0 >>> wg2z.jun = -20.0 The following derived parameters need to be updated: >>> derived.moy.update() >>> derived.hours.update() For a daily simulation step size, the values of |PossibleSunshineDuration| (of the current simulation step) and |DailyPossibleSunshineDuration| must be identical: >>> inputs.possiblesunshineduration = 14.0 >>> fluxes.dailypossiblesunshineduration = 14.0 We set |DailyNetRadiation| to 100 W/m² for most response units, except one of the |ACKER| ones: >>> fluxes.dailynetradiation = 100.0, 100.0, -100.0, 100.0, 100.0, 100.0 For the given daily simulation step size, method |Calc_G_V1| calculates soil surface fluxes identical with the |WG2Z| value of the respective month: >>> model.idx_sim = pub.timegrids.init["2000-05-31"] >>> model.calc_g_v1() >>> fluxes.g g(10.0, 10.0, 10.0, 10.0, 10.0, 0.0) >>> model.idx_sim = pub.timegrids.init["2000-06-01"] >>> model.calc_g_v1() >>> fluxes.g g(-20.0, -20.0, -20.0, -20.0, -20.0, 0.0) Next, we switch to an hourly step size and focus on May: >>> pub.timegrids = "2000-05-30 00:00", "2000-06-02 00:00", "1h" >>> model.idx_sim = pub.timegrids.init["2000-05-31"] >>> derived.moy.update() >>> derived.hours.update() >>> derived.days.update() During daytime (when the possible sunshine duration is one hour), the soil surface flux is negative for net radiation values larger one and for leaf area index values smaller than ten (see response units one and two). Negative net radiation (response unit three) ar leaf area indices larger then ten (response unit five) result in positive soil surface fluxes: >>> inputs.possiblesunshineduration = 1.0 >>> model.calc_g_v1() >>> fluxes.g g(-2.142857, -1.071429, 1.071429, 0.0, 1.071429, 0.0) >>> day = fluxes.g.values.copy() The nighttime soil surface fluxes (which we calculate through setting |PossibleSunshineDuration| to zero) somehow compensate the daytime ones: >>> inputs.possiblesunshineduration = 0.0 >>> model.calc_g_v1() >>> fluxes.g g(4.0, 2.5, -0.5, 1.0, -0.5, 0.0) >>> night = fluxes.g.values.copy() This compensation enforces that the daily sum the surface flux agrees with the actual value of |WG2Z|, which we confirm by the following test calculation: >>> from hydpy import print_values >>> print_values(day*fluxes.dailypossiblesunshineduration + ... night*(24-fluxes.dailypossiblesunshineduration)) 10.0, 10.0, 10.0, 10.0, 10.0, 0.0 .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.LAI, lland_control.WG2Z, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Hours, ) REQUIREDSEQUENCES = ( lland_inputs.PossibleSunshineDuration, lland_fluxes.DailyPossibleSunshineDuration, lland_fluxes.DailyNetRadiation, ) RESULTSEQUENCES = (lland_fluxes.G,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (FLUSS, SEE, WASSER): flu.g[k] = 0.0 else: idx = der.moy[model.idx_sim] d_cr = 0.3 - 0.03 * con.lai[con.lnk[k] - 1, idx] d_gd = -d_cr * flu.dailynetradiation[k] d_gn = con.wg2z[idx] - d_gd d_gd_h = d_gd / flu.dailypossiblesunshineduration d_gn_h = d_gn / (24.0 - flu.dailypossiblesunshineduration) flu.g[k] = ( inp.possiblesunshineduration * d_gd_h + (der.hours - inp.possiblesunshineduration) * d_gn_h )
[docs] class Calc_G_V2(modeltools.Method): """Take the actual daily soil heat flux from parameter |WG2Z|. Basic equations: :math:`G = WG2Z` .. note:: Method |Calc_G_V1| workaround. We might remove it, as soon as the shortcomings of method |Calc_G_V1| are fixed. Examples: >>> from hydpy import pub >>> pub.timegrids = "2000-05-30 00:00", "2000-06-02 00:00", "1h" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(ACKER, WASSER, FLUSS, SEE) >>> wg2z.may = 12.0 >>> wg2z.jun = -24.0 >>> derived.moy.update() >>> derived.days.update() >>> model.idx_sim = pub.timegrids.init["2000-05-31 23:00"] >>> model.calc_g_v2() >>> fluxes.g g(12.0, 0.0, 0.0, 0.0) >>> model.idx_sim = pub.timegrids.init["2000-06-01 00:00"] >>> model.calc_g_v2() >>> fluxes.g g(-24.0, 0.0, 0.0, 0.0) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.WG2Z, ) DERIVEDPARAMETERS = (lland_derived.MOY,) RESULTSEQUENCES = (lland_fluxes.G,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (FLUSS, SEE, WASSER): flu.g[k] = 0.0 else: flu.g[k] = con.wg2z[der.moy[model.idx_sim]]
[docs] class Return_WG_V1(modeltools.Method): """Calculate and return the "dynamic" soil heat flux according to :cite:t:`ref-LARSIM`. The soil heat flux |WG| depends on the temperature gradient between depth z and the surface. We set the soil surface temperature to the air temperature for snow-free conditions and otherwise to the average temperature of the snow layer. Basic equations: .. math:: \\begin{cases} LambdaG \\cdot \\frac{TZ-TempS}{Z} &|\\ WAeS > 0 \\\\ LambdaG \\cdot \\frac{TZ-TKor}{Z} &|\\ WAeS = 0 \\end{cases} Examples: >>> from hydpy.models.lland import * >>> simulationstep("1h") >>> parameterstep("1d") >>> nhru(2) >>> fixed.lambdag.restore() >>> fluxes.tz = 2.0 >>> fluxes.tkor = 10.0 >>> states.waes = 0.0, 1.0 >>> aides.temps = nan, -1.0 >>> from hydpy import round_ >>> round_(model.return_wg_v1(0)) -48.0 >>> round_(model.return_wg_v1(1)) 18.0 """ FIXEDPARAMETERS = ( lland_fixed.Z, lland_fixed.LambdaG, ) REQUIREDSEQUENCES = ( lland_fluxes.TZ, lland_fluxes.TKor, lland_states.WAeS, lland_aides.TempS, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess if sta.waes[k] > 0.0: d_temp = aid.temps[k] else: d_temp = flu.tkor[k] return fix.lambdag * (flu.tz[k] - d_temp) / fix.z
[docs] class Calc_WG_V1(modeltools.Method): """Calculate the soil heat flux. Method |Calc_WG_V1| uses method |Return_WG_V1| to calculate the "dynamic" soil heat flux for all hydrological response units that do not represent water areas. Examples: >>> from hydpy.models.lland import * >>> simulationstep("1h") >>> parameterstep("1d") >>> nhru(5) >>> lnk(ACKER, VERS, WASSER, FLUSS, SEE) >>> fixed.lambdag.restore() >>> fluxes.tz = 2.0 >>> fluxes.tkor = 10.0 >>> states.waes = 0.0, 1.0, 0.0, 0.0, 0.0 >>> aides.temps = nan, -1.0, nan, nan, nan >>> model.calc_wg_v1() >>> fluxes.wg wg(-48.0, 18.0, 0.0, 0.0, 0.0) """ SUBMETHODS = (Return_WG_V1,) CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) FIXEDPARAMETERS = ( lland_fixed.Z, lland_fixed.LambdaG, ) REQUIREDSEQUENCES = ( lland_fluxes.TZ, lland_fluxes.TKor, lland_states.WAeS, lland_aides.TempS, ) RESULTSEQUENCES = (lland_fluxes.WG,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (FLUSS, SEE, WASSER): flu.wg[k] = 0.0 else: flu.wg[k] = model.return_wg_v1(k)
[docs] class Update_EBdn_V1(modeltools.Method): """Update the thermal energy content of the upper soil layer according to :cite:t:`ref-LARSIM`. Basic equation: :math:`\\frac{dEBdn}{dt} = WG2Z - WG` Example: Water areas do not posses a soil energy content. For all other landuse types, the above equation applies: >>> from hydpy.models.lland import * >>> from hydpy import pub >>> parameterstep() >>> nhru(6) >>> lnk(WASSER, FLUSS, SEE, ACKER, ACKER, ACKER) >>> pub.timegrids = "2019-04-29", "2019-05-03", "1d" >>> derived.moy.update() >>> wg2z.apr = -0.347222 >>> wg2z.may = -0.462963 >>> states.ebdn = 0.0 >>> fluxes.wg = 0.0, 0.0, 0.0, 5.787037, 11.574074, 23.148148 >>> model.idx_sim = 1 >>> model.update_ebdn_v1() >>> states.ebdn ebdn(0.0, 0.0, 0.0, -6.134259, -11.921296, -23.49537) >>> model.idx_sim = 2 >>> model.update_ebdn_v1() >>> states.ebdn ebdn(0.0, 0.0, 0.0, -12.384259, -23.958333, -47.106481) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.WG2Z, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = (lland_fluxes.WG,) UPDATEDSEQUENCES = (lland_states.EBdn,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): sta.ebdn[k] = 0.0 else: sta.ebdn[k] += con.wg2z[der.moy[model.idx_sim]] - flu.wg[k]
[docs] class Return_WSensInz_V1(modeltools.Method): r"""Calculate and return the sensible heat flux between the intercepted snow and the atmosphere. Basic equation: :math:`(Turb0 + Turb1 \cdot WindSpeed2m) \cdot (TempSInz - TKor)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> turb0(2.0) >>> turb1(2.0) >>> fluxes.tkor = -2.0, -1.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> aides.tempsinz = -5.0, 0.0 >>> from hydpy import round_ >>> round_(model.return_wsensinz_v1(0)) -24.0 >>> round_(model.return_wsensinz_v1(1)) 8.0 """ CONTROLPARAMETERS = ( lland_control.Turb0, lland_control.Turb1, ) REQUIREDSEQUENCES = ( lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_aides.TempSInz, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess return (con.turb0 + con.turb1 * flu.reducedwindspeed2m[k]) * ( aid.tempsinz[k] - flu.tkor[k] )
[docs] class Return_WSensSnow_V1(modeltools.Method): """Calculate and return the sensible heat flux of the snow surface according to :cite:t:`ref-LARSIM`. Basic equation: :math:`(Turb0 + Turb1 \\cdot WindSpeed2m) \\cdot (TempSSurface - TKor)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> turb0(2.0) >>> turb1(2.0) >>> fluxes.tkor = -2.0, -1.0 >>> fluxes.tempssurface = -5.0, 0.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> from hydpy import round_ >>> round_(model.return_wsenssnow_v1(0)) -24.0 >>> round_(model.return_wsenssnow_v1(1)) 8.0 """ CONTROLPARAMETERS = ( lland_control.Turb0, lland_control.Turb1, ) REQUIREDSEQUENCES = ( lland_fluxes.TKor, lland_fluxes.TempSSurface, lland_fluxes.ReducedWindSpeed2m, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess return (con.turb0 + con.turb1 * flu.reducedwindspeed2m[k]) * ( flu.tempssurface[k] - flu.tkor[k] )
[docs] class Return_WLatInz_V1(modeltools.Method): r"""Calculate and return the latent heat flux between the surface of the intercepted snow and the atmosphere. Basic equation: :math:`(Turb0 + Turb1 \cdot ReducedWindSpeed2m) \cdot PsiInv \cdot (SaturationVapourPressureInz - ActualVapourPressure))` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> turb0(2.0) >>> turb1(2.0) >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.saturationvapourpressureinz = 4.0, 8.0 >>> fluxes.actualvapourpressure = 6.108 >>> from hydpy import round_ >>> round_(model.return_wlatinz_v1(0)) -29.68064 >>> round_(model.return_wlatinz_v1(1)) 26.63936 """ CONTROLPARAMETERS = (lland_control.Turb0, lland_control.Turb1) FIXEDPARAMETERS = (lland_fixed.PsyInv,) REQUIREDSEQUENCES = ( lland_fluxes.ReducedWindSpeed2m, lland_fluxes.SaturationVapourPressureInz, lland_fluxes.ActualVapourPressure, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess return ( (con.turb0 + con.turb1 * flu.reducedwindspeed2m[k]) * fix.psyinv * (flu.saturationvapourpressureinz[k] - flu.actualvapourpressure[k]) )
[docs] class Return_WLatSnow_V1(modeltools.Method): """Calculate and return the latent heat flux of the snow surface according to :cite:t:`ref-LARSIM`. Basic equation: :math:`(Turb0 + Turb1 \\cdot ReducedWindSpeed2m) \\cdot PsiInv \\cdot (SaturationVapourPressureSnow - ActualVapourPressure))` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> turb0(2.0) >>> turb1(2.0) >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.saturationvapourpressuresnow = 4.0, 8.0 >>> fluxes.actualvapourpressure = 6.108 >>> from hydpy import round_ >>> round_(model.return_wlatsnow_v1(0)) -29.68064 >>> round_(model.return_wlatsnow_v1(1)) 26.63936 """ CONTROLPARAMETERS = (lland_control.Turb0, lland_control.Turb1) FIXEDPARAMETERS = (lland_fixed.PsyInv,) REQUIREDSEQUENCES = ( lland_fluxes.ReducedWindSpeed2m, lland_fluxes.SaturationVapourPressureSnow, lland_fluxes.ActualVapourPressure, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess return ( (con.turb0 + con.turb1 * flu.reducedwindspeed2m[k]) * fix.psyinv * (flu.saturationvapourpressuresnow[k] - flu.actualvapourpressure[k]) )
[docs] class Return_WSurf_V1(modeltools.Method): """Calculate and return the snow surface heat flux according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-LUBW2006b`). Basic equation: :math:`KTSchnee \\cdot (TempS - TempSSurface)` Example: >>> from hydpy.models.lland import * >>> simulationstep("1h") >>> parameterstep("1d") >>> nhru(1) >>> ktschnee(5.0) >>> fluxes.tempssurface = -4.0 >>> aides.temps = -2.0 >>> from hydpy import round_ >>> round_(model.return_wsurf_v1(0)) 10.0 Method |Return_WSurf_V1| explicitely supports infinite |KTSchnee| values: >>> ktschnee(inf) >>> round_(model.return_wsurf_v1(0)) inf """ CONTROLPARAMETERS = (lland_control.KTSchnee,) REQUIREDSEQUENCES = ( lland_aides.TempS, lland_fluxes.TempSSurface, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess if modelutils.isinf(con.ktschnee): return modelutils.inf return con.ktschnee * (aid.temps[k] - flu.tempssurface[k])
[docs] class Return_NetRadiation_V1(modeltools.Method): """Calculate and return the total net radiation. Basic equation: :math:`netshortwaveradiation - netlongwaveradiation` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> from hydpy import round_ >>> round_(model.return_netradiation_v1(300.0, 200.0)) 100.0 """ @staticmethod def __call__( model: modeltools.Model, netshortwaveradiation: float, netlongwaveradiation: float, ) -> float: return netshortwaveradiation - netlongwaveradiation
[docs] class Calc_NetRadiation_V1(modeltools.Method): """Calculate the total net radiation. Basic equation: :math:`NetRadiation = Return\\_NetRadiation\\_V1( NetShortwaveRadiation, DailyNetLongwaveRadiation \\cdot Days)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(1) >>> fluxes.netshortwaveradiation = 300.0 >>> fluxes.dailynetlongwaveradiation = 200.0 >>> model.calc_netradiation_v1() >>> fluxes.netradiation netradiation(100.0) """ SUBMETHODS = (Return_NetRadiation_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_fluxes.NetShortwaveRadiation, lland_fluxes.DailyNetLongwaveRadiation, ) UPDATEDSEQUENCES = (lland_fluxes.NetRadiation,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.netradiation[k] = model.return_netradiation_v1( flu.netshortwaveradiation[k], flu.dailynetlongwaveradiation[k] )
[docs] class Calc_DailyNetRadiation_V1(modeltools.Method): """Calculate the daily total net radiation for snow-free land surfaces. Basic equation: :math:`NetRadiation = Return\\_NetRadiation\\_V1( DailyDailyNetShortwaveRadiation, DailyNetLongwaveRadiation)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(1) >>> fluxes.dailynetshortwaveradiation = 300.0 >>> fluxes.dailynetlongwaveradiation = 200.0 >>> model.calc_dailynetradiation_v1() >>> fluxes.dailynetradiation dailynetradiation(100.0) """ SUBMETHODS = (Return_NetRadiation_V1,) CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_fluxes.DailyNetLongwaveRadiation, lland_fluxes.DailyNetShortwaveRadiation, ) RESULTSEQUENCES = (lland_fluxes.DailyNetRadiation,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.dailynetradiation[k] = model.return_netradiation_v1( flu.dailynetshortwaveradiation[k], flu.dailynetlongwaveradiation[k], )
[docs] class Return_EnergyGainSnowSurface_V1(modeltools.Method): """Calculate and return the net energy gain of the snow surface (:cite:t`ref-LARSIM` based on :cite:t:`ref-LUBW2006b`, modified). As surface cannot store any energy, method |Return_EnergyGainSnowSurface_V1| returns zero if one supplies it with the correct temperature of the snow surface. Hence, this methods provides a means to find this "correct" temperature via root finding algorithms. Basic equation: :math:`WSurf(tempssurface) + NetshortwaveRadiation - NetlongwaveRadiation(tempssurface) - WSensSnow(tempssurface) - WLatSnow(tempssurface)` Example: Method |Return_EnergyGainSnowSurface_V1| relies on multiple submethods with different requirements. Hence, we first need to specify a lot of input data (see the documentation on the different submethods for further information): >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> nhru(1) >>> lnk(ACKER) >>> turb0(2.0) >>> turb1(2.0) >>> ktschnee(5.0) >>> derived.days.update() >>> inputs.relativehumidity = 60.0 >>> states.waes = 1.0 >>> fluxes.tkor = -3.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.actualvapourpressure = 2.9 >>> fluxes.netshortwaveradiationsnow = 20.0 >>> derived.nmblogentries.update() >>> aides.temps = -2.0 >>> aides.rlatm = 200.0 Under the defined conditions and for a snow surface temperature of -4 °C, the net energy gain would be negative: >>> from hydpy import round_ >>> model.idx_hru = 0 >>> round_(model.return_energygainsnowsurface_v1(-4.0)) -82.62933 As a side-effect, method |Return_EnergyGainSnowSurface_V1| calculates the following flux sequences for the given snow surface temperature: >>> fluxes.saturationvapourpressuresnow saturationvapourpressuresnow(4.539126) >>> fluxes.netlongwaveradiationsnow netlongwaveradiationsnow(97.550439) >>> fluxes.netradiationsnow netradiationsnow(-77.550439) >>> fluxes.wsenssnow wsenssnow(-8.0) >>> fluxes.wlatsnow wlatsnow(23.078891) >>> fluxes.wsurf wsurf(10.0) Technical checks: Note that method |Return_EnergyGainSnowSurface_V1| calculates the value of sequence |SaturationVapourPressureSnow| before its submethod |Return_WLatSnow_V1| uses it and that it assigns the given value to sequence |TempSSurface| before its submethods |Return_WSensSnow_V1|, |Return_NetLongwaveRadiationSnow_V1|, and |Return_WSurf_V1| use it: >>> from hydpy.core.testtools import check_selectedvariables >>> from hydpy.models.lland.lland_model import ( ... Return_EnergyGainSnowSurface_V1) >>> print(check_selectedvariables(Return_EnergyGainSnowSurface_V1)) Possibly missing (REQUIREDSEQUENCES): Return_WLatSnow_V1: SaturationVapourPressureSnow Return_WSensSnow_V1: TempSSurface Return_NetLongwaveRadiationSnow_V1: TempSSurface Return_WSurf_V1: TempSSurface """ SUBMETHODS = ( Return_SaturationVapourPressure_V1, Return_WLatSnow_V1, Return_WSensSnow_V1, Return_NetLongwaveRadiationSnow_V1, Return_NetRadiation_V1, Return_WSurf_V1, ) CONTROLPARAMETERS = ( lland_control.Lnk, lland_control.KTSchnee, lland_control.Turb0, lland_control.Turb1, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = ( lland_fixed.PsyInv, lland_fixed.Sigma, ) REQUIREDSEQUENCES = ( lland_fluxes.NetShortwaveRadiationSnow, lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_fluxes.ActualVapourPressure, lland_aides.TempS, lland_aides.RLAtm, ) RESULTSEQUENCES = ( lland_fluxes.TempSSurface, lland_fluxes.SaturationVapourPressureSnow, lland_fluxes.WSensSnow, lland_fluxes.WLatSnow, lland_fluxes.NetLongwaveRadiationSnow, lland_fluxes.NetRadiationSnow, lland_fluxes.WSurf, ) @staticmethod def __call__( model: modeltools.Model, tempssurface: float, ) -> float: flu = model.sequences.fluxes.fastaccess k = model.idx_hru flu.tempssurface[k] = tempssurface flu.saturationvapourpressuresnow[k] = model.return_saturationvapourpressure_v1( flu.tempssurface[k] ) flu.wlatsnow[k] = model.return_wlatsnow_v1(k) flu.wsenssnow[k] = model.return_wsenssnow_v1(k) flu.netlongwaveradiationsnow[k] = model.return_netlongwaveradiationsnow_v1(k) flu.netradiationsnow[k] = model.return_netradiation_v1( flu.netshortwaveradiationsnow[k], flu.netlongwaveradiationsnow[k], ) flu.wsurf[k] = model.return_wsurf_v1(k) return ( flu.wsurf[k] + flu.netradiationsnow[k] - flu.wsenssnow[k] - flu.wlatsnow[k] )
[docs] class Return_TempSSurface_V1(modeltools.Method): """Determine and return the snow surface temperature (:cite:t:`ref-LARSIM` based on :cite:t:`ref-LUBW2006b`, modified). |Return_TempSSurface_V1| needs to determine the snow surface temperature via iteration. Therefore, it uses the class |PegasusTempSSurface| which searches the root of the net energy gain of the snow surface defined by method |Return_EnergyGainSnowSurface_V1|. For snow-free conditions, method |Return_TempSSurface_V1| sets the value of |TempSSurface| to |numpy.nan| and the values of |WSensSnow|, |WLatSnow|, and |WSurf| to zero. For snow conditions, the side-effects discussed in the documentation on method |Return_EnergyGainSnowSurface_V1| apply. Example: We reuse the configuration of the documentation on method |Return_EnergyGainSnowSurface_V1|, except that we prepare six hydrological response units and calculate their snow surface temperature: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> nhru(6) >>> lnk(ACKER) >>> turb0(2.0) >>> turb1(2.0) >>> ktschnee(5.0) >>> derived.days.update() >>> inputs.relativehumidity = 60.0 >>> states.waes = 0.0, 1.0, 1.0, 1.0, 1.0, 1.0 >>> fluxes.tkor = -3.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.actualvapourpressure = 2.9 >>> fluxes.netshortwaveradiationsnow = 10.0, 10.0, 20.0, 20.0, 20.0, 200.0 >>> aides.temps = nan, -2.0, -2.0, -50.0, -200.0, -2.0 >>> aides.rlatm = 200.0 >>> for hru in range(6): ... _ = model.return_tempssurface_v1(hru) For the first, snow-free response unit, we cannot define a reasonable snow surface temperature, of course: >>> fluxes.tempssurface[0] nan Comparing response units two and three shows that a moderate increase in short wave radiation is compensated by a moderate increase in snow surface temperature: >>> from hydpy import print_values >>> print_values(fluxes.tempssurface[1:3]) -8.307868, -7.828995 To demonstrate the robustness of the implemented approach, response units three to five show the extreme decrease in surface temperature due to an even more extrem decrease in the bulk temperature of the snow layer: >>> print_values(fluxes.tempssurface[2:5]) -7.828995, -20.191197, -66.644357 The sixths response unit comes with a very high net radiation to clarify that the allowed maximum value of the snow surface temperature is 0°C. Hence, the snow temperature gradient might actually be too small for conducting all available energy deeper into the snow layer. In reality, only the topmost snow layer would melt in such a situation. Here, we do not differentiate between melting processes in different layers and thus add the potential energy excess at the snow surface to |WSurf|: >>> print_values(fluxes.tempssurface[5:]) 0.0 As to be expected, the energy fluxes of the snow surface neutralise each other (within the defined numerical accuracy): >>> print_values(fluxes.netradiationsnow - ... fluxes.wsenssnow - ... fluxes.wlatsnow + ... fluxes.wsurf) nan, 0.0, 0.0, 0.0, 0.0, 0.0 Through setting parameter |KTSchnee| to |numpy.inf|, we disable the iterative search for the correct surface temperature. Instead, method |Return_TempSSurface_V1| simply uses the bulk temperature of the snow layer as its surface temperature and sets |WSurf| so that the energy gain of the snow surface is zero: >>> ktschnee(inf) >>> for hru in range(6): ... _ = model.return_tempssurface_v1(hru) >>> fluxes.tempssurface tempssurface(nan, -2.0, -2.0, -50.0, -200.0, -2.0) >>> fluxes.wsurf wsurf(0.0, 137.892846, 127.892846, -495.403823, -1835.208545, -52.107154) >>> print_values(fluxes.netradiationsnow - ... fluxes.wsenssnow - ... fluxes.wlatsnow + ... fluxes.wsurf) nan, 0.0, 0.0, 0.0, 0.0, 0.0 """ SUBMETHODS = (Return_EnergyGainSnowSurface_V1,) CONTROLPARAMETERS = ( lland_control.Lnk, lland_control.Turb0, lland_control.Turb1, lland_control.KTSchnee, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = ( lland_fixed.Sigma, lland_fixed.PsyInv, ) REQUIREDSEQUENCES = ( lland_fluxes.NetShortwaveRadiationSnow, lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_fluxes.ActualVapourPressure, lland_states.WAeS, lland_aides.TempS, lland_aides.RLAtm, ) RESULTSEQUENCES = ( lland_fluxes.TempSSurface, lland_fluxes.SaturationVapourPressureSnow, lland_fluxes.WSensSnow, lland_fluxes.WLatSnow, lland_fluxes.NetLongwaveRadiationSnow, lland_fluxes.NetRadiationSnow, lland_fluxes.WSurf, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess if sta.waes[k] > 0.0: if modelutils.isinf(con.ktschnee): model.idx_hru = k model.return_energygainsnowsurface_v1(aid.temps[k]) flu.wsurf[k] = ( flu.wsenssnow[k] + flu.wlatsnow[k] - flu.netradiationsnow[k] ) else: model.idx_hru = k model.pegasustempssurface.find_x(-50.0, 0.0, -100.0, 0.0, 0.0, 1e-8, 10) flu.wsurf[k] -= model.return_energygainsnowsurface_v1( flu.tempssurface[k] ) else: flu.tempssurface[k] = modelutils.nan flu.saturationvapourpressuresnow[k] = 0.0 flu.wsenssnow[k] = 0.0 flu.wlatsnow[k] = 0.0 flu.wsurf[k] = 0.0 return flu.tempssurface[k]
[docs] class Return_WSurfInz_V1(modeltools.Method): r"""Calculate and return the intercepted snow heat flux |WSurfInz|. Basic equation: :math:`WSurfInz = WSensInz + WLatInz - NetRadiationInz` Note that method |Return_WSurfInz_V1| calls a number of submethods and uses their results to update the corresponding sequences. When calculating |SaturationVapourPressureInz| calls method |Return_SaturationVapourPressure_V1|, as follows: :math:`SaturationVapourPressureInz = Return_SaturationVapourPressure_V1( max(TempSInz, TKor)` The reason for using the maximum of the intercepted snow temperature (|TempSInz|) and the air temperature (|TKor|) is to increase the evaporation of intercepted snow (|EvSInz|). See :cite:t:`ref-LARSIM`. Example: >>> from hydpy import pub >>> pub.timegrids = "2000-01-01", "2000-01-02", "1d" >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(1) >>> lnk(LAUBW) >>> turb0(2.0) >>> turb1(2.0) >>> derived.fr(0.5) >>> derived.nmblogentries.update() >>> derived.days.update() >>> derived.moy.update() >>> inputs.relativehumidity = 60.0 >>> fluxes.tkor = -3.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.actualvapourpressure = 2.9 >>> fluxes.netshortwaveradiationinz = 20.0 >>> fluxes.dailysunshineduration = 10.0 >>> fluxes.dailypossiblesunshineduration = 12.0 >>> aides.tempsinz = -6.675053 >>> aides.rlatm = 200.0 >>> from hydpy import round_ >>> round_(model.return_wsurfinz_v1(0)) 21.616068 >>> fluxes.saturationvapourpressureinz saturationvapourpressureinz(4.893489) >>> fluxes.netlongwaveradiationinz netlongwaveradiationinz(42.94817) >>> fluxes.netradiationinz netradiationinz(-22.94817) >>> fluxes.wsensinz wsensinz(-29.400424) >>> fluxes.wlatinz wlatinz(28.068322) >>> fluxes.wsurfinz wsurfinz(21.616068) Technical checks: >>> from hydpy.core.testtools import check_selectedvariables >>> from hydpy.models.lland.lland_model import Return_WSurfInz_V1 >>> print(check_selectedvariables(Return_WSurfInz_V1)) Possibly missing (REQUIREDSEQUENCES): Return_WLatInz_V1: SaturationVapourPressureInz .. testsetup:: >>> del pub.timegrids """ SUBMETHODS = ( Return_SaturationVapourPressure_V1, Return_WLatInz_V1, Return_WSensInz_V1, Return_NetLongwaveRadiationInz_V1, Return_NetRadiation_V1, ) CONTROLPARAMETERS = ( lland_control.Turb0, lland_control.Turb1, lland_control.Lnk, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = ( lland_fixed.Sigma, lland_fixed.PsyInv, ) REQUIREDSEQUENCES = ( lland_fluxes.NetShortwaveRadiationInz, lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_fluxes.ActualVapourPressure, lland_aides.TempSInz, lland_aides.RLAtm, ) RESULTSEQUENCES = ( lland_fluxes.SaturationVapourPressureInz, lland_fluxes.WSensInz, lland_fluxes.WLatInz, lland_fluxes.NetLongwaveRadiationInz, lland_fluxes.NetRadiationInz, lland_fluxes.WSurfInz, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: flu = model.sequences.fluxes.fastaccess aid = model.sequences.aides.fastaccess flu.saturationvapourpressureinz[k] = model.return_saturationvapourpressure_v1( max(aid.tempsinz[k], flu.tkor[k]) ) flu.wlatinz[k] = model.return_wlatinz_v1(k) flu.wsensinz[k] = model.return_wsensinz_v1(k) flu.netlongwaveradiationinz[k] = model.return_netlongwaveradiationinz_v1(k) flu.netradiationinz[k] = model.return_netradiation_v1( flu.netshortwaveradiationinz[k], flu.netlongwaveradiationinz[k] ) flu.wsurfinz[k] = flu.wsensinz[k] + flu.wlatinz[k] - flu.netradiationinz[k] return flu.wsurfinz[k]
[docs] class Return_BackwardEulerErrorInz_V1(modeltools.Method): r"""Calculate and return the "Backward Euler error" regarding the update of |ESnowInz| due to the energy flux |WSurfInz|. Basic equation: :math:`ESnowInz_{old} - esnowinz_{new} - WSurfInz(esnowinz{new})` Example: >>> from hydpy import pub >>> pub.timegrids = "2000-01-01", "2000-01-02", "1d" >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(1) >>> lnk(LAUBW) >>> turb0(2.0) >>> turb1(2.0) >>> derived.fr(0.5) >>> derived.nmblogentries.update() >>> derived.days.update() >>> derived.moy.update() >>> inputs.relativehumidity = 60.0 >>> states.sinz = 120.0 >>> states.stinz = 100.0 >>> fluxes.tkor = -3.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.actualvapourpressure = 2.9 >>> fluxes.netshortwaveradiationinz = 20.0 >>> states.esnowinz = -1.0 >>> fluxes.dailysunshineduration = 10.0 >>> fluxes.dailypossiblesunshineduration = 12.0 >>> aides.rlatm = 200.0 >>> from hydpy import round_ >>> model.idx_hru = 0 >>> round_(model.return_backwardeulererrorinz_v1(-22.6160684)) 0.0 >>> aides.tempsinz tempsinz(-6.675053) >>> fluxes.saturationvapourpressureinz saturationvapourpressureinz(4.893489) >>> fluxes.netlongwaveradiationinz netlongwaveradiationinz(42.94817) >>> fluxes.netradiationinz netradiationinz(-22.94817) >>> fluxes.wsensinz wsensinz(-29.400424) >>> fluxes.wlatinz wlatinz(28.068322) >>> fluxes.wsurfinz wsurfinz(21.616068) >>> states.esnowinz esnowinz(-1.0) >>> states.sinz = 0.0 >>> round_(model.return_backwardeulererrorinz_v1(-1.8516245536573426)) nan >>> fluxes.wsurfinz wsurfinz(21.616068) Technical checks: >>> from hydpy.core.testtools import check_selectedvariables >>> from hydpy.models.lland.lland_model import Return_BackwardEulerErrorInz_V1 >>> print(check_selectedvariables(Return_BackwardEulerErrorInz_V1)) Possibly missing (REQUIREDSEQUENCES): Return_WLatInz_V1: SaturationVapourPressureInz Return_WSensInz_V1: TempSInz Return_NetLongwaveRadiationInz_V1: TempSInz Return_WSurfInz_V1: TempSInz .. testsetup:: >>> del pub.timegrids """ SUBMETHODS = ( Return_TempSInz_V1, Return_SaturationVapourPressure_V1, Return_WLatInz_V1, Return_WSensInz_V1, Return_NetLongwaveRadiationInz_V1, Return_NetRadiation_V1, Return_WSurfInz_V1, ) CONTROLPARAMETERS = ( lland_control.Turb0, lland_control.Turb1, lland_control.Lnk, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, lland_fixed.Sigma, lland_fixed.PsyInv, ) REQUIREDSEQUENCES = ( lland_states.SInz, lland_states.STInz, lland_fluxes.NetShortwaveRadiationInz, lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_fluxes.ActualVapourPressure, lland_states.ESnowInz, lland_aides.RLAtm, ) RESULTSEQUENCES = ( lland_aides.TempSInz, lland_fluxes.SaturationVapourPressureInz, lland_fluxes.WSensInz, lland_fluxes.WLatInz, lland_fluxes.NetLongwaveRadiationInz, lland_fluxes.NetRadiationInz, lland_fluxes.WSurfInz, ) @staticmethod def __call__( model: modeltools.Model, esnowinz: float, ) -> float: sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess k = model.idx_hru if sta.sinz[k] > 0.0: d_esnowinz_old = sta.esnowinz[k] sta.esnowinz[k] = esnowinz aid.tempsinz[k] = model.return_tempsinz_v1(k) sta.esnowinz[k] = d_esnowinz_old return d_esnowinz_old - esnowinz - model.return_wsurfinz_v1(k) return modelutils.nan
[docs] class Return_BackwardEulerError_V1(modeltools.Method): """Calculate and return the "Backward Euler error" regarding the update of |ESnow| due to the energy fluxes |WG| and |WSurf| (:cite:t:`ref-LARSIM` based on :cite:t:`ref-LUBW2006b`, modified). Basic equation: :math:`ESnow_{old} - esnow_{new} + WG(esnow{new}) - WSurf(esnow{new})` Method |Return_BackwardEulerError_V1| does not calculate any hydrologically meaningfull property. It is just a technical means to update the energy content of the snow layer with running into instability issues. See the documentation on method |Update_ESnow_V1| for further information. Example: Method |Return_BackwardEulerError_V1| relies on multiple submethods with different requirements. Hence, we first need to specify a lot of input data (see the documentation on the different submethods for further information): >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> nhru(1) >>> lnk(ACKER) >>> turb0(2.0) >>> turb1(2.0) >>> derived.nmblogentries.update() >>> derived.days.update() >>> inputs.relativehumidity = 60.0 >>> states.waes = 12.0 >>> states.wats = 10.0 >>> fluxes.tkor = -3.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.actualvapourpressure = 2.9 >>> fluxes.netshortwaveradiationsnow = 20.0 >>> states.esnow = -1.0 >>> fluxes.tz = 0.0 >>> aides.rlatm = 200.0 Under the defined conditions the "correct" next value of |ESnow|, when following the Backward Euler approach, is approximately -2.36 Wd/m²: >>> ktschnee(inf) >>> from hydpy import round_ >>> model.idx_hru = 0 >>> round_(model.return_backwardeulererror_v1(-2.3582799)) 0.0 As a side-effect, method |Return_BackwardEulerError_V1| calculates the following flux sequences for the given amount of energy: >>> aides.temps temps(-6.96038) >>> fluxes.tempssurface tempssurface(-6.96038) >>> fluxes.saturationvapourpressuresnow saturationvapourpressuresnow(3.619445) >>> fluxes.netlongwaveradiationsnow netlongwaveradiationsnow(84.673816) >>> fluxes.netradiationsnow netradiationsnow(-64.673816) >>> fluxes.wsenssnow wsenssnow(-31.683041) >>> fluxes.wlatsnow wlatsnow(10.129785) >>> fluxes.wsurf wsurf(43.120561) >>> fluxes.wg wg(41.762281) Note that the original value of |ESnow| remains unchanged, to allow for calling method |Return_BackwardEulerError_V1| multiple times during a root search without the need for an external reset: >>> states.esnow esnow(-1.0) In the above example, |KTSchnee| is set to |numpy.inf|, which is why |TempSSurface| is identical with |TempS|. After setting the common value of 5 W/m²/K, the surface temperature is calculated by another, embeded iteration approach (see method |Return_TempSSurface_V1|). Hence, the values of |TempSSurface| and |TempS| differ and the energy amount of -2.36 Wd/m² is not correct anymore: >>> ktschnee(5.0) >>> round_(model.return_backwardeulererror_v1(-2.3582799)) 32.808687 >>> aides.temps temps(-6.96038) >>> fluxes.tempssurface tempssurface(-9.022755) >>> fluxes.saturationvapourpressuresnow saturationvapourpressuresnow(3.080429) >>> fluxes.netlongwaveradiationsnow netlongwaveradiationsnow(75.953474) >>> fluxes.netradiationsnow netradiationsnow(-55.953474) >>> fluxes.wsenssnow wsenssnow(-48.182039) >>> fluxes.wlatsnow wlatsnow(2.540439) >>> fluxes.wsurf wsurf(10.311874) >>> fluxes.wg wg(41.762281) >>> states.esnow esnow(-1.0) If there is no snow-cover, it makes little sense to call method |Return_BackwardEulerError_V1|. For savety, we let it then return a |numpy.nan| value: >>> states.waes = 0.0 >>> round_(model.return_backwardeulererror_v1(-2.3582799)) nan >>> fluxes.wsurf wsurf(10.311874) """ SUBMETHODS = ( Return_TempS_V1, Return_WG_V1, Return_TempSSurface_V1, ) CONTROLPARAMETERS = ( lland_control.Turb0, lland_control.Turb1, lland_control.Lnk, lland_control.KTSchnee, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, lland_fixed.Z, lland_fixed.LambdaG, lland_fixed.Sigma, lland_fixed.PsyInv, ) REQUIREDSEQUENCES = ( lland_states.WAeS, lland_states.WATS, lland_fluxes.NetShortwaveRadiationSnow, lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_fluxes.TZ, lland_fluxes.ActualVapourPressure, lland_states.ESnow, lland_aides.TempS, lland_aides.RLAtm, ) RESULTSEQUENCES = ( lland_fluxes.TempSSurface, lland_fluxes.SaturationVapourPressureSnow, lland_fluxes.WSensSnow, lland_fluxes.WLatSnow, lland_fluxes.NetLongwaveRadiationSnow, lland_fluxes.NetRadiationSnow, lland_fluxes.WSurf, lland_fluxes.WG, ) @staticmethod def __call__( model: modeltools.Model, esnow: float, ) -> float: flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess k = model.idx_hru if sta.waes[k] > 0.0: d_esnow_old = sta.esnow[k] sta.esnow[k] = esnow aid.temps[k] = model.return_temps_v1(k) sta.esnow[k] = d_esnow_old model.return_tempssurface_v1(k) flu.wg[k] = model.return_wg_v1(k) return d_esnow_old - esnow + flu.wg[k] - flu.wsurf[k] return modelutils.nan
[docs] class Update_ESnowInz_V1(modeltools.Method): """Update the thermal energy content of the intercepted snow with regard to the energy fluxes from the atmosphere (except the one related to the heat content of precipitation). Basic equation: :math:`\\frac{dESnow}{dt} = - WSurf` Example: >>> from hydpy import pub >>> pub.timegrids = "2000-01-01", "2000-01-02", "1d" >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> nhru(8) >>> lnk(LAUBW) >>> turb0(2.0) >>> turb1(2.0) >>> derived.fr(0.5) >>> derived.nmblogentries.update() >>> derived.days.update() >>> derived.moy.update() >>> inputs.relativehumidity = 60.0 >>> states.sinz = 0.0, 120.0, 12.0, 1.2, 0.12, 0.012, 1.2e-6, 1.2e-12 >>> states.stinz = 0.0, 100.0, 12.0, 1.0, 0.10, 0.010, 1.0e-6, 1.0e-12 >>> fluxes.tkor = -3.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.actualvapourpressure = 2.9 >>> fluxes.netshortwaveradiationinz = 20.0 >>> states.esnowinz = -1.0 >>> aides.rlatm = 200.0 >>> model.update_esnowinz_v1() >>> states.esnowinz esnowinz(0.0, -22.616068, -2.514108, -0.300877, -0.030179, -0.003019, 0.0, 0.0) >>> aides.tempsinz tempsinz(nan, -6.675053, -8.661041, -8.880269, -8.907091, -8.909782, -8.910081, -8.910081) >>> esnowinz = states.esnowinz.values.copy() >>> states.esnowinz = -1.0 >>> errors = [] >>> for hru in range(8): ... model.idx_hru = hru ... errors.append(model.return_backwardeulererrorinz_v1(esnowinz[hru])) >>> from hydpy import print_values >>> print_values(errors) nan, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 Technical checks: >>> from hydpy.core.testtools import check_selectedvariables >>> from hydpy.models.lland.lland_model import Update_ESnowInz_V1 >>> print(check_selectedvariables(Update_ESnowInz_V1)) Possibly missing (REQUIREDSEQUENCES): Return_WSurfInz_V1: TempSInz .. testsetup:: >>> del pub.timegrids """ SUBMETHODS = ( Return_ESnowInz_V1, Return_BackwardEulerErrorInz_V1, Return_WSurfInz_V1, ) CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.Turb0, lland_control.Turb1, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, lland_fixed.Sigma, lland_fixed.PsyInv, ) REQUIREDSEQUENCES = ( lland_fluxes.NetShortwaveRadiationInz, lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_fluxes.ActualVapourPressure, lland_states.STInz, lland_states.SInz, lland_aides.RLAtm, ) UPDATEDSEQUENCES = (lland_states.ESnowInz,) RESULTSEQUENCES = ( lland_aides.TempSInz, lland_fluxes.SaturationVapourPressureInz, lland_fluxes.WSensInz, lland_fluxes.WLatInz, lland_fluxes.NetLongwaveRadiationInz, lland_fluxes.NetRadiationInz, lland_fluxes.WSurfInz, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): if sta.sinz[k] > 0.0: model.idx_hru = k d_esnowinz = sta.esnowinz[k] sta.esnowinz[k] = model.pegasusesnowinz.find_x( model.return_esnowinz_v1(k, -30.0), model.return_esnowinz_v1(k, 30.0), model.return_esnowinz_v1(k, -100.0), model.return_esnowinz_v1(k, 100.0), 0.0, 1e-8, 10, ) if sta.esnowinz[k] > 0.0: aid.tempsinz[k] = 0.0 sta.esnowinz[k] = d_esnowinz - model.return_wsurfinz_v1(k) else: sta.esnowinz[k] = 0.0 aid.tempsinz[k] = modelutils.nan flu.netlongwaveradiationinz[k] = 0.0 flu.netradiationinz[k] = 0.0 flu.saturationvapourpressureinz[k] = 0.0 flu.wsensinz[k] = 0.0 flu.wlatinz[k] = 0.0 flu.wsurfinz[k] = 0.0
[docs] class Update_ESnow_V1(modeltools.Method): """Update the thermal energy content of the snow layer with regard to the energy fluxes from the soil and the atmosphere (except the one related to the heat content of precipitation). :cite:t:`ref-LARSIM` based on :cite:t:`ref-LUBW2006b`, modified. Basic equation: :math:`\\frac{dESnow}{dt} = WG - WSurf` For a thin snow cover, small absolute changes in its energy content result in extreme temperature changes, which makes the above calculation stiff. Furthermore, the nonlinearity of the term :math:`WSurf(ESnow(t))` prevents from finding an analytical solution of the problem. This is why we apply the A-stable Backward Euler method. Through our simplified approach of taking only one variable (|ESnow|) into account, we can solve the underlying root finding problem without any need to calculate or approximate derivatives. Speaking plainly, we use the Pegasus iterator |PegasusESnow| to find the root of method |Return_BackwardEulerError_V1| whenever the surface is snow-covered. Example: We reuse the configuration of the documentation on method |Return_BackwardEulerError_V1|, except that we prepare five hydrological response units: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> nhru(8) >>> lnk(ACKER) >>> turb0(2.0) >>> turb1(2.0) >>> ktschnee(inf) >>> derived.nmblogentries.update() >>> derived.days.update() >>> inputs.relativehumidity = 60.0 >>> states.waes = 0.0, 120.0, 12.0, 1.2, 0.12, 0.012, 1.2e-6, 1.2e-12 >>> states.wats = 0.0, 100.0, 12.0, 1.0, 0.10, 0.010, 1.0e-6, 1.0e-12 >>> fluxes.tkor = -3.0 >>> fluxes.reducedwindspeed2m = 3.0 >>> fluxes.actualvapourpressure = 2.9 >>> fluxes.netshortwaveradiationsnow = 20.0 >>> states.esnow = -1.0 >>> fluxes.tz = 0.0 >>> aides.rlatm = 200.0 For the first, snow-free response unit, the energy content of the snow layer is zero. For the other response units we see that the energy content decreases with decreasing snow thickness (This result is intuitively clear, but requires iteration in very different orders of magnitudes. We hope, our implementation works always well. Please tell us, if you encounter any cases where it does not): >>> model.update_esnow_v1() >>> states.esnow esnow(0.0, -20.789871, -2.024799, -0.239061, -0.023939, -0.002394, 0.0, 0.0) >>> aides.temps temps(nan, -6.136057, -6.975386, -7.055793, -7.065486, -7.066457, -7.066565, -7.066565) >>> fluxes.tempssurface tempssurface(nan, -6.136057, -6.975386, -7.055793, -7.065486, -7.066457, -7.066565, -7.066565) As to be expected, the requirement of the Backward Euler method is approximetely fullfilled for each response unit: >>> esnow = states.esnow.values.copy() >>> states.esnow = -1.0 >>> errors = [] >>> for hru in range(8): ... model.idx_hru = hru ... errors.append(model.return_backwardeulererror_v1(esnow[hru])) >>> from hydpy import print_values >>> print_values(errors) nan, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 The following example repeats the above one, but enables the iterative adjustment of the snow surface temperature, which is embeded into the iterative adjustment of the energy content of the snow layer: >>> ktschnee(5.0) >>> model.update_esnow_v1() >>> states.esnow esnow(0.0, -9.695862, -1.085682, -0.130026, -0.013043, -0.001305, 0.0, 0.0) >>> aides.temps temps(nan, -2.861699, -3.740149, -3.837669, -3.849607, -3.850805, -3.850938, -3.850938) >>> fluxes.tempssurface tempssurface(nan, -8.034911, -8.245463, -8.268877, -8.271743, -8.272031, -8.272063, -8.272063) The resulting energy amounts are, again, the "correct" ones: >>> esnow = states.esnow.values.copy() >>> states.esnow = -1.0 >>> errors = [] >>> for hru in range(8): ... model.idx_hru = hru ... errors.append(model.return_backwardeulererror_v1(esnow[hru])) >>> print_values(errors) nan, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 """ SUBMETHODS = ( Return_ESnow_V1, Return_TempSSurface_V1, Return_WG_V1, Return_BackwardEulerError_V1, ) CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.Turb0, lland_control.Turb1, lland_control.KTSchnee, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Fr, ) FIXEDPARAMETERS = ( lland_fixed.CPWasser, lland_fixed.CPEis, lland_fixed.Z, lland_fixed.LambdaG, lland_fixed.Sigma, lland_fixed.PsyInv, ) REQUIREDSEQUENCES = ( lland_fluxes.NetShortwaveRadiationSnow, lland_fluxes.TKor, lland_fluxes.ReducedWindSpeed2m, lland_fluxes.ActualVapourPressure, lland_fluxes.TZ, lland_states.WATS, lland_states.WAeS, lland_aides.TempS, lland_aides.RLAtm, ) UPDATEDSEQUENCES = (lland_states.ESnow,) RESULTSEQUENCES = ( lland_fluxes.SaturationVapourPressureSnow, lland_fluxes.TempSSurface, lland_fluxes.WSensSnow, lland_fluxes.WLatSnow, lland_fluxes.NetLongwaveRadiationSnow, lland_fluxes.NetRadiationSnow, lland_fluxes.WSurf, lland_fluxes.WG, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess aid = model.sequences.aides.fastaccess for k in range(con.nhru): if sta.waes[k] > 0.0: model.idx_hru = k d_esnow = sta.esnow[k] sta.esnow[k] = model.pegasusesnow.find_x( model.return_esnow_v1(k, -30.0), model.return_esnow_v1(k, 30.0), model.return_esnow_v1(k, -100.0), model.return_esnow_v1(k, 100.0), 0.0, 1e-8, 10, ) if sta.esnow[k] > 0.0: aid.temps[k] = 0.0 flu.tempssurface[k] = model.return_tempssurface(k) flu.wg[k] = model.return_wg_v1(k) sta.esnow[k] = d_esnow + flu.wg[k] - flu.wsurf[k] else: sta.esnow[k] = 0.0 aid.temps[k] = modelutils.nan flu.tempssurface[k] = modelutils.nan flu.netlongwaveradiationsnow[k] = 0.0 flu.netradiationsnow[k] = 0.0 flu.saturationvapourpressuresnow[k] = 0.0 flu.wsenssnow[k] = 0.0 flu.wlatsnow[k] = 0.0 flu.wsurf[k] = 0.0
[docs] class Calc_SchmPot_V1(modeltools.Method): """Calculate the potential snow melt according to the day degree method. Basic equation: :math:`SchmPot = max\\left(\\frac{WGTF + WNied}{RSchmelz}, 0\\right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(2) >>> fluxes.wgtf = 20.0 >>> fluxes.wnied = 10.0, 20.0 >>> model.calc_schmpot_v1() >>> fluxes.schmpot schmpot(3.88024, 5.173653) """ CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = ( lland_fluxes.WGTF, lland_fluxes.WNied, ) RESULTSEQUENCES = (lland_fluxes.SchmPot,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.schmpot[k] = max((flu.wgtf[k] + flu.wnied[k]) / fix.rschmelz, 0.0)
[docs] class Calc_SchmPot_V2(modeltools.Method): """Calculate the potential snow melt according to the heat content of the snow layer. Basic equation: :math:`SchmPot = max\\left(\\frac{ESnow}{RSchmelz}, 0\\right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(4) >>> states.waes = 0.0, 1.0, 1.0, 1.0 >>> states.esnow = nan, 100.0, 50.0, -50.0 >>> model.calc_schmpot_v2() >>> fluxes.schmpot schmpot(0.0, 12.934132, 6.467066, 0.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = ( lland_states.ESnow, lland_states.WAeS, ) RESULTSEQUENCES = (lland_fluxes.SchmPot,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if sta.waes[k] > 0.0: flu.schmpot[k] = max(sta.esnow[k] / fix.rschmelz, 0.0) else: flu.schmpot[k] = 0.0
[docs] class Calc_GefrPot_V1(modeltools.Method): """Calculate the potential refreezing within the snow layer according to the thermal energy content of the snow layer. Basic equation: :math:`GefrPot = max\\left(-\\frac{ESnow}{RSchmelz}, 0\\right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(4) >>> states.waes = 0.0, 1.0, 1.0, 1.0 >>> states.esnow = nan, -100.0, -50.0, 50.0 >>> model.calc_gefrpot_v1() >>> fluxes.gefrpot gefrpot(0.0, 12.934132, 6.467066, 0.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = ( lland_states.ESnow, lland_states.WAeS, ) RESULTSEQUENCES = (lland_fluxes.GefrPot,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if sta.waes[k] > 0: flu.gefrpot[k] = max(-sta.esnow[k] / fix.rschmelz, 0) else: flu.gefrpot[k] = 0.0
[docs] class Calc_Schm_WATS_V1(modeltools.Method): """Calculate the actual amount of water melting within the snow layer. Basic equations: :math:`\\frac{dW\\!ATS}{dt} = -Schm` .. math:: Schm = \\begin{cases} SchmPot &|\\ WATS > 0 \\\\ 0 &|\\ WATS = 0 \\end{cases} Examples: We initialise two water (|FLUSS| and |SEE|) and four arable land (|ACKER|) HRUs. We assume the same values for the initial amount of frozen water (|WATS|) and the frozen part of stand precipitation (|SBes|), but different values for potential snowmelt (|SchmPot|): >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(6) >>> lnk(FLUSS, SEE, ACKER, ACKER, ACKER, ACKER) >>> states.wats = 0.0, 0.0, 2.0, 2.0, 2.0, 2.0 >>> fluxes.schmpot = 1.0, 1.0, 0.0, 1.0, 3.0, 5.0 >>> model.calc_schm_wats_v1() >>> states.wats wats(0.0, 0.0, 2.0, 1.0, 0.0, 0.0) >>> fluxes.schm schm(0.0, 0.0, 0.0, 1.0, 2.0, 2.0) Actual melt is either limited by potential melt or the available frozen water, which is the sum of initial frozen water and the frozen part of stand precipitation. """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = (lland_fluxes.SchmPot,) RESULTSEQUENCES = (lland_fluxes.Schm,) UPDATEDSEQUENCES = (lland_states.WATS,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.schm[k] = 0.0 else: flu.schm[k] = min(flu.schmpot[k], sta.wats[k]) sta.wats[k] -= flu.schm[k]
[docs] class Calc_Gefr_WATS_V1(modeltools.Method): """Calculate the actual amount of water melting within the snow layer. Basic equations: :math:`\\frac{dGefr}{dt} = -Gefr` .. math:: Gefr = \\begin{cases} GefrPot &|\\ WAeS - WATS > 0 \\\\ 0 &|\\ WAeS - WATS = 0 \\end{cases} Examples: We initialise two water (|FLUSS| and |SEE|) and four arable land (|ACKER|) HRUs. We assume the same values for the initial amount of frozen water (|WATS|) and the frozen part of stand precipitation (|SBes|), but different values for potential snowmelt (|SchmPot|): >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(6) >>> lnk(FLUSS, SEE, ACKER, ACKER, ACKER, ACKER) >>> refreezeflag(True) >>> states.wats = 0.0, 0.0, 2.0, 2.0, 2.0, 2.0 >>> states.waes = 0.0, 0.0, 4.0, 4.0, 4.0, 4.0 >>> fluxes.gefrpot = 1.0, 1.0, 0.0, 1.0, 3.0, 5.0 >>> model.calc_gefr_wats_v1() >>> states.wats wats(0.0, 0.0, 2.0, 3.0, 4.0, 4.0) >>> fluxes.gefr gefr(0.0, 0.0, 0.0, 1.0, 2.0, 2.0) If we deactivate the refreezing flag, liquid water in the snow layer does not refreeze. |Gefr| is set to 0 and |WATS| does not change: >>> refreezeflag(False) >>> model.calc_gefr_wats_v1() >>> states.wats wats(0.0, 0.0, 2.0, 3.0, 4.0, 4.0) >>> fluxes.gefr gefr(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) Actual refreezing is either limited by potential refreezing or the available water. """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.RefreezeFlag, ) REQUIREDSEQUENCES = ( lland_fluxes.GefrPot, lland_states.WAeS, ) RESULTSEQUENCES = (lland_fluxes.Gefr,) UPDATEDSEQUENCES = (lland_states.WATS,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE) or not con.refreezeflag: flu.gefr[k] = 0.0 else: flu.gefr[k] = min(flu.gefrpot[k], (sta.waes[k] - sta.wats[k])) sta.wats[k] += flu.gefr[k]
[docs] class Update_WaDa_WAeS_V1(modeltools.Method): """Update the actual water release from and the liquid water content of the snow layer due to melting. Basic equations: :math:`WaDa_{new} = WaDa_{old} + \\Delta` :math:`WAeS_{new} = WAeS_{old} - \\Delta` :math:`\\Delta = max(WAeS-PWMax \\cdot WATS, 0)` Examples: We set the threshold parameter |PWMax| for each of the seven initialised hydrological response units to two. Thus, the snow cover can hold as much liquid water as it contains frozen water. For the first three response units, which represent water surfaces, snow-related processes are ignored and the original values of |WaDa| and |WAeS| remain unchanged. For all other land use classes (of which we select |ACKER| arbitrarily), method |Update_WaDa_WAeS_V1| passes only the amount of |WAeS| exceeding the actual snow holding capacity to |WaDa|: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(7) >>> lnk(WASSER, FLUSS, SEE, ACKER, ACKER, ACKER, ACKER) >>> pwmax(2.0) >>> states.wats = 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0 >>> states.waes = 1.0, 1.0, 1.0, 0.0, 1.0, 2.0, 3.0 >>> fluxes.wada = 1.0 >>> model.update_wada_waes_v1() >>> states.waes waes(1.0, 1.0, 1.0, 0.0, 1.0, 2.0, 2.0) >>> fluxes.wada wada(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.PWMax, ) REQUIREDSEQUENCES = (lland_states.WATS,) UPDATEDSEQUENCES = ( lland_states.WAeS, lland_fluxes.WaDa, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] not in (WASSER, FLUSS, SEE): d_wada_corr = max(sta.waes[k] - con.pwmax[k] * sta.wats[k], 0.0) flu.wada[k] += d_wada_corr sta.waes[k] -= d_wada_corr
[docs] class Update_ESnow_V2(modeltools.Method): """Update the thermal energy content of the snow layer regarding snow melt and refreezing. Basic equation: :math:`\\frac{dESNOW}{dt} = RSchmelz \\cdot (Gefr - Schm)` Examples: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(7) >>> lnk(WASSER, FLUSS, SEE, ACKER, ACKER, ACKER, ACKER) >>> fluxes.gefr = 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 4.0 >>> fluxes.schm = 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 4.0 >>> states.esnow = 20.0, 20.0, 20.0, 20.0, -40.0, 30.925926, 0.0 >>> states.waes = 1.0, 1.0, 1.0, 0.0, 5.0, 5.0, 10.0 >>> model.update_esnow_v2() >>> states.esnow esnow(0.0, 0.0, 0.0, 0.0, -9.074074, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) FIXEDPARAMETERS = (lland_fixed.RSchmelz,) REQUIREDSEQUENCES = ( lland_fluxes.Gefr, lland_fluxes.Schm, lland_states.WAeS, ) UPDATEDSEQUENCES = (lland_states.ESnow,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess sta = model.sequences.states.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if (con.lnk[k] in (WASSER, FLUSS, SEE)) or (sta.waes[k] <= 0.0): sta.esnow[k] = 0.0 else: sta.esnow[k] += fix.rschmelz * (flu.gefr[k] - flu.schm[k])
[docs] class Calc_SFF_V1(modeltools.Method): r"""Calculate the ratio between frozen water and total water within the top soil layer according to :cite:t:`ref-LARSIM`. Basic equations: :math:`SFF = min \left( max \left( 1 - \frac{EBdn}{BoWa2Z \cdot RSchmelz}, 0 \right), 1 \right)` Example: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(9) >>> lnk(VERS, WASSER, FLUSS, SEE, ACKER, ACKER, ACKER, ACKER, ACKER) >>> states.ebdn(0.0, 0.0, 0.0, 0.0, 620.0, 618.519, 309.259, 0.0, -1.0) >>> model.calc_sff_v1() >>> fluxes.sff sff(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) FIXEDPARAMETERS = ( lland_fixed.BoWa2Z, lland_fixed.RSchmelz, ) REQUIREDSEQUENCES = (lland_states.EBdn,) RESULTSEQUENCES = (lland_fluxes.SFF,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (VERS, WASSER, FLUSS, SEE): flu.sff[k] = 0.0 else: d_sff = 1.0 - sta.ebdn[k] / (fix.bowa2z[k] * fix.rschmelz) flu.sff[k] = min(max(d_sff, 0.0), 1.0)
[docs] class Calc_FVG_V1(modeltools.Method): """Calculate the degree of frost sealing of the soil according to :cite:t:`ref-LARSIM`. Basic equation: :math:`FVG = min\\bigl(FVF \\cdot SFF^{BSFF}, 1\\bigl)` Examples: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(7) >>> lnk(VERS, WASSER, FLUSS, SEE, ACKER, ACKER, ACKER) >>> fvf(0.8) >>> bsff(2.0) >>> fluxes.sff(1.0, 1.0, 1.0, 1.0, 0.0, 0.5, 1.0) >>> model.calc_fvg_v1() >>> fluxes.fvg fvg(0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.8) The calculated degree of frost sealing does never exceed one, even when defining |FVF| values larger one: >>> fvf(1.6) >>> model.calc_fvg_v1() >>> fluxes.fvg fvg(0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 1.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.BSFF, lland_control.FVF, ) REQUIREDSEQUENCES = (lland_fluxes.SFF,) RESULTSEQUENCES = (lland_fluxes.FVG,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (VERS, WASSER, FLUSS, SEE): flu.fvg[k] = 0.0 else: flu.fvg[k] = min(con.fvf * flu.sff[k] ** con.bsff, 1.0)
[docs] class Calc_EvB_V1(modeltools.Method): """Calculate the actual soil evapotranspiration. Basic equations: :math:`EvB = (EvPo - EvI) \\cdot \\frac{1 - temp}{1 + temp -2 \\cdot exp(-GrasRef_R)}` :math:`temp = exp\\left(-GrasRef_R \\cdot \\frac{BoWa}{WMax}\\right)` Examples: Soil evapotranspiration is calculated neither for water nor for sealed areas (see the first three hydrological reponse units of type |FLUSS|, |SEE|, and |VERS|). All other land use classes are handled in accordance with a recommendation of the set of codes described in DVWK-M 504 :cite:p:`ref-DVWK`. In case maximum soil water storage (|WMax|) is zero, soil evaporation (|EvB|) is generally set to zero (see the fourth hydrological response unit). The last three response units demonstrate the rise in soil evaporation with increasing soil moisture, which is lessening in the high soil moisture range: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(7) >>> lnk(FLUSS, SEE, VERS, ACKER, ACKER, ACKER, ACKER) >>> grasref_r(5.0) >>> wmax(100.0, 100.0, 100.0, 0.0, 100.0, 100.0, 100.0) >>> fluxes.evpo = 5.0 >>> fluxes.evi = 3.0 >>> states.bowa = 50.0, 50.0, 50.0, 0.0, 0.0, 50.0, 100.0 >>> model.calc_evb_v1() >>> fluxes.evb evb(0.0, 0.0, 0.0, 0.0, 0.0, 1.717962, 2.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.WMax, lland_control.GrasRef_R, ) REQUIREDSEQUENCES = ( lland_states.BoWa, lland_fluxes.EvPo, lland_fluxes.EvI, ) RESULTSEQUENCES = (lland_fluxes.EvB,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (VERS, WASSER, FLUSS, SEE) or con.wmax[k] <= 0.0: flu.evb[k] = 0.0 else: d_temp = modelutils.exp(-con.grasref_r * sta.bowa[k] / con.wmax[k]) flu.evb[k] = ( (flu.evpo[k] - flu.evi[k]) * (1.0 - d_temp) / (1.0 + d_temp - 2.0 * modelutils.exp(-con.grasref_r)) )
[docs] class Calc_DryAirPressure_V1(modeltools.Method): """Calculate the pressure of the dry air (based on :cite:t:`ref-DWD1987`). Basic equation: :math:`DryAirPressure = AirPressure - ActualVapourPressure` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> fluxes.actualvapourpressure = 9.0, 11.0, 25.0 >>> inputs.atmosphericpressure = 1200.0 >>> model.calc_dryairpressure_v1() >>> fluxes.dryairpressure dryairpressure(1191.0, 1189.0, 1175.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_fluxes.ActualVapourPressure, lland_inputs.AtmosphericPressure, ) RESULTSEQUENCES = (lland_fluxes.DryAirPressure,) @staticmethod def __call__(model: modeltools.Model) -> None: inp = model.sequences.inputs.fastaccess con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.dryairpressure[k] = ( inp.atmosphericpressure - flu.actualvapourpressure[k] )
[docs] class Calc_DensityAir_V1(modeltools.Method): r"""Calculate the density of the air (based on :cite:t:`ref-DWD1987`). Basic equation: :math:`DensityAir = \frac{100 \cdot DryAirPressure}{RDryAir \cdot (TKor + 273.15)} + \frac{100 \cdot ActualVapourPressure}{RWaterVapour \cdot (TKor + 273.15)}` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> fluxes.dryairpressure = 1191.0, 1000.0, 1150.0 >>> fluxes.actualvapourpressure = 8.0, 7.0, 10.0 >>> fluxes.tkor = 10.0, 12.0, 14.0 >>> model.calc_densityair_v1() >>> fluxes.densityair densityair(1.471419, 1.226998, 1.402691) """ CONTROLPARAMETERS = (lland_control.NHRU,) FIXEDPARAMETERS = ( lland_fixed.RDryAir, lland_fixed.RWaterVapour, ) REQUIREDSEQUENCES = ( lland_fluxes.ActualVapourPressure, lland_fluxes.DryAirPressure, lland_fluxes.TKor, ) RESULTSEQUENCES = (lland_fluxes.DensityAir,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): d_t = flu.tkor[k] + 273.15 flu.densityair[k] = 100.0 * ( flu.dryairpressure[k] / (fix.rdryair * d_t) + flu.actualvapourpressure[k] / (fix.rwatervapour * d_t) )
[docs] class Calc_AerodynamicResistance_V1(modeltools.Method): """Calculate the aerodynamic resistance according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Thompson1981`). Basic equations (:math:`z_0` after Quast and Boehm, 1997): .. math:: AerodynamicResistance = \\begin{cases} \\frac{6.25}{WindSpeed10m} \\cdot ln\\left(\\frac{10}{z_0}\\right)^2 &|\\ z_0 < 10 \\\\ \\frac{94}{WindSpeed10m} &|\\ z_0 \\geq 10 \\end{cases} :math:`z_0 = 0.021 + 0.163 \\cdot CropHeight` Examples: Besides wind speed, aerodynamic resistance depends on the crop height, which typically varies between different landuse-use classes and, for vegetated surfaces, months. In the first example, we set some different crop heights for different landuse-use types to cover the relevant range of values: >>> from hydpy import pub >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(5) >>> lnk(WASSER, ACKER, OBSTB, LAUBW, NADELW) >>> pub.timegrids = "2000-01-30", "2000-02-03", "1d" >>> derived.moy.update() >>> cropheight.wasser_jan = 0.0 >>> cropheight.acker_jan = 1.0 >>> cropheight.obstb_jan = 5.0 >>> cropheight.laubw_jan = 10.0 >>> cropheight.nadelw_jan = 15.0 >>> fluxes.windspeed10m = 3.0 >>> model.idx_sim = 1 >>> model.calc_aerodynamicresistance_v1() >>> fluxes.aerodynamicresistance aerodynamicresistance(79.202731, 33.256788, 12.831028, 31.333333, 31.333333) The last example shows an decrease in resistance for increasing crop height for the first three hydrological response units only. When reaching a crop height of 10 m, the calculated resistance increases discontinously (see the fourth response unit) and then remains constant (see the fifth response unit). In the next example, we set some unrealistic values, to inspect this behaviour more closely: >>> cropheight.wasser_feb = 8.0 >>> cropheight.acker_feb = 9.0 >>> cropheight.obstb_feb = 10.0 >>> cropheight.laubw_feb = 11.0 >>> cropheight.nadelw_feb = 12.0 >>> model.idx_sim = 2 >>> model.calc_aerodynamicresistance_v1() >>> fluxes.aerodynamicresistance aerodynamicresistance(8.510706, 7.561677, 31.333333, 31.333333, 31.333333) To get rid of this jump, one could use a threshold crop height of 1.14 m instead of 10 m for the selection of the two underlying equations: :math:`1.1404411695422059 = \\frac{\\frac{10}{\\exp(\\sqrt{94/6.25}}-0.021}{0.163}` The last example shows the inverse relationship between resistance and wind speed. For zero wind speed, resistance becomes infinite: >>> from hydpy import print_values >>> cropheight(2.0) >>> for ws in (0.0, 0.1, 1.0, 10.0): ... fluxes.windspeed10m = ws ... model.calc_aerodynamicresistance_v1() ... print_values([ws, fluxes.aerodynamicresistance[0]]) 0.0, inf 0.1, 706.026613 1.0, 70.602661 10.0, 7.060266 .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.CropHeight, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = (lland_fluxes.WindSpeed10m,) RESULTSEQUENCES = (lland_fluxes.AerodynamicResistance,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess if flu.windspeed10m > 0.0: for k in range(con.nhru): d_ch = con.cropheight[con.lnk[k] - 1, der.moy[model.idx_sim]] if d_ch < 10.0: d_z0 = 0.021 + 0.163 * d_ch flu.aerodynamicresistance[k] = ( 6.25 / flu.windspeed10m * modelutils.log(10.0 / d_z0) ** 2 ) else: flu.aerodynamicresistance[k] = 94.0 / flu.windspeed10m else: for k in range(con.nhru): flu.aerodynamicresistance[k] = modelutils.inf
[docs] class Calc_SoilSurfaceResistance_V1(modeltools.Method): """Calculate the surface resistance of the bare soil surface according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Thompson1981`). Basic equation: .. math:: SoilSurfaceResistance = \\begin{cases} 100 &|\\ NFk > 20 \\\\ \\frac{100 \\cdot NFk}{max(BoWa-PWP, 0) + NFk/100} &|\\ NFk \\geq 20 \\end{cases} Examples: Water areas and sealed surfaces do not posses a soil, which is why we set the soil surface resistance to |numpy.nan|: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(VERS, WASSER, FLUSS, SEE) >>> fk(0.0) >>> pwp(0.0) >>> derived.nfk.update() >>> states.bowa = 0.0 >>> model.calc_soilsurfaceresistance_v1() >>> fluxes.soilsurfaceresistance soilsurfaceresistance(nan, nan, nan, nan) For "typical" soils, which posses an available field capacity larger than 20 mm, we set the soil surface resistance to 100.0 s/m: >>> lnk(ACKER) >>> fk(20.1, 30.0, 40.0, 40.0) >>> pwp(0.0, 0.0, 10.0, 10.0) >>> derived.nfk.update() >>> states.bowa = 0.0, 20.0, 5.0, 30.0 >>> model.calc_soilsurfaceresistance_v1() >>> fluxes.soilsurfaceresistance soilsurfaceresistance(100.0, 100.0, 100.0, 100.0) For all soils with smaller actual field capacities, resistance is 10'000 s/m as long as the soil water content does not exceed the permanent wilting point: >>> pwp(0.1, 20.0, 20.0, 30.0) >>> derived.nfk.update() >>> states.bowa = 0.0, 10.0, 20.0, 30.0 >>> model.calc_soilsurfaceresistance_v1() >>> fluxes.soilsurfaceresistance soilsurfaceresistance(10000.0, 10000.0, 10000.0, 10000.0) With increasing soil water contents, resistance decreases and reaches a value of 99 s/m: >>> pwp(0.0) >>> fk(20.0) >>> derived.nfk.update() >>> states.bowa = 5.0, 10.0, 15.0, 20.0 >>> model.calc_soilsurfaceresistance_v1() >>> fluxes.soilsurfaceresistance soilsurfaceresistance(384.615385, 196.078431, 131.578947, 99.009901) >>> fk(50.0) >>> pwp(40.0) >>> derived.nfk.update() >>> states.bowa = 42.5, 45.0, 47.5, 50.0 >>> model.calc_soilsurfaceresistance_v1() >>> fluxes.soilsurfaceresistance soilsurfaceresistance(384.615385, 196.078431, 131.578947, 99.009901) For zero field capacity, we set the soil surface resistance to zero: >>> pwp(0.0) >>> fk(0.0) >>> derived.nfk.update() >>> states.bowa = 0.0 >>> model.calc_soilsurfaceresistance_v1() >>> fluxes.soilsurfaceresistance soilsurfaceresistance(inf, inf, inf, inf) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.PWP, ) DERIVEDPARAMETERS = (lland_derived.NFk,) REQUIREDSEQUENCES = (lland_states.BoWa,) RESULTSEQUENCES = (lland_fluxes.SoilSurfaceResistance,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (VERS, FLUSS, SEE, WASSER): flu.soilsurfaceresistance[k] = modelutils.nan elif der.nfk[k] > 20.0: flu.soilsurfaceresistance[k] = 100.0 elif der.nfk[k] > 0.0: d_free = min(max(sta.bowa[k] - con.pwp[k], 0.0), der.nfk[k]) flu.soilsurfaceresistance[k] = ( 100.0 * der.nfk[k] / (d_free + 0.01 * der.nfk[k]) ) else: flu.soilsurfaceresistance[k] = modelutils.inf
[docs] class Calc_LanduseSurfaceResistance_V1(modeltools.Method): r"""Calculate the surface resistance of vegetation, water and sealed areas according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Thompson1981`). Basic equations: :math:`LanduseSurfaceResistance = SR^* \cdot \left(3.5 \cdot \left(1 - \frac{min(BoWa, PY)}{PY}\right) + exp\left(\frac{0.2 \cdot PY}{max(BoWa, 0)}\right)\right)` .. math:: SR^* = \begin{cases} SurfaceResistance &|\ Lnk \neq NADELW \\ 10\,000 &|\ Lnk = NADELW \;\; \land \;\; (TKor \leq -5 \;\; \lor \;\; \Delta \geq 20) \\ min\left(\frac{25 \cdot SurfaceResistance}{(TKor + 5) \cdot (1 - \Delta / 20)}, 10\,000\right) &|\ Lnk = NADELW \;\; \land \;\; (-5 < TKor < 20 \;\; \land \;\; \Delta < 20) \\ min\left(\frac{SurfaceResistance}{1 - \Delta / 20}, 10\,000\right) &|\ Lnk = NADELW \;\; \land \;\; (20 \leq TKor \;\; \land \;\; \Delta < 20) \end{cases} :math:`\Delta = SaturationVapourPressure - ActualVapourPressure` Example: Method |Calc_LanduseSurfaceResistance_V1| relies on multiple discontinuous relationships, works different for different types of landuse-use, and uses montly varying base parameters. Hence, we try to start simple and introduce further complexities step by step. For sealed surfaces and water areas the original parameter values are in effect without any modification: >>> from hydpy import pub >>> pub.timegrids = "2000-05-30", "2000-06-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(VERS, FLUSS, SEE, WASSER) >>> surfaceresistance.fluss_mai = 0.0 >>> surfaceresistance.see_mai = 0.0 >>> surfaceresistance.wasser_mai = 0.0 >>> surfaceresistance.vers_mai = 500.0 >>> derived.moy.update() >>> model.idx_sim = 1 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(500.0, 0.0, 0.0, 0.0) For all "soil areas", we sligthly increase the original parameter value by a constant factor for wet soils (|BoWa| > |PY|) and increase it even more for dry soils (|BoWa| > |PY|). For a completely dry soil, surface resistance becomes infinite: >>> lnk(ACKER) >>> py(0.0) >>> surfaceresistance.acker_jun = 40.0 >>> states.bowa = 0.0, 10.0, 20.0, 30.0 >>> model.idx_sim = 2 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(inf, 48.85611, 48.85611, 48.85611) >>> py(20.0) >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(inf, 129.672988, 48.85611, 48.85611) >>> states.bowa = 17.0, 18.0, 19.0, 20.0 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(71.611234, 63.953955, 56.373101, 48.85611) Only for coniferous trees, we further increase surface resistance for low temperatures and high water vapour pressure deficits. The highest resistance value results from air temperatures lower than -5 °C or vapour deficits higher than 20 hPa: >>> lnk(NADELW) >>> surfaceresistance.nadelw_jun = 80.0 >>> states.bowa = 20.0 >>> fluxes.tkor = 30.0 >>> fluxes.saturationvapourpressure = 30.0 >>> fluxes.actualvapourpressure = 0.0, 10.0, 20.0, 30.0 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(12214.027582, 12214.027582, 195.424441, 97.712221) >>> fluxes.actualvapourpressure = 10.0, 10.1, 11.0, 12.0 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(12214.027582, 12214.027582, 1954.244413, 977.122207) >>> fluxes.actualvapourpressure = 20.0 >>> fluxes.tkor = -10.0, 5.0, 20.0, 35.0 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(12214.027582, 488.561103, 195.424441, 195.424441) >>> fluxes.tkor = -6.0, -5.0, -4.0, -3.0 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(12214.027582, 12214.027582, 4885.611033, 2442.805516) >>> fluxes.tkor = 18.0, 19.0, 20.0, 21.0 >>> model.calc_landusesurfaceresistance_v1() >>> fluxes.landusesurfaceresistance landusesurfaceresistance(212.417871, 203.567126, 195.424441, 195.424441) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.SurfaceResistance, lland_control.PY, ) DERIVEDPARAMETERS = (lland_derived.MOY,) REQUIREDSEQUENCES = ( lland_fluxes.TKor, lland_fluxes.SaturationVapourPressure, lland_fluxes.ActualVapourPressure, lland_states.BoWa, ) RESULTSEQUENCES = (lland_fluxes.LanduseSurfaceResistance,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): d_res = con.surfaceresistance[con.lnk[k] - 1, der.moy[model.idx_sim]] if con.lnk[k] == NADELW: d_def = flu.saturationvapourpressure[k] - flu.actualvapourpressure[k] if (flu.tkor[k] <= -5.0) or (d_def >= 20.0): flu.landusesurfaceresistance[k] = 10000.0 elif flu.tkor[k] < 20.0: flu.landusesurfaceresistance[k] = min( (25.0 * d_res) / (flu.tkor[k] + 5.0) / (1.0 - 0.05 * d_def), 10000.0, ) else: flu.landusesurfaceresistance[k] = min( d_res / (1.0 - 0.05 * d_def), 10000.0 ) else: flu.landusesurfaceresistance[k] = d_res if con.lnk[k] not in (WASSER, FLUSS, SEE, VERS): if sta.bowa[k] <= 0.0: flu.landusesurfaceresistance[k] = modelutils.inf elif sta.bowa[k] < con.py[k]: flu.landusesurfaceresistance[k] *= 3.5 * ( 1.0 - sta.bowa[k] / con.py[k] ) + modelutils.exp(0.2 * con.py[k] / sta.bowa[k]) else: flu.landusesurfaceresistance[k] *= modelutils.exp(0.2)
[docs] class Calc_ActualSurfaceResistance_V1(modeltools.Method): """Calculate the total surface resistance according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Grant1975`). Basic equations: :math:`ActualSurfaceResistance = \\left(w \\cdot \\frac{1}{SRD} + (1-w) \\cdot \\frac{1}{SRN}\\right)^{-1}` :math:`SRD = \\left(\\frac{1-0.7^{LAI}}{LanduseSurfaceResistance} + \\frac{0.7^{LAI}}{SoilSurfaceResistance}\\right)^{-1}` :math:`SRN = \\left(\\frac{LAI}{2500} + \\frac{1}{SoilSurfaceResistance}\\right)^{-1}` :math:`w = \\frac{PossibleSunshineDuration}{Hours}` Examples: For sealed surfaces and water areas, method |Calc_ActualSurfaceResistance_V1| just uses the values of sequence |LanduseSurfaceResistance| as the effective surface resistance values: >>> from hydpy import pub >>> pub.timegrids = "2019-05-30", "2019-06-03", "1d" >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(4) >>> lnk(VERS, FLUSS, SEE, WASSER) >>> derived.moy.update() >>> derived.hours.update() >>> fluxes.soilsurfaceresistance = nan >>> fluxes.landusesurfaceresistance = 500.0, 0.0, 0.0, 0.0 >>> model.idx_sim = 1 >>> model.calc_actualsurfaceresistance_v1() >>> fluxes.actualsurfaceresistance actualsurfaceresistance(500.0, 0.0, 0.0, 0.0) For all other landuse types, the final soil resistance is a combination of |LanduseSurfaceResistance| and |SoilSurfaceResistance| that depends on the leaf area index. We demonstrate this for the landuse types |ACKER|, |OBSTB|, |LAUBW|, and |NADELW|, for which we define different leaf area indices: >>> lnk(ACKER, OBSTB, LAUBW, NADELW) >>> lai.acker_mai = 0.0 >>> lai.obstb_mai = 2.0 >>> lai.laubw_mai = 5.0 >>> lai.nadelw_mai = 10.0 The soil and landuse surface resistance are identical for all four hydrological response units: >>> fluxes.soilsurfaceresistance = 200.0 >>> fluxes.landusesurfaceresistance = 100.0 When we assume that the sun shines the whole day, the final resistance is identical with the soil surface resistance for zero leaf area indices (see the first response unit) and becomes closer to landuse surface resistance for when the leaf area index increases: >>> inputs.possiblesunshineduration = 24.0 >>> model.calc_actualsurfaceresistance_v1() >>> fluxes.actualsurfaceresistance actualsurfaceresistance(200.0, 132.450331, 109.174477, 101.43261) For a polar night, there is a leaf area index-dependend interpolation between soil surface resistance and a fixed resistance value: >>> inputs.possiblesunshineduration = 0.0 >>> model.calc_actualsurfaceresistance_v1() >>> fluxes.actualsurfaceresistance actualsurfaceresistance(200.0, 172.413793, 142.857143, 111.111111) For all days that are not polar nights or polar days, the values of the two examples above are weighted based on the possible sunshine duration of that day: >>> inputs.possiblesunshineduration = 12.0 >>> model.calc_actualsurfaceresistance_v1() >>> fluxes.actualsurfaceresistance actualsurfaceresistance(200.0, 149.812734, 123.765057, 106.051498) The following examples demonstrate that method |Calc_ActualSurfaceResistance_V1| works similar when applied on an hourly time basis during the daytime period (first example), during the nighttime (second example), and during dawn or dusk (third example): >>> pub.timegrids = "2019-05-31 22:00", "2019-06-01 03:00", "1h" >>> nhru(1) >>> lnk(NADELW) >>> fluxes.soilsurfaceresistance = 200.0 >>> fluxes.landusesurfaceresistance = 100.0 >>> derived.moy.update() >>> derived.hours.update() >>> lai.nadelw_jun = 5.0 >>> model.idx_sim = 2 >>> inputs.possiblesunshineduration = 1.0 >>> model.calc_actualsurfaceresistance_v1() >>> fluxes.actualsurfaceresistance actualsurfaceresistance(109.174477) >>> inputs.possiblesunshineduration = 0.0 >>> model.calc_actualsurfaceresistance_v1() >>> fluxes.actualsurfaceresistance actualsurfaceresistance(142.857143) >>> inputs.possiblesunshineduration = 0.5 >>> model.calc_actualsurfaceresistance_v1() >>> fluxes.actualsurfaceresistance actualsurfaceresistance(123.765057) .. testsetup:: >>> del pub.timegrids """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.LAI, ) DERIVEDPARAMETERS = ( lland_derived.MOY, lland_derived.Hours, ) REQUIREDSEQUENCES = ( lland_inputs.PossibleSunshineDuration, lland_fluxes.SoilSurfaceResistance, lland_fluxes.LanduseSurfaceResistance, ) RESULTSEQUENCES = (lland_fluxes.ActualSurfaceResistance,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess inp = model.sequences.inputs.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (VERS, FLUSS, SEE, WASSER): flu.actualsurfaceresistance[k] = flu.landusesurfaceresistance[k] else: d_lai = con.lai[con.lnk[k] - 1, der.moy[model.idx_sim]] d_invrestday = ( (1.0 - 0.7**d_lai) / flu.landusesurfaceresistance[k] ) + 0.7**d_lai / flu.soilsurfaceresistance[k] d_invrestnight = d_lai / 2500.0 + 1.0 / flu.soilsurfaceresistance[k] flu.actualsurfaceresistance[k] = 1.0 / ( ( inp.possiblesunshineduration / der.hours * d_invrestday + (1.0 - inp.possiblesunshineduration / der.hours) * d_invrestnight ) )
[docs] class Return_Penman_V1(modeltools.Method): r"""Calculate and return the evaporation from water surfaces according to Penman according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-DVWK1996`). Basic equation: :math:`\frac{DailySaturationVapourPressureSlope \cdot DailyNetRadiation/ LW + Days \cdot Psy \cdot (0.13 + 0.094 \cdot DailyWindSpeed2m) \cdot (DailySaturationVapourPressure - DailyActualVapourPressure)} {DailySaturationVapourPressureSlope + Psy}` Examples: We initialise seven hydrological response units. In reponse units one to three, evaporation is due to radiative forcing only. In response units four to six, evaporation is due to aerodynamic forcing only. Response unit seven shows the combined effect of both forces: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep() >>> nhru(7) >>> derived.days.update() >>> fluxes.dailynetradiation = 0.0, 50.0, 100.0, 0.0, 0.0, 0.0, 100.0 >>> fluxes.dailywindspeed2m = 2.0 >>> fluxes.dailysaturationvapourpressure = 12.0 >>> fluxes.dailysaturationvapourpressureslope = 0.8 >>> fluxes.dailyactualvapourpressure = 12.0, 12.0, 12.0, 12.0, 6.0, 0.0, 0.0 >>> from hydpy import print_values >>> for hru in range(7): ... deficit = (fluxes.dailysaturationvapourpressure[hru] - ... fluxes.dailyactualvapourpressure[hru]) ... evap = model.return_penman_v1(hru) ... print_values([fluxes.dailynetradiation[hru], deficit, evap]) 0.0, 0.0, 0.0 50.0, 0.0, 0.964611 100.0, 0.0, 1.929222 0.0, 0.0, 0.0 0.0, 6.0, 0.858928 0.0, 12.0, 1.717856 100.0, 12.0, 3.647077 The above results apply for a daily simulation time step. The following example demonstrates that we get equivalent results for hourly time steps: >>> simulationstep("1h") >>> derived.days.update() >>> fixed.lw.restore() >>> for hru in range(7): ... deficit = (fluxes.dailysaturationvapourpressure[hru] - ... fluxes.dailyactualvapourpressure[hru]) ... evap = model.return_penman_v1(hru) ... print_values([fluxes.dailynetradiation[hru], deficit, 24 * evap]) 0.0, 0.0, 0.0 50.0, 0.0, 0.964611 100.0, 0.0, 1.929222 0.0, 0.0, 0.0 0.0, 6.0, 0.858928 0.0, 12.0, 1.717856 100.0, 12.0, 3.647077 """ DERIVEDPARAMETERS = (lland_derived.Days,) FIXEDPARAMETERS = ( lland_fixed.LW, lland_fixed.Psy, ) REQUIREDSEQUENCES = ( lland_fluxes.DailySaturationVapourPressureSlope, lland_fluxes.DailyNetRadiation, lland_fluxes.DailyWindSpeed2m, lland_fluxes.DailySaturationVapourPressure, lland_fluxes.DailyActualVapourPressure, ) @staticmethod def __call__( model: modeltools.Model, k: int, ) -> float: der = model.parameters.derived.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess return ( flu.dailysaturationvapourpressureslope[k] * flu.dailynetradiation[k] / fix.lw + fix.psy * der.days * (0.13 + 0.094 * flu.dailywindspeed2m) * (flu.dailysaturationvapourpressure[k] - flu.dailyactualvapourpressure[k]) ) / (flu.dailysaturationvapourpressureslope[k] + fix.psy)
[docs] class Return_PenmanMonteith_V1(modeltools.Method): r"""Calculate and return the evapotranspiration according to Penman-Monteith according to :cite:t:`ref-LARSIM` (based on :cite:t:`ref-Thompson1981`). Basic equations: :math:`\frac{SaturationVapourPressureSlope \cdot (NetRadiation + G) + Seconds \cdot C \cdot DensitiyAir \cdot CPLuft \cdot \frac{SaturationVapourPressure - ActualVapourPressure} {AerodynamicResistance^*}} {LW \cdot (SaturationVapourPressureSlope + Psy \cdot C \cdot (1 + \frac{actualsurfaceresistance}{AerodynamicResistance^*}))}` :math:`C = 1 + \frac{b' \cdot AerodynamicResistance^*}{DensitiyAir \cdot CPLuft}` :math:`b' = 4 \cdot Emissivity \cdot Sigma / Seconds \cdot (273.15 + TKor)^3` :math:`AerodynamicResistance^* = min\Bigl(max\bigl(AerodynamicResistance, 10^{-6}\bigl), 10^6\Bigl)` Correction factor `C` takes the difference between measured temperature and actual surface temperature into account. Example: We build the following example on the first example of the documentation on method |Return_Penman_V1|. The total available energy (|NetRadiation| plus |G|) and the vapour saturation pressure deficit (|SaturationVapourPressure| minus |ActualVapourPressure| are identical. To make the results roughly comparable, we use resistances suitable for water surfaces through setting |ActualSurfaceResistance| to zero and |AerodynamicResistance| to a reasonable precalculated value of 106 s/m: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep() >>> nhru(7) >>> emissivity(0.96) >>> derived.seconds.update() >>> fluxes.netradiation = 10.0, 50.0, 100.0, 10.0, 10.0, 10.0, 100.0 >>> fluxes.g = -10.0 >>> fluxes.saturationvapourpressure = 12.0 >>> fluxes.saturationvapourpressureslope = 0.8 >>> fluxes.actualvapourpressure = 12.0, 12.0, 12.0, 12.0, 6.0, 0.0, 0.0 >>> fluxes.densityair = 1.24 >>> fluxes.actualsurfaceresistance = 0.0 >>> fluxes.aerodynamicresistance = 106.0 >>> fluxes.tkor = 10.0 For the first three hydrological response units with energy forcing only, there is a relatively small deviation to the results of method |Return_Penman_v1| due to the correction factor `C`, which is only implemented by method |Return_PenmanMonteith_v1|. For response units four to six with dynamic forcing only, the results of method |Return_PenmanMonteith_v1| are more than twice as large as those of method |Return_Penman_v1|: >>> from hydpy import print_values >>> for hru in range(7): ... deficit = (fluxes.saturationvapourpressure[hru] - ... fluxes.actualvapourpressure[hru]) ... evap = model.return_penmanmonteith_v1( ... hru, fluxes.actualsurfaceresistance[hru]) ... print_values([fluxes.netradiation[hru]+fluxes.g[hru], deficit, evap]) 0.0, 0.0, 0.0 40.0, 0.0, 0.648881 90.0, 0.0, 1.459982 0.0, 0.0, 0.0 0.0, 6.0, 2.031724 0.0, 12.0, 4.063447 90.0, 12.0, 5.523429 Next, we repeat the above calculations using resistances relevant for vegetated surfaces (note that this also changes the results of the first three response units due to correction factor `C` depending on |AerodynamicResistance|): >>> fluxes.actualsurfaceresistance = 80.0 >>> fluxes.aerodynamicresistance = 40.0 >>> for hru in range(7): ... deficit = (fluxes.saturationvapourpressure[hru] - ... fluxes.actualvapourpressure[hru]) ... evap = model.return_penmanmonteith_v1( ... hru, fluxes.actualsurfaceresistance[hru]) ... print_values([fluxes.netradiation[hru]+fluxes.g[hru], deficit, evap]) 0.0, 0.0, 0.0 40.0, 0.0, 0.364933 90.0, 0.0, 0.8211 0.0, 0.0, 0.0 0.0, 6.0, 2.469986 0.0, 12.0, 4.939972 90.0, 12.0, 5.761072 The above results are sensitive to the relation of |ActualSurfaceResistance| and |AerodynamicResistance|. The following example demonstrates this sensitivity through varying |ActualSurfaceResistance| over a wide range: >>> fluxes.netradiation = 100.0 >>> fluxes.actualvapourpressure = 0.0 >>> fluxes.actualsurfaceresistance = ( ... 0.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0) >>> for hru in range(7): ... print_values([fluxes.actualsurfaceresistance[hru], ... model.return_penmanmonteith_v1( ... hru, fluxes.actualsurfaceresistance[hru])]) 0.0, 11.370311 20.0, 9.144449 50.0, 7.068767 100.0, 5.128562 200.0, 3.31099 500.0, 1.604779 1000.0, 0.863312 One potential pitfall of the given Penman-Monteith equation is that |AerodynamicResistance| becomes infinite for zero windspeed. We protect method |Return_PenmanMonteith_V1| against this problem (and the less likely problem of zero aerodynamic resistance) by limiting the value of |AerodynamicResistance| to the interval :math:`[10^{-6}, 10^6]`: >>> fluxes.actualvapourpressure = 6.0 >>> fluxes.actualsurfaceresistance = 80.0 >>> fluxes.aerodynamicresistance = (0.0, 1e-6, 1e-3, 1.0, 1e3, 1e6, inf) >>> for hru in range(7): ... print_values([fluxes.aerodynamicresistance[hru], ... model.return_penmanmonteith_v1( ... hru, fluxes.actualsurfaceresistance[hru])]) 0.0, 5.00683 0.000001, 5.00683 0.001, 5.006739 1.0, 4.918573 1000.0, 0.887816 1000000.0, 0.001372 inf, 0.001372 Now we change the simulation time step from one day to one hour to demonstrate that we can reproduce the results of the first example, which requires to adjust |NetRadiation| and |WG|: >>> simulationstep("1h") >>> derived.seconds.update() >>> fixed.lw.restore() >>> fixed.cpluft.restore() >>> fluxes.netradiation = 10.0, 50.0, 100.0, 10.0, 10.0, 10.0, 100.0 >>> fluxes.actualvapourpressure = 12.0, 12.0, 12.0, 12.0, 6.0, 0.0, 0.0 >>> fluxes.actualsurfaceresistance = 0.0 >>> fluxes.aerodynamicresistance = 106.0 >>> for hru in range(7): ... deficit = (fluxes.saturationvapourpressure[hru] - ... fluxes.actualvapourpressure[hru]) ... evap = 24 * model.return_penmanmonteith_v1( ... hru, fluxes.actualsurfaceresistance[hru]) ... print_values([fluxes.netradiation[hru]+fluxes.g[hru], deficit, evap]) 0.0, 0.0, 0.0 40.0, 0.0, 0.648881 90.0, 0.0, 1.459982 0.0, 0.0, 0.0 0.0, 6.0, 2.031724 0.0, 12.0, 4.063447 90.0, 12.0, 5.523429 """ CONTROLPARAMETERS = (lland_control.Emissivity,) DERIVEDPARAMETERS = (lland_derived.Seconds,) FIXEDPARAMETERS = ( lland_fixed.Sigma, lland_fixed.LW, lland_fixed.CPLuft, lland_fixed.Psy, ) REQUIREDSEQUENCES = ( lland_fluxes.NetRadiation, lland_fluxes.G, lland_fluxes.TKor, lland_fluxes.SaturationVapourPressureSlope, lland_fluxes.SaturationVapourPressure, lland_fluxes.ActualVapourPressure, lland_fluxes.DensityAir, lland_fluxes.AerodynamicResistance, ) @staticmethod def __call__( model: modeltools.Model, k: int, actualsurfaceresistance: float, ) -> float: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess d_ar = min(max(flu.aerodynamicresistance[k], 1e-6), 1e6) d_t = 273.15 + flu.tkor[k] d_b = (4.0 * con.emissivity * fix.sigma / der.seconds) * d_t**3 d_c = 1.0 + d_b * d_ar / flu.densityair[k] / fix.cpluft return ( ( flu.saturationvapourpressureslope[k] * (flu.netradiation[k] + flu.g[k]) + der.seconds * d_c * flu.densityair[k] * fix.cpluft * (flu.saturationvapourpressure[k] - flu.actualvapourpressure[k]) / d_ar ) / ( flu.saturationvapourpressureslope[k] + fix.psy * d_c * (1.0 + actualsurfaceresistance / d_ar) ) / fix.lw )
[docs] class Calc_EvPo_V2(modeltools.Method): """Calculate the potential evaporation according to Penman or Penman-Monteith. Method |Calc_EvPo_V2| applies method |Return_Penman_V1| on all water areas and method |Return_PenmanMonteith_V1| on all other hydrological response units. For Penman-Monteith, we yield potential values by passing zero surface resistance values to method |Return_PenmanMonteith_V1|. Example: We recalculate the results for the seventh hydrological response unit of the first example of the documention on the methods |Return_Penman_V1| and |Return_PenmanMonteith_V1|. The calculated potential values differ markedly: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep() >>> nhru(2) >>> lnk(WASSER, ACKER) >>> emissivity(0.96) >>> derived.nmblogentries.update() >>> derived.seconds.update() >>> derived.days.update() >>> fluxes.netradiation = 90.0 >>> fluxes.dailynetradiation = 80.0 >>> fluxes.g = -10.0 >>> fluxes.saturationvapourpressure = 12.0 >>> fluxes.dailysaturationvapourpressure = 12.0 >>> fluxes.saturationvapourpressureslope = 0.8 >>> fluxes.dailysaturationvapourpressureslope = 0.8 >>> fluxes.actualvapourpressure = 0.0 >>> fluxes.dailyactualvapourpressure = 0.0 >>> fluxes.windspeed2m = 2.0 >>> fluxes.dailywindspeed2m = 2.0 >>> fluxes.tkor = 10.0 >>> fluxes.densityair = 1.24 >>> fluxes.aerodynamicresistance = 106.0 >>> model.calc_evpo_v2() >>> fluxes.evpo evpo(3.261233, 5.361209) """ SUBMETHODS = ( Return_Penman_V1, Return_PenmanMonteith_V1, ) CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.Emissivity, ) DERIVEDPARAMETERS = ( lland_derived.Days, lland_derived.Seconds, ) FIXEDPARAMETERS = ( lland_fixed.Sigma, lland_fixed.LW, lland_fixed.CPLuft, lland_fixed.Psy, ) REQUIREDSEQUENCES = ( lland_fluxes.NetRadiation, lland_fluxes.G, lland_fluxes.TKor, lland_fluxes.SaturationVapourPressureSlope, lland_fluxes.SaturationVapourPressure, lland_fluxes.ActualVapourPressure, lland_fluxes.DensityAir, lland_fluxes.AerodynamicResistance, lland_fluxes.DailySaturationVapourPressureSlope, lland_fluxes.DailyNetRadiation, lland_fluxes.DailyWindSpeed2m, lland_fluxes.DailySaturationVapourPressure, lland_fluxes.DailyActualVapourPressure, ) RESULTSEQUENCES = (lland_fluxes.EvPo,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, SEE, FLUSS): flu.evpo[k] = model.return_penman_v1(k) else: flu.evpo[k] = model.return_penmanmonteith_v1(k, 0.0)
[docs] class Calc_EvS_WAeS_WATS_V1(modeltools.Method): """Calculate the evaporation from the snow layer, if any exists, and update the snow cover accordingly. Basic equations: :math:`WAeS_{new} = f \\cdot WAeS_{old}` :math:`WATS_{new} = f \\cdot WATS_{old}` :math:`f = \\frac{WAeS-EvS}{WAeS}` :math:`EvS = max\\left(\\frac{WLatSnow}{L}, WAeS\\right)` Example: The first hydrological response unit shows that method |Calc_EvS_WAeS_WATS_V1| does calculate condensation and deposition if a negative flux of latent heat suggests so. We define the relative amounts of condensation and deposition so that the fraction between the frozen and the total water content of the snow layer does not change. The same holds for vaporisation and sublimation, as shown by response units to to seven. Note that method |Calc_EvS_WAeS_WATS_V1| prevents negative water contents but does not prevent to high liquid water contents, which eventually needs to be corrected by another method called subsequentially. The last response unit shows that method |Calc_EvS_WAeS_WATS_V1| sets |EvS|, |WATS| and |WAeS| to zero for water areas: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(8) >>> lnk(ACKER, ACKER, ACKER, ACKER, ACKER, ACKER, ACKER, WASSER) >>> fluxes.wlatsnow = -25.0, 0.0, 50.0, 90.0, 150.0, 150.0, 150.0, 150.0 >>> states.waes = 2.0, 2.0, 2.0, 2.0, 2.0, 1.5, 0.0, 2.0 >>> states.wats = 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.0, 1.0 >>> model.calc_evs_waes_wats_v1() >>> fluxes.evs evs(-0.404881, 0.0, 0.809762, 1.457572, 2.0, 1.5, 0.0, 0.0) >>> states.waes waes(2.404881, 2.0, 1.190238, 0.542428, 0.0, 0.0, 0.0, 0.0) >>> states.wats wats(1.202441, 1.0, 0.595119, 0.271214, 0.0, 0.0, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) FIXEDPARAMETERS = (lland_fixed.LWE,) REQUIREDSEQUENCES = (lland_fluxes.WLatSnow,) RESULTSEQUENCES = (lland_fluxes.EvS,) UPDATEDSEQUENCES = ( lland_states.WAeS, lland_states.WATS, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess fix = model.parameters.fixed.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, SEE, FLUSS) or (sta.waes[k] <= 0.0): flu.evs[k] = 0.0 sta.waes[k] = 0.0 sta.wats[k] = 0.0 else: flu.evs[k] = min(flu.wlatsnow[k] / fix.lwe, sta.waes[k]) d_frac = (sta.waes[k] - flu.evs[k]) / sta.waes[k] sta.waes[k] *= d_frac sta.wats[k] *= d_frac
[docs] class Calc_EvI_Inzp_V1(modeltools.Method): """Calculate interception evaporation and update the interception storage accordingly. Basic equation: :math:`\\frac{dInzp}{dt} = -EvI` .. math:: EvI = \\begin{cases} EvPo &|\\ Inzp > 0 \\\\ 0 &|\\ Inzp = 0 \\end{cases} Examples: For all "land response units" like arable land (|ACKER|), interception evaporation (|EvI|) is identical with potential evapotranspiration (|EvPo|) as long as it is met by available intercepted water (|Inzp|): >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(3) >>> lnk(ACKER) >>> states.inzp = 0.0, 2.0, 4.0 >>> fluxes.evpo = 3.0 >>> model.calc_evi_inzp_v1() >>> states.inzp inzp(0.0, 0.0, 1.0) >>> fluxes.evi evi(0.0, 2.0, 3.0) For water areas (|WASSER|, |FLUSS| and |SEE|), |EvI| is generally equal to |EvPo| (but this might be corrected by a method called after |Calc_EvI_Inzp_V1| has been applied) and |Inzp| is set to zero: >>> lnk(WASSER, FLUSS, SEE) >>> states.inzp = 2.0 >>> fluxes.evpo = 3.0 >>> model.calc_evi_inzp_v1() >>> states.inzp inzp(0.0, 0.0, 0.0) >>> fluxes.evi evi(3.0, 3.0, 3.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = (lland_fluxes.EvPo,) UPDATEDSEQUENCES = (lland_states.Inzp,) RESULTSEQUENCES = (lland_fluxes.EvI,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.evi[k] = flu.evpo[k] sta.inzp[k] = 0.0 else: flu.evi[k] = min(flu.evpo[k], sta.inzp[k]) sta.inzp[k] -= flu.evi[k]
[docs] class Calc_EvI_Inzp_V2(modeltools.Method): r"""Calculate interception evaporation and update the interception storage accordingly. Basic equation: :math:`\frac{dInzp}{dt} = -EvI` .. math:: EvI = \begin{cases} EvPo &|\ Inzp > 0 \land ( Forest \lor WAeS = 0 ) \\ 0 &|\ Inzp = 0 \lor ( \lnot Forest \land WAeS > 0 ) \end{cases} Examples: For all forest land-use types (|LAUBW|, |MISCHW|, |NADELW|), interception evaporation (|EvI|) is identical with potential evapotranspiration (|EvPo|) as long as it is met by available intercepted water (|Inzp|): >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(3) >>> lnk(LAUBW, MISCHW, NADELW) >>> states.inzp = 0.0, 2.0, 4.0 >>> states.waes = 0.0, 0.0, 1.0 >>> fluxes.evpo = 3.0 >>> model.calc_evi_inzp_v2() >>> states.inzp inzp(0.0, 0.0, 1.0) >>> fluxes.evi evi(0.0, 2.0, 3.0) For all other land areas, no interception evaporation occurs when a snow cover is present (indicated by a |WAeS| values larger zero): >>> lnk(ACKER) >>> states.inzp = 0.0, 2.0, 4.0 >>> model.calc_evi_inzp_v2() >>> states.inzp inzp(0.0, 0.0, 4.0) >>> fluxes.evi evi(0.0, 2.0, 0.0) For water areas (|WASSER|, |FLUSS| and |SEE|), |EvI| is generally equal to |EvPo| (but this might be corrected by a method called after |Calc_EvI_Inzp_V2| has been applied) and |Inzp| is set to zero: >>> lnk(WASSER, FLUSS, SEE) >>> states.inzp = 2.0 >>> fluxes.evpo = 3.0 >>> model.calc_evi_inzp_v1() >>> states.inzp inzp(0.0, 0.0, 0.0) >>> fluxes.evi evi(3.0, 3.0, 3.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = ( lland_fluxes.EvPo, lland_states.WAeS, ) UPDATEDSEQUENCES = (lland_states.Inzp,) RESULTSEQUENCES = (lland_fluxes.EvI,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.evi[k] = flu.evpo[k] sta.inzp[k] = 0.0 elif con.lnk[k] in (LAUBW, MISCHW, NADELW) or sta.waes[k] == 0: flu.evi[k] = min(flu.evpo[k], sta.inzp[k]) sta.inzp[k] -= flu.evi[k] else: flu.evi[k] = 0.0
[docs] class Calc_EvI_Inzp_V3(modeltools.Method): r"""Calculate interception evaporation and update the interception storage accordingly if the snow interception storage is empty. Basic equation: :math:`\frac{dInzp}{dt} = -EvI` .. math:: EvI = \begin{cases} EvPo &|\ Inzp > 0 \land \big( ( Forest \land SInz = 0 ) \lor ( \lnot Forest \land WAeS = 0 ) \big) \\ 0 &|\ Inzp = 0 \lor \big( ( Forest \land SInz > 0) \lor ( \lnot Forest \land WAeS > 0 ) \big) \end{cases} Examples: For all forest land-use types (|LAUBW|, |MISCHW|, |NADELW|), interception evaporation (|EvI|) is identical with potential evapotranspiration (|EvPo|) as long as it is met by available intercepted water (|Inzp|) and there is no intercepted snow (indicated by |SInz| values larger zero): >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(4) >>> lnk(LAUBW, MISCHW, NADELW, NADELW) >>> states.inzp = 2.0, 4.0, 4.0, 4.0 >>> states.sinz = 0.0, 0.0, 1.0, 0.0 >>> states.waes = 0.0, 0.0, 0.0, 1.0 >>> fluxes.evpo = 3.0 >>> model.calc_evi_inzp_v3() >>> states.inzp inzp(0.0, 1.0, 4.0, 1.0) >>> fluxes.evi evi(2.0, 3.0, 0.0, 3.0) For all other land areas, no interception evaporation occurs when a snow cover is present (indicated by a |WAeS| values larger zero): >>> lnk(ACKER) >>> states.inzp = 2.0, 4.0, 4.0, 4.0 >>> model.calc_evi_inzp_v3() >>> states.inzp inzp(0.0, 1.0, 1.0, 4.0) >>> fluxes.evi evi(2.0, 3.0, 3.0, 0.0) For water areas (|WASSER|, |FLUSS| and |SEE|), |EvI| is generally equal to |EvPo| (but this might be corrected by a method called after |Calc_EvI_Inzp_V3| has been applied) and |Inzp| is set to zero: >>> lnk(WASSER, FLUSS, SEE, SEE) >>> states.inzp = 2.0 >>> fluxes.evpo = 3.0 >>> model.calc_evi_inzp_v3() >>> states.inzp inzp(0.0, 0.0, 0.0, 0.0) >>> fluxes.evi evi(3.0, 3.0, 3.0, 3.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, ) REQUIREDSEQUENCES = ( lland_fluxes.EvPo, lland_states.SInz, lland_states.WAeS, ) UPDATEDSEQUENCES = (lland_states.Inzp,) RESULTSEQUENCES = (lland_fluxes.EvI,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (WASSER, FLUSS, SEE): flu.evi[k] = flu.evpo[k] sta.inzp[k] = 0.0 elif con.lnk[k] in (LAUBW, MISCHW, NADELW): if sta.sinz[k] == 0.0: flu.evi[k] = min(flu.evpo[k], sta.inzp[k]) sta.inzp[k] -= flu.evi[k] else: flu.evi[k] = 0.0 elif sta.waes[k] == 0.0: flu.evi[k] = min(flu.evpo[k], sta.inzp[k]) sta.inzp[k] -= flu.evi[k] else: flu.evi[k] = 0.0
[docs] class Calc_EvB_V2(modeltools.Method): """Calculate the actual evapotranspiration from the soil. Basic equation: :math:`EvB = \\frac{EvPo - EvI}{EvPo} \\cdot Return\\_PenmanMonteith\\_V1(ActualSurfaceResistance)` Example: For sealed surfaces, water areas and snow-covered hydrological response units that are not forests, there is no soil evapotranspiration: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep() >>> nhru(6) >>> lnk(VERS, WASSER, FLUSS, SEE, ACKER, ACKER) >>> states.waes = 0.0, 0.0, 0.0, 0.0, 0.1, 10.0 >>> model.calc_evb_v2() >>> fluxes.evb evb(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) For all other cases, method |Calc_EvB_V2| first applies method |Return_PenmanMonteith_V1| (of which's documentation we take most of the following test configuration) and adjusts the returned soil evapotranspiration to the (prioritised) interception evaporation in accordance with the base equation defined above: >>> lnk(NADELW) >>> emissivity(0.96) >>> derived.seconds.update() >>> fluxes.netradiation = 100.0, 100.0, 100.0, 100.0, 100.0, -300.0 >>> fluxes.g = -11.574074074074 >>> fluxes.saturationvapourpressure = 12.0 >>> fluxes.saturationvapourpressureslope = 0.8 >>> fluxes.actualvapourpressure = 0.0 >>> fluxes.densityair = 1.24 >>> fluxes.actualsurfaceresistance = 0.0 >>> fluxes.aerodynamicresistance = 106.0 >>> fluxes.tkor = 10.0 >>> fluxes.evpo = 0.0, 6.0, 6.0, 6.0, 6.0, -6.0 >>> fluxes.evi = 0.0, 0.0, 2.0, 4.0, 6.0, -3.0 >>> model.calc_evb_v2() >>> fluxes.evb evb(0.0, 5.497895, 3.665263, 1.832632, 0.0, -0.495458) """ SUBMETHODS = (Return_PenmanMonteith_V1,) CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.Emissivity, ) DERIVEDPARAMETERS = (lland_derived.Seconds,) FIXEDPARAMETERS = ( lland_fixed.Sigma, lland_fixed.LW, lland_fixed.CPLuft, lland_fixed.Psy, ) REQUIREDSEQUENCES = ( lland_fluxes.NetRadiation, lland_fluxes.G, lland_fluxes.TKor, lland_fluxes.SaturationVapourPressureSlope, lland_fluxes.SaturationVapourPressure, lland_fluxes.ActualVapourPressure, lland_fluxes.DensityAir, lland_fluxes.AerodynamicResistance, lland_fluxes.ActualSurfaceResistance, lland_fluxes.EvPo, lland_fluxes.EvI, lland_states.WAeS, ) RESULTSEQUENCES = (lland_fluxes.EvB,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if ( (con.lnk[k] in (VERS, WASSER, FLUSS, SEE)) or ((con.lnk[k] not in (LAUBW, MISCHW, NADELW) and sta.waes[k] > 0.0)) or (flu.evpo[k] == 0.0) ): flu.evb[k] = 0.0 else: flu.evb[k] = ( (flu.evpo[k] - flu.evi[k]) / flu.evpo[k] * model.return_penmanmonteith_v1( k, flu.actualsurfaceresistance[k], ) )
[docs] class Calc_QKap_V1(modeltools.Method): """Calculate the capillary rise. Basic equation: :math:`QKap = KapMax \\cdot min\\left(max\\left(1 - \\frac{BoWa-KapGrenz_1}{KapGrenz_2-KapGrenz_1}, 0 \\right), 1 \\right)` Examples: We prepare six hydrological response units of landuse type |ACKER| with different soil water contents: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(6) >>> lnk(ACKER) >>> wmax(100.0) >>> states.bowa(0.0, 20.0, 40.0, 60.0, 80.0, 100.0) The maximum capillary rise is 3 mm/d (which is 1.5 mm for the actual simulation time step of 12 hours): >>> kapmax(3.0) Please read the documentation on parameter |KapGrenz|, which gives some examples on how to configure the capillary rise thresholds. The following examples show the calculation results for with and without a range of linear transition: >>> kapgrenz(20.0, 80.0) >>> model.calc_qkap_v1() >>> fluxes.qkap qkap(1.5, 1.5, 1.0, 0.5, 0.0, 0.0) >>> kapgrenz(40.0, 40.0) >>> model.calc_qkap_v1() >>> fluxes.qkap qkap(1.5, 1.5, 1.5, 0.0, 0.0, 0.0) You are allowed to set the lower threshold to values lower than zero and the larger one to values larger than |WMax|: >>> kapgrenz(-50.0, 150.0) >>> model.calc_qkap_v1() >>> fluxes.qkap qkap(1.125, 0.975, 0.825, 0.675, 0.525, 0.375) For water areas and sealed surfaces, capillary rise is always zero: >>> lnk(WASSER, FLUSS, SEE, VERS, VERS, VERS) >>> model.calc_qkap_v1() >>> fluxes.qkap qkap(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.KapMax, lland_control.KapGrenz, lland_control.WMax, ) REQUIREDSEQUENCES = (lland_states.BoWa,) RESULTSEQUENCES = (lland_fluxes.QKap,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if (con.lnk[k] in (VERS, WASSER, FLUSS, SEE)) or (con.wmax[k] <= 0.0): flu.qkap[k] = 0.0 elif sta.bowa[k] <= con.kapgrenz[k, 0]: flu.qkap[k] = con.kapmax[k] elif sta.bowa[k] <= con.kapgrenz[k, 1]: flu.qkap[k] = con.kapmax[k] * ( 1.0 - (sta.bowa[k] - con.kapgrenz[k, 0]) / (con.kapgrenz[k, 1] - con.kapgrenz[k, 0]) ) else: flu.qkap[k] = 0
[docs] class Calc_QBB_V1(modeltools.Method): """Calculate the amount of base flow released from the soil. Basic equations: .. math:: QBB = \\begin{cases} 0 &|\\ BoWa \\leq PWP \\;\\; \\lor \\;\\; (BoWa \\leq NFk \\;\\; \\land \\;\\; RBeta) \\\\ Beta_{eff} \\cdot (BoWa - PWP) &|\\ BoWa > NFk \\;\\; \\lor \\;\\; (BoWa > PWP \\;\\; \\land \\;\\; \\overline{RBeta}) \\end{cases} .. math:: Beta_{eff} = \\begin{cases} Beta &|\\ BoWa \\leq FK \\\\ Beta \\cdot \\left(1+(FBeta-1)\\cdot\\frac{BoWa-FK}{WMax-FK}\\right) &|\\ BoWa > FK \\end{cases} Examples: For water and sealed areas, no base flow is calculated (see the first three hydrological response units of type |VERS|, |FLUSS|, and |SEE|). No principal distinction is made between the remaining land use classes (arable land |ACKER| has been selected for the last five HRUs arbitrarily): >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(8) >>> lnk(FLUSS, SEE, VERS, ACKER, ACKER, ACKER, ACKER, ACKER) >>> beta(0.04) >>> fbeta(2.0) >>> wmax(0.0, 0.0, 0.0, 0.0, 100.0, 100.0, 100.0, 200.0) >>> fk(70.0) >>> pwp(10.0) >>> rbeta(False) Note the time dependence of parameter |Beta|: >>> beta beta(0.04) >>> beta.values array([0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02]) In the first example, the actual soil water content |BoWa| is set to low values. For values below the threshold |PWP|, no percolation occurs. Above |PWP| (but below |FK|), |QBB| increases linearly by an amount defined by parameter |Beta|: >>> states.bowa = 20.0, 20.0, 20.0, 0.0, 0.0, 10.0, 20.0, 20.0 >>> model.calc_qbb_v1() >>> fluxes.qbb qbb(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.2) Note that for the last two response units the same amount of base flow generation is determined, in spite of the fact that both exhibit different relative soil moistures. If we set the reduction option |RBeta| to |False|, |QBB| is reduced to zero as long as |BoWa| does not exceed |FK|: >>> rbeta(True) >>> model.calc_qbb_v1() >>> fluxes.qbb qbb(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) In the second example, we set the actual soil water content |BoWa| to high values. For values below threshold |FK|, the discussion above remains valid. For values above |FK|, percolation shows a nonlinear behaviour when factor |FBeta| is set to values larger than one: >>> rbeta(False) >>> wmax(0.0, 0.0, 0.0, 100.0, 100.0, 100.0, 100.0, 200.0) >>> fk(70.0) >>> pwp(10.0) >>> states.bowa = 0.0, 0.0, 0.0, 60.0, 70.0, 80.0, 100.0, 200.0 >>> model.calc_qbb_v1() >>> fluxes.qbb qbb(0.0, 0.0, 0.0, 1.0, 1.2, 1.866667, 3.6, 7.6) >>> rbeta(True) >>> model.calc_qbb_v1() >>> fluxes.qbb qbb(0.0, 0.0, 0.0, 0.0, 0.0, 1.866667, 3.6, 7.6) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.WMax, lland_control.Beta, lland_control.FBeta, lland_control.RBeta, lland_control.PWP, lland_control.FK, ) REQUIREDSEQUENCES = (lland_states.BoWa,) RESULTSEQUENCES = (lland_fluxes.QBB,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if ( (con.lnk[k] in (VERS, WASSER, FLUSS, SEE)) or (sta.bowa[k] <= con.pwp[k]) or (con.wmax[k] <= 0.0) ): flu.qbb[k] = 0.0 elif sta.bowa[k] <= con.fk[k]: if con.rbeta: flu.qbb[k] = 0.0 else: flu.qbb[k] = con.beta[k] * (sta.bowa[k] - con.pwp[k]) else: flu.qbb[k] = ( con.beta[k] * (sta.bowa[k] - con.pwp[k]) * ( 1.0 + (con.fbeta[k] - 1.0) * (sta.bowa[k] - con.fk[k]) / (con.wmax[k] - con.fk[k]) ) )
[docs] class Calc_QIB1_V1(modeltools.Method): """Calculate the first inflow component released from the soil. Basic equation: :math:`QIB1 = DMin \\cdot \\frac{BoWa}{WMax}` Examples: For water and sealed areas, no interflow is calculated (the first three HRUs are of type |FLUSS|, |SEE|, and |VERS|, respectively). No principal distinction is made between the remaining land use classes (arable land |ACKER| has been selected for the last five HRUs arbitrarily): >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(8) >>> lnk(FLUSS, SEE, VERS, ACKER, ACKER, ACKER, ACKER, ACKER) >>> dmax(10.0) >>> dmin(4.0) >>> wmax(101.0, 101.0, 101.0, 0.0, 101.0, 101.0, 101.0, 202.0) >>> pwp(10.0) >>> states.bowa = 10.1, 10.1, 10.1, 0.0, 0.0, 10.0, 10.1, 10.1 Note the time dependence of parameter |DMin|: >>> dmin dmin(4.0) >>> dmin.values array([2., 2., 2., 2., 2., 2., 2., 2.]) Compared to the calculation of |QBB|, the following results show some relevant differences: >>> model.calc_qib1_v1() >>> fluxes.qib1 qib1(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.1) Firstly, as demonstrated with the help of the seventh and the eight HRU, the generation of the first interflow component |QIB1| depends on relative soil moisture. Secondly, as demonstrated with the help the sixth and seventh HRU, it starts abruptly whenever the slightest exceedance of the threshold parameter |PWP| occurs. Such sharp discontinuouties are a potential source of trouble. """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.DMin, lland_control.WMax, lland_control.PWP, ) REQUIREDSEQUENCES = (lland_states.BoWa,) RESULTSEQUENCES = (lland_fluxes.QIB1,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if (con.lnk[k] in (VERS, WASSER, FLUSS, SEE)) or ( sta.bowa[k] <= con.pwp[k] ): flu.qib1[k] = 0.0 else: flu.qib1[k] = con.dmin[k] * (sta.bowa[k] / con.wmax[k])
[docs] class Calc_QIB2_V1(modeltools.Method): """Calculate the second inflow component released from the soil. Basic equation: :math:`QIB2 = (DMax-DMin) \\cdot \\left(\\frac{BoWa-FK}{WMax-FK}\\right)^\\frac{3}{2}` Examples: For water and sealed areas, no interflow is calculated (the first three HRUs are of type |FLUSS|, |SEE|, and |VERS|, respectively). No principal distinction is made between the remaining land use classes (arable land |ACKER| has been selected for the last five HRUs arbitrarily): >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> nhru(8) >>> lnk(FLUSS, SEE, VERS, ACKER, ACKER, ACKER, ACKER, ACKER) >>> dmax(10.0) >>> dmin(4.0) >>> wmax(100.0, 100.0, 100.0, 50.0, 100.0, 100.0, 100.0, 200.0) >>> fk(50.0) >>> states.bowa = 100.0, 100.0, 100.0, 50.1, 50.0, 75.0, 100.0, 100.0 Note the time dependence of parameters |DMin| (see the example above) and |DMax|: >>> dmax dmax(10.0) >>> dmax.values array([5., 5., 5., 5., 5., 5., 5., 5.]) The following results show that he calculation of |QIB2| both resembles those of |QBB| and |QIB1| in some regards: >>> model.calc_qib2_v1() >>> fluxes.qib2 qib2(0.0, 0.0, 0.0, 0.0, 0.0, 1.06066, 3.0, 0.57735) In the given example, the maximum rate of total interflow generation is 5 mm/12h (parameter |DMax|). For the seventh zone, which contains a saturated soil, the value calculated for the second interflow component (|QIB2|) is 3 mm/h. The "missing" value of 2 mm/12h is be calculated by method |Calc_QIB1_V1|. (The fourth zone, which is slightly oversaturated, is only intended to demonstrate that zero division due to |WMax| = |FK| is circumvented.) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.WMax, lland_control.DMax, lland_control.DMin, lland_control.FK, ) REQUIREDSEQUENCES = (lland_states.BoWa,) RESULTSEQUENCES = (lland_fluxes.QIB2,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if ( (con.lnk[k] in (VERS, WASSER, FLUSS, SEE)) or (sta.bowa[k] <= con.fk[k]) or (con.wmax[k] <= con.fk[k]) ): flu.qib2[k] = 0.0 else: flu.qib2[k] = (con.dmax[k] - con.dmin[k]) * ( (sta.bowa[k] - con.fk[k]) / (con.wmax[k] - con.fk[k]) ) ** 1.5
[docs] class Calc_QDB_V1(modeltools.Method): """Calculate direct runoff released from the soil. Basic equations: .. math:: QDB = \\begin{cases} max\\bigl(Exz, 0\\bigl) &|\\ SfA \\leq 0 \\\\ max\\bigl(Exz + WMax \\cdot SfA^{BSf+1}, 0\\bigl) &|\\ SfA > 0 \\end{cases} :math:`SFA = \\left(1 - \\frac{BoWa}{WMax}\\right)^\\frac{1}{BSf+1} - \\frac{WaDa}{(BSf+1) \\cdot WMax}` :math:`Exz = (BoWa + WaDa) - WMax` Examples: For water areas (|FLUSS| and |SEE|), sealed areas (|VERS|), and areas without any soil storage capacity, all water is completely routed as direct runoff |QDB| (see the first four HRUs). No principal distinction is made between the remaining land use classes (arable land |ACKER| has been selected for the last five HRUs arbitrarily): >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(9) >>> lnk(FLUSS, SEE, VERS, ACKER, ACKER, ACKER, ACKER, ACKER, ACKER) >>> bsf(0.4) >>> wmax(100.0, 100.0, 100.0, 0.0, 100.0, 100.0, 100.0, 100.0, 100.0) >>> fluxes.wada = 10.0 >>> states.bowa = ( ... 100.0, 100.0, 100.0, 0.0, -0.1, 0.0, 50.0, 100.0, 100.1) >>> model.calc_qdb_v1() >>> fluxes.qdb qdb(10.0, 10.0, 10.0, 10.0, 0.142039, 0.144959, 1.993649, 10.0, 10.1) With the common |BSf| value of 0.4, the discharge coefficient increases more or less exponentially with soil moisture. For soil moisture values slightly below zero or above usable field capacity, plausible amounts of generated direct runoff are ensured. """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.WMax, lland_control.BSf, ) REQUIREDSEQUENCES = ( lland_fluxes.WaDa, lland_states.BoWa, ) RESULTSEQUENCES = (lland_fluxes.QDB,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] == WASSER: flu.qdb[k] = 0.0 elif (con.lnk[k] in (VERS, FLUSS, SEE)) or (con.wmax[k] <= 0.0): flu.qdb[k] = flu.wada[k] else: if sta.bowa[k] < con.wmax[k]: d_sfa = (1.0 - sta.bowa[k] / con.wmax[k]) ** ( 1.0 / (con.bsf[k] + 1.0) ) - (flu.wada[k] / ((con.bsf[k] + 1.0) * con.wmax[k])) else: d_sfa = 0.0 d_exz = sta.bowa[k] + flu.wada[k] - con.wmax[k] flu.qdb[k] = d_exz if d_sfa > 0.0: flu.qdb[k] += d_sfa ** (con.bsf[k] + 1.0) * con.wmax[k] flu.qdb[k] = max(flu.qdb[k], 0.0)
[docs] class Update_QDB_V1(modeltools.Method): """Update the direct runoff according to the degree of frost sealing. Basic equation: :math:`QDB_{updated} = QDB + FVG \\cdot (WaDa - QDB)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> fluxes.qdb(5.0) >>> fluxes.wada(10.0) >>> fluxes.fvg(0.2) >>> model.update_qdb_v1() >>> fluxes.qdb qdb(6.0, 6.0, 6.0) """ CONTROLPARAMETERS = (lland_control.NHRU,) REQUIREDSEQUENCES = ( lland_fluxes.WaDa, lland_fluxes.FVG, ) UPDATEDSEQUENCES = (lland_fluxes.QDB,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess for k in range(con.nhru): flu.qdb[k] += flu.fvg[k] * (flu.wada[k] - flu.qdb[k])
[docs] class Calc_BoWa_V1(modeltools.Method): """Update the soil moisture and, if necessary, correct the ingoing and outgoing fluxes. Basic equations: :math:`\\frac{dBoWa}{dt} = WaDa + Qkap - EvB - QBB - QIB1 - QIB2 - QDB` :math:`0 \\leq BoWa \\leq WMax` Examples: For water areas and sealed surfaces, we simply set soil moisture |BoWa| to zero and do not need to perform any flux corrections: >>> from hydpy.models.lland import * >>> parameterstep("1d") >>> nhru(4) >>> lnk(FLUSS, SEE, WASSER, VERS) >>> states.bowa = 100.0 >>> model.calc_bowa_v1() >>> states.bowa bowa(0.0, 0.0, 0.0, 0.0) We make no principal distinction between the remaining land use classes and select arable land (|ACKER|) for the following examples. To prevent soil water contents increasing |WMax|, we might need to decrease |WaDa| and |QKap|: >>> lnk(ACKER) >>> wmax(100.0) >>> states.bowa = 90.0 >>> fluxes.wada = 0.0, 5.0, 10.0, 15.0 >>> fluxes.qkap = 10.0 >>> fluxes.evb = 5.0 >>> fluxes.qbb = 0.0 >>> fluxes.qib1 = 0.0 >>> fluxes.qib2 = 0.0 >>> fluxes.qdb = 0.0 >>> model.calc_bowa_v1() >>> states.bowa bowa(95.0, 100.0, 100.0, 100.0) >>> fluxes.wada wada(0.0, 5.0, 2.5, 6.0) >>> fluxes.qkap qkap(10.0, 10.0, 2.5, 4.0) >>> fluxes.evb evb(5.0, 5.0, 5.0, 5.0) Additionally, to prevent storage overlflows we might need to decrease |EvB| in case it is negative (meaning, condensation increases the soil water storage): >>> states.bowa = 90.0 >>> fluxes.wada = 0.0, 5.0, 10.0, 15.0 >>> fluxes.qkap = 2.5 >>> fluxes.evb = -2.5 >>> model.calc_bowa_v1() >>> states.bowa bowa(95.0, 100.0, 100.0, 100.0) >>> fluxes.wada wada(0.0, 5.0, 3.333333, 7.5) >>> fluxes.qkap qkap(2.5, 2.5, 0.833333, 1.25) >>> fluxes.evb evb(-2.5, -2.5, -0.833333, -1.25) To prevent negative |BoWa| value, we might need to decrease |QBB|, |QIB1|, |QIB1|, |QBB|, and |EvB|: >>> states.bowa = 10.0 >>> fluxes.wada = 0.0 >>> fluxes.qkap = 0.0 >>> fluxes.evb = 0.0, 5.0, 10.0, 15.0 >>> fluxes.qbb = 1.25 >>> fluxes.qib1 = 1.25 >>> fluxes.qib2 = 1.25 >>> fluxes.qdb = 1.25 >>> model.calc_bowa_v1() >>> states.bowa bowa(5.0, 0.0, 0.0, 0.0) >>> fluxes.evb evb(0.0, 5.0, 6.666667, 7.5) >>> fluxes.qbb qbb(1.25, 1.25, 0.833333, 0.625) >>> fluxes.qib1 qib1(1.25, 1.25, 0.833333, 0.625) >>> fluxes.qib2 qib2(1.25, 1.25, 0.833333, 0.625) >>> fluxes.qdb qdb(1.25, 1.25, 0.833333, 0.625) We do not to modify negative |EvB| values (condensation) to prevent negative |BoWa| values: >>> states.bowa = 10.0 >>> fluxes.evb = -15.0, -10.0, -5.0, 0.0 >>> fluxes.qbb = 5.0 >>> fluxes.qib1 = 5.0 >>> fluxes.qib2 = 5.0 >>> fluxes.qdb = 5.0 >>> model.calc_bowa_v1() >>> states.bowa bowa(5.0, 0.0, 0.0, 0.0) >>> fluxes.evb evb(-15.0, -10.0, -5.0, 0.0) >>> fluxes.qbb qbb(5.0, 5.0, 3.75, 2.5) >>> fluxes.qib1 qib1(5.0, 5.0, 3.75, 2.5) >>> fluxes.qib2 qib2(5.0, 5.0, 3.75, 2.5) >>> fluxes.qdb qdb(5.0, 5.0, 3.75, 2.5) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.WMax, ) REQUIREDSEQUENCES = (lland_fluxes.WaDa,) UPDATEDSEQUENCES = ( lland_states.BoWa, lland_fluxes.EvB, lland_fluxes.QBB, lland_fluxes.QIB1, lland_fluxes.QIB2, lland_fluxes.QDB, lland_fluxes.QKap, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess for k in range(con.nhru): if con.lnk[k] in (VERS, WASSER, FLUSS, SEE): sta.bowa[k] = 0.0 else: d_decr = flu.qbb[k] + flu.qib1[k] + flu.qib2[k] + flu.qdb[k] d_incr = flu.wada[k] + flu.qkap[k] if flu.evb[k] > 0.0: d_decr += flu.evb[k] else: d_incr -= flu.evb[k] if d_decr > sta.bowa[k] + d_incr: d_rvl = (sta.bowa[k] + d_incr) / d_decr if flu.evb[k] > 0.0: flu.evb[k] *= d_rvl flu.qbb[k] *= d_rvl flu.qib1[k] *= d_rvl flu.qib2[k] *= d_rvl flu.qdb[k] *= d_rvl sta.bowa[k] = 0.0 else: sta.bowa[k] = (sta.bowa[k] + d_incr) - d_decr if sta.bowa[k] > con.wmax[k]: d_factor = (sta.bowa[k] - con.wmax[k]) / d_incr if flu.evb[k] < 0.0: flu.evb[k] *= d_factor flu.wada[k] *= d_factor flu.qkap[k] *= d_factor sta.bowa[k] = con.wmax[k]
[docs] class Calc_QBGZ_V1(modeltools.Method): """Aggregate the amount of base flow released by all "soil type" HRUs and the "net precipitation" above water areas of type |SEE|. Water areas of type |SEE| are assumed to be directly connected with groundwater, but not with the stream network. This is modelled by adding their (positive or negative) "net input" (|NKor|-|EvI|) to the "percolation output" of the soil containing HRUs. Basic equation: :math:`QBGZ = \\Sigma\\bigl(FHRU \\cdot \\bigl(QBB-Qkap\\bigl)\\bigl) + \\Sigma\\bigl(FHRU \\cdot \\bigl(NKor_{SEE}-EvI_{SEE}\\bigl)\\bigl)` Examples: The first example shows that |QBGZ| is the area weighted sum of |QBB| from "soil type" HRUs like arable land (|ACKER|) and of |NKor|-|EvI| from water areas of type |SEE|. All other water areas (|WASSER| and |FLUSS|) and also sealed surfaces (|VERS|) have no impact on |QBGZ|: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(6) >>> lnk(ACKER, ACKER, VERS, WASSER, FLUSS, SEE) >>> fhru(0.1, 0.2, 0.1, 0.1, 0.1, 0.4) >>> fluxes.qbb = 2., 4.0, 300.0, 300.0, 300.0, 300.0 >>> fluxes.qkap = 1., 2.0, 150.0, 150.0, 150.0, 150.0 >>> fluxes.nkor = 200.0, 200.0, 200.0, 200.0, 200.0, 20.0 >>> fluxes.evi = 100.0, 100.0, 100.0, 100.0, 100.0, 10.0 >>> model.calc_qbgz_v1() >>> states.qbgz qbgz(4.5) The second example shows that large evaporation values above a HRU of type |SEE| can result in negative values of |QBGZ|: >>> fluxes.evi[5] = 30 >>> model.calc_qbgz_v1() >>> states.qbgz qbgz(-3.5) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.FHRU, ) REQUIREDSEQUENCES = ( lland_fluxes.NKor, lland_fluxes.EvI, lland_fluxes.QBB, lland_fluxes.QKap, ) RESULTSEQUENCES = (lland_states.QBGZ,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess sta.qbgz = 0.0 for k in range(con.nhru): if con.lnk[k] == SEE: sta.qbgz += con.fhru[k] * (flu.nkor[k] - flu.evi[k]) elif con.lnk[k] not in (WASSER, FLUSS, VERS): sta.qbgz += con.fhru[k] * (flu.qbb[k] - flu.qkap[k])
[docs] class Calc_QIGZ1_V1(modeltools.Method): """Aggregate the amount of the first interflow component released by all HRUs. Basic equation: :math:`QIGZ1 = \\Sigma(FHRU \\cdot QIB1)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> fhru(0.75, 0.25) >>> fluxes.qib1 = 1.0, 5.0 >>> model.calc_qigz1_v1() >>> states.qigz1 qigz1(2.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.FHRU, ) REQUIREDSEQUENCES = (lland_fluxes.QIB1,) RESULTSEQUENCES = (lland_states.QIGZ1,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess sta.qigz1 = 0.0 for k in range(con.nhru): sta.qigz1 += con.fhru[k] * flu.qib1[k]
[docs] class Calc_QIGZ2_V1(modeltools.Method): """Aggregate the amount of the second interflow component released by all HRUs. Basic equation: :math:`QIGZ2 = \\Sigma(FHRU \\cdot QIB2)` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(2) >>> fhru(0.75, 0.25) >>> fluxes.qib2 = 1.0, 5.0 >>> model.calc_qigz2_v1() >>> states.qigz2 qigz2(2.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.FHRU, ) REQUIREDSEQUENCES = (lland_fluxes.QIB2,) RESULTSEQUENCES = (lland_states.QIGZ2,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess sta.qigz2 = 0.0 for k in range(con.nhru): sta.qigz2 += con.fhru[k] * flu.qib2[k]
[docs] class Calc_QDGZ_V1(modeltools.Method): """Aggregate the amount of total direct flow released by all HRUs. Basic equation: :math:`QDGZ = \\Sigma\\bigl(FHRU \\cdot QDB\\bigl) + \\Sigma\\bigl(FHRU \\cdot \\bigl(NKor_{FLUSS}-EvI_{FLUSS}\\bigl)\\bigl)` Examples: The first example shows that |QDGZ| is the area weighted sum of |QDB| from "land type" HRUs like arable land (|ACKER|) and sealed surfaces (|VERS|) as well as of |NKor|-|EvI| from water areas of type |FLUSS|. Water areas of type |WASSER| and |SEE| have no impact on |QDGZ|: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(5) >>> lnk(ACKER, VERS, WASSER, SEE, FLUSS) >>> fhru(0.1, 0.2, 0.1, 0.2, 0.4) >>> fluxes.qdb = 2., 4.0, 300.0, 300.0, 300.0 >>> fluxes.nkor = 200.0, 200.0, 200.0, 200.0, 20.0 >>> fluxes.evi = 100.0, 100.0, 100.0, 100.0, 10.0 >>> model.calc_qdgz_v1() >>> fluxes.qdgz qdgz(5.0) The second example shows that large evaporation values above a HRU of type |FLUSS| can result in negative values of |QDGZ|: >>> fluxes.evi[4] = 30 >>> model.calc_qdgz_v1() >>> fluxes.qdgz qdgz(-3.0) """ CONTROLPARAMETERS = ( lland_control.NHRU, lland_control.Lnk, lland_control.FHRU, ) REQUIREDSEQUENCES = ( lland_fluxes.NKor, lland_fluxes.EvI, lland_fluxes.QDB, ) RESULTSEQUENCES = (lland_fluxes.QDGZ,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess flu.qdgz = 0.0 for k in range(con.nhru): if con.lnk[k] == FLUSS: flu.qdgz += con.fhru[k] * (flu.nkor[k] - flu.evi[k]) elif con.lnk[k] not in (WASSER, SEE): flu.qdgz += con.fhru[k] * flu.qdb[k]
[docs] class Calc_QDGZ1_QDGZ2_V1(modeltools.Method): """Separate total direct flow into a slower and a faster component. Basic equations: :math:`QDGZ2 = \\frac{(QDGZ-A2)^2}{QDGZ+A1-A2}` :math:`QDGZ1 = QDGZ - QDGZ2` Examples: We borrowed the formula for calculating the amount of the faster component of direct flow from the well-known curve number approach. Parameter |A2| would be the initial loss and parameter |A1| the maximum storage, but one should not take this analogy too literally. With the value of parameter |A1| set to zero, parameter |A2| defines the maximum amount of "slow" direct runoff per time step: >>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> a1(0.0) Let us set the value of |A2| to 4 mm/d, which is 2 mm/12h concerning the selected simulation step size: >>> a2(4.0) >>> a2 a2(4.0) >>> a2.value 2.0 Define a test function and let it calculate |QDGZ1| and |QDGZ1| for values of |QDGZ| ranging from -10 to 100 mm/12h: >>> from hydpy import UnitTest >>> test = UnitTest(model, ... model.calc_qdgz1_qdgz2_v1, ... last_example=6, ... parseqs=(fluxes.qdgz, ... states.qdgz1, ... states.qdgz2)) >>> test.nexts.qdgz = -10.0, 0.0, 1.0, 2.0, 3.0, 100.0 >>> test() | ex. | qdgz | qdgz1 | qdgz2 | ------------------------------- | 1 | -10.0 | -10.0 | 0.0 | | 2 | 0.0 | 0.0 | 0.0 | | 3 | 1.0 | 1.0 | 0.0 | | 4 | 2.0 | 2.0 | 0.0 | | 5 | 3.0 | 2.0 | 1.0 | | 6 | 100.0 | 2.0 | 98.0 | Setting |A2| to zero and |A1| to 4 mm/d (or 2 mm/12h) results in a smoother transition: >>> a2(0.0) >>> a1(4.0) >>> test() | ex. | qdgz | qdgz1 | qdgz2 | -------------------------------------- | 1 | -10.0 | -10.0 | 0.0 | | 2 | 0.0 | 0.0 | 0.0 | | 3 | 1.0 | 0.666667 | 0.333333 | | 4 | 2.0 | 1.0 | 1.0 | | 5 | 3.0 | 1.2 | 1.8 | | 6 | 100.0 | 1.960784 | 98.039216 | Alternatively, one can mix these two configurations by setting the values of both parameters to 2 mm/h: >>> a2(2.0) >>> a1(2.0) >>> test() | ex. | qdgz | qdgz1 | qdgz2 | ------------------------------------- | 1 | -10.0 | -10.0 | 0.0 | | 2 | 0.0 | 0.0 | 0.0 | | 3 | 1.0 | 1.0 | 0.0 | | 4 | 2.0 | 1.5 | 0.5 | | 5 | 3.0 | 1.666667 | 1.333333 | | 6 | 100.0 | 1.99 | 98.01 | Note the similarity of the results for very high values of total direct flow |QDGZ| in all three examples, which converge to the sum of the values of parameter |A1| and |A2|, representing the maximum value of `slow` direct flow generation per simulation step """ CONTROLPARAMETERS = ( lland_control.A2, lland_control.A1, ) REQUIREDSEQUENCES = (lland_fluxes.QDGZ,) RESULTSEQUENCES = ( lland_states.QDGZ2, lland_states.QDGZ1, ) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess if flu.qdgz > con.a2: sta.qdgz2 = (flu.qdgz - con.a2) ** 2 / (flu.qdgz + con.a1 - con.a2) sta.qdgz1 = flu.qdgz - sta.qdgz2 else: sta.qdgz2 = 0.0 sta.qdgz1 = flu.qdgz
[docs] class Calc_QBGA_V1(modeltools.Method): """Perform the runoff concentration calculation for base flow. The working equation is the analytical solution of the linear storage equation under the assumption of constant change in inflow during the simulation time step. Basic equation: :math:`QBGA_{neu} = QBGA_{alt} + (QBGZ_{alt}-QBGA_{alt}) \\cdot (1-exp(-KB^{-1})) + (QBGZ_{neu}-QBGZ_{alt}) \\cdot (1-KB\\cdot(1-exp(-KB^{-1})))` Examples: A normal test case: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> derived.kb(0.1) >>> states.qbgz.old = 2.0 >>> states.qbgz.new = 4.0 >>> states.qbga.old = 3.0 >>> model.calc_qbga_v1() >>> states.qbga qbga(3.800054) First extreme test case (zero division is circumvented): >>> derived.kb(0.0) >>> model.calc_qbga_v1() >>> states.qbga qbga(4.0) Second extreme test case (numerical overflow is circumvented): >>> derived.kb(1e500) >>> model.calc_qbga_v1() >>> states.qbga qbga(5.0) """ DERIVEDPARAMETERS = (lland_derived.KB,) REQUIREDSEQUENCES = (lland_states.QBGZ,) UPDATEDSEQUENCES = (lland_states.QBGA,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess old = model.sequences.states.fastaccess_old new = model.sequences.states.fastaccess_new if der.kb <= 0.0: new.qbga = new.qbgz elif der.kb > 1e200: new.qbga = old.qbga + new.qbgz - old.qbgz else: d_temp = 1.0 - modelutils.exp(-1.0 / der.kb) new.qbga = ( old.qbga + (old.qbgz - old.qbga) * d_temp + (new.qbgz - old.qbgz) * (1.0 - der.kb * d_temp) )
[docs] class Update_QDGZ_QBGZ_QBGA_V1(modeltools.Method): r"""Redirect the inflow into the storage compartment for base flow into the storage compartments for direct flow upon exceedance of the groundwater aquifer's limited volume according to :cite:t:`ref-LARSIM`. We gained the following equation for updating |QBGZ| by converting the equation used by method |Calc_QBGA_V1|. Note that applying method |Update_QDGZ_QBGZ_QBGA_V1| can result in some oscillation both of |QBGZ| and |QDGZ|. Due to the dampening effect of the runoff concentration storages for direct runoff, this oscillation should seldom be traced through to the resulting outflow values (|QDGA1| and |QDGA2|). However, for long simulation time steps and short runoff concentration times, it is possible. Fixing this issue would probably require to solve the differential equation of the linear storage with a different approach. See the results of the integration test :ref:`lland_v1_acker_gw_vol` for an example. Basic equations: :math:`QBGA_{neu}^{final} = min \left ( QBGAMax, QBGA_{neu}^{orig} \right )` :math:`QBGZ_{neu}^{final} = QBGZ_{alt} + \frac{QBGA_{neu}^{final} - QBGA_{alt} - (QBGZ_{alt}-QBGA_{alt}) \cdot (1-exp(-KB^{-1})} {1-KB \cdot (1-exp(-KB^{-1})}` :math:`QDGZ^{final} = QDGZ^{orig} + QBGZ_{neu}^{orig} - QBGZ_{neu}^{final}` Examples: We modify the first example of the documentation on method |Calc_QBGA_V1| to show that both equations are consistent. The following setting is nearly identical, except for a higher recent inflow (6 mm/d instead of 4 mm/d). As a consequence, the recent outflow is also increased (5.6 mm/d instead of 3.8 mm/d): >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> derived.kb(0.1) >>> fluxes.qdgz = 1.0 >>> states.qbgz.old = 2.0 >>> states.qbgz.new = 6.0 >>> states.qbga.old = 3.0 >>> states.qbga.new = 5.6000636 Now we set the highest allowed outflow (which is proportional to the maximum storage capacity, see the documentation on parameter |QBGAMax|) to 3.8 mm/d and apply method |Update_QDGZ_QBGZ_QBGA_V1|: >>> derived.qbgamax(3.8000545) >>> model.update_qdgz_qbgz_qbga_v1() First, |Update_QDGZ_QBGZ_QBGA_V1| resets the recent outflow to the highest value allowed: >>> states.qbga qbga(3.800054) Second, it determines the recent inflow that results in the highest allowed outflow: >>> states.qbgz qbgz(4.0) Third, it adds the excess of groundwater recharge (2 mm/d) to the to the already generated direct discharge: >>> fluxes.qdgz qdgz(3.0) The final example deals with the edge case of zero storage capacity. Without any storage capacity, the aquifer can neither receive nor release any water. Here, method |Update_QDGZ_QBGZ_QBGA_V1| needs to reset the recent inflow to a negative value to accomplish zero outflow, as the old inflow and outflow values are not consistent with our modification of parameter |QBGAMax|: >>> derived.qbgamax(0.0) >>> model.update_qdgz_qbgz_qbga_v1() >>> states.qbga qbga(0.0) >>> states.qbgz qbgz(-0.222261) >>> fluxes.qdgz qdgz(7.222261) """ DERIVEDPARAMETERS = ( lland_derived.KB, lland_derived.QBGAMax, ) UPDATEDSEQUENCES = ( lland_fluxes.QDGZ, lland_states.QBGA, lland_states.QBGZ, ) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess old = model.sequences.states.fastaccess_old new = model.sequences.states.fastaccess_new if new.qbga > der.qbgamax: d_temp = 1.0 - modelutils.exp(-1.0 / der.kb) d_qbgz_adj = old.qbgz + ( (der.qbgamax - old.qbga - (old.qbgz - old.qbga) * d_temp) / (1.0 - der.kb * d_temp) ) new.qbga = der.qbgamax flu.qdgz += new.qbgz - d_qbgz_adj new.qbgz = d_qbgz_adj
[docs] class Update_QDGZ_QBGZ_QBGA_V2(modeltools.Method): r"""Redirect (a portion of) the inflow into the storage compartment for base flow into the storage compartments for direct flow due to the groundwater table's too fast rise according to :cite:t:`ref-LARSIM`. In contrast to restricting an aquifer's volume (implemented by the method |Update_QDGZ_QBGZ_QBGA_V1|), limiting the water table's increase seems a litter counterintuitive. However, we provide both kinds of restrictions to fully capture the option "GS BASIS LIMITIERT" of the original LARSIM model. The problem of oscillating time series for |QBGZ| and |QDGZ| discussed for method |Update_QDGZ_QBGZ_QBGA_V1| also holds for method |Update_QDGZ_QBGZ_QBGA_V2|. The smaller the "transition area" (the difference between |GSBGrad2| and |GSBGrad1|, the larger the oscillations. See the results of the integration test :ref:`lland_v1_acker_gw_rise` for an example. Basic equations: :math:`Grad = KB \cdot (QBGA_{neu}^{orig} - QBGA_{alt})` :math:`QBGZ_{neu}^{final} = QBGZ_{neu}^{orig} \cdot min \left( max \left( \frac{Grad-GSBGrad1}{GSBGrad2 - GSBGrad1}, 0 \right), 1 \right)` :math:`QBGA_{neu}^{final} = QBGA_{alt} + (QBGZ_{alt}-QBGA_{alt}) \cdot (1-exp(-KB^{-1})) + (QBGZ_{neu}^{final}-QBGZ_{alt}) \cdot (1-KB\cdot(1-exp(-KB^{-1})))` :math:`QDGZ^{final} = QDGZ^{orig} + QBGZ_{neu}^{orig} - QBGZ_{neu}^{final}` Examples: We build our explanations on the first example of the documentation on method |Calc_QBGA_V1|: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> derived.kb(0.1) >>> fluxes.qdgz = 1.0 >>> states.qbgz.old = 2.0 >>> states.qbgz.new = 4.0 >>> states.qbga.old = 3.0 >>> model.calc_qbga_v1() >>> states.qbga qbga(3.800054) In this example, the old and the new outflow value of the groundwater storage is 3.0 mm/d and 3.8 mm/d, respectively, which corresponds to a storage increase of 0.08 mm/d: >>> from hydpy import round_ >>> round_((states.qbga.new - states.qbga.old) * derived.kb) 0.080005 If we assign larger values to the threshold parameters |GSBGrad1| (0.1 mm/d) and |GSBGrad2| (0.2 mm/d), method |Update_QDGZ_QBGZ_QBGA_V2| does not need to change anything: >>> gsbgrad1(0.1) >>> gsbgrad2(0.2) >>> model.update_qdgz_qbgz_qbga_v2() >>> states.qbga qbga(3.800054) >>> states.qbgz qbgz(4.0) >>> fluxes.qdgz qdgz(1.0) If we assign smaller values to both threshold parameters, method |Update_QDGZ_QBGZ_QBGA_V2| redirects all groundwater inflow to direct runoff and recalculates groundwater outflow accordingly: >>> gsbgrad1(0.01) >>> gsbgrad2(0.05) >>> model.update_qdgz_qbgz_qbga_v2() >>> states.qbga qbga(0.200036) >>> states.qbgz qbgz(0.0) >>> fluxes.qdgz qdgz(5.0) For an actual rise (we first reset it to 0.08 mm/d) between both threshold values, method |Update_QDGZ_QBGZ_QBGA_V2| interpolates the fraction of redirected groundwater inflow linearly: >>> fluxes.qdgz = 1.0 >>> states.qbgz.new = 4.0 >>> model.calc_qbga_v1() >>> gsbgrad1(0.01) >>> gsbgrad2(0.1) >>> model.update_qdgz_qbgz_qbga_v2() >>> states.qbga qbga(0.999822) >>> states.qbgz qbgz(0.888647) >>> fluxes.qdgz qdgz(4.111353) """ CONTROLPARAMETERS = ( lland_control.GSBGrad1, lland_control.GSBGrad2, ) DERIVEDPARAMETERS = (lland_derived.KB,) UPDATEDSEQUENCES = ( lland_fluxes.QDGZ, lland_states.QBGA, lland_states.QBGZ, ) SUBMETHODS = (Calc_QBGA_V1,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess old = model.sequences.states.fastaccess_old new = model.sequences.states.fastaccess_new d_grad = der.kb * (new.qbga - old.qbga) if d_grad > con.gsbgrad1: if d_grad < con.gsbgrad2: d_qbgz_exc = new.qbgz * ( (d_grad - con.gsbgrad1) / (con.gsbgrad2 - con.gsbgrad1) ) else: d_qbgz_exc = new.qbgz flu.qdgz += d_qbgz_exc new.qbgz -= d_qbgz_exc model.calc_qbga_v1()
[docs] class Calc_QIGA1_V1(modeltools.Method): """Perform the runoff concentration calculation for the first interflow component. The working equation is the analytical solution of the linear storage equation under the assumption of constant change in inflow during the simulation time step. Basic equation: :math:`QIGA1_{neu} = QIGA1_{alt} + (QIGZ1_{alt}-QIGA1_{alt}) \\cdot (1-exp(-KI1^{-1})) + (QIGZ1_{neu}-QIGZ1_{alt}) \\cdot (1-KI1\\cdot(1-exp(-KI1^{-1})))` Examples: A normal test case: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> derived.ki1(0.1) >>> states.qigz1.old = 2.0 >>> states.qigz1.new = 4.0 >>> states.qiga1.old = 3.0 >>> model.calc_qiga1_v1() >>> states.qiga1 qiga1(3.800054) First extreme test case (zero division is circumvented): >>> derived.ki1(0.0) >>> model.calc_qiga1_v1() >>> states.qiga1 qiga1(4.0) Second extreme test case (numerical overflow is circumvented): >>> derived.ki1(1e500) >>> model.calc_qiga1_v1() >>> states.qiga1 qiga1(5.0) """ DERIVEDPARAMETERS = (lland_derived.KI1,) REQUIREDSEQUENCES = (lland_states.QIGZ1,) UPDATEDSEQUENCES = (lland_states.QIGA1,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess old = model.sequences.states.fastaccess_old new = model.sequences.states.fastaccess_new if der.ki1 <= 0.0: new.qiga1 = new.qigz1 elif der.ki1 > 1e200: new.qiga1 = old.qiga1 + new.qigz1 - old.qigz1 else: d_temp = 1.0 - modelutils.exp(-1.0 / der.ki1) new.qiga1 = ( old.qiga1 + (old.qigz1 - old.qiga1) * d_temp + (new.qigz1 - old.qigz1) * (1.0 - der.ki1 * d_temp) )
[docs] class Calc_QIGA2_V1(modeltools.Method): """Perform the runoff concentration calculation for the second interflow component. The working equation is the analytical solution of the linear storage equation under the assumption of constant change in inflow during the simulation time step. Basic equation: :math:`QIGA2_{neu} = QIGA2_{alt} + (QIGZ2_{alt}-QIGA2_{alt}) \\cdot (1-exp(-KI2^{-1})) + (QIGZ2_{neu}-QIGZ2_{alt}) \\cdot (1-KI2\\cdot(1-exp(-KI2^{-1})))` Examples: A normal test case: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> derived.ki2(0.1) >>> states.qigz2.old = 2.0 >>> states.qigz2.new = 4.0 >>> states.qiga2.old = 3.0 >>> model.calc_qiga2_v1() >>> states.qiga2 qiga2(3.800054) First extreme test case (zero division is circumvented): >>> derived.ki2(0.0) >>> model.calc_qiga2_v1() >>> states.qiga2 qiga2(4.0) Second extreme test case (numerical overflow is circumvented): >>> derived.ki2(1e500) >>> model.calc_qiga2_v1() >>> states.qiga2 qiga2(5.0) """ DERIVEDPARAMETERS = (lland_derived.KI2,) REQUIREDSEQUENCES = (lland_states.QIGZ2,) UPDATEDSEQUENCES = (lland_states.QIGA2,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess old = model.sequences.states.fastaccess_old new = model.sequences.states.fastaccess_new if der.ki2 <= 0.0: new.qiga2 = new.qigz2 elif der.ki2 > 1e200: new.qiga2 = old.qiga2 + new.qigz2 - old.qigz2 else: d_temp = 1.0 - modelutils.exp(-1.0 / der.ki2) new.qiga2 = ( old.qiga2 + (old.qigz2 - old.qiga2) * d_temp + (new.qigz2 - old.qigz2) * (1.0 - der.ki2 * d_temp) )
[docs] class Calc_QDGA1_V1(modeltools.Method): """Perform the runoff concentration calculation for "slow" direct runoff. The working equation is the analytical solution of the linear storage equation under the assumption of constant change in inflow during the simulation time step. Basic equation: :math:`QDGA1_{neu} = QDGA1_{alt} + (QDGZ1_{alt}-QDGA1_{alt}) \\cdot (1-exp(-KD1^{-1})) + (QDGZ1_{neu}-QDGZ1_{alt}) \\cdot (1-KD1\\cdot(1-exp(-KD1^{-1})))` Examples: A normal test case: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> derived.kd1(0.1) >>> states.qdgz1.old = 2.0 >>> states.qdgz1.new = 4.0 >>> states.qdga1.old = 3.0 >>> model.calc_qdga1_v1() >>> states.qdga1 qdga1(3.800054) First extreme test case (zero division is circumvented): >>> derived.kd1(0.0) >>> model.calc_qdga1_v1() >>> states.qdga1 qdga1(4.0) Second extreme test case (numerical overflow is circumvented): >>> derived.kd1(1e500) >>> model.calc_qdga1_v1() >>> states.qdga1 qdga1(5.0) """ DERIVEDPARAMETERS = (lland_derived.KD1,) REQUIREDSEQUENCES = (lland_states.QDGZ1,) UPDATEDSEQUENCES = (lland_states.QDGA1,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess old = model.sequences.states.fastaccess_old new = model.sequences.states.fastaccess_new if der.kd1 <= 0.0: new.qdga1 = new.qdgz1 elif der.kd1 > 1e200: new.qdga1 = old.qdga1 + new.qdgz1 - old.qdgz1 else: d_temp = 1.0 - modelutils.exp(-1.0 / der.kd1) new.qdga1 = ( old.qdga1 + (old.qdgz1 - old.qdga1) * d_temp + (new.qdgz1 - old.qdgz1) * (1.0 - der.kd1 * d_temp) )
[docs] class Calc_QDGA2_V1(modeltools.Method): """Perform the runoff concentration calculation for "fast" direct runoff. The working equation is the analytical solution of the linear storage equation under the assumption of constant change in inflow during the simulation time step. Basic equation: :math:`QDGA2_{neu} = QDGA2_{alt} + (QDGZ2_{alt}-QDGA2_{alt}) \\cdot (1-exp(-KD2^{-1})) + (QDGZ2_{neu}-QDGZ2_{alt}) \\cdot (1-KD2\\cdot(1-exp(-KD2^{-1})))` Examples: A normal test case: >>> from hydpy.models.lland import * >>> simulationstep("1d") >>> parameterstep("1d") >>> derived.kd2(0.1) >>> states.qdgz2.old = 2.0 >>> states.qdgz2.new = 4.0 >>> states.qdga2.old = 3.0 >>> model.calc_qdga2_v1() >>> states.qdga2 qdga2(3.800054) First extreme test case (zero division is circumvented): >>> derived.kd2(0.0) >>> model.calc_qdga2_v1() >>> states.qdga2 qdga2(4.0) Second extreme test case (numerical overflow is circumvented): >>> derived.kd2(1e500) >>> model.calc_qdga2_v1() >>> states.qdga2 qdga2(5.0) """ DERIVEDPARAMETERS = (lland_derived.KD2,) REQUIREDSEQUENCES = (lland_states.QDGZ2,) UPDATEDSEQUENCES = (lland_states.QDGA2,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess old = model.sequences.states.fastaccess_old new = model.sequences.states.fastaccess_new if der.kd2 <= 0.0: new.qdga2 = new.qdgz2 elif der.kd2 > 1e200: new.qdga2 = old.qdga2 + new.qdgz2 - old.qdgz2 else: d_temp = 1.0 - modelutils.exp(-1.0 / der.kd2) new.qdga2 = ( old.qdga2 + (old.qdgz2 - old.qdga2) * d_temp + (new.qdgz2 - old.qdgz2) * (1.0 - der.kd2 * d_temp) )
[docs] class Calc_QAH_V1(modeltools.Method): """Calculate the final runoff in mm. Basic equation: :math:`QAH = QZH + QBGA + QIGA1 + QIGA2 + QDGA1 + QDGA2 + NKor_{WASSER} - EvI_{WASSER}` In case there are water areas of type |WASSER|, their |NKor| values are added and their |EvPo| values are subtracted from the "potential" runoff value, if possible. This can result in problematic modifications of simulated runoff series. It seems advisable to use the water types |FLUSS| and |SEE| instead. Examples: When there are no water areas in the respective subbasin (we choose arable land |ACKER| arbitrarily), the inflow (|QZH|) and the different runoff components (|QBGA|, |QIGA1|, |QIGA2|, |QDGA1|, and |QDGA2|) are simply summed up: >>> from hydpy.models.lland import * >>> parameterstep() >>> nhru(3) >>> lnk(ACKER, ACKER, ACKER) >>> fhru(0.5, 0.2, 0.3) >>> negq(False) >>> states.qbga = 0.1 >>> states.qiga1 = 0.2 >>> states.qiga2 = 0.3 >>> states.qdga1 = 0.4 >>> states.qdga2 = 0.5 >>> fluxes.qzh = 1.0 >>> fluxes.nkor = 10.0 >>> fluxes.evi = 4.0, 5.0, 3.0 >>> model.calc_qah_v1() >>> fluxes.qah qah(2.5) >>> fluxes.evi evi(4.0, 5.0, 3.0) The defined values of interception evaporation do not show any impact on the result of the given example, the predefined values for sequence |EvI| remain unchanged. But when we define the first hydrological as a water area (|WASSER|), |Calc_QAH_V1| adds the adjusted precipitaton (|NKor|) to and subtracts the interception evaporation (|EvI|) from |lland_fluxes.QAH|: >>> control.lnk(WASSER, VERS, NADELW) >>> model.calc_qah_v1() >>> fluxes.qah qah(5.5) >>> fluxes.evi evi(4.0, 5.0, 3.0) Note that only 5 mm are added (instead of the |NKor| value 10 mm) and that only 2 mm are substracted (instead of the |EvI| value 4 mm, as the first response unit area only accounts for 50 % of the total subbasin area. Setting also the land use class of the second response unit to water type |WASSER| and resetting |NKor| to zero would result in overdrying. To avoid this, both actual water evaporation values stored in sequence |EvI| are reduced by the same factor: >>> control.lnk(WASSER, WASSER, NADELW) >>> fluxes.nkor = 0.0 >>> model.calc_qah_v1() >>> fluxes.qah qah(0.0) >>> fluxes.evi evi(3.333333, 4.166667, 3.0) The handling of water areas of type |FLUSS| and |SEE| differs from those of type |WASSER|, as these do receive their net input before the runoff concentration routines are applied. This should be more realistic in most cases (especially for type |SEE|, representing lakes not directly connected to the stream network). But it could also, at least for very dry periods, result in negative outflow values. We avoid this by setting |lland_fluxes.QAH| to zero and adding the truncated negative outflow value to the |EvI| value of all response units of type |FLUSS| and |SEE|: >>> control.lnk(FLUSS, SEE, NADELW) >>> states.qbga = -1.0 >>> states.qdga2 = -1.9 >>> fluxes.evi = 4.0, 5.0, 3.0 >>> model.calc_qah_v1() >>> fluxes.qah qah(0.0) >>> fluxes.evi evi(2.571429, 3.571429, 3.0) This adjustment of |EvI| is only correct regarding the total water balance. Neither spatial nor temporal consistency of the resulting |EvI| values are assured. In the most extreme case, even negative |EvI| values might occur. This seems acceptable, as long as the adjustment of |EvI| is rarely triggered. When in doubt about this, check sequences |EvPo| and |EvI| of all |FLUSS| and |SEE| units for possible discrepancies. Also note that |lland_fluxes.QAH| might perform unnecessary corrections when you combine water type |WASSER| with water type |SEE| or |FLUSS|. For some model configurations, negative discharges for dry conditions are acceptable or even preferable, which is possible through setting parameter |NegQ| to |True|: >>> negq(True) >>> fluxes.evi = 4.0, 5.0, 3.0 >>> model.calc_qah_v1() >>> fluxes.qah qah(-1.0) >>> fluxes.evi evi(4.0, 5.0, 3.0) """ CONTROLPARAMETERS = ( lland_control.NegQ, lland_control.NHRU, lland_control.Lnk, lland_control.FHRU, ) REQUIREDSEQUENCES = ( lland_states.QBGA, lland_states.QIGA1, lland_states.QIGA2, lland_states.QDGA1, lland_states.QDGA2, lland_fluxes.NKor, lland_fluxes.QZH, ) UPDATEDSEQUENCES = (lland_fluxes.EvI,) RESULTSEQUENCES = (lland_fluxes.QAH,) @staticmethod def __call__(model: modeltools.Model) -> None: con = model.parameters.control.fastaccess flu = model.sequences.fluxes.fastaccess sta = model.sequences.states.fastaccess flu.qah = flu.qzh + sta.qbga + sta.qiga1 + sta.qiga2 + sta.qdga1 + sta.qdga2 if (not con.negq) and (flu.qah < 0.0): d_area = 0.0 for k in range(con.nhru): if con.lnk[k] in (FLUSS, SEE): d_area += con.fhru[k] if d_area > 0.0: for k in range(con.nhru): if con.lnk[k] in (FLUSS, SEE): flu.evi[k] += flu.qah / d_area flu.qah = 0.0 d_epw = 0.0 for k in range(con.nhru): if con.lnk[k] == WASSER: flu.qah += con.fhru[k] * flu.nkor[k] d_epw += con.fhru[k] * flu.evi[k] if (flu.qah > d_epw) or con.negq: flu.qah -= d_epw elif d_epw > 0.0: for k in range(con.nhru): if con.lnk[k] == WASSER: flu.evi[k] *= flu.qah / d_epw flu.qah = 0.0
[docs] class Calc_QA_V1(modeltools.Method): """Calculate the final runoff in m³/s. Basic equation: :math:`QA = QFactor \\cdot QAH` Example: >>> from hydpy.models.lland import * >>> parameterstep() >>> derived.qfactor(2.0) >>> fluxes.qah = 3.0 >>> model.calc_qa_v1() >>> fluxes.qa qa(6.0) """ DERIVEDPARAMETERS = (lland_derived.QFactor,) REQUIREDSEQUENCES = (lland_fluxes.QAH,) RESULTSEQUENCES = (lland_fluxes.QA,) @staticmethod def __call__(model: modeltools.Model) -> None: der = model.parameters.derived.fastaccess flu = model.sequences.fluxes.fastaccess flu.qa = der.qfactor * flu.qah
[docs] class Pass_QA_V1(modeltools.Method): """Update the outlet link sequence. Basic equation: :math:`Q_{outlets} = QA` """ REQUIREDSEQUENCES = (lland_fluxes.QA,) RESULTSEQUENCES = (lland_outlets.Q,) @staticmethod def __call__(model: modeltools.Model) -> None: flu = model.sequences.fluxes.fastaccess out = model.sequences.outlets.fastaccess out.q[0] += flu.qa
[docs] class PegasusESnowInz(roottools.Pegasus): """Pegasus iterator for finding the correct energy content of the intercepted snow.""" METHODS = (Return_BackwardEulerErrorInz_V1,)
[docs] class PegasusESnow(roottools.Pegasus): """Pegasus iterator for finding the correct snow energy content.""" METHODS = (Return_BackwardEulerError_V1,)
[docs] class PegasusTempSSurface(roottools.Pegasus): """Pegasus iterator for finding the correct snow surface temperature.""" METHODS = (Return_EnergyGainSnowSurface_V1,)
[docs] class Model(modeltools.AdHocModel): """Base model for HydPy-L-Land.""" INLET_METHODS = (Pick_QZ_V1,) RECEIVER_METHODS = () ADD_METHODS = ( Return_AdjustedWindSpeed_V1, Return_ActualVapourPressure_V1, Calc_DailyNetLongwaveRadiation_V1, Return_NetLongwaveRadiationInz_V1, Return_NetLongwaveRadiationSnow_V1, Return_Penman_V1, Return_PenmanMonteith_V1, Return_EnergyGainSnowSurface_V1, Return_SaturationVapourPressure_V1, Return_SaturationVapourPressureSlope_V1, Return_NetRadiation_V1, Return_WSensInz_V1, Return_WSensSnow_V1, Return_WLatInz_V1, Return_WLatSnow_V1, Return_WSurfInz_V1, Return_WSurf_V1, Return_BackwardEulerErrorInz_V1, Return_BackwardEulerError_V1, Return_TempSInz_V1, Return_TempS_V1, Return_WG_V1, Return_ESnowInz_V1, Return_ESnow_V1, Return_TempSSurface_V1, ) RUN_METHODS = ( Calc_QZH_V1, Update_LoggedTemL_V1, Calc_TemLTag_V1, Update_LoggedRelativeHumidity_V1, Calc_DailyRelativeHumidity_V1, Update_LoggedSunshineDuration_V1, Calc_DailySunshineDuration_V1, Update_LoggedPossibleSunshineDuration_V1, Calc_DailyPossibleSunshineDuration_V1, Update_LoggedGlobalRadiation_V1, Calc_DailyGlobalRadiation_V1, Calc_NKor_V1, Calc_TKor_V1, Calc_TKorTag_V1, Calc_WindSpeed2m_V1, Calc_ReducedWindSpeed2m_V1, Update_LoggedWindSpeed2m_V1, Calc_DailyWindSpeed2m_V1, Calc_WindSpeed10m_V1, Calc_SaturationVapourPressure_V1, Calc_DailySaturationVapourPressure_V1, Calc_SaturationVapourPressureSlope_V1, Calc_DailySaturationVapourPressureSlope_V1, Calc_ActualVapourPressure_V1, Calc_DailyActualVapourPressure_V1, Calc_ET0_V1, Calc_ET0_WET0_V1, Calc_EvPo_V1, Calc_NBes_Inzp_V1, Calc_SNRatio_V1, Calc_SBes_V1, Calc_SnowIntMax_V1, Calc_SnowIntRate_V1, Calc_NBesInz_V1, Calc_SBesInz_V1, Calc_STInz_V1, Calc_WaDaInz_SInz_V1, Calc_WNiedInz_ESnowInz_V1, Calc_TempSInz_V1, Update_ASInz_V1, Calc_NetShortwaveRadiationInz_V1, Calc_ActualAlbedoInz_V1, Update_ESnowInz_V1, Calc_SchmPotInz_V1, Calc_SchmInz_STInz_V1, Calc_GefrPotInz_V1, Calc_GefrInz_STInz_V1, Calc_EvSInz_SInz_STInz_V1, Update_WaDaInz_SInz_V1, Update_ESnowInz_V2, Calc_WATS_V1, Calc_WATS_V2, Calc_WaDa_WAeS_V1, Calc_WaDa_WAeS_V2, Calc_WGTF_V1, Calc_WNied_V1, Calc_WNied_ESnow_V1, Calc_TZ_V1, Calc_WG_V1, Calc_TempS_V1, Update_TauS_V1, Calc_ActualAlbedo_V1, Calc_NetShortwaveRadiation_V1, Calc_NetShortwaveRadiationSnow_V1, Calc_DailyNetShortwaveRadiation_V1, Calc_RLAtm_V1, Calc_G_V1, Calc_G_V2, Calc_EvB_V2, Update_EBdn_V1, Update_ESnow_V1, Calc_NetRadiation_V1, Calc_DailyNetRadiation_V1, Calc_DryAirPressure_V1, Calc_DensityAir_V1, Calc_AerodynamicResistance_V1, Calc_SoilSurfaceResistance_V1, Calc_LanduseSurfaceResistance_V1, Calc_ActualSurfaceResistance_V1, Calc_EvPo_V2, Calc_EvI_Inzp_V1, Calc_EvI_Inzp_V2, Calc_EvI_Inzp_V3, Calc_EvB_V1, Calc_SchmPot_V1, Calc_SchmPot_V2, Calc_GefrPot_V1, Calc_Schm_WATS_V1, Calc_Gefr_WATS_V1, Calc_EvS_WAeS_WATS_V1, Update_WaDa_WAeS_V1, Update_ESnow_V2, Calc_SFF_V1, Calc_FVG_V1, Calc_QKap_V1, Calc_QBB_V1, Calc_QIB1_V1, Calc_QIB2_V1, Calc_QDB_V1, Update_QDB_V1, Calc_BoWa_V1, Calc_QBGZ_V1, Calc_QIGZ1_V1, Calc_QIGZ2_V1, Calc_QDGZ_V1, Calc_QBGA_V1, Update_QDGZ_QBGZ_QBGA_V1, Update_QDGZ_QBGZ_QBGA_V2, Calc_QIGA1_V1, Calc_QIGA2_V1, Calc_QDGZ1_QDGZ2_V1, Calc_QDGA1_V1, Calc_QDGA2_V1, Calc_QAH_V1, Calc_QA_V1, ) OUTLET_METHODS = (Pass_QA_V1,) SENDER_METHODS = () SUBMODELS = ( PegasusESnowInz, PegasusESnow, PegasusTempSSurface, ) idx_hru = modeltools.Idx_HRU()