Wave boundaries from existing files, and reuse¶
What this shows: how to use XBeach wave boundary files made elsewhere, and how to reuse the boundary time series XBeach generated in a previous run.
Prerequisites: Waves from spectra.
You will learn:
- how
BoundaryFileJons,BoundaryFileJonstableandBoundaryFileSwancopy existing files into the workspace - how to point to a file list of time-varying boundary files
- how
BoundaryReusemakes a second run use exactly the same wave forcing as the first
Data used: bathy.tif and ww3-spectra-20230101-short.nc, used to make the
"existing" files. The reuse section runs XBeach with Docker.
Setup¶
import shutil
import subprocess
from pathlib import Path
from rompy.core.time import TimeRange
from rompy.logging import config as logging_config
from rompy_xbeach.grid import RegularGrid
from rompy_xbeach.source import SourceCRSWavespectra
from rompy_xbeach.types import XBeachDataBlob
logging_config.update(level="WARNING") # rompy logs every step, show warnings only
DATA_DIR = Path("../data")
OUT_DIR = Path("_output") / "waves_from_files_and_reuse"
shutil.rmtree(OUT_DIR, ignore_errors=True)
grid = RegularGrid(
ori={"x": 115.594239, "y": -32.641104, "crs": 4326},
alfa=347.0,
dx=20.0,
dy=30.0,
nx=115,
ny=110,
crs=28350,
)
period = TimeRange(start="2023-01-01T00", end="2023-01-01T12", interval="1h")
DIRECTIONS = {"thetamin": -90.0, "thetamax": 90.0, "dtheta_s": 10.0}
To keep this example self-contained, the "existing" boundary files are created first, in a folder that stands in for files you received from elsewhere.
from rompy_xbeach.data.boundary import (
BoundaryStationSpectraJons,
BoundaryStationSpectraJonstable,
BoundaryStationSpectraSwan,
)
existing = OUT_DIR / "existing"
existing.mkdir(parents=True)
spectra = SourceCRSWavespectra(
uri=DATA_DIR / "ww3-spectra-20230101-short.nc", reader="read_ww3"
)
for boundary in [
BoundaryStationSpectraJons(source=spectra, location="offshore", filelist=True),
BoundaryStationSpectraJonstable(source=spectra, location="offshore"),
BoundaryStationSpectraSwan(source=spectra, location="offshore"),
]:
boundary.get(destdir=existing, grid=grid, time=period)
sorted(p.name for p in existing.iterdir())
['jonstable-20230101T000000-20230101T120000.txt', 'parametric-20230101T000000.txt', 'parametric-20230101T030000.txt', 'parametric-20230101T060000.txt', 'parametric-20230101T090000.txt', 'parametric-filelist.txt', 'swan-20230101T000000.txt']
1. Existing boundary files¶
The BoundaryFile... classes take a file through XBeachDataBlob, copy it into the
workspace and write the matching wbctype and bcfile. The file can be a local path
or a remote URI.
def show(boundary, name):
"""Write a boundary to its own folder and print its parameters and files."""
destdir = OUT_DIR / name
params = boundary.get(destdir=destdir, grid=grid, time=period)
for key, value in params.items():
print(f"{key} = {value}")
print("files:", sorted(p.name for p in destdir.iterdir()))
from rompy_xbeach.data.boundary import BoundaryFileJons
show(
BoundaryFileJons(
bcfile_source=XBeachDataBlob(source=existing / "parametric-20230101T000000.txt")
),
"file_jons",
)
wbctype = parametric bcfile = parametric-20230101T000000.txt dtbc = 1.0 files: ['parametric-20230101T000000.txt']
For time-varying JONSWAP or SWAN boundaries, point to the file list and set
filelist=True. The files named in the list are copied too.
show(
BoundaryFileJons(
bcfile_source=XBeachDataBlob(source=existing / "parametric-filelist.txt"),
filelist=True,
),
"file_jons_filelist",
)
wbctype = parametric bcfile = parametric-filelist.txt dtbc = 1.0 files: ['parametric-20230101T000000.txt', 'parametric-20230101T030000.txt', 'parametric-20230101T060000.txt', 'parametric-20230101T090000.txt', 'parametric-filelist.txt']
JONSWAP tables and SWAN spectra work the same way.
from rompy_xbeach.data.boundary import BoundaryFileJonstable, BoundaryFileSwan
jonstable_file = next(existing.glob("jonstable-*.txt"))
show(
BoundaryFileJonstable(bcfile_source=XBeachDataBlob(source=jonstable_file)),
"file_jonstable",
)
wbctype = jonstable bcfile = jonstable-20230101T000000-20230101T120000.txt dtbc = 1.0 files: ['jonstable-20230101T000000-20230101T120000.txt']
show(
BoundaryFileSwan(
bcfile_source=XBeachDataBlob(source=existing / "swan-20230101T000000.txt")
),
"file_swan",
)
wbctype = swan bcfile = swan-20230101T000000.txt dtbc = 1.0 files: ['swan-20230101T000000.txt']
2. Reusing boundaries from a previous run¶
XBeach turns a spectral boundary into wave energy and long-wave time series before it
starts, and saves them as .bcf files. BoundaryReuse copies these files from a
previous run, so a second run gets identical wave forcing, for example when
comparing settings.
This section runs XBeach with the public Docker image and is skipped if Docker is not available.
from rompy.backends import DockerConfig
from rompy.model import ModelRun
from rompy_xbeach.components.boundary.parameters import TideBoundaryConditions
from rompy_xbeach.components.output import Output
from rompy_xbeach.components.physics import Physics
from rompy_xbeach.components.physics.wavemodel import Surfbeat
from rompy_xbeach.config import Config, DataInterface
from rompy_xbeach.data.bathy import SeawardExtensionLinear, XBeachBathy
from rompy_xbeach.source import SourceGeotiff
bathy = XBeachBathy(
source=SourceGeotiff(filename=DATA_DIR / "bathy.tif"),
posdwn=False,
extension=SeawardExtensionLinear(depth=15.0, slope=0.05),
)
short_run = TimeRange(start="2023-01-01T00", end="2023-01-01T00:10", interval="10m")
backend = DockerConfig(image="ghcr.io/rom-py/xbeach:trunk-r6147", executable="xbeach")
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_model(run_id, wave):
"""Generate and run a short surfbeat model with the given wave boundary."""
config = Config(
grid=grid,
bathy=bathy,
input=DataInterface(wave=wave),
physics=Physics(wavemodel=Surfbeat()),
tide_boundary=TideBoundaryConditions(tideloc=0, zs0=0.0),
output=Output(globalvars=["H"], tintg=300.0),
)
modelrun = ModelRun(
run_id=run_id, period=short_run, output_dir=OUT_DIR, config=config
)
workspace = Path(modelrun())
ok = modelrun.run(backend, workspace_dir=workspace)
print(f"{run_id}: {'finished' if ok else 'failed'}")
return workspace
The first run builds its boundary from spectra and saves the .bcf series.
if docker_available():
first = run_model(
"first_run",
BoundaryStationSpectraJons(source=spectra, location="offshore", **DIRECTIONS),
)
print("Boundary series written:", sorted(p.name for p in first.glob("*.bcf")))
else:
first = None
print("Docker is not available, skipping the runs")
first_run: finished Boundary series written: ['E_reuse.bcf', 'Es_reuse.bcf', 'ebcflist.bcf', 'esbcflist.bcf', 'q_reuse.bcf', 'qbcflist.bcf']
The second run reuses them. BoundaryReuse copies the list files (ebcflist.bcf,
qbcflist.bcf and, for single-direction runs, esbcflist.bcf) and the series files
they reference.
from rompy_xbeach.data.boundary import BoundaryReuse
from rompy_xbeach.types import XBeachDirectoryBlob
if first is not None:
second = run_model(
"second_run",
BoundaryReuse(
previous_run=XBeachDirectoryBlob(source=str(first)), **DIRECTIONS
),
)
params_txt = (second / "params.txt").read_text()
print([line for line in params_txt.splitlines() if line.startswith("wbctype")])
second_run: finished ['wbctype = reuse']
Summary¶
| Class | Input | Use for |
|---|---|---|
BoundaryFileJons |
JONSWAP file or file list | Boundaries prepared by other tools |
BoundaryFileJonstable |
JONSWAP table | Boundaries prepared by other tools |
BoundaryFileSwan |
SWAN spectrum or file list | Nesting in a SWAN model |
BoundaryReuse |
Folder of a previous XBeach run | Identical forcing across runs |
See also: Hotstart and chained runs combines reuse with a hotstart to continue a simulation.