Skip to content

Commit

Permalink
Small fixes to support HBVECMOD emulator out of the box (#338)
Browse files Browse the repository at this point in the history
* add Interpolation command options. Let VegetationClass records contain symbolic expressions

* Support for custom RV subclasses included in Config

* Use HRU_ID instead of SubId for reservoirs

* Update History

* bug fix for _rv occurring since Config inherited __rv__ attribute.

* Prepare release

---------

Co-authored-by: Trevor James Smith <[email protected]>
  • Loading branch information
huard and Zeitsperre authored Mar 13, 2024
1 parent 5f695fa commit e7744e3
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 10 deletions.
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

0 comments on commit e7744e3

Please sign in to comment.