5. Choosing model settings¶
What this shows: how SCHISM's settings in param.nml are organised and set with
rompy-schism, which ones matter first, and what rompy-schism fills in or checks for
you.
Prerequisites: Tutorial 4: Atmospheric forcing.
You will learn:
- how
param.nmlis organised and howParamsets it - the settings to decide first: barotropic or baroclinic, time step, ramps, friction, wetting and drying
- which settings rompy-schism sets from the rest of the configuration
- how SCHISM versions differ, and how
schema_versionhandles it - how output and scribes fit together, and why a run must be checked
Data used: hgrid.gr3 and tides/.
Setup¶
import shutil
import subprocess
from pathlib import Path
from pydantic import ValidationError
from rompy.backends import DockerConfig
from rompy.core.data import DataBlob
from rompy.core.time import TimeRange
from rompy.logging import config as logging_config
from rompy.model import ModelRun
from rompy_schism.boundary_core import TidalDataset
from rompy_schism.config import SCHISMConfig
from rompy_schism.data import (
BoundarySetupWithSource,
SCHISMData,
SCHISMDataBoundaryConditions,
)
from rompy_schism.grid import SCHISMGrid
from rompy_schism.namelists import NML, Param
logging_config.update(level="ERROR")
DATA_DIR = Path("../data")
OUT_DIR = Path("_output") / "05_model_settings"
shutil.rmtree(OUT_DIR, ignore_errors=True)
SCHISM_IMAGE = "ghcr.io/rom-py/schism:5.13.0"
1. param.nml¶
SCHISM reads its settings from param.nml, a Fortran namelist with three groups:
CORE: settings without defaults, which every run needs: the time stepdt, the run lengthrnday, output intervals, barotropic or baroclinic (ibc).OPT: optional settings with defaults: start date, ramps, friction, numerics, atmospheric forcing, modules.SCHOUT: output: which variables, how often, hotstart files, stations.
Param has one field per group, with SCHISM's own parameter names. Give only what
differs from the defaults, as a dictionary or as the Core, Opt and Schout
models. Array entries such as iof_hydro(1) are written iof_hydro__1.
The SCHISM manual
describes every parameter.
param = Param(core={"dt": 120.0}, opt={"dramp": 1.0}, schout={"iof_hydro__16": 1})
print(param.render()[:600])
! SCHISM rompy_schism.namelists.param namelist rendered from Rompy &core ipre = 0 ibc = 0 ibtp = 1 rnday = 30.0 dt = 120.0 msc2 = 24 mdc2 = 24 ntracer_gen = 2 ntracer_age = 4 sed_class = 5 eco_class = 27 nspool = 36 ihfskip = 864 nbins_veg_vert = 2 / &opt ipre2 = 0 itransport_only = 0 iloadtide = 0 loadtide_coef = 0.1 start_year = 2000 start_month = 1 start_day = 1 start_hour = 0 utc_start = 0 ics = 2 ihot = 0 ieos_type = 0 ieos_pres = 0 eos_a = -0.1 eos_b = 1001.0 dramp = 1.0 drampbc = 0.0 iupwind_mom = 0 indvel = 0 ihorcon = 0 hvis_coef0 = 0.025 ishapiro = 1 niter_shap = 1 shapiro0 = 0.5 t
A misspelt name is an error, not a setting silently ignored:
try:
Param(opt={"dramps": 2.0})
except ValidationError as err:
print(str(err).splitlines()[0], "...", str(err).splitlines()[2].strip())
1 validation error for Param ... Extra inputs are not permitted [type=extra_forbidden, input_value=2.0, input_type=float]
2. The settings to decide first¶
| Setting | Default | What it does |
|---|---|---|
core.ibc, core.ibtp |
0, 1 | ibc=0 is baroclinic: temperature and salinity are computed and drive density currents; they need initial and boundary values. ibc=1, ibtp=0 is barotropic, driven by tides, wind and pressure only |
core.dt |
100 s | Time step. SCHISM is semi-implicit: 100–400 s for barotropic runs, with a Courant number above about 0.4 (Tutorial 2) |
opt.dramp, opt.drampwind |
1 day | Ramps for the boundary forcing and the wind: the model starts from rest and the forcing grows smoothly |
opt.h0 |
0.01 m | Minimum depth for wetting and drying |
opt.nchi |
from the grid | Which friction file SCHISM reads: set by rompy-schism from SCHISMGrid (drag, manning or rough) |
opt.thetai |
0.6 | Implicitness, 0.5 to 1; 1 is the most stable, for strong wetting and drying |
opt.indvel, opt.ihorcon, opt.ishapiro |
0, 0, 1 | Momentum stabilisation: the defaults suit most coastal models |
opt.itur |
3 | Vertical turbulence closure, for 3D models |
rompy-schism's default is a baroclinic model, like SCHISM's sample param.nml. A
baroclinic model needs initial temperature and salinity, from temp.ic and
salt.ic, ts.ic or a hotstart file (Baroclinic 3D model).
The models in these tutorials are driven by tides and wind alone, so they set
ibc=1, ibtp=0.
The runs below use two helpers: docker_available, and run_completed, which reads
SCHISM's log. SCHISM exits with success even when it stops on an error, so the
exit code, and rompy's run() which relies on it, cannot tell whether a run
finished. Always check outputs/mirror.out and outputs/fatal.error.
def docker_available() -> bool:
"""Return True if the Docker daemon can be reached."""
try:
return subprocess.run(["docker", "info"], capture_output=True).returncode == 0
except FileNotFoundError:
return False
def run_completed(workspace: Path) -> bool:
"""Check SCHISM's log: SCHISM exits with success even when it stops on an error."""
mirror = workspace / "outputs" / "mirror.out"
if mirror.exists() and "Run completed successfully" in mirror.read_text():
return True
fatal = workspace / "outputs" / "fatal.error"
print(fatal.read_text().strip() if fatal.exists() else "SCHISM did not finish")
return False
grid = SCHISMGrid(hgrid=DataBlob(source=DATA_DIR / "hgrid.gr3"), drag=0.0025)
tides = TidalDataset(
tidal_database=DATA_DIR / "tides",
tidal_model="TPXO9-perth",
constituents=["M2", "S2", "K1", "O1"],
)
data = SCHISMData(
boundary_conditions=SCHISMDataBoundaryConditions(
tidal_data=tides,
default_boundary=BoundarySetupWithSource(elev_type=3, vel_type=3),
)
)
period = TimeRange(start="2023-01-01T00:00", end="2023-01-01T06:00", interval="1h")
backend = DockerConfig(image=SCHISM_IMAGE, executable="schism 2", mpiexec="mpirun", cpu=6)
core = {"ibc": 1, "ibtp": 0, "dt": 120.0, "nspool": 30, "ihfskip": 180}
output = {"iof_hydro__1": 1, "iof_hydro__26": 0}
def run(run_id: str, config: SCHISMConfig, backend: DockerConfig = backend) -> bool:
"""Generate and run a model, returning whether SCHISM completed."""
modelrun = ModelRun(run_id=run_id, period=period, output_dir=OUT_DIR, config=config)
workspace = Path(modelrun())
if not docker_available():
print("Docker is not available, skipping the model run")
return False
returned = modelrun.run(backend, workspace_dir=workspace)
completed = run_completed(workspace)
print(f"rompy's run() returned {returned}; SCHISM completed: {completed}")
return completed
# ## 3. What rompy-schism sets for you
#
# Some settings follow from the rest of the configuration, so rompy-schism fills them in
# when you leave them unset, and raises an error when you set a value that contradicts
# the rest:
#
# | Setting | Set from |
# |---|---|
# | `core.rnday`, `opt.start_year` … `start_hour` | the run period |
# | `opt.nws` | atmospheric data: 2 with sflux |
# | `opt.nchi` | the grid's friction field |
# | `opt.ihot` | a hotstart file written by the boundary conditions: 1 |
#
# Friction with Manning's n, for instance:
manning = SCHISMConfig(
grid=SCHISMGrid(hgrid=DataBlob(source=DATA_DIR / "hgrid.gr3"), manning=0.025),
data=data,
nml=NML(param=Param(core=core, schout=output)),
)
print(f"nchi = {manning.nml.param.opt.nchi}")
try:
SCHISMConfig(
grid=manning.grid, data=data, nml=NML(param=Param(core=core, opt={"nchi": 0}))
)
except ValidationError as err:
print(str(err).splitlines()[1].strip())
nchi = -1
Value error, nml.param.opt.nchi=0 makes SCHISM read a different friction file from the grid's manning.gr3: set nchi to -1 or leave it unset [type=value_error, input_value={'grid': SCHISMGrid([115....ne=None, wwminput=None)}, input_type=dict]
4. SCHISM versions¶
SCHISM stops on a param.nml name it does not know, and names change between
versions. schema_version on SCHISMConfig selects the parameters of a version:
schism-v5.13 (the default, and the version in the Docker image) or schism-v5.14,
which adds nmarsh_types and removes isconsv.
for version in ["schism-v5.13", "schism-v5.14"]:
config = SCHISMConfig(grid=grid, schema_version=version, nml=NML(param=Param()))
rendered = config.nml.param.render(config.schema_version)
found = [name for name in ("nmarsh_types", "isconsv") if f"{name} =" in rendered]
print(f"{version}: {', '.join(found)}")
schism-v5.13: isconsv schism-v5.14: nmarsh_types
5. Output and scribes¶
schout.iof_hydro(i) switches each output variable on (1) or off (0): for example 1
is the water level, 14 the wind, 16 the depth-averaged velocity, 26 the 3D velocity.
nspool sets how often SCHISM writes, in time steps, and ihfskip how many steps go
in each output file.
Output is written by scribes, processes that do only that. SCHISM needs one for all
2D variables, plus one per 3D variable (two for vectors), and the vertical
coordinates always count as one. The number of scribes is the argument of schism
(schism 2), and mpirun counts them among its processes. With the 3D velocity on,
the default, this model needs 4:
with_3d_velocity = SCHISMConfig(
grid=grid, data=data, nml=NML(param=Param(core=core, schout={"iof_hydro__1": 1}))
)
completed = run("too_few_scribes", with_3d_velocity)
1: ABORT: INIT: Too few scribes (2). Please specify atleast equal to number of output variables (4) rompy's run() returned True; SCHISM completed: False
SCHISM stopped at start-up, while rompy's run() returned True: only the log shows
it. With 4 scribes among 8 processes, it runs:
completed = run(
"four_scribes",
with_3d_velocity,
DockerConfig(image=SCHISM_IMAGE, executable="schism 4", mpiexec="mpirun", cpu=8),
)
rompy's run() returned True; SCHISM completed: True
Summary¶
Param(core=..., opt=..., schout=...)setsparam.nml, with SCHISM's names; misspelt names are errors.- rompy-schism defaults to a baroclinic model: set
ibc=1, ibtp=0for tides and wind alone. - The run period, atmospheric data, grid friction and hotstart set
rnday, the start date,nws,nchiandihot. schema_versionmust match the SCHISM version you run.- Give SCHISM enough scribes for its output, and check
mirror.outandfatal.errorafter every run.
Next: 6. A tide and wind hindcast.