From 740fc3ab6e790c820c16a0f3ec89002097d1c5b1 Mon Sep 17 00:00:00 2001 From: daniloceano Date: Thu, 11 Jun 2026 21:49:30 +0000 Subject: [PATCH 1/7] gitignore: ignore WPS_GEOG and geog downloads Co-Authored-By: Claude Opus 4.8 --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 0d1c3f67..fdfe91bd 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,9 @@ met_data # Python compiled bytecode folders __pycache__ + +# WPS geographical static data (large downloads — never commit) +WPS_GEOG/ +._WPS_GEOG +geog_*.tar.gz +geog_*.tar.bz2 From c24534c1de5f0cf379121ccb1202a4d095f970ad Mon Sep 17 00:00:00 2001 From: daniloceano Date: Mon, 15 Jun 2026 19:26:14 +0000 Subject: [PATCH 2/7] gitignore: ignore stray HOME/per-user dirs created in the repo Co-Authored-By: Claude Opus 4.8 --- .gitignore | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitignore b/.gitignore index fdfe91bd..e86b337b 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,16 @@ WPS_GEOG/ ._WPS_GEOG geog_*.tar.gz geog_*.tar.bz2 + +# Stray per-user / HOME files created in the repo when a tool ran with HOME +# pointing here (conda cache+envs, shell history, etc.). Never commit; the real +# ones live in $HOME and these copies are safe to delete. +.conda/ +.cache/ +.local/ +.config/ +.claude/ +.condarc +.viminfo +.python_history +.bash_history From e4a586678f100ea2f1bd492212b6327edd267e7a Mon Sep 17 00:00:00 2001 From: daniloceano Date: Mon, 15 Jun 2026 18:51:07 +0000 Subject: [PATCH 3/7] usp-utils: robust conda env activation + add install script shebang - setup_environment.sh: stop inferring env existence from 'conda activate's exit code (which gives a misleading "Couldn't find the environment" when the env exists but a transient activate returns non-zero). Now: ensure the 'conda' shell function is loaded (source conda.sh if only the binary is on PATH), check the env in 'conda env list', then activate. Clearer not-found message. - install_conda_environment.sh: add '#!/usr/bin/env bash' shebang. It uses bash-only features (BASH_SOURCE, '[ == ]'); running it with sh/dash failed with "Bad substitution" / "unexpected operator" and left SCRIPT_DIR empty. Co-Authored-By: Claude Opus 4.8 --- usp-utils/install_conda_environment.sh | 7 ++++++- usp-utils/setup_environment.sh | 26 ++++++++++++++++---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/usp-utils/install_conda_environment.sh b/usp-utils/install_conda_environment.sh index 3d3e4004..eeb9bca3 100755 --- a/usp-utils/install_conda_environment.sh +++ b/usp-utils/install_conda_environment.sh @@ -1,4 +1,9 @@ -#get directory where the sourced script is located +#!/usr/bin/env bash +# Run with bash (./install_conda_environment.sh or bash install_conda_environment.sh). +# It uses bash-only features (BASH_SOURCE, [ == ]); running it with `sh`/dash +# fails with "Bad substitution" / "unexpected operator". + +# get directory where this script is located SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) CYAN='\033[0;36m' diff --git a/usp-utils/setup_environment.sh b/usp-utils/setup_environment.sh index d1b6ba20..5cf0aee7 100644 --- a/usp-utils/setup_environment.sh +++ b/usp-utils/setup_environment.sh @@ -29,18 +29,24 @@ fi export PYTHONPATH="$SCRIPT_DIR/libs/py:$PYTHONPATH" echo -e "${INFO} New enviroment variable set: PYTHONPATH=$PYTHONPATH" -conda &> /dev/null -status=$? -if [ ! $status == "0" ]; then - echo -e "${WARNING} Couldn't find 'conda' binary, 'cgfd-usp-mpas' conda environment will not be activated. Python scripts might not work." +if ! command -v conda &> /dev/null; then + echo -e "${WARNING} Couldn't find 'conda', the 'cgfd-usp-mpas' conda environment will not be activated. Python scripts might not work." return 1 fi -conda activate cgfd-usp-mpas &> /dev/null -status=$? -if [ ! $status == "0" ]; then - echo -e "${WARNING} Couldn't find the 'cgfd-usp-mpas' conda environment. It will not be activated and Python scripts might not work. If you wish to install the conda environment please run the script in $SCRIPT_DIR/install_conda_environment.sh" - return 1 +# 'conda activate' needs the 'conda' shell function; load it if only the binary +# is on PATH (e.g. in a shell where 'conda init' hasn't run). +if ! type conda 2> /dev/null | grep -q 'function'; then + __conda_sh="$(conda info --base 2> /dev/null)/etc/profile.d/conda.sh" + [ -f "$__conda_sh" ] && source "$__conda_sh" fi -echo -e "${INFO} Conda enviroment 'cgfd-usp-mpas' activated" +# Decide from the actual list of environments, not from 'activate's exit code +# (the env may exist even if a transient activate call returns non-zero). +if conda env list 2> /dev/null | awk '{print $1}' | grep -qx 'cgfd-usp-mpas'; then + conda activate cgfd-usp-mpas + echo -e "${INFO} Conda enviroment 'cgfd-usp-mpas' activated" +else + echo -e "${WARNING} The 'cgfd-usp-mpas' conda environment was not found. It will not be activated and Python scripts might not work. To install it, run: bash $SCRIPT_DIR/install_conda_environment.sh" + return 1 +fi From 62be7ce74e74880d35508b63431c9fbddc9f6864 Mon Sep 17 00:00:00 2001 From: daniloceano Date: Sat, 20 Jun 2026 17:00:30 +0000 Subject: [PATCH 4/7] pre_proc: real-data + static-field preparation pipelines and docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the pre-processing utilities for MPAS init_atmosphere under usp-utils/pre_proc/. real_data/ — download + intermediate-file generation for real-data init: - download_{era5,gfs,oisst}.py and {era5,gfs,gfs_sst,oisst,oisst_clim}_to_intermediate.py - prepare_{era5,gfs,oisst}.sh end-to-end wrappers - README plus per-scenario recipes (operational/hindcast x global/regional) static_fields/ — static-field generation (init_atmosphere config_init_case = 7): - download_static_data.sh (mesh + MPAS geog bundle, integrity-checked) - STATIC_FIELDS_GUIDE.md walkthrough Co-Authored-By: Claude Opus 4.8 (1M context) --- usp-utils/pre_proc/real_data/README.md | 296 +++++++++++++ usp-utils/pre_proc/real_data/download_era5.py | 166 ++++++++ usp-utils/pre_proc/real_data/download_gfs.py | 391 ++++++++++++++++++ .../pre_proc/real_data/download_oisst.py | 132 ++++++ .../real_data/era5_to_intermediate.py | 176 ++++++++ .../real_data/gfs_sst_to_intermediate.py | 98 +++++ .../pre_proc/real_data/gfs_to_intermediate.py | 128 ++++++ .../real_data/oisst_clim_to_intermediate.py | 126 ++++++ .../real_data/oisst_to_intermediate.py | 115 ++++++ usp-utils/pre_proc/real_data/prepare_era5.sh | 55 +++ usp-utils/pre_proc/real_data/prepare_gfs.sh | 81 ++++ usp-utils/pre_proc/real_data/prepare_oisst.sh | 55 +++ .../pre_proc/real_data/recipes/README.md | 25 ++ .../real_data/recipes/hindcast_global.md | 36 ++ .../real_data/recipes/hindcast_regional.md | 45 ++ .../real_data/recipes/operational_global.md | 41 ++ .../real_data/recipes/operational_regional.md | 41 ++ .../static_fields/STATIC_FIELDS_GUIDE.md | 224 ++++++++++ .../static_fields/download_static_data.sh | 254 ++++++++++++ 19 files changed, 2485 insertions(+) create mode 100644 usp-utils/pre_proc/real_data/README.md create mode 100755 usp-utils/pre_proc/real_data/download_era5.py create mode 100755 usp-utils/pre_proc/real_data/download_gfs.py create mode 100755 usp-utils/pre_proc/real_data/download_oisst.py create mode 100755 usp-utils/pre_proc/real_data/era5_to_intermediate.py create mode 100755 usp-utils/pre_proc/real_data/gfs_sst_to_intermediate.py create mode 100755 usp-utils/pre_proc/real_data/gfs_to_intermediate.py create mode 100755 usp-utils/pre_proc/real_data/oisst_clim_to_intermediate.py create mode 100755 usp-utils/pre_proc/real_data/oisst_to_intermediate.py create mode 100755 usp-utils/pre_proc/real_data/prepare_era5.sh create mode 100755 usp-utils/pre_proc/real_data/prepare_gfs.sh create mode 100755 usp-utils/pre_proc/real_data/prepare_oisst.sh create mode 100644 usp-utils/pre_proc/real_data/recipes/README.md create mode 100644 usp-utils/pre_proc/real_data/recipes/hindcast_global.md create mode 100644 usp-utils/pre_proc/real_data/recipes/hindcast_regional.md create mode 100644 usp-utils/pre_proc/real_data/recipes/operational_global.md create mode 100644 usp-utils/pre_proc/real_data/recipes/operational_regional.md create mode 100644 usp-utils/pre_proc/static_fields/STATIC_FIELDS_GUIDE.md create mode 100755 usp-utils/pre_proc/static_fields/download_static_data.sh diff --git a/usp-utils/pre_proc/real_data/README.md b/usp-utils/pre_proc/real_data/README.md new file mode 100644 index 00000000..7b8781b5 --- /dev/null +++ b/usp-utils/pre_proc/real_data/README.md @@ -0,0 +1,296 @@ +# `real_data/` — GFS / ERA5 → WPS intermediate met files for MPAS + +Scripts to fetch meteorological data and turn it into the **WPS intermediate-format** +file (`GFS:YYYY-MM-DD_HH`) that `init_atmosphere` reads to build the real-data +`init.nc`. MPAS has no built-in ungrib, so this is done in Python with **pywinter** +— no need to build WPS/ungrib. + +``` +download_gfs.py → GFS GRIB2 → gfs_to_intermediate.py ┐ + ├→ GFS:YYYY-MM-DD_HH +download_era5.py → ERA5 GRIB → era5_to_intermediate.py ┘ +``` + +The intermediate file keeps the `GFS:` prefix (= `config_met_prefix='GFS'`) +regardless of the source, so the `init_atmosphere` setup is identical either way. +This directory only produces that **met intermediate file** — running +`init_atmosphere` with it (namelist, streams, linking `*.static.nc`, etc.) is covered +by the model usage tutorial, not repeated here. Run these after the static fields +are ready (see `../static_fields/`). + +## Which source? (data availability) + +Two products feed init_atmosphere: the **atmosphere** (3D state → `GFS:` intermediate, +init case 7) and the **SST/sea-ice** lower boundary (→ `SST:` intermediate, init case 8, +see "SST / sea-ice update"). + +**Atmosphere:** + +| Source | Coverage | Resolution | Notes | +|---|---|---|---| +| GFS `--source aws` (default) | **2021-03-23 → present** | 0.25° | S3 byte-range subset, no rate limit | +| GFS `--source nomads` | **last ~10 days** | 0.25/0.5/1° | grib_filter; can hit `Over Rate Limit` | +| GFS `--source rda` | **2015-01-15 → present** | 0.25° | NCAR RDA ds084.1, full files; may need a free RDA account | +| ERA5 (`download_era5.py`) | **1940 → present** | 0.25° | Copernicus CDS; needs a CDS account | + +**SST / sea-ice:** + +| Source | Coverage | Notes | +|---|---|---| +| GFS (`gfs_sst_to_intermediate.py`, or `--fields sst`) | same as GFS above | from the GFS GRIB; analysis **or** forecast | +| OISST observed (`download_oisst.py`) | **1981-09-01 → present** | NOAA OI SST v2.1 daily 0.25° (NCEI) | +| OISST climatology (`oisst_clim_to_intermediate.py`) | **any date** (LTM 1991-2020) | day-of-year climatology via OPeNDAP; for forecasts / typical-month runs | + +### GFS is a *forecast* model — analysis vs forecast +GFS runs four times a day, at **00, 06, 12 and 18 UTC**. Each of those runs is a +**cycle**: `--cycle 00` selects the 00 UTC run of `--date`. Within a cycle, `--fhour` +is the **forecast hour** (lead time, in hours after the cycle time). + +A cycle produces an **analysis** at forecast hour 0 (`--fhour 0`, the default) and +**forecasts** at later hours (`--fhour 3, 6, … up to 384`, i.e. 16 days). So: +- **Analysis** (`--fhour 0`) = best estimate of the atmosphere *at the cycle time*. Use it + for the initial conditions. Only exists for the past (and the current cycle). +- **Forecast** (`--fhour > 0`) = a prediction for times *after* the cycle, i.e. it can give + data for the **future** (times that have not happened yet) — that is what makes an + operational MPAS forecast possible. + +By contrast **ERA5 (reanalysis) and OISST observed only exist for the past.** For the +future (or any date with no observed SST) use the **OISST climatology**. + +There is **no GFS 0.25° before 2015-01-15** — for anything older, use ERA5 (atmosphere) +and OISST (SST). `download_gfs.py` fails fast with the exact limit for out-of-range dates. + +## Scripts + +| Script | What it does | +|--------|--------------| +| `download_gfs.py` | Downloads the GFS fields/levels MPAS needs, with a `*.provenance.json`. Sources `aws` / `nomads` / `rda` (see table above). `--fields sst` for a small surface-only (SST update) download. | +| `gfs_to_intermediate.py` | Converts one GFS GRIB2 file to a WPS intermediate file via pywinter. Flips latitude to S→N, writes pressure in **Pa**, prints the level count for `config_nfglevels`. | +| `download_era5.py` | Downloads ERA5 pressure- and single-level GRIB via the CDS API (cdsapi), with provenance. For dates GFS can't reach. | +| `era5_to_intermediate.py` | ERA5 counterpart of the GFS converter (same intermediate field names; handles geopotential→height and snow units). | +| `download_oisst.py` | Downloads NOAA OISST v2.1 daily 0.25° SST/sea-ice NetCDF (NCEI) over a date range. For SST update files (scientific pipeline). | +| `oisst_to_intermediate.py` | Converts OISST daily files to `SST:` intermediate files (SST→K, SEAICE, LANDSEA) — one per day. | +| `oisst_clim_to_intermediate.py` | Builds `SST:` files from the OISST daily **climatology** (LTM 1991-2020, via OPeNDAP) for target dates with no observed SST (forecasts / typical-month runs). | +| `gfs_sst_to_intermediate.py` | Extracts SST/SEAICE/LANDSEA from a GFS GRIB into an `SST:` intermediate file (operational SST update). | + +One-shot wrappers (download + convert in one call, inside the conda env): + +| Wrapper | Covers | +|---------|--------| +| `prepare_gfs.sh` | GFS atmosphere and/or SST — `--product atm` (default), `sst`, or `both` (one download, both products). | +| `prepare_era5.sh` | ERA5 atmosphere (download pl+sl → `GFS:` intermediate). | +| `prepare_oisst.sh` | OISST SST over a date range — observed (default) or `--climatology` (→ daily `SST:` intermediates). | + +## Dependencies + +The `cgfd-usp-mpas` conda env: `cfgrib` + `eccodes` (conda-forge) and `pywinter` +(pip); plus `cdsapi` (pip) for the ERA5 path. Run with +`conda run -n cgfd-usp-mpas python