From 90058a6289298fcb50b1c4c87f624a847ffefc48 Mon Sep 17 00:00:00 2001 From: Nick Savage <6161374+nhsavage@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:29:03 +0100 Subject: [PATCH 1/4] change print to logging --- scripts/run_all.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index 49a9916..965f7e4 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -13,6 +13,7 @@ from __future__ import annotations import argparse +import logging import os from pathlib import Path import shutil @@ -22,6 +23,8 @@ import yaml +logger = logging.getLogger(__name__) + ALLOWED_VARIABLES = {"2m_temperature", "total_precipitation"} @@ -298,7 +301,7 @@ def _run_step( command: list[str], dry_run: bool, env: dict[str, str] | None = None ) -> int: cmd_display = " ".join(command) - print(f"[RUN] {cmd_display}") + logger.info("[RUN] %s", cmd_display) if dry_run: return 0 From 803b05679bf7cdd478967d133856d5b8cc0fa5e9 Mon Sep 17 00:00:00 2001 From: Nick Savage <6161374+nhsavage@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:36:44 +0100 Subject: [PATCH 2/4] remove dry-run flag --- scripts/run_all.py | 31 +++---- scripts/tests/test_run_all.py | 150 +++++++++++----------------------- 2 files changed, 60 insertions(+), 121 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index 965f7e4..cf42953 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -297,14 +297,9 @@ def ensure_directories(paths: dict[str, Any]) -> None: Path(target).mkdir(parents=True, exist_ok=True) -def _run_step( - command: list[str], dry_run: bool, env: dict[str, str] | None = None -) -> int: +def _run_step(command: list[str], env: dict[str, str] | None = None) -> int: cmd_display = " ".join(command) logger.info("[RUN] %s", cmd_display) - if dry_run: - return 0 - completed = subprocess.run(command, check=False, env=env) return completed.returncode @@ -377,7 +372,7 @@ def _bool_str(value: bool) -> str: return "True" if value else "False" -def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int: +def run_pipeline(config: dict[str, Any], script_dir: Path) -> int: """Execute configured hindcast and forecast workflows. Parameters @@ -386,8 +381,6 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int Validated configuration mapping. script_dir : pathlib.Path Directory containing the workflow scripts to execute. - dry_run : bool - If ``True``, print commands without running subprocesses. Returns ------- @@ -419,7 +412,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int "--pycptdir", paths["hindcast"]["pycpt"], ] - if _run_step(era5_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(era5_cmd, env=subprocess_env) != 0: failures.append("era5") for centre in centres: @@ -437,7 +430,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int "--pycptdir", paths["hindcast"]["pycpt"], ] - if _run_step(download_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(download_cmd, env=subprocess_env) != 0: failures.append(f"hindcast-download:{centre}") continue @@ -456,7 +449,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int "--pycptdir", paths["hindcast"]["pycpt"], ] - if _run_step(products_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(products_cmd, env=subprocess_env) != 0: failures.append(f"hindcast-products:{centre}") continue @@ -471,7 +464,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int "--productsdir", paths["hindcast"]["products"], ] - if _run_step(scores_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(scores_cmd, env=subprocess_env) != 0: failures.append(f"hindcast-scores:{centre}") continue @@ -491,7 +484,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int if params["method"] is not None: plots_cmd.extend(["--method", params["method"]]) - if _run_step(plots_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(plots_cmd, env=subprocess_env) != 0: failures.append(f"hindcast-plots:{centre}") if config["workflow"]["forecast"]: @@ -512,7 +505,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int "--pycptdir", paths["forecast"]["pycpt"], ] - if _run_step(forecast_download_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(forecast_download_cmd, env=subprocess_env) != 0: failures.append(f"forecast-download:{centre}") continue @@ -539,7 +532,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int "--hindcast_pycptdir", paths["hindcast"]["pycpt"], ] - if _run_step(forecast_products_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(forecast_products_cmd, env=subprocess_env) != 0: failures.append(f"forecast-products:{centre}") continue @@ -558,7 +551,7 @@ def run_pipeline(config: dict[str, Any], script_dir: Path, dry_run: bool) -> int "--yearsfc", str(params["forecast_year"]), ] - if _run_step(forecast_plots_cmd, dry_run, env=subprocess_env) != 0: + if _run_step(forecast_plots_cmd, env=subprocess_env) != 0: failures.append(f"forecast-plots:{centre}") if failures: @@ -585,8 +578,6 @@ def build_parser() -> argparse.ArgumentParser: default=str(Path(__file__).resolve().parents[1] / "osop_config.yml"), help="Path to YAML configuration file", ) - parser.add_argument("--dry-run", action="store_true", help="Print commands only") - return parser @@ -612,7 +603,7 @@ def main(argv: list[str] | None = None) -> int: validated = validate_config(loaded) script_dir = Path(__file__).resolve().parent - return run_pipeline(validated, script_dir, dry_run=args.dry_run) + return run_pipeline(validated, script_dir) except ConfigError as exc: print(f"Configuration error: {exc}", file=sys.stderr) return 2 diff --git a/scripts/tests/test_run_all.py b/scripts/tests/test_run_all.py index 6c34ca6..e97e5fd 100644 --- a/scripts/tests/test_run_all.py +++ b/scripts/tests/test_run_all.py @@ -385,18 +385,12 @@ def test_ensure_directories_creates_all_dirs(tmp_path): # --------------------------------------------------------------------------- -def test_run_step_dry_run_returns_zero(): - """In dry-run mode no subprocess is spawned and 0 is returned.""" - run_all = _get_run_all() - assert run_all._run_step(["echo", "hello"], dry_run=True) == 0 - - def test_run_step_real_run_success(): """A subprocess returning 0 causes _run_step to return 0.""" run_all = _get_run_all() with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=0) - assert run_all._run_step(["echo", "hello"], dry_run=False) == 0 + assert run_all._run_step(["echo", "hello"]) == 0 def test_run_step_real_run_failure(): @@ -404,7 +398,7 @@ def test_run_step_real_run_failure(): run_all = _get_run_all() with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=1) - assert run_all._run_step(["false"], dry_run=False) == 1 + assert run_all._run_step(["false"]) == 1 # --------------------------------------------------------------------------- @@ -499,31 +493,31 @@ def test_select_centres_empty_raises(tmp_path): # --------------------------------------------------------------------------- -def test_hindcast_dry_run(tmp_path): - """Hindcast pipeline in dry-run mode returns 0 without spawning subprocesses.""" +def test_hindcast_returns_zero(tmp_path): + """Hindcast pipeline returns 0 when all steps succeed.""" run_all = _get_run_all() config = _minimal_config(tmp_path) config["workflow"]["hindcast"] = True config["workflow"]["forecast"] = False - rc = run_all.run_pipeline( - run_all.validate_config(config), - script_dir=Path(__file__).resolve().parents[1], - dry_run=True, - ) + with patch.object(run_all, "_run_step", return_value=0): + rc = run_all.run_pipeline( + run_all.validate_config(config), + script_dir=Path(__file__).resolve().parents[1], + ) assert rc == 0 -def test_hindcast_no_method_dry_run(tmp_path): +def test_hindcast_no_method_omits_flag(tmp_path): """Hindcast pipeline without a plot method omits --method from the plots command.""" run_all = _get_run_all() config = _minimal_config(tmp_path) config["workflow"]["hindcast"] = True config["parameters"]["method"] = None - rc = run_all.run_pipeline( - run_all.validate_config(config), - script_dir=Path(__file__).resolve().parents[1], - dry_run=True, - ) + with patch.object(run_all, "_run_step", return_value=0): + rc = run_all.run_pipeline( + run_all.validate_config(config), + script_dir=Path(__file__).resolve().parents[1], + ) assert rc == 0 @@ -535,12 +529,7 @@ def test_hindcast_step_failure_returns_nonzero(tmp_path): config["centres"] = ["ukmo"] validated = run_all.validate_config(config) with patch.object(run_all, "_run_step", return_value=1): - assert ( - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) - == 1 - ) + assert run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) == 1 def test_hindcast_mme_skips_download(tmp_path): @@ -555,11 +544,9 @@ def test_hindcast_mme_skips_download(tmp_path): with patch.object( run_all, "_run_step", - side_effect=lambda cmd, dry_run, env=None: calls.append(cmd) or 0, + side_effect=lambda cmd, env=None: calls.append(cmd) or 0, ): - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) + run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) script_names = [Path(cmd[1]).name for cmd in calls if len(cmd) > 1] assert "get_any_hindcast.py" not in script_names @@ -574,16 +561,11 @@ def test_hindcast_products_failure_skips_scores_plots(tmp_path): config["centres"] = ["ukmo"] validated = run_all.validate_config(config) - def selective_fail(command, dry_run, env=None): + def selective_fail(command, env=None): return 1 if Path(command[1]).name == "compute_products.py" else 0 with patch.object(run_all, "_run_step", side_effect=selective_fail): - assert ( - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) - == 1 - ) + assert run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) == 1 def test_hindcast_scores_failure_skips_plots(tmp_path): @@ -594,16 +576,11 @@ def test_hindcast_scores_failure_skips_plots(tmp_path): config["centres"] = ["ukmo"] validated = run_all.validate_config(config) - def selective_fail(command, dry_run, env=None): + def selective_fail(command, env=None): return 1 if Path(command[1]).name == "compute_scores.py" else 0 with patch.object(run_all, "_run_step", side_effect=selective_fail): - assert ( - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) - == 1 - ) + assert run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) == 1 def test_hindcast_plots_failure_recorded(tmp_path): @@ -614,16 +591,11 @@ def test_hindcast_plots_failure_recorded(tmp_path): config["centres"] = ["ukmo"] validated = run_all.validate_config(config) - def selective_fail(command, dry_run, env=None): + def selective_fail(command, env=None): return 1 if Path(command[1]).name == "plot_verification.py" else 0 with patch.object(run_all, "_run_step", side_effect=selective_fail): - assert ( - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) - == 1 - ) + assert run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) == 1 # --------------------------------------------------------------------------- @@ -631,17 +603,17 @@ def selective_fail(command, dry_run, env=None): # --------------------------------------------------------------------------- -def test_forecast_dry_run(tmp_path): - """Forecast pipeline in dry-run mode returns 0 without spawning subprocesses.""" +def test_forecast_returns_zero(tmp_path): + """Forecast pipeline returns 0 when all steps succeed.""" run_all = _get_run_all() config = _minimal_config(tmp_path) config["workflow"]["hindcast"] = False config["workflow"]["forecast"] = True - rc = run_all.run_pipeline( - run_all.validate_config(config), - script_dir=Path(__file__).resolve().parents[1], - dry_run=True, - ) + with patch.object(run_all, "_run_step", return_value=0): + rc = run_all.run_pipeline( + run_all.validate_config(config), + script_dir=Path(__file__).resolve().parents[1], + ) assert rc == 0 @@ -654,12 +626,7 @@ def test_forecast_step_failure_returns_nonzero(tmp_path): config["centres"] = ["ukmo"] validated = run_all.validate_config(config) with patch.object(run_all, "_run_step", return_value=1): - assert ( - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) - == 1 - ) + assert run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) == 1 def test_forecast_mme_skips_download(tmp_path): @@ -675,11 +642,9 @@ def test_forecast_mme_skips_download(tmp_path): with patch.object( run_all, "_run_step", - side_effect=lambda cmd, dry_run, env=None: calls.append(cmd) or 0, + side_effect=lambda cmd, env=None: calls.append(cmd) or 0, ): - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) + run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) script_names = [Path(cmd[1]).name for cmd in calls if len(cmd) > 1] assert "get_any_hindcast.py" not in script_names @@ -696,16 +661,11 @@ def test_forecast_products_failure_skips_plots(tmp_path): config["centres"] = ["ukmo"] validated = run_all.validate_config(config) - def selective_fail(command, dry_run, env=None): + def selective_fail(command, env=None): return 1 if Path(command[1]).name == "forecast_products.py" else 0 with patch.object(run_all, "_run_step", side_effect=selective_fail): - assert ( - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) - == 1 - ) + assert run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) == 1 def test_forecast_plots_failure_recorded(tmp_path): @@ -717,16 +677,11 @@ def test_forecast_plots_failure_recorded(tmp_path): config["centres"] = ["ukmo"] validated = run_all.validate_config(config) - def selective_fail(command, dry_run, env=None): + def selective_fail(command, env=None): return 1 if Path(command[1]).name == "forecast_plots.py" else 0 with patch.object(run_all, "_run_step", side_effect=selective_fail): - assert ( - run_all.run_pipeline( - validated, Path(__file__).resolve().parents[1], dry_run=False - ) - == 1 - ) + assert run_all.run_pipeline(validated, Path(__file__).resolve().parents[1]) == 1 # --------------------------------------------------------------------------- @@ -734,17 +689,17 @@ def selective_fail(command, dry_run, env=None): # --------------------------------------------------------------------------- -def test_dry_run_writes_services_and_returns_success(tmp_path): +def test_run_pipeline_writes_services(tmp_path): """Ensure run_pipeline writes parseyml.yml into both hindcast and forecast download dirs.""" run_all = _load_run_all_module() config = _minimal_config(tmp_path) validated = run_all.validate_config(config) - rc = run_all.run_pipeline( - validated, - script_dir=Path(__file__).resolve().parents[1], - dry_run=True, - ) + with patch.object(run_all, "_run_step", return_value=0): + rc = run_all.run_pipeline( + validated, + script_dir=Path(__file__).resolve().parents[1], + ) assert rc == 0 parseyml_hc = Path(validated["paths"]["hindcast"]["downloads"]) / "parseyml.yml" @@ -764,32 +719,25 @@ def test_dry_run_writes_services_and_returns_success(tmp_path): def test_build_parser_defaults(): - """Parser defaults expose only YAML path and dry-run switch.""" + """Parser exposes only the YAML config path.""" run_all = _get_run_all() args = run_all.build_parser().parse_args([]) - assert args.dry_run is False assert args.config -def test_build_parser_with_dry_run_flag(): - """--dry-run is parsed correctly.""" - run_all = _get_run_all() - args = run_all.build_parser().parse_args(["--dry-run"]) - assert args.dry_run is True - - # --------------------------------------------------------------------------- # main # --------------------------------------------------------------------------- -def test_main_dry_run(tmp_path): - """Ensure main() loads config, validates it, and runs the pipeline in dry-run mode.""" +def test_main_runs_pipeline(tmp_path): + """Ensure main() loads config, validates it, and runs the pipeline.""" run_all = _get_run_all() cfg_path = tmp_path / "config.yml" with cfg_path.open("w", encoding="utf-8") as f: yaml.safe_dump(_minimal_config(tmp_path), f) - assert run_all.main(["--config", str(cfg_path), "--dry-run"]) == 0 + with patch.object(run_all, "_run_step", return_value=0): + assert run_all.main(["--config", str(cfg_path)]) == 0 def test_main_config_error(tmp_path): From df4a6be6586804c770e54285039f9320c4e656a2 Mon Sep 17 00:00:00 2001 From: Nick Savage <6161374+nhsavage@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:39:37 +0100 Subject: [PATCH 3/4] remove reference to dry-run in docs --- docs/source/run.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/source/run.md b/docs/source/run.md index 18f05cc..4c2170d 100644 --- a/docs/source/run.md +++ b/docs/source/run.md @@ -11,12 +11,6 @@ From the repository root: python scripts/run_all.py --config osop_config.yml ``` -For a safe command preview that does not download or process data: - -```bash -python scripts/run_all.py --config osop_config.yml --dry-run -``` - ## Configuration file All user options are in `osop_config.yml`: From 04d724eb303f4ed385b68e55c41b05bc41e3b064 Mon Sep 17 00:00:00 2001 From: Nick Savage <6161374+nhsavage@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:12:58 +0100 Subject: [PATCH 4/4] restore missing parts of run.md but with clearer and update explanations --- docs/source/run.md | 180 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 168 insertions(+), 12 deletions(-) diff --git a/docs/source/run.md b/docs/source/run.md index 4c2170d..2b5b855 100644 --- a/docs/source/run.md +++ b/docs/source/run.md @@ -1,35 +1,191 @@ # Running the toolkit -The recommended top-level entrypoint is now a Python script controlled by one -YAML file. +Inside the Gitbash or Linux terminal, navigating to the osop-main file +and typing `ls` should bring up a list of directories contained within +the toolkit. ## Run command -From the repository root: +The top-level programme is a Python script controlled by a YAML file. + +From the repository root run: ```bash python scripts/run_all.py --config osop_config.yml ``` +This runs the python code using the options selected in the +`osop_config.yml` file. + +Summary of run options: + +- `--config`: Path to the YAML configuration file (example: `osop_config.yml`). +- The script uses the `workflow`, `parameters`, `paths`, `centres`, and `services` sections from the config. + ## Configuration file -All user options are in `osop_config.yml`: +The script as downloaded is set up with default options for the +variable, season and area to provide a forecast for. However, the user +will want to set the time frame, location and variable of their +choice. + +To do this we will need to edit the `osop_config.yml` file. The first +step here is to open this file which can be found at the top level of +the osop toolkit. It does not matter how this is opened. One option +for people who have followed this text up to this point is to open it +with nano from a terminal as was done earlier. -- workflow switches (`hindcast`, `forecast`) -- core parameters (`month`, `leads`, `area`, `variable`, `location`) -- pycpt options (`pycpt`, `predictor_area`) -- forecast year (`forecast_year`) -- output directories (`paths`) -- service IDs and weights (`services`) +This file is written in [YAML](https://en.wikipedia.org/wiki/YAML) which +is commonly used for configuration files. It aims to be readable for +human beings while also being capable of being loaded by a computer. -This replaces editing shell variables directly. +Below we explain each of the contents of the configuration file. +Make the changes you need to run your experiment then run: + +```bash +python scripts/run_all.py --config osop_config.yml +``` + +Keep an eye on the output. Once complete the plots should be available +in the dedicated directory. This will give you the hindcast +verification for your set up and the forecast if requested. + +The configuration file has a number of sections. Example file: + +```yaml +workflow: + hindcast: true + forecast: false + +parameters: + month: 5 + leads: "2,3,4" + area: "39,60,-11,141" + variable: "total_precipitation" + location: "None" + method: "pmesh" + pycpt: true + predictor_area: "40,0,-40,359" + forecast_year: 2025 + +paths: + base: "./output/single_script" + logdir: "{base}/logfiles" + hindcast: + downloads: "{base}/hindcast/downloads" + products: "{base}/hindcast/products" + scores: "{base}/hindcast/scores" + plots: "{base}/hindcast/plots" + pycpt: "{base}/hindcast/pycpt" + forecast: + downloads: "{base}/forecast/downloads" + products: "{base}/forecast/products" + scores: "{base}/forecast/scores" + plots: "{base}/forecast/plots" + pycpt: "{base}/forecast/pycpt" + +centres: + - meteo_france + - dwd + - cmcc + - ncep + - ukmo + - ecmwf + - jma + - eccc + - bom + - mme + +services: + ecmwf: [51, 1] + meteo_france: [9, 1] + dwd: [22, 1] + cmcc: [35, 1] + ncep: [2, 1] + jma: [3, 0] + eccc_can: [4, 1] + eccc_gem5: [5, 1] + ukmo: [604, 1] + bom: [2, 1] + mme: [1, 0] + +``` + +### Workflow + +- **`hindcast`**: Run hindcast verification (true/false). +- **`forecast`**: Run generation of forecast outputs (true/false). + +### Parameters + +- **`month`**: Integer. Month used for initialisation time of the forecast (example: `5`). +- **`leads`**: Comma-separated lead times as a string (example: `"2,3,4"`). With an initialisation +month of 5 leads 2,3,4 gives a forecast/hindcast averaged across the months of June, July and August. Note that the code follows the C3S conventions and so 0 is not a valid lead time. To use the data from the same month as the forecast initialisation time use a lead of 1. +- **`area`**: Bounding box to be forecast as a string `latN,latS,lonW,lonE` (example: `"39,60,-11,141"`). +- **`variable`**: Forecast variable. Valid values are `total_precipitation` and `2m_temperature`. +- **`location`**: Optional named location (string) or `None`. +- **`method`**: Plotting method. `pmesh` gives colormesh plots, any other value gives contour plots. +- **`pycpt`**: Boolean toggle to enable pycpt processing. +- **`predictor_area`**: Predictor bbox string used by pycpt (example: `"40,0,-40,359"`). +- **`forecast_year`**: Year used for forecast (integer). + +### Paths + +- **`base`**: Base output directory (example: `./output/single_script`). In the following entries `{base}` is replaced with the value of this setting. +- **`logdir`**: Log directory +- **`hindcast` / `forecast`**: Subfolders for `downloads`, `products`, `scores`, `plots`, and `pycpt`. + +### Centres + +- Ordered list of model centres to include (examples: `meteo_france`, `dwd`, `ecmwf`, `ukmo`, etc.). + +### Services + +- Mapping of centre names to a two-element array: `[service_id, + enabled_flag_or_weight]` (example: `ecmwf: [51, 1]`). + - Service ID is the a unique value which is assigned by CDS trying + to map as close as possible the version numbering used by the + forecasting centres. + - Weight is the relvative weight given to each model when producing + a multimodel ensemble (MME). + + + For a given centre you will need to check two things. A) The service + you would like to use is included and B) that the system version is + correct for the time frame and use case. Generally, you want to be + on the latest version. However, some systems do not run historic + years until the month is reached for the new model. As such checking + the data is available on Copernicus Climate Data Store is advised. + See: [Description of the C3S seasonal + multi-system](https://confluence.ecmwf.int/spaces/CKB/pages/77213502/Description+of+the+C3S+seasonal+multi-system) + and/or [Seasonal forecast monthly statistics on single + levels](https://cds.climate.copernicus.eu/datasets/seasonal-monthly-single-levels?tab=overview) ## Viewing outputs -Outputs are written to the path configured in `paths.base`. +After running the script you will probably want to review the output of your +work. Outputs are written to the path configured in the yml file as +explained above. - Hindcast plots: `{base}/hindcast/plots` - Forecast plots: `{base}/forecast/plots` - Logs: `{base}/logfiles` If the default config is unchanged, output is under `./output/single_script`. + +The rest of these instructions assume the files are stored under the +output folder. These instructions show how to navigate via terminal +using Gitbash. Type `cd output/data/master/hindcast/plots` and then +type `ls`. This will bring up a list of appropriate plots. From here +copy a file name and type code `pastefile` and enter. This should +bring up an image output. + +If you receive an error and want to debug it, you may wish to see the +logfiles. To do that you can go to `cd +./output/data/master/hindcast/logfiles`. This is where output of the +code is stored for checking each time it is run. If an error is +received it will be captured here. It can be accessed the same way as +the image plots. + +Forecasts are stored in a slightly different location if nothing has +been changed by user inputs `cd ./output/data/master/forecasts/plots`.