source-modelling 2026.7.2__tar.gz → 2026.7.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/PKG-INFO +1 -1
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/fsp.py +1 -7
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/parse_utils.py +0 -1
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/rupture_propagation.py +2 -2
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/sources.py +10 -11
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/stoch.py +46 -30
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling.egg-info/PKG-INFO +1 -1
- source_modelling-2026.7.3/source_modelling.egg-info/scm_version.json +8 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/src/srf_parser.rs +47 -27
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/src/srf_writer.rs +15 -16
- source_modelling-2026.7.3/uv.lock +2414 -0
- source_modelling-2026.7.2/source_modelling.egg-info/scm_version.json +0 -8
- source_modelling-2026.7.2/uv.lock +0 -2539
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/deptry.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/git-extension.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/numpydoc.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/publish-PyPI.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/pytest.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/ruff.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/rust.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/types.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.github/workflows/wiki.yml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.gitignore +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.isort.cfg +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/.python-version +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/Cargo.toml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/MANIFEST.in +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/pyproject.toml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/setup.cfg +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/setup.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.CPG +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.dbf +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.prj +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.sbn +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.sbx +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.shp +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.shp.xml +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/NZ_CFM/NZ_CFM_v1_0.shx +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/__init__.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/ccldpy.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/community_fault_model.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/gc2_distances.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/gsf.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/magnitude_scaling.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/moment.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/slip.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/srf.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/trim.py +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling.egg-info/SOURCES.txt +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling.egg-info/dependency_links.txt +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling.egg-info/not-zip-safe +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling.egg-info/requires.txt +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling.egg-info/scm_file_list.json +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling.egg-info/top_level.txt +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/src/lib.rs +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/src/pytypes.rs +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/src/scanner.rs +0 -0
- {source_modelling-2026.7.2 → source_modelling-2026.7.3}/src/types.rs +0 -0
|
@@ -65,8 +65,6 @@ def _normalise_value(value: float) -> float | None:
|
|
|
65
65
|
class FSPParseError(Exception):
|
|
66
66
|
"""Exception raised for errors in parsing FSP files."""
|
|
67
67
|
|
|
68
|
-
pass
|
|
69
|
-
|
|
70
68
|
|
|
71
69
|
@dataclasses.dataclass
|
|
72
70
|
class Segment:
|
|
@@ -278,11 +276,7 @@ class FSPFile:
|
|
|
278
276
|
for line in fsp_file_handle:
|
|
279
277
|
if line.startswith("% Data"):
|
|
280
278
|
break
|
|
281
|
-
if (
|
|
282
|
-
line.startswith("% -")
|
|
283
|
-
or line.strip() == "%"
|
|
284
|
-
or line.startswith("% Event :")
|
|
285
|
-
):
|
|
279
|
+
if line.startswith(("% -", "% Event :")) or line.strip() == "%":
|
|
286
280
|
continue
|
|
287
281
|
# Strip the leading "% ", and deduplicate the spaces in the line.
|
|
288
282
|
# This is required to normalise the string so that the parse
|
{source_modelling-2026.7.2 → source_modelling-2026.7.3}/source_modelling/rupture_propagation.py
RENAMED
|
@@ -59,7 +59,7 @@ def spanning_tree_with_probabilities(
|
|
|
59
59
|
trees = []
|
|
60
60
|
probabilities = []
|
|
61
61
|
|
|
62
|
-
for tree in mst.SpanningTreeIterator(graph): #
|
|
62
|
+
for tree in mst.SpanningTreeIterator(graph): # ty: ignore[invalid-argument-type]
|
|
63
63
|
p_tree = 1.0
|
|
64
64
|
for u, v in graph.edges:
|
|
65
65
|
if tree.has_edge(u, v):
|
|
@@ -223,7 +223,7 @@ def select_top_spanning_trees(
|
|
|
223
223
|
cumulative_tree_weight = 0.0
|
|
224
224
|
spanning_trees = []
|
|
225
225
|
|
|
226
|
-
for spanning_tree in mst.SpanningTreeIterator(weighted_graph, minimum=False): #
|
|
226
|
+
for spanning_tree in mst.SpanningTreeIterator(weighted_graph, minimum=False): # ty: ignore[invalid-argument-type]
|
|
227
227
|
spanning_trees.append(spanning_tree)
|
|
228
228
|
tree_log_probability = sum(
|
|
229
229
|
spanning_tree[node_u][node_v]["weight"]
|
|
@@ -1042,7 +1042,7 @@ class Fault:
|
|
|
1042
1042
|
|
|
1043
1043
|
# This relation can now be used to identify if the list of planes given is a line.
|
|
1044
1044
|
points_into_graph: nx.DiGraph = nx.from_dict_of_lists(
|
|
1045
|
-
points_into_relation, #
|
|
1045
|
+
points_into_relation, # ty: ignore[invalid-argument-type]
|
|
1046
1046
|
create_using=nx.DiGraph,
|
|
1047
1047
|
)
|
|
1048
1048
|
try:
|
|
@@ -1221,12 +1221,12 @@ class Fault:
|
|
|
1221
1221
|
return self.fault_coordinates_to_wgs_depth_coordinates(np.array([1 / 2, 1 / 2]))
|
|
1222
1222
|
|
|
1223
1223
|
@property
|
|
1224
|
-
def geometry(self) -> shapely.
|
|
1224
|
+
def geometry(self) -> shapely.Geometry: # numpydoc ignore=RT01
|
|
1225
1225
|
"""shapely.Polygon or LineString: A shapely geometry for the fault (projected onto the surface).
|
|
1226
1226
|
|
|
1227
1227
|
Geometry will be LineString if `dip = 90`.
|
|
1228
1228
|
"""
|
|
1229
|
-
return shapely.normalize(
|
|
1229
|
+
return shapely.normalize(
|
|
1230
1230
|
shapely.union_all([plane.geometry for plane in self.planes])
|
|
1231
1231
|
)
|
|
1232
1232
|
|
|
@@ -1376,8 +1376,8 @@ class Fault:
|
|
|
1376
1376
|
float
|
|
1377
1377
|
The Rjb distance (in metres) to the point.
|
|
1378
1378
|
"""
|
|
1379
|
-
return
|
|
1380
|
-
shapely.Point(coordinates.wgs_depth_to_nztm(point))
|
|
1379
|
+
return shapely.distance(
|
|
1380
|
+
self.geometry, shapely.Point(coordinates.wgs_depth_to_nztm(point))
|
|
1381
1381
|
)
|
|
1382
1382
|
|
|
1383
1383
|
def rx_ry_distance(self, point: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
@@ -1512,15 +1512,14 @@ class CoordinateBounds(NamedTuple):
|
|
|
1512
1512
|
"""float: Maximum normalised dip coordinate, in the range of [0, 1]."""
|
|
1513
1513
|
|
|
1514
1514
|
|
|
1515
|
+
DEFAULT_BOUNDS = CoordinateBounds(min_strike=0, min_dip=0, max_strike=1, max_dip=1)
|
|
1516
|
+
|
|
1517
|
+
|
|
1515
1518
|
def closest_point_between_sources(
|
|
1516
1519
|
source_a: IsSource,
|
|
1517
1520
|
source_b: IsSource,
|
|
1518
|
-
source_a_coordinate_bounds: CoordinateBounds =
|
|
1519
|
-
|
|
1520
|
-
),
|
|
1521
|
-
source_b_coordinate_bounds: CoordinateBounds = CoordinateBounds(
|
|
1522
|
-
min_strike=0, min_dip=0, max_strike=1, max_dip=1
|
|
1523
|
-
),
|
|
1521
|
+
source_a_coordinate_bounds: CoordinateBounds = DEFAULT_BOUNDS,
|
|
1522
|
+
source_b_coordinate_bounds: CoordinateBounds = DEFAULT_BOUNDS,
|
|
1524
1523
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
1525
1524
|
"""Find the closest point between two sources that have local coordinates.
|
|
1526
1525
|
|
|
@@ -12,8 +12,9 @@ References
|
|
|
12
12
|
.. [0] https://wiki.canterbury.ac.nz/display/QuakeCore/File+Formats+Used+In+Ground+Motion+Simulation#FileFormatsUsedInGroundMotionSimulation-Stochformat
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
import dataclasses
|
|
15
16
|
from pathlib import Path
|
|
16
|
-
from typing import NamedTuple,
|
|
17
|
+
from typing import IO, NamedTuple, Self, TextIO, cast
|
|
17
18
|
|
|
18
19
|
import numpy as np
|
|
19
20
|
from numpy.typing import NDArray
|
|
@@ -23,9 +24,9 @@ from source_modelling import parse_utils
|
|
|
23
24
|
from source_modelling.sources import Plane
|
|
24
25
|
|
|
25
26
|
# Type aliases for NumPy arrays with specific shapes and dtypes
|
|
26
|
-
FloatArray2D
|
|
27
|
-
LatLonArray
|
|
28
|
-
CoordinateArray
|
|
27
|
+
type FloatArray2D = NDArray[np.float32] # 2D array of float32
|
|
28
|
+
type LatLonArray = NDArray[np.float64] # Array for latitude/longitude
|
|
29
|
+
type CoordinateArray = NDArray[np.float64] # Array for coordinates
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class StochHeader(NamedTuple):
|
|
@@ -149,6 +150,7 @@ def _read_stoch_plane(handle: TextIO) -> StochPlane:
|
|
|
149
150
|
return StochPlane(header, slip, rise, trup)
|
|
150
151
|
|
|
151
152
|
|
|
153
|
+
@dataclasses.dataclass
|
|
152
154
|
class StochFile:
|
|
153
155
|
"""
|
|
154
156
|
Class for reading and accessing stochastic slip model files.
|
|
@@ -156,30 +158,10 @@ class StochFile:
|
|
|
156
158
|
This class handles the parsing of stochastic slip model files and provides
|
|
157
159
|
access to the contained planes and their properties.
|
|
158
160
|
|
|
159
|
-
Parameters
|
|
160
|
-
----------
|
|
161
|
-
filename : Path
|
|
162
|
-
Path to the stochastic slip model file.
|
|
163
|
-
|
|
164
|
-
Attributes
|
|
165
|
-
----------
|
|
166
|
-
data : list[StochPlane]
|
|
167
|
-
Structured raw data read from the stoch file.
|
|
168
|
-
|
|
169
|
-
Raises
|
|
170
|
-
------
|
|
171
|
-
ValueError
|
|
172
|
-
If the number of planes specified in the file is not a positive integer.
|
|
173
|
-
|
|
174
|
-
Notes
|
|
175
|
-
-----
|
|
176
|
-
Stochastic slip model files contain information about fault planes and their
|
|
177
|
-
properties such as slip, rise time, and rupture time.
|
|
178
|
-
|
|
179
161
|
Examples
|
|
180
162
|
--------
|
|
181
163
|
>>> # Assuming 'stoch_model.stoch' exists with valid stochastic slip model data
|
|
182
|
-
>>> stoch_file = StochFile('stoch_model.stoch')
|
|
164
|
+
>>> stoch_file = StochFile.from_file('stoch_model.stoch')
|
|
183
165
|
>>> planes = stoch_file.planes
|
|
184
166
|
>>> slips = stoch_file.slip
|
|
185
167
|
>>> rise_times = stoch_file.rise
|
|
@@ -190,15 +172,23 @@ class StochFile:
|
|
|
190
172
|
... print(f"Slip array shape for the first plane: {slips[0].shape}")
|
|
191
173
|
"""
|
|
192
174
|
|
|
193
|
-
|
|
175
|
+
data: list[StochPlane]
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def from_file(cls, filename: Path) -> Self:
|
|
194
179
|
"""
|
|
195
|
-
|
|
180
|
+
Initialise a StochFile instance by reading data from the specified file.
|
|
196
181
|
|
|
197
182
|
Parameters
|
|
198
183
|
----------
|
|
199
184
|
filename : Path
|
|
200
185
|
Path to the stochastic slip model file.
|
|
201
186
|
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
StochFile
|
|
190
|
+
The stoch file parsed from the filename.
|
|
191
|
+
|
|
202
192
|
Raises
|
|
203
193
|
------
|
|
204
194
|
ValueError
|
|
@@ -209,10 +199,36 @@ class StochFile:
|
|
|
209
199
|
n_planes = parse_utils.read_int(handle, "n_planes")
|
|
210
200
|
if n_planes <= 0:
|
|
211
201
|
raise parse_utils.ParseError(
|
|
212
|
-
f"Expected
|
|
202
|
+
f"Expected positive integer number of planes, received: {n_planes}."
|
|
213
203
|
)
|
|
214
204
|
planes = [_read_stoch_plane(handle) for _ in range(n_planes)]
|
|
215
|
-
|
|
205
|
+
return cls(planes)
|
|
206
|
+
|
|
207
|
+
def dump(self, handle: IO[str]) -> None:
|
|
208
|
+
"""Write a stoch file to a file-like object.
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
handle : File-like object
|
|
213
|
+
The object to write the stoch file output to.
|
|
214
|
+
"""
|
|
215
|
+
if not self.data:
|
|
216
|
+
raise ValueError("Cannot dump empty stoch file")
|
|
217
|
+
handle.write(f"{len(self.data)}\n")
|
|
218
|
+
for plane in self.data:
|
|
219
|
+
header = plane.header
|
|
220
|
+
handle.write(
|
|
221
|
+
f"{header.longitude:10.4f} {header.latitude:10.4f} {header.nx:5d} {header.ny:5d} {header.dx:8.2f} {header.dy:8.2f}\n"
|
|
222
|
+
)
|
|
223
|
+
handle.write(
|
|
224
|
+
f"{header.strike:4.0f} {header.dip:4.0f} {header.average_rake:4.0f} {header.dtop:8.2f} {header.shypo:8.2f} {header.dhypo:8.2f}\n"
|
|
225
|
+
)
|
|
226
|
+
np.savetxt(
|
|
227
|
+
handle,
|
|
228
|
+
np.vstack((plane.slip, plane.rise, plane.trup)),
|
|
229
|
+
fmt="%13.5e",
|
|
230
|
+
delimiter="",
|
|
231
|
+
)
|
|
216
232
|
|
|
217
233
|
@property
|
|
218
234
|
def planes(self) -> list[Plane]:
|
|
@@ -226,7 +242,7 @@ class StochFile:
|
|
|
226
242
|
|
|
227
243
|
Examples
|
|
228
244
|
--------
|
|
229
|
-
>>> stoch_file = StochFile('stoch_model.stoch')
|
|
245
|
+
>>> stoch_file = StochFile.from_file('stoch_model.stoch')
|
|
230
246
|
>>> planes = stoch_file.planes
|
|
231
247
|
>>> print(f"Number of planes: {len(planes)}")
|
|
232
248
|
"""
|
|
@@ -53,6 +53,7 @@ fn read_srf_header(
|
|
|
53
53
|
let ndip = scanner.next()?;
|
|
54
54
|
let len = scanner.next()?;
|
|
55
55
|
let wid = scanner.next()?;
|
|
56
|
+
scanner.expect_end_of_line()?;
|
|
56
57
|
let stk = scanner.next()?;
|
|
57
58
|
let dip = scanner.next()?;
|
|
58
59
|
let dtop = scanner.next()?;
|
|
@@ -81,6 +82,7 @@ fn read_srf_header(
|
|
|
81
82
|
const APPROX_BYTES_PER_SLIP_VALUE: usize = 12;
|
|
82
83
|
|
|
83
84
|
// Tiny PointHeader used to construct actual points later.
|
|
85
|
+
#[derive(Debug)]
|
|
84
86
|
struct PointHeader {
|
|
85
87
|
lon: f32,
|
|
86
88
|
lat: f32,
|
|
@@ -206,11 +208,6 @@ fn read_srf_points_v2(
|
|
|
206
208
|
let mut slipt1 = CsrMatrix::new(point_count, slipt1_capacity);
|
|
207
209
|
|
|
208
210
|
for (i, plane) in planes.iter().enumerate() {
|
|
209
|
-
// In version 2.0 (and version 2.0 only), it is possible to construct SRFs
|
|
210
|
-
// with multiple POINT instantiations. Technically V1.0 SRFs could be
|
|
211
|
-
// constructed with multiple POINTS instantiations but we are deliberately
|
|
212
|
-
// parsing a stricter subset of the format.
|
|
213
|
-
|
|
214
211
|
scanner.skip_token(b"POINTS")?;
|
|
215
212
|
let plane_point_count = scanner.next()?;
|
|
216
213
|
if plane.points() != plane_point_count {
|
|
@@ -293,7 +290,8 @@ mod tests {
|
|
|
293
290
|
|
|
294
291
|
const SRF_V1: &[u8] = b"1.0\n\
|
|
295
292
|
PLANE 1\n\
|
|
296
|
-
0.0 0.0 2 1 4.0 2.0
|
|
293
|
+
0.0 0.0 2 1 4.0 2.0\n\
|
|
294
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
297
295
|
POINTS 2\n\
|
|
298
296
|
0.1 -43.0 5.0 90.0 45.0 1.0e10 0.5 0.1\n\
|
|
299
297
|
30.0 1.5 3 0.0 0 0.0 0\n\
|
|
@@ -304,7 +302,8 @@ POINTS 2\n\
|
|
|
304
302
|
|
|
305
303
|
const SRF_V2: &[u8] = b"2.0\n\
|
|
306
304
|
PLANE 1\n\
|
|
307
|
-
0.0 0.0 2 1 4.0 2.0
|
|
305
|
+
0.0 0.0 2 1 4.0 2.0\n\
|
|
306
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
308
307
|
POINTS 2\n\
|
|
309
308
|
0.1 -43.0 5.0 90.0 45.0 1.0e10 0.5 0.1 3.5 2.7\n\
|
|
310
309
|
30.0 1.5 3 0.0 0 0.0 0\n\
|
|
@@ -347,8 +346,10 @@ POINTS 2\n\
|
|
|
347
346
|
// Two 1x1 planes, each with its own POINTS block.
|
|
348
347
|
const SRF_V2_TWO_PLANES: &[u8] = b"2.0\n\
|
|
349
348
|
PLANE 2\n\
|
|
350
|
-
0.0 0.0 1 1 4.0 2.0
|
|
351
|
-
|
|
349
|
+
0.0 0.0 1 1 4.0 2.0\n\
|
|
350
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
351
|
+
0.5 0.5 1 1 4.0 2.0\n\
|
|
352
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
352
353
|
POINTS 1\n\
|
|
353
354
|
0.1 -43.0 5.0 90.0 45.0 1.0e10 0.5 0.1 3.5 2.7\n\
|
|
354
355
|
30.0 1.5 2 0.0 0 0.0 0\n\
|
|
@@ -381,6 +382,34 @@ POINTS 1\n\
|
|
|
381
382
|
assert_eq!(srf.slipt1.data, vec![0.1, 0.2, 0.4]);
|
|
382
383
|
}
|
|
383
384
|
|
|
385
|
+
const SRF_V1_TWO_PLANES: &[u8] = b"1.0\n\
|
|
386
|
+
PLANE 2\n\
|
|
387
|
+
0.0 0.0 1 1 4.0 2.0\n\
|
|
388
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
389
|
+
0.5 0.5 1 1 4.0 2.0\n\
|
|
390
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
391
|
+
POINTS 2\n\
|
|
392
|
+
0.1 -43.0 5.0 90.0 45.0 1.0e10 0.5 0.1\n\
|
|
393
|
+
30.0 1.5 2 0.0 0 0.0 0\n\
|
|
394
|
+
0.1 0.2\n\
|
|
395
|
+
0.2 -43.1 5.5 90.0 45.0 1.0e10 0.6 0.1\n\
|
|
396
|
+
45.0 2.0 1 0.0 0 0.0 0\n\
|
|
397
|
+
0.4\n";
|
|
398
|
+
|
|
399
|
+
#[test]
|
|
400
|
+
fn parses_v1_with_multiple_planes() {
|
|
401
|
+
let mut scanner = scanner::Scanner::new(SRF_V1_TWO_PLANES);
|
|
402
|
+
let srf = read_srf_struct(&mut scanner).unwrap();
|
|
403
|
+
assert_eq!(srf.planes.len(), 2);
|
|
404
|
+
let metadata = match &srf.metadata {
|
|
405
|
+
SrfMetadataVersioned::V1(metadata) => metadata,
|
|
406
|
+
SrfMetadataVersioned::V2(_) => panic!("expected V1 metadata"),
|
|
407
|
+
};
|
|
408
|
+
assert_eq!(metadata.lon, vec![0.1, 0.2]);
|
|
409
|
+
assert_eq!(srf.slipt1.row_ptr, vec![0, 2, 3]);
|
|
410
|
+
assert_eq!(srf.slipt1.data, vec![0.1, 0.2, 0.4]);
|
|
411
|
+
}
|
|
412
|
+
|
|
384
413
|
#[test]
|
|
385
414
|
fn rejects_v1_point_count_mismatch() {
|
|
386
415
|
// Plane declares 2x1 points but POINTS declares 3.
|
|
@@ -460,25 +489,16 @@ POINTS 1\n\
|
|
|
460
489
|
assert!(read_srf_struct(&mut scanner).is_ok());
|
|
461
490
|
}
|
|
462
491
|
|
|
492
|
+
const SRF_BAD_PLANE_HEADER: &[u8] = b"0.0 0.0 2 1 4.0 2.0 90.0 45.0 0.0 0.0 1.0";
|
|
493
|
+
|
|
463
494
|
#[test]
|
|
464
|
-
fn
|
|
465
|
-
let
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
"PLANE headers expect 2 total points but POINTS declares 3"
|
|
472
|
-
);
|
|
473
|
-
let err = SrfParseError::PlanePointCountMismatch {
|
|
474
|
-
plane: 1,
|
|
475
|
-
declared: 2,
|
|
476
|
-
expected: 1,
|
|
477
|
-
};
|
|
478
|
-
assert_eq!(
|
|
479
|
-
err.to_string(),
|
|
480
|
-
"plane 1 expects 1 points but its POINTS block declares 2"
|
|
481
|
-
);
|
|
495
|
+
fn plane_header_rejects_no_newline() {
|
|
496
|
+
let mut scanner = scanner::Scanner::new(SRF_BAD_PLANE_HEADER);
|
|
497
|
+
let err = read_srf_header(&mut scanner, 1).unwrap_err();
|
|
498
|
+
assert!(matches!(
|
|
499
|
+
err,
|
|
500
|
+
SrfParseError::Scanner(scanner::ScannerError::NoNewlineFound { .. })
|
|
501
|
+
));
|
|
482
502
|
}
|
|
483
503
|
|
|
484
504
|
fn replace_once(data: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
|
|
@@ -113,18 +113,13 @@ fn write_plane_header<W: Write>(writer: &mut W, planes: &[SrfPlane]) -> Result<(
|
|
|
113
113
|
for plane in planes {
|
|
114
114
|
writeln!(
|
|
115
115
|
writer,
|
|
116
|
-
"{} {} {} {} {} {}
|
|
117
|
-
plane.elon,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
plane.
|
|
123
|
-
plane.stk,
|
|
124
|
-
plane.dip,
|
|
125
|
-
plane.dtop,
|
|
126
|
-
plane.shyp,
|
|
127
|
-
plane.dhyp
|
|
116
|
+
"{} {} {} {} {} {}",
|
|
117
|
+
plane.elon, plane.elat, plane.nstk, plane.ndip, plane.len, plane.wid,
|
|
118
|
+
)?;
|
|
119
|
+
writeln!(
|
|
120
|
+
writer,
|
|
121
|
+
"{} {} {} {} {}",
|
|
122
|
+
plane.stk, plane.dip, plane.dtop, plane.shyp, plane.dhyp
|
|
128
123
|
)?;
|
|
129
124
|
}
|
|
130
125
|
Ok(())
|
|
@@ -165,7 +160,8 @@ mod tests {
|
|
|
165
160
|
|
|
166
161
|
const SRF_V1: &[u8] = b"1.0\n\
|
|
167
162
|
PLANE 1\n\
|
|
168
|
-
0.0 0.0 2 1 4.0 2.0
|
|
163
|
+
0.0 0.0 2 1 4.0 2.0\n\
|
|
164
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
169
165
|
POINTS 2\n\
|
|
170
166
|
0.1 -43.0 5.0 90.0 45.0 1.0e10 0.5 0.1\n\
|
|
171
167
|
30.0 1.5 3 0.0 0 0.0 0\n\
|
|
@@ -176,8 +172,10 @@ POINTS 2\n\
|
|
|
176
172
|
|
|
177
173
|
const SRF_V2_TWO_PLANES: &[u8] = b"2.0\n\
|
|
178
174
|
PLANE 2\n\
|
|
179
|
-
0.0 0.0 1 1 4.0 2.0
|
|
180
|
-
|
|
175
|
+
0.0 0.0 1 1 4.0 2.0\n\
|
|
176
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
177
|
+
0.5 0.5 1 1 4.0 2.0\n\
|
|
178
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
181
179
|
POINTS 1\n\
|
|
182
180
|
0.1 -43.0 5.0 90.0 45.0 1.0e10 0.5 0.1 3.5 2.7\n\
|
|
183
181
|
30.0 1.5 2 0.0 0 0.0 0\n\
|
|
@@ -275,7 +273,8 @@ POINTS 1\n\
|
|
|
275
273
|
fn empty_slip_row_roundtrips() {
|
|
276
274
|
let data = b"1.0\n\
|
|
277
275
|
PLANE 1\n\
|
|
278
|
-
0.0 0.0 1 1 4.0 2.0
|
|
276
|
+
0.0 0.0 1 1 4.0 2.0\n\
|
|
277
|
+
90.0 45.0 0.0 0.0 1.0\n\
|
|
279
278
|
POINTS 1\n\
|
|
280
279
|
0.1 -43.0 5.0 90.0 45.0 1.0e10 0.5 0.1\n\
|
|
281
280
|
30.0 1.5 0 0.0 0 0.0 0\n";
|