# -*- 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()