Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small fixes to support HBVECMOD emulator out of the box #338

Merged
merged 9 commits into from
Mar 13, 2024
6 changes: 5 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
History
=======

0.14.0 (2024--soon)
0.14.0 (2024-03-13)
-------------------
* Add support for new processes and methods added in Raven v3.8.
* Add Interpolation command options.
* Let VegetationClass records contain symbolic expressions.
* Add support for custom RV subclasses;
* Use HRU_ID (if available) instead of SubId in BasinMaker reservoirs extraction logic;
* Added support for Python 3.12 and dropped support for Python3.8.
* Added support for `raven-hydro` v0.3.0 and `RavenHydroFramework` to v3.8.
* `ravenpy` now requires `xclim` >= v0.48.2, `xarray` >= v2023.11.0, and `pandas` >= 2.2.0.
Expand Down
2 changes: 2 additions & 0 deletions ravenpy/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ def set_default_units(self):
class RV(Command):
"""Base class for RV configuration objects."""

__rv__ = ""

@property
def _template(self):
return "{_commands}\n"
Expand Down
6 changes: 3 additions & 3 deletions ravenpy/config/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,9 +992,9 @@ def __str__(self):

class VegetationClass(Record):
name: str = ""
max_ht: float = 0.0
max_lai: float = 0.0
max_leaf_cond: float = 0.0
max_ht: Sym = 0.0
max_lai: Sym = 0.0
max_leaf_cond: Sym = 0.0

def __str__(self):
template = "{name:<16},{max_ht:>18},{max_lai:>18},{max_leaf_cond:>18}"
Expand Down
2 changes: 1 addition & 1 deletion ravenpy/config/emulators/hbvec.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

@dataclass(config=SymConfig)
class P(Params):
X01: Sym = Variable("X01") #
X01: Sym = Variable("X01")
X02: Sym = Variable("X02")
X03: Sym = Variable("X03")
X04: Sym = Variable("X04")
Expand Down
7 changes: 7 additions & 0 deletions ravenpy/config/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,13 @@ class LWIncomingMethod(Enum):
DINGMAN = "LW_INC_DINGMAN"


class Interpolation(Enum):
FROM_FILE = "INTERP_FROM_FILE"
AVERAGE_ALL = "INTERP_AVERAGE_ALL"
NEAREST_NEIGHBOR = "INTERP_NEAREST_NEIGHBOR"
INVERSE_DISTANCE = "INTERP_INVERSE_DISTANCE"


class LWRadiationMethod(Enum):
DATA = "LW_RAD_DATA"
DEFAULT = "LW_RAD_DEFAULT"
Expand Down
21 changes: 18 additions & 3 deletions ravenpy/config/rvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@


class RVI(RV):
__rv__ = "RVI"

# Run parameters
silent_mode: Optional[bool] = optfield(alias="SilentMode")
noisy_mode: Optional[bool] = optfield(alias="NoisyMode")
Expand Down Expand Up @@ -141,6 +143,8 @@ def dates2cf(cls, v, info):


class RVT(RV):
__rv__ = "RVT"

gauge: Optional[Sequence[rc.Gauge]] = optfield(alias="Gauge")
station_forcing: Optional[Sequence[rc.StationForcing]] = optfield(
alias="StationForcing"
Expand All @@ -154,6 +158,8 @@ class RVT(RV):


class RVP(RV):
__rv__ = "RVP"

params: Any = None
soil_classes: Optional[rc.SoilClasses] = optfield(alias="SoilClasses")
soil_profiles: Optional[rc.SoilProfiles] = optfield(alias="SoilProfiles")
Expand Down Expand Up @@ -190,6 +196,8 @@ class RVP(RV):


class RVC(RV):
__rv__ = "RVC"

hru_state_variable_table: Optional[rc.HRUStateVariableTable] = optfield(
alias="HRUStateVariableTable"
)
Expand All @@ -202,6 +210,8 @@ class RVC(RV):


class RVH(RV):
__rv__ = "RVH"

sub_basins: Optional[rc.SubBasins] = optfield(alias="SubBasins")
sub_basin_group: Optional[Sequence[rc.SubBasinGroup]] = optfield(
alias="SubBasinGroup"
Expand All @@ -218,6 +228,8 @@ class RVH(RV):


class RVE(RV):
__rv__ = "RVE"

enkf_mode: Optional[o.EnKFMode] = optfield(alias="EnKFMode")
window_size: Optional[int] = optfield(alias="WindowSize")
solution_run_name: Optional[str] = optfield(alias="SolutionRunName")
Expand All @@ -242,6 +254,8 @@ class RVE(RV):


class Config(RVI, RVC, RVH, RVT, RVP, RVE):
__rv__ = None

def header(self, rv):
"""Return the header to print at the top of each RV file."""
import datetime as dt
Expand Down Expand Up @@ -358,16 +372,17 @@ def duplicate(self, **kwds):

def _rv(self, rv: str):
"""Return RV configuration."""
import inspect

# if self.is_symbolic:
# raise ValueError(
# "Cannot write RV files if `params` has symbolic variables. Use `set_params` method to set numerical "
# "values for `params`."
# )

# Get RV class
rvs = {b.__name__: b for b in Config.__bases__}
cls = rvs[rv.upper()]
for cls in inspect.getmro(self.__class__):
if getattr(cls, "__rv__") == rv.upper():
break

# Instantiate RV class
attrs = dict(self)
Expand Down
2 changes: 1 addition & 1 deletion ravenpy/extractors/routing_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _extract_reservoir(row) -> dict:

return dict(
subbasin_id=int(row["SubId"]),
hru_id=int(row["SubId"]),
hru_id=int(row.get("HRU_ID", row["SubId"])),
name=f"Lake_{lake_id}",
weir_coefficient=BasinMakerExtractor.WEIR_COEFFICIENT,
crest_width=row["BkfWidth"],
Expand Down
24 changes: 23 additions & 1 deletion tests/test_rvs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime as dt
from typing import Union
from typing import Sequence, Union

import cftime
import pytest
Expand All @@ -8,6 +8,7 @@
from pymbolic.primitives import Variable

from ravenpy.config import commands as rc
from ravenpy.config import options
from ravenpy.config.rvs import RV, RVI, Config, optfield


Expand Down Expand Up @@ -122,6 +123,27 @@ def test_config(dummy_config):
# assert nt.air_snow_coeff == 0.5


def test_custom_subclass(dummy_config, tmp_path):
"""Test that users can subclass RV and Config."""
cls, P = dummy_config

# Custom RVI
class myRVI(RVI):
run_name: str = Field("myRunName", alias="RunName")

# Custom config with custom RVI
class MyConfig(myRVI, cls):
params: P = P()
enkf_mode: options.EnKFMode = optfield(alias="EnKFMode")

# Make sure rv files can be written
conf = MyConfig(EnKFMode="ENKF_SPINUP").set_params([0.5])
conf.write_rv(workdir=tmp_path)
assert conf.run_name == "myRunName"
assert "myRunName" in conf._rv("RVI")
assert "EnKFMode" not in conf._rv("RVI")


def test_hru_filter():
"""Test that unrecognized HRU types are filtered out."""
from ravenpy.config.emulators.gr4jcn import LakeHRU
Expand Down
Loading