Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0ed7118
modify sequence grid to accept 2d shape and spacing
mcflugen Oct 21, 2023
5d02608
modify to find shoreline at each grid row
mcflugen Oct 21, 2023
c365aac
modify flexure to calculate for each row
mcflugen Oct 21, 2023
d0ca8d2
add some errors for invalid output files
mcflugen Oct 21, 2023
92af39b
add file name to sequence-plot cli
mcflugen Oct 21, 2023
17347d0
modify to write multiple rows to output file
mcflugen Oct 21, 2023
ac893b7
fix some typing; add some docstrings
mcflugen Oct 21, 2023
8b8ed57
modify fluvial to only operate on the middle row
mcflugen Oct 21, 2023
83a5b3f
write layers on row and columns
mcflugen Oct 21, 2023
8732f67
modify plotter to plot a given row
mcflugen Oct 21, 2023
c5cd1d9
track shoreline and shelf edge at each row
mcflugen Oct 21, 2023
2382fc2
modify to operate with multiple rows
mcflugen Oct 21, 2023
44de4af
remove some lint
mcflugen Oct 21, 2023
59030d8
add properties for number of rows/columns
mcflugen Oct 22, 2023
677e174
plot rows of a grid
mcflugen Oct 22, 2023
c1f632a
keep track of shoreline, shelf_edge at rows over time
mcflugen Oct 22, 2023
f3de8dd
wrap long line
mcflugen Oct 22, 2023
07693c5
add some docstrings
mcflugen Oct 23, 2023
9991ecc
modify for multiple rows
mcflugen Oct 23, 2023
ae430f4
update tests for multiple rows
mcflugen Oct 23, 2023
4a524c5
drop testing with python 3.9
mcflugen Oct 23, 2023
62ebd07
fix the default rows to write to the output file
mcflugen Nov 20, 2023
b1b6e2b
update default values for components
mcflugen Nov 20, 2023
c315df8
keep x_of_shore and x_of_shelf_edge arrays 2d
mcflugen Nov 20, 2023
9681f93
fix toml literal block
mcflugen Nov 20, 2023
a2f77be
fix job name
mcflugen Nov 20, 2023
ac4dbc1
fix default row to plot for plot_grid
mcflugen Nov 20, 2023
4e990c9
add news fragment
mcflugen Nov 20, 2023
eef79c7
update input file docs for sequence.grid section
mcflugen Nov 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/notebooks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ jobs:
- name: Install dependencies
run: pip install nox

- name: Build documentation
- name: Test notebooks
run: nox -s test-notebooks
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.10", "3.11"]

steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/cli/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ``sequence``
# Command-line interface

The *Sequence* model can be run from the command line using the
*sequence* program.
Expand Down
27 changes: 25 additions & 2 deletions docs/reference/cli/input_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,34 @@ spacing = 1000.0
```

In this case we have a grid that represents a 1D profile that consists of
500 columns (i.e. vertical stacks) of sediment (the *n_cols* parameter).
100 columns (i.e. vertical stacks) of sediment (the *n_cols* parameter).

The *spacing* parameter is the width of each of your sediment stacks in meters.
Thus, the length of you domain is the product of the number of columns with
the spacing (that is, for this example, 500 * 100 m or 50 km).
the spacing (that is, for this example, 100 * 1000 m or 100 km).

You can also run *Sequence* in a quasi-2d mode in which *Sequence* models parallel
cross-shore profiles. Each profile will have its own sediment supply that is
transported into the ocean and then can be diffused between profiles. To set up
such a case, the grid section can, for example, be modified in the following way,

```toml
[sequence.grid]
shape = [3, 100]
spacing = [10000.0, 1000.0]
```
In this case *Sequence* will create three parallel profiles (with an along-shore
width of 10000.0 m).

:::{note}
The following is equivalent to the 1D example above,

```toml
[sequence.grid]
shape = [1, 100]
spacing = [1.0, 1000.0]
```
:::

(the-output-section)=

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/cli/sealevel.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ we replace this section with the following,

```toml
[sequence.sea_level]
filepath = sealevel.csv
filepath = "sealevel.csv"
```

With this configuration, *Sequence* will read sea level values from this
Expand Down
3 changes: 3 additions & 0 deletions news/78.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

Added a quasi-2d mode for *Sequence* that keeps track of multiple cross-shore
rows.
116 changes: 97 additions & 19 deletions sequence/_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,93 @@
class SequenceModelGrid(RasterModelGrid):
"""Create a Landlab ModelGrid for use with Sequence."""

def __init__(self, n_cols: int, spacing: float = 100.0):
def __init__(
self, shape: int | tuple[int, int], spacing: float | tuple[float, float] = 100.0
):
"""Create a *Landlab* :class:`~landlab.grid.base.ModelGrid` for use with *Sequence*.

Parameters
----------
n_cols : int
The number of columns.
spacing : float, optional
The spacing between columns.
shape : int or tuple of int
The number of columns in the cross-shore direction or, if a ``tuple``,
``(n_rows, n_cols)`` where rows are in the along-shore direction and
columns in the cross-shore direction.
spacing : float or tuple of float, optional
The spacing between columns or, if ``len(shape) == 2``,
``(row_spacing, col_spacing)``.

Examples
--------
>>> from sequence import SequenceModelGrid
>>> grid = SequenceModelGrid(500, spacing=10.0)
>>> grid.shape
(3, 500)
>>> grid.spacing
(1.0, 10.0)
>>> grid = SequenceModelGrid(5, spacing=10.0)
>>> grid.y_of_row
array([ 0., 1., 2.])
>>> grid.x_of_column
array([ 0., 10., 20., 30., 40.])

>>> grid = SequenceModelGrid((3, 5), spacing=(10000.0, 10.0))
>>> grid.y_of_row
array([ 0., 10000., 20000., 30000., 40000.])
>>> grid.x_of_column
array([ 0., 10., 20., 30., 40.])
"""
super().__init__((3, n_cols), xy_spacing=(spacing, 1.0))
row_col_shape: list[int] = np.atleast_1d(np.asarray(shape, dtype=int))
row_col_spacing: list[float] = np.atleast_1d(np.asarray(spacing, dtype=float))

if (len(row_col_shape) == 1 and len(row_col_spacing) != 1) or (
len(row_col_shape) != 1 and len(row_col_spacing) != len(row_col_shape)
):
raise ValueError(
f"spacing dimension ({len(row_col_spacing)}) does not match shape"
f" dimension ({len(row_col_shape)})"
)

if len(row_col_shape) == 1:
n_rows, n_cols = 1, row_col_shape[0]
elif len(row_col_shape) == 2:
n_rows, n_cols = row_col_shape
else:
raise ValueError(
f"invalid number of dimensions for grid ({len(row_col_shape)})"
)

if len(row_col_shape) == 1:
row_spacing, col_spacing = 1.0, row_col_spacing[0]
elif len(row_col_shape) == 2:
row_spacing, col_spacing = row_col_spacing

super().__init__((n_rows + 2, n_cols), xy_spacing=(col_spacing, row_spacing))

self.status_at_node[self.nodes_at_top_edge] = self.BC_NODE_IS_CLOSED
self.status_at_node[self.nodes_at_bottom_edge] = self.BC_NODE_IS_CLOSED

self.at_node["sediment_deposit__thickness"] = self.zeros(at="node")
self.at_grid["sea_level__elevation"] = 0.0

# self.new_field_location("row", size=int(n_rows + 2))
self.new_field_location("row", size=int(n_rows))

@property
def x_of_column(self) -> NDArray:
"""X-coordinate for each column of the grid."""
return self.x_of_node[self.nodes_at_top_edge]

@property
def number_of_rows(self) -> int:
"""Number of rows of nodes."""
return self.shape[0]
# return self.shape[0] - 2

@property
def number_of_columns(self) -> int:
"""Number of columns of nodes."""
return self.shape[1]

@property
def y_of_row(self) -> NDArray:
"""Y-coordinate for each row of the grid."""
return self.y_of_node[self.nodes_at_left_edge]

def get_profile(self, name: str) -> NDArray:
"""Return the values of a field along the grid's profile.

Expand All @@ -55,7 +115,8 @@ def get_profile(self, name: str) -> NDArray:
values : ndarray
The values of the field located at the middle row of nodes.
"""
return self.at_node[name].reshape(self.shape)[1]
return self.at_node[name].reshape(self.shape)[1:-1]
# return self.at_node[name].reshape(self.shape)[row]

@classmethod
def from_toml(cls, filepath: os.PathLike[str]) -> "SequenceModelGrid":
Expand Down Expand Up @@ -85,19 +146,36 @@ def from_dict(cls, params: dict) -> "SequenceModelGrid":
params : dict
A dictionary that contains the parameters needed to
create the grid.

Examples
--------
>>> from sequence import SequenceModelGrid
>>> params = {"shape": 5, "spacing": 10.0}
>>> grid = SequenceModelGrid.from_dict(params)
>>> grid.y_of_row
array([ 0., 1., 2.])
>>> grid.x_of_column
array([ 0., 10., 20., 30., 40.])

>>> params = {"shape": (3, 5), "spacing": (10000.0, 10.0)}
>>> grid = SequenceModelGrid.from_dict(params)
>>> grid.y_of_row
array([ 0., 10000., 20000., 30000., 40000.])
>>> grid.x_of_column
array([ 0., 10., 20., 30., 40.])
"""
if "n_cols" in params:
n_cols = params["n_cols"]
elif "shape" in params:
n_cols = params["shape"][1]
if "shape" in params:
shape = params["shape"]
elif "n_cols" in params:
shape = params["n_cols"]
else:
raise KeyError("n_cols")
raise KeyError("shape")

if "spacing" in params:
spacing = params["spacing"]
elif "xy_spacing" in params:
spacing = np.broadcast_to(params["xy_spacing"], 2)[0]
spacing = np.atleast_1d(params["xy_spacing"])[::-1]
else:
raise KeyError("spacing")

return cls(n_cols, spacing=spacing)
return cls(shape, spacing=spacing)
26 changes: 15 additions & 11 deletions sequence/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from numpy.typing import ArrayLike
from tqdm import tqdm

from .errors import MissingRequiredVariable
from .errors import OutputValidationError
from .input_reader import TimeVaryingConfig
from .logging import LoggingHandler
from .plot import plot_file, plot_layers
Expand Down Expand Up @@ -381,12 +381,20 @@ def setup(set: str) -> None:


@sequence.command()
@click.option("--set", multiple=True, help="Set model parameters")
@click.option("--set", "-S", multiple=True, help="Set model parameters")
@click.argument(
"netcdf_file",
type=click.Path(
exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path
),
nargs=1,
)
@click.pass_context
def plot(ctx: Any, set: str) -> None:
def plot(ctx: Any, set: str, netcdf_file) -> None:
"""Plot a Sequence output file."""
verbose = ctx.parent.params["verbose"]
folder = pathlib.Path.cwd()
path_to_file = folder / netcdf_file

config = PLOT_KEYWORDS.copy()

Expand All @@ -398,7 +406,6 @@ def plot(ctx: Any, set: str) -> None:
)

config.update(**_load_params_from_strings(set))

if verbose and len(config) > 0:
logger.info(
os.linesep.join(
Expand All @@ -409,14 +416,11 @@ def plot(ctx: Any, set: str) -> None:
)
)

logger.info(f"Plotting {folder / 'sequence.nc'}")
logger.info(f"Plotting {path_to_file!s}")
try:
plot_file(folder / "sequence.nc", **config)
except MissingRequiredVariable as error:
logger.error(
f"{folder / 'sequence.nc'}: output file is missing a required variable "
f"({error})"
)
plot_file(path_to_file, **config)
except OutputValidationError as error:
logger.error(f"{path_to_file!s}: output file is invalid ({error!s})")
raise click.Abort() from error


Expand Down
24 changes: 21 additions & 3 deletions sequence/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,33 @@ def __str__(self) -> str:
return self._msg


class MissingRequiredVariable(SequenceError):
class OutputValidationError(SequenceError):
"""Raise this exception if there is something wrong with an output file."""

pass


class InvalidRowError(OutputValidationError):
"""Raise this exception if a required variable is missing from an output file."""

def __init__(self, row: int, n_rows: int):
self._row = row
self._n_rows = n_rows

def __str__(self) -> str:
"""Return error message that includes the requested row."""
return f"row {self._row!r} is out of bounds for output file with {self._n_rows} rows"


class MissingRequiredVariable(OutputValidationError):
"""Raise this exception if a required variable is missing from an output file."""

def __init__(self, name: str):
self._name = name

def __str_(self) -> str:
def __str__(self) -> str:
"""Return error message that includes the name of the missing variable."""
return f"{self._name!r}"
return f"requireed variable ({self._name!r}) is missing from output file"


class ParameterMismatchError(SequenceError):
Expand Down
Loading