seiscode-models 0.1.0__tar.gz → 0.2.0__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.
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/PKG-INFO +1 -1
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/pyproject.toml +1 -1
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/__init__.py +2 -0
- seiscode_models-0.2.0/src/seiscode_models/core.py +226 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/PKG-INFO +1 -1
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/SOURCES.txt +2 -0
- seiscode_models-0.2.0/tests/test_core.py +30 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/LICENSE +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/README.md +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/setup.cfg +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/models/__init__.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/models/geological.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/models/primitives.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/__init__.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/dtypes.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/io.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/validation.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/visualization.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/dependency_links.txt +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/requires.txt +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/top_level.txt +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_dtypes.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_geological.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_io.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_primitives.py +0 -0
- {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_visualization.py +0 -0
|
@@ -20,10 +20,12 @@ from .utils.io import (
|
|
|
20
20
|
load_model
|
|
21
21
|
)
|
|
22
22
|
from .utils.visualization import quick_view
|
|
23
|
+
from .core import VelocityModel
|
|
23
24
|
|
|
24
25
|
__version__ = "0.1.0"
|
|
25
26
|
|
|
26
27
|
__all__ = [
|
|
28
|
+
"VelocityModel",
|
|
27
29
|
"gradient_background",
|
|
28
30
|
"add_gaussian_anomaly",
|
|
29
31
|
"add_fault_offset",
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Object-oriented interface for 2D subsurface velocity models."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Union, Tuple, Optional
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .models.primitives import gradient_background, add_gaussian_anomaly, add_fault_offset
|
|
7
|
+
from .models.geological import (
|
|
8
|
+
dipping_layers,
|
|
9
|
+
sinusoidal_interface,
|
|
10
|
+
faulted_layers,
|
|
11
|
+
checkerboard,
|
|
12
|
+
salt_dome,
|
|
13
|
+
low_velocity_layer,
|
|
14
|
+
heterogeneous_anomaly_model,
|
|
15
|
+
)
|
|
16
|
+
from .utils.dtypes import parse_dtype
|
|
17
|
+
from .utils.validation import validate_model
|
|
18
|
+
from .utils.io import export_model
|
|
19
|
+
from .utils.visualization import quick_view
|
|
20
|
+
|
|
21
|
+
class VelocityModel:
|
|
22
|
+
"""Represents a 2D subsurface velocity model with grid metadata and method chaining."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, data: np.ndarray, dx: float = 1.0, dz: float = 1.0) -> None:
|
|
25
|
+
"""Initialize a VelocityModel from an existing NumPy array.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
data : ndarray (nx, nz)
|
|
30
|
+
Velocity model array in m/s.
|
|
31
|
+
dx, dz : float, optional
|
|
32
|
+
Grid spacing in meters. Default is 1.0.
|
|
33
|
+
"""
|
|
34
|
+
validate_model(data, dx=dx, dz=dz)
|
|
35
|
+
self._data: np.ndarray = data
|
|
36
|
+
self._dx: float = float(dx)
|
|
37
|
+
self._dz: float = float(dz)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def data(self) -> np.ndarray:
|
|
41
|
+
"""Underlying NumPy array (nx, nz)."""
|
|
42
|
+
return self._data
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def nx(self) -> int:
|
|
46
|
+
"""Number of grid cells along x."""
|
|
47
|
+
return self._data.shape[0]
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def nz(self) -> int:
|
|
51
|
+
"""Number of grid cells along z."""
|
|
52
|
+
return self._data.shape[1]
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def dx(self) -> float:
|
|
56
|
+
"""Grid spacing along x in meters."""
|
|
57
|
+
return self._dx
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def dz(self) -> float:
|
|
61
|
+
"""Grid spacing along z in meters."""
|
|
62
|
+
return self._dz
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def dtype(self) -> np.dtype:
|
|
66
|
+
"""NumPy data type precision (float32 or float64)."""
|
|
67
|
+
return self._data.dtype
|
|
68
|
+
|
|
69
|
+
# --- Factory Methods (Constructeurs géologiques orientés objet) ---
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def gradient(
|
|
73
|
+
cls,
|
|
74
|
+
nx: int = 201,
|
|
75
|
+
nz: int = 51,
|
|
76
|
+
dx: float = 1.0,
|
|
77
|
+
dz: float = 1.0,
|
|
78
|
+
v0: float = 1500.0,
|
|
79
|
+
v1: float = 4500.0,
|
|
80
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
81
|
+
) -> "VelocityModel":
|
|
82
|
+
"""Create a model with a linear velocity gradient with depth."""
|
|
83
|
+
arr = gradient_background(nx, nz, v0=v0, v1=v1, dtype=dtype)
|
|
84
|
+
return cls(arr, dx=dx, dz=dz)
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def salt_dome(
|
|
88
|
+
cls,
|
|
89
|
+
nx: int = 201,
|
|
90
|
+
nz: int = 51,
|
|
91
|
+
dx: float = 1.0,
|
|
92
|
+
dz: float = 1.0,
|
|
93
|
+
cx: int = 140,
|
|
94
|
+
width: float = 40.0,
|
|
95
|
+
v_salt: float = 4500.0,
|
|
96
|
+
v_sed: float = 2500.0,
|
|
97
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
98
|
+
) -> "VelocityModel":
|
|
99
|
+
"""Create a salt dome model."""
|
|
100
|
+
arr = salt_dome(nx, nz, cx=cx, width=width, v_salt=v_salt, v_sed=v_sed, dtype=dtype)
|
|
101
|
+
return cls(arr, dx=dx, dz=dz)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def dipping_layers(
|
|
105
|
+
cls,
|
|
106
|
+
nx: int = 201,
|
|
107
|
+
nz: int = 51,
|
|
108
|
+
dx: float = 1.0,
|
|
109
|
+
dz: float = 1.0,
|
|
110
|
+
v: Tuple[float, ...] = (1800.0, 2400.0, 3200.0, 4200.0),
|
|
111
|
+
dip_deg: float = 8.0,
|
|
112
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
113
|
+
) -> "VelocityModel":
|
|
114
|
+
"""Create a dipping layers model."""
|
|
115
|
+
arr = dipping_layers(nx=nx, nz=nz, dx=dx, dz=dz, v=v, dip_deg=dip_deg, dtype=dtype)
|
|
116
|
+
return cls(arr, dx=dx, dz=dz)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def sinusoidal_interface(
|
|
120
|
+
cls,
|
|
121
|
+
nx: int = 201,
|
|
122
|
+
nz: int = 51,
|
|
123
|
+
dx: float = 1.0,
|
|
124
|
+
dz: float = 1.0,
|
|
125
|
+
period: float = 100.0,
|
|
126
|
+
amplitude: float = 8.0,
|
|
127
|
+
v_top: float = 2000.0,
|
|
128
|
+
v_bottom: float = 3500.0,
|
|
129
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
130
|
+
) -> "VelocityModel":
|
|
131
|
+
"""Create a model with a sinusoidal interface."""
|
|
132
|
+
arr = sinusoidal_interface(nx=nx, nz=nz, period=period, amplitude=amplitude, v_top=v_top, v_bottom=v_bottom, dtype=dtype)
|
|
133
|
+
return cls(arr, dx=dx, dz=dz)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def faulted_layers(
|
|
137
|
+
cls,
|
|
138
|
+
nx: int = 201,
|
|
139
|
+
nz: int = 51,
|
|
140
|
+
dx: float = 1.0,
|
|
141
|
+
dz: float = 1.0,
|
|
142
|
+
ix_fault: int = 120,
|
|
143
|
+
throw: int = 8,
|
|
144
|
+
v: Tuple[float, ...] = (1800.0, 2400.0, 3200.0, 4200.0),
|
|
145
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
146
|
+
) -> "VelocityModel":
|
|
147
|
+
"""Create a faulted stratigraphic layers model."""
|
|
148
|
+
arr = faulted_layers(nx=nx, nz=nz, ix_fault=ix_fault, throw=throw, v=v, dtype=dtype)
|
|
149
|
+
return cls(arr, dx=dx, dz=dz)
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def checkerboard(
|
|
153
|
+
cls,
|
|
154
|
+
nx: int = 201,
|
|
155
|
+
nz: int = 51,
|
|
156
|
+
dx: float = 1.0,
|
|
157
|
+
dz: float = 1.0,
|
|
158
|
+
cell: int = 20,
|
|
159
|
+
v0: float = 3000.0,
|
|
160
|
+
dv: float = 600.0,
|
|
161
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
162
|
+
) -> "VelocityModel":
|
|
163
|
+
"""Create a checkerboard resolution test model."""
|
|
164
|
+
arr = checkerboard(nx=nx, nz=nz, cell=cell, v0=v0, dv=dv, dtype=dtype)
|
|
165
|
+
return cls(arr, dx=dx, dz=dz)
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def low_velocity_layer(
|
|
169
|
+
cls,
|
|
170
|
+
nx: int = 201,
|
|
171
|
+
nz: int = 51,
|
|
172
|
+
dx: float = 1.0,
|
|
173
|
+
dz: float = 1.0,
|
|
174
|
+
k0: int = 20,
|
|
175
|
+
thickness: int = 8,
|
|
176
|
+
v_slow: float = 1200.0,
|
|
177
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
178
|
+
) -> "VelocityModel":
|
|
179
|
+
"""Create a model with a buried low-velocity channel."""
|
|
180
|
+
arr = low_velocity_layer(nx=nx, nz=nz, k0=k0, thickness=thickness, v_slow=v_slow, dtype=dtype)
|
|
181
|
+
return cls(arr, dx=dx, dz=dz)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def heterogeneous_anomaly_model(
|
|
185
|
+
cls,
|
|
186
|
+
nx: int = 384,
|
|
187
|
+
nz: int = 122,
|
|
188
|
+
dx: float = 1.0,
|
|
189
|
+
dz: float = 1.0,
|
|
190
|
+
seed: int = 7,
|
|
191
|
+
dtype: Union[str, np.dtype] = np.float32
|
|
192
|
+
) -> "VelocityModel":
|
|
193
|
+
"""Create a complex heterogeneous synthetic anomaly model."""
|
|
194
|
+
arr = heterogeneous_anomaly_model(nx=nx, nz=nz, seed=seed, dtype=dtype)
|
|
195
|
+
return cls(arr, dx=dx, dz=dz)
|
|
196
|
+
|
|
197
|
+
# --- Modifier Methods (Fluent Interface / Method Chaining) ---
|
|
198
|
+
|
|
199
|
+
def add_anomaly(
|
|
200
|
+
self,
|
|
201
|
+
cx: float,
|
|
202
|
+
cz: float,
|
|
203
|
+
sigma_x: float,
|
|
204
|
+
sigma_z: float,
|
|
205
|
+
dv: float,
|
|
206
|
+
vmin: float = 200.0,
|
|
207
|
+
vmax: float = 8000.0
|
|
208
|
+
) -> "VelocityModel":
|
|
209
|
+
"""Add a Gaussian velocity anomaly in place and return self for method chaining."""
|
|
210
|
+
self._data = add_gaussian_anomaly(self._data, cx, cz, sigma_x, sigma_z, dv, vmin=vmin, vmax=vmax)
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
def add_fault(self, ix_fault: int, throw: int) -> "VelocityModel":
|
|
214
|
+
"""Apply a fault offset in place and return self for method chaining."""
|
|
215
|
+
self._data = add_fault_offset(self._data, ix_fault, throw)
|
|
216
|
+
return self
|
|
217
|
+
|
|
218
|
+
# --- Utility Methods ---
|
|
219
|
+
|
|
220
|
+
def export(self, filepath: Union[str, Path], format: str = "bin") -> None:
|
|
221
|
+
"""Export the model to a file compatible with seismic solvers."""
|
|
222
|
+
export_model(self._data, filepath, format=format, dx=self._dx, dz=self._dz)
|
|
223
|
+
|
|
224
|
+
def plot(self, title: str = "Velocity model") -> None:
|
|
225
|
+
"""Display the velocity model."""
|
|
226
|
+
quick_view(self._data, dx=self._dx, dz=self._dz, title=title)
|
|
@@ -2,6 +2,7 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/seiscode_models/__init__.py
|
|
5
|
+
src/seiscode_models/core.py
|
|
5
6
|
src/seiscode_models.egg-info/PKG-INFO
|
|
6
7
|
src/seiscode_models.egg-info/SOURCES.txt
|
|
7
8
|
src/seiscode_models.egg-info/dependency_links.txt
|
|
@@ -15,6 +16,7 @@ src/seiscode_models/utils/dtypes.py
|
|
|
15
16
|
src/seiscode_models/utils/io.py
|
|
16
17
|
src/seiscode_models/utils/validation.py
|
|
17
18
|
src/seiscode_models/utils/visualization.py
|
|
19
|
+
tests/test_core.py
|
|
18
20
|
tests/test_dtypes.py
|
|
19
21
|
tests/test_geological.py
|
|
20
22
|
tests/test_io.py
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Tests for the object-oriented VelocityModel interface."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pytest
|
|
4
|
+
import seiscode_models as sm
|
|
5
|
+
|
|
6
|
+
class TestVelocityModelOOP:
|
|
7
|
+
def test_factory_and_properties(self):
|
|
8
|
+
model = sm.VelocityModel.gradient(nx=100, nz=50, dx=2.0, dz=2.0, dtype="f32")
|
|
9
|
+
assert model.nx == 100
|
|
10
|
+
assert model.nz == 50
|
|
11
|
+
assert model.dx == 2.0
|
|
12
|
+
assert model.dz == 2.0
|
|
13
|
+
assert model.dtype == np.float32
|
|
14
|
+
assert model.data.shape == (100, 50)
|
|
15
|
+
|
|
16
|
+
def test_method_chaining(self):
|
|
17
|
+
model = (
|
|
18
|
+
sm.VelocityModel.gradient(nx=80, nz=40, dtype="f64")
|
|
19
|
+
.add_anomaly(cx=40, cz=20, sigma_x=5.0, sigma_z=5.0, dv=500.0)
|
|
20
|
+
.add_fault(ix_fault=50, throw=5)
|
|
21
|
+
)
|
|
22
|
+
assert isinstance(model, sm.VelocityModel)
|
|
23
|
+
assert model.dtype == np.float64
|
|
24
|
+
assert model.data.shape == (80, 40)
|
|
25
|
+
|
|
26
|
+
def test_export_from_model(self, tmp_path):
|
|
27
|
+
model = sm.VelocityModel.salt_dome(30, 30)
|
|
28
|
+
file_path = tmp_path / "oop_model.bin"
|
|
29
|
+
model.export(file_path, format="bin")
|
|
30
|
+
assert file_path.exists()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|