Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 38 additions & 16 deletions source_modelling/stoch.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,38 @@ def _read_stoch_header(handle: TextIO) -> StochHeader:
)


def _read_array(handle: TextIO, header: StochHeader, label: str) -> FloatArray2D:
"""
Read a single (ny, nx) data array of a stochastic plane.

Parameters
----------
handle : TextIO
Text file handle positioned at the start of the array.
header : StochHeader
Header of the plane, giving the array dimensions.
label : str
Name of the array being read, used in error messages.

Returns
-------
FloatArray2D
The array, with shape (ny, nx).

Raises
------
parse_utils.ParseError
If fewer than nx * ny values could be read.
"""
n = header.nx * header.ny
array = np.fromfile(handle, dtype=np.float32, sep=" ", count=n)
if array.size != n:
raise parse_utils.ParseError(
f"Expected {n} values for {label}, got {array.size}"
)
return cast(FloatArray2D, array.reshape((header.ny, header.nx)))


def _read_stoch_plane(handle: TextIO) -> StochPlane:
"""
Read a complete stochastic plane from a file.
Expand All @@ -132,20 +164,9 @@ def _read_stoch_plane(handle: TextIO) -> StochPlane:
Named tuple containing the plane's header and data arrays (slip, rise, trup).
"""
header = _read_stoch_header(handle)
slip_array = np.fromfile(
handle, dtype=np.float32, sep=" ", count=header.nx * header.ny
).reshape((header.ny, header.nx))
slip = cast(FloatArray2D, slip_array)

rise_array = np.fromfile(
handle, dtype=np.float32, sep=" ", count=header.nx * header.ny
).reshape((header.ny, header.nx))
rise = cast(FloatArray2D, rise_array)

trup_array = np.fromfile(
handle, dtype=np.float32, sep=" ", count=header.nx * header.ny
).reshape((header.ny, header.nx))
trup = cast(FloatArray2D, trup_array)
slip = _read_array(handle, header, "slip")
rise = _read_array(handle, header, "rise")
trup = _read_array(handle, header, "trup")

return StochPlane(header, slip, rise, trup)

Expand Down Expand Up @@ -192,8 +213,9 @@ def from_file(cls, filename: Path) -> Self:

Raises
------
ValueError
If the number of planes specified in the file is not a positive integer.
parse_utils.ParseError
If the number of planes specified in the file is not a positive
integer, or if the file contains truncated or malformed data.
"""
filename = Path(filename)
with open(filename, "r") as handle:
Expand Down
15 changes: 15 additions & 0 deletions tests/test_stoch.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,21 @@ def test_stoch_file_truncated_raises_instead_of_hanging(truncated_stoch_file: Pa
assert "Unexpected end of file" in queue.get()


def test_stoch_file_truncated_array_raises_parse_error(tmp_path: Path):
"""A stoch file truncated inside a plane's arrays should raise ParseError
naming the array, rather than a bare reshape ValueError."""
stoch_filepath = tmp_path / "truncated_array_stoch_file"
stoch_filepath.write_text(
"1\n"
"174.5 -41.3 2 2 1.0 1.0 45 60 90 0.5 2.5 1.5\n"
"1.0 2.0 3.0 4.0\n"
"0.1 0.2 0.3 0.4\n"
"0.01 0.02 0.03\n"
)
with pytest.raises(parse_utils.ParseError, match="Expected 4 values for trup"):
StochFile.from_file(stoch_filepath)


def dump_and_reload(stoch_file: StochFile, tmp_path: Path) -> StochFile:
"""Dump a StochFile and read the result back in."""
dumped = tmp_path / "dumped.stoch"
Expand Down
Loading