from typing import Any
from hydrobricks._exceptions import ConfigurationError
from hydrobricks._hydrobricks import SettingsModel
[docs]
class ModelSettings:
"""Base class for the model settings"""
def __init__(
self, solver: str = "heun_explicit", record_all: bool = False, **kwargs: Any
) -> None:
"""
Parameters
----------
solver
Solver to use
record_all
Record all state and flux values
kwargs
Keyword arguments
"""
self.settings: SettingsModel = SettingsModel()
self.settings.log_all(record_all)
self.settings.set_solver(solver)
[docs]
def set_timer(
self,
start_date: str,
end_date: str,
time_step: int = 1,
time_step_unit: str = "day",
) -> None:
"""
Set the timer
Parameters
----------
start_date
Start date of the simulation
end_date
End date of the simulation
time_step
Time step
time_step_unit
Time step unit
"""
self.settings.set_timer(start_date, end_date, int(time_step), time_step_unit)
[docs]
def set_parameter_value(self, component: str, name: str, value: float) -> bool:
"""
Set a parameter value
Parameters
----------
component
Name of the component
name
Name of the parameter
value
Value of the parameter
Returns
-------
True if the parameter was set successfully, False otherwise.
"""
return self.settings.set_parameter_value(component, name, float(value))
[docs]
def generate_base_structure(
self,
land_cover_names: list[str],
land_cover_types: list[str],
with_snow: bool = True,
snow_melt_process: str = "melt:degree_day",
snow_rain_process: str | None = None,
snow_ice_transformation: str | None = None,
snow_redistribution: str | None = None,
snow_water_retention_process: str | None = None,
snow_refreezing_process: str | None = None,
rain_to_snowpack: bool = False,
) -> None:
"""
Generate basic elements
Parameters
----------
land_cover_names
List of land cover names
land_cover_types
List of land cover types
with_snow
Account for snow
snow_melt_process
Snow melt process
snow_rain_process
Rain/snow partitioning method (overrides the
default derived from snow_melt_process)
snow_ice_transformation
Snow and ice transformation method (optional)
snow_redistribution
Snow redistribution method (optional)
snow_water_retention_process
Outflow process of the snowpack liquid water storage (optional). When
provided, the snowpacks are generated with liquid water retention: the
melt water is kept in the snowpack water container and released by the
given process (e.g. 'outflow:snow_holding').
snow_refreezing_process
Refreezing process of the retained liquid water (optional; requires
snow_water_retention_process). E.g. 'refreeze:degree_day'.
rain_to_snowpack
Route the rain to the snowpack liquid water storage instead of the
land cover (requires snow_water_retention_process).
The rain is retained in the snowpack (up to the holding capacity)
and exposed to refreezing; without snow, it reaches the land cover
within the same time step.
"""
if len(land_cover_names) != len(land_cover_types):
raise ConfigurationError(
"The length of the land cover names and types do not match.",
reason="Mismatched array sizes",
)
# Precipitation
if snow_rain_process is not None:
splitter_type = snow_rain_process
elif snow_melt_process == "melt:cemaneige":
splitter_type = "snow_rain:cemaneige"
else:
splitter_type = "snow_rain:linear"
self.settings.generate_precipitation_splitters(with_snow, splitter_type)
# Add the land covers, each by its own name: generic-behaviour covers (incl.
# forest, which is generic plus a canopy) map to the generic_land_cover brick,
# while special covers (e.g. glacier) keep their type. Several generic covers
# can coexist (e.g. open and forest), each getting its own snowpack and
# soil routine.
for cover_type, cover_name in zip(land_cover_types, land_cover_names):
if cover_type in ["ground", "generic_land_cover", "open", "forest", "lake"]:
self.settings.add_land_cover_brick(cover_name, "generic_land_cover")
else:
self.settings.add_land_cover_brick(cover_name, cover_type)
# Forest canopy interception, on the rain path upstream of the snowpack.
# Generated before the snowpacks so the canopy (a surface component) is
# declared/computed before the snowpack it feeds; the throughfall rejoins the
# original rain target (the snowpack when the rain is routed to it,
# otherwise the land cover).
rain_to_snowpack_active = with_snow and rain_to_snowpack
for cover_type, cover_name in zip(land_cover_types, land_cover_names):
if cover_type == "forest":
if rain_to_snowpack_active:
throughfall_target = f"{cover_name}_snowpack"
else:
throughfall_target = cover_name
self.settings.generate_canopy_interception(
cover_name, throughfall_target
)
# Snowpack
if with_snow:
if snow_refreezing_process and not snow_water_retention_process:
raise ConfigurationError(
"Snow refreezing requires a snow water retention process.",
item_name="snow_refreezing_process",
item_value=snow_refreezing_process,
reason="Missing snow water retention process",
)
if rain_to_snowpack and not snow_water_retention_process:
raise ConfigurationError(
"Routing the rain to the snowpacks requires a snow water "
"retention process.",
item_name="rain_to_snowpack",
item_value=rain_to_snowpack,
reason="Missing snow water retention process",
)
if snow_water_retention_process:
self.settings.generate_snowpacks_with_water_retention(
snow_melt_process,
snow_water_retention_process,
rain_to_snowpack,
)
if snow_refreezing_process:
self.settings.add_snowpack_refreezing(snow_refreezing_process)
else:
self.settings.generate_snowpacks(snow_melt_process)
if snow_ice_transformation:
self.settings.add_snow_ice_transformation(snow_ice_transformation)
if snow_redistribution:
self.settings.add_snow_redistribution(snow_redistribution)
[docs]
def add_land_cover_brick(self, name: str, kind: str) -> None:
"""
Add a land cover brick
Parameters
----------
name
Name of the land cover brick
kind
Type of the land cover brick
"""
self.settings.add_land_cover_brick(name, kind)
[docs]
def add_hydro_unit_brick(self, name: str, kind: str) -> None:
"""
Add a hydro unit brick
Parameters
----------
name
Name of the hydro unit brick
kind
Type of the hydro unit brick
"""
self.settings.add_hydro_unit_brick(name, kind)
[docs]
def add_sub_basin_brick(self, name: str, kind: str) -> None:
"""
Add a sub basin brick
Parameters
----------
name
Name of the sub basin brick
kind
Type of the sub basin brick
"""
self.settings.add_sub_basin_brick(name, kind)
[docs]
def select_hydro_unit_brick(self, name: str) -> None:
"""
Select a hydro unit brick
Parameters
----------
name
Name of the hydro unit brick
"""
self.settings.select_hydro_unit_brick(name)
[docs]
def add_brick_process(
self,
name: str,
kind: str,
target: str = "",
log: bool = False,
instantaneous: bool = False,
) -> None:
"""
Add a brick process
Parameters
----------
name
Name of the brick process
kind
Type of the brick process
target
Target of the process output
log
Log the brick process
instantaneous
Process outputs are instantaneous
"""
self.settings.add_brick_process(name, kind, target, log)
# Define output as static
if kind in ["outflow:direct", "outflow:rest"]:
self.settings.set_process_outputs_as_static()
# Define output as instantaneous
if instantaneous:
self.settings.set_process_outputs_as_instantaneous()
[docs]
def add_process_output(self, target: str) -> None:
"""
Add an extra output target to the most recently added process.
Parameters
----------
target
Target brick of the additional output.
"""
self.settings.add_process_output(target)
[docs]
def add_brick_parameter(
self, name: str, value: int | float | bool, kind: str = "constant"
) -> None:
"""
Add a brick parameter
Parameters
----------
name
Name of the brick parameter
value
Value of the brick parameter
kind
Type of the brick parameter (for now has to be 'constant')
"""
self.settings.add_brick_parameter(name, float(value), kind)
[docs]
def set_current_brick_computed_directly(self) -> None:
"""
Mark the selected brick as computed directly (explicitly, without the ODE
solver). Used for fully explicit formulations such as the GR4J production
store and routing, where processes apply an exact discrete update each step.
"""
self.settings.set_current_brick_computed_directly()
[docs]
def add_process_parameter(
self, name: str, value: int | float | bool, kind: str = "constant"
) -> None:
"""
Add a process parameter
Parameters
----------
name
Name of the process parameter
value
Value of the process parameter
kind
Type of the process parameter (for now has to be 'constant')
"""
self.settings.add_process_parameter(name, float(value), kind)
[docs]
def add_logging_to(self, item: str) -> None:
"""
Add logging to an item
Parameters
----------
item
Name of the item
"""
self.settings.add_logging_to(item)
[docs]
def add_structure(self) -> int:
"""
Add a new (empty) model-structure variant and select it.
Subsequent structure-building calls populate the newly selected structure.
Units are auto-assigned (in the core) to the variant matching their land
covers.
Returns
-------
The id of the newly created structure.
"""
return self.settings.add_structure()
[docs]
def set_process_outputs_as_instantaneous(self) -> None:
"""Set all process outputs as instantaneous"""
self.settings.set_process_outputs_as_instantaneous()
[docs]
def set_process_outputs_as_static(self) -> None:
"""Set all process outputs as static"""
self.settings.set_process_outputs_as_static()