diff --git a/bin_mounted/cli_args.py b/bin_mounted/cli_args.py new file mode 100644 index 0000000..1f69815 --- /dev/null +++ b/bin_mounted/cli_args.py @@ -0,0 +1,42 @@ +from dataclasses import dataclass + +# from mswm.utils.settings import LAGGED_ENSEMBLE_MEMBER_LAGS +# TODO replace with import of mswm.utils.settings.LAGGED_ENSEMBLE_MEMBER_LAGS +from consts import LAGGED_ENSEMBLE_MEMBER_LAGS + + +@dataclass +class ArgsKwargs: + """Simple args list and kwargs dict to be passed later to parser.add_argument(*args, **kwargs)""" + + args: list + kwargs: dict + + +LAGGED_ENSEMBLE = ArgsKwargs( + args=["-le", "--lagged-ensemble"], + kwargs={ + "dest": "lagged_ensemble_args", + "type": str, + "nargs": 3, + "required": False, + "help": f"""Provide this multi-part argument to run one member of a lagged ensemble (see nwm-fcst-mgr function `run_lagged_ensemble`). + Only applicable to the "medium_range" forcing configuration. + Not applicable to the cold-start realization. + + To run an ensemble, call this script multiple times with varying values for this argument, e.g. for "mem1", "mem2", etc. + + This argument has 3 parts: + 1. member_name : str (required when -le provided) + Name of the ensemble member. Choose from: {list(LAGGED_ENSEMBLE_MEMBER_LAGS)} + 2. open_loop_state : str (optional) + Path to an existing open-loop state file. + To omit, provide an empty string for this part. + 3. closed_loop_state : str (optional) + Path to an existing closed-loop state file. + To omit, provide an empty string for this part. + + To run a lagged ensemble member without the optional parts, provide them as empty strings e.g. `-le 'mem2' '' ''`. + """, + }, +) diff --git a/bin_mounted/configs.py b/bin_mounted/configs.py index e736064..cb6ddd0 100644 --- a/bin_mounted/configs.py +++ b/bin_mounted/configs.py @@ -4,6 +4,10 @@ import re from typing import Literal +# from mswm.utils.settings import LAGGED_ENSEMBLE_MEMBER_LAGS +# TODO replace with import of mswm.utils.settings.LAGGED_ENSEMBLE_MEMBER_LAGS +from consts import LAGGED_ENSEMBLE_MEMBER_LAGS + from mswm.utils.input_configuration import ( InputConfig, GeneralConfig, @@ -96,76 +100,138 @@ def valid_yaml(self) -> str: return f"{self.dir_output}/Validation_Run/{self.gage_id}_config_valid_best.yaml" -class RTESetup(BaseModel): - """Used to set up a RTE run. Triggers certain setup actions, such as creation of WCOSS-path symlinks. +class RTEBaseConfig(BaseModel): + """Base RTE configuration class to be inherited by child classes. + Triggers certain setup actions, such as creation of WCOSS-path symlinks. Classes that inherit from this should call super().model_post_init(__context) inside their own model_post_init() method, if they have that method also defined in the child.""" + # Set during init + delete_scratch_and_mesh_first: bool + delete_forcing_raw_input_first: bool + nprocs: int = Field(ge=1) + global_domain: str + forcing_static_dir: str + forcing_provider: str + + # Set after init (not provided as args) + errors: list | None = Field(init=False, default=None) + + gage_id: str = Field(init=False, default=None) + gage_vintage: str = Field(init=False, default=None) + + # For lagged ensemble + use_lagged_ensemble: bool | None = Field(init=False, default=False) + lagged_ens_mem: str | None = Field(init=False, default=None) + forcing_lag: str | None = Field(init=False, default=None) + le__open_loop_state: str | None = Field(init=False, default=None) + le__closed_loop_state: str | None = Field(init=False, default=None) + def model_post_init(self, __context) -> None: + self.errors = [] make_wcoss_path_symlinks() + def _parse_gage_id__gage_vintage(self) -> None: + """Parse the provided string and split it into two strings: gage_id and gage_vintage and set attributes. + Extend errors list as appropriate. + Called by child classes which define the necessary attributes.""" + gage_id, gage_vintage = self.gage_id__gage_vintage + + if gage_id != gage_id.strip(): + self.errors.append( + ValueError(f"Whitespace found on end of gage_id: {repr(gage_id)}") + ) + gage_id = None + + if gage_vintage != gage_vintage.strip(): + self.errors.append( + ValueError( + f"Whitespace found on end of gage_vintage: {repr(gage_vintage)}" + ) + ) + gage_vintage = None + + self.gage_id = gage_id + self.gage_vintage = gage_vintage + + def _parse_lagged_ensemble_args(self): + """Break up the multipart lagged ensemble arg into distinct args and set them. + Called by child classes which define the necessary attributes.""" + if self.lagged_ensemble_args: + if self.forcing_configuration != "medium_range": + raise ValueError( + f"lagged ensemble only supported for medium_range, but forcing configuration {repr(self.forcing_configuration)} was provided" + ) + + self.use_lagged_ensemble = True + + member_name, open_ls, closed_ls = self.lagged_ensemble_args + + self.lagged_ens_mem = member_name if member_name.strip() else None + self.forcing_lag = LAGGED_ENSEMBLE_MEMBER_LAGS[self.lagged_ens_mem] + self.le__open_loop_state = open_ls if open_ls.strip() else None + self.le__closed_loop_state = closed_ls if closed_ls.strip() else None -class RTEDefaultConfig(RTESetup): + if self.lagged_ens_mem not in LAGGED_ENSEMBLE_MEMBER_LAGS: + raise KeyError( + f"Invalid lagged ensemble member {repr(self.lagged_ens_mem)} (choose from: {list(LAGGED_ENSEMBLE_MEMBER_LAGS)})" + ) + + if self.le__open_loop_state or self.le__closed_loop_state: + raise NotImplementedError( + "Lagged ensemble args for Open Loop State and Closed Loop State are not yet implemented in nwm-rte (should be provided as empty strings for now)" + ) + + +class RTEDefaultConfig(RTEBaseConfig): """Configuration class for building and running one default realization (realtime forcing configuration or historical / retrospective forcing configuration).""" model_config = ConfigDict(strict=True, arbitrary_types_allowed=True) - delete_scratch_and_mesh_first: bool - delete_forcing_raw_input_first: bool gage_id__gage_vintage: list[str] = Field(min_length=2, max_length=2) - global_domain: str - forcing_static_dir: str - forcing_provider: str cycle_datetime: datetime historical_sim_duration: timedelta | None forcing_configuration: str fcst_run_name: str - nprocs: int = Field(ge=1) + # For medium-range lagged ensemble + lagged_ensemble_args: list[str] | None = Field(min_length=3, max_length=3) # Set after init - gage_id: str = Field(init=False, default=None) - gage_vintage: str = Field(init=False, default=None) realtime_mode: bool = Field(init=False, default=None) # Other derived attrs (not passed to __init__) realization_builder_kwargs: dict = Field(init=False, default=None) def model_post_init(self, __context) -> None: - super().model_post_init(__context) # Call RTESetup's post init - - errors = [] + super().model_post_init(__context) # Call RTEBaseConfig's post init + super()._parse_gage_id__gage_vintage() if ( self.forcing_configuration - not in c.FORECAST_FORCING_CONFIGURATION_TYPES__ALL + not in c.FORECAST_FORCING_CONFIGURATION_TYPES__ALL + ["medium_range"] ): self.realtime_mode = False else: self.realtime_mode = True - self.gage_id, self.gage_vintage, errors_extend = parse_gage_id__gage_vintage( - self.gage_id__gage_vintage - ) - errors.extend(errors_extend) - if (not self.realtime_mode) and (not self.historical_sim_duration): - errors.extend( + self.errors.extend( [ f"Forcing configuration {repr(self.forcing_configuration)} is *not* realtime, and requires that CLI arg -dur aka --historical_sim_duration is provided, but it was not." ] ) if self.realtime_mode and self.historical_sim_duration: - errors.extend( + self.errors.extend( [ f"Forcing configuration {repr(self.forcing_configuration)} *is* realtime, but CLI arg -dur aka --historical_sim_duration was also provided (it should not be)." ] ) - if errors: - raise RuntimeError(errors) - + super()._parse_lagged_ensemble_args() self.realization_builder_kwargs = self._make_realization_builder_kwargs() + if self.errors: + raise RuntimeError(self.errors) def _make_realization_builder_kwargs(self) -> dict: """Build and return a dictionary for creating a RealizationBuilder instance.""" @@ -238,20 +304,21 @@ def _make_realization_builder_kwargs(self) -> dict: ), Parallel=make_parallel_config(self.nprocs), ), + # Lagged ensemble args + "use_lagged_ens": self.use_lagged_ensemble, + "lagged_ens_mem": self.lagged_ens_mem, + "forcing_lag": self.forcing_lag, } return realization_kwargs -class RTECalibConfig(RTESetup): +class RTECalibConfig(RTEBaseConfig): """Configuration class for building and running one calibration realization.""" model_config = ConfigDict(strict=True, arbitrary_types_allowed=True) - delete_scratch_and_mesh_first: bool - delete_forcing_raw_input_first: bool objective_function: c.CalObjective optimization_algorithm: c.CalOptimizationAlgo - nprocs: int = Field(ge=1) gage_id__gage_vintage: list[str] = Field(min_length=2, max_length=2) calib_sim_start: datetime calib_sim_duration: timedelta @@ -259,56 +326,41 @@ class RTECalibConfig(RTESetup): valid_sim_advancement: timedelta valid_eval_curtailment: timedelta forcing_source: str - global_domain: str - forcing_provider: str - forcing_static_dir: str worker_name: str | None # Set after init - gage_id: str = Field(init=False, default=None) - gage_vintage: str = Field(init=False, default=None) obs_dir: str | None = Field(init=False, default=None) nwmretro_file: str | None = Field(init=False, default=None) def model_post_init(self, __context) -> None: - super().model_post_init(__context) # Call RTESetup's post init - - errors = [] - - self.gage_id, self.gage_vintage, errors_extend = parse_gage_id__gage_vintage( - self.gage_id__gage_vintage - ) - errors.extend(errors_extend) + super().model_post_init(__context) # Call RTEBaseConfig's post init + super()._parse_gage_id__gage_vintage() self.obs_dir, self.nwmretro_file, errors_extend = get_data_paths_for_lstm( self.global_domain, self.gage_id, ) - errors.extend(errors_extend) + self.errors.extend(errors_extend) - if errors: - raise RuntimeError(errors) + if self.errors: + raise RuntimeError(self.errors) -class RTEForecastConfig(RTESetup): +class RTEForecastConfig(RTEBaseConfig): """Configuration class for building and running one forecast realization.""" model_config = ConfigDict(strict=True, arbitrary_types_allowed=True) - delete_scratch_and_mesh_first: bool - delete_forcing_raw_input_first: bool ### These calibration parameters affect directory path objective_function: c.CalObjective optimization_algorithm: c.CalOptimizationAlgo gage_id: str - global_domain: str - forcing_static_dir: str - forcing_provider: str cycle_datetime: datetime | None cold_start_datetime: datetime | None forcing_configuration: str fcst_run_name: str - nprocs: int = Field(ge=1) + # For medium-range lagged ensemble + lagged_ensemble_args: list[str] | None = Field(min_length=3, max_length=3) # Derived paths (not passed to __init__) run_dir_base: str = Field(init=False, default=None) @@ -321,7 +373,7 @@ class RTEForecastConfig(RTESetup): realization_builder_kwargs: dict = Field(init=False, default=None) def model_post_init(self, __context) -> None: - super().model_post_init(__context) # Call RTESetup's post init + super().model_post_init(__context) # Call RTEBaseConfig's post init self.run_dir_base = f"{c.DEFAULT_MAIN_DIR}/{self.objective_function.value}_{self.optimization_algorithm.value}/test_{self.forcing_provider}/{self.gage_id}" if not os.path.isdir(self.run_dir_base): @@ -333,10 +385,11 @@ def model_post_init(self, __context) -> None: self.ngen_log_file = f"{self.run_dir_base}/logs/ngen.log" self.valid_best_yaml = f"{self.run_dir_output}/Validation_Run/{self.gage_id}_config_valid_best.yaml" - self.realization_builder_kwargs = self._make_realization_builder_kwargs() + super()._parse_lagged_ensemble_args() + self._make_realization_builder_kwargs() - def _make_realization_builder_kwargs(self) -> dict: - """Build and return a dictionary for creating a RealizationBuilder instance.""" + def _make_realization_builder_kwargs(self) -> None: + """Build and set a dictionary for creating a RealizationBuilder instance.""" fpp = ForcingProviderPaths( forcing_provider=self.forcing_provider, global_domain=self.global_domain, @@ -369,17 +422,19 @@ def _make_realization_builder_kwargs(self) -> dict: ), Parallel=make_parallel_config(self.nprocs), ), + # Lagged ensemble args + "use_lagged_ens": self.use_lagged_ensemble, + "lagged_ens_mem": self.lagged_ens_mem, + "forcing_lag": self.forcing_lag, } - return realization_kwargs + self.realization_builder_kwargs = realization_kwargs -class RTETestConfig(RTESetup): +class RTETestConfig(RTEBaseConfig): """Configuration class for building and running a set of test realizations.""" model_config = ConfigDict(strict=True, arbitrary_types_allowed=True) - delete_scratch_and_mesh_first: bool - delete_forcing_raw_input_first: bool skip_forecast: bool quit_forecast_after_forcing_running: bool quit_forecast_after_duration: float | None = Field(ge=0) @@ -394,36 +449,22 @@ class RTETestConfig(RTESetup): do_all_forcing_configs: bool do_coldstart: bool fcst_run_name: str - nprocs: int = Field(ge=1) gage_id__gage_vintage: list[str] = Field(min_length=2, max_length=2) - global_domain: str - forcing_provider: str - forcing_static_dir: str noop: bool - # Set after init - gage_id: str = Field(init=False, default=None) - gage_vintage: str = Field(init=False, default=None) - def model_post_init(self, __context) -> None: - super().model_post_init(__context) # Call RTESetup's post init - - errors = [] + super().model_post_init(__context) # Call RTEBaseConfig's post init + super()._parse_gage_id__gage_vintage() if self.quit_forecast_after_forcing_running: - errors.append( + self.errors.append( RuntimeError( "quit_forecast_after_forcing_running is currently not allowed, pending updates." ) ) - self.gage_id, self.gage_vintage, errors_extend = parse_gage_id__gage_vintage( - self.gage_id__gage_vintage - ) - errors.extend(errors_extend) - errors_extend = parse_fcst_run_name(self.fcst_run_name) - errors.extend(errors_extend) + self.errors.extend(errors_extend) if self.do_all_objective_functions: self.objective_functions = list(c.CalObjective) @@ -432,14 +473,14 @@ def model_post_init(self, __context) -> None: if self.do_all_forcing_configs: if self.skip_forecast and (not self.do_coldstart): - errors.append( + self.errors.append( ValueError( f"When do_all_forcing_configs={self.do_all_forcing_configs}, must have coldstart and/or forecast enabled." ) ) - if errors: - raise RuntimeError(errors) + if self.errors: + raise RuntimeError(self.errors) def get_calib_permutations( self, @@ -486,28 +527,6 @@ def make_parallel_config(nprocs: int) -> ParallelConfig: return parallel -def parse_gage_id__gage_vintage( - gage_id__gage_vintage: tuple[str, str], -) -> tuple[str | None, str | None, list[Exception]]: - """Parse the provided string and split it into two strings: gage_id and gage_vintage""" - errors: list[Exception] = [] - gage_id, gage_vintage = gage_id__gage_vintage - - if gage_id != gage_id.strip(): - errors.append( - ValueError(f"Whitespace found on end of gage_id: {repr(gage_id)}") - ) - gage_id = None - - if gage_vintage != gage_vintage.strip(): - errors.append( - ValueError(f"Whitespace found on end of gage_vintage: {repr(gage_vintage)}") - ) - gage_vintage = None - - return gage_id, gage_vintage, errors - - def parse_fcst_run_name(fcst_run_name: str) -> list[Exception]: """Validate the provided forecast run name, and return a list of errors.""" errors: list[Exception] = [] diff --git a/bin_mounted/consts.py b/bin_mounted/consts.py index 53afb41..6d505e1 100644 --- a/bin_mounted/consts.py +++ b/bin_mounted/consts.py @@ -105,7 +105,9 @@ ] ALL_FORCING_CONFIGURATION_TYPES = ( - FORECAST_FORCING_CONFIGURATION_TYPES__ALL + CALIB_FORCING_CONFIGURATION_TYPES + FORECAST_FORCING_CONFIGURATION_TYPES__ALL + + CALIB_FORCING_CONFIGURATION_TYPES + + ["medium_range"] ) CALIB_GLOBAL_DOMAIN_DEFAULT = "CONUS" @@ -178,3 +180,15 @@ FORCING_PRODUCT_VERSIONS_DICT = json.load(f) else: FORCING_PRODUCT_VERSIONS_DICT = None + + +# TODO replace with import of mswm.utils.settings.LAGGED_ENSEMBLE_MEMBER_LAGS +LAGGED_ENSEMBLE_MEMBER_LAGS: dict[str, int] = { + "no_da": 0, + "mem1": 0, + "mem2": 6, + "mem3": 12, + "mem4": 18, + "mem5": 24, + "mem6": 30, +} diff --git a/bin_mounted/run_default.py b/bin_mounted/run_default.py index fdbb713..775bcc4 100644 --- a/bin_mounted/run_default.py +++ b/bin_mounted/run_default.py @@ -6,9 +6,9 @@ from mswm.build_inputs import RealizationBuilder +import cli_args from utils import ( timedelta_from_effective_days, - effective_days_from_timedelta, configure_ngen_log, datetime_type, ) @@ -148,6 +148,9 @@ def main(cfg: RTEDefaultConfig): default=None, help=f"Only used for historical / retrospective forcing (required in that case). Simulation duration in days. Default={None}", ) + parser.add_argument( + *cli_args.LAGGED_ENSEMBLE.args, **cli_args.LAGGED_ENSEMBLE.kwargs + ) parser.add_argument( "-fconfig", "--forcing_configuration", diff --git a/bin_mounted/run_forecast.py b/bin_mounted/run_forecast.py index e798e32..0854b9d 100644 --- a/bin_mounted/run_forecast.py +++ b/bin_mounted/run_forecast.py @@ -2,8 +2,11 @@ import argparse from mswm.build_inputs import RealizationBuilder -from nwm_fcst_mgr.forecast import run_forecast as run_fcst +from nwm_fcst_mgr.forecast import ( + run_forecast as run_fcst, +) +import cli_args import consts as c from configs import RTEForecastConfig import utils_testing_setup @@ -12,38 +15,66 @@ print = functools.partial(print, flush=True) -def build_coldstart_realization(cfg: RTEForecastConfig) -> RealizationBuilder: - """Build and return a coldstart forecast realization""" - print( - f"Building coldstart realization: {cfg.realization_builder_kwargs}, use_cold_start=True" - ) - rb_cs = RealizationBuilder(**cfg.realization_builder_kwargs, use_cold_start=True) - rb_cs.build_fcst_realization() - print(f"Wrote: {rb_cs.realization_file}") - # From existing fcst mgr conventions, e.g. ### Set environment variable NGEN_RESULTS_DIR to: /ngwpc/run_ngen/kge_dds/test_bmi/01123000/Output/Forecast_Run/fcst_run1_short_range - configure_ngen_log(rb_cs.input_dir, "cs") - if cfg.nprocs > 1 and not rb_cs.part_file: +def build_realization( + cfg: RTEForecastConfig, + rb_kwargs_final: dict, + log_label: str, +) -> RealizationBuilder: + """Build and return a forecast realization, applygin the provided rb_kwargs_final as-is.""" + print(f"Building realization: {rb_kwargs_final}") + rb = RealizationBuilder(**rb_kwargs_final) + rb.build_fcst_realization() + configure_ngen_log(rb.input_dir, log_label) + print(f"Wrote: {rb.realization_file}") + if cfg.nprocs > 1 and not rb.part_file: raise ValueError( - f"Expected partition file since cfg.nprocs > 1 ({cfg.nprocs}), but it is {repr(rb_cs.part_file)}" + f"Expected partition file since cfg.nprocs > 1 ({cfg.nprocs}), but it is {repr(rb.part_file)}" ) - return rb_cs + return rb + + +def build_run_coldstart_realization(cfg: RTEForecastConfig) -> RealizationBuilder: + """Build and run a coldstart forecast realization.""" + rb_kwargs_final = cfg.realization_builder_kwargs | {"use_cold_start": True} + rb = build_realization(cfg, rb_kwargs_final, "cs") + run_realization(rb, cfg) + return rb + + +def build_run_forecast_realization(cfg: RTEForecastConfig) -> RealizationBuilder: + """Build and return a non-coldstart forecast realization.""" + rb_kwargs_final = cfg.realization_builder_kwargs | { + "use_cold_start": False, + } + + rb = build_realization(cfg, rb_kwargs_final, "fcst") + run_realization(rb, cfg) + return rb -def build_forecast_realization(cfg: RTEForecastConfig) -> RealizationBuilder: - """Build and return a non-coldstart forecast realization""" +def run_realization( + rb: RealizationBuilder, + cfg: RTEForecastConfig, +) -> None: + """Run the realization, which can be a coldstart, forecast, or lagged ensemble.""" + # partition_file = getattr(rb, "part_file", None) + print( - f"Building forecast realization: {cfg.realization_builder_kwargs}, use_cold_start=False" + f"Running realization with Forcing configuration: {rb.input_configs['Forcing']}" ) - rb_fcst = RealizationBuilder(**cfg.realization_builder_kwargs, use_cold_start=False) - rb_fcst.build_fcst_realization() - # From existing fcst mgr conventions, e.g. ### Set environment variable NGEN_RESULTS_DIR to: /ngwpc/run_ngen/kge_dds/test_bmi/01123000/Output/Forecast_Run/fcst_run1_short_range - configure_ngen_log(rb_fcst.input_dir, "fcst") - print(f"Wrote: {rb_fcst.realization_file}") - if cfg.nprocs > 1 and not rb_fcst.part_file: - raise ValueError( - f"Expected partition file since cfg.nprocs > 1 ({cfg.nprocs}), but it is {repr(rb_fcst.part_file)}" + + if rb.use_hindcast: + raise NotImplementedError("use_hindcast not yet implemented in nwm-rte") + elif rb.use_warm_start: + raise NotImplementedError("use_warm_start not yet implemented in nwm-rte") + else: + print(f"Calling: {run_fcst}") + run_fcst( + valid_yaml=cfg.valid_best_yaml, + real_path=str(rb.realization_file), + partition_file=rb.part_file, ) - return rb_fcst + print(f"Finished calling: {run_fcst}") def main(cfg: RTEForecastConfig): @@ -62,25 +93,15 @@ def main(cfg: RTEForecastConfig): ) if cfg.cold_start_datetime: - rb_cs = build_coldstart_realization(cfg) - print(f"Running coldstart realization: {rb_cs.input_configs['Forcing']}") - run_fcst( - valid_yaml=cfg.valid_best_yaml, - real_path=str(rb_cs.realization_file), - partition_file=rb_cs.part_file if hasattr(rb_cs, "part_file") else None, - ) - + rb_cs = build_run_coldstart_realization(cfg) + run_realization(rb_cs, cfg) if cfg.cycle_datetime: - rb_fcst = build_forecast_realization(cfg) - print(f"Running forecast realization: {rb_fcst.input_configs['Forcing']}") - run_fcst( - valid_yaml=cfg.valid_best_yaml, - real_path=str(rb_fcst.realization_file), - partition_file=rb_fcst.part_file if hasattr(rb_fcst, "part_file") else None, - ) + rb_fcst = build_run_forecast_realization(cfg) + run_realization(rb_fcst, cfg) -if __name__ == "__main__": +def cli_arg_parser() -> argparse.ArgumentParser: + """Build and return the CLI argument parser""" parser = argparse.ArgumentParser() parser.add_argument( "-delscratch", @@ -141,16 +162,19 @@ def main(cfg: RTEForecastConfig): "-dt", "--cycle_datetime", type=datetime_type, - help="start date/time for the forecast cycle (also the end of cold-start if chosen), format= 'YYYY-MM-DD HH:mm:ss'. If omitted, a forecast will not be ran.", - default=None, + help="For a regular forecast, this is the start time. When cold-start is used, this is the *end* of the cold-start cycle. Format: 'YYYY-MM-DD HH:mm:ss'.", + required=True, ) parser.add_argument( "-csdt", "--cold_start_datetime", type=datetime_type, - help="start date/time for cold-start, format= 'YYYY-MM-DD HH:mm:ss'. If omitted, a cold-start will not be used.", + help="If provided, a cold-start realization will be ran prior to the forecast, and this value will be the start time for the cold-start. Format: 'YYYY-MM-DD HH:mm:ss'.", default=None, ) + parser.add_argument( + *cli_args.LAGGED_ENSEMBLE.args, **cli_args.LAGGED_ENSEMBLE.kwargs + ) parser.add_argument( "-fconfig", "--forcing_configuration", @@ -172,6 +196,12 @@ def main(cfg: RTEForecastConfig): default=c.DEFAULT_NPROCS, help=f"""Replaces default value for nprocs ({repr(c.DEFAULT_NPROCS)}) and subsequently the ParallelConfig instance that is passed to MSWM.""", ) + + return parser + + +if __name__ == "__main__": + parser = cli_arg_parser() args = parser.parse_args() cfg = RTEForecastConfig(**vars(args)) main(cfg) diff --git a/bin_mounted/utils.py b/bin_mounted/utils.py index e0a0699..8e851a1 100644 --- a/bin_mounted/utils.py +++ b/bin_mounted/utils.py @@ -37,7 +37,7 @@ def datetime_type(datetime_str) -> datetime: return datetime.strptime(datetime_str, mswm_settings.DEFAULT_DATETIME_FORMAT) -def configure_ngen_log(fallback_log_dir: str | pathlib.Path, description: str) -> None: +def configure_ngen_log(fallback_log_dir: str | pathlib.Path, label: str) -> None: """Configure the ngen logging, by setting the associated OS env variable for the directory to hold the logs, and copying the associated json file into that directory. @@ -45,7 +45,7 @@ def configure_ngen_log(fallback_log_dir: str | pathlib.Path, description: str) - fallback_log_dir : str Is ignored when the RTE OS env var key NGEN_LOG_TO_RTE is true. Used to emulate behavior of nwm-cal-mgr and nwm-fcst-mgr (what they would use without RTE) - description : str + label : str Is ignored when the RTE OS env var key NGEN_LOG_TO_RTE is false. Used to build the timestamped dir name. """ @@ -64,7 +64,7 @@ def configure_ngen_log(fallback_log_dir: str | pathlib.Path, description: str) - # Decide the dir setting_val = os.environ.get(c.RTE_NGEN_LOG_BEHAVIOR_KEY, "").lower().strip() if setting_val == "true": - log_dir = os.path.join("/ngen-app/rte_ngen_logs", f"{now_str}_{description}") + log_dir = os.path.join("/ngen-app/rte_ngen_logs", f"{now_str}_{label}") elif setting_val == "false": log_dir = fallback_log_dir else: diff --git a/run_default.sh b/run_default.sh index 58dba17..01a576a 100755 --- a/run_default.sh +++ b/run_default.sh @@ -17,6 +17,15 @@ docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "stan ### Medium Range docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range_blend" -dt "2026-03-30 06:00:00" -rname "default_mr" +### Medium Range Lagged Ensemble +# docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range" -dt "2026-03-30 06:00:00" -rname "default_mr_le" -le "no_da" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range" -dt "2026-03-30 06:00:00" -rname "default_mr_le" -le "mem1" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range" -dt "2026-03-30 06:00:00" -rname "default_mr_le" -le "mem2" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range" -dt "2026-03-30 06:00:00" -rname "default_mr_le" -le "mem3" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range" -dt "2026-03-30 06:00:00" -rname "default_mr_le" -le "mem4" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range" -dt "2026-03-30 06:00:00" -rname "default_mr_le" -le "mem5" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "medium_range" -dt "2026-03-30 06:00:00" -rname "default_mr_le" -le "mem6" "" "" + ### Historical / Retrospective Forcing docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "aorc" -dt "2013-07-25 00:00:00" -dur 2 -rname "default_aorc" docker_run python "/ngen-app/bin/bin_mounted/run_default.py" -n 2 -fconfig "nwm" -dt "2013-07-25 00:00:00" -dur 2 -rname "default_nwm" diff --git a/run_fcst.sh b/run_fcst.sh index 37c1729..cf455f7 100755 --- a/run_fcst.sh +++ b/run_fcst.sh @@ -19,6 +19,16 @@ docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -dt "2025-09-15 00 # docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_ana" -fconfig standard_ana -# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr" -fconfig medium_range +### Medium Range +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr" -fconfig medium_range_no_da + +### Medium Range Lagged Ensemble Members +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -n 2 -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr_le" -fconfig medium_range -le "no_da" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -n 2 -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr_le" -fconfig medium_range -le "mem1" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -n 2 -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr_le" -fconfig medium_range -le "mem2" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -n 2 -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr_le" -fconfig medium_range -le "mem3" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -n 2 -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr_le" -fconfig medium_range -le "mem4" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -n 2 -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr_le" -fconfig medium_range -le "mem5" "" "" +# docker_run python "/ngen-app/bin/bin_mounted/run_forecast.py" -n 2 -dt "2025-09-15 00:00:00" -rname "${fcst_run_name}_mr_le" -fconfig medium_range -le "mem6" "" "" exit 0