parametertools¶
This module provides tools for defining and handling different kinds of parameters of hydrological models.
Module parametertools implements the following members:
trim_kwarg()Helper function for model developers for trimming scalar keyword arguments of typefloat.
IntConstantClass forintobjects with individual docstrings.
ConstantsBase class for defining integer constants for a specific model.
ParametersBase class for handling all parameters of a specific model.
FastAccessParameterUsed as a surrogate for typed Cython classes handling parameters when working in pure Python mode.
SubParametersBase class for handling subgroups of model parameters.
KeywordHelper class to describe parameter-specific keyword arguments for defining values by “calling” a parameter object.
KeywordArgumentsErrorA specialisedRuntimeErrorraised by classKeywordArguments.
KeywordArgumentsA handler for the keyword arguments of the instances of specificParametersubclasses.
ParameterBase class for model parameters.
NmbParameterBase class for defining “number parameters” that define the shape of other variables.
NameParameterParameter displaying the names of constants instead of their values.
ZipParameterBase class for 1-dimensional model parameters that offers an additional keyword-based zipping functionality.
SeasonalParameterBase class for parameters handling values showing a seasonal variation.
KeywordParameter1DBase class for 1-dimensional model parameters with values depending on one factor.
MonthParameterBase class for parameters whose values depend on the actual month.
KeywordParameter2DBase class for 2-dimensional model parameters with values depending on two factors.
LeftRightParameterBase class for handling two values, a left one and a right one.
SortedParameterBase parameter class that checks if given values are sorted in increasing order.
FixedParameterBase class for defining parameters with fixed values.
SolverParameterBase class for defining parameters controlling numerical algorithms for solving model equations.
SecondsParameterThe length of the actual simulation step size in seconds [s].
HoursParameterThe length of the actual simulation step size in hours [h].
DaysParameterThe length of the actual simulation step size in days [d].
IndexParameterBase class for parameters that do not allocate RAM for handling their data but reference an index array provided by the instance of class|Indexer| available in modulepub.
TOYParameterReferences thetimeofyearindex array provided by the instance of classIndexeravailable in modulepub. [-].
MOYParameterReferences themonthofyearindex array provided by the instance of classIndexeravailable in modulepub[-].
DOYParameterReferences thedayofyearindex array provided by the instance of classIndexeravailable in modulepub[-].
SCTParameterReferences thestandardclocktimearray provided by the instance of classIndexeravailable in modulepub[h].
UTCLongitudeParameterReferences the current “UTC longitude” defined by optionutclongitude.
do_nothing()The default Python version of theCallbackParametercallbackfunction, which does nothing.
CallbackParameterBase class for parameters that support calculating their values via user-defined callback functions alternatively of sticking to the same values during a simulation run.
- hydpy.core.parametertools.trim_kwarg(parameter: Parameter, name: str, value: float, lower: float = -inf, upper: float = inf) float[source]¶
Helper function for model developers for trimming scalar keyword arguments of type
float.Function
trim_kwarg()works similarly to functiontrim()but targets defining parameter values via parameter-specific keyword arguments. Due to the individual nature of calculating parameter values from keyword arguments, usingtrim_kwarg()is less standardisable than usingtrim(). Hence, model developers must include it manually into the __call__ methods of theirParametersubclasses when trimming keyword arguments is required.The following tests show that
trim_kwarg()returns the eventually trimmed value, and, liketrim(), emits warnings only in case of boundary violations beyond the size of pure precision-related artefacts:>>> from hydpy.core.parametertools import Parameter, trim_kwarg >>> parameter = Parameter(None)
>>> trim_kwarg(parameter, "x", 1.0) == 1.0 True
>>> from hydpy.core.testtools import warn_later >>> with warn_later(): ... trim_kwarg(parameter, "x", 0.0, lower=1.0) == 1.0 True UserWarning: For parameter `parameter` of element `?` the keyword argument `x` with value `0.0` needed to be trimmed to `1.0`.
>>> with warn_later(): ... trim_kwarg(parameter, "x", 2.0, upper=1.0) == 1.0 True UserWarning: For parameter `parameter` of element `?` the keyword argument `x` with value `2.0` needed to be trimmed to `1.0`.
>>> x = 1.0 - 1e-15 >>> x == 1.0 False >>> with warn_later(): ... trim_kwarg(parameter, "x", x, lower=1.0) == 1.0 True
>>> x = 1.0 + 1e-15 >>> x == 1.0 False >>> with warn_later(): ... trim_kwarg(parameter, "x", x, upper=1.0) == 1.0 True
- class hydpy.core.parametertools.IntConstant(value)[source]¶
Bases:
intClass for
intobjects with individual docstrings.
- class hydpy.core.parametertools.Constants(*args, **kwargs)[source]¶
-
Base class for defining integer constants for a specific model.
- get_sortednames(*, relevant: Sequence[int] | None = None) tuple[str, ...][source]¶
Get the lowercase constants’ names, sorted by the constants’ values.
>>> from hydpy.core.parametertools import Constants >>> Constants(GRASS=2, TREES=0, WATER=1).get_sortednames() ('trees', 'water', 'grass')
You can pass the values of relevant constants to exclude the names of the remaining constants:
>>> Constants(GRASS=2, TREES=0, WATER=1).get_sortednames(relevant=[0, 2]) ('trees', 'grass')
- class hydpy.core.parametertools.Parameters(kwargs)[source]¶
Bases:
objectBase class for handling all parameters of a specific model.
Parametersobjects handle four subgroups as attributes: the control subparameters, the derived subparameters, the fixed subparameters and the solver subparameters:>>> from hydpy.models.meteo_glob_fao56 import * >>> parameterstep("1d") >>> assert model.parameters >>> assert model.parameters.control >>> assert not model.parameters.solver
Iterations makes only the non-empty subgroups available, which are actually handling
Parameterobjects:>>> for subpars in model.parameters: ... print(subpars.name) control derived fixed >>> len(model.parameters) 3
Keyword access provides a type-safe way to query a subgroup via a string:
>>> type(model.parameters["control"]).__name__ 'ControlParameters' >>> type(model.parameters["wrong"]) Traceback (most recent call last): ... TypeError: There is no parameter subgroup named `wrong`. >>> model.parameters["model"] Traceback (most recent call last): ... TypeError: Attribute `model` is of type `Model`, which is not a subtype of class `SubParameters`.
- model: modeltools.Model¶
- control: SubParameters¶
- derived: SubParameters¶
- fixed: SubParameters¶
- solver: SubParameters¶
- update(ignore_errors: bool = False) None[source]¶
Call method
update()of all “secondary” parameters.Directly after initialisation, neither the primary (control) parameters nor the secondary (derived) parameters of application model
meteo_glob_fao56are ready for usage:>>> from hydpy.models.meteo_glob_fao56 import * >>> parameterstep("1d") >>> simulationstep("1d") >>> derived doy(?) moy(?) hours(?) days(?) sct(?) utclongitude(?) latituderad(?)
Trying to update the values of the secondary parameters while the primary ones are still not defined raises errors like the following:
>>> model.parameters.update() Traceback (most recent call last): ... hydpy.core.exceptiontools.AttributeNotReady: While trying to update parameter `doy` of element `?`, the following error occurred: An Indexer object has been asked for an `dayofyear` array. Such an array has neither been determined yet nor can it be determined automatically at the moment. Either define an `dayofyear` array manually and pass it to the Indexer object, or make a proper Timegrids object available within the pub module.
>>> from hydpy import pub >>> pub.timegrids = "2000-01-30", "2000-02-04", "1d" >>> model.parameters.update() Traceback (most recent call last): ... hydpy.core.exceptiontools.AttributeNotReady: While trying to update parameter `latituderad` of element `?`, the following error occurred: While trying to multiply variable `latitude` and `float` instance `0.017453`, the following error occurred: For variable `latitude`, no value has been defined so far.
With a defined
Timegridsobject and proper values both for parametersLatitudeandLongitude, updating the derived parameters succeeds:>>> latitude(50.0) >>> longitude(10.0) >>> model.parameters.update() >>> derived doy(29, 30, 31, 32, 33) moy(0, 0, 1, 1, 1) hours(24.0) days(1.0) sct(12.0, 12.0, 12.0, 12.0, 12.0) utclongitude(15) latituderad(0.872665)
- verify() None[source]¶
Call method
verify()of allParameterobjects handled by the actual model.When calling method
verify()directly after initialising modelmeteo_glob_fao56(without using default values), it raises aRuntimeErrordue to the undefined value of control parameterLatitude:>>> from hydpy.models.meteo_glob_fao56 import * >>> parameterstep("1d") >>> simulationstep("1d") >>> model.parameters.verify() Traceback (most recent call last): ... RuntimeError: For variable `latitude`, 1 required value has not been set yet: latitude(?).
Assigning a value to
Latitudeis not sufficient:>>> model.parameters.control.latitude(50.0) >>> model.parameters.verify() Traceback (most recent call last): ... RuntimeError: For variable `longitude`, 1 required value has not been set yet: longitude(?).
After also defining suitable values for all remaining control parameters, the derived parameters are still not ready:
>>> model.parameters.control.longitude(10.0) >>> model.parameters.control.angstromconstant(0.25) >>> model.parameters.control.angstromfactor(0.5) >>> model.parameters.verify() Traceback (most recent call last): ... hydpy.core.exceptiontools.AttributeNotReady: Shape information for variable `doy` can only be retrieved after it has been defined.
After updating the derived parameters (which requires preparing a
Timegridsobject first), methodverify()has no reason to complain anymore:>>> from hydpy import pub >>> pub.timegrids = "2000-01-30", "2000-02-04", "1d" >>> model.parameters.update() >>> model.parameters.verify()
- property secondary_subpars: Iterator[SubParameters]¶
Iterate through all subgroups of “secondary” parameters.
These secondary parameter subgroups are the derived parameters and the solver parameters at the moment:
>>> from hydpy.models.meteo_glob_fao56 import * >>> parameterstep("1d") >>> for subpars in model.parameters.secondary_subpars: ... print(subpars.name) derived solver
- class hydpy.core.parametertools.FastAccessParameter[source]¶
Bases:
FastAccessUsed as a surrogate for typed Cython classes handling parameters when working in pure Python mode.
- class hydpy.core.parametertools.SubParameters(master: Parameters, cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)[source]¶
Bases:
SubVariables[Parameters,Parameter,FastAccessParameter]Base class for handling subgroups of model parameters.
When trying to implement a new model, one has to define its specific
Parametersubclasses. Currently, the HydPy framework distinguishes between control parameters, derived parameters, fixed parameters, and solver parameters. EachParametersubclass is a member of a collection class derived fromSubParameters, called “ControlParameters”, “DerivedParameters”, “FixedParameters”, or “SolverParameters”, respectively. Indicate membership by putting the parameter subclasses into thetuple“CLASSES”:>>> from hydpy.core.parametertools import Parameter, SubParameters >>> class Par2(Parameter): ... """Parameter 2 [-].""" ... NDIM = 1 ... TYPE = float ... TIME = None >>> class Par1(Parameter): ... """Parameter 1 [-].""" ... NDIM = 1 ... TYPE = float ... TIME = None >>> class ControlParameters(SubParameters): ... """Control Parameters.""" ... CLASSES = (Par2, ... Par1)
The order within the tuple determines the order of iteration:
>>> control = ControlParameters(None) >>> control par2(?) par1(?)
Each
SubParametersobject has a fastaccess attribute. When working in pure Python mode, this is an instance of classFastAccessParameter:>>> from hydpy import classname, prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("lland_dd") >>> classname(model.parameters.control.fastaccess) 'FastAccessParameter'
When working in Cython mode (which is the default mode and much faster), fastaccess is an object of a Cython extension class specialised for the respective model and sequence group:
>>> with pub.options.usecython(True): ... model = prepare_model("lland_dd") >>> classname(model.parameters.control.fastaccess) 'ControlParameters'
- class hydpy.core.parametertools.Keyword(name: str, type_: type[float | int] = <class 'float'>, time: bool | None = None, span: tuple[float | None, float | None] = (None, None))[source]¶
Bases:
NamedTupleHelper class to describe parameter-specific keyword arguments for defining values by “calling” a parameter object.
- type_: type[float | int]¶
The keyword argument’s type (equivalent to the
TYPEattribute of classVariable).
- exception hydpy.core.parametertools.KeywordArgumentsError[source]¶
Bases:
RuntimeErrorA specialised
RuntimeErrorraised by classKeywordArguments.
- class hydpy.core.parametertools.KeywordArguments(_KeywordArguments__valid: bool = True, **keywordarguments: T)[source]¶
Bases:
Generic[T]A handler for the keyword arguments of the instances of specific
Parametersubclasses.Class
KeywordArgumentsis a rather elaborate feature of HydPy primarily thought for framework developers. One possible use-case for (advanced) HydPy users is writing polished auxiliary control files. When dealing with such a problem, have a look on methodextend().The purpose of class
KeywordArgumentsis to simplify handling instances ofParametersubclasses which allow setting values by calling them with keyword arguments. When useful, instances ofParametersubclasses should return a validKeywordArgumentsobject via propertykeywordarguments. This object should contain the keyword arguments that, when passed to the same parameter instance or another parameter instance of the same type, sets it into an equal state. This is best explained by the following example based on parameterTRefTof application modellland_dd(see the documentation on propertykeywordargumentsof classZipParameterfor additional information):>>> from hydpy.models.lland_dd import * >>> parameterstep() >>> nhru(4) >>> lnk(ACKER, LAUBW, WASSER, ACKER) >>> treft(acker=2.0, laubw=1.0) >>> treft.keywordarguments KeywordArguments(acker=2.0, laubw=1.0)
You can initialise a
KeywordArgumentsobject on your own:>>> from hydpy import KeywordArguments >>> kwargs1 = KeywordArguments(acker=3.0, laubw=2.0, nadelw=1.0) >>> kwargs1 KeywordArguments(acker=3.0, laubw=2.0, nadelw=1.0)
After preparing a
KeywordArgumentsobject, it is “valid” by default:>>> kwargs1.valid True
Pass
Falseas a positional argument to the constructor if you want yourKeywordArgumentsobject to be invalid at first:>>> kwargs2 = KeywordArguments(False) >>> kwargs2 KeywordArguments() >>> kwargs2.valid False
Flag
valid, for example, helps to distinguish between empty objects that are okay to be empty and those that are not. When we, for example, set all hydrological response units to land-use typeWASSER(water), parameterTRefTreturns the following validKeywordArgumentsobject, as its values do not need to be defined for water areas:>>> lnk(WASSER) >>> treft treft(nan) >>> treft.keywordarguments KeywordArguments() >>> treft.keywordarguments.valid True
Class
KeywordArgumentssupports features like iteration but raises the exceptionKeywordArgumentsErrorwhen trying to iterate an invalid object:>>> for keyword, argument in kwargs1: ... print(keyword, argument) acker 3.0 laubw 2.0 nadelw 1.0
>>> for keyword, argument in kwargs2: ... print(keyword, argument) Traceback (most recent call last): ... hydpy.core.parametertools.KeywordArgumentsError: Cannot iterate an invalid `KeywordArguments` object.
The same holds when trying to check if a specific keyword-value item is available:
>>> ("acker", 3.0) in kwargs1 True >>> ("laubw", 3.0) in kwargs1 False >>> ("?", "???") in kwargs1 False >>> ("laubw", 3.0) in kwargs2 Traceback (most recent call last): ... hydpy.core.parametertools.KeywordArgumentsError: Cannot check if an item is defined by an invalid `KeywordArguments` object.
However, keyword access is always possible:
>>> kwargs2["laubw"] = 3.0
>>> kwargs2["laubw"] 3.0
>>> del kwargs2["laubw"]
>>> kwargs2["laubw"] Traceback (most recent call last): ... KeyError: 'The current `KeywordArguments` object does not handle an argument under the keyword `laubw`.'
>>> del kwargs2["laubw"] Traceback (most recent call last): ... KeyError: 'The current `KeywordArguments` object does not handle an argument under the keyword `laubw`.'
Two
KeywordArgumentsobjects are considered equal if they have the same validity state, the same length, and if all items are equal:>>> KeywordArguments(True) == KeywordArguments(False) False >>> KeywordArguments(x=1) == KeywordArguments(x=1, y=2) False >>> KeywordArguments(x=1, y=2) == KeywordArguments(x=1, y=3) False >>> KeywordArguments(x=1, y=2) == KeywordArguments(x=1, y=2) True
You can also compare with other objects (always
False) and use the “!=” operator:>>> KeywordArguments() == "test" False >>> KeywordArguments(x=1, y=2) != KeywordArguments(x=1, y=2) False
- valid: bool¶
Flag indicating whether the actual
KeywordArgumentsobject is valid or not.
- add(name: str, value: T) None[source]¶
Add a keyword argument.
Method
add()works both for valid and invalidKeywordArgumentsobjects without changing their validity status:>>> from hydpy import KeywordArguments >>> kwargs = KeywordArguments() >>> kwargs.add("one", 1) >>> kwargs.valid = False >>> kwargs.add("two", 2) >>> kwargs KeywordArguments(one=1, two=2)
It raises the following error when (possibly accidentally) trying to overwrite an existing keyword argument:
>>> kwargs.add("one", 3) Traceback (most recent call last): ... hydpy.core.parametertools.KeywordArgumentsError: Cannot add argument value `3` of type `int` to the current `KeywordArguments` object as it already handles the unequal argument `1` under the keyword `one`.
On the other hand, redefining the save value causes no harm and thus does not trigger an exception:
>>> kwargs.add("one", 1) >>> kwargs KeywordArguments(one=1, two=2)
- subset_of(other: KeywordArguments[T]) bool[source]¶
Check if the actual
KeywordArgumentsobject is a subset of the given one.First, we define the following (valid)
KeywordArgumentsobjects:>>> from hydpy import KeywordArguments >>> kwargs1 = KeywordArguments(a=1, b=2) >>> kwargs2 = KeywordArguments(a= 1, b=2, c=3) >>> kwargs3 = KeywordArguments(a= 1, b=3)
Method
subset_of()requires that the keywords handled by the leftKeywordArgumentsobject form a subset of the keywords of the rightKeywordArgumentsobject:>>> kwargs1.subset_of(kwargs2) True >>> kwargs2.subset_of(kwargs1) False
Additionally, all values corresponding to the union of the relevant keywords must be equal:
>>> kwargs1.subset_of(kwargs3) False
If at least one of both
KeywordArgumentsis invalid, methodsubset_of()generally returnsFalse:>>> kwargs2.valid = False >>> kwargs1.subset_of(kwargs2) False >>> kwargs2.subset_of(kwargs1) False >>> kwargs2.subset_of(kwargs2) False
- extend(parametertype: type[Parameter], elements: Iterable[devicetools.Element], raise_exception: bool = True) None[source]¶
Extend the currently available keyword arguments based on the parameters of the given type handled by the given elements.
Sometimes (for example, when writing auxiliary control files) one is interested in a superset of all keyword arguments related to a specific
Parametertype relevant for certainElementobjects. To show how methodextend()can help in such cases, we make use of the HydPy-H-Lahn example project:>>> from hydpy.core.testtools import prepare_full_example_2 >>> hp, pub, TestIO = prepare_full_example_2()
First, we prepare an empty
KeywordArgumentsobject:>>> from hydpy import KeywordArguments >>> kwargs = KeywordArguments() >>> kwargs KeywordArguments()
When passing a
Parametersubclass (in our exampleIcMax) and someElementobjects (at first the headwater elements, which handle instances of application modelhland_96), methodextend()collects their relevant keyword arguments:>>> from hydpy.models.hland.hland_control import IcMax >>> kwargs.extend(IcMax, pub.selections.headwaters.elements) >>> kwargs KeywordArguments(field=1.0, forest=1.5)
Applying method
extend()also on the non-headwaters does not change anything, as the values of parameterIcMaxare consistent for the whole Lahn river basin:>>> kwargs.extend(IcMax, pub.selections.nonheadwaters.elements) >>> kwargs KeywordArguments(field=1.0, forest=1.5)
Next, we change the interception capacity of forests in one subcatchment:
>>> icmax = hp.elements.land_lahn_leun.model.parameters.control.icmax >>> icmax(field=1.0, forest=2.0)
Re-applying method
extend()now raises the following error:>>> kwargs.extend(IcMax, pub.selections.nonheadwaters.elements) Traceback (most recent call last): ... hydpy.core.parametertools.KeywordArgumentsError: While trying to extend the keyword arguments based on the available `IcMax` parameter objects, the following error occurred: While trying to add the keyword arguments for element `land_lahn_leun`, the following error occurred: Cannot add argument value `2.0` of type `float64` to the current `KeywordArguments` object as it already handles the unequal argument `1.5` under the keyword `forest`.
The old keywords arguments and the validity status remain unchanged:
>>> kwargs KeywordArguments(field=1.0, forest=1.5) >>> kwargs.valid True
When we modify the same
IcMaxparameter object in a way that it cannot return a validKeywordArgumentsobject anymore, we get the following error message:>>> icmax(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0) >>> kwargs.extend(IcMax, pub.selections.nonheadwaters.elements) Traceback (most recent call last): ... hydpy.core.parametertools.KeywordArgumentsError: While trying to extend the keyword arguments based on the available `IcMax` parameter objects, the following error occurred: While trying to add the keyword arguments for element `land_lahn_leun`, the following error occurred: Cannot iterate an invalid `KeywordArguments` object.
When setting the raise_exception argument to
False, methodextend()handles such errors internally and, instead of raising an error invalidates the actualKeywordArgumentsobject:>>> kwargs.extend( ... IcMax, pub.selections.nonheadwaters.elements, raise_exception=False) >>> kwargs KeywordArguments() >>> kwargs.valid False
Trying to extend an invalid
KeywordArgumentsobject by default also raises an exception of typeKeywordArgumentsError:>>> kwargs.extend(IcMax, pub.selections.headwaters.elements) Traceback (most recent call last): ... hydpy.core.parametertools.KeywordArgumentsError: While trying to extend the keyword arguments based on the available `IcMax` parameter objects, the following error occurred: The `KeywordArguments` object is invalid.
When setting raise_exception to
Falseinstead, nothing happens:>>> kwargs.extend(IcMax, pub.selections.headwaters.elements, raise_exception=False) >>> kwargs KeywordArguments() >>> kwargs.valid False
- class hydpy.core.parametertools.Parameter(subvars: SubParameters)[source]¶
Bases:
VariableBase class for model parameters.
In HydPy, each kind of model parameter is represented by a unique class. In almost all cases, you should derive such a class, directly or indirectly, from class
Parameter, which provides all necessary features that assure the new parameter class works well when applied in different contexts.In most cases, the functionalities of class
Parameterare sufficient for deriving new classes without doing any real coding (writing new or extending existing methods). However, one sometimes prefers to do some extensions to simplify the usage of the parameter class. Before doing so on your own, have a look at the specialised subclasses already available in moduleparametertools. One example is classSeasonalParameter, which allows defining parameter values that vary seasonally (e.g. the leaf area index).Class
Parameteritself extends classVariable. Hence, all model-specificParametersubclasses must define both a value dimensionality and a type via class constants NDIM and TYPE, respectively. Additionally, one has to define the class constant TIME, telling how parameter values depend on the simulation step size (see methodsget_timefactor()andapply_timefactor()). Also, model developers might find it useful to define initial default values via INIT or minimum and maximum values via SPAN:Let us first prepare a new parameter class without time-dependency (indicated by assigning
None) and initialise it:>>> from hydpy.core.parametertools import Parameter >>> class Par(Parameter): ... NDIM = 0 ... TYPE = float ... TIME = None ... SPAN = 0.0, 5.0 >>> par = Par(None)
As described in the documentation on base class
Variable, one can directly assign values via propertyvalue:>>> par.value = 6.0 >>> par par(6.0)
For
Parameterobjects, there is an alternative way to define new values, which should be preferred in most cases (especially when writing control files), by “calling” the parameter with suitable arguments. For example, this offers the advantage of automatical trimming. In the example above, the assigned value (6.0) violates the upper bound (5.0) of our test parameter. When using the “call” syntax, the wrong value is corrected immediately:>>> from hydpy import pub >>> from hydpy.core.testtools import warn_later >>> with pub.options.warntrim(True), warn_later(): ... par(7.0) UserWarning: For variable `par` at least one value needed to be trimmed. The old and the new value(s) are `7.0` and `5.0`, respectively. >>> par par(5.0)
The “call” syntax provides some additional features and related error messages. Use the auxfile keyword argument to tell that another control file defines the actual parameter value. Note that you cannot use this feature in the interactive mode:
>>> par(auxfile="test") Traceback (most recent call last): ... RuntimeError: While trying to extract information for parameter `par` from file `test`, the following error occurred: Cannot determine the corresponding model. Use the `auxfile` keyword in usual parameter control files only.
Also, note that you cannot combine the auxfile keyword with any other keyword:
>>> par(auxfile="test", x1=1, x2=2, x3=3) Traceback (most recent call last): ... ValueError: It is not allowed to combine keyword `auxfile` with other keywords, but for parameter `par` of element `?` also the following keywords are used: x1, x2, and x3.
Some
Parametersubclasses support other keyword arguments. The standard error message for unsupported arguments is the following:>>> par(wrong=1.0) Traceback (most recent call last): ... NotImplementedError: The value(s) of parameter `par` of element `?` could not be set based on the given keyword arguments.
Passing a wrong number of positional arguments results in the following error:
>>> par(1.0, 2.0) Traceback (most recent call last): ... TypeError: While trying to set the value(s) of variable `par`, the following error occurred: The given value `[1. 2.]` cannot be converted to type `float`.
Passing no argument or both positional and keyword arguments are also disallowed:
>>> par() Traceback (most recent call last): ... ValueError: For parameter `par` of element `?` neither a positional nor a keyword argument is given.
>>> par(1.0, auxfile="test") Traceback (most recent call last): ... ValueError: For parameter `par` of element `?` both positional and keyword arguments are given, which is ambiguous.
Our next
Parametertest class is a little more complicated, as it handles a (1-dimensional) vector of time-dependent values (indicated by setting the class attribute TIME toTrue):>>> from hydpy import print_vector >>> class Par(Parameter): ... NDIM = 1 ... TYPE = float ... TIME = True ... SPAN = 0.0, None
We prepare a shape of length 2 and set different simulation and parameter step sizes, to see that the required time-related adjustments work correctly:
>>> par = Par(None) >>> par.shape = (2,) >>> pub.options.parameterstep = "1d" >>> pub.options.simulationstep = "2d"
Now you can pass one single value, an iterable containing two values, or two separate values as positional arguments, to set both required values. Note that the given values are assumed to agree with the actual parameter step size, and are converted internally to agree with the actual simulation step size. The string representation shows values that agree with the parameter step size; the
valuesproperty shows the values that agree with the simulation step size, relevant during simulation runs. Also, the string representation shows only one value, in case all (relevant) values are identical:>>> par(3.0) >>> par par(3.0) >>> print_vector(par.values) 6.0, 6.0
>>> par([0.0, 4.0]) >>> par par(0.0, 4.0) >>> print_vector(par.values) 0.0, 8.0
>>> par(1.0, 2.0) >>> par par(1.0, 2.0) >>> print_vector(par.values) 2.0, 4.0
Using the call syntax to set parameter values triggers method
trim()automatically:>>> with pub.options.warntrim(True), warn_later(): ... par(-1.0, 3.0) UserWarning: For variable `par` at least one value needed to be trimmed. The old and the new value(s) are `-2.0, 6.0` and `0.0, 6.0`, respectively. >>> par par(0.0, 3.0) >>> print_vector(par.values) 0.0, 6.0
You are free to change the parameter step size (temporarily) to change the string representation of
Parameterhandling time-dependent values without a risk to change the actual values relevant for simulation:>>> with pub.options.parameterstep("2d"): ... print(par) ... print_vector(par.values) par(0.0, 6.0) 0.0, 6.0 >>> par par(0.0, 3.0) >>> print_vector(par.values) 0.0, 6.0
The highest number of dimensions of
Parametersubclasses supported is currently two. The following examples repeat some examples from above for a 2-dimensional parameter that handles that values inversely related to the simulation step size (indicated by setting the class attribute TIME toFalse):>>> from hydpy import print_matrix >>> class Par(Parameter): ... NDIM = 2 ... TYPE = float ... TIME = False ... SPAN = 0.0, 5.0
>>> par = Par(None) >>> par.shape = (2, 3)
>>> par(9.0) >>> par par(9.0) >>> print_matrix(par.values) | 4.5, 4.5, 4.5 | | 4.5, 4.5, 4.5 |
>>> par([[1.0, 2.0, 3.0], ... [4.0, 5.0, 6.0]]) >>> print_matrix(par) | 0.5, 1.0, 1.5 | | 2.0, 2.5, 3.0 | >>> print_matrix(par.values) | 0.5, 1.0, 1.5 | | 2.0, 2.5, 3.0 |
>>> par(1.0, 2.0) Traceback (most recent call last): ... ValueError: While trying to set the value(s) of variable `par`, the following error occurred: While trying to convert the value(s) `[0.5 1. ]` to a numpy ndarray with shape `(2, 3)` and type `float`, the following error occurred: could not broadcast input array from shape (2,) into shape (2,3)
- property initinfo: tuple[float | int | bool, bool]¶
A
tuplecontaining the initial value andTrueor a missing value andFalse, depending on the actualParametersubclass and the actual value of optionusedefaultvalues.In the following we show how method the effects of property
initinfowhen initiasing newParameterobjects. Let’s define a parameter test class and prepare a function for initialising it and connecting the resulting instance to aSubParametersobject:>>> from hydpy.core.parametertools import Parameter, SubParameters >>> class Test(Parameter): ... NDIM = 0 ... TYPE = float ... TIME = None ... INIT = 2.0 >>> class SubGroup(SubParameters): ... CLASSES = (Test,) >>> def prepare(): ... subpars = SubGroup(None) ... test = Test(subpars) ... test.__hydpy__connect_variable2subgroup__() ... return test
By default, making use of the INIT attribute is disabled:
>>> test = prepare() >>> test test(?)
Enable it through setting
usedefaultvaluestoTrue:>>> from hydpy import pub >>> pub.options.usedefaultvalues = True >>> test = prepare() >>> test test(2.0)
When no INIT attribute is defined (indicated by
None), enablingusedefaultvalueshas no effect, of course:>>> Test.INIT = None >>> test = prepare() >>> test test(?)
For time-dependent parameter values, the INIT attribute is assumed to be related to a
parameterstep()of one day:>>> pub.options.parameterstep = "2d" >>> pub.options.simulationstep = "12h" >>> Test.INIT = 2.0 >>> Test.TIME = True >>> test = prepare() >>> test test(4.0) >>> test.value 1.0
- classmethod get_timefactor() float[source]¶
Factor to adjust a new value of a time-dependent parameter.
For a time-dependent parameter, its effective value depends on the simulation step size. Method
get_timefactor()returns the fraction between the current simulation step size and the current parameter step size.Method
get_timefactor()raises the following error when time information is not available:>>> from hydpy.core.parametertools import Parameter >>> Parameter.get_timefactor() Traceback (most recent call last): ... RuntimeError: To calculate the conversion factor for adapting the values of the time-dependent parameters, you need to define both a parameter and a simulation time step size first.
One can define both time step sizes directly:
>>> from hydpy import pub >>> pub.options.parameterstep = "1d" >>> pub.options.simulationstep = "6h" >>> Parameter.get_timefactor() 0.25
As usual, the “global” simulation step size of the
Timegridsobject of modulepubis prefered:>>> from hydpy import pub >>> pub.timegrids = "2000-01-01", "2001-01-01", "12h" >>> Parameter.get_timefactor() 0.5
- trim(lower=None, upper=None) bool[source]¶
Apply function
trim()of modulevariabletools.
- classmethod apply_timefactor(values: ArrayFloat) ArrayFloat[source]¶
Change and return the given value(s) in accordance with
get_timefactor()and the type of time-dependence of the actual parameter subclass.For the same conversion factor returned by method
get_timefactor(), methodapply_timefactor()behaves differently depending on the TIME attribute of the respectiveParametersubclass. We first prepare a parameter test class and define both the parameter and simulation step size:>>> from hydpy.core.parametertools import Parameter >>> class Par(Parameter): ... TIME = None >>> from hydpy import pub >>> pub.options.parameterstep = "1d" >>> pub.options.simulationstep = "6h"
Nonemeans the value(s) of the parameter are not time-dependent (e.g. maximum storage capacity). Hence,apply_timefactor()returns the original value(s):>>> Par.apply_timefactor(4.0) 4.0
Truemeans the effective parameter value is proportional to the simulation step size (e.g. travel time). Hence,apply_timefactor()returns a reduced value in the next example (where the simulation step size is smaller than the parameter step size):>>> Par.TIME = True >>> Par.apply_timefactor(4.0) 1.0
Falsemeans the effective parameter value is inversely proportional to the simulation step size (e.g. storage coefficient). Hence,apply_timefactor()returns an increased value in the next example:>>> Par.TIME = False >>> Par.apply_timefactor(4.0) 16.0
- classmethod revert_timefactor(values: ArrayFloat) ArrayFloat[source]¶
The inverse version of method
apply_timefactor().See the explanations on method Parameter.apply_timefactor| to understand the following examples:
>>> from hydpy.core.parametertools import Parameter >>> class Par(Parameter): ... TIME = None >>> Par.parameterstep = "1d" >>> Par.simulationstep = "6h" >>> Par.revert_timefactor(4.0) 4.0
>>> Par.TIME = True >>> Par.revert_timefactor(4.0) 16.0
>>> Par.TIME = False >>> Par.revert_timefactor(4.0) 1.0
- update() None[source]¶
To be overridden by those “primary” parameters which need special handling and all “secondary” parameters.
- property keywordarguments: KeywordArguments¶
A
KeywordArgumentsobject.By default, instances of
Parametersubclasses return empty, invalidKeywordArgumentsobjects:>>> from hydpy.core.parametertools import Keyword, KeywordArguments, Parameter >>> par = Parameter(None) >>> kwa = par.keywordarguments >>> kwa KeywordArguments() >>> kwa.valid False
See class
ZipParameterfor an implementation example of aParametersubclass overriding this behaviour. Another example is classNmbSegments, which relies on the following mechanism.Model developers can use the private _keywordarguments attribute. Property
keywordargumentsreturns a deep copy of theKeywordArgumentsobject stored here:>>> par._keywordarguments = KeywordArguments(x=1.0, y=2.0, z=3.0) >>> par.keywordarguments KeywordArguments(x=1.0, y=2.0, z=3.0) >>> par.keywordarguments is par._keywordarguments False
We assume that the values of time-dependent keyword arguments stored under the private attribute refer to the simulation step size. However, the values of the
KeywordArgumentsobject returned bykeywordargumentsmust refer to the current parameter step size. If the relevantParameterclass providesKeywordinstances that describe the individual keyword arguments,keywordargumentscan perform the necessary adjustments automatically:>>> par.KEYWORDS = {"x": Keyword(name="x", time=None), ... "y": Keyword(name="y", time=True), ... "z": Keyword(name="z", time=False)} >>> from hydpy import pub >>> with pub.options.simulationstep("1d"), pub.options.parameterstep("2d"): ... par.keywordarguments KeywordArguments(x=1.0, y=4.0, z=1.5)
- compress_repr() str | None[source]¶
Try to find a compressed parameter value representation and return it.
compress_repr()raises aNotImplementedErrorwhen failing to find a compressed representation.For the following examples, we define a 1-dimensional sequence handling time-dependent floating-point values:
>>> from hydpy.core.parametertools import Parameter >>> class Test(Parameter): ... NDIM = 1 ... TYPE = float ... TIME = True >>> test = Test(None)
Before and directly after defining the parameter shape, nan is returned:
>>> test.compress_repr() '?' >>> test test(?) >>> test.shape = 4 >>> test test(?)
Due to the time-dependence of the values of our test class, we need to specify a parameter and a simulation time step:
>>> from hydpy import print_vector, pub >>> pub.options.parameterstep = "1d" >>> pub.options.simulationstep = "8h"
Compression succeeds when all required values are identical:
>>> test(3.0, 3.0, 3.0, 3.0) >>> print_vector(test.values) 1.0, 1.0, 1.0, 1.0 >>> test.compress_repr() '3.0' >>> test test(3.0)
Method
compress_repr()returnsNonein case the required values are not identical:>>> test(1.0, 2.0, 3.0, 3.0) >>> test.compress_repr() >>> test test(1.0, 2.0, 3.0, 3.0)
If some values are not required, indicate this by the mask descriptor:
>>> import numpy >>> test(3.0, 3.0, 3.0, numpy.nan) >>> test test(3.0, 3.0, 3.0, nan) >>> Test.mask = numpy.array([True, True, True, False]) >>> test test(3.0)
If trying to access the mask results in an error,
compress_repr()behaves as if no mask were available:>>> def getattribute(obj, name): ... if name == 'mask': ... raise BaseException ... return object.__getattribute__(obj, name) >>> Test.__getattribute__ = getattribute >>> test test(3.0, 3.0, 3.0, nan)
For a shape of zero, the string representing includes an empty list:
>>> test.shape = 0 >>> test.compress_repr() '[]' >>> test test([])
Method
compress_repr()works similarly for differentParametersubclasses. The following examples focus on a 2-dimensional parameter handling integer values:>>> from hydpy.core.parametertools import Parameter >>> class Test(Parameter): ... NDIM = 2 ... TYPE = int ... TIME = None >>> test = Test(None)
>>> test.compress_repr() '?' >>> test test(?) >>> test.shape = (2, 3) >>> test test(?)
>>> test([[3, 3, 3], ... [3, 3, 3]]) >>> test test(3)
>>> test([[3, 3, -999999], ... [3, 3, 3]]) >>> test test([[3, 3, -999999], [3, 3, 3]])
>>> Test.mask = numpy.array([[True, True, False], ... [True, True, True]]) >>> test test(3)
>>> test.shape = (0, 0) >>> test test([[]])
- class hydpy.core.parametertools.NmbParameter(subvars: SubParameters)[source]¶
Bases:
ParameterBase class for defining “number parameters” that define the shape of other variables.
We take the parameter
NmbSegmentsof application modelkinw_impl_euleras an example, which automatically prepares the shape of some 1-dimensional sequences:>>> from hydpy.models.kinw_impl_euler import * >>> parameterstep() >>> nmbsegments(2) >>> nmbsegments nmbsegments(2) >>> states.watervolume.shape (2,) >>> fluxes.internalflow.shape (1,)
As can be inferred from the different shapes of the involved variables, they must not take the original shape but can modify it. Likewise, they should but must not preserve existing values if the shape remains unchanged:
>>> states.watervolume = 1.0, 2.0 >>> nmbsegments(2) >>> states.watervolume watervolume(1.0, 2.0)
In the case of parameter
NmbSegments, zero values are allowed:>>> nmbsegments(0) >>> states.watervolume.shape (0,) >>> fluxes.internalflow.shape (0,)
- class hydpy.core.parametertools.NameParameter(subvars: SubParameters)[source]¶
Bases:
_MixinModifiableParameter,ParameterParameter displaying the names of constants instead of their values.
For demonstration, we define the test class LandType, covering three different types of land covering. For this purpose, we need to prepare a dictionary of type
Constants(class attribute constants), mapping the land type names to identity values. The class attributes NDIM, TYPE, and TIME are already set to 1, int, and None via base classNameParameter. Furthermore, both SPAN tuple entries are None becauseNameParametercan perform checks against the available constants, which is more precise than only checking against the lowest and highest constant value:>>> from hydpy.core.parametertools import Constants, NameParameter >>> class LandType(NameParameter): ... __name__ = "temp.py" ... constants = Constants(SOIL=1, WATER=2, GLACIER=3)
Additionally, we make the constants available within the local namespace (which is usually done by importing the constants from the selected application model automatically):
>>> SOIL, WATER, GLACIER = 1, 2, 3
For parameters of zero length, unprepared values, and identical required values, the string representations of
NameParametersubclasses equal those of otherParametersubclasses:>>> landtype = LandType(None) >>> landtype.shape = 0 >>> landtype landtype([]) >>> landtype.shape = 5 >>> landtype landtype(?) >>> landtype(SOIL) >>> landtype landtype(SOIL)
For non-identical required values, class
NameParameterreplaces the identity values with their names:>>> landtype(SOIL, WATER, GLACIER, WATER, SOIL) >>> landtype landtype(SOIL, WATER, GLACIER, WATER, SOIL)
For high numbers of entries, string representations are wrapped:
>>> landtype.shape = 22 >>> landtype(SOIL) >>> landtype.values[0] = WATER >>> landtype.values[-1] = GLACIER >>> landtype landtype(WATER, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, SOIL, GLACIER)
- classmethod modify_constants(constants: Constants | None) Generator[None, None, None][source]¶
Modify the relevant constants temporarily.
The constants for defining land-use types are fixed for typical “main models” like
hland. However, some submodels must take over the constants defined by their current main model, which are only known at runtime. For example, consider the following simple LandType parameter that handles predefined constants as a class attribute:>>> from hydpy.core.parametertools import Constants, NameParameter >>> class LandType(NameParameter): ... __name__ = "temp.py" ... constants = Constants(SOIL=1, WATER=2, GLACIER=3) >>> SOIL, WATER, GLACIER = 1, 2, 3 >>> landtype1 = LandType(None) >>> landtype1.shape = 3 >>> landtype1(SOIL, WATER, SOIL) >>> landtype1 landtype(SOIL, WATER, SOIL)
We can use
modify_constants()to temporarily change these constants:>>> FIELD, FOREST = 1, 4 >>> with LandType.modify_constants(Constants(FIELD=1, FOREST=4)): ... landtype2 = LandType(None) ... landtype2.shape = 4 ... landtype2(FOREST, FOREST, FIELD, FIELD) ... landtype2 landtype(FOREST, FOREST, FIELD, FIELD)
During initialisation, these constants become an instance attribute, so the parameter instance does not forget them after leaving the with block (when the class attribute is reset to its previous value):
>>> landtype1.constants {'SOIL': 1, 'WATER': 2, 'GLACIER': 3} >>> landtype2.constants {'FIELD': 1, 'FOREST': 4} >>> LandType.constants {'SOIL': 1, 'WATER': 2, 'GLACIER': 3}
One can now use both parameter instances with their specific constants without the risk of impacting the other:
>>> landtype1(SOIL, WATER, WATER) >>> landtype1 landtype(SOIL, WATER, WATER) >>> landtype2 landtype(FOREST, FOREST, FIELD, FIELD)
>>> landtype2(FIELD, FOREST, FOREST, FIELD) >>> landtype2 landtype(FIELD, FOREST, FOREST, FIELD) >>> landtype1 landtype(SOIL, WATER, WATER)
Passing
Nonedoes not overwrite the default or the previously set references:>>> with LandType.modify_constants(None): ... LandType.constants ... landtype3 = LandType(None) ... landtype3.shape = 4 ... landtype3(GLACIER, SOIL, GLACIER, WATER) ... landtype3 {'SOIL': 1, 'WATER': 2, 'GLACIER': 3} landtype(GLACIER, SOIL, GLACIER, WATER) >>> LandType.constants {'SOIL': 1, 'WATER': 2, 'GLACIER': 3} >>> landtype3 landtype(GLACIER, SOIL, GLACIER, WATER)
- trim(lower=None, upper=None) bool[source]¶
Check if all previously set values comply with the supported constants.
>>> from hydpy.core.parametertools import Constants, NameParameter >>> class LandType(NameParameter): ... __name__ = "temp.py" ... constants = Constants(SOIL=1, WATER=2, GLACIER=4) >>> SOIL, WATER, ROCK, GLACIER = 1, 2, 3, 4 >>> landtype = LandType(None) >>> landtype.shape = 4 >>> landtype(SOIL, WATER, ROCK, GLACIER) Traceback (most recent call last): .. ValueError: At least one value of parameter `landtype` of element `?` is not valid. >>> landtype landtype(SOIL, WATER, 3, GLACIER)
- class hydpy.core.parametertools.ZipParameter(subvars: SubParameters)[source]¶
Bases:
_MixinModifiableParameter,ParameterBase class for 1-dimensional model parameters that offers an additional keyword-based zipping functionality.
Many models implemented in the HydPy framework realise the concept of hydrological response units via 1-dimensional
Parameterobjects, each entry corresponding with an individual unit. To allow for a maximum of flexibility, one can define their values independently, which allows, for example, for applying arbitrary relationships between the altitude of individual response units and a precipitation correction factor to be parameterised.However, very often, hydrological modellers set identical values for different hydrological response units of the same type. One could, for example, set the same leaf area index for all units of the same land-use type. Class
ZipParameterallows defining parameters, which conveniently support this parameterisation strategy.To see how base class
ZipParameterworks, we need to create some additional subclasses. First, we need a parameter defining the type of the individual hydrological response units, which can be done by subclassing fromNameParameter. We do so by taking the example from the documentation of theNameParameterclass:>>> from hydpy.core.parametertools import NameParameter >>> SOIL, WATER, GLACIER = 1, 2, 3 >>> class LandType(NameParameter): ... SPAN = (1, 3) ... constants = {"SOIL": SOIL, "WATER": WATER, "GLACIER": GLACIER} >>> landtype = LandType(None)
Second, we need an
IndexMasksubclass. Our subclass Land references the respective LandType parameter object (we do this in a simplified manner, see classParameterCompletefor a “real world” example) but is supposed to focus on the response units of type soil or glacier only:>>> from hydpy.core.masktools import IndexMask >>> class Land(IndexMask): ... relevant = (SOIL, GLACIER) ... @staticmethod ... def get_refindices(variable): ... return variable.landtype
Third, we prepare the actual
ZipParametersubclass, holding the same constants dictionary as the LandType parameter and the Land mask as attributes. We assume that the values of our test class Par are time-dependent and set different parameter and simulation step sizes, to show that the related value adjustments work. Also, we make the LandType object available via attribute access, which is a hack to make the above simplification work:>>> from hydpy.core.parametertools import ZipParameter >>> class Par(ZipParameter): ... TYPE = float ... TIME = True ... SPAN = (0.0, None) ... constants = LandType.constants ... mask = Land() ... landtype = landtype >>> par = Par(None) >>> from hydpy import pub >>> pub.options.parameterstep = "1d" >>> pub.options.simulationstep = "12h"
For parameters with zero-length or with unprepared or identical parameter values, the string representation looks as usual:
>>> from hydpy import print_vector >>> landtype.shape = 0 >>> par.shape = 0 >>> par par([]) >>> landtype.shape = 5 >>> landtype(SOIL, WATER, GLACIER, WATER, SOIL) >>> par.shape = 5 >>> par par(?) >>> par(2.0) >>> par par(2.0) >>> print_vector(par.values) 1.0, 1.0, 1.0, 1.0, 1.0
The extended feature of class
ZipParameteris to allow passing values via keywords, each keyword corresponding to one of the relevant constants (in our example: SOIL and GLACIER) in lower case letters:>>> par(soil=4.0, glacier=6.0) >>> par par(glacier=6.0, soil=4.0) >>> print_vector(par.values) 2.0, nan, 3.0, nan, 2.0
Use the default argument if you want to assign the same value to entries with different constants:
>>> par(soil=2.0, default=8.0) >>> par par(glacier=8.0, soil=2.0) >>> print_vector(par.values) 1.0, nan, 4.0, nan, 1.0
Using a keyword argument corresponding to an existing, but not relevant constant (in our example: WATER) is silently ignored:
>>> par(soil=4.0, glacier=6.0, water=8.0) >>> par par(glacier=6.0, soil=4.0) >>> print_vector(par.values) 2.0, nan, 3.0, nan, 2.0
However, using a keyword not corresponding to any constant raises an exception:
>>> par(soil=4.0, glacier=6.0, wrong=8.0) Traceback (most recent call last): ... TypeError: While trying to set the values of parameter `par` of element `?` based on keyword arguments `soil, glacier, and wrong`, the following error occurred: Keyword `wrong` is not among the available model constants.
The same is true when passing incomplete information:
>>> par(soil=4.0) Traceback (most recent call last): ... TypeError: While trying to set the values of parameter `par` of element `?` based on keyword arguments `soil`, the following error occurred: The given keywords are incomplete and no default value is available.
Values exceeding the bounds defined by class attribute SPAN are trimmed as usual:
>>> from hydpy import pub >>> with pub.options.warntrim(False): ... par(soil=-10.0, glacier=10.0) >>> par par(glacier=10.0, soil=0.0)
For convenience, you can get or set all values related to a specific constant via attribute access:
>>> print_vector(par.soil) 0.0, 0.0 >>> par.soil = 2.5 >>> par par(glacier=10.0, soil=5.0)
Improper use of these “special attributes” results in errors like the following:
>>> par.Soil Traceback (most recent call last): ... AttributeError: `Soil` is neither a normal attribute of parameter `par` of element `?` nor among the following special attributes: soil, water, and glacier...
>>> par.soil = "test" Traceback (most recent call last): ... ValueError: While trying the set the value(s) of parameter `par` of element `?` related to the special attribute `soil`, the following error occurred: could not convert string to float: 'test'
- classmethod modify_refindices(refindices: NameParameter | None) Generator[None, None, None][source]¶
Eventually, set or modify the reference to the required index parameter.
The following example demonstrates that changes affect the relevant class only temporarily, but its objects initialised within the “with” block persistently:
>>> from hydpy.core.variabletools import FastAccess, Variable >>> GRASS, TREES, WATER = 0, 1, 2 >>> class RefPar(Variable): ... NDIM = 1 ... TYPE = int ... TIME = None ... initinfo = 0, True ... _CLS_FASTACCESS_PYTHON = FastAccess ... constants = {"GRASS": GRASS, "TREES": TREES, "WATER": WATER} ... relevant = (0, 1, 2) >>> from hydpy.core.parametertools import ZipParameter >>> from hydpy.core.masktools import SubmodelIndexMask >>> class ZipPar(ZipParameter): ... NDIM = 1 ... TYPE = float ... TIME = None ... initinfo = 0.0, True ... _CLS_FASTACCESS_PYTHON = FastAccess ... constants = {"FIELD": 1, "FOREST": 3} ... relevant = (1, 3) ... mask = SubmodelIndexMask() >>> refpar = RefPar(None) >>> refpar.shape = 3 >>> refpar(1, 2, 1) >>> with ZipPar.modify_refindices(refpar): ... ZipPar.refindices.name ... ZipPar.constants ... ZipPar.relevant ... zippar1 = ZipPar(None) ... zippar1.shape = 3 ... zippar1(water=1.0, default=2.) ... zippar1 'refpar' {'GRASS': 0, 'TREES': 1, 'WATER': 2} (0, 1, 2) zippar(trees=2.0, water=1.0)
>>> ZipPar.refindices >>> ZipPar.constants {'FIELD': 1, 'FOREST': 3} >>> ZipPar.relevant (1, 3) >>> zippar1 zippar(trees=2.0, water=1.0)
Passing
Nonedoes not overwrite previously set references:>>> with ZipPar.modify_refindices(None): ... ZipPar.refindices ... ZipPar.constants ... ZipPar.relevant {'FIELD': 1, 'FOREST': 3} (1, 3)
>>> with ZipPar.modify_refindices(None): ... zippar2 = ZipPar(None) ... zippar2.shape = 3 ... zippar2(water=1.0, default=2.) Traceback (most recent call last): ... RuntimeError: While trying to set the values of parameter `zippar` of element `?` based on keyword arguments `water and default`, the following error occurred: Variable `zippar` of element `?` does currently not reference an instance-specific index parameter.
>>> ZipPar.refindices >>> ZipPar.constants {'FIELD': 1, 'FOREST': 3} >>> ZipPar.relevant (1, 3)
- refindices: NameParameter | None = None¶
Optional reference to the relevant index parameter.
- property keywordarguments: KeywordArguments[float]¶
A
KeywordArgumentsobject providing the currently valid keyword arguments.We take parameter
TRefTof application modellland_ddas an example and set its shape (the number of hydrological response units defined by parameterNHRU) to four and prepare the land use typesACKER(acre),LAUBW(deciduous forest), andWASSER(water) via parameterLnk:>>> from hydpy.models.lland_dd import * >>> parameterstep() >>> nhru(4) >>> lnk(ACKER, LAUBW, WASSER, ACKER)
After defining all required values via keyword arguments (note that parameter
TRefTdoes not need any values for response units of typeWASSER), propertykeywordargumentsmakes exactly these keywords arguments available:>>> treft(acker=2.0, laubw=1.0) >>> treft.keywordarguments KeywordArguments(acker=2.0, laubw=1.0) >>> treft.keywordarguments.valid True
In the following example, both the first and the fourth response unit are of type
ACKERbut have differentTRefTvalues, which cannot be the result of defining values via keyword arguments. Hence, the returnedKeywordArgumentsobject is invalid:>>> treft(1.0, 2.0, 3.0, 4.0) >>> treft.keywordarguments KeywordArguments() >>> treft.keywordarguments.valid False
This is different from the situation where all response units are of type
WASSER, where one does not need to define any values for parameterTRefT. Thus, the returnedKeywordArgumentsobject is also empty but valid:>>> lnk(WASSER) >>> treft.keywordarguments KeywordArguments() >>> treft.keywordarguments.valid True
ToDo: document “refinement” asa lland_dd uses the AETModel_V1 interface
- class hydpy.core.parametertools.SeasonalParameter(subvars)[source]¶
Bases:
ParameterBase class for parameters handling values showing a seasonal variation.
Quite a lot of model parameter values change on an annual basis. One example is the leaf area index. For deciduous forests within temperate climatic regions, it shows a clear peak during the summer season.
If you want to vary the parameter values on a fixed (for example, a monthly) basis,
KeywordParameter2Dmight be the best starting point. See theLanduseMonthParameterclass of thellandbase model as an example, which is used to define parameterLAI, handling monthly leaf area index values for different land-use classes.However, class
SeasonalParameteroffers more flexibility in defining seasonal patterns, which is often helpful for modelling technical control systems. One example is the parameterTargetVolumeof base modeldamfor determining a dam’s desired volume throughout the year.For the following examples, we assume a simulation step size of one day:
>>> from hydpy import pub >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
Let us prepare an empty 1-dimensional
SeasonalParameterinstance:>>> from hydpy.core.parametertools import SeasonalParameter >>> class Par(SeasonalParameter): ... NDIM = 1 ... TIME = None >>> par = Par(None) >>> par.NDIM = 1 >>> par par()
The shape is determined automatically, as described in the documentation on property
shapein more detail:>>> par.shape = (None,) >>> par.shape (366,)
Pairs of
TOYobjects andfloatvalues define the seasonal pattern. One can assign them all at once via keyword arguments:>>> par(_1=2., _7_1=4., _3_1_0_0_0=5.)
Note that all keywords in the call above are proper
TOYinitialisation arguments. Misspelt keywords result in error messages like the following:>>> Par(None)(_a=1.) Traceback (most recent call last): ... ValueError: While trying to define the seasonal parameter value `par` of element `?` for time of year `_a`, the following error occurred: While trying to initialise a TOY object based on argument value `_a` of type `str`, the following error occurred: While trying to retrieve the month, the following error occurred: For TOY (time of year) objects, all properties must be of type `int`, but the value `a` of type `str` given for property `month` cannot be converted to `int`.
As the following string representation shows are the pairs of each
SeasonalParameterinstance automatically sorted:>>> par par(toy_1_1_0_0_0=2.0, toy_3_1_0_0_0=5.0, toy_7_1_0_0_0=4.0)
By default, toy is used as a prefix string. Using this prefix string, one can change the toy-value pairs via attribute access:
>>> par.toy_1_1_0_0_0 2.0 >>> del par.toy_1_1_0_0_0 >>> par.toy_2_1_0_0_0 = 2. >>> par par(toy_2_1_0_0_0=2.0, toy_3_1_0_0_0=5.0, toy_7_1_0_0_0=4.0)
For attribute access, zero hours, minutes, or seconds can be left out:
>>> par.toy_2_1 2.0
When using functions
getattr()anddelattr(), one can also omit the “toy” prefix:>>> getattr(par, "2_1") 2.0 >>> delattr(par, "2_1") >>> getattr(par, "2_1") Traceback (most recent call last): ... AttributeError: Seasonal parameter `par` of element `?` has neither a normal attribute nor does it handle a "time of year" named `2_1`. >>> delattr(par, "2_1") Traceback (most recent call last): ... AttributeError: Seasonal parameter `par` of element `?` has neither a normal attribute nor does it handle a "time of year" named `2_1`.
Applying the
len()operator onSeasonalParameterobjects returns the number of toy-value pairs.>>> len(par) 2
New values are checked to be compatible with the predefined shape:
>>> par.toy_1_1_0_0_0 = [1., 2.] Traceback (most recent call last): ... TypeError: While trying to add a new or change an existing toy-value pair for the seasonal parameter `par` of element `?`, the following error occurred: float() argument must be a string or a... number... >>> par = Par(None) >>> par.NDIM = 2 >>> par.shape = (None, 3) >>> par.toy_1_1_0_0_0 = [1., 2.] Traceback (most recent call last): ... ValueError: While trying to add a new or change an existing toy-value pair for the seasonal parameter `par` of element `?`, the following error occurred: could not broadcast input array from shape (2,) into shape (3,)
If you do not require seasonally varying parameter values in a specific situation, you can pass a single positional argument:
>>> par(5.0) >>> par par([5.0, 5.0, 5.0])
Note that class
SeasonalParameterassociates the given value(s) to the “first” time of the year internally:>>> par.toys (TOY("1_1_0_0_0"),)
Incompatible positional arguments result in errors like the following:
>>> par(1.0, 2.0) Traceback (most recent call last): ... ValueError: While trying to set the value(s) of variable `par`, the following error occurred: While trying to convert the value(s) `[1. 2.]` to a numpy ndarray with shape `(366, 3)` and type `float`, the following error occurred: could not broadcast input array from shape (2,) into shape (366,3)
- refresh() None[source]¶
Update the actual simulation values based on the toy-value pairs.
Usually, one does not need to call refresh explicitly. The “magic” methods __call__, __setattr__, and __delattr__ invoke it automatically, when required.
Method
refresh()calculates only those time variable parameter values required for the defined initialisation period. We start with an initialisation period covering a full year, making a complete calculation necessary:>>> from hydpy import pub >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d"
Instantiate a 1-dimensional
SeasonalParameterobject:>>> from hydpy.core.parametertools import SeasonalParameter >>> class Par(SeasonalParameter): ... NDIM = 1 ... TYPE = float ... TIME = None >>> par = Par(None) >>> par.shape = (None,)
When a
SeasonalParameterobject does not contain any toy-value pairs yet, the methodrefresh()sets all actual simulation values to zero:>>> par.values = 1.0 >>> par.refresh() >>> from hydpy import round_ >>> round_(par.values[0]) 0.0
When there is only one toy-value pair, its values are relevant for all actual simulation values:
>>> par.toy_1 = 2.0 # calls refresh automatically >>> round_(par.values[0]) 2.0
Method
refresh()performs a linear interpolation for the central time points of each simulation time step. Hence, in the following example, the original values of the toy-value pairs do not show up:>>> par.toy_12_31 = 4.0 >>> round_(par.values[0]) 2.00274 >>> round_(par.values[-2]) 3.99726 >>> round_(par.values[-1]) 3.0
If one wants to preserve the original values in this example, one must set the corresponding toy instances in the middle of some simulation step intervals:
>>> del par.toy_1 >>> del par.toy_12_31 >>> par.toy_1_1_12 = 2 >>> par.toy_12_31_12 = 4.0 >>> round_(par.values[0]) 2.0 >>> round_(par.values[1]) 2.005479 >>> round_(par.values[-2]) 3.994521 >>> round_(par.values[-1]) 4.0
For short initialisation periods, method
refresh()performs only the required interpolations for efficiency:>>> pub.timegrids = "2000-01-02", "2000-01-05", "1d" >>> Par.NDIM = 2 >>> par = Par(None) >>> par.shape = (None, 3) >>> par.toy_1_2_12 = 2.0 >>> par.toy_1_6_12 = 0.0, 2.0, 4.0 >>> from hydpy import print_matrix >>> print_matrix(par.values[:6]) | nan, nan, nan | | 2.0, 2.0, 2.0 | | 1.5, 2.0, 2.5 | | 1.0, 2.0, 3.0 | | nan, nan, nan | | nan, nan, nan |
- interp(date: Date) float[source]¶
Perform a linear value interpolation for the given date and return the result.
Instantiate a 1-dimensional
SeasonalParameterobject:>>> from hydpy import pub >>> pub.timegrids = "2000-01-01", "2001-01-01", "1d" >>> from hydpy.core.parametertools import SeasonalParameter >>> class Par(SeasonalParameter): ... NDIM = 1 ... TYPE = float ... TIME = None >>> par = Par(None) >>> par.shape = (None,)
Define three toy-value pairs:
>>> par(_1=2.0, _2=5.0, _12_31=4.0)
Passing a
Dateobject matching aTOYobject exactly returns the correspondingfloatvalue:>>> from hydpy import Date >>> par.interp(Date("2000.01.01")) 2.0 >>> par.interp(Date("2000.02.01")) 5.0 >>> par.interp(Date("2000.12.31")) 4.0
For all intermediate points,
interp()performs a linear interpolation:>>> from hydpy import round_ >>> round_(par.interp(Date("2000.01.02"))) 2.096774 >>> round_(par.interp(Date("2000.01.31"))) 4.903226 >>> round_(par.interp(Date("2000.02.02"))) 4.997006 >>> round_(par.interp(Date("2000.12.30"))) 4.002994
Linear interpolation is also allowed between the first and the last pair when they do not capture the endpoints of the year:
>>> par(_1_2=2.0, _12_30=4.0) >>> round_(par.interp(Date("2000.12.29"))) 3.99449 >>> par.interp(Date("2000.12.30")) 4.0 >>> round_(par.interp(Date("2000.12.31"))) 3.333333 >>> round_(par.interp(Date("2000.01.01"))) 2.666667 >>> par.interp(Date("2000.01.02")) 2.0 >>> round_(par.interp(Date("2000.01.03"))) 2.00551
The following example briefly shows interpolation performed for a 2-dimensional parameter:
>>> Par.NDIM = 2 >>> par = Par(None) >>> par.shape = (None, 2) >>> par(_1_1=[1., 2.], _1_3=[-3, 0.]) >>> result = par.interp(Date("2000.01.02")) >>> round_(result[0]) -1.0 >>> round_(result[1]) 1.0
- shape¶
A tuple containing the actual lengths of all dimensions.
Setting the shape of
SeasonalParameterobjects differs from setting the shape of otherVariablesubclasses, due to handling time on the first axis. The simulation step size determines the length of this axis. Hence, trying to set the shape before the simulation step size is known does not work:>>> from hydpy.core.parametertools import SeasonalParameter >>> class Par(SeasonalParameter): ... NDIM = 1 ... TYPE = float ... TIME = None >>> par = Par(None) >>> par.shape = (None,) Traceback (most recent call last): ... RuntimeError: It is not possible the set the shape of the seasonal parameter `par` of element `?` at the moment. You need to define the simulation step size first. However, in complete HydPy projects this stepsize is indirectly defined via `pub.timegrids.stepsize` automatically.
After preparing the simulation step size, you can pass a tuple with a single entry of any value to define the shape of the defined 1-dimensional test class. Property
shapereplaces this arbitrary value by the number of simulation steps fitting into a leap year:>>> from hydpy import pub >>> pub.options.simulationstep = "1d" >>> par.shape = (123,) >>> par.shape (366,)
Assigning a single, arbitrary value also works well:
>>> par.shape = None >>> par.shape (366,)
For higher-dimensional parameters, property
shapereplaces the first entry of the assigned iterable, accordingly:>>> Par.NDIM = 2 >>> par.shape = (None, 3) >>> par.shape (366, 3)
For simulation steps not cleanly fitting into a leap year, the ceil-operation determines the number of entries:
>>> pub.options.simulationstep = "100d" >>> par.shape = (None, 3) >>> par.shape (4, 3)
- trim(lower=None, upper=None) bool[source]¶
Extend the usual trim logic with a warning mechanism to account for that trimming only affects “background values” and not the “visible” time of year / value pairs.
The usual trimming process affects the simulation-relevant “background values”, not the “visible” original values supplied by the user. Hence,
SeasonalParametertries to keep track of recent trimmings to warn if users try to used the unmodified “visible” values later:>>> from hydpy import pub, round_ >>> pub.timegrids = "2000-01-01", "2000-01-04", "1d" >>> from hydpy.models.sw1d import * >>> parameterstep() >>> upperlowwaterthreshold.shape = 1 >>> upperlowwaterthreshold.values[:3] = 1.0, 2.0, 3.0 >>> bottomlowwaterthreshold(2.0) >>> round_(bottomlowwaterthreshold.values[:3]) 1.0, 2.0, 2.0 >>> from hydpy.core.testtools import warn_later >>> with pub.options.warntrim(True), warn_later(): ... bottomlowwaterthreshold bottomlowwaterthreshold(2.0) UserWarning: The "background values" of parameter `bottomlowwaterthreshold` of element `?` have been trimmed but not its original time of year-specific values. Using the latter without modification might result in inconsistencies.
In such cases, modify the values of the affected parameter or its boundaries and call
refresh()manually. If successful, the warning disappears:>>> upperlowwaterthreshold.values[0] = 2.0 >>> bottomlowwaterthreshold.refresh() >>> with pub.options.warntrim(True): ... bottomlowwaterthreshold bottomlowwaterthreshold(2.0)
The explained mechanism equivalently works when defining seasonally varying parameter values and trying to access the unmodified “visible” values in other ways:
>>> bottomlowwaterthreshold(_1_1_12=3.0, _1_3_12=1.0) >>> round_(bottomlowwaterthreshold.values[:3]) 2.0, 2.0, 1.0 >>> with pub.options.warntrim(True), warn_later(): ... round_(bottomlowwaterthreshold.toy_1_1_12) 3.0 UserWarning: The "background values"...result in inconsistencies. >>> with pub.options.warntrim(True), warn_later(): ... tuple(bottomlowwaterthreshold) ((TOY("1_1_12_0_0"), 3.0), (TOY("1_3_12_0_0"), 1.0)) UserWarning: The "background values"...result in inconsistencies. >>> with pub.options.warntrim(True), warn_later(): ... len(bottomlowwaterthreshold) 2 UserWarning: The "background values"...result in inconsistencies.
- class hydpy.core.parametertools.KeywordParameter1D(subvars: SubParameters)[source]¶
Bases:
_MixinModifiableParameter,ParameterBase class for 1-dimensional model parameters with values depending on one factor.
When subclassing from
KeywordParameter1D, one needs to define the class attribute entrynames. A typical use case is that entrynames defines seasons like the months or, as in our example, half-years:>>> from hydpy.core.parametertools import KeywordParameter1D >>> class IsHot(KeywordParameter1D): ... TYPE = bool ... TIME = None ... entrynames = ("winter", "summer")
Usually,
KeywordParameter1Dobjects prepare their shape automatically. However, to simplify this test case, we define it manually:>>> ishot = IsHot(None) >>> ishot.shape = 2
You can pass all parameter values both via positional or keyword arguments:
>>> ishot(True) >>> ishot ishot(True)
>>> ishot(False, True) >>> ishot ishot(winter=False, summer=True)
>>> from hydpy import print_vector >>> ishot(winter=True, summer=False) >>> ishot ishot(winter=True, summer=False) >>> print_vector(ishot.values) True, False
We check the given keyword arguments for correctness and completeness:
>>> ishot(winter=True) Traceback (most recent call last): ... ValueError: When setting parameter `ishot` of element `?` via keyword arguments, each string defined in `entrynames` must be used as a keyword, but the following keywords are not: `summer`.
>>> ishot(winter=True, summer=False, spring=True, autumn=False) Traceback (most recent call last): ... ValueError: When setting parameter `ishot` of element `?` via keyword arguments, each keyword must be defined in `entrynames`, but the following keywords are not: `spring and autumn`.
Class
KeywordParameter1Dimplements attribute access, including specialised error messages:>>> ishot.winter, ishot.summer = ishot.summer, ishot.winter >>> ishot ishot(winter=False, summer=True)
>>> ishot.spring Traceback (most recent call last): ... AttributeError: Parameter `ishot` of element `?` does not handle an attribute named `spring`.
>>> ishot.shape = 1 >>> ishot.summer Traceback (most recent call last): ... IndexError: While trying to retrieve a value from parameter `ishot` of element `?` via the attribute `summer`, the following error occurred: index 1 is out of bounds for axis 0 with size 1
>>> ishot.summer = True Traceback (most recent call last): ... IndexError: While trying to assign a new value to parameter `ishot` of element `?` via attribute `summer`, the following error occurred: index 1 is out of bounds for axis 0 with size 1
- classmethod modify_entries(constants: Constants | None) Generator[None, None, None][source]¶
Modify the relevant entry names temporarily.
The entry names for defining properties like land-use types are fixed for typical “main models” like
hland. However, some submodels must take over the entry names defined by their current main model, which are only known at runtime. For example, consider the following simple LAI parameter that handles predefined land use type names as a class attribute:>>> from hydpy.core.parametertools import KeywordParameter1D >>> class LAI(KeywordParameter1D): ... TYPE = float ... TIME = None ... entrynames = ("field", "forest") >>> lai1 = LAI(None) >>> lai1.shape = 2 >>> lai1(field=1.0, forest=2.0) >>> lai1 lai(field=1.0, forest=2.0)
We can use
modify_entries()to temporarily change these names:>>> from hydpy.core.parametertools import Constants >>> constants = Constants(GRASS=2, SOIL=1, TREES=3) >>> with LAI.modify_entries(constants): ... lai2 = LAI(None) ... lai2.shape = 3 ... lai2(soil=0.0, grass=1.0, trees=2.0) ... lai2 lai(soil=0.0, grass=1.0, trees=2.0)
During initialisation, the names and the lowest value of the given constants become instance attributes, so the parameter instance does not forget them after leaving the with block (when the class attribute is reset to its previous value):
>>> lai1.entrynames ('field', 'forest') >>> lai2.entrynames ('soil', 'grass', 'trees') >>> LAI.entrynames ('field', 'forest')
>>> lai1.entrymin 0 >>> lai2.entrymin 1 >>> LAI.entrymin 0
Passing
Nonedoes not overwrite the default or the previously set references:>>> LAI.entrymin = 3 >>> with LAI.modify_entries(None): ... LAI.entrynames ... LAI.entrymin ... lai3 = LAI(None) ... lai3.shape = 2 ... lai3(field=2.0, forest=1.0) ... lai3 ('field', 'forest') 3 lai(field=2.0, forest=1.0) >>> LAI.entrynames ('field', 'forest') >>> LAI.entrymin 3 >>> lai3 lai(field=2.0, forest=1.0)
- class hydpy.core.parametertools.MonthParameter(subvars: SubParameters)[source]¶
Bases:
KeywordParameter1DBase class for parameters whose values depend on the actual month.
Please see the documentation on class
KeywordParameter1Don how to useMonthParameterobjects and classWG2Zof base modelllandas an example implementation:>>> from hydpy.models.lland import * >>> simulationstep("12h") >>> parameterstep("1d") >>> wg2z(3.0, 2.0, 1.0, 0.0, -1.0, -2.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0) >>> wg2z wg2z(jan=3.0, feb=2.0, mar=1.0, apr=0.0, may=-1.0, jun=-2.0, jul=-3.0, aug=-2.0, sep=-1.0, oct=0.0, nov=1.0, dec=2.0)
Note that attribute access provides access to the “real” values related to the current simulation time step:
>>> wg2z.feb 2.0 >>> wg2z.feb = 4.0 >>> wg2z wg2z(jan=3.0, feb=4.0, mar=1.0, apr=0.0, may=-1.0, jun=-2.0, jul=-3.0, aug=-2.0, sep=-1.0, oct=0.0, nov=1.0, dec=2.0)
- class hydpy.core.parametertools.KeywordParameter2D(subvars: SubParameters)[source]¶
Bases:
_MixinModifiableParameter,ParameterBase class for 2-dimensional model parameters with values depending on two factors.
When subclassing from
KeywordParameter2Done needs to define the attributes rownames and columnnames (both of typetuple). A typical use case is that rownames defines some land-use classes, and columnnames defines seasons, months, etc. Here, we consider a simple corresponding example where the values of the boolean parameter IsWarm depend on the on the hemisphere and the half-year period:>>> from hydpy.core.parametertools import KeywordParameter2D >>> class IsWarm(KeywordParameter2D): ... TYPE = bool ... TIME = None ... rownames = ("north", "south") ... columnnames = ("apr2sep", "oct2mar")
Instantiate the defined parameter class and define its shape:
>>> iswarm = IsWarm(None) >>> iswarm.shape = (2, 2)
KeywordParameter2Dallows us to set the values of all rows via keyword arguments:>>> from hydpy import print_matrix >>> iswarm(north=[True, False], ... south=[False, True]) >>> iswarm iswarm(north=[True, False], south=[False, True]) >>> print_matrix(iswarm.values) | True, False | | False, True |
If a keyword is missing, it raises a
ValueError:>>> iswarm(north=[True, False]) Traceback (most recent call last): ... ValueError: While setting parameter `iswarm` of element `?` via row related keyword arguments, each string defined in `rownames` must be used as a keyword, but the following keywords are not: `south`.
One can modify single rows via attribute access:
>>> from hydpy import print_vector >>> iswarm.north = False, False >>> print_vector(iswarm.north) False, False
The same holds for the columns:
>>> iswarm.apr2sep = True, False >>> print_vector(iswarm.apr2sep) True, False
Also, combined row-column access is possible:
>>> iswarm.north_apr2sep True >>> iswarm.north_apr2sep = False >>> iswarm.north_apr2sep False
All three forms of attribute access define augmented exception messages in case anything goes wrong:
>>> iswarm.north = True, True, True Traceback (most recent call last): ... ValueError: While trying to assign new values to parameter `iswarm` of element `?` via the row related attribute `north`, the following error occurred: could not broadcast input array from shape (3,) into shape (2,) >>> iswarm.apr2sep = True, True, True Traceback (most recent call last): ... ValueError: While trying to assign new values to parameter `iswarm` of element `?` via the column related attribute `apr2sep`, the following error occurred: could not broadcast input array from shape (3,) into shape (2,)
>>> iswarm.shape = (1, 1)
>>> iswarm.south_apr2sep = False Traceback (most recent call last): ... IndexError: While trying to assign new values to parameter `iswarm` of element `?` via the row and column related attribute `south_apr2sep`, the following error occurred: index 1 is out of bounds for axis 0 with size 1
>>> iswarm.south Traceback (most recent call last): ... IndexError: While trying to retrieve values from parameter `iswarm` of element `?` via the row related attribute `south`, the following error occurred: index 1 is out of bounds for axis 0 with size 1 >>> iswarm.oct2mar Traceback (most recent call last): ... IndexError: While trying to retrieve values from parameter `iswarm` of element `?` via the column related attribute `oct2mar`, the following error occurred: index 1 is out of bounds for axis 1 with size 1 >>> iswarm.south_oct2mar Traceback (most recent call last): ... IndexError: While trying to retrieve values from parameter `iswarm` of element `?` via the row and column related attribute `south_oct2mar`, the following error occurred: index 1 is out of bounds for axis 0 with size 1
>>> iswarm.shape = (2, 2)
Unknown attribute names result in the following error:
>>> iswarm.wrong Traceback (most recent call last): ... AttributeError: Parameter `iswarm` of element `?` does neither handle a normal attribute nor a row or column related attribute named `wrong`.
One can still define the parameter values via positional arguments:
>>> iswarm(True) >>> iswarm iswarm(north=[True, True], south=[True, True])
For parameters with many columns, string representations are adequately wrapped:
>>> iswarm.shape = (2, 10) >>> iswarm iswarm(north=[False, False, False, False, False, False, False, False, False, False], south=[False, False, False, False, False, False, False, False, False, False])
- classmethod modify_rows(constants: Constants | None) Generator[None, None, None][source]¶
Modify the relevant row names temporarily.
Methods
modify_rows()andmodify_columns()serve the same purpose and behave exactly on the respective axisKeywordParameter2Dinstances as methodmodify_entries()on the single axis ofKeywordParameter1Dinstances. Hence, we only test their implementation here. Please read the documentation on methodmodify_entries()for more information:>>> from hydpy import print_vector >>> from hydpy.core.parametertools import KeywordParameter2D >>> class IsWarm(KeywordParameter2D): ... TYPE = bool ... TIME = None ... rownames = ("north", "south") ... columnnames = ("apr2sep", "oct2mar") >>> iswarm1 = IsWarm(None) >>> iswarm1.shape = (2, 2) >>> iswarm1(north=[True, False], ... south=[False, True]) >>> print_vector(iswarm1.north) True, False >>> print_vector(iswarm1.apr2sep) True, False
>>> from hydpy.core.parametertools import Constants >>> consts_row = Constants(N=1, S=2) >>> consts_column = Constants(APR2JUN=2, JUN2SEP=3, OCT2DEC=4, JAN2MAR=5) >>> with IsWarm.modify_rows(consts_row), IsWarm.modify_columns(consts_column): ... iswarm2 = IsWarm(None) ... iswarm2.shape = (2, 4) ... iswarm2(n=[True, True, False, False], ... s=[False, False, True, True]) ... print_vector(iswarm2.n) ... print_vector(iswarm2.apr2jun) True, True, False, False True, False
>>> iswarm1.rownames ('north', 'south') >>> iswarm1.columnnames ('apr2sep', 'oct2mar') >>> iswarm1.rowmin 0 >>> iswarm1.columnmin 0
>>> iswarm2.rownames ('n', 's') >>> iswarm2.columnnames ('apr2jun', 'jun2sep', 'oct2dec', 'jan2mar') >>> iswarm2.rowmin 1 >>> iswarm2.columnmin 2
>>> IsWarm.rownames ('north', 'south') >>> IsWarm.columnnames ('apr2sep', 'oct2mar') >>> IsWarm.rowmin 0 >>> IsWarm.columnmin 0
>>> IsWarm.rowmin = 2 >>> IsWarm.columnmin = 3 >>> with IsWarm.modify_rows(None), IsWarm.modify_columns(None): ... IsWarm.rownames ... IsWarm.rowmin ... IsWarm.columnnames ... IsWarm.columnmin ... iswarm3 = IsWarm(None) ... iswarm3.shape = (2, 2) ... iswarm3(north=[True, False], ... south=[False, True]) ... iswarm3 ... print_vector(iswarm1.north) ('north', 'south') 2 ('apr2sep', 'oct2mar') 3 iswarm(north=[True, False], south=[False, True]) True, False >>> IsWarm.rownames ('north', 'south') >>> IsWarm.columnnames ('apr2sep', 'oct2mar') >>> IsWarm.rowmin 2 >>> IsWarm.columnmin 3 >>> iswarm3 iswarm(north=[True, False], south=[False, True]) >>> print_vector(iswarm1.north) True, False
- class hydpy.core.parametertools.LeftRightParameter(subvars: SubParameters)[source]¶
Bases:
MixinFixedShape,ParameterBase class for handling two values, a left one and a right one.
The original purpose of class
LeftRightParameteris to make the handling of river channel-related parameters with different values for both river banks a little more convenient.As an example, we define a parameter class describing the width of both the left and the right floodplain of a river segment:
>>> from hydpy.core.parametertools import LeftRightParameter >>> class FloodPlainWidth(LeftRightParameter): ... TYPE = float ... TIME = None >>> floodplainwidth = FloodPlainWidth(None)
Here, we need to set the shape of the parameter to 2, which is an automated procedure in full model setups:
>>> floodplainwidth.shape = 2
Parameter values can be defined as usual:
>>> floodplainwidth(3.0) >>> floodplainwidth floodplainwidth(3.0)
Alternatively, use the keywords left or l and right or r to define both values individually:
>>> floodplainwidth(left=1.0, right=2.0) >>> floodplainwidth floodplainwidth(left=1.0, right=2.0) >>> floodplainwidth(l=2.0, r=1.0) >>> floodplainwidth floodplainwidth(left=2.0, right=1.0)
Incomplete information results in the following errors:
>>> floodplainwidth(left=2.0) Traceback (most recent call last): ... ValueError: When setting the values of parameter `floodplainwidth` of element `?` via keyword arguments, either `right` or `r` for the "right" parameter value must be given, but is not.
>>> floodplainwidth(right=1.0) Traceback (most recent call last): ... ValueError: When setting the values of parameter `floodplainwidth` of element `?` via keyword arguments, either `left` or `l` for the "left" parameter value must be given, but is not.
Additionally, one can query and modify the individual values via the attribute names left and right:
>>> floodplainwidth.left 2.0 >>> floodplainwidth.left = 3.0 >>> floodplainwidth.right 1.0 >>> floodplainwidth.right = 4.0 >>> floodplainwidth floodplainwidth(left=3.0, right=4.0)
- class hydpy.core.parametertools.SortedParameter(subvars: SubParameters)[source]¶
Bases:
ParameterBase parameter class that checks if given values are sorted in increasing order.
We take the subclassed parameter
Heightsof the base modelwqas an example:>>> from hydpy.models.wq import * >>> parameterstep() >>> nmbwidths(3) >>> heights(1.0 / 3.0, 0.0, 2.0 / 3.0) Traceback (most recent call last): ... ValueError: The values assigned to parameter `heights` of element `?` are not sorted in increasing order (0.333333, 0.0, and 0.666667).
>>> heights heights(nan)
Subsequent identical values are allowed:
>>> heights(1.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0) >>> heights heights(0.333333, 0.666667, 0.666667)
- class hydpy.core.parametertools.FixedParameter(subvars: SubParameters)[source]¶
Bases:
ParameterBase class for defining parameters with fixed values.
Model model-users usually do not modify the values of
FixedParameterobjects. Hence, such objects prepare their “initial” values automatically whenever possible, even when optionusedefaultvaluesis disabled.- property initinfo: tuple[float | int | bool, bool]¶
A
tuplealways containing the fixed value andTrue, except for time-dependent parameters and incomplete time-information.>>> from hydpy.core.parametertools import FixedParameter >>> class Par(FixedParameter): ... NDIM, TYPE, TIME, SPAN = 0, float, True, (0., None) ... INIT = 100.0 >>> par = Par(None) >>> par.initinfo (nan, False) >>> from hydpy import pub >>> pub.options.parameterstep = "1d" >>> pub.options.simulationstep = "12h" >>> par.initinfo (50.0, True)
- restore() None[source]¶
Restore the original parameter value.
Method
restore()is relevant for testing mainly. Note that it might be necessary to call it after changing the simulation step size, as shown in the following example using the parameterHeatOfCondensationof base modelevap:>>> from hydpy.models.evap import * >>> simulationstep("1d") >>> parameterstep("1d") >>> from hydpy import round_ >>> fixed.heatofcondensation heatofcondensation(28.5) >>> round_(fixed.heatofcondensation.value) 28.5 >>> simulationstep("12h") >>> fixed.heatofcondensation heatofcondensation(14.25) >>> round_(fixed.heatofcondensation.value) 28.5 >>> fixed.heatofcondensation.restore() >>> fixed.heatofcondensation heatofcondensation(28.5) >>> round_(fixed.heatofcondensation.value) 57.0
- class hydpy.core.parametertools.SolverParameter(subvars)[source]¶
Bases:
ParameterBase class for defining parameters controlling numerical algorithms for solving model equations.
So far, the equation systems of most models implemented into HydPy are primarily coded as approximative solutions. However, there are also some models stating the original equations only. One example is the
dammodel. Its code consists of the original differential equations, which must be solved by a separate algorithm. Such algorithms, like the Runge Kutta schema implemented in classELSModel, often come with some degrees of freedom, for example, to control the striven numerical accuracy.On the one hand, the model developer should know best how to configure a numerical algorithm he selects for solving the model equations. On the other hand, there might be situations when the model user has diverging preferences. For example, he might favour higher numerical accuracies in one project and faster computation times in another one. Therefore, the
update()method of classSolverParameterrelies on an INIT value defined by the model developer as long as the user does not define an alternative value in his control files.As an example, we derive the numerical tolerance parameter Tol:
>>> from hydpy.core.parametertools import SolverParameter >>> class Tol(SolverParameter): ... NDIM = 0 ... TYPE = float ... TIME = None ... SPAN = (0.0, None) ... INIT = 0.1
Initially, the method
update()applies the value of the class constant INIT:>>> tol = Tol(None) >>> tol.update() >>> tol tol(0.1)
One can define an alternative value via “calling” the parameter as usual:
>>> tol(0.01) >>> tol tol(0.01)
Afterwards,
update()reuses the alternative value instead of the value of class constant INIT:>>> tol.update() >>> tol tol(0.01)
This alternative value is accessible and changeable via property
alternative_initvalue:>>> tol.alternative_initvalue 0.01
>>> tol.alternative_initvalue = 0.001 >>> tol.update() >>> tol tol(0.001)
One must delete the alternative value to make INIT relevant again:
>>> del tol.alternative_initvalue >>> tol.alternative_initvalue Traceback (most recent call last): ... hydpy.core.exceptiontools.AttributeNotReady: No alternative initial value for solver parameter `tol` of element `?` has been defined so far. >>> tol.update() >>> tol tol(0.1)
Very often, solver parameters depend on other model settings as the simulation step size or the catchment size, but INIT is always constant. To allow for more flexibility, model developers can override the method
modify_init(), which allows adapting the effective parameter value to the actual project settings.As a most simple example, we extend our class Tol with a
modify_init()method that doubles the original INIT value:>>> class ModTol(Tol): ... def modify_init(self): ... return 2.0 * self.INIT >>> modtol = ModTol(None) >>> modtol.update() >>> modtol modtol(0.2)
Note that
modify_init()changes the value of INIT only, not the value ofalternative_initvalue:>>> modtol.alternative_initvalue = 0.01 >>> modtol.update() >>> modtol modtol(0.01)
- update() None[source]¶
Update the actual parameter value based on INIT or, if available, on
alternative_initvalue.See the main documentation on class
SolverParameterfor more information.
- modify_init() bool | int | float[source]¶
Return the value of class constant INIT.
Override this method to support project-specific solver parameters. See the main documentation on class
SolverParameterfor more information.
- property alternative_initvalue: bool | int | float¶
A user-defined value to be used instead of the value of class constant INIT.
See the main documentation on class
SolverParameterfor more information.
- class hydpy.core.parametertools.SecondsParameter(subvars: SubParameters)[source]¶
Bases:
ParameterThe length of the actual simulation step size in seconds [s].
- update() None[source]¶
Take the number of seconds from the current simulation time step.
>>> from hydpy import pub >>> from hydpy.core.parametertools import SecondsParameter >>> secondsparameter = SecondsParameter(None) >>> with pub.options.parameterstep("1d"): ... with pub.options.simulationstep("12h"): ... secondsparameter.update() ... secondsparameter secondsparameter(43200.0)
- class hydpy.core.parametertools.HoursParameter(subvars: SubParameters)[source]¶
Bases:
ParameterThe length of the actual simulation step size in hours [h].
- update() None[source]¶
Take the number of hours from the current simulation time step.
>>> from hydpy import pub >>> from hydpy.core.parametertools import HoursParameter >>> hoursparameter = HoursParameter(None) >>> with pub.options.parameterstep("1d"): ... with pub.options.simulationstep("12h"): ... hoursparameter.update() >>> hoursparameter hoursparameter(12.0)
- class hydpy.core.parametertools.DaysParameter(subvars: SubParameters)[source]¶
Bases:
ParameterThe length of the actual simulation step size in days [d].
- update() None[source]¶
Take the number of days from the current simulation time step.
>>> from hydpy import pub >>> from hydpy.core.parametertools import DaysParameter >>> daysparameter = DaysParameter(None) >>> with pub.options.parameterstep("1d"): ... with pub.options.simulationstep("12h"): ... daysparameter.update() >>> daysparameter daysparameter(0.5)
- class hydpy.core.parametertools.IndexParameter(subvars: SubParameters)[source]¶
Bases:
ParameterBase class for parameters that do not allocate RAM for handling their data but reference an index array provided by the instance of class|Indexer| available in module
pub.- compress_repr() str | None[source]¶
Return a compressed parameter value representation that agrees with the current setting of the
ellipsisoption.IndexParameterinstances reference arrays that handle one value per s imulation time step. Therefore, the usualParameterstring representation generation mechanism would often print too much information:>>> from hydpy import pub >>> pub.timegrids = "27.02.2004", "4.03.2004", "1d" >>> from hydpy.core.parametertools import TOYParameter >>> toyparameter = TOYParameter(None) >>> toyparameter.update() >>> toyparameter toyparameter(57, 58, 59, 60, 61, 62)
Hence,
IndexParametermakes use of theellipsisoption. By default, it shows only the first and the last value:>>> with pub.options.ellipsis(-999): ... toyparameter toyparameter(57, ..., 62)
You can increase the number of shown values up to the number of available ones:
>>> with pub.options.ellipsis(2): ... toyparameter toyparameter(57, 58, ..., 61, 62)
>>> with pub.options.ellipsis(3): ... toyparameter toyparameter(57, 58, 59, 60, 61, 62)
- class hydpy.core.parametertools.TOYParameter(subvars: SubParameters)[source]¶
Bases:
IndexParameterReferences the
timeofyearindex array provided by the instance of classIndexeravailable in modulepub. [-].- update() None[source]¶
Reference the actual
timeofyeararray of theIndexerobject available in modulepub.>>> from hydpy import pub >>> pub.timegrids = "27.02.2004", "3.03.2004", "1d" >>> from hydpy.core.parametertools import TOYParameter >>> toyparameter = TOYParameter(None) >>> toyparameter.update() >>> toyparameter toyparameter(57, 58, 59, 60, 61)
- class hydpy.core.parametertools.MOYParameter(subvars: SubParameters)[source]¶
Bases:
IndexParameterReferences the
monthofyearindex array provided by the instance of classIndexeravailable in modulepub[-].- update() None[source]¶
Reference the actual
monthofyeararray of theIndexerobject available in modulepub.>>> from hydpy import pub >>> pub.timegrids = "27.02.2004", "3.03.2004", "1d" >>> from hydpy.core.parametertools import MOYParameter >>> moyparameter = MOYParameter(None) >>> moyparameter.update() >>> moyparameter moyparameter(1, 1, 1, 2, 2)
- class hydpy.core.parametertools.DOYParameter(subvars: SubParameters)[source]¶
Bases:
IndexParameterReferences the
dayofyearindex array provided by the instance of classIndexeravailable in modulepub[-].- update() None[source]¶
Reference the actual
dayofyeararray of theIndexerobject available in modulepub.>>> from hydpy import pub >>> pub.timegrids = "27.02.2004", "3.03.2004", "1d" >>> from hydpy.core.parametertools import DOYParameter >>> doyparameter = DOYParameter(None) >>> doyparameter.update() >>> doyparameter doyparameter(57, 58, 59, 60, 61)
- class hydpy.core.parametertools.SCTParameter(subvars: SubParameters)[source]¶
Bases:
IndexParameterReferences the
standardclocktimearray provided by the instance of classIndexeravailable in modulepub[h].- update() None[source]¶
Reference the actual
standardclocktimearray of theIndexerobject available in modulepub.>>> from hydpy import pub >>> pub.timegrids = "27.02.2004 21:00", "28.02.2004 03:00", "1h" >>> from hydpy.core.parametertools import SCTParameter >>> sctparameter = SCTParameter(None) >>> sctparameter.update() >>> sctparameter sctparameter(21.5, 22.5, 23.5, 0.5, 1.5, 2.5)
- class hydpy.core.parametertools.UTCLongitudeParameter(subvars: SubParameters)[source]¶
Bases:
IndexParameterReferences the current “UTC longitude” defined by option
utclongitude.- update()[source]¶
Apply the current value of option
utclongitude.>>> from hydpy import pub >>> pub.options.utclongitude 15 >>> from hydpy.core.parametertools import UTCLongitudeParameter >>> utclongitudeparameter = UTCLongitudeParameter(None) >>> utclongitudeparameter.update() >>> utclongitudeparameter utclongitudeparameter(15)
Note that changing the value of option
utclongitudemight makes re-calling methodupdate()necessary:>>> pub.options.utclongitude = 0 >>> utclongitudeparameter utclongitudeparameter(15) >>> utclongitudeparameter.update() >>> utclongitudeparameter utclongitudeparameter(0)
- hydpy.core.parametertools.do_nothing(model: modeltools.Model) None[source]¶
The default Python version of the
CallbackParametercallbackfunction, which does nothing.
- class hydpy.core.parametertools.CallbackParameter(subvars: SubParameters)[source]¶
Bases:
ParameterBase class for parameters that support calculating their values via user-defined callback functions alternatively of sticking to the same values during a simulation run.
We use the callback parameter
GateHeightof application modelsw1d_gate_outfor the following technical explanations (for a more application-oriented example, see theCalc_Discharge_V3documentation):>>> from hydpy.models.sw1d_gate_out import * >>> parameterstep()
You can define a fixed gate height as usual:
>>> gateheight gateheight(?) >>> gateheight(3.0) >>> gateheight gateheight(3.0)
Alternatively, you can write an individual callback function. Its only argument accepts the model under consideration (here,
sw1d_gate_out). Principally, you are free to modify the model in any way you like, but the expected behaviour is to set the considered parameter’s value only:>>> def adjust(model) -> None: ... con = model.parameters.control.fastaccess ... my_gateheight: float = 2.0 + 3.0 ... con.gateheight = my_gateheight
However, when working in Cython mode, HydPy converts the pure Python function to a Cython function and compiles it to C in the background, similar to how it handles “normal” model methods. This background conversion is crucial for efficiency but restricts the allowed syntax and functionality. Generally, you should work with the usual “fast access shortcuts”, be explicit about the
Nonereturn type, and cannot import any Python library, but are free to use Cython-functionalities implemented for and used by other model methods instead. A trivial example is usingfabs()for calculating absolute values.Next, we hand the callback function over to the parameter. Here, we do this a little strangely between the creation of two tuples for hiding potential information printed by Cython or the used C compiler:
>>> ();gateheight(callback=adjust);() (...)
The string representation now includes the callback’s source code:
>>> gateheight def adjust(model) -> None: con = model.parameters.control.fastaccess my_gateheight: float = 2.0 + 3.0 con.gateheight = my_gateheight gateheight(callback=adjust)
When interested in the parameter’s value, request it via the
valueproperty. Note that this property applies the callback automatically before returning the (then updated) value:>>> from hydpy import round_ >>> round_(gateheight.value) 5.0
You can return the parameter to “normal behaviour” by assigning a fixed value:
>>> gateheight(7.0) >>> gateheight gateheight(7.0)
Alternatively, one can assign a function via the
callbackproperty. We do not need to hide potential compiler output this time because the Python function has already been converted to a reusable Cython function:>>> gateheight.callback = adjust >>> gateheight def adjust(model) -> None: con = model.parameters.control.fastaccess my_gateheight: float = 2.0 + 3.0 con.gateheight = my_gateheight gateheight(callback=adjust) >>> round_(gateheight.value) 5.0
Note that HydPy stores the Cython callbacks persistently on disk, using the Python function name as a part of the Cython module name. Hence, you cannot use two equally named callback functions for the same parameter of the same application model within one project.
Use the del statement to remove the callback function:
>>> assert gateheight.callback is not None >>> del gateheight.callback >>> assert gateheight.callback is None >>> gateheight gateheight(5.0) >>> round_(gateheight.value) 5.0
Failing attempts to pass a callback function might result in the following errors:
>>> gateheight(Callback=adjust) Traceback (most recent call last): ... ValueError: When trying to prepare parameter `gateheight` of element `?` via a keyword argument, it must be `callback`, and you need to pass a callback function.
>>> gateheight(value=1.0, callback=adjust) Traceback (most recent call last): ... ValueError: Parameter `gateheight` of element `?` does not allow to combine the `callback` argument with other arguments.
The conversion from Python to Cython also works when defining the original function in an indentated block:
>>> try: ... def adjust_2(model) -> None: ... con = model.parameters.control.fastaccess ... my_gateheight: float = 2.0 * 3.0 ... con.gateheight = my_gateheight ... finally: ... ();gateheight(callback=adjust_2);() (...) >>> gateheight.callback = adjust_2 >>> gateheight def adjust_2(model) -> None: con = model.parameters.control.fastaccess my_gateheight: float = 2.0 * 3.0 con.gateheight = my_gateheight gateheight(callback=adjust_2) >>> round_(gateheight.value) 6.0
- property callback: Callable[[modeltools.Model], None] | None¶
The currently handled callback function for updating the parameter value.
- property value¶
The fixed value or the value last updated by the callback function.