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.
Files changed (26) hide show
  1. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/PKG-INFO +1 -1
  2. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/pyproject.toml +1 -1
  3. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/__init__.py +2 -0
  4. seiscode_models-0.2.0/src/seiscode_models/core.py +226 -0
  5. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/PKG-INFO +1 -1
  6. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/SOURCES.txt +2 -0
  7. seiscode_models-0.2.0/tests/test_core.py +30 -0
  8. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/LICENSE +0 -0
  9. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/README.md +0 -0
  10. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/setup.cfg +0 -0
  11. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/models/__init__.py +0 -0
  12. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/models/geological.py +0 -0
  13. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/models/primitives.py +0 -0
  14. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/__init__.py +0 -0
  15. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/dtypes.py +0 -0
  16. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/io.py +0 -0
  17. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/validation.py +0 -0
  18. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models/utils/visualization.py +0 -0
  19. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/dependency_links.txt +0 -0
  20. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/requires.txt +0 -0
  21. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/src/seiscode_models.egg-info/top_level.txt +0 -0
  22. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_dtypes.py +0 -0
  23. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_geological.py +0 -0
  24. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_io.py +0 -0
  25. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_primitives.py +0 -0
  26. {seiscode_models-0.1.0 → seiscode_models-0.2.0}/tests/test_visualization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seiscode-models
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Synthetic subsurface velocity model generators for seismic testing
5
5
  Author-email: Damien Pageot <damien.pageot@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "seiscode-models"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Synthetic subsurface velocity model generators for seismic testing"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seiscode-models
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Synthetic subsurface velocity model generators for seismic testing
5
5
  Author-email: Damien Pageot <damien.pageot@gmail.com>
6
6
  License: MIT
@@ -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