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

Simplify config module #790

Merged
merged 16 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/troute-config/troute/config/bmi_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Optional, List


class BMIParameters(BaseModel, extra='forbid'):
class BMIParameters(BaseModel):
flowpath_columns: Optional[List[str]] = Field(
default_factory=lambda: [
'id',
Expand Down
26 changes: 13 additions & 13 deletions src/troute-config/troute/config/compute_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
ComputeKernel = Literal["V02-structured", "diffusive", "diffusice_cnt"]


class ComputeParameters(BaseModel, extra='forbid'):
class ComputeParameters(BaseModel):
parallel_compute_method: ParallelComputeMethod = "by-network"
compute_kernel: ComputeKernel = "V02-structured"
assume_short_ts: bool = False
Expand All @@ -44,7 +44,7 @@ class ComputeParameters(BaseModel, extra='forbid'):

# TODO: determine how to handle context specific required fields
# TODO: consider other ways to handle wrf hydro fields (i.e. subclass)
class RestartParameters(BaseModel, extra='forbid'):
class RestartParameters(BaseModel):
# NOTE: this is technically optional as it can be derived from the
# `wrf_hydro_channel_restart_file` if the `start_datetime` is not provided.
start_datetime: Optional[datetime] = None
Expand Down Expand Up @@ -77,9 +77,9 @@ class RestartParameters(BaseModel, extra='forbid'):


# TODO: determine how to handle context specific required fields
class HybridParameters(BaseModel, extra='forbid'):
class HybridParameters(BaseModel):
# NOTE: required for hybrid simulations
run_hybrid_routing: bool
run_hybrid_routing: bool = False
# NOTE: required for hybrid simulations
diffusive_domain: Optional[FilePath] = None

Expand All @@ -97,15 +97,15 @@ class HybridParameters(BaseModel, extra='forbid'):
coastal_boundary_domain: Optional[FilePath] = None


class QLateralForcingSet(BaseModel, extra='forbid'):
class QLateralForcingSet(BaseModel):
nts: "QLateralFiles"


class QLateralFiles(BaseModel, extra='forbid'):
class QLateralFiles(BaseModel):
qlat_files: List[FilePath]


class StreamflowDA(BaseModel, extra='forbid'):
class StreamflowDA(BaseModel):
# NOTE: mandatory for streamflow DA, defaults to False
streamflow_nudging: bool = False
# NOTE: mandatory for streamflow DA on NHDNetwork.
Expand Down Expand Up @@ -140,15 +140,15 @@ class ReservoirPersistenceDA(BaseModel, extra='ignore'):
crosswalk_usace_lakeID_field: str = "usace_lake_id"


class ReservoirRfcParameters(BaseModel, extra='forbid'):
class ReservoirRfcParameters(BaseModel):
reservoir_rfc_forecasts: bool = False
reservoir_rfc_forecasts_time_series_path: Optional[FilePath] = None
reservoir_rfc_forecasts_time_series_path: Optional[DirectoryPath] = None
reservoir_rfc_forecasts_lookback_hours: int = 28
reservoir_rfc_forecasts_offset_hours: int = 28
reservoir_rfc_forecast_persist_days: int = 11


class ReservoirDA(BaseModel, extra='forbid'):
class ReservoirDA(BaseModel):
reservoir_persistence_da: Optional[ReservoirPersistenceDA] = None
reservoir_rfc_da: Optional[ReservoirRfcParameters] = None
reservoir_parameter_file: Optional[FilePath] = None
Expand All @@ -162,15 +162,15 @@ class DataAssimilationParameters(BaseModel, extra='ignore'):
# NOTE: required for canada reservoir DA
canada_timeslices_folder: Optional[DirectoryPath] = None
# NOTE: required for LakeOntario reservoir DA
LakeOntario_outflow: Optional[DirectoryPath] = None
LakeOntario_outflow: Optional[FilePath] = None
# NOTE: required for reservoir DA - suggested value 24 (1 days)
timeslice_lookback_hours: int = 24

interpolation_limit_min: int = 59

wrf_hydro_lastobs_lead_time_relative_to_simulation_start_time: int = 0
wrf_lastobs_type: str = "obs-based"
streamflow_da: StreamflowDA
streamflow_da: StreamflowDA = None
# NOTE: this appears to be optional. See nwm_routing/input.py ~:439
reservoir_da: Optional[ReservoirDA] = None

Expand All @@ -183,7 +183,7 @@ class DataAssimilationParameters(BaseModel, extra='ignore'):
)(coerce_none_to_default)


class ForcingParameters(BaseModel, extra='forbid'):
class ForcingParameters(BaseModel):
qts_subdivisions: int = 12
dt: int = 300
# TODO: see note about potentially throwing in v3_doc.yaml
Expand Down
139 changes: 76 additions & 63 deletions src/troute-config/troute/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
from .bmi_parameters import BMIParameters
from ._utils import use_strict

class LoggingParameters(BaseModel):
showtiming: Optional[bool] = None
log_level: Optional[str] = None
log_directory: Optional[str] = None

class Config(BaseModel, extra='forbid'):
class Config(BaseModel):
log_parameters: LoggingParameters = Field(default_factory=LoggingParameters)
# TODO: not sure if default is None or {}. see nhd_io.read_config_file ~:100
network_topology_parameters: Optional[NetworkTopologyParameters] = None
Expand Down Expand Up @@ -105,83 +109,92 @@ def check_coastal_domain(cls, values):

@root_validator(skip_on_failure=True)
def check_gage_segID_crosswalk_file(cls, values):
streamflow_DA = values['compute_parameters'].data_assimilation_parameters.streamflow_da
streamflow_nudging = streamflow_DA.streamflow_nudging
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
if streamflow_nudging and network_type=='NHDNetwork':
assert streamflow_DA.gage_segID_crosswalk_file, 'Streamflow nuding is enabled on NHDNetwork, but gage_segID_crosswalk_file is missing.'
da_parameters = values['compute_parameters'].data_assimilation_parameters
if da_parameters:
streamflow_DA = da_parameters.streamflow_da
if streamflow_DA:
streamflow_nudging = streamflow_DA.streamflow_nudging
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
if streamflow_nudging and network_type=='NHDNetwork':
assert streamflow_DA.gage_segID_crosswalk_file, 'Streamflow nuding is enabled on NHDNetwork, but gage_segID_crosswalk_file is missing.'

return values

@root_validator(skip_on_failure=True)
def check_rfc_parameters(cls, values):
reservoir_da = values['compute_parameters'].data_assimilation_parameters.reservoir_da
if reservoir_da:
reservoir_rfc_da = reservoir_da.reservoir_rfc_da
reservoir_rfc_forecasts = False
if reservoir_rfc_da:
reservoir_rfc_forecasts = reservoir_rfc_da.reservoir_rfc_forecasts
reservoir_rfc_forecasts_time_series_path = reservoir_rfc_da.reservoir_rfc_forecasts_time_series_path
if reservoir_rfc_forecasts:
error_message = ''
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
reservoir_parameter_file = reservoir_da.reservoir_parameter_file
if not reservoir_parameter_file and network_type=='NHDNetwork':
error_message += ' Reservoir_parameter_file is missing (and network type is NHDNetwork).'
if not reservoir_rfc_forecasts_time_series_path:
error_message += ' RFC timeseries path is missing.'
else:
if not os.path.exists(reservoir_rfc_forecasts_time_series_path):
error_message += ' reservoir_rfc_forecasts_time_series_path provided does not exist. '
assert not error_message, 'RFC forecast is enabled, but:' + error_message
da_parameters = values['compute_parameters'].data_assimilation_parameters
if da_parameters:
reservoir_da = da_parameters.reservoir_da
if reservoir_da:
reservoir_rfc_da = reservoir_da.reservoir_rfc_da
reservoir_rfc_forecasts = False
if reservoir_rfc_da:
reservoir_rfc_forecasts = reservoir_rfc_da.reservoir_rfc_forecasts
reservoir_rfc_forecasts_time_series_path = reservoir_rfc_da.reservoir_rfc_forecasts_time_series_path
if reservoir_rfc_forecasts:
error_message = ''
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
reservoir_parameter_file = reservoir_da.reservoir_parameter_file
if not reservoir_parameter_file and network_type=='NHDNetwork':
error_message += ' Reservoir_parameter_file is missing (and network type is NHDNetwork).'
if not reservoir_rfc_forecasts_time_series_path:
error_message += ' RFC timeseries path is missing.'
else:
if not os.path.exists(reservoir_rfc_forecasts_time_series_path):
error_message += ' reservoir_rfc_forecasts_time_series_path provided does not exist. '
assert not error_message, 'RFC forecast is enabled, but:' + error_message

return values

@root_validator(skip_on_failure=True)
def check_usgs_reservoir_da_parameters(cls, values):
reservoir_da = values['compute_parameters'].data_assimilation_parameters.reservoir_da
if reservoir_da:
reservoir_persistence_da = reservoir_da.reservoir_persistence_da
reservoir_persistence_usgs = False
if reservoir_persistence_da:
reservoir_persistence_usgs = reservoir_persistence_da.reservoir_persistence_usgs
usgs_timeslices_folder = values['compute_parameters'].data_assimilation_parameters.usgs_timeslices_folder
if reservoir_persistence_usgs:
error_message = ''
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
reservoir_parameter_file = reservoir_da.reservoir_parameter_file
if not reservoir_parameter_file and network_type=='NHDNetwork':
error_message += ' Reservoir_parameter_file is missing (and network type is NHDNetwork).'
if not usgs_timeslices_folder:
error_message += ' USGS_timeslices_folder is missing.'
else:
if not os.path.exists(usgs_timeslices_folder):
error_message += ' USGS_timeslices_folder path provided does not exist. '
assert not error_message, 'USGS reservoir DA is enabled, but:' + error_message
da_parameters = values['compute_parameters'].data_assimilation_parameters
if da_parameters:
reservoir_da = da_parameters.reservoir_da
if reservoir_da:
reservoir_persistence_da = reservoir_da.reservoir_persistence_da
reservoir_persistence_usgs = False
if reservoir_persistence_da:
reservoir_persistence_usgs = reservoir_persistence_da.reservoir_persistence_usgs
usgs_timeslices_folder = values['compute_parameters'].data_assimilation_parameters.usgs_timeslices_folder
if reservoir_persistence_usgs:
error_message = ''
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
reservoir_parameter_file = reservoir_da.reservoir_parameter_file
if not reservoir_parameter_file and network_type=='NHDNetwork':
error_message += ' Reservoir_parameter_file is missing (and network type is NHDNetwork).'
if not usgs_timeslices_folder:
error_message += ' USGS_timeslices_folder is missing.'
else:
if not os.path.exists(usgs_timeslices_folder):
error_message += ' USGS_timeslices_folder path provided does not exist. '
assert not error_message, 'USGS reservoir DA is enabled, but:' + error_message

return values

@root_validator(skip_on_failure=True)
def check_usace_reservoir_da_parameters(cls, values):
reservoir_da = values['compute_parameters'].data_assimilation_parameters.reservoir_da
if reservoir_da:
reservoir_persistence_da = reservoir_da.reservoir_persistence_da
reservoir_persistence_usace = False
if reservoir_persistence_da:
reservoir_persistence_usace = reservoir_persistence_da.reservoir_persistence_usace
usace_timeslices_folder = values['compute_parameters'].data_assimilation_parameters.usace_timeslices_folder
if reservoir_persistence_usace:
error_message = ''
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
reservoir_parameter_file = reservoir_da.reservoir_parameter_file
if not reservoir_parameter_file and network_type=='NHDNetwork':
error_message += ' Reservoir_parameter_file is missing (and network type is NHDNetwork).'
if not usace_timeslices_folder:
error_message += ' USACE_timeslices_folder is missing.'
else:
if not os.path.exists(usace_timeslices_folder):
error_message += ' USACE_timeslices_folder path provided does not exist. '
assert not error_message, 'USACE reservoir DA is enabled, but:' + error_message
da_parameters = values['compute_parameters'].data_assimilation_parameters
if da_parameters:
reservoir_da = da_parameters.reservoir_da
if reservoir_da:
reservoir_persistence_da = reservoir_da.reservoir_persistence_da
reservoir_persistence_usace = False
if reservoir_persistence_da:
reservoir_persistence_usace = reservoir_persistence_da.reservoir_persistence_usace
usace_timeslices_folder = values['compute_parameters'].data_assimilation_parameters.usace_timeslices_folder
if reservoir_persistence_usace:
error_message = ''
network_type = values['network_topology_parameters'].supernetwork_parameters.network_type
reservoir_parameter_file = reservoir_da.reservoir_parameter_file
if not reservoir_parameter_file and network_type=='NHDNetwork':
error_message += ' Reservoir_parameter_file is missing (and network type is NHDNetwork).'
if not usace_timeslices_folder:
error_message += ' USACE_timeslices_folder is missing.'
else:
if not os.path.exists(usace_timeslices_folder):
error_message += ' USACE_timeslices_folder path provided does not exist. '
assert not error_message, 'USACE reservoir DA is enabled, but:' + error_message

return values

Expand Down
2 changes: 1 addition & 1 deletion src/troute-config/troute/config/logging_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
LogLevel = Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"]


class LoggingParameters(BaseModel, extra='forbid'):
class LoggingParameters(BaseModel):
log_level: LogLevel = "DEBUG"
"""
Python logging level. Can either be a string or an integer from the list below optional,
Expand Down
60 changes: 8 additions & 52 deletions src/troute-config/troute/config/network_topology_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .types import FilePath, DirectoryPath


class NetworkTopologyParameters(BaseModel, extra='forbid'):
class NetworkTopologyParameters(BaseModel):
# TODO: default {}. see nhd_io.read_config_file ~:100
preprocessing_parameters: "PreprocessingParameters" = Field(default_factory=dict)
# TODO: not sure if default {}. see nhd_io.read_config_file ~:100
Expand All @@ -19,7 +19,7 @@ class NetworkTopologyParameters(BaseModel, extra='forbid'):

# TODO: This is an old parameter but probably worth keeping moving forward. However, it is
# not implemented in V4 at the moment (Aug 11, 2023). Need to add this functionality to t-route.
class PreprocessingParameters(BaseModel, extra='forbid'):
class PreprocessingParameters(BaseModel):
preprocess_only: bool = False
# NOTE: required if preprocess_only = True
# TODO: determine if str type
Expand All @@ -31,10 +31,10 @@ class PreprocessingParameters(BaseModel, extra='forbid'):
preprocess_source_file: Optional[FilePath] = None


class SupernetworkParameters(BaseModel, extra='forbid'):
class SupernetworkParameters(BaseModel):
title_string: Optional[str] = None
# TODO: hopefully places in the code can be changed so this is a `Path` instead of a `str`
geo_file_path: str
geo_file_path: FilePath
network_type: Literal["HYFeaturesNetwork", "NHDNetwork"] = "HYFeaturesNetwork"
flowpath_edge_list: Optional[str] = None
mask_file_path: Optional[FilePath] = None
Expand All @@ -55,51 +55,7 @@ class SupernetworkParameters(BaseModel, extra='forbid'):
]
)
synthetic_wb_id_offset: float = 9.99e11
duplicate_wb_segments: Optional[List[int]] = Field(
default_factory=lambda: [
717696,
1311881,
3133581,
1010832,
1023120,
1813525,
1531545,
1304859,
1320604,
1233435,
11816,
1312051,
2723765,
2613174,
846266,
1304891,
1233595,
1996602,
2822462,
2384576,
1021504,
2360642,
1326659,
1826754,
572364,
1336910,
1332558,
1023054,
3133527,
3053788,
3101661,
2043487,
3056866,
1296744,
1233515,
2045165,
1230577,
1010164,
1031669,
1291638,
1637751,
]
)

terminal_code: int = 0
# TODO: It would be nice if this were a literal / str
driver_string: Union[str, Literal["NetCDF"]] = "NetCDF"
Expand Down Expand Up @@ -150,7 +106,7 @@ def get_columns(cls, columns: dict, values: Dict[str, Any]) -> dict:
return default_columns


class Columns(BaseModel, extra='forbid'):
class Columns(BaseModel):
# string, unique segment identifier
key: str
# string, unique identifier of downstream segment
Expand Down Expand Up @@ -185,14 +141,14 @@ class Columns(BaseModel, extra='forbid'):
mainstem: Optional[str]


class WaterbodyParameters(BaseModel, extra='forbid'):
class WaterbodyParameters(BaseModel):
# NOTE: required, True for simulations with waterbodies.
break_network_at_waterbodies: bool = False
level_pool: Optional["LevelPool"] = None
waterbody_null_code: int = -9999


class LevelPool(BaseModel, extra='forbid'):
class LevelPool(BaseModel):
# string, filepath to waterbody parameter file (LAKEPARM.nc)
level_pool_waterbody_parameter_file_path: Optional[FilePath] = None
level_pool_waterbody_id: Union[str, Literal["lake_id"]] = "lake_id"
Expand Down
Loading
Loading