Usage
Quick start: PrepOrchestrator
The simplest way to run a full SCHISM prep cycle is via
PrepOrchestrator. It chains every processor
in the correct order and runs the lightweight ones (GFS, HRRR, NWM, tidal)
concurrently via a ThreadPoolExecutor; the heavy RTOFS and nudging steps
run sequentially in Phase 2 because the netCDF4 C library can stall under
concurrent thread access on large reads.
from nos_utils.config import ForcingConfig
from nos_utils.orchestrator import PrepOrchestrator
config = ForcingConfig.for_secofs(pdy="20260406", cyc=0)
paths = {
"gfs": "/lfs/h1/ops/prod/com/gfs/v16.3",
"hrrr": "/lfs/h1/ops/prod/com/hrrr/v4.1",
"rtofs": "/lfs/h1/ops/prod/com/rtofs/v2.5",
"nwm": "/lfs/h1/ops/prod/com/nwm/v3.0",
"fix": "/path/to/fix/secofs",
"output": "/tmp/secofs_prep",
"comout": "/tmp/secofs_comout",
}
orch = PrepOrchestrator(config, paths, run_name="secofs")
result = orch.run(phase="nowcast")
print(result.summary())
PrepResult exposes success (all critical steps passed),
results (one ForcingResult per step),
all_output_files, all_errors, and elapsed_seconds.
Switching OFS
Running STOFS-3D-ATL is a one-line config change; the orchestrator
auto-detects it from run_name or from the presence of RTOFS ROI indices
in the config:
config = ForcingConfig.for_stofs_3d_atl(pdy="20260406", cyc=0)
orch = PrepOrchestrator(config, paths, run_name="stofs_3d_atl")
The STOFS factory pre-configures GFS 0.25 deg resolution, a separate HRRR
domain, RTOFS ROI indices for both OBC and nudging, ADT satellite SSH
blending, interior T/S nudging, and the NWM medium_range_mem1 product
with 121 target files.
Configuration
ForcingConfig is a dataclass that defines the
domain, cycle, time windows, and per-processor options. Factory methods
create preconfigured instances:
from nos_utils.config import ForcingConfig
# SE Coastal OFS (6h nowcast / 48h forecast, GFS 0.5 deg, GFS+HRRR blend)
config = ForcingConfig.for_secofs(pdy="20260406", cyc=0)
# SECOFS with UFS-Coastal DATM coupling (nws=4)
config = ForcingConfig.for_secofs_ufs(pdy="20260406", cyc=0)
# STOFS-3D-ATL (24h nowcast / 108h forecast, ADT blending, nudging)
config = ForcingConfig.for_stofs_3d_atl(pdy="20260406", cyc=0)
# STOFS-3D-ATL + UFS-Coastal DATM
config = ForcingConfig.for_stofs_3d_atl_ufs(pdy="20260406", cyc=0)
# Ensemble member (GEFS-driven; member 0 = control, met_num=2)
config = ForcingConfig.for_ensemble(pdy="20260406", cyc=0, member=3)
# Load from YAML (requires pyyaml)
config = ForcingConfig.from_yaml("/path/to/secofs.yaml",
pdy="20260406", cyc=0)
Individual processors
Each processor follows a consistent pattern:
from nos_utils.forcing import GFSProcessor
gfs = GFSProcessor(
config=config,
input_path="/lfs/h1/ops/prod/com/gfs/v16.3",
output_path="/work/secofs/sflux",
resolution="0p50", # SECOFS uses 0.50 deg; STOFS uses 0.25
phase="nowcast",
time_hotstart=datetime(2026, 4, 5, 18, 0),
)
result = gfs.process()
print(result.success) # bool
print(result.output_files) # list[Path]
print(result.errors) # list[str]
print(result.metadata) # dict (grid_shape, num_timesteps, ...)
Available processors (imported from nos_utils.forcing):
Processor |
Responsibility |
|---|---|
|
GFS GRIB2 -> |
|
HRRR GRIB2 -> |
|
GEFS ensemble GRIB2 -> sflux per member |
|
RTOFS NetCDF -> |
|
NWM channel_rt -> |
|
USGS climatology fallback for rivers |
|
bctides.in generation (calls Fortran |
|
|
|
|
|
Locate and validate restart file; extract |
|
|
|
ESMF mesh for UFS-Coastal DATM coupling |
|
HRRR+GFS Delaunay blending -> |
|
SCHISM sflux NetCDF writer ( |
|
UFS-Coastal DATM NetCDF writer ( |
RTOFS with precomputed weights and ADT blending
RTOFSProcessor will automatically pick up
precomputed INTERP_REMESH weights from the FIX directory if they are
present, falling back to on-the-fly Delaunay otherwise. The weights give
Fortran-equivalent results (SSH RMSE = 0.5 mm vs the operational Fortran).
from nos_utils.forcing import RTOFSProcessor
rtofs = RTOFSProcessor(
config=config,
input_path="/data/rtofs",
output_path="/work/secofs/obc",
fix_path="/path/to/fix/secofs",
)
result = rtofs.process()
# ADT blending runs automatically when config.adt_enabled is True
# and the ADT data + fix weights are available.
NWM river forcing
from nos_utils.forcing import NWMProcessor
nwm = NWMProcessor(
config=config,
input_path="/data/nwm",
output_path="/work/secofs",
phase="nowcast",
)
result = nwm.process()
For the STOFS domain this assembles 121 medium_range_mem1 files into
vsource.th / msource.th / source_sink.in. SECOFS has no matching
NWM reaches and uses RiverClimProcessor (USGS
daily climatology) instead.
Interior nudging
from nos_utils.forcing import NudgingProcessor
nudge = NudgingProcessor(
config=config,
input_path="/data/rtofs",
output_path="/work/secofs",
fix_path="/path/to/fix/secofs",
)
result = nudge.process()
The nudging processor reads the ~32K interior-node weight file
(secofs.obc_nudge_weights.npz) and writes TEM_nu.nc and
SAL_nu.nc with a configurable relaxation timescale
(config.nudging_timescale_seconds, default 86400 s).
NCO / ecFlow integration
On WCOSS2, nos_utils.nco_bridge.run_prep() is the canonical entrypoint
called from a J-job. It reads the standard NCO environment variables and
drives the orchestrator:
# In the J-job
export PYTHONPATH=$HOMEnos/ush/python/nos-utils:$PYTHONPATH
python3 -c "from nos_utils.nco_bridge import run_prep; \
import sys; sys.exit(0 if run_prep(phase='nowcast') else 1)"
Environment variables read by the bridge:
Variable |
Purpose |
|---|---|
|
Production date (YYYYMMDD) – required |
|
Cycle hour (0, 6, 12, 18) – required |
|
OFS name ( |
|
Optional path to a YAML config file |
|
Set to |
|
GFS input root |
|
HRRR input root |
|
RTOFS input root |
|
NWM input root |
|
FIX directory (grid, templates, |
|
Working directory |
|
Output archive directory |
|
Previous cycle output (for hotstart search) |
|
Explicit hotstart search override |
CLI
nos-utils is also installed as a console script:
# SECOFS prep (nowcast)
nos-utils prep --ofs secofs --pdy 20260406 --cyc 0 \
--gfs /data/gfs --hrrr /data/hrrr --rtofs /data/rtofs \
--nwm /data/nwm --fix /data/fix/secofs --output /work/prep/
# STOFS-3D-ATL prep (nowcast)
nos-utils prep --ofs stofs_3d_atl --pdy 20260406 --cyc 0 \
--gfs /data/gfs --hrrr /data/hrrr --rtofs /data/rtofs \
--nwm /data/nwm --fix /data/fix/stofs_3d_atl --output /work/prep/
# UFS-Coastal mode (nws=4)
nos-utils prep --ofs stofs_3d_atl --ufs --pdy 20260406 --cyc 0 \
--fix /data/fix/stofs_3d_atl --output /work/prep/
# Custom YAML config
nos-utils prep --yaml /path/to/myofs.yaml --pdy 20260406 --cyc 0 \
--fix /data/fix --output /work/prep/
# List available OFS factories and processors
nos-utils list
# Show help
nos-utils --help