Skip to content

Components API

CGRID Components

REGULAR

Bases: CGRID

SWAN regular computational grid.

.. code-block:: text

CGRID REGULAR [xpc] [ypc] [alpc] [xlenc] [ylenc] [mxc] [myc] &
    ->CIRCLE|SECTOR [mdc] [flow] [fhigh] [msc]

This is a group component that includes a CGRID and a READGRID component.

Note

In 1D-mode, alpc should be equal to the direction alpinp.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.cgrid import REGULAR
cgrid = REGULAR(
    grid=dict(xp=0, yp=0, alp=0, xlen=2000, ylen=1300, mx=100, my=100),
    spectrum=dict(mdc=36, flow=0.04, fhigh=1.0),
)
print(cgrid.render())
Source code in src/rompy_swan/components/cgrid.py
class REGULAR(CGRID):
    """SWAN regular computational grid.

    .. code-block:: text

        CGRID REGULAR [xpc] [ypc] [alpc] [xlenc] [ylenc] [mxc] [myc] &
            ->CIRCLE|SECTOR [mdc] [flow] [fhigh] [msc]

    This is a group component that includes a `CGRID` and a `READGRID` component.

    Note
    ----
    In 1D-mode, `alpc` should be equal to the direction `alpinp`.


    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.cgrid import REGULAR
        cgrid = REGULAR(
            grid=dict(xp=0, yp=0, alp=0, xlen=2000, ylen=1300, mx=100, my=100),
            spectrum=dict(mdc=36, flow=0.04, fhigh=1.0),
        )
        print(cgrid.render())

    """

    model_type: Literal["regular", "REGULAR"] = Field(
        default="regular", description="Model type discriminator"
    )
    grid: GRIDREGULAR = Field(description="Computational grid definition")

    @model_validator(mode="after")
    def grid_suffix(self) -> "REGULAR":
        """Set expected grid suffix."""
        if self.grid.suffix != "c":
            logger.debug(f"Set grid suffix 'c' instead of {self.grid.suffix}")
            self.grid.suffix = "c"
        return self

    def cmd(self) -> str:
        repr = f"CGRID REGULAR {self.grid.render()} {self.spectrum.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['regular', 'REGULAR'] = Field(default='regular', description='Model type discriminator')

grid class-attribute instance-attribute

grid: GRIDREGULAR = Field(description='Computational grid definition')

Functions

grid_suffix

grid_suffix() -> REGULAR

Set expected grid suffix.

Source code in src/rompy_swan/components/cgrid.py
@model_validator(mode="after")
def grid_suffix(self) -> "REGULAR":
    """Set expected grid suffix."""
    if self.grid.suffix != "c":
        logger.debug(f"Set grid suffix 'c' instead of {self.grid.suffix}")
        self.grid.suffix = "c"
    return self

cmd

cmd() -> str
Source code in src/rompy_swan/components/cgrid.py
def cmd(self) -> str:
    repr = f"CGRID REGULAR {self.grid.render()} {self.spectrum.render()}"
    return repr

CURVILINEAR

Bases: CGRID

SWAN curvilinear computational grid.

.. code-block:: text

CGRID CURVILINEAR [mxc] [myc] (EXCEPTION [xexc] [yexc])
    ->CIRCLE|SECTOR [mdc] [flow] [fhigh] [msc]
READGRID COORDINATES [fac] 'fname' [idla] [nhedf] [nhedvec] &
    FREE|FORMAT ('form'|[idfm])

This is a group component that includes a CGRID and a READGRID component.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.cgrid import CURVILINEAR
cgrid = CURVILINEAR(
    mxc=199,
    myc=199,
    readcoord=dict(fname="./coords.txt"),
    spectrum=dict(mdc=36, flow=0.04, fhigh=1.0),
)
print(cgrid.render())
Source code in src/rompy_swan/components/cgrid.py
class CURVILINEAR(CGRID):
    """SWAN curvilinear computational grid.

    .. code-block:: text

        CGRID CURVILINEAR [mxc] [myc] (EXCEPTION [xexc] [yexc])
            ->CIRCLE|SECTOR [mdc] [flow] [fhigh] [msc]
        READGRID COORDINATES [fac] 'fname' [idla] [nhedf] [nhedvec] &
            FREE|FORMAT ('form'|[idfm])

    This is a group component that includes a `CGRID` and a `READGRID` component.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.cgrid import CURVILINEAR
        cgrid = CURVILINEAR(
            mxc=199,
            myc=199,
            readcoord=dict(fname="./coords.txt"),
            spectrum=dict(mdc=36, flow=0.04, fhigh=1.0),
        )
        print(cgrid.render())

    """

    model_type: Literal["curvilinear", "CURVILINEAR"] = Field(
        default="curvilinear", description="Model type discriminator"
    )
    mxc: int = Field(
        description=(
            "Number of meshes in computational grid in ξ-direction (this number is "
            "one less than the number of grid points in this domain)."
        ),
    )
    myc: int = Field(
        description=(
            "Number of meshes in computational grid in η-direction (this number is "
            "one less than the number of grid points in this domain)."
        ),
    )
    xexc: Optional[float] = Field(
        default=None,
        description=(
            "the value which the user uses to indicate that a grid point is to be "
            "ignored in the computations (this value is provided by the user at the "
            "location of the x-coordinate considered in the file of the "
            "x-coordinates, see command READGRID COOR)."
        ),
    )
    yexc: Optional[float] = Field(
        default=None,
        description=(
            "the value which the user uses to indicate that a grid point is to be "
            "ignored in the computations (this value is provided by the user at the "
            "location of the y-coordinate considered in the file of the "
            "y-coordinates, see command READGRID COOR)."
        ),
    )
    readcoord: READCOORD = Field(
        description="Grid coordinates reader.",
    )

    @model_validator(mode="after")
    def xexc_and_yexc_or_neither(self) -> "CURVILINEAR":
        if [self.xexc, self.yexc].count(None) == 1:
            raise ValueError("xexc and yexc must be specified together")
        return self

    @property
    def exception(self):
        if self.xexc is not None:
            return f"EXCEPTION xexc={self.xexc} xexc={self.yexc}"
        else:
            return ""

    @property
    def format_repr(self):
        if self.format == "free":
            repr = "FREE"
        elif self.format == "fixed" and self.form:
            repr = f"FORMAT form='{self.form}'"
        elif self.format == "fixed" and self.idfm:
            repr = f"FORMAT idfm={self.idfm}"
        elif self.format == "unformatted":
            repr = "UNFORMATTED"
        return repr

    def cmd(self) -> str:
        repr = f"CGRID CURVILINEAR mxc={self.mxc} myc={self.myc}"
        if self.exception:
            repr += f" {self.exception}"
        repr += f" {self.spectrum.render()}"
        repr = [repr] + [self.readcoord.render()]
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['curvilinear', 'CURVILINEAR'] = Field(default='curvilinear', description='Model type discriminator')

mxc class-attribute instance-attribute

mxc: int = Field(description='Number of meshes in computational grid in ξ-direction (this number is one less than the number of grid points in this domain).')

myc class-attribute instance-attribute

myc: int = Field(description='Number of meshes in computational grid in η-direction (this number is one less than the number of grid points in this domain).')

xexc class-attribute instance-attribute

xexc: Optional[float] = Field(default=None, description='the value which the user uses to indicate that a grid point is to be ignored in the computations (this value is provided by the user at the location of the x-coordinate considered in the file of the x-coordinates, see command READGRID COOR).')

yexc class-attribute instance-attribute

yexc: Optional[float] = Field(default=None, description='the value which the user uses to indicate that a grid point is to be ignored in the computations (this value is provided by the user at the location of the y-coordinate considered in the file of the y-coordinates, see command READGRID COOR).')

readcoord class-attribute instance-attribute

readcoord: READCOORD = Field(description='Grid coordinates reader.')

exception property

exception

format_repr property

format_repr

Functions

xexc_and_yexc_or_neither

xexc_and_yexc_or_neither() -> CURVILINEAR
Source code in src/rompy_swan/components/cgrid.py
@model_validator(mode="after")
def xexc_and_yexc_or_neither(self) -> "CURVILINEAR":
    if [self.xexc, self.yexc].count(None) == 1:
        raise ValueError("xexc and yexc must be specified together")
    return self

cmd

cmd() -> str
Source code in src/rompy_swan/components/cgrid.py
def cmd(self) -> str:
    repr = f"CGRID CURVILINEAR mxc={self.mxc} myc={self.myc}"
    if self.exception:
        repr += f" {self.exception}"
    repr += f" {self.spectrum.render()}"
    repr = [repr] + [self.readcoord.render()]
    return repr

UNSTRUCTURED

Bases: CGRID

SWAN unstructured computational grid.

.. code-block:: text

CGRID UNSTRUCTURED CIRCLE|SECTOR [mdc] [flow] [fhigh] [msc]
READGRID UNSTRUCTURED [grid_type] ('fname')

This is a group component that includes a CGRID and a READGRID component.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.cgrid import UNSTRUCTURED
cgrid = UNSTRUCTURED(
    grid_type="adcirc",
    spectrum=dict(mdc=36, flow=0.04, fhigh=1.0),
)
print(cgrid.render())
Source code in src/rompy_swan/components/cgrid.py
class UNSTRUCTURED(CGRID):
    """SWAN unstructured computational grid.

    .. code-block:: text

        CGRID UNSTRUCTURED CIRCLE|SECTOR [mdc] [flow] [fhigh] [msc]
        READGRID UNSTRUCTURED [grid_type] ('fname')

    This is a group component that includes a `CGRID` and a `READGRID` component.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.cgrid import UNSTRUCTURED
        cgrid = UNSTRUCTURED(
            grid_type="adcirc",
            spectrum=dict(mdc=36, flow=0.04, fhigh=1.0),
        )
        print(cgrid.render())

    """

    model_type: Literal["unstructured"] = Field(
        default="unstructured", description="Model type discriminator"
    )
    grid_type: Literal["adcirc", "triangle", "easymesh"] = Field(
        default="adcirc",
        description="Unstructured grid type",
    )
    fname: Optional[str] = Field(
        default=None,
        description="Name of the file containing the unstructured grid",
        max_length=36,
    )

    @model_validator(mode="after")
    def check_fname_required(self) -> "UNSTRUCTURED":
        """Check that fname needs to be provided."""
        if self.grid_type == "adcirc" and self.fname is not None:
            raise ValueError("fname must not be specified for ADCIRC grid")
        elif self.grid_type != "adcirc" and self.fname is None:
            raise ValueError(f"fname must be specified for {self.grid_type} grid")
        return self

    def cmd(self) -> str:
        repr = [f"CGRID UNSTRUCTURED {self.spectrum.cmd()}"]
        repr += [f"READGRID UNSTRUCTURED {self.grid_type.upper()}"]
        if self.grid_type in ["triangle", "easymesh"]:
            repr[-1] += f" fname='{self.fname}'"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['unstructured'] = Field(default='unstructured', description='Model type discriminator')

grid_type class-attribute instance-attribute

grid_type: Literal['adcirc', 'triangle', 'easymesh'] = Field(default='adcirc', description='Unstructured grid type')

fname class-attribute instance-attribute

fname: Optional[str] = Field(default=None, description='Name of the file containing the unstructured grid', max_length=36)

Functions

check_fname_required

check_fname_required() -> UNSTRUCTURED

Check that fname needs to be provided.

Source code in src/rompy_swan/components/cgrid.py
@model_validator(mode="after")
def check_fname_required(self) -> "UNSTRUCTURED":
    """Check that fname needs to be provided."""
    if self.grid_type == "adcirc" and self.fname is not None:
        raise ValueError("fname must not be specified for ADCIRC grid")
    elif self.grid_type != "adcirc" and self.fname is None:
        raise ValueError(f"fname must be specified for {self.grid_type} grid")
    return self

cmd

cmd() -> str
Source code in src/rompy_swan/components/cgrid.py
def cmd(self) -> str:
    repr = [f"CGRID UNSTRUCTURED {self.spectrum.cmd()}"]
    repr += [f"READGRID UNSTRUCTURED {self.grid_type.upper()}"]
    if self.grid_type in ["triangle", "easymesh"]:
        repr[-1] += f" fname='{self.fname}'"
    return repr

Startup Components

PROJECT

Bases: BaseComponent

SWAN Project.

.. code-block:: text

PROJECT 'name' 'nr' 'title' 'title2 'title3'

With this required command the user defines a number of strings to identify the SWAN run (project name e.g., an engineering project) in the print and plot file.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.startup import PROJECT
proj = PROJECT(nr="01")
print(proj.render())
proj = PROJECT(
    name="waus",
    nr="001",
    title1="Western Australia",
    title2="Perth Nest"
)
print(proj.render())
Source code in src/rompy_swan/components/startup.py
class PROJECT(BaseComponent):
    """SWAN Project.

    .. code-block:: text

        PROJECT 'name' 'nr' 'title' 'title2 'title3'

    With this required command the user defines a number of strings to identify the
    SWAN run (project name e.g., an engineering project) in the print and plot file.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.startup import PROJECT
        proj = PROJECT(nr="01")
        print(proj.render())
        proj = PROJECT(
            name="waus",
            nr="001",
            title1="Western Australia",
            title2="Perth Nest"
        )
        print(proj.render())

    """

    model_type: Literal["project", "PROJECT"] = Field(
        default="project",
        description="Model type discriminator",
    )
    name: Optional[str] = Field(
        default=None,
        description="Is the name of the project, at most 16 characters long",
        max_length=16,
    )
    nr: str = Field(
        description=(
            "Is the run identification (to be provided as a character string; e.g. "
            "the run number) to distinguish this run among other runs for the same "
            "project; it is at most 4 characters long. It is the only required "
            "information in this command."
        ),
        max_length=4,
    )
    title1: Optional[str] = Field(
        default=None,
        description=(
            "A string of at most 72 characters provided by the user to appear in the "
            "output of the program for the user's convenience (SWAN default: blanks)"
        ),
        max_length=72,
    )
    title2: Optional[str] = Field(
        default=None, description="Same as 'title1'", max_length=72
    )
    title3: Optional[str] = Field(
        default=None, description="Same as 'title1'", max_length=72
    )

    def cmd(self) -> str:
        repr = "PROJECT"
        if self.name is not None:
            repr += f" name='{self.name}'"
        repr += f" nr='{self.nr}'"
        if self.title1 is not None:
            repr += f" title1='{self.title1}'"
        if self.title2 is not None:
            repr += f" title2='{self.title2}'"
        if self.title3 is not None:
            repr += f" title3='{self.title3}'"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['project', 'PROJECT'] = Field(default='project', description='Model type discriminator')

name class-attribute instance-attribute

name: Optional[str] = Field(default=None, description='Is the name of the project, at most 16 characters long', max_length=16)

nr class-attribute instance-attribute

nr: str = Field(description='Is the run identification (to be provided as a character string; e.g. the run number) to distinguish this run among other runs for the same project; it is at most 4 characters long. It is the only required information in this command.', max_length=4)

title1 class-attribute instance-attribute

title1: Optional[str] = Field(default=None, description="A string of at most 72 characters provided by the user to appear in the output of the program for the user's convenience (SWAN default: blanks)", max_length=72)

title2 class-attribute instance-attribute

title2: Optional[str] = Field(default=None, description="Same as 'title1'", max_length=72)

title3 class-attribute instance-attribute

title3: Optional[str] = Field(default=None, description="Same as 'title1'", max_length=72)

Functions

cmd

cmd() -> str
Source code in src/rompy_swan/components/startup.py
def cmd(self) -> str:
    repr = "PROJECT"
    if self.name is not None:
        repr += f" name='{self.name}'"
    repr += f" nr='{self.nr}'"
    if self.title1 is not None:
        repr += f" title1='{self.title1}'"
    if self.title2 is not None:
        repr += f" title2='{self.title2}'"
    if self.title3 is not None:
        repr += f" title3='{self.title3}'"
    return repr

SET

Bases: BaseComponent

SWAN setting commands.

.. code-block:: text

SET [level] [nor] [depmin] [maxmes] [maxerr] [grav] [rho] [cdcap] &
    [inrhog] [hsrerr] NAUTICAL|->CARTESIAN [pwtail] [froudmax] [icewind]

With this optional command the user assigns values to various general parameters.

Notes

The error level maxerr is coded as follows:

  • 1: warnings
  • 2: errors (possibly automatically repaired or repairable by SWAN)
  • 3: severe errors

Default values for pwtail depend on formulations of physics:

  • command GEN1: pwtail = 5
  • command GEN2: pwtail = 5
  • command GEN3 KOMEN: pwtail = 4
  • command GEN3 WESTH: pwtail = 4
  • command GEN3 JANSSEN: pwtail = 5

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.startup import SET
set = SET(level=0.5, direction_convention="nautical")
print(set.render())
set = SET(
    level=-1.0,
    nor=90,
    depmin=0.01,
    maxerr=3,
    grav=9.81,
    rho=1025,
    cdcap=2.5e-3,
    inrhog=0,
    hsrerr=0.1,
    direction_convention="nautical",
)
print(set.render())
Source code in src/rompy_swan/components/startup.py
class SET(BaseComponent):
    """SWAN setting commands.

    .. code-block:: text

        SET [level] [nor] [depmin] [maxmes] [maxerr] [grav] [rho] [cdcap] &
            [inrhog] [hsrerr] NAUTICAL|->CARTESIAN [pwtail] [froudmax] [icewind]

    With this optional command the user assigns values to various general parameters.

    Notes
    -----
    The error level `maxerr` is coded as follows:

    * 1: warnings
    * 2: errors (possibly automatically repaired or repairable by SWAN)
    * 3: severe errors

    Default values for `pwtail` depend on formulations of physics:

    * command GEN1: `pwtail = 5`
    * command GEN2: `pwtail = 5`
    * command GEN3 KOMEN: `pwtail = 4`
    * command GEN3 WESTH: `pwtail = 4`
    * command GEN3 JANSSEN: `pwtail = 5`

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.startup import SET
        set = SET(level=0.5, direction_convention="nautical")
        print(set.render())
        set = SET(
            level=-1.0,
            nor=90,
            depmin=0.01,
            maxerr=3,
            grav=9.81,
            rho=1025,
            cdcap=2.5e-3,
            inrhog=0,
            hsrerr=0.1,
            direction_convention="nautical",
        )
        print(set.render())

    """

    model_type: Literal["set", "SET"] = Field(
        default="set", description="Model type discriminator"
    )
    level: Optional[float] = Field(
        default=None,
        description=(
            "Increase in water level that is constant in space and time can be given "
            "with this option, `level` is the value of this increase (in m). For a "
            "variable water level reference is made to the commands "
            "INPGRID and READINP (SWAN default: 0)"
        ),
        examples=[0],
    )
    nor: Optional[float] = Field(
        default=None,
        description=(
            "Direction of North with respect to the x-axis (measured "
            "counterclockwise); default `nor = 90`, i.e. x-axis of the problem "
            "coordinate system points East. When spherical coordinates are used "
            "(see command COORD) the value of `nor` may not be modified"
        ),
        ge=-360.0,
        le=360.0,
    )
    depmin: Optional[float] = Field(
        default=None,
        description=(
            "Threshold depth (in m). In the computation any positive depth smaller "
            "than `depmin` is made equal to `depmin` (SWAN default: 0.05)"
        ),
        ge=0.0,
    )
    maxmes: Optional[int] = Field(
        default=None,
        description=(
            "Maximum number of error messages during the computation at which the "
            "computation is terminated. During the computational process messages are "
            "written to the print file (SWAN default: 200)"
        ),
        ge=0,
    )
    maxerr: Optional[Literal[1, 2, 3]] = Field(
        default=None,
        description=(
            "During pre-processing SWAN checks input data. Depending on the severity "
            "of the errors encountered during this pre-processing, SWAN does not "
            "start computations. The user can influence the error level above which "
            "SWAN will  not start computations (at the level indicated the "
            "computations will continue) (SWAN default: 1)"
        ),
    )
    grav: Optional[float] = Field(
        default=None,
        description="The gravitational acceleration (in m/s2) (SWAN default: 9.81)",
        ge=0.0,
    )
    rho: Optional[float] = Field(
        default=None,
        description="The water density (in kg/m3) (SWAN default: 1025)",
        ge=0.0,
    )
    cdcap: Optional[float] = Field(
        default=None,
        description=(
            "The maximum value for the wind drag coefficient. A value of 99999 means"
            "no cutting off the drag coefficient. A suggestion for this parameter is "
            "`cdcap = 2.5x 10-3` (SWAN default: 99999) "
        ),
        ge=0.0,
    )
    inrhog: Optional[Literal[0, 1]] = Field(
        default=None,
        description=(
            "To indicate whether the user requires output based on variance or based "
            "on true energy (see Section 2.5). `inrhog` = 0: output based on variance, "
            "`inrhog` = 1: output based on true energy (SWAN default: 0)"
        ),
    )
    hsrerr: Optional[float] = Field(
        default=None,
        description=(
            "The relative difference between the user imposed significant wave height "
            "and the significant wave height computed by SWAN (anywhere along the "
            "computational grid boundary) above which a warning will be given. This "
            "relative difference is the difference normalized with the user provided "
            "significant wave height. This warning will be given for each boundary "
            "grid point where the problem occurs (with its x- and y-index number of "
            "the computational grid). The cause of the difference is explained in "
            "Section 2.6.3. To suppress these warnings (in particular for "
            "nonstationary computations), set `hsrerr` at a very high value or use "
            "command OFF BNDCHK (SWAN default: 0.10) (ONLY MEANT FOR STRUCTURED GRIDS)"
        ),
        ge=0.0,
    )
    direction_convention: Literal["nautical", "cartesian"] = Field(
        description=(
            "Direction convention: `nautical` indicates that the Nautical convention "
            "for wind and wave direction (SWAN input and output) will be used, "
            "`cartesian` indicates that the Cartesian convention for wind and wave "
            "direction will be used. For definition, see Section 2.5 or Appendix A "
            "(SWAN default: `cartesian`)"
        ),
    )
    pwtail: Optional[int] = Field(
        default=None,
        description=(
            "Power of high frequency tail; defines the shape of the spectral tail "
            "above the highest prognostic frequency `fhigh` (see command CGRID). "
            "The energy density is assumed to be proportional to frequency to the "
            "power `pwtail`. If the user wishes to use another value, then this SET "
            "command should be located in the command file after GEN1, GEN2 or GEN3 "
            "command (these will override the SET command with respect to `pwtail`)"
        ),
        ge=0,
    )
    froudmax: Optional[float] = Field(
        default=None,
        description=(
            "Is the maximum Froude number (`U/√gd` with `U` the current and `d` the "
            "water depth). The currents taken from a circulation model may mismatch "
            "with given water depth `d` in the sense that the Froude number becomes "
            "larger than 1. For this, the current velocities will be maximized by "
            "Froude number times `sqrt(gh)` (SWAN default: 0.8)"
        ),
        ge=0.0,
    )
    icewind: Optional[Literal[0, 1]] = Field(
        default=None,
        description=(
            "Controls the scaling of wind input by open water fraction. Default value "
            "of zero corresponds to the case where wind input is scaled by the open "
            "water fraction. If `icewind = 1` then sea ice does not affect wind input "
            "directly. (Though there is still indirect effect via the sea ice sink "
            "term; see command SICE) (SWAN default: 0)"
        ),
    )

    @field_validator("pwtail")
    @classmethod
    def pwtail_after_gen(cls, v):
        if v is not None:
            logger.warning("pwtail only has effect if set after GEN command")
        return v

    def cmd(self) -> str:
        repr = "SET"
        if self.level is not None:
            repr += f" level={self.level}"
        if self.nor is not None:
            repr += f" nor={self.nor}"
        if self.depmin is not None:
            repr += f" depmin={self.depmin}"
        if self.maxmes is not None:
            repr += f" maxmes={self.maxmes}"
        if self.maxerr is not None:
            repr += f" maxerr={self.maxerr}"
        if self.grav is not None:
            repr += f" grav={self.grav}"
        if self.rho is not None:
            repr += f" rho={self.rho}"
        if self.cdcap is not None:
            repr += f" cdcap={self.cdcap}"
        if self.inrhog is not None:
            repr += f" inrhog={self.inrhog}"
        if self.hsrerr is not None:
            repr += f" hsrerr={self.hsrerr}"
        if self.direction_convention is not None:
            repr += f" {self.direction_convention.upper()}"
        if self.pwtail is not None:
            repr += f" pwtail={self.pwtail}"
        if self.froudmax is not None:
            repr += f" froudmax={self.froudmax}"
        if self.icewind is not None:
            repr += f" icewind={self.icewind}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['set', 'SET'] = Field(default='set', description='Model type discriminator')

level class-attribute instance-attribute

level: Optional[float] = Field(default=None, description='Increase in water level that is constant in space and time can be given with this option, `level` is the value of this increase (in m). For a variable water level reference is made to the commands INPGRID and READINP (SWAN default: 0)', examples=[0])

nor class-attribute instance-attribute

nor: Optional[float] = Field(default=None, description='Direction of North with respect to the x-axis (measured counterclockwise); default `nor = 90`, i.e. x-axis of the problem coordinate system points East. When spherical coordinates are used (see command COORD) the value of `nor` may not be modified', ge=-360.0, le=360.0)

depmin class-attribute instance-attribute

depmin: Optional[float] = Field(default=None, description='Threshold depth (in m). In the computation any positive depth smaller than `depmin` is made equal to `depmin` (SWAN default: 0.05)', ge=0.0)

maxmes class-attribute instance-attribute

maxmes: Optional[int] = Field(default=None, description='Maximum number of error messages during the computation at which the computation is terminated. During the computational process messages are written to the print file (SWAN default: 200)', ge=0)

maxerr class-attribute instance-attribute

maxerr: Optional[Literal[1, 2, 3]] = Field(default=None, description='During pre-processing SWAN checks input data. Depending on the severity of the errors encountered during this pre-processing, SWAN does not start computations. The user can influence the error level above which SWAN will  not start computations (at the level indicated the computations will continue) (SWAN default: 1)')

grav class-attribute instance-attribute

grav: Optional[float] = Field(default=None, description='The gravitational acceleration (in m/s2) (SWAN default: 9.81)', ge=0.0)

rho class-attribute instance-attribute

rho: Optional[float] = Field(default=None, description='The water density (in kg/m3) (SWAN default: 1025)', ge=0.0)

cdcap class-attribute instance-attribute

cdcap: Optional[float] = Field(default=None, description='The maximum value for the wind drag coefficient. A value of 99999 meansno cutting off the drag coefficient. A suggestion for this parameter is `cdcap = 2.5x 10-3` (SWAN default: 99999) ', ge=0.0)

inrhog class-attribute instance-attribute

inrhog: Optional[Literal[0, 1]] = Field(default=None, description='To indicate whether the user requires output based on variance or based on true energy (see Section 2.5). `inrhog` = 0: output based on variance, `inrhog` = 1: output based on true energy (SWAN default: 0)')

hsrerr class-attribute instance-attribute

hsrerr: Optional[float] = Field(default=None, description='The relative difference between the user imposed significant wave height and the significant wave height computed by SWAN (anywhere along the computational grid boundary) above which a warning will be given. This relative difference is the difference normalized with the user provided significant wave height. This warning will be given for each boundary grid point where the problem occurs (with its x- and y-index number of the computational grid). The cause of the difference is explained in Section 2.6.3. To suppress these warnings (in particular for nonstationary computations), set `hsrerr` at a very high value or use command OFF BNDCHK (SWAN default: 0.10) (ONLY MEANT FOR STRUCTURED GRIDS)', ge=0.0)

direction_convention class-attribute instance-attribute

direction_convention: Literal['nautical', 'cartesian'] = Field(description='Direction convention: `nautical` indicates that the Nautical convention for wind and wave direction (SWAN input and output) will be used, `cartesian` indicates that the Cartesian convention for wind and wave direction will be used. For definition, see Section 2.5 or Appendix A (SWAN default: `cartesian`)')

pwtail class-attribute instance-attribute

pwtail: Optional[int] = Field(default=None, description='Power of high frequency tail; defines the shape of the spectral tail above the highest prognostic frequency `fhigh` (see command CGRID). The energy density is assumed to be proportional to frequency to the power `pwtail`. If the user wishes to use another value, then this SET command should be located in the command file after GEN1, GEN2 or GEN3 command (these will override the SET command with respect to `pwtail`)', ge=0)

froudmax class-attribute instance-attribute

froudmax: Optional[float] = Field(default=None, description='Is the maximum Froude number (`U/√gd` with `U` the current and `d` the water depth). The currents taken from a circulation model may mismatch with given water depth `d` in the sense that the Froude number becomes larger than 1. For this, the current velocities will be maximized by Froude number times `sqrt(gh)` (SWAN default: 0.8)', ge=0.0)

icewind class-attribute instance-attribute

icewind: Optional[Literal[0, 1]] = Field(default=None, description='Controls the scaling of wind input by open water fraction. Default value of zero corresponds to the case where wind input is scaled by the open water fraction. If `icewind = 1` then sea ice does not affect wind input directly. (Though there is still indirect effect via the sea ice sink term; see command SICE) (SWAN default: 0)')

Functions

pwtail_after_gen classmethod

pwtail_after_gen(v)
Source code in src/rompy_swan/components/startup.py
@field_validator("pwtail")
@classmethod
def pwtail_after_gen(cls, v):
    if v is not None:
        logger.warning("pwtail only has effect if set after GEN command")
    return v

cmd

cmd() -> str
Source code in src/rompy_swan/components/startup.py
def cmd(self) -> str:
    repr = "SET"
    if self.level is not None:
        repr += f" level={self.level}"
    if self.nor is not None:
        repr += f" nor={self.nor}"
    if self.depmin is not None:
        repr += f" depmin={self.depmin}"
    if self.maxmes is not None:
        repr += f" maxmes={self.maxmes}"
    if self.maxerr is not None:
        repr += f" maxerr={self.maxerr}"
    if self.grav is not None:
        repr += f" grav={self.grav}"
    if self.rho is not None:
        repr += f" rho={self.rho}"
    if self.cdcap is not None:
        repr += f" cdcap={self.cdcap}"
    if self.inrhog is not None:
        repr += f" inrhog={self.inrhog}"
    if self.hsrerr is not None:
        repr += f" hsrerr={self.hsrerr}"
    if self.direction_convention is not None:
        repr += f" {self.direction_convention.upper()}"
    if self.pwtail is not None:
        repr += f" pwtail={self.pwtail}"
    if self.froudmax is not None:
        repr += f" froudmax={self.froudmax}"
    if self.icewind is not None:
        repr += f" icewind={self.icewind}"
    return repr

MODE

Bases: BaseComponent

SWAN Mode.

.. code-block:: text

MODE ->STATIONARY|NONSTATIONARY ->TWODIMENSIONAL|ONEDIMENSIONAL

With this optional command the user indicates that the run will be either stationary or nonstationary and one-dimensional (1D-mode) or two-dimensional (2D-mode). Nonstationary means either (see command COMPUTE):

  • (a) one nonstationary computations or
  • (b) a sequence of stationary computations or
  • (c) a mix of (a) and (b)

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.startup import MODE
mode = MODE()
print(mode.render())
mode = MODE(kind="nonstationary", dim="twodimensional")
print(mode.render())
Source code in src/rompy_swan/components/startup.py
class MODE(BaseComponent):
    """SWAN Mode.

    .. code-block:: text

        MODE ->STATIONARY|NONSTATIONARY ->TWODIMENSIONAL|ONEDIMENSIONAL

    With this optional command the user indicates that the run will be either
    stationary or nonstationary and one-dimensional (1D-mode) or two-dimensional
    (2D-mode). Nonstationary means either (see command COMPUTE):

    * (a) one nonstationary computations or
    * (b) a sequence of stationary computations or
    * (c) a mix of (a) and (b)

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.startup import MODE
        mode = MODE()
        print(mode.render())
        mode = MODE(kind="nonstationary", dim="twodimensional")
        print(mode.render())

    """

    model_type: Literal["mode", "MODE"] = Field(
        default="mode", description="Model type discriminator."
    )
    kind: Literal["stationary", "nonstationary"] = Field(
        default="stationary",
        description="Indicates if run will be stationary or nonstationary",
    )
    dim: Literal["onedimensional", "twodimensional"] = Field(
        default="twodimensional",
        description=(
            "Indicates that the run will be either one-dimensional (1D-mode) or "
            "two-dimensional (2D-mode)"
        ),
    )

    def cmd(self) -> str:
        return f"MODE {self.kind.upper()} {self.dim.upper()}"

Attributes

model_type class-attribute instance-attribute

model_type: Literal['mode', 'MODE'] = Field(default='mode', description='Model type discriminator.')

kind class-attribute instance-attribute

kind: Literal['stationary', 'nonstationary'] = Field(default='stationary', description='Indicates if run will be stationary or nonstationary')

dim class-attribute instance-attribute

dim: Literal['onedimensional', 'twodimensional'] = Field(default='twodimensional', description='Indicates that the run will be either one-dimensional (1D-mode) or two-dimensional (2D-mode)')

Functions

cmd

cmd() -> str
Source code in src/rompy_swan/components/startup.py
def cmd(self) -> str:
    return f"MODE {self.kind.upper()} {self.dim.upper()}"

COORDINATES

Bases: BaseComponent

SWAN Coordinates.

.. code-block:: text

COORDINATES ->CARTESIAN|SPHERICAL REPEATING

Command to choose between Cartesian and spherical coordinates (see Section 2.5). A nested SWAN run must use the same coordinate system as the coarse grid SWAN run.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.startup import COORDINATES
coords = COORDINATES()
print(coords.render())
coords = COORDINATES(
    kind=dict(model_type="spherical", projection="ccm"),
    reapeating=True,
)
print(coords.render())
Source code in src/rompy_swan/components/startup.py
class COORDINATES(BaseComponent):
    """SWAN Coordinates.

    .. code-block:: text

        COORDINATES ->CARTESIAN|SPHERICAL REPEATING

    Command to choose between Cartesian and spherical coordinates (see Section 2.5).
    A nested SWAN run must use the same coordinate system as the coarse grid SWAN run.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.startup import COORDINATES
        coords = COORDINATES()
        print(coords.render())
        coords = COORDINATES(
            kind=dict(model_type="spherical", projection="ccm"),
            reapeating=True,
        )
        print(coords.render())

    """

    model_type: Literal["coordinates", "COORDINATES"] = Field(
        default="coordinates",
        description="Model type discriminator",
    )
    kind: CARTESIAN | SPHERICAL = Field(
        default_factory=CARTESIAN,
        description="Coordinates kind",
    )
    reapeating: bool = Field(
        default=False,
        description=(
            "This option is only for academic cases. It means that wave energy "
            "leaving at one end of the domain (in computational x-direction) enter at "
            "the other side; it is as if the wave field repeats itself in x-direction "
            "with the length of the domain in x-direction. This option cannot be used "
            "in combination with computation of set-up (see command SETUP). This "
            "option is available only with regular grids"
        ),
    )

    def cmd(self) -> str:
        repr = f"COORDINATES {self.kind.render()}"
        if self.reapeating:
            repr += " REPEATING"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['coordinates', 'COORDINATES'] = Field(default='coordinates', description='Model type discriminator')

kind class-attribute instance-attribute

kind: CARTESIAN | SPHERICAL = Field(default_factory=CARTESIAN, description='Coordinates kind')

reapeating class-attribute instance-attribute

reapeating: bool = Field(default=False, description='This option is only for academic cases. It means that wave energy leaving at one end of the domain (in computational x-direction) enter at the other side; it is as if the wave field repeats itself in x-direction with the length of the domain in x-direction. This option cannot be used in combination with computation of set-up (see command SETUP). This option is available only with regular grids')

Functions

cmd

cmd() -> str
Source code in src/rompy_swan/components/startup.py
def cmd(self) -> str:
    repr = f"COORDINATES {self.kind.render()}"
    if self.reapeating:
        repr += " REPEATING"
    return repr

Boundary Components

BOUNDSPEC

Bases: BaseComponent

Boundary along sides or segment.

.. code-block:: text

BOUNDSPEC ->SIDE|SEGMENT CONSTANT|VARIABLE PAR|FILE

This command BOUNDSPEC defines parametric spectra at the boundary. It consists of two parts, the first part defines the boundary side or segment where the spectra will be given, the second part defines the spectral parameters of these spectra. Note that in fact only the incoming wave components of these spectra are used by SWAN. The fact that complete spectra are calculated at the model boundaries from the spectral parameters should not be misinterpreted. Only the incoming components are effective in the computation.

TODO: Add support for unstructured grid (k).

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.boundary import BOUNDSPEC
boundary = BOUNDSPEC(
    shapespec=dict(model_type="shapespec", shape=dict(model_type="pm")),
    location=dict(model_type="side", side="west", direction="ccw"),
    data=dict(model_type="constantpar", hs=2, per=8, dir=270, dd=30),
)
print(boundary.render())
boundary = BOUNDSPEC(
    shapespec=dict(model_type="shapespec", shape=dict(model_type="pm")),
    location=dict(
        model_type="segment",
        points=dict(model_type="ij", i=[0, 0], j=[0, 3])
    ),
    data=dict(model_type="constantpar", hs=2, per=8, dir=270, dd=30),
)
print(boundary.render())
Source code in src/rompy_swan/components/boundary.py
class BOUNDSPEC(BaseComponent):
    """Boundary along sides or segment.

    .. code-block:: text

        BOUNDSPEC ->SIDE|SEGMENT CONSTANT|VARIABLE PAR|FILE

    This command BOUNDSPEC defines parametric spectra at the boundary. It consists of
    two parts, the first part defines the boundary side or segment where the spectra
    will be given, the second part defines the spectral parameters of these spectra.
    Note that in fact only the incoming wave components of these spectra are used by
    SWAN. The fact that complete spectra are calculated at the model boundaries from
    the spectral parameters should not be misinterpreted. Only the incoming components
    are effective in the computation.

    TODO: Add support for unstructured grid (k).

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.boundary import BOUNDSPEC
        boundary = BOUNDSPEC(
            shapespec=dict(model_type="shapespec", shape=dict(model_type="pm")),
            location=dict(model_type="side", side="west", direction="ccw"),
            data=dict(model_type="constantpar", hs=2, per=8, dir=270, dd=30),
        )
        print(boundary.render())
        boundary = BOUNDSPEC(
            shapespec=dict(model_type="shapespec", shape=dict(model_type="pm")),
            location=dict(
                model_type="segment",
                points=dict(model_type="ij", i=[0, 0], j=[0, 3])
            ),
            data=dict(model_type="constantpar", hs=2, per=8, dir=270, dd=30),
        )
        print(boundary.render())

    """

    model_type: Literal["boundspec", "BOUNDSPEC"] = Field(
        default="boundspec",
        description="Model type discriminator",
    )
    shapespec: SHAPESPEC = Field(
        default_factory=SHAPESPEC,
        description="Spectral shape specification",
    )
    location: SIDE | SEGMENT = Field(
        description="Location to apply the boundary",
    )
    data: CONSTANTPAR | CONSTANTFILE | VARIABLEPAR | VARIABLEFILE = Field(
        description="Spectral data",
    )

    def cmd(self) -> list:
        repr = [f"{self.shapespec.render()}"]
        repr += [f"BOUNDSPEC {self.location.render()}{self.data.render()}"]
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['boundspec', 'BOUNDSPEC'] = Field(default='boundspec', description='Model type discriminator')

shapespec class-attribute instance-attribute

shapespec: SHAPESPEC = Field(default_factory=SHAPESPEC, description='Spectral shape specification')

location class-attribute instance-attribute

location: SIDE | SEGMENT = Field(description='Location to apply the boundary')

data class-attribute instance-attribute

data: CONSTANTPAR | CONSTANTFILE | VARIABLEPAR | VARIABLEFILE = Field(description='Spectral data')

Functions

cmd

cmd() -> list
Source code in src/rompy_swan/components/boundary.py
def cmd(self) -> list:
    repr = [f"{self.shapespec.render()}"]
    repr += [f"BOUNDSPEC {self.location.render()}{self.data.render()}"]
    return repr

BOUNDNEST1

Bases: BaseComponent

Boundary spectra from a coarser SWAN nest.

.. code-block:: text

BOUNDNEST1 NEST 'fname' ->CLOSED|OPEN

With this optional command a nested SWAN run can be carried out with the boundary conditions obtained from a coarse grid SWAN run (generated in that previous SWAN run with command NESTOUT). The spectral frequencies and directions of the coarse grid run do not have to coincide with the frequencies and directions used in the nested SWAN run; SWAN will interpolate to these frequencies and directions in the nested run (see Section 2.6.3). To generate the nest boundary in the coarse grid run, use command NGRID. For the nested run, use the command CGRID with identical geographical information except the number of meshes (which will be much higher for the nested run). This BOUNDNEST1 command is not available for 1D computations; in such cases the commands SPECOUT and BOUNDSPEC can be used for the same purpose. A nested SWAN run must use the same coordinate system as the coarse grid SWAN run. For a curvilinear grid, it is advised to use the commands POINTS or CURVE and SPECOUT instead of NGRID and NESTOUT.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.boundary import BOUNDNEST1
boundary = BOUNDNEST1(fname="boundary.swn", rectangle="closed")
print(boundary.render())
Source code in src/rompy_swan/components/boundary.py
class BOUNDNEST1(BaseComponent):
    """Boundary spectra from a coarser SWAN nest.

    .. code-block:: text

        BOUNDNEST1 NEST 'fname' ->CLOSED|OPEN

    With this optional command a nested SWAN run can be carried out with the boundary
    conditions obtained from a coarse grid SWAN run (generated in that previous SWAN
    run with command NESTOUT). The spectral frequencies and directions of the coarse
    grid run do not have to coincide with the frequencies and directions used in the
    nested SWAN run; SWAN will interpolate to these frequencies and directions in the
    nested run (see Section 2.6.3). To generate the nest boundary in the coarse grid
    run, use command NGRID. For the nested run, use the command CGRID with identical
    geographical information except the number of meshes (which will be much higher for
    the nested run). This BOUNDNEST1 command is not available for 1D computations; in
    such cases the commands SPECOUT and BOUNDSPEC can be used for the same purpose. A
    nested SWAN run must use the same coordinate system as the coarse grid SWAN run.
    For a curvilinear grid, it is advised to use the commands POINTS or CURVE and
    SPECOUT instead of NGRID and NESTOUT.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.boundary import BOUNDNEST1
        boundary = BOUNDNEST1(fname="boundary.swn", rectangle="closed")
        print(boundary.render())

    """

    model_type: Literal["boundnest1", "BOUNDNEST1"] = Field(
        default="boundnest1",
        description="Model type discriminator",
    )
    fname: str = Field(
        description=(
            "Name of the file containing the boundary conditions for the present run, "
            "created by the previous SWAN coarse grid run. This file is structured "
            "according to the rules given in Appendix D for 2D spectra"
        ),
        min_length=1,
        max_length=36,
    )
    rectangle: Literal["closed", "open"] = Field(
        default="closed",
        description=(
            "Boundary is defined over a closed (default) or an open rectangle. "
            "Boundary generated from the NESTOUT command is aways closed"
        ),
    )

    def cmd(self) -> str:
        return f"BOUNDNEST1 NEST fname='{self.fname}' {self.rectangle.upper()}"

Attributes

model_type class-attribute instance-attribute

model_type: Literal['boundnest1', 'BOUNDNEST1'] = Field(default='boundnest1', description='Model type discriminator')

fname class-attribute instance-attribute

fname: str = Field(description='Name of the file containing the boundary conditions for the present run, created by the previous SWAN coarse grid run. This file is structured according to the rules given in Appendix D for 2D spectra', min_length=1, max_length=36)

rectangle class-attribute instance-attribute

rectangle: Literal['closed', 'open'] = Field(default='closed', description='Boundary is defined over a closed (default) or an open rectangle. Boundary generated from the NESTOUT command is aways closed')

Functions

cmd

cmd() -> str
Source code in src/rompy_swan/components/boundary.py
def cmd(self) -> str:
    return f"BOUNDNEST1 NEST fname='{self.fname}' {self.rectangle.upper()}"

BOUNDNEST2

Bases: BaseComponent

Boundary spectra from WAM.

.. code-block:: text

BOUNDNEST2 WAMNEST 'fname' FREE|UNFORMATTED ->CRAY|WKSTAT [xgc] [ygc] [lwdate]

With this optional command (not fully tested) a nested SWAN run can be carried out with the boundary conditions obtained from a coarse grid WAM run (WAM Cycle 4.5, source code as distributed by the Max Planck Institute in Hamburg). The spectral frequencies and directions of the coarse grid run do not have to coincide with the frequencies and directions used in the nested SWAN run; SWAN will interpolate to these frequencies and directions in the nested run (see Section 2.6.3). Note that SWAN will accept output of a WAM output location only if the SWAN grid point on the nest boundary lies within a rectangle between two consecutive WAM output locations with a width equal to 0.1 times the distance between these output locations on either side of the line between these WAM output locations. This BOUNDNEST2 command is not available for 1D computations. Only boundary conditions generated by WAM Cycle 4.5 can be read properly by SWAN. A nested SWAN run may use either Cartesian or spherical coordinates. A curvilinear grid may be used in the nested grid but the boundaries of this nest should conform to the rectangular course grid nest boundaries. WAM output files are unformatted (binary); this usually implies that WAM and SWAN have to run on the same computer. For those cases where WAM and SWAN run on different types of machines (binary files do not transfer properly), the option FREE is available in this command. The distributed version of WAM does not support the required free format nesting output; WAM users who modify WAM such that it can make formatted output, must modify WAM such that the files made by WAM can be read in free format, i.e. with at least a blank or comma between numbers.

Note

the contents of 'fname' file could look like:

.. code-block:: text

CBO9212010000
CBO9212020000
CBO9212030000

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.boundary import BOUNDNEST2
boundary = BOUNDNEST2(fname="boundary.wam", format="cray", lwdate=12)
print(boundary.render())
Source code in src/rompy_swan/components/boundary.py
class BOUNDNEST2(BaseComponent):
    """Boundary spectra from WAM.

    .. code-block:: text

        BOUNDNEST2 WAMNEST 'fname' FREE|UNFORMATTED ->CRAY|WKSTAT [xgc] [ygc] [lwdate]

    With this optional command (not fully tested) a nested SWAN run can be carried out
    with the boundary conditions obtained from a coarse grid WAM run (WAM Cycle 4.5,
    source code as distributed by the Max Planck Institute in Hamburg). The spectral
    frequencies and directions of the coarse grid run do not have to coincide with the
    frequencies and directions used in the nested SWAN run; SWAN will interpolate to
    these frequencies and directions in the nested run (see Section 2.6.3). Note that
    SWAN will accept output of a WAM output location only if the SWAN grid point on the
    nest boundary lies within a rectangle between two consecutive WAM output locations
    with a width equal to 0.1 times the distance between these output locations on
    either side of the line between these WAM output locations. This BOUNDNEST2 command
    is not available for 1D computations. Only boundary conditions generated by WAM
    Cycle 4.5 can be read properly by SWAN. A nested SWAN run may use either Cartesian
    or spherical coordinates. A curvilinear grid may be used in the nested grid but the
    boundaries of this nest should conform to the rectangular course grid nest
    boundaries. WAM output files are unformatted (binary); this usually implies that
    WAM and SWAN have to run on the same computer. For those cases where WAM and SWAN
    run on different types of machines (binary files do not transfer properly), the
    option FREE is available in this command. The distributed version of WAM does not
    support the required free format nesting output; WAM users who modify WAM such that
    it can make formatted output, must modify WAM such that the files made by WAM can
    be read in free format, i.e. with at least a blank or comma between numbers.

    Note
    ----
    the contents of 'fname' file could look like:

    .. code-block:: text

        CBO9212010000
        CBO9212020000
        CBO9212030000

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.boundary import BOUNDNEST2
        boundary = BOUNDNEST2(fname="boundary.wam", format="cray", lwdate=12)
        print(boundary.render())

    """

    model_type: Literal["boundnest2", "BOUNDNEST2"] = Field(
        default="boundnest2",
        description="Model type discriminator",
    )
    fname: str = Field(
        description=(
            "A file name that contains all the names of WAM files containing the "
            "nested boundary conditions in time-sequence (usually one file per day)"
        ),
        min_length=1,
        max_length=36,
    )
    format: Literal["cray", "wkstat", "free"] = Field(
        description=(
            "Format of the WAM file. `cray`: CRAY version of WAM, `wkstat`: "
            "WORKSTATION version of WAM, `free`: Free format (these files are not "
            "generated standard by WAM)"
        ),
    )
    xgc: Optional[float] = Field(
        default=None,
        description=(
            "If SWAN is used with Cartesian coordinates: longitude of south-west "
            "corner of SWAN computational grid (in degrees); if the south-west "
            "corner of the nest in the WAM computation is on land this value is "
            "required. If SWAN is used with spherical coordinates then `xgc` is "
            "ignored by SWAN (SWAN default: the location of the first spectrum "
            "encountered in the nest file"
        ),
    )
    ygc: Optional[float] = Field(
        default=None,
        description=(
            "If SWAN is used with Cartesian coordinates: latitude of south-west "
            "corner of SWAN computational grid (in degrees); if the south-west "
            "corner of the nest in the WAM computation is on land this value is "
            "required. If SWAN is used with spherical coordinates then `ygc` is "
            "ignored by SWAN (SWAN default: the location of the first spectrum "
            "encountered in the nest file"
        ),
    )
    lwdate: Literal[10, 12, 14] = Field(
        default=12,
        description=(
            "Length of character string for date-time as used in the WAM files. "
            "Possible values are: 10 (i.e. YYMMDDHHMM), 12 (i.e. YYMMDDHHMMSS) "
            "or 14 (i.e. YYYYMMDDHHMMSS) (SWAN default: `lwdate` = 12)"
        ),
    )

    @property
    def format_str(self):
        if self.format == "cray":
            return "UNFORMATTED CRAY"
        elif self.format == "wkstat":
            return "UNFORMATTED WKSTAT"
        elif self.format == "free":
            return "FREE"
        else:
            raise ValueError(f"Unknown format {self.format}")

    def cmd(self) -> str:
        repr = f"BOUNDNEST2 WAMNEST fname='{self.fname}' {self.format_str}"
        if self.xgc is not None:
            repr += f" xgc={self.xgc}"
        if self.ygc is not None:
            repr += f" ygc={self.ygc}"
        repr += f" lwdate={self.lwdate}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['boundnest2', 'BOUNDNEST2'] = Field(default='boundnest2', description='Model type discriminator')

fname class-attribute instance-attribute

fname: str = Field(description='A file name that contains all the names of WAM files containing the nested boundary conditions in time-sequence (usually one file per day)', min_length=1, max_length=36)

format class-attribute instance-attribute

format: Literal['cray', 'wkstat', 'free'] = Field(description='Format of the WAM file. `cray`: CRAY version of WAM, `wkstat`: WORKSTATION version of WAM, `free`: Free format (these files are not generated standard by WAM)')

xgc class-attribute instance-attribute

xgc: Optional[float] = Field(default=None, description='If SWAN is used with Cartesian coordinates: longitude of south-west corner of SWAN computational grid (in degrees); if the south-west corner of the nest in the WAM computation is on land this value is required. If SWAN is used with spherical coordinates then `xgc` is ignored by SWAN (SWAN default: the location of the first spectrum encountered in the nest file')

ygc class-attribute instance-attribute

ygc: Optional[float] = Field(default=None, description='If SWAN is used with Cartesian coordinates: latitude of south-west corner of SWAN computational grid (in degrees); if the south-west corner of the nest in the WAM computation is on land this value is required. If SWAN is used with spherical coordinates then `ygc` is ignored by SWAN (SWAN default: the location of the first spectrum encountered in the nest file')

lwdate class-attribute instance-attribute

lwdate: Literal[10, 12, 14] = Field(default=12, description='Length of character string for date-time as used in the WAM files. Possible values are: 10 (i.e. YYMMDDHHMM), 12 (i.e. YYMMDDHHMMSS) or 14 (i.e. YYYYMMDDHHMMSS) (SWAN default: `lwdate` = 12)')

format_str property

format_str

Functions

cmd

cmd() -> str
Source code in src/rompy_swan/components/boundary.py
def cmd(self) -> str:
    repr = f"BOUNDNEST2 WAMNEST fname='{self.fname}' {self.format_str}"
    if self.xgc is not None:
        repr += f" xgc={self.xgc}"
    if self.ygc is not None:
        repr += f" ygc={self.ygc}"
    repr += f" lwdate={self.lwdate}"
    return repr

BOUNDNEST3

Bases: BaseComponent

Boundary spectra from WAVEWATCHIII.

.. code-block:: text

BOUNDNEST3 WW3 'fname' FREE|UNFORMATTED ->CLOSED|OPEN [xgc] [ygc]

With this optional command a nested SWAN run can be carried out with the boundary conditions obtained from a coarse grid WAVEWATCH III run. The spectral frequencies and directions of the coarse grid run do not have to coincide with the frequencies and directions used in the nested SWAN run; SWAN will interpolate to these frequencies and directions in the nested run (see Section 2.6.3). The output files of WAVEWATCH III have to be created with the post-processor of WAVEWATCH III as output transfer files (formatted or unformatted) with WW_3 OUTP (output type 1 sub type 3) at the locations along the nest boundary (i.e. computational grid points in WAVEWATCH III). These locations are equal to the corner points of the SWAN nested grid and optionally also distributed between the corner points of the SWAN nested grid (the boundary of the WAVEWATCH III nested grid need not be closed and may cover land). The locations should be output by WAVEWATCH III in sequence (going along the nest boundary, clockwise or counterclockwise). Note that SWAN will accept output of a WAVEWATCH III output location only if the SWAN grid point on the nest boundary lies within a rectangle between two consecutive WAVEWATCH III output locations with a width equal to 0.1 times the distance between these output locations on either side of the line between these WAVEWATCH III output locations. This BOUNDNEST3 command is not available for 1D computations. A nested SWAN run may use either Cartesian or spherical coordinates. A curvilinear grid may be used in the nested grid but the boundaries of this nest should conform to the rectangular course grid nest boundaries.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.boundary import BOUNDNEST3
boundary = BOUNDNEST3(
    fname="boundary.ww3",
    format="free",
    rectangle="closed",
)
print(boundary.render())
Source code in src/rompy_swan/components/boundary.py
class BOUNDNEST3(BaseComponent):
    """Boundary spectra from WAVEWATCHIII.

    .. code-block:: text

        BOUNDNEST3 WW3 'fname' FREE|UNFORMATTED ->CLOSED|OPEN [xgc] [ygc]

    With this optional command a nested SWAN run can be carried out with the boundary
    conditions obtained from a coarse grid WAVEWATCH III run. The spectral frequencies
    and directions of the coarse grid run do not have to coincide with the frequencies
    and directions used in the nested SWAN run; SWAN will interpolate to these
    frequencies and directions in the nested run (see Section 2.6.3). The output files
    of WAVEWATCH III have to be created with the post-processor of WAVEWATCH III as
    output transfer files (formatted or unformatted) with WW_3 OUTP (output type 1 sub
    type 3) at the locations along the nest boundary (i.e. computational grid points in
    WAVEWATCH III). These locations are equal to the corner points of the SWAN nested
    grid and optionally also distributed between the corner points of the SWAN nested
    grid (the boundary of the WAVEWATCH III nested grid need not be closed and may
    cover land). The locations should be output by WAVEWATCH III in sequence (going
    along the nest boundary, clockwise or counterclockwise). Note that SWAN will accept
    output of a WAVEWATCH III output location only if the SWAN grid point on the nest
    boundary lies within a rectangle between two consecutive WAVEWATCH III output
    locations with a width equal to 0.1 times the distance between these output
    locations on either side of the line between these WAVEWATCH III output locations.
    This BOUNDNEST3 command is not available for 1D computations. A nested SWAN run may
    use either Cartesian or spherical coordinates. A curvilinear grid may be used in
    the nested grid but the boundaries of this nest should conform to the rectangular
    course grid nest boundaries.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.boundary import BOUNDNEST3
        boundary = BOUNDNEST3(
            fname="boundary.ww3",
            format="free",
            rectangle="closed",
        )
        print(boundary.render())

    """

    model_type: Literal["boundnest3", "BOUNDNEST3"] = Field(
        default="boundnest3",
        description="Model type discriminator",
    )
    fname: str = Field(
        description=(
            "The name of the file that contains the spectra computed by WAVEWATCH III"
        ),
        min_length=1,
        max_length=36,
    )
    format: Literal["unformatted", "free"] = Field(
        description=(
            "Format of the WW3 file. `unformatted`: The input WW3 files are binary, "
            "`free`: The input WW3 files are formatted"
        ),
    )
    rectangle: Literal["closed", "open"] = Field(
        default="closed",
        description=(
            "Boundary is defined over a closed (default) or an open rectangle. "
            "Boundary generated from the NESTOUT command is aways closed"
        ),
    )
    xgc: Optional[float] = Field(
        default=None,
        description=(
            "If SWAN is used with Cartesian coordinates: longitude of south-west "
            "corner of SWAN computational grid (in degrees); if the south-west "
            "corner of the nest in the WAM computation is on land this value is "
            "required. If SWAN is used with spherical coordinates then `xgc` is "
            "ignored by SWAN (SWAN default: the location of the first spectrum "
            "encountered in the nest file. "
        ),
    )
    ygc: Optional[float] = Field(
        default=None,
        description=(
            "If SWAN is used with Cartesian coordinates: latitude of south-west "
            "corner of SWAN computational grid (in degrees); if the south-west "
            "corner of the nest in the WAM computation is on land this value is "
            "required. If SWAN is used with spherical coordinates then `ygc` is "
            "ignored by SWAN (SWAN default: the location of the first spectrum "
            "encountered in the nest file. "
        ),
    )

    def cmd(self) -> str:
        repr = f"BOUNDNEST3 WW3 fname='{self.fname}' {self.format.upper()} "
        repr += f"{self.rectangle.upper()}"
        if self.xgc is not None:
            repr += f" xgc={self.xgc}"
        if self.ygc is not None:
            repr += f" ygc={self.ygc}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['boundnest3', 'BOUNDNEST3'] = Field(default='boundnest3', description='Model type discriminator')

fname class-attribute instance-attribute

fname: str = Field(description='The name of the file that contains the spectra computed by WAVEWATCH III', min_length=1, max_length=36)

format class-attribute instance-attribute

format: Literal['unformatted', 'free'] = Field(description='Format of the WW3 file. `unformatted`: The input WW3 files are binary, `free`: The input WW3 files are formatted')

rectangle class-attribute instance-attribute

rectangle: Literal['closed', 'open'] = Field(default='closed', description='Boundary is defined over a closed (default) or an open rectangle. Boundary generated from the NESTOUT command is aways closed')

xgc class-attribute instance-attribute

xgc: Optional[float] = Field(default=None, description='If SWAN is used with Cartesian coordinates: longitude of south-west corner of SWAN computational grid (in degrees); if the south-west corner of the nest in the WAM computation is on land this value is required. If SWAN is used with spherical coordinates then `xgc` is ignored by SWAN (SWAN default: the location of the first spectrum encountered in the nest file. ')

ygc class-attribute instance-attribute

ygc: Optional[float] = Field(default=None, description='If SWAN is used with Cartesian coordinates: latitude of south-west corner of SWAN computational grid (in degrees); if the south-west corner of the nest in the WAM computation is on land this value is required. If SWAN is used with spherical coordinates then `ygc` is ignored by SWAN (SWAN default: the location of the first spectrum encountered in the nest file. ')

Functions

cmd

cmd() -> str
Source code in src/rompy_swan/components/boundary.py
def cmd(self) -> str:
    repr = f"BOUNDNEST3 WW3 fname='{self.fname}' {self.format.upper()} "
    repr += f"{self.rectangle.upper()}"
    if self.xgc is not None:
        repr += f" xgc={self.xgc}"
    if self.ygc is not None:
        repr += f" ygc={self.ygc}"
    return repr

INITIAL

Bases: BaseComponent

Initial conditions.

.. code-block:: text

INITIAL -> DEFAULT|ZERO|PAR|HOTSTART

This command can be used to specify the initial values for a stationary (INITIAL HOTSTART only) or nonstationary computation. The initial values thus specified override the default initialization (see Section 2.6.3). Note that it is possible to obtain an initial state by carrying out a previous stationary or nonstationary computation.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.boundary import INITIAL
init = INITIAL()
print(init.render())
init = INITIAL(
    kind=dict(model_type="hotmultiple", fname="hotstart.swn", format="free")
)
print(init.render())
Source code in src/rompy_swan/components/boundary.py
class INITIAL(BaseComponent):
    """Initial conditions.

    .. code-block:: text

        INITIAL -> DEFAULT|ZERO|PAR|HOTSTART

    This command can be used to specify the initial values for a stationary (INITIAL
    HOTSTART only) or nonstationary computation. The initial values thus specified
    override the default initialization (see Section 2.6.3). Note that it is possible
    to obtain an initial state by carrying out a previous stationary or nonstationary
    computation.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.boundary import INITIAL
        init = INITIAL()
        print(init.render())
        init = INITIAL(
            kind=dict(model_type="hotmultiple", fname="hotstart.swn", format="free")
        )
        print(init.render())

    """

    model_type: Literal["initial", "INITIAL"] = Field(
        default="initial",
        description="Model type discriminator",
    )
    kind: DEFAULT | ZERO | PAR | HOTSINGLE | HOTMULTIPLE = Field(
        default_factory=DEFAULT,
        description="Initial condition type",
    )

    def cmd(self) -> str:
        repr = f"INITIAL {self.kind.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['initial', 'INITIAL'] = Field(default='initial', description='Model type discriminator')

kind class-attribute instance-attribute

kind: DEFAULT | ZERO | PAR | HOTSINGLE | HOTMULTIPLE = Field(default_factory=DEFAULT, description='Initial condition type')

Functions

cmd

cmd() -> str
Source code in src/rompy_swan/components/boundary.py
def cmd(self) -> str:
    repr = f"INITIAL {self.kind.render()}"
    return repr

Physics Components

GEN1

Bases: BaseComponent

First generation source terms GEN1.

.. code-block:: text

GEN1 [cf10] [cf20] [cf30] [cf40] [edmlpm] [cdrag] [umin] [cfpm]

With this command the user indicates that SWAN should run in first-generation mode (see Scientific/Technical documentation).

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import GEN1
gen = GEN1()
print(gen.render())
kwargs = dict(
    cf10=188.0,
    cf20=0.59,
    cf30=0.12,
    cf40=250.0,
    edmlpm=0.0036,
    cdrag=0.0012,
    umin=1.0,
    cfpm=0.13
)
gen = GEN1(**kwargs)
print(gen.render())
Source code in src/rompy_swan/components/physics.py
class GEN1(BaseComponent):
    """First generation source terms GEN1.

    .. code-block:: text

        GEN1 [cf10] [cf20] [cf30] [cf40] [edmlpm] [cdrag] [umin] [cfpm]

    With this command the user indicates that SWAN should run in first-generation mode
    (see Scientific/Technical documentation).

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import GEN1
        gen = GEN1()
        print(gen.render())
        kwargs = dict(
            cf10=188.0,
            cf20=0.59,
            cf30=0.12,
            cf40=250.0,
            edmlpm=0.0036,
            cdrag=0.0012,
            umin=1.0,
            cfpm=0.13
        )
        gen = GEN1(**kwargs)
        print(gen.render())

    """

    model_type: Literal["gen1", "GEN1"] = Field(
        default="gen1", description="Model type discriminator"
    )
    cf10: Optional[float] = Field(
        default=None,
        description="Controls the linear wave growth (SWAN default: 188.0)",
    )
    cf20: Optional[float] = Field(
        default=None,
        description="Controls the exponential wave growth (SWAN default: 0.59)",
    )
    cf30: Optional[float] = Field(
        default=None,
        description="Controls the exponential wave growth (SWAN default: 0.12)",
    )
    cf40: Optional[float] = Field(
        default=None,
        description=(
            "Controls the dissipation rate, i.e., the time decay scale "
            "(SWAN default: 250.0)"
        ),
    )
    edmlpm: Optional[float] = Field(
        default=None,
        description=(
            "Maximum non-dimensionless energy density of the wind sea part of the "
            "spectrum according to Pierson Moskowitz (SWAN default: 0.0036)"
        ),
    )
    cdrag: Optional[float] = Field(
        default=None, description="Drag coefficient (SWAN default: 0.0012)"
    )
    umin: Optional[float] = Field(
        default=None,
        description=(
            "Minimum wind velocity (relative to current; all wind speeds "
            "are taken at 10 m above sea level) (SWAN default: 1)"
        ),
    )
    cfpm: Optional[float] = Field(
        default=None,
        description=(
            "Coefficient which determines the Pierson Moskowitz frequency: "
            "`delta_PM = 2pi g / U_10` (SWAN default: 0.13)"
        ),
    )

    def cmd(self):
        """Command line string for this component."""
        repr = "GEN1"
        if self.cf10 is not None:
            repr += f" cf10={self.cf10}"
        if self.cf20 is not None:
            repr += f" cf20={self.cf20}"
        if self.cf30 is not None:
            repr += f" cf30={self.cf30}"
        if self.cf40 is not None:
            repr += f" cf40={self.cf40}"
        if self.edmlpm is not None:
            repr += f" edmlpm={self.edmlpm}"
        if self.cdrag is not None:
            repr += f" cdrag={self.cdrag}"
        if self.umin is not None:
            repr += f" umin={self.umin}"
        if self.cfpm is not None:
            repr += f" cfpm={self.cfpm}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['gen1', 'GEN1'] = Field(default='gen1', description='Model type discriminator')

cf10 class-attribute instance-attribute

cf10: Optional[float] = Field(default=None, description='Controls the linear wave growth (SWAN default: 188.0)')

cf20 class-attribute instance-attribute

cf20: Optional[float] = Field(default=None, description='Controls the exponential wave growth (SWAN default: 0.59)')

cf30 class-attribute instance-attribute

cf30: Optional[float] = Field(default=None, description='Controls the exponential wave growth (SWAN default: 0.12)')

cf40 class-attribute instance-attribute

cf40: Optional[float] = Field(default=None, description='Controls the dissipation rate, i.e., the time decay scale (SWAN default: 250.0)')

edmlpm class-attribute instance-attribute

edmlpm: Optional[float] = Field(default=None, description='Maximum non-dimensionless energy density of the wind sea part of the spectrum according to Pierson Moskowitz (SWAN default: 0.0036)')

cdrag class-attribute instance-attribute

cdrag: Optional[float] = Field(default=None, description='Drag coefficient (SWAN default: 0.0012)')

umin class-attribute instance-attribute

umin: Optional[float] = Field(default=None, description='Minimum wind velocity (relative to current; all wind speeds are taken at 10 m above sea level) (SWAN default: 1)')

cfpm class-attribute instance-attribute

cfpm: Optional[float] = Field(default=None, description='Coefficient which determines the Pierson Moskowitz frequency: `delta_PM = 2pi g / U_10` (SWAN default: 0.13)')

Functions

cmd

cmd()

Command line string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self):
    """Command line string for this component."""
    repr = "GEN1"
    if self.cf10 is not None:
        repr += f" cf10={self.cf10}"
    if self.cf20 is not None:
        repr += f" cf20={self.cf20}"
    if self.cf30 is not None:
        repr += f" cf30={self.cf30}"
    if self.cf40 is not None:
        repr += f" cf40={self.cf40}"
    if self.edmlpm is not None:
        repr += f" edmlpm={self.edmlpm}"
    if self.cdrag is not None:
        repr += f" cdrag={self.cdrag}"
    if self.umin is not None:
        repr += f" umin={self.umin}"
    if self.cfpm is not None:
        repr += f" cfpm={self.cfpm}"
    return repr

GEN2

Bases: GEN1

Second generation source terms GEN2.

.. code-block:: text

GEN2 [cf10] [cf20] [cf30] [cf40] [cf50] [cf60] [edmlpm] [cdrag] [umin] [cfpm]

With this command the user indicates that SWAN should run in second-generation mode (see Scientific/Technical documentation).

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import GEN2
gen = GEN2()
print(gen.render())
kwargs = dict(
    cf10=188.0,
    cf20=0.59,
    cf30=0.12,
    cf40=250.0,
    cf50=0.0023,
    cf60=-0.223,
    edmlpm=0.0036,
    cdrag=0.0012,
    umin=1.0,
    cfpm=0.13
)
gen = GEN2(**kwargs)
print(gen.render())
Source code in src/rompy_swan/components/physics.py
class GEN2(GEN1):
    """Second generation source terms GEN2.

    .. code-block:: text

        GEN2 [cf10] [cf20] [cf30] [cf40] [cf50] [cf60] [edmlpm] [cdrag] [umin] [cfpm]

    With this command the user indicates that SWAN should run in second-generation mode
    (see Scientific/Technical documentation).

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import GEN2
        gen = GEN2()
        print(gen.render())
        kwargs = dict(
            cf10=188.0,
            cf20=0.59,
            cf30=0.12,
            cf40=250.0,
            cf50=0.0023,
            cf60=-0.223,
            edmlpm=0.0036,
            cdrag=0.0012,
            umin=1.0,
            cfpm=0.13
        )
        gen = GEN2(**kwargs)
        print(gen.render())

    """

    model_type: Literal["gen2", "GEN2"] = Field(
        default="gen2", description="Model type discriminator"
    )
    cf50: Optional[float] = Field(
        default=None,
        description=(
            "Controls the spectral energy scale of the limit spectrum "
            "(SWAN default: 0.0023)"
        ),
    )
    cf60: Optional[float] = Field(
        default=None,
        description=(
            "Ccontrols the spectral energy scale of the limit spectrum "
            "(SWAN default: -0.223"
        ),
    )

    def cmd(self):
        """Command line string for this component."""
        repr = "GEN2"
        if self.cf10 is not None:
            repr += f" cf10={self.cf10}"
        if self.cf20 is not None:
            repr += f" cf20={self.cf20}"
        if self.cf30 is not None:
            repr += f" cf30={self.cf30}"
        if self.cf40 is not None:
            repr += f" cf40={self.cf40}"
        if self.cf50 is not None:
            repr += f" cf50={self.cf50}"
        if self.cf60 is not None:
            repr += f" cf60={self.cf60}"
        if self.edmlpm is not None:
            repr += f" edmlpm={self.edmlpm}"
        if self.cdrag is not None:
            repr += f" cdrag={self.cdrag}"
        if self.umin is not None:
            repr += f" umin={self.umin}"
        if self.cfpm is not None:
            repr += f" cfpm={self.cfpm}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['gen2', 'GEN2'] = Field(default='gen2', description='Model type discriminator')

cf50 class-attribute instance-attribute

cf50: Optional[float] = Field(default=None, description='Controls the spectral energy scale of the limit spectrum (SWAN default: 0.0023)')

cf60 class-attribute instance-attribute

cf60: Optional[float] = Field(default=None, description='Ccontrols the spectral energy scale of the limit spectrum (SWAN default: -0.223')

Functions

cmd

cmd()

Command line string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self):
    """Command line string for this component."""
    repr = "GEN2"
    if self.cf10 is not None:
        repr += f" cf10={self.cf10}"
    if self.cf20 is not None:
        repr += f" cf20={self.cf20}"
    if self.cf30 is not None:
        repr += f" cf30={self.cf30}"
    if self.cf40 is not None:
        repr += f" cf40={self.cf40}"
    if self.cf50 is not None:
        repr += f" cf50={self.cf50}"
    if self.cf60 is not None:
        repr += f" cf60={self.cf60}"
    if self.edmlpm is not None:
        repr += f" edmlpm={self.edmlpm}"
    if self.cdrag is not None:
        repr += f" cdrag={self.cdrag}"
    if self.umin is not None:
        repr += f" umin={self.umin}"
    if self.cfpm is not None:
        repr += f" cfpm={self.cfpm}"
    return repr

GEN3

Bases: BaseComponent

Third generation source terms GEN3.

.. code-block:: text

GEN3 JANSSEN|KOMEN|->WESTHUYSEN|ST6 AGROW [a]

With this command the user indicates that SWAN should run in third-generation mode for wind input, quadruplet interactions and whitecapping.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import GEN3
gen = GEN3(
    source_terms=dict(
        model_type="westhuysen",
        wind_drag="wu",
        agrow=True,
    ),
)
print(gen.render())
from rompy_swan.subcomponents.physics import ST6C1
gen = GEN3(source_terms=ST6C1())
print(gen.render())
Source code in src/rompy_swan/components/physics.py
class GEN3(BaseComponent):
    """Third generation source terms GEN3.

    .. code-block:: text

        GEN3 JANSSEN|KOMEN|->WESTHUYSEN|ST6 AGROW [a]

    With this command the user indicates that SWAN should run in third-generation mode
    for wind input, quadruplet interactions and whitecapping.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import GEN3
        gen = GEN3(
            source_terms=dict(
                model_type="westhuysen",
                wind_drag="wu",
                agrow=True,
            ),
        )
        print(gen.render())
        from rompy_swan.subcomponents.physics import ST6C1
        gen = GEN3(source_terms=ST6C1())
        print(gen.render())

    """

    model_type: Literal["gen3", "GEN3"] = Field(
        default="gen3", description="Model type discriminator"
    )
    source_terms: SOURCE_TERMS = Field(
        default_factory=WESTHUYSEN,
        description="SWAN source terms to be used (SWAN default: WESTHUYSEN)",
        discriminator="model_type",
    )

    def cmd(self):
        """Command line string for this component."""
        repr = f"GEN3 {self.source_terms.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['gen3', 'GEN3'] = Field(default='gen3', description='Model type discriminator')

source_terms class-attribute instance-attribute

source_terms: SOURCE_TERMS = Field(default_factory=WESTHUYSEN, description='SWAN source terms to be used (SWAN default: WESTHUYSEN)', discriminator='model_type')

Functions

cmd

cmd()

Command line string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self):
    """Command line string for this component."""
    repr = f"GEN3 {self.source_terms.render()}"
    return repr

BREAKING_CONSTANT

Bases: BaseComponent

Constant wave breaking index.

.. code-block:: text

BREAKING CONSTANT [alpha] [gamma]

Indicates that a constant breaker index is to be used.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import BREAKING_CONSTANT
breaking = BREAKING_CONSTANT()
print(breaking.render())
breaking = BREAKING_CONSTANT(alpha=1.0, gamma=0.73)
print(breaking.render())
Source code in src/rompy_swan/components/physics.py
class BREAKING_CONSTANT(BaseComponent):
    """Constant wave breaking index.

    .. code-block:: text

        BREAKING CONSTANT [alpha] [gamma]

    Indicates that a constant breaker index is to be used.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import BREAKING_CONSTANT
        breaking = BREAKING_CONSTANT()
        print(breaking.render())
        breaking = BREAKING_CONSTANT(alpha=1.0, gamma=0.73)
        print(breaking.render())

    """

    model_type: Literal["constant", "CONSTANT"] = Field(
        default="constant", description="Model type discriminator"
    )
    alpha: Optional[float] = Field(
        default=None,
        description=(
            "Proportionality coefficient of the rate of dissipation "
            "(SWAN default: 1.0)"
        ),
    )
    gamma: Optional[float] = Field(
        default=None,
        description=(
            "The breaker index, i.e. the ratio of maximum individual wave height "
            "over depth (SWAN default: 0.73)"
        ),
    )

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = "BREAKING CONSTANT"
        if self.alpha is not None:
            repr += f" alpha={self.alpha}"
        if self.gamma is not None:
            repr += f" gamma={self.gamma}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['constant', 'CONSTANT'] = Field(default='constant', description='Model type discriminator')

alpha class-attribute instance-attribute

alpha: Optional[float] = Field(default=None, description='Proportionality coefficient of the rate of dissipation (SWAN default: 1.0)')

gamma class-attribute instance-attribute

gamma: Optional[float] = Field(default=None, description='The breaker index, i.e. the ratio of maximum individual wave height over depth (SWAN default: 0.73)')

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = "BREAKING CONSTANT"
    if self.alpha is not None:
        repr += f" alpha={self.alpha}"
    if self.gamma is not None:
        repr += f" gamma={self.gamma}"
    return repr

BREAKING_BKD

Bases: BaseComponent

Variable wave breaking index.

.. code-block:: text

BREAKING BKD [alpha] [gamma0] [a1] [a2] [a3]

Indicates that the breaker index scales with both the bottom slope (beta) and the dimensionless depth (kd).

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import BREAKING_BKD
breaking = BREAKING_BKD()
print(breaking.render())
breaking = BREAKING_BKD(alpha=1.0, gamma0=0.54, a1=7.59, a2=-8.06, a3=8.09)
print(breaking.render())
Source code in src/rompy_swan/components/physics.py
class BREAKING_BKD(BaseComponent):
    """Variable wave breaking index.

    .. code-block:: text

        BREAKING BKD [alpha] [gamma0] [a1] [a2] [a3]

    Indicates that the breaker index scales with both the bottom slope (`beta`)
    and the dimensionless depth (kd).

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import BREAKING_BKD
        breaking = BREAKING_BKD()
        print(breaking.render())
        breaking = BREAKING_BKD(alpha=1.0, gamma0=0.54, a1=7.59, a2=-8.06, a3=8.09)
        print(breaking.render())

    """

    model_type: Literal["bkd", "BKD"] = Field(
        default="bkd", description="Model type discriminator"
    )
    alpha: Optional[float] = Field(
        default=None,
        description=(
            "Proportionality coefficient of the rate of dissipation "
            "(SWAN default: 1.0)"
        ),
    )
    gamma0: Optional[float] = Field(
        default=None,
        description="The reference $gamma$ for horizontal slopes (SWAN default: 0.54)",
    )
    a1: Optional[float] = Field(
        default=None,
        description=(
            "First tunable coefficient for the breaker index (SWAN default: 7.59)"
        ),
    )
    a2: Optional[float] = Field(
        default=None,
        description=(
            "Second tunable coefficient for the breaker index (SWAN default: -8.06)"
        ),
    )
    a3: Optional[float] = Field(
        default=None,
        description=(
            "Third tunable coefficient for the breaker index (SWAN default: 8.09)"
        ),
    )

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = "BREAKING BKD"
        if self.alpha is not None:
            repr += f" alpha={self.alpha}"
        if self.gamma0 is not None:
            repr += f" gamma0={self.gamma0}"
        if self.a1 is not None:
            repr += f" a1={self.a1}"
        if self.a2 is not None:
            repr += f" a2={self.a2}"
        if self.a3 is not None:
            repr += f" a3={self.a3}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['bkd', 'BKD'] = Field(default='bkd', description='Model type discriminator')

alpha class-attribute instance-attribute

alpha: Optional[float] = Field(default=None, description='Proportionality coefficient of the rate of dissipation (SWAN default: 1.0)')

gamma0 class-attribute instance-attribute

gamma0: Optional[float] = Field(default=None, description='The reference $gamma$ for horizontal slopes (SWAN default: 0.54)')

a1 class-attribute instance-attribute

a1: Optional[float] = Field(default=None, description='First tunable coefficient for the breaker index (SWAN default: 7.59)')

a2 class-attribute instance-attribute

a2: Optional[float] = Field(default=None, description='Second tunable coefficient for the breaker index (SWAN default: -8.06)')

a3 class-attribute instance-attribute

a3: Optional[float] = Field(default=None, description='Third tunable coefficient for the breaker index (SWAN default: 8.09)')

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = "BREAKING BKD"
    if self.alpha is not None:
        repr += f" alpha={self.alpha}"
    if self.gamma0 is not None:
        repr += f" gamma0={self.gamma0}"
    if self.a1 is not None:
        repr += f" a1={self.a1}"
    if self.a2 is not None:
        repr += f" a2={self.a2}"
    if self.a3 is not None:
        repr += f" a3={self.a3}"
    return repr

FRICTION_JONSWAP

Bases: BaseComponent

Hasselmann et al. (1973) Jonswap friction.

.. code-block:: text

FRICTION JONSWAP CONSTANT [cfjon]

Indicates that the semi-empirical expression derived from the JONSWAP results for bottom friction dissipation (Hasselmann et al., 1973, JONSWAP) should be activated. This option is default.

References

Hasselmann, K., Barnett, T.P., Bouws, E., Carlson, H., Cartwright, D.E., Enke, K., Ewing, J.A., Gienapp, A., Hasselmann, D.E., Kruseman, P. and Meerburg, A., 1973. Measurements of wind-wave growth and swell decay during the Joint North Sea Wave Project (JONSWAP). Deutches Hydrographisches Institut, Hamburg, Germany, Rep. No. 12, 95 pp.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import FRICTION_JONSWAP
friction = FRICTION_JONSWAP()
print(friction.render())
friction = FRICTION_JONSWAP(cfjon=0.038)
print(friction.render())

TODO: Implement VARIABLE option?

Source code in src/rompy_swan/components/physics.py
class FRICTION_JONSWAP(BaseComponent):
    """Hasselmann et al. (1973) Jonswap friction.

    .. code-block:: text

        FRICTION JONSWAP CONSTANT [cfjon]

    Indicates that the semi-empirical expression derived from the JONSWAP results for
    bottom friction dissipation (Hasselmann et al., 1973, JONSWAP) should be activated.
    This option is default.

    References
    ----------
    Hasselmann, K., Barnett, T.P., Bouws, E., Carlson, H., Cartwright, D.E., Enke, K.,
    Ewing, J.A., Gienapp, A., Hasselmann, D.E., Kruseman, P. and Meerburg, A., 1973.
    Measurements of wind-wave growth and swell decay during the Joint North Sea Wave
    Project (JONSWAP). Deutches Hydrographisches Institut, Hamburg, Germany,
    Rep. No. 12, 95 pp.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import FRICTION_JONSWAP
        friction = FRICTION_JONSWAP()
        print(friction.render())
        friction = FRICTION_JONSWAP(cfjon=0.038)
        print(friction.render())

    TODO: Implement VARIABLE option?

    """

    model_type: Literal["jonswap", "JONSWAP"] = Field(
        default="jonswap", description="Model type discriminator"
    )
    cfjon: Optional[float] = Field(
        default=None,
        description="Coefficient of the JONSWAP formulation (SWAN default: 0.038)",
    )

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = "FRICTION JONSWAP CONSTANT"
        if self.cfjon is not None:
            repr += f" cfjon={self.cfjon}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['jonswap', 'JONSWAP'] = Field(default='jonswap', description='Model type discriminator')

cfjon class-attribute instance-attribute

cfjon: Optional[float] = Field(default=None, description='Coefficient of the JONSWAP formulation (SWAN default: 0.038)')

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = "FRICTION JONSWAP CONSTANT"
    if self.cfjon is not None:
        repr += f" cfjon={self.cfjon}"
    return repr

FRICTION_COLLINS

Bases: BaseComponent

Collins (1972) friction.

.. code-block:: text

FRICTION COLLINS [cfw]

Note that cfw is allowed to vary over the computational region; in that case use the commands INPGRID FRICTION and READINP FRICTION to define and read the friction data. This command FRICTION is still required to define the type of friction expression. The value of cfw in this command is then not required (it will be ignored).

References

Collins, J.I., 1972. Prediction of shallow-water spectra. Journal of Geophysical Research, 77(15), pp.2693-2707.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import FRICTION_COLLINS
friction = FRICTION_COLLINS()
print(friction.render())
friction = FRICTION_COLLINS(cfw=0.038)
print(friction.render())
Source code in src/rompy_swan/components/physics.py
class FRICTION_COLLINS(BaseComponent):
    """Collins (1972) friction.

    .. code-block:: text

        FRICTION COLLINS [cfw]

    Note that `cfw` is allowed to vary over the computational region; in that case use
    the commands INPGRID FRICTION and READINP FRICTION to define and read the friction
    data. This command FRICTION is still required to define the type of friction
    expression. The value of `cfw` in this command is then not required (it will be
    ignored).

    References
    ----------
    Collins, J.I., 1972. Prediction of shallow-water spectra. Journal of Geophysical
    Research, 77(15), pp.2693-2707.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import FRICTION_COLLINS
        friction = FRICTION_COLLINS()
        print(friction.render())
        friction = FRICTION_COLLINS(cfw=0.038)
        print(friction.render())

    """

    model_type: Literal["collins", "COLLINS"] = Field(
        default="collins", description="Model type discriminator"
    )
    cfw: Optional[float] = Field(
        default=None,
        description="Collins bottom friction coefficient (SWAN default: 0.015)",
    )

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = "FRICTION COLLINS"
        if self.cfw is not None:
            repr += f" cfw={self.cfw}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['collins', 'COLLINS'] = Field(default='collins', description='Model type discriminator')

cfw class-attribute instance-attribute

cfw: Optional[float] = Field(default=None, description='Collins bottom friction coefficient (SWAN default: 0.015)')

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = "FRICTION COLLINS"
    if self.cfw is not None:
        repr += f" cfw={self.cfw}"
    return repr

FRICTION_MADSEN

Bases: BaseComponent

Madsen et al (1988) friction.

.. code-block:: text

FRICTION MADSEN [kn]

Note that kn is allowed to vary over the computational region; in that case use the commands INPGRID FRICTION and READINP FRICTION to define and read the friction data. This command FRICTION is still required to define the type of friction expression. The value of kn in this command is then not required (it will be ignored).

References

Madsen, O.S., Poon, Y.K. and Graber, H.C., 1988. Spectral wave attenuation by bottom friction: Theory. In Coastal engineering 1988 (pp. 492-504).

Madsen, O.S. and Rosengaus, M.M., 1988. Spectral wave attenuation by bottom friction: Experiments. In Coastal Engineering 1988 (pp. 849-857).

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.physics import FRICTION_MADSEN
friction = FRICTION_MADSEN()
print(friction.render())
friction = FRICTION_MADSEN(kn=0.038)
print(friction.render())
Source code in src/rompy_swan/components/physics.py
class FRICTION_MADSEN(BaseComponent):
    """Madsen et al (1988) friction.

    .. code-block:: text

        FRICTION MADSEN [kn]

    Note that `kn` is allowed to vary over the computational region; in that case use
    the commands INPGRID FRICTION and READINP FRICTION to define and read the friction
    data. This command FRICTION is still required to define the type of friction
    expression. The value of `kn` in this command is then not required (it will be
    ignored).

    References
    ----------
    Madsen, O.S., Poon, Y.K. and Graber, H.C., 1988. Spectral wave attenuation by
    bottom friction: Theory. In Coastal engineering 1988 (pp. 492-504).

    Madsen, O.S. and Rosengaus, M.M., 1988. Spectral wave attenuation by bottom
    friction: Experiments. In Coastal Engineering 1988 (pp. 849-857).

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.physics import FRICTION_MADSEN
        friction = FRICTION_MADSEN()
        print(friction.render())
        friction = FRICTION_MADSEN(kn=0.038)
        print(friction.render())

    """

    model_type: Literal["madsen", "MADSEN"] = Field(
        default="madsen", description="Model type discriminator"
    )
    kn: Optional[float] = Field(
        default=None,
        description=(
            "equivalent roughness length scale of the bottom (in m) "
            "(SWAN default: 0.05)"
        ),
    )

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = "FRICTION MADSEN"
        if self.kn is not None:
            repr += f" kn={self.kn}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['madsen', 'MADSEN'] = Field(default='madsen', description='Model type discriminator')

kn class-attribute instance-attribute

kn: Optional[float] = Field(default=None, description='equivalent roughness length scale of the bottom (in m) (SWAN default: 0.05)')

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/physics.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = "FRICTION MADSEN"
    if self.kn is not None:
        repr += f" kn={self.kn}"
    return repr

Numerics Components

PROP

Bases: BaseComponent

Propagation scheme.

.. code-block:: text

PROP BSTB|GSE

Notes

  • The scheme defaults to S&L and SORDUP for nonstationary and stationary simulations if not specified.
  • All schemes (BSBT, SORDUP and S&L) can be used in combination with curvilinear grids. With the higher order schemes (S&L and SORDUP) it is important to use a gradually varying grid otherwise there may be a severe loss of accuracy. If sharp transitions in the grid cannot be avoided it is safer to use the BSBT scheme.
  • In the computation with unstructured meshes, a lowest order upwind scheme will be employed. This scheme is very robust but rather diffusive. This may only be significant for the case when swell waves propagate over relative large distances (in the order of thousands of kilometers) within the model domain. However and most fortunately, in such a case this will alleviate the garden-sprinkler effect.
  • Alleviating the garden-sprinkler effect by adding some diffusion makes the SWAN computation conditionally stable. You can either use (i) a smaller time step, (ii) a lower value of waveage, (iii) better resolution in the directional space, or (iv) worse resolution in the geographic space, in order of preference, to make the model stable when necessary.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.numerics import PROP
prop = PROP()
print(prop.render())
prop = PROP(scheme=dict(model_type="bsbt"))
print(prop.render())
prop = PROP(
    scheme=dict(
        model_type="gse",
        waveage=dict(delt="PT5H", dfmt="hr"),
    ),
)
print(prop.render())
Source code in src/rompy_swan/components/numerics.py
class PROP(BaseComponent):
    """Propagation scheme.

    .. code-block:: text

        PROP BSTB|GSE

    Notes
    -----
    * The scheme defaults to `S&L` and `SORDUP` for nonstationary and stationary
      simulations if not specified.
    * All schemes (BSBT, SORDUP and S&L) can be used in combination with curvilinear
      grids. With the higher order schemes (S&L and SORDUP) it is important to use a
      gradually varying grid otherwise there may be a severe loss of accuracy. If sharp
      transitions in the grid cannot be avoided it is safer to use the BSBT scheme.
    * In the computation with unstructured meshes, a lowest order upwind scheme will be
      employed. This scheme is very robust but rather diffusive. This may only be
      significant for the case when swell waves propagate over relative large distances
      (in the order of thousands of kilometers) within the model domain. However and
      most fortunately, in such a case this will alleviate the garden-sprinkler effect.
    * Alleviating the garden-sprinkler effect by adding some diffusion makes the SWAN
      computation conditionally stable. You can either use (i) a smaller time step,
      (ii) a lower value of `waveage`, (iii) better resolution in the directional
      space, or (iv) worse resolution in the geographic space, in order of preference,
      to make the model stable when necessary.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.numerics import PROP
        prop = PROP()
        print(prop.render())
        prop = PROP(scheme=dict(model_type="bsbt"))
        print(prop.render())
        prop = PROP(
            scheme=dict(
                model_type="gse",
                waveage=dict(delt="PT5H", dfmt="hr"),
            ),
        )
        print(prop.render())

    """

    model_type: Literal["prop", "PROP"] = Field(
        default="prop", description="Model type discriminator"
    )
    scheme: Optional[PROP_TYPE] = Field(
        default=None,
        description=(
            "Propagation scheme, by default S&L for nonstationary and SORDUP for "
            "stationary computation."
        ),
    )

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = "PROP"
        if self.scheme is not None:
            repr += f" {self.scheme.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['prop', 'PROP'] = Field(default='prop', description='Model type discriminator')

scheme class-attribute instance-attribute

scheme: Optional[PROP_TYPE] = Field(default=None, description='Propagation scheme, by default S&L for nonstationary and SORDUP for stationary computation.')

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/numerics.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = "PROP"
    if self.scheme is not None:
        repr += f" {self.scheme.render()}"
    return repr

NUMERIC

Bases: BaseComponent

Numerical properties.

.. code-block:: text

NUMeric ( STOPC [dabs] [drel] [curvat] [npnts] ->STAT|NONSTAT [limiter] ) &
    ( DIRimpl [cdd] ) ( SIGIMpl [css] [eps2] [outp] [niter] ) &
    ( CTheta [cfl] ) ( CSigma [cfl] ) ( SETUP [eps2] [outp] [niter] )

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.numerics import NUMERIC
numeric = NUMERIC()
print(numeric.render())
numeric = NUMERIC(
    stop=dict(
        model_type="stopc",
        dabs=0.05,
        drel=0.01,
        curvat=0.05,
        npnts=99.5,
    ),
    dirimpl=dict(cdd=0.5),
    sigimpl=dict(css=0.5, eps2=1e-4, outp=0, niter=20),
    ctheta=dict(cfl=0.9),
    csigma=dict(cfl=0.9),
    setup=dict(eps2=1e-4, outp=0, niter=20),
)
print(numeric.render())
Source code in src/rompy_swan/components/numerics.py
class NUMERIC(BaseComponent):
    """Numerical properties.

    .. code-block:: text

        NUMeric ( STOPC [dabs] [drel] [curvat] [npnts] ->STAT|NONSTAT [limiter] ) &
            ( DIRimpl [cdd] ) ( SIGIMpl [css] [eps2] [outp] [niter] ) &
            ( CTheta [cfl] ) ( CSigma [cfl] ) ( SETUP [eps2] [outp] [niter] )

    Examples
    --------
    .. ipython:: python
        :okwarning:

        from rompy_swan.components.numerics import NUMERIC
        numeric = NUMERIC()
        print(numeric.render())
        numeric = NUMERIC(
            stop=dict(
                model_type="stopc",
                dabs=0.05,
                drel=0.01,
                curvat=0.05,
                npnts=99.5,
            ),
            dirimpl=dict(cdd=0.5),
            sigimpl=dict(css=0.5, eps2=1e-4, outp=0, niter=20),
            ctheta=dict(cfl=0.9),
            csigma=dict(cfl=0.9),
            setup=dict(eps2=1e-4, outp=0, niter=20),
        )
        print(numeric.render())

    """

    model_type: Literal["numeric", "NUMERIC"] = Field(
        default="numeric", description="Model type discriminator"
    )
    stop: Optional[Union[STOPC, ACCUR]] = Field(
        default=None,
        description="Iteration termination criteria",
        discriminator="model_type",
    )
    dirimpl: Optional[DIRIMPL] = Field(
        default=None,
        description="Numerical scheme for refraction",
    )
    sigimpl: Optional[SIGIMPL] = Field(
        default=None,
        description="Frequency shifting accuracy",
    )
    ctheta: Optional[CTHETA] = Field(
        default=None,
        description="Prevents excessive directional turning",
    )
    csigma: Optional[CSIGMA] = Field(
        default=None,
        description="Prevents excessive frequency shifting",
    )
    setup: Optional[SETUP] = Field(
        default=None,
        description="Stop criteria in the computation of wave setup",
    )

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = "NUMERIC"
        if self.stop is not None:
            repr += f" {self.stop.render()}"
        if self.dirimpl is not None:
            repr += f" {self.dirimpl.render()}"
        if self.sigimpl is not None:
            repr += f" {self.sigimpl.render()}"
        if self.ctheta is not None:
            repr += f" {self.ctheta.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['numeric', 'NUMERIC'] = Field(default='numeric', description='Model type discriminator')

stop class-attribute instance-attribute

stop: Optional[Union[STOPC, ACCUR]] = Field(default=None, description='Iteration termination criteria', discriminator='model_type')

dirimpl class-attribute instance-attribute

dirimpl: Optional[DIRIMPL] = Field(default=None, description='Numerical scheme for refraction')

sigimpl class-attribute instance-attribute

sigimpl: Optional[SIGIMPL] = Field(default=None, description='Frequency shifting accuracy')

ctheta class-attribute instance-attribute

ctheta: Optional[CTHETA] = Field(default=None, description='Prevents excessive directional turning')

csigma class-attribute instance-attribute

csigma: Optional[CSIGMA] = Field(default=None, description='Prevents excessive frequency shifting')

setup class-attribute instance-attribute

setup: Optional[SETUP] = Field(default=None, description='Stop criteria in the computation of wave setup')

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/numerics.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = "NUMERIC"
    if self.stop is not None:
        repr += f" {self.stop.render()}"
    if self.dirimpl is not None:
        repr += f" {self.dirimpl.render()}"
    if self.sigimpl is not None:
        repr += f" {self.sigimpl.render()}"
    if self.ctheta is not None:
        repr += f" {self.ctheta.render()}"
    return repr

Output Components

BLOCK

Bases: BaseWrite

Write spatial distributions.

.. code-block:: text

BLOCK 'sname' ->HEADER|NOHEADER 'fname' (LAYOUT [idla]) < output > &
    [unit] (OUTPUT [tbegblk] [deltblk]) SEC|MIN|HR|DAY

With this optional command the user indicates that one or more spatial distributions should be written to a file.

Note

The SWAN special frames 'BOTTGRID' or 'COMPGRID' can be set with the sname field.

Note

The text of the header indicates run identification (see command PROJECT), time, frame or group name ('sname'), variable and unit. The number of header lines is 8.

Note

Cannot be used in 1D-mode.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.output import BLOCK
block = BLOCK(sname="outgrid", fname="./depth-frame.nc", output=["depth"])
print(block.render())
block = BLOCK(
    sname="COMPGRID",
    header=False,
    fname="./output-grid.nc",
    idla=3,
    output=["hsign", "hswell", "dir", "tps", "tm01", "watlev", "qp"],
    times=dict(
        tbeg="2012-01-01T00:00:00",
        delt="PT30M",
        tfmt=1,
        dfmt="min",
        suffix="",
    )
)
print(block.render())
Source code in src/rompy_swan/components/output.py
class BLOCK(BaseWrite):
    """Write spatial distributions.

    .. code-block:: text

        BLOCK 'sname' ->HEADER|NOHEADER 'fname' (LAYOUT [idla]) < output > &
            [unit] (OUTPUT [tbegblk] [deltblk]) SEC|MIN|HR|DAY

    With this optional command the user indicates that one or more spatial
    distributions should be written to a file.

    Note
    ----
    The SWAN special frames 'BOTTGRID' or 'COMPGRID' can be set with the `sname` field.

    Note
    ----
    The text of the header indicates run identification (see command `PROJECT`), time,
    frame or group name ('sname'), variable and unit. The number of header lines is 8.

    Note
    ----
    Cannot be used in 1D-mode.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.output import BLOCK
        block = BLOCK(sname="outgrid", fname="./depth-frame.nc", output=["depth"])
        print(block.render())
        block = BLOCK(
            sname="COMPGRID",
            header=False,
            fname="./output-grid.nc",
            idla=3,
            output=["hsign", "hswell", "dir", "tps", "tm01", "watlev", "qp"],
            times=dict(
                tbeg="2012-01-01T00:00:00",
                delt="PT30M",
                tfmt=1,
                dfmt="min",
                suffix="",
            )
        )
        print(block.render())

    """

    model_type: Literal["block", "BLOCK"] = Field(
        default="block", description="Model type discriminator"
    )
    header: Optional[bool] = Field(
        default=None,
        description=(
            "Indicate if the output should be written to a file with header lines "
            "(SWAN default: True)"
        ),
    )
    idla: Optional[IDLA] = Field(
        default=None,
        description=(
            "Prescribe the lay-out of the output to file (supported options here are "
            "1, 3, 4). Option 4 is recommended for postprocessing an ASCII file by "
            "MATLAB, however option 3 is recommended in case of binary MATLAB output "
            "(SWAN default: 1)"
        ),
    )
    output: list[BlockOptions] = Field(
        description="The output variables to output to block file",
        min_length=1,
    )
    unit: Optional[float] = Field(
        default=None,
        description=(
            "Controls the scaling of the output. The program divides computed values "
            "by `unit` before writing to file, so the user should multiply the "
            "written value by `unit` to obtain the proper value. By default, if "
            "HEADER is selected, value is written as a 5 position integer. SWAN takes "
            "`unit` such that the largest number occurring in the block can be "
            "printed. If NOHEADER is selected, values are printed in floating-point "
            "format by default (`unit=1`)"
        ),
    )

    @field_validator("idla")
    @classmethod
    def validate_idla(cls, idla: IDLA) -> IDLA:
        if idla is not None and idla not in (1, 3, 4):
            raise ValueError(
                f"Only IDLA options (1, 3, 4) are supported in BLOCK, got {idla}"
            )
        return idla

    @property
    def suffix(self) -> str:
        return "blk"

    @property
    def _header(self) -> str:
        """Render the header instruction."""
        if self.header:
            return "HEADER"
        else:
            return "NOHEADER"

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = f"BLOCK sname='{self.sname}'"
        if self.header is not None:
            repr += f" {self._header}"
        repr += f" fname='{self.fname}'"
        if self.idla is not None:
            repr += f" LAYOUT idla={self.idla}"
        for output in self.output:
            if len(self.output) > 1:
                repr += "\n"
            else:
                repr += " "
            repr += f"{output.upper()}"
        if self.unit is not None:
            repr += f"\nunit={self.unit}"
        if self.times is not None:
            repr += f"\nOUTPUT {self.times.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['block', 'BLOCK'] = Field(default='block', description='Model type discriminator')

header class-attribute instance-attribute

header: Optional[bool] = Field(default=None, description='Indicate if the output should be written to a file with header lines (SWAN default: True)')

idla class-attribute instance-attribute

idla: Optional[IDLA] = Field(default=None, description='Prescribe the lay-out of the output to file (supported options here are 1, 3, 4). Option 4 is recommended for postprocessing an ASCII file by MATLAB, however option 3 is recommended in case of binary MATLAB output (SWAN default: 1)')

output class-attribute instance-attribute

output: list[BlockOptions] = Field(description='The output variables to output to block file', min_length=1)

unit class-attribute instance-attribute

unit: Optional[float] = Field(default=None, description='Controls the scaling of the output. The program divides computed values by `unit` before writing to file, so the user should multiply the written value by `unit` to obtain the proper value. By default, if HEADER is selected, value is written as a 5 position integer. SWAN takes `unit` such that the largest number occurring in the block can be printed. If NOHEADER is selected, values are printed in floating-point format by default (`unit=1`)')

suffix property

suffix: str

Functions

validate_idla classmethod

validate_idla(idla: IDLA) -> IDLA
Source code in src/rompy_swan/components/output.py
@field_validator("idla")
@classmethod
def validate_idla(cls, idla: IDLA) -> IDLA:
    if idla is not None and idla not in (1, 3, 4):
        raise ValueError(
            f"Only IDLA options (1, 3, 4) are supported in BLOCK, got {idla}"
        )
    return idla

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/output.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = f"BLOCK sname='{self.sname}'"
    if self.header is not None:
        repr += f" {self._header}"
    repr += f" fname='{self.fname}'"
    if self.idla is not None:
        repr += f" LAYOUT idla={self.idla}"
    for output in self.output:
        if len(self.output) > 1:
            repr += "\n"
        else:
            repr += " "
        repr += f"{output.upper()}"
    if self.unit is not None:
        repr += f"\nunit={self.unit}"
    if self.times is not None:
        repr += f"\nOUTPUT {self.times.render()}"
    return repr

TABLE

Bases: BaseWrite

Write spatial distributions.

.. code-block:: text

TABLE 'sname' ->HEADER|NOHEADER|INDEXED 'fname'  < output > &
    (OUTPUT [tbegblk] [deltblk]) SEC|MIN|HR|DAY

With this optional command the user indicates that for each location of the output location set 'sname' (see commands POINTS, CURVE, FRAME or GROUP) one or more variables should be written to a file. The keywords HEADER and NOHEADER determine the appearance of the table; the filename determines the destination of the data.

Note

HEADER: output is written in fixed format to file with headers giving name of variable and unit per column (numbers too large to be written will be shown as ****. The number of header lines is 4.

NOHEADER: output is written in floating point format to file and has no headers.

INDEXED: output compatible with GIS tools such as ARCVIEW, ARCINFO, etc. The user should give two TABLE commands, one to produce one file with XP and YP as output quantities, the other with HS, RTM01 or other output quantities.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.output import TABLE
table = TABLE(
    sname="outpts",
    format="noheader",
    fname="./output_table.nc",
    output=["hsign", "hswell", "dir", "tps", "tm01", "watlev", "qp"],
    times=dict(tbeg="2012-01-01T00:00:00", delt="PT30M", dfmt="min"),
)
print(table.render())
Source code in src/rompy_swan/components/output.py
class TABLE(BaseWrite):
    """Write spatial distributions.

    .. code-block:: text

        TABLE 'sname' ->HEADER|NOHEADER|INDEXED 'fname'  < output > &
            (OUTPUT [tbegblk] [deltblk]) SEC|MIN|HR|DAY

    With this optional command the user indicates that for each location of the output
    location set 'sname' (see commands `POINTS`, `CURVE`, `FRAME` or `GROUP`) one or
    more variables should be written to a file. The keywords `HEADER` and `NOHEADER`
    determine the appearance of the table; the filename determines the destination of
    the data.

    Note
    ----
    **HEADER**:
    output is written in fixed format to file with headers giving name of variable
    and unit per column (numbers too large to be written will be shown as `****`.
    The number of header lines is 4.

    **NOHEADER**:
    output is written in floating point format to file and has no headers.

    **INDEXED**:
    output compatible with GIS tools such as ARCVIEW, ARCINFO, etc. The user should
    give two TABLE commands, one to produce one file with `XP` and `YP` as output
    quantities, the other with `HS`, `RTM01` or other output quantities.


    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.output import TABLE
        table = TABLE(
            sname="outpts",
            format="noheader",
            fname="./output_table.nc",
            output=["hsign", "hswell", "dir", "tps", "tm01", "watlev", "qp"],
            times=dict(tbeg="2012-01-01T00:00:00", delt="PT30M", dfmt="min"),
        )
        print(table.render())

    """

    model_type: Literal["table", "TABLE"] = Field(
        default="table", description="Model type discriminator"
    )
    format: Optional[Literal["header", "noheader", "indexed"]] = Field(
        default=None,
        description=(
            "Indicate if the table should be written to a file as a HEADER, NOHEADER "
            "or INDEXED table format (SWAN default: HEADER)"
        ),
    )
    output: list[BlockOptions] = Field(
        description="The output variables to output to block file",
        min_length=1,
    )

    @property
    def suffix(self) -> str:
        return "tbl"

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = f"TABLE sname='{self.sname}'"
        if self.format is not None:
            repr += f" {self.format.upper()}"
        repr += f" fname='{self.fname}'"
        for output in self.output:
            if len(self.output) > 1:
                repr += "\n"
            else:
                repr += " "
            repr += f"{output.upper()}"
        if self.times is not None:
            repr += f"\nOUTPUT {self.times.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['table', 'TABLE'] = Field(default='table', description='Model type discriminator')

format class-attribute instance-attribute

format: Optional[Literal['header', 'noheader', 'indexed']] = Field(default=None, description='Indicate if the table should be written to a file as a HEADER, NOHEADER or INDEXED table format (SWAN default: HEADER)')

output class-attribute instance-attribute

output: list[BlockOptions] = Field(description='The output variables to output to block file', min_length=1)

suffix property

suffix: str

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/output.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = f"TABLE sname='{self.sname}'"
    if self.format is not None:
        repr += f" {self.format.upper()}"
    repr += f" fname='{self.fname}'"
    for output in self.output:
        if len(self.output) > 1:
            repr += "\n"
        else:
            repr += " "
        repr += f"{output.upper()}"
    if self.times is not None:
        repr += f"\nOUTPUT {self.times.render()}"
    return repr

SPECOUT

Bases: BaseWrite

Write to data file the wave spectra.

.. code-block:: text

SPECOUT 'sname' SPEC1D|->SPEC2D ->ABS|REL 'fname' &
    (OUTPUT [tbeg] [delt] SEC|MIN|HR|DAY)

With this optional command the user indicates that for each location of the output location set 'sname' (see commands POINTS, CURVE, FRAME or GROUP) the 1D or 2D variance / energy (see command SET) density spectrum (either the relative frequency or the absolute frequency spectrum) is to be written to a data file.

Note

This write command supports the following location types: POINTS, CURVE, FRAME and GROUP.

Note

This output file can be used for defining boundary conditions for subsequent SWAN runs (command BOUNDSPEC).

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.output import SPECOUT
out = SPECOUT(sname="outpts", fname="./specout.swn")
print(out.render())
out = SPECOUT(
    sname="outpts",
    dim=dict(model_type="spec2d"),
    freq=dict(model_type="rel"),
    fname="./specout.nc",
    times=dict(tbeg="2012-01-01T00:00:00", delt="PT30M", dfmt="min"),
)
print(out.render())
Source code in src/rompy_swan/components/output.py
class SPECOUT(BaseWrite):
    """Write to data file the wave spectra.

    .. code-block:: text

        SPECOUT 'sname' SPEC1D|->SPEC2D ->ABS|REL 'fname' &
            (OUTPUT [tbeg] [delt] SEC|MIN|HR|DAY)

    With this optional command the user indicates that for each location of the output
    location set 'sname' (see commands `POINTS`, `CURVE`, `FRAME` or `GROUP`) the 1D
    or 2D variance / energy (see command `SET`) density spectrum (either the relative
    frequency or the absolute frequency spectrum) is to be written to a data file.

    Note
    ----
    This write command supports the following location types: `POINTS`, `CURVE`,
    `FRAME` and `GROUP`.

    Note
    ----
    This output file can be used for defining boundary conditions for subsequent SWAN
    runs (command `BOUNDSPEC`).

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.output import SPECOUT
        out = SPECOUT(sname="outpts", fname="./specout.swn")
        print(out.render())
        out = SPECOUT(
            sname="outpts",
            dim=dict(model_type="spec2d"),
            freq=dict(model_type="rel"),
            fname="./specout.nc",
            times=dict(tbeg="2012-01-01T00:00:00", delt="PT30M", dfmt="min"),
        )
        print(out.render())

    """

    model_type: Literal["specout", "SPECOUT"] = Field(
        default="specout", description="Model type discriminator"
    )
    dim: Optional[DIM_TYPE] = Field(default=None)
    freq: Optional[FREQ_TYPE] = Field(default=None)

    @property
    def suffix(self) -> str:
        return "spc"

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = f"SPECOUT sname='{self.sname}'"
        if self.dim is not None:
            repr += f" {self.dim.render()}"
        if self.freq is not None:
            repr += f" {self.freq.render()}"
        repr += f" fname='{self.fname}'"
        if self.times is not None:
            repr += f" OUTPUT {self.times.render()}"
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['specout', 'SPECOUT'] = Field(default='specout', description='Model type discriminator')

dim class-attribute instance-attribute

dim: Optional[DIM_TYPE] = Field(default=None)

freq class-attribute instance-attribute

freq: Optional[FREQ_TYPE] = Field(default=None)

suffix property

suffix: str

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/output.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = f"SPECOUT sname='{self.sname}'"
    if self.dim is not None:
        repr += f" {self.dim.render()}"
    if self.freq is not None:
        repr += f" {self.freq.render()}"
    repr += f" fname='{self.fname}'"
    if self.times is not None:
        repr += f" OUTPUT {self.times.render()}"
    return repr

Group Components

STARTUP

Bases: BaseGroupComponent

Startup group component.

.. code-block:: text

PROJECT ...
SET ...
MODE ...
COORDINATES ...

This group component is used to group individual startup components. Only fields that are explicitly prescribed are rendered by this group component.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.startup import PROJECT, SET, MODE, COORDINATES
from rompy_swan.components.group import STARTUP
proj = PROJECT(nr="01")
set = SET(level=0.5, direction_convention="nautical")
mode = MODE(kind="nonstationary", dim="twodimensional")
coords = COORDINATES(kind=dict(model_type="spherical", projection="ccm"))
startup = STARTUP(
    project=proj,
    set=set,
    mode=mode,
    coordinates=coords,
)
print(startup.render())
Source code in src/rompy_swan/components/group.py
class STARTUP(BaseGroupComponent):
    """Startup group component.

    .. code-block:: text

        PROJECT ...
        SET ...
        MODE ...
        COORDINATES ...

    This group component is used to group individual startup components. Only fields
    that are explicitly prescribed are rendered by this group component.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.startup import PROJECT, SET, MODE, COORDINATES
        from rompy_swan.components.group import STARTUP
        proj = PROJECT(nr="01")
        set = SET(level=0.5, direction_convention="nautical")
        mode = MODE(kind="nonstationary", dim="twodimensional")
        coords = COORDINATES(kind=dict(model_type="spherical", projection="ccm"))
        startup = STARTUP(
            project=proj,
            set=set,
            mode=mode,
            coordinates=coords,
        )
        print(startup.render())

    """

    model_type: Literal["startup", "STARTUP"] = Field(
        default="startup", description="Model type discriminator"
    )
    project: Optional[PROJECT_TYPE] = Field(default=None)
    set: Optional[SET_TYPE] = Field(default=None)
    mode: Optional[MODE_TYPE] = Field(default=None)
    coordinates: Optional[COORDINATES_TYPE] = Field(default=None)

    def cmd(self) -> str:
        """Command file string for this component."""
        repr = []
        if self.project is not None:
            repr += [f"{self.project.cmd()}"]
        if self.set is not None:
            repr += [f"{self.set.cmd()}"]
        if self.mode is not None:
            repr += [f"{self.mode.cmd()}"]
        if self.coordinates is not None:
            repr += [f"{self.coordinates.cmd()}"]
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['startup', 'STARTUP'] = Field(default='startup', description='Model type discriminator')

project class-attribute instance-attribute

project: Optional[PROJECT_TYPE] = Field(default=None)

set class-attribute instance-attribute

set: Optional[SET_TYPE] = Field(default=None)

mode class-attribute instance-attribute

mode: Optional[MODE_TYPE] = Field(default=None)

coordinates class-attribute instance-attribute

coordinates: Optional[COORDINATES_TYPE] = Field(default=None)

Functions

cmd

cmd() -> str

Command file string for this component.

Source code in src/rompy_swan/components/group.py
def cmd(self) -> str:
    """Command file string for this component."""
    repr = []
    if self.project is not None:
        repr += [f"{self.project.cmd()}"]
    if self.set is not None:
        repr += [f"{self.set.cmd()}"]
    if self.mode is not None:
        repr += [f"{self.mode.cmd()}"]
    if self.coordinates is not None:
        repr += [f"{self.coordinates.cmd()}"]
    return repr

PHYSICS

Bases: BaseGroupComponent

Physics group component.

The physics group component is a convenience to allow specifying several individual components in a single command and check for consistency between them.

Exemples

.. ipython:: python :okwarning:

from rompy_swan.components.group import PHYSICS
gen = {"model_type": "gen3", "source_terms": {"model_type": "komen"}}
phys = PHYSICS(gen=gen)
print(phys.render())
phys = PHYSICS(
    gen=dict(model_type="gen3", source_terms={"model_type": "st6c1"}),
    negatinp={"model_type": "negatinp", "rdcoef": 0.04},
    sswell={"model_type": "zieger"},
    breaking={"model_type": "constant", "alpha": 1.0, "gamma": 0.73},
    friction={"model_type": "jonswap", "cfjon": 0.038},
)
print(phys.render())
Source code in src/rompy_swan/components/group.py
class PHYSICS(BaseGroupComponent):
    """Physics group component.

    The physics group component is a convenience to allow specifying several individual
    components in a single command and check for consistency between them.

    Exemples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.group import PHYSICS
        gen = {"model_type": "gen3", "source_terms": {"model_type": "komen"}}
        phys = PHYSICS(gen=gen)
        print(phys.render())
        phys = PHYSICS(
            gen=dict(model_type="gen3", source_terms={"model_type": "st6c1"}),
            negatinp={"model_type": "negatinp", "rdcoef": 0.04},
            sswell={"model_type": "zieger"},
            breaking={"model_type": "constant", "alpha": 1.0, "gamma": 0.73},
            friction={"model_type": "jonswap", "cfjon": 0.038},
        )
        print(phys.render())

    """

    model_type: Literal["physics", "PHYSICS"] = Field(
        default="physics", description="Model type discriminator"
    )
    gen: Optional[GEN_TYPE] = Field(default=None)
    sswell: Optional[SSWELL_TYPE] = Field(default=None)
    negatinp: Optional[NEGATINP_TYPE] = Field(default=None)
    wcapping: Optional[WCAPPING_TYPE] = Field(default=None)
    quadrupl: Optional[QUADRUPL_TYPE] = Field(default=None)
    breaking: Optional[BREAKING_TYPE] = Field(default=None)
    friction: Optional[FRICTION_TYPE] = Field(default=None)
    triad: Optional[TRIAD_TYPE] = Field(default=None)
    vegetation: Optional[VEGETATION_TYPE] = Field(default=None)
    mud: Optional[MUD_TYPE] = Field(default=None)
    sice: Optional[SICE_TYPE] = Field(default=None)
    turbulence: Optional[TURBULENCE_TYPE] = Field(default=None)
    bragg: Optional[BRAGG_TYPE] = Field(default=None)
    limiter: Optional[LIMITER_TYPE] = Field(default=None)
    obstacle: Optional[OBSTACLE_TYPE] = Field(default=None)
    setup: Optional[SETUP_TYPE] = Field(default=None)
    diffraction: Optional[DIFFRACTION_TYPE] = Field(default=None)
    surfbeat: Optional[SURFBEAT_TYPE] = Field(default=None)
    scat: Optional[SCAT_TYPE] = Field(default=None)
    deactivate: Optional[OFF_TYPE] = Field(default=None)

    @field_validator("deactivate")
    @classmethod
    def deactivate_physics(cls, off: OFF_TYPE) -> OFF_TYPE:
        """Convert OFF to OFFS so list is rendered."""
        for phys in PhysicsOff:
            print(phys.value)
        return off

    @model_validator(mode="after")
    def negatinp_only_with_zieger(self) -> "PHYSICS":
        """Log a warning if NEGATINP is used with a non-ZIEGER SSWELL."""
        if self.negatinp is None:
            return self
        elif self.sswell is None:
            logger.warning(
                "The negative wind input NEGATINP is only intended to use with the "
                "swell dissipation SSWELL ZIEGER but no SSWELL has been specified."
            )
        elif self.sswell.model_type != "zieger":
            logger.warning(
                "The negative wind input NEGATINP is only intended to use with the "
                "swell dissipation SSWELL ZIEGER but the SSWELL "
                f"{self.sswell.model_type.upper()} has been specified."
            )
        return self

    def cmd(self):
        repr = []
        if self.gen is not None:
            repr += [self.gen.cmd()]
        if self.sswell is not None:
            repr += [f"{self.sswell.cmd()}"]
        if self.negatinp is not None:
            repr += [self.negatinp.cmd()]
        if self.wcapping is not None:
            repr += [self.wcapping.cmd()]
        if self.quadrupl is not None:
            repr += [self.quadrupl.cmd()]
        if self.breaking is not None:
            repr += [self.breaking.cmd()]
        if self.friction is not None:
            repr += [self.friction.cmd()]
        if self.triad is not None:
            repr += [self.triad.cmd()]
        if self.vegetation is not None:
            repr += [self.vegetation.cmd()]
        if self.mud is not None:
            repr += [self.mud.cmd()]
        if self.sice is not None:
            repr += [self.sice.cmd()]
        if self.turbulence is not None:
            repr += [self.turbulence.cmd()]
        if self.bragg is not None:
            repr += [self.bragg.cmd()]
        if self.limiter is not None:
            repr += [self.limiter.cmd()]
        if self.obstacle is not None:
            repr += self.obstacle.cmd()  # Object returns a list of components
        if self.setup is not None:
            repr += [self.setup.cmd()]
        if self.diffraction is not None:
            repr += [self.diffraction.cmd()]
        if self.surfbeat is not None:
            repr += [self.surfbeat.cmd()]
        if self.scat is not None:
            repr += [self.scat.cmd()]
        if self.deactivate is not None:
            repr += self.deactivate.cmd()  # Object returns a list of components
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['physics', 'PHYSICS'] = Field(default='physics', description='Model type discriminator')

gen class-attribute instance-attribute

gen: Optional[GEN_TYPE] = Field(default=None)

sswell class-attribute instance-attribute

sswell: Optional[SSWELL_TYPE] = Field(default=None)

negatinp class-attribute instance-attribute

negatinp: Optional[NEGATINP_TYPE] = Field(default=None)

wcapping class-attribute instance-attribute

wcapping: Optional[WCAPPING_TYPE] = Field(default=None)

quadrupl class-attribute instance-attribute

quadrupl: Optional[QUADRUPL_TYPE] = Field(default=None)

breaking class-attribute instance-attribute

breaking: Optional[BREAKING_TYPE] = Field(default=None)

friction class-attribute instance-attribute

friction: Optional[FRICTION_TYPE] = Field(default=None)

triad class-attribute instance-attribute

triad: Optional[TRIAD_TYPE] = Field(default=None)

vegetation class-attribute instance-attribute

vegetation: Optional[VEGETATION_TYPE] = Field(default=None)

mud class-attribute instance-attribute

mud: Optional[MUD_TYPE] = Field(default=None)

sice class-attribute instance-attribute

sice: Optional[SICE_TYPE] = Field(default=None)

turbulence class-attribute instance-attribute

turbulence: Optional[TURBULENCE_TYPE] = Field(default=None)

bragg class-attribute instance-attribute

bragg: Optional[BRAGG_TYPE] = Field(default=None)

limiter class-attribute instance-attribute

limiter: Optional[LIMITER_TYPE] = Field(default=None)

obstacle class-attribute instance-attribute

obstacle: Optional[OBSTACLE_TYPE] = Field(default=None)

setup class-attribute instance-attribute

setup: Optional[SETUP_TYPE] = Field(default=None)

diffraction class-attribute instance-attribute

diffraction: Optional[DIFFRACTION_TYPE] = Field(default=None)

surfbeat class-attribute instance-attribute

surfbeat: Optional[SURFBEAT_TYPE] = Field(default=None)

scat class-attribute instance-attribute

scat: Optional[SCAT_TYPE] = Field(default=None)

deactivate class-attribute instance-attribute

deactivate: Optional[OFF_TYPE] = Field(default=None)

Functions

deactivate_physics classmethod

deactivate_physics(off: OFF_TYPE) -> OFF_TYPE

Convert OFF to OFFS so list is rendered.

Source code in src/rompy_swan/components/group.py
@field_validator("deactivate")
@classmethod
def deactivate_physics(cls, off: OFF_TYPE) -> OFF_TYPE:
    """Convert OFF to OFFS so list is rendered."""
    for phys in PhysicsOff:
        print(phys.value)
    return off

negatinp_only_with_zieger

negatinp_only_with_zieger() -> PHYSICS

Log a warning if NEGATINP is used with a non-ZIEGER SSWELL.

Source code in src/rompy_swan/components/group.py
@model_validator(mode="after")
def negatinp_only_with_zieger(self) -> "PHYSICS":
    """Log a warning if NEGATINP is used with a non-ZIEGER SSWELL."""
    if self.negatinp is None:
        return self
    elif self.sswell is None:
        logger.warning(
            "The negative wind input NEGATINP is only intended to use with the "
            "swell dissipation SSWELL ZIEGER but no SSWELL has been specified."
        )
    elif self.sswell.model_type != "zieger":
        logger.warning(
            "The negative wind input NEGATINP is only intended to use with the "
            "swell dissipation SSWELL ZIEGER but the SSWELL "
            f"{self.sswell.model_type.upper()} has been specified."
        )
    return self

cmd

cmd()
Source code in src/rompy_swan/components/group.py
def cmd(self):
    repr = []
    if self.gen is not None:
        repr += [self.gen.cmd()]
    if self.sswell is not None:
        repr += [f"{self.sswell.cmd()}"]
    if self.negatinp is not None:
        repr += [self.negatinp.cmd()]
    if self.wcapping is not None:
        repr += [self.wcapping.cmd()]
    if self.quadrupl is not None:
        repr += [self.quadrupl.cmd()]
    if self.breaking is not None:
        repr += [self.breaking.cmd()]
    if self.friction is not None:
        repr += [self.friction.cmd()]
    if self.triad is not None:
        repr += [self.triad.cmd()]
    if self.vegetation is not None:
        repr += [self.vegetation.cmd()]
    if self.mud is not None:
        repr += [self.mud.cmd()]
    if self.sice is not None:
        repr += [self.sice.cmd()]
    if self.turbulence is not None:
        repr += [self.turbulence.cmd()]
    if self.bragg is not None:
        repr += [self.bragg.cmd()]
    if self.limiter is not None:
        repr += [self.limiter.cmd()]
    if self.obstacle is not None:
        repr += self.obstacle.cmd()  # Object returns a list of components
    if self.setup is not None:
        repr += [self.setup.cmd()]
    if self.diffraction is not None:
        repr += [self.diffraction.cmd()]
    if self.surfbeat is not None:
        repr += [self.surfbeat.cmd()]
    if self.scat is not None:
        repr += [self.scat.cmd()]
    if self.deactivate is not None:
        repr += self.deactivate.cmd()  # Object returns a list of components
    return repr

INPGRIDS

Bases: BaseGroupComponent

SWAN input grids group component.

.. code-block:: text

INPGRID ...
READGRID ...

INPGRID ...
READGRID ...

...

This group component is a convenience to allow defining and rendering a list of input grid components.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.inpgrid import REGULAR, ICE
from rompy_swan.components.group import INPGRIDS
inpgrid_bottom = REGULAR(
    grid_type="bottom",
    excval=-99.0,
    xpinp=172.0,
    ypinp=-41.0,
    alpinp=0.0,
    mxinp=99,
    myinp=99,
    dxinp=0.005,
    dyinp=0.005,
    readinp=dict(fname1="bottom.txt"),
)
inpgrid_wind = REGULAR(
    grid_type="wind",
    excval=-99.0,
    xpinp=172.0,
    ypinp=-41.0,
    alpinp=0.0,
    mxinp=99,
    myinp=99,
    dxinp=0.005,
    dyinp=0.005,
    readinp=dict(fname1="wind.txt"),
    nonstationary=dict(
        tbeg="2019-01-01T00:00:00",
        tend="2019-01-07 00:00:00",
        delt=3600,
        dfmt="hr",
    ),
)
inpgrid_ice_cte = ICE(aice=0.8, hice=2.0)
inpgrids = INPGRIDS(inpgrids=[inpgrid_bottom, inpgrid_wind, inpgrid_ice_cte])
print(inpgrids.render())
Source code in src/rompy_swan/components/group.py
class INPGRIDS(BaseGroupComponent):
    """SWAN input grids group component.

    .. code-block:: text

        INPGRID ...
        READGRID ...

        INPGRID ...
        READGRID ...

        ...

    This group component is a convenience to allow defining and rendering
    a list of input grid components.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.inpgrid import REGULAR, ICE
        from rompy_swan.components.group import INPGRIDS
        inpgrid_bottom = REGULAR(
            grid_type="bottom",
            excval=-99.0,
            xpinp=172.0,
            ypinp=-41.0,
            alpinp=0.0,
            mxinp=99,
            myinp=99,
            dxinp=0.005,
            dyinp=0.005,
            readinp=dict(fname1="bottom.txt"),
        )
        inpgrid_wind = REGULAR(
            grid_type="wind",
            excval=-99.0,
            xpinp=172.0,
            ypinp=-41.0,
            alpinp=0.0,
            mxinp=99,
            myinp=99,
            dxinp=0.005,
            dyinp=0.005,
            readinp=dict(fname1="wind.txt"),
            nonstationary=dict(
                tbeg="2019-01-01T00:00:00",
                tend="2019-01-07 00:00:00",
                delt=3600,
                dfmt="hr",
            ),
        )
        inpgrid_ice_cte = ICE(aice=0.8, hice=2.0)
        inpgrids = INPGRIDS(inpgrids=[inpgrid_bottom, inpgrid_wind, inpgrid_ice_cte])
        print(inpgrids.render())

    """

    model_type: Literal["inpgrids"] = Field(
        default="inpgrids", description="Model type discriminator"
    )
    inpgrids: list[INPGRID_TYPE] = Field(
        min_length=1,
        description="List of input grid components",
    )

    @field_validator("inpgrids")
    @classmethod
    def ensure_unique_grid_type(cls, inpgrids: INPGRID_TYPE) -> INPGRID_TYPE:
        """Ensure that each grid type is unique."""
        grid_types = [inp.grid_type for inp in inpgrids if hasattr(inp, "grid_type")]
        if len(grid_types) != len(set(grid_types)):
            raise ValueError("Each grid type must be unique")
        return inpgrids

    def cmd(self) -> str | list:
        repr = []
        for inpgrid in self.inpgrids:
            repr += [inpgrid.cmd()]
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['inpgrids'] = Field(default='inpgrids', description='Model type discriminator')

inpgrids class-attribute instance-attribute

inpgrids: list[INPGRID_TYPE] = Field(min_length=1, description='List of input grid components')

Functions

ensure_unique_grid_type classmethod

ensure_unique_grid_type(inpgrids: INPGRID_TYPE) -> INPGRID_TYPE

Ensure that each grid type is unique.

Source code in src/rompy_swan/components/group.py
@field_validator("inpgrids")
@classmethod
def ensure_unique_grid_type(cls, inpgrids: INPGRID_TYPE) -> INPGRID_TYPE:
    """Ensure that each grid type is unique."""
    grid_types = [inp.grid_type for inp in inpgrids if hasattr(inp, "grid_type")]
    if len(grid_types) != len(set(grid_types)):
        raise ValueError("Each grid type must be unique")
    return inpgrids

cmd

cmd() -> str | list
Source code in src/rompy_swan/components/group.py
def cmd(self) -> str | list:
    repr = []
    for inpgrid in self.inpgrids:
        repr += [inpgrid.cmd()]
    return repr

OUTPUT

Bases: BaseGroupComponent

Output group component.

.. code-block:: text

FRAME 'sname' ...
GROUP 'sname' ...
CURVE 'sname' ...
RAY 'rname' ...
ISOLINE 'sname' 'rname' ...
POINTS 'sname ...
NGRID 'sname' ...
QUANTITY ...
OUTPUT OPTIONS ...
BLOCK 'sname' ...
TABLE 'sname' ...
SPECOUT 'sname' ...
NESTOUT 'sname ...

This group component is used to define multiple types of output locations and write components in a single model. Only fields that are explicitly prescribed are rendered by this group component.

Note

The components prescribed are validated according to some constraints as defined in the SWAN manual:

  • The name 'sname' of each Locations component must be unique.
  • The Locations 'sname' assigned to each write component must be defined.
  • The BLOCK component must be associated with either a FRAME or GROUP.
  • The ISOLINE write component must be associated with a RAY component.
  • The NGRID and NESTOUT components must be defined together.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.output import POINTS, BLOCK, QUANTITIES, TABLE
from rompy_swan.components.group import OUTPUT
points = POINTS(sname="outpts", xp=[172.3, 172.4], yp=[-39, -39])
quantity = QUANTITIES(
    quantities=[
        dict(output=["depth", "hsign", "tps", "dir", "tm01"], excv=-9),
    ]
)
times = dict(tbeg="2012-01-01T00:00:00", delt="PT30M", tfmt=1, dfmt="min")
block = BLOCK(
    model_type="block",
    sname="COMPGRID",
    fname="./swangrid.nc",
    output=["depth", "hsign", "tps", "dir"],
    times=times,
)
table = TABLE(
    sname="outpts",
    format="noheader",
    fname="./swantable.nc",
    output=["hsign", "hswell", "dir", "tps", "tm01", "watlev", "qp"],
    times=times,
)
out = OUTPUT(
    points=points,
    quantity=quantity,
    block=block,
    table=table,
)
print(out.render())
Source code in src/rompy_swan/components/group.py
class OUTPUT(BaseGroupComponent):
    """Output group component.

    .. code-block:: text

        FRAME 'sname' ...
        GROUP 'sname' ...
        CURVE 'sname' ...
        RAY 'rname' ...
        ISOLINE 'sname' 'rname' ...
        POINTS 'sname ...
        NGRID 'sname' ...
        QUANTITY ...
        OUTPUT OPTIONS ...
        BLOCK 'sname' ...
        TABLE 'sname' ...
        SPECOUT 'sname' ...
        NESTOUT 'sname ...

    This group component is used to define multiple types of output locations and
    write components in a single model. Only fields that are explicitly prescribed are
    rendered by this group component.

    Note
    ----
    The components prescribed are validated according to some constraints as defined
    in the SWAN manual:

    - The name `'sname'` of each Locations component must be unique.
    - The Locations `'sname'` assigned to each write component must be defined.
    - The BLOCK component must be associated with either a `FRAME` or `GROUP`.
    - The ISOLINE write component must be associated with a `RAY` component.
    - The NGRID and NESTOUT components must be defined together.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.output import POINTS, BLOCK, QUANTITIES, TABLE
        from rompy_swan.components.group import OUTPUT
        points = POINTS(sname="outpts", xp=[172.3, 172.4], yp=[-39, -39])
        quantity = QUANTITIES(
            quantities=[
                dict(output=["depth", "hsign", "tps", "dir", "tm01"], excv=-9),
            ]
        )
        times = dict(tbeg="2012-01-01T00:00:00", delt="PT30M", tfmt=1, dfmt="min")
        block = BLOCK(
            model_type="block",
            sname="COMPGRID",
            fname="./swangrid.nc",
            output=["depth", "hsign", "tps", "dir"],
            times=times,
        )
        table = TABLE(
            sname="outpts",
            format="noheader",
            fname="./swantable.nc",
            output=["hsign", "hswell", "dir", "tps", "tm01", "watlev", "qp"],
            times=times,
        )
        out = OUTPUT(
            points=points,
            quantity=quantity,
            block=block,
            table=table,
        )
        print(out.render())

    """

    model_type: Literal["output", "OUTPUT"] = Field(
        default="output", description="Model type discriminator"
    )
    frame: Optional[FRAME_TYPE] = Field(default=None)
    group: Optional[GROUP_TYPE] = Field(default=None)
    curve: Optional[CURVE_TYPE] = Field(default=None)
    ray: Optional[RAY_TYPE] = Field(default=None)
    isoline: Optional[ISOLINE_TYPE] = Field(default=None)
    points: Optional[POINTS_TYPE] = Field(default=None)
    ngrid: Optional[NGRID_TYPE] = Field(default=None)
    quantity: Optional[QUANTITY_TYPE] = Field(default=None)
    output_options: Optional[OUTOPT_TYPE] = Field(default=None)
    block: Optional[BLOCK_TYPE] = Field(default=None)
    table: Optional[TABLE_TYPE] = Field(default=None)
    specout: Optional[SPECOUT_TYPE] = Field(default=None)
    nestout: Optional[NESTOUT_TYPE] = Field(default=None)
    test: Optional[TEST_TYPE] = Field(default=None)
    _location_fields: list = ["frame", "group", "curve", "isoline", "points", "ngrid"]
    _write_fields: list = ["block", "table", "specout", "nestout"]

    @model_validator(mode="after")
    def write_locations_exists(self) -> "OUTPUT":
        """Ensure the location component requested by a write component exists."""
        for write in self.write_set:
            obj = getattr(self, write)
            if obj is None:
                continue
            snames = obj.sname if isinstance(obj.sname, list) else [obj.sname]
            for sname in snames:
                if sname in SPECIAL_NAMES:
                    return self
                try:
                    self._filter_location(sname)
                except ValueError as err:
                    raise ValueError(
                        f"Write component '{write}' specified with sname='{sname}' but "
                        f"no location component with sname='{sname}' has been defined"
                    ) from err
        return self

    @model_validator(mode="after")
    def locations_sname_unique(self) -> "OUTPUT":
        """Ensure same `sname` isn't used in more than one set of output locations."""
        duplicates = {x for x in self.snames if self.snames.count(x) > 1}
        if duplicates:
            raise ValueError(
                "The following snames are used to define more than one set of output "
                f"components: {duplicates}, please ensure each location component has "
                "a unique `sname`"
            )
        return self

    @model_validator(mode="after")
    def block_with_frame_or_group(self) -> "OUTPUT":
        """Ensure Block is only defined for FRAME or GROUP locations."""
        if self.block is not None:
            snames = self.block.sname
            if isinstance(snames, str):
                snames = [self.block.sname]
            for sname in snames:
                if sname not in ["BOTTGRID", "COMPGRID"]:
                    location = self._filter_location(sname)
                    component = location.model_type.upper().split("_")[0]
                    if component not in ["FRAME", "GROUP"]:
                        raise ValueError(
                            f"Block sname='{sname}' specified with {component} "
                            "location component but only only FRAME or GROUP "
                            "components are supported"
                        )
        return self

    @model_validator(mode="after")
    def isoline_ray_defined(self) -> "OUTPUT":
        """Ensure the isoline ray has been defined."""
        if self.isoline is not None:
            if self.ray is None:
                raise ValueError(
                    f"Isoline {self.isoline} requires RAY rname='{self.isoline.rname}'"
                    " but no RAY component has been defined"
                )
            elif self.ray.rname != self.isoline.rname:
                raise ValueError(
                    f"Isoline rname='{self.isoline.rname}' does not match "
                    f"the ray rname='{self.ray.rname}'"
                )
        return self

    @model_validator(mode="after")
    def ngrid_and_nestout(self) -> "OUTPUT":
        """Ensure NGRID and NESTOUT are specified together."""
        if self.ngrid is not None and self.nestout is None:
            raise ValueError(
                "NGRID component specified but no NESTOUT component has been defined"
            )
        elif self.ngrid is None and self.nestout is not None:
            raise ValueError(
                "NESTOUT component specified but no NGRID component has been defined"
            )
        elif self.ngrid is not None and self.nestout is not None:
            if self.ngrid.sname != self.nestout.sname:
                raise ValueError(
                    f"NGRID sname='{self.ngrid.sname}' does not match "
                    f"the NESTOUT sname='{self.nestout.sname}'"
                )
        return self

    @property
    def locations_set(self):
        """List of specified location fields."""
        return [fld for fld in self.model_fields_set if fld in self._location_fields]

    @property
    def write_set(self):
        """List of specified write fields."""
        return [fld for fld in self.model_fields_set if fld in self._write_fields]

    @property
    def snames(self):
        """List of snames from specified location components."""
        snames = []
        for field in self.locations_set:
            obj = getattr(self, field)
            if obj is None:
                continue
            sname = obj.sname
            if isinstance(sname, str):
                sname = [sname]
            snames.extend(sname)
        return snames

    def _filter_location(self, sname):
        """Filter the location component defined with the specified sname."""
        for field in self.locations_set:
            obj = getattr(self, field)
            if obj is None:
                continue
            obj_snames = obj.sname if isinstance(obj.sname, list) else [obj.sname]
            for obj_sname in obj_snames:
                if obj_sname == sname:
                    return obj
        raise ValueError(f"Location component with sname='{sname}' not found")

    def cmd(self) -> list:
        """Command file string for this component."""
        repr = []
        if self.frame is not None:
            repr += [f"{self.frame.cmd()}"]
        if self.group is not None:
            repr += [f"{self.group.cmd()}"]
        if self.curve is not None:
            # Component renders a list
            repr += self.curve.cmd()
        if self.ray is not None:
            repr += [f"{self.ray.cmd()}"]
        if self.isoline is not None:
            repr += [f"{self.isoline.cmd()}"]
        if self.points is not None:
            repr += [f"{self.points.cmd()}"]
        if self.ngrid is not None:
            repr += [f"{self.ngrid.cmd()}"]
        if self.quantity is not None:
            # Component renders a list
            repr += self.quantity.cmd()
        if self.output_options is not None:
            repr += [f"{self.output_options.cmd()}"]
        if self.block is not None:
            # Component may or may not render a list, handles both
            cmds = self.block.cmd()
            if not isinstance(cmds, list):
                cmds = [cmds]
            for cmd in cmds:
                repr += [f"{cmd}"]
        if self.table is not None:
            repr += [f"{self.table.cmd()}"]
        if self.specout is not None:
            repr += [f"{self.specout.cmd()}"]
        if self.nestout is not None:
            repr += [f"{self.nestout.cmd()}"]
        if self.test is not None:
            repr += [f"{self.test.cmd()}"]
        return repr

Attributes

model_type class-attribute instance-attribute

model_type: Literal['output', 'OUTPUT'] = Field(default='output', description='Model type discriminator')

frame class-attribute instance-attribute

frame: Optional[FRAME_TYPE] = Field(default=None)

group class-attribute instance-attribute

group: Optional[GROUP_TYPE] = Field(default=None)

curve class-attribute instance-attribute

curve: Optional[CURVE_TYPE] = Field(default=None)

ray class-attribute instance-attribute

ray: Optional[RAY_TYPE] = Field(default=None)

isoline class-attribute instance-attribute

isoline: Optional[ISOLINE_TYPE] = Field(default=None)

points class-attribute instance-attribute

points: Optional[POINTS_TYPE] = Field(default=None)

ngrid class-attribute instance-attribute

ngrid: Optional[NGRID_TYPE] = Field(default=None)

quantity class-attribute instance-attribute

quantity: Optional[QUANTITY_TYPE] = Field(default=None)

output_options class-attribute instance-attribute

output_options: Optional[OUTOPT_TYPE] = Field(default=None)

block class-attribute instance-attribute

block: Optional[BLOCK_TYPE] = Field(default=None)

table class-attribute instance-attribute

table: Optional[TABLE_TYPE] = Field(default=None)

specout class-attribute instance-attribute

specout: Optional[SPECOUT_TYPE] = Field(default=None)

nestout class-attribute instance-attribute

nestout: Optional[NESTOUT_TYPE] = Field(default=None)

test class-attribute instance-attribute

test: Optional[TEST_TYPE] = Field(default=None)

locations_set property

locations_set

List of specified location fields.

write_set property

write_set

List of specified write fields.

snames property

snames

List of snames from specified location components.

Functions

write_locations_exists

write_locations_exists() -> OUTPUT

Ensure the location component requested by a write component exists.

Source code in src/rompy_swan/components/group.py
@model_validator(mode="after")
def write_locations_exists(self) -> "OUTPUT":
    """Ensure the location component requested by a write component exists."""
    for write in self.write_set:
        obj = getattr(self, write)
        if obj is None:
            continue
        snames = obj.sname if isinstance(obj.sname, list) else [obj.sname]
        for sname in snames:
            if sname in SPECIAL_NAMES:
                return self
            try:
                self._filter_location(sname)
            except ValueError as err:
                raise ValueError(
                    f"Write component '{write}' specified with sname='{sname}' but "
                    f"no location component with sname='{sname}' has been defined"
                ) from err
    return self

locations_sname_unique

locations_sname_unique() -> OUTPUT

Ensure same sname isn't used in more than one set of output locations.

Source code in src/rompy_swan/components/group.py
@model_validator(mode="after")
def locations_sname_unique(self) -> "OUTPUT":
    """Ensure same `sname` isn't used in more than one set of output locations."""
    duplicates = {x for x in self.snames if self.snames.count(x) > 1}
    if duplicates:
        raise ValueError(
            "The following snames are used to define more than one set of output "
            f"components: {duplicates}, please ensure each location component has "
            "a unique `sname`"
        )
    return self

block_with_frame_or_group

block_with_frame_or_group() -> OUTPUT

Ensure Block is only defined for FRAME or GROUP locations.

Source code in src/rompy_swan/components/group.py
@model_validator(mode="after")
def block_with_frame_or_group(self) -> "OUTPUT":
    """Ensure Block is only defined for FRAME or GROUP locations."""
    if self.block is not None:
        snames = self.block.sname
        if isinstance(snames, str):
            snames = [self.block.sname]
        for sname in snames:
            if sname not in ["BOTTGRID", "COMPGRID"]:
                location = self._filter_location(sname)
                component = location.model_type.upper().split("_")[0]
                if component not in ["FRAME", "GROUP"]:
                    raise ValueError(
                        f"Block sname='{sname}' specified with {component} "
                        "location component but only only FRAME or GROUP "
                        "components are supported"
                    )
    return self

isoline_ray_defined

isoline_ray_defined() -> OUTPUT

Ensure the isoline ray has been defined.

Source code in src/rompy_swan/components/group.py
@model_validator(mode="after")
def isoline_ray_defined(self) -> "OUTPUT":
    """Ensure the isoline ray has been defined."""
    if self.isoline is not None:
        if self.ray is None:
            raise ValueError(
                f"Isoline {self.isoline} requires RAY rname='{self.isoline.rname}'"
                " but no RAY component has been defined"
            )
        elif self.ray.rname != self.isoline.rname:
            raise ValueError(
                f"Isoline rname='{self.isoline.rname}' does not match "
                f"the ray rname='{self.ray.rname}'"
            )
    return self

ngrid_and_nestout

ngrid_and_nestout() -> OUTPUT

Ensure NGRID and NESTOUT are specified together.

Source code in src/rompy_swan/components/group.py
@model_validator(mode="after")
def ngrid_and_nestout(self) -> "OUTPUT":
    """Ensure NGRID and NESTOUT are specified together."""
    if self.ngrid is not None and self.nestout is None:
        raise ValueError(
            "NGRID component specified but no NESTOUT component has been defined"
        )
    elif self.ngrid is None and self.nestout is not None:
        raise ValueError(
            "NESTOUT component specified but no NGRID component has been defined"
        )
    elif self.ngrid is not None and self.nestout is not None:
        if self.ngrid.sname != self.nestout.sname:
            raise ValueError(
                f"NGRID sname='{self.ngrid.sname}' does not match "
                f"the NESTOUT sname='{self.nestout.sname}'"
            )
    return self

cmd

cmd() -> list

Command file string for this component.

Source code in src/rompy_swan/components/group.py
def cmd(self) -> list:
    """Command file string for this component."""
    repr = []
    if self.frame is not None:
        repr += [f"{self.frame.cmd()}"]
    if self.group is not None:
        repr += [f"{self.group.cmd()}"]
    if self.curve is not None:
        # Component renders a list
        repr += self.curve.cmd()
    if self.ray is not None:
        repr += [f"{self.ray.cmd()}"]
    if self.isoline is not None:
        repr += [f"{self.isoline.cmd()}"]
    if self.points is not None:
        repr += [f"{self.points.cmd()}"]
    if self.ngrid is not None:
        repr += [f"{self.ngrid.cmd()}"]
    if self.quantity is not None:
        # Component renders a list
        repr += self.quantity.cmd()
    if self.output_options is not None:
        repr += [f"{self.output_options.cmd()}"]
    if self.block is not None:
        # Component may or may not render a list, handles both
        cmds = self.block.cmd()
        if not isinstance(cmds, list):
            cmds = [cmds]
        for cmd in cmds:
            repr += [f"{cmd}"]
    if self.table is not None:
        repr += [f"{self.table.cmd()}"]
    if self.specout is not None:
        repr += [f"{self.specout.cmd()}"]
    if self.nestout is not None:
        repr += [f"{self.nestout.cmd()}"]
    if self.test is not None:
        repr += [f"{self.test.cmd()}"]
    return repr

LOCKUP

Bases: BaseComponent

Lockup group component.

.. code-block:: text

COMPUTE ...
HOTFILE ...
COMPUTE ...
HOTFILE ...
...
STOP

This is a group component to specify SWAN "Lockup" commands including multiple COMPUTE commands that may or may not be interleaved with HOTFILE commands, and a final STOP command.

Examples

.. ipython:: python :okwarning:

from rompy_swan.components.group import LOCKUP
lockup = LOCKUP(
    compute=dict(
        model_type="stat",
        times=dict(
            model_type="nonstationary",
            tbeg="1990-01-01T00:00:00",
            tend="1990-01-01T03:00:00",
            delt="PT1H",
            dfmt="hr",
        ),
        hotfile=dict(fname="hotfile"),
        hottimes=[-1],
    ),
)
print(lockup.render())
Source code in src/rompy_swan/components/group.py
class LOCKUP(BaseComponent):
    """Lockup group component.

    .. code-block:: text

        COMPUTE ...
        HOTFILE ...
        COMPUTE ...
        HOTFILE ...
        ...
        STOP

    This is a group component to specify SWAN "Lockup" commands including multiple
    `COMPUTE` commands that may or may not be interleaved with `HOTFILE` commands,
    and a final `STOP` command.

    Examples
    --------

    .. ipython:: python
        :okwarning:

        from rompy_swan.components.group import LOCKUP
        lockup = LOCKUP(
            compute=dict(
                model_type="stat",
                times=dict(
                    model_type="nonstationary",
                    tbeg="1990-01-01T00:00:00",
                    tend="1990-01-01T03:00:00",
                    delt="PT1H",
                    dfmt="hr",
                ),
                hotfile=dict(fname="hotfile"),
                hottimes=[-1],
            ),
        )
        print(lockup.render())

    """

    model_type: Literal["lockup", "LOCKUP"] = Field(
        default="lockup", description="Model type discriminator"
    )
    compute: COMPUTE_TYPE = Field(description="Compute components")

    def cmd(self) -> list:
        """Command file strings for this component."""
        return self.compute.cmd() + [STOP().render()]

Attributes

model_type class-attribute instance-attribute

model_type: Literal['lockup', 'LOCKUP'] = Field(default='lockup', description='Model type discriminator')

compute class-attribute instance-attribute

compute: COMPUTE_TYPE = Field(description='Compute components')

Functions

cmd

cmd() -> list

Command file strings for this component.

Source code in src/rompy_swan/components/group.py
def cmd(self) -> list:
    """Command file strings for this component."""
    return self.compute.cmd() + [STOP().render()]