Rompy-XBeach Architecture¶
This document describes the architecture of rompy-xbeach, explaining how XBeach model parameters are organised into Python components and why this structure was chosen.
Overview¶
Rompy-xbeach is a Python interface for the XBeach coastal morphodynamic model. It provides:
- Type-safe configuration — Pydantic models validate parameters before running XBeach
- Data interfaces — Automatic generation of boundary condition files from various data sources
- Structured organisation — Related parameters grouped into logical components
The core challenge is that XBeach uses a flat params.txt file with ~250 key-value pairs, while Python benefits from hierarchical organisation. Rompy-xbeach bridges this gap by providing structured input that serialises to the flat format XBeach expects.
The Config Class¶
The Config class is the main entry point. It orchestrates all XBeach parameters and generates the params.txt file.
from rompy_xbeach.config import Config
from rompy_xbeach.grid import RegularGrid
from rompy_xbeach.data.bathy import XBeachBathy
from rompy_xbeach.components.physics import Physics
from rompy_xbeach.components.physics.wavemodel import Surfbeat
from rompy_xbeach.components.physics.friction import Cf
from rompy_xbeach.components.sediment import Sediment
from rompy_xbeach.components.sediment.morphology import Morphology
from rompy_xbeach.components.output import Output
config = Config(
grid=RegularGrid(...),
bathy=XBeachBathy(...),
physics=Physics(
wavemodel=Surfbeat(),
bedfriction=Cf(bedfriccoef=0.01),
),
sediment=Sediment(
morphology=Morphology(morfac=10),
),
output=Output(outputformat="netcdf"),
)
When called, Config serialises all components to a flat dictionary that becomes params.txt:
Component Hierarchy¶
Parameters are organised into components based on their function:
Config
├── grid # Grid definition (generates x, y files)
├── bathy # Bathymetry (generates depth file)
├── input # Data-driven boundary conditions
│ ├── wave # Wave forcing (generates boundary files)
│ ├── tide # Tide forcing (generates zs0file)
│ └── wind # Wind forcing (generates wind file)
├── physics # Physical process parameters
│ ├── wavemodel # Wave model selection and parameters
│ ├── bedfriction # Bed friction formulation
│ ├── viscosity # Horizontal viscosity
│ ├── vegetation # Vegetation effects
│ ├── wind # Wind drag parameters
│ ├── wave_numerics # Wave solver numerics
│ ├── flow_numerics # Flow solver numerics
│ └── constants # Physical constants
├── sediment # Sediment transport and morphology
│ ├── transport # Transport formulation
│ ├── morphology # Morphological acceleration
│ ├── bed_update # Bed level update controls
│ ├── bed_composition # Grain sizes, porosity
│ └── groundwater # Groundwater flow
├── output # Output configuration
├── flow_boundary # Flow boundary conditions
├── tide_boundary # Tide/surge boundary parameters
├── wave_boundary # Manual wave boundary specification
├── hotstart # Hotstart initialisation
└── mpi # MPI parallelisation
Why Components?¶
1. Maintainability¶
With ~250 parameters, a single flat class would be unwieldy. Components group related parameters, making the codebase easier to navigate and maintain.
2. Validation¶
Components enable context-aware validation. For example:
Morphologycan validate thatmorfac > 0BedCompositioncan validate thatD90 > D50Nonhwavemodel can warn ifswave=True(incompatible)
3. Discoverability¶
IDE autocomplete guides users through the hierarchy:
config.sediment. # Shows: transport, morphology, bed_update, ...
config.sediment.morphology. # Shows: morfac, morstart, morstop, ...
4. Mutual Exclusivity¶
Some XBeach parameters only apply to certain modes. Components enforce this:
# Surfbeat-specific parameters are only available on Surfbeat
physics=Physics(wavemodel=Surfbeat(breaktype=Roelvink1()))
# Non-hydrostatic parameters are only available on Nonh
physics=Physics(wavemodel=Nonh(nhbreaker=1, solver="tridiag"))
Attempting to set nhbreaker on Surfbeat is a type error — the parameter doesn't exist on that class.
Key Patterns¶
Process Switches: Union[bool, Component]¶
Many XBeach features can be enabled with defaults or customised:
# Enable with XBeach defaults
physics=Physics(vegetation=True)
# Enable with custom parameters
from rompy_xbeach.components.physics.vegetation import Vegetation
physics=Physics(vegetation=Vegetation(nsec=2, ah=[1.5], Cd=[1.0]))
# Disable explicitly
physics=Physics(vegetation=False)
# Omit entirely (uses XBeach default, typically disabled)
physics=Physics()
This pattern applies to: vegetation, viscosity, wind, bedfriction, ships, hotstart.
Discriminated Unions for Wave Models¶
The wavemodel field uses Pydantic's discriminated unions:
Each variant has different parameters:
| Wavemodel | Key Parameters |
|---|---|
Stationary |
breaktype (Baldock, Janssen) |
Surfbeat |
breaktype (Roelvink variants), single_dir, wci |
Nonh |
nhbreaker, solver, kdmin, Topt |
Breaker Formulations¶
Breaker types are further specialised:
# Roelvink (1993) - only for Surfbeat
Surfbeat(breaktype=Roelvink1(gamma=0.55, alpha=1.0))
# Roelvink-Daly (2012) - only for Surfbeat
Surfbeat(breaktype=RoelvinkDaly(gamma=0.55))
# Baldock (1998) - only for Stationary
Stationary(breaktype=Baldock(gamma=0.78, alpha=1.0))
This prevents invalid combinations that XBeach would reject at runtime.
Data Interfaces vs Parameter Components¶
There's an important distinction:
Data interfaces (input.wave, input.tide, input.wind) fetch external data, generate files, and produce parameters:
input=Input(
wave=BoundaryJonstable(
source=SourceDataset(...), # External data source
hm0_var="hs", # Variable mapping
tp_var="tp",
)
)
# Generates: jonswap.txt, bcfile entries, and wbctype/dtbc/etc parameters
Parameter components (wave_boundary, flow_boundary) specify parameters directly:
wave_boundary=SpectralWaveBoundary(
wbctype="jonstable",
bcfile="jonswap.txt", # Pre-existing file
dtbc=1.0,
)
Use data interfaces when generating boundary conditions from data sources. Use parameter components when working with pre-existing boundary files.
Finding Parameters¶
By Component¶
If you know the category, navigate the hierarchy:
# Morphology parameters
config.sediment.morphology.morfac
config.sediment.morphology.morstart
# Wave numerics
config.physics.wave_numerics.scheme
config.physics.wave_numerics.wavint
# Output settings
config.output.outputformat
config.output.tintg
By XBeach Name¶
Most field names match XBeach parameter names exactly:
| XBeach Parameter | Rompy Field |
|---|---|
morfac |
sediment.morphology.morfac |
D50 |
sediment.bed_composition.D50 |
bedfriccoef |
physics.bedfriction.bedfriccoef |
front |
flow_boundary.front |
tideloc |
tide_boundary.tideloc |
hotstart |
hotstart (bool) or hotstart.hotstart (component) |
Exceptions exist where a field controls internal behaviour rather than an XBeach parameter (e.g., source fields on data blobs).
Serialisation¶
Components implement a get(destdir) method that returns a dictionary of XBeach parameters:
morphology = Morphology(morfac=10, morstart=3600)
params = morphology.get("/path/to/run")
# Returns: {"morfac": 10, "morstart": 3600}
The Config.__call__() method aggregates all component outputs:
config = Config(...)
params = config(runtime) # Calls get() on all components
# params is a flat dict ready for params.txt
Boolean values are converted to integers (XBeach convention):
YAML Configuration¶
Configs can be defined in YAML for reproducibility:
model_type: xbeach
grid:
model_type: regular
dx: 5
dy: 10
# ...
physics:
wavemodel:
model_type: surfbeat
breaktype:
model_type: roelvink1
gamma: 0.55
bedfriction:
bedfriccoef: 0.01
sediment:
morphology:
morfac: 10
morstart: 3600
output:
outputformat: netcdf
tintg: 3600
The model_type field enables Pydantic to deserialise the correct class for discriminated unions.
Best Practices¶
1. Start Simple¶
Begin with minimal configuration and add components as needed:
config = Config(
grid=grid,
bathy=bathy,
input=Input(wave=wave_boundary),
)
# Uses XBeach defaults for physics, sediment, output
2. Use Components for Non-Default Behaviour¶
Only specify components when you need to change defaults:
# Only customise what you need
physics=Physics(
wavemodel=Surfbeat(), # Explicit surfbeat (default is stationary)
bedfriction=BedFriction(bedfriccoef=0.02), # Custom friction
# viscosity, vegetation, etc. use defaults
)
3. Leverage Type Hints¶
IDEs provide autocomplete and type checking:
from rompy_xbeach.components.physics import Surfbeat, Roelvink1
# IDE shows available parameters
wavemodel = Surfbeat(
breaktype=Roelvink1(gamma=0.55), # IDE shows Roelvink1 params
)
4. Validate Early¶
Pydantic validates on construction. Run your config setup before submitting jobs:
try:
config = Config(...)
except ValidationError as e:
print(e) # Shows which parameter failed and why
Summary¶
Rompy-xbeach organises XBeach's flat parameter space into a structured hierarchy:
- Components group related parameters for maintainability
- Discriminated unions enforce valid parameter combinations
- Data interfaces generate boundary files from external sources
- Serialisation flattens the hierarchy back to
params.txt
The learning curve is knowing which component contains which parameter, but IDE support and consistent naming (matching XBeach parameter names) minimise this friction.