diff --git a/python/ngen_conf/src/ngen/config/_version.py b/python/ngen_conf/src/ngen/config/_version.py index 13a85f77..44b18069 100644 --- a/python/ngen_conf/src/ngen/config/_version.py +++ b/python/ngen_conf/src/ngen/config/_version.py @@ -1 +1 @@ -__version__ = '0.2.5' +__version__ = '0.2.6' diff --git a/python/ngen_conf/src/ngen/config/init_config/noahowp.py b/python/ngen_conf/src/ngen/config/init_config/noahowp.py index 747529a5..bb1a8caa 100644 --- a/python/ngen_conf/src/ngen/config/init_config/noahowp.py +++ b/python/ngen_conf/src/ngen/config/init_config/noahowp.py @@ -1,34 +1,34 @@ -from enum import Enum +import warnings from datetime import datetime +from enum import Enum from pathlib import Path, PosixPath, WindowsPath -from pydantic import BaseModel, validator, root_validator +from typing import ClassVar, Dict, List, Literal, Union from ngen.init_config import core from ngen.init_config import serializer_deserializer as serde +from pydantic import BaseModel, root_validator, validator from .noahowp_options import ( - PrecipPhaseOption, - SnowAlbedoOption, - DynamicVegOption, - RunoffOption, + CanopyStomResistOption, + CropModelOption, DrainageOption, - FrozenSoilOption, + DynamicVegOption, DynamicVicOption, + EvapSrfcResistanceOption, + FrozenSoilOption, + PrecipPhaseOption, RadiativeTransferOption, + RunoffOption, SfcDragCoeffOption, - CanopyStomResistOption, - CropModelOption, + SnowAlbedoOption, SnowsoilTempTimeOption, SoilTempBoundaryOption, - SupercooledWaterOption, StomatalResistanceOption, - EvapSrfcResistanceOption, SubsurfaceOption, + SupercooledWaterOption, ) -from .validators import validate_str_len_lt from .utils import serialize_enum_value - -from typing import Dict, ClassVar, List, Literal, Union +from .validators import validate_str_len_lt MODIFIED_IGBP_MODIS_NOAH_NVEG = 20 USGS_NVEG = 27 @@ -69,9 +69,10 @@ class Config(serde.NamelistSerializerDeserializer.Config): @root_validator def _validate(cls, values: Dict[str, BaseModel]) -> Dict[str, BaseModel]: - parameters: Parameters = values["parameters"] # type: ignore - structure: Structure = values["structure"] # type: ignore + parameters: Parameters = values["parameters"] # type: ignore + structure: Structure = values["structure"] # type: ignore _set_nveg_based_on_veg_class_name(parameters, structure) + _warn_if_soil_or_veg_type_is_water_but_not_both(parameters, structure) return values @@ -270,4 +271,28 @@ class Config(core.Base.Config): } +def _warn_if_soil_or_veg_type_is_water_but_not_both( + parameters: "Parameters", structure: "Structure" +): + if parameters.soil_class_name not in ("STAS", "STAS-RUC"): + return + SOIL_TYPE_WATER = 14 + soil_type_is_water = structure.isltyp == SOIL_TYPE_WATER + + if parameters.veg_class_name == "USGS": + VEG_USGS_WATER = 16 + veg_type_is_water = structure.vegtyp == VEG_USGS_WATER + elif parameters.veg_class_name == "MODIFIED_IGBP_MODIS_NOAH": + VEG_MODIS_WATER = 17 + veg_type_is_water = structure.vegtyp == VEG_MODIS_WATER + else: + return + + # NOTE: ensure arguments to XOR (^) are bools + if (veg_type_is_water) ^ (soil_type_is_water): + warnings.warn( + f"'isltyp' is {'' if soil_type_is_water else 'not'} water but 'vegtyp' is {'' if veg_type_is_water else 'not'} water" + ) + + NoahOWP.update_forward_refs() diff --git a/python/ngen_conf/tests/test_init_config_models.py b/python/ngen_conf/tests/test_init_config_models.py index 4e4a6541..34f73ed5 100644 --- a/python/ngen_conf/tests/test_init_config_models.py +++ b/python/ngen_conf/tests/test_init_config_models.py @@ -1,7 +1,11 @@ +import warnings + +import pytest from ngen.init_config import utils + from ngen.config.init_config.cfe import CFE -from ngen.config.init_config.pet import PET from ngen.config.init_config.noahowp import NoahOWP +from ngen.config.init_config.pet import PET def test_cfe(cfe_init_config: str): @@ -21,3 +25,77 @@ def test_pet(pet_init_config: str): def test_noah_owp(noah_owp_init_config: str): o = NoahOWP.from_namelist_str(noah_owp_init_config) assert o.to_namelist_str() == noah_owp_init_config + + +SOIL_TYPE_WATER = 14 +VEG_USGS_WATER = 16 +VEG_MODIS_WATER = 17 + +does_warn_cases = ( + ("USGS", VEG_USGS_WATER, SOIL_TYPE_WATER + 1), + ("USGS", VEG_USGS_WATER + 1, SOIL_TYPE_WATER), + ("MODIFIED_IGBP_MODIS_NOAH", VEG_MODIS_WATER, SOIL_TYPE_WATER + 1), + ("MODIFIED_IGBP_MODIS_NOAH", VEG_MODIS_WATER + 1, SOIL_TYPE_WATER), +) + + +@pytest.mark.parametrize("veg_class,veg_type,soil_type", does_warn_cases) +def test_noah_owp_does_warns_if_soil_or_veg_type_are_water_but_not_both( + noah_owp_init_config: str, + veg_class: str, + veg_type: int, + soil_type: int, +): + o = NoahOWP.from_namelist_str(noah_owp_init_config) + o.parameters.veg_class_name = veg_class + o.structure.vegtyp = veg_type + o.structure.isltyp = soil_type + + # ensure warning _is_ emitted + with pytest.warns(): + NoahOWP.from_namelist_str(o.to_namelist_str()) + + +does_not_warn_cases = ( + # positive cases + ("USGS", VEG_USGS_WATER, SOIL_TYPE_WATER), + ("MODIFIED_IGBP_MODIS_NOAH", VEG_MODIS_WATER, SOIL_TYPE_WATER), + # negative cases + ("USGS", VEG_USGS_WATER + 1, SOIL_TYPE_WATER + 1), + ("MODIFIED_IGBP_MODIS_NOAH", VEG_MODIS_WATER + 1, SOIL_TYPE_WATER + 1), +) + + +@pytest.mark.parametrize("veg_class,veg_type,soil_type", does_not_warn_cases) +def test_noah_owp_does_not_warns_if_soil_and_veg_type_are_water_or_neither_water( + noah_owp_init_config: str, + veg_class: str, + veg_type: int, + soil_type: int, +): + o = NoahOWP.from_namelist_str(noah_owp_init_config) + o.parameters.veg_class_name = veg_class + o.structure.vegtyp = veg_type + o.structure.isltyp = soil_type + + # ensure warning is not emitted + with warnings.catch_warnings(): + warnings.simplefilter("error") + NoahOWP.from_namelist_str(o.to_namelist_str()) + + # WATER = 14 + # o.parameters.veg_class_name = "USGS" + # o.structure.isltyp = WATER + # VEG_USGS_WATER = 16 + # o.structure.vegtyp = VEG_USGS_WATER + 1 + + # # ensure warning is not emitted + # with pytest.warns(): + # NoahOWP.from_namelist_str(o.to_namelist_str()) + + # o.parameters.veg_class_name = "MODIFIED_IGBP_MODIS_NOAH" + # o.structure.isltyp = WATER + # VEG_MODIS_WATER = 17 + # o.structure.vegtyp = VEG_MODIS_WATER + + # assert o.to_namelist_str() == noah_owp_init_config