DeepGPR 0.0.14__tar.gz → 0.0.16__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 (36) hide show
  1. {deepgpr-0.0.14 → deepgpr-0.0.16}/PKG-INFO +86 -18
  2. {deepgpr-0.0.14 → deepgpr-0.0.16}/README.md +85 -17
  3. {deepgpr-0.0.14 → deepgpr-0.0.16}/pyproject.toml +1 -1
  4. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR/__init__.py +24 -8
  5. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR/common.py +327 -190
  6. deepgpr-0.0.16/src/DeepGPR/compute2.py +1082 -0
  7. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr.cu +2072 -0
  8. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr.dll +0 -0
  9. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr.h +92 -0
  10. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr.so +0 -0
  11. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr_cpu.c +1809 -0
  12. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr_cpu.dll +0 -0
  13. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr_cpu.dylib +0 -0
  14. deepgpr-0.0.16/src/DeepGPR/lib/deepgpr_cpu.so +0 -0
  15. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR/lib/libomp.dylib +0 -0
  16. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR/multiscale.py +15 -8
  17. deepgpr-0.0.16/src/DeepGPR/wavelet.py +140 -0
  18. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR.egg-info/PKG-INFO +86 -18
  19. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR.egg-info/SOURCES.txt +5 -1
  20. deepgpr-0.0.16/tests/test_discrete_adjoint.py +383 -0
  21. deepgpr-0.0.16/tests/test_numerics.py +544 -0
  22. deepgpr-0.0.16/tests/test_wavelet.py +135 -0
  23. deepgpr-0.0.14/src/DeepGPR/compute2.py +0 -491
  24. deepgpr-0.0.14/src/DeepGPR/lib/deepgpr.cu +0 -1390
  25. deepgpr-0.0.14/src/DeepGPR/lib/deepgpr.dll +0 -0
  26. deepgpr-0.0.14/src/DeepGPR/lib/deepgpr.so +0 -0
  27. deepgpr-0.0.14/src/DeepGPR/lib/deepgpr_cpu.c +0 -1161
  28. deepgpr-0.0.14/src/DeepGPR/lib/deepgpr_cpu.dll +0 -0
  29. deepgpr-0.0.14/src/DeepGPR/lib/deepgpr_cpu.dylib +0 -0
  30. deepgpr-0.0.14/src/DeepGPR/lib/deepgpr_cpu.so +0 -0
  31. deepgpr-0.0.14/src/DeepGPR/wavelet.py +0 -34
  32. {deepgpr-0.0.14 → deepgpr-0.0.16}/license +0 -0
  33. {deepgpr-0.0.14 → deepgpr-0.0.16}/setup.cfg +0 -0
  34. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR.egg-info/dependency_links.txt +0 -0
  35. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR.egg-info/requires.txt +0 -0
  36. {deepgpr-0.0.14 → deepgpr-0.0.16}/src/DeepGPR.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DeepGPR
3
- Version: 0.0.14
3
+ Version: 0.0.16
4
4
  Summary: PyTorch and CUDA for GPR FWI
5
5
  Author-email: Lei Liu <liulei990222@gmail.com>
6
6
  License-Expression: MIT
@@ -72,7 +72,7 @@ import matplotlib.pyplot as plt
72
72
 
73
73
  # Set up the parameters and models
74
74
  device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
75
- dx=0.02
75
+ dx=0.02 # Or [dx, dy, dz], for example [0.02, 0.015, 0.01]
76
76
  dt=3e-11
77
77
  nt=2000
78
78
  er = torch.ones(100, 100,1) * 2
@@ -84,7 +84,9 @@ receiver_location=torch.tensor([[[10,90,0]]],device=device,dtype=torch.int)
84
84
  freq=2e8
85
85
  peak_time = 1 / freq
86
86
  source_amplitudes = torch.zeros((1,nt,1),device=device)
87
- source_amplitudes[0,:,0]=DeepGPR.ricker(freq, nt, dt, peak_time).to(device)
87
+ source_amplitudes[0,:,0]=DeepGPR.wavelet.ricker(
88
+ freq, nt, dt, peak_time, device=device
89
+ )
88
90
 
89
91
 
90
92
  #forward modeling
@@ -111,6 +113,28 @@ plt.show()
111
113
 
112
114
  There are more examples in the ./examples.
113
115
 
116
+ ## Source Wavelets
117
+
118
+ Wavelets are available from the `DeepGPR.wavelet` module. Every function
119
+ returns a one-dimensional PyTorch tensor and accepts optional `dtype` and
120
+ `device` arguments.
121
+
122
+ ```python
123
+ ricker = DeepGPR.wavelet.ricker(freq, nt, dt, peak_time, device=device)
124
+ gaussian = DeepGPR.wavelet.gaussian(freq, nt, dt, peak_time, device=device)
125
+ derivative = DeepGPR.wavelet.gaussian_derivative(
126
+ freq, nt, dt, peak_time, device=device
127
+ )
128
+ morlet = DeepGPR.wavelet.morlet(
129
+ freq, nt, dt, peak_time, cycles=3.0, device=device
130
+ )
131
+ burst = DeepGPR.wavelet.sine_burst(
132
+ freq, nt, dt, peak_time, cycles=3.0, device=device
133
+ )
134
+ ```
135
+
136
+ `DeepGPR.ricker(...)` remains available as a backward-compatible alias.
137
+
114
138
 
115
139
 
116
140
  The following figures present representative 2D and 3D full-waveform inversion (FWI) examples. For each case, the true model, initial model, and inverted result are shown to evaluate the reconstruction performance of the proposed method.
@@ -148,31 +172,48 @@ def compute(device, dx=None, dt=None,
148
172
  E=None, H=None, PML=None,
149
173
  pmlthick=10, source_direction=2, reciever_direction=2,
150
174
  model_gradient_sampling_interval=1,
175
+ wavefield_storage_dtype=torch.float32,
151
176
  use_async_offload=False,
152
177
  fdtd_order=2,
153
178
  mode=2,
154
- debug=False):
179
+ debug=False,
180
+ print_parameters=False,
181
+ save_forward_wavefield_path=None):
155
182
  ```
183
+
184
+ Set `print_parameters=True` on an ordinary call to print the normalized
185
+ configuration and memory estimate immediately before the native solver starts:
186
+
187
+ ```python
188
+ result = DeepGPR.compute(
189
+ device="cuda:0",
190
+ # Other model, source, and acquisition arguments...
191
+ print_parameters=True,
192
+ )
193
+ ```
194
+
156
195
  ## 📥 Input Parameters
157
196
  ### 1. Basic Physics & Grid Parameters
158
197
 
159
198
  | Parameter | Data Type | Description |
160
199
  | :--- | :--- | :--- |
161
200
  | **`device`** | `torch.device` / `str` | PyTorch computation device, e.g., `'cuda:0'` or `'cpu'`. CUDA loads `deepgpr.so/.dll`; CPU loads `deepgpr_cpu.so/.dll/.dylib`. |
162
- | **`dx`** | `float` | Spatial grid step size (assuming an isotropic grid, i.e., $dx = dy = dz$). Typically in meters (m). |
163
- | **`dt`** | `float` | Time step size. **Note**: Must strictly satisfy the CFL (Courant-Friedrichs-Lewy) stability condition, or an exception will be raised. Typically in seconds (s). |
201
+ | **`dx`** | `float` / 3-value `list`, `tuple`, or `Tensor` | Grid spacing in meters. A scalar uses an isotropic grid ($dx = dy = dz$). Three values specify independent $(dx, dy, dz)$ spacings for the finite differences, CFL condition, CPML coefficients, and source scaling. |
202
+ | **`dt`** | `float` | Time step size. It is checked against a material-aware CFL limit that includes the selected 2/4/8-order stencil. Typically in seconds (s). |
164
203
  | **`fdtd_order`** | `int` | Spatial finite-difference order used by the FDTD field updates. Supported values are `2`, `4`, and `8`; default is `2` for compatibility with earlier versions. |
165
204
  | **`mode`** | `int` | FWI gradient mode. `2` keeps the previous Ez-only model-gradient calculation. `3` uses Ex, Ey, and Ez electric-field contributions for relative permittivity and conductivity gradients. |
166
- | **`debug`** | `bool` | Runs expensive NaN/Inf and zero-field validation checks when `True`. The default `False` keeps these checks disabled for faster production runs. |
205
+ | **`debug`** | `bool` | Runs expensive NaN/Inf and zero-field validation checks when `True`. Backward validation covers material, source, and initial-state gradients actually requested by autograd. The default `False` keeps these checks disabled for faster production runs. |
206
+ | **`print_parameters`** | `bool` | Prints a preflight summary before native FDTD execution. The summary includes all simulation options and a tensor-payload memory estimate for model/state tensors, CPML, saved `E_saved`/`R_saved` wavefields, receiver buffers, gradients, low-precision snapshots, and CUDA offload buffers. CPU and CUDA estimates are reported separately with a 20% capacity margin. |
207
+ | **`save_forward_wavefield_path`** | `str` / path-like / `None` | Directory used to save `E_saved` after a successful forward run. The default `None` performs no file I/O. Files use the local 24-hour start time, for example `forward_wavefield_14-35.pt`; a numeric suffix prevents overwriting when multiple runs start in the same minute. |
167
208
  ### 2. Medium Model Parameters
168
209
 
169
210
  This section defines the electromagnetic properties of the simulation space. For 2D simulations, set `nz=1`.
170
211
 
171
212
  | Parameter | Data Type | Shape | Description |
172
213
  | :--- | :--- | :--- | :--- |
173
- | **`er`** | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permittivity ($\epsilon_r$). Values must be $\ge 1$. |
174
- | **`se`** | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Electrical conductivity ($\sigma$). Values must be non-negative. |
175
- | **`mr`** | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permeability ($\mu_r$). Optional; defaults to 1 for the entire space. Shape must match `er`. |
214
+ | **`eps_r`** (`er`) | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permittivity ($\epsilon_r$). Values must be $\ge 1$. `er` is the deprecated alias. |
215
+ | **`sigma`** (`se`) | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Electrical conductivity ($\sigma$). Values must be non-negative. `se` is the deprecated alias. |
216
+ | **`mu_r`** (`mr`) | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permeability ($\mu_r$). Optional; defaults to 1 for the entire space. `mr` is the deprecated alias. |
176
217
 
177
218
  > **Dimension Key**: `nx`, `ny`, and `nz` represent the number of grid cells along the X, Y, and Z axes, respectively.
178
219
 
@@ -186,7 +227,7 @@ This section defines the geometric observation system (coordinates) and the exci
186
227
  | **`source_location`** | `Tensor` (int) | `(nstep, nsr, 3)` | Grid coordinate indices of the sources.<br>The last dimension corresponds to `[x_idx, y_idx, z_idx]`. |
187
228
  | **`receiver_location`** | `Tensor` (int) | `(nstep, nrx, 3)` | Grid coordinate indices of the receivers.<br>The last dimension corresponds to `[x_idx, y_idx, z_idx]`. |
188
229
  | **`source_direction`** | `int` | Scalar | Polarization direction/component of the source excitation.<br>`0` = X, `1` = Y, `2` = Z (e.g., exciting $E_z$). |
189
- | **`reciever_direction`** | `int` | Scalar | The component direction recorded by the receivers. (Note: typo in variable name is kept as-is to match code).<br>`0` = $E_x$, `1` = $E_y$, `2` = $E_z$. |
230
+ | **`receiver_component`** (`reciever_direction`) | `int` | Scalar | The component recorded by the receivers.<br>`0` = $E_x$, `1` = $E_y$, `2` = $E_z$. The misspelled name remains as a deprecated alias. |
190
231
 
191
232
  > **Core Shape Definitions**:
192
233
  > * `nstep`: Number of shots/batches (independent simulation tasks running in parallel).
@@ -199,15 +240,26 @@ This section defines the geometric observation system (coordinates) and the exci
199
240
  | Parameter | Data Type | Format | Description |
200
241
  | :--- | :--- | :--- | :--- |
201
242
  | **`pmlthick`** | `int` / `list` / `Tensor`| Scalar or list of 6 | Thickness (in grid layers) of the PML (Perfectly Matched Layer) absorbing boundaries.<br>- Integer `p`: All six boundaries have thickness `p` (Z-boundaries are ignored in 2D).<br>- List `[x0, xm, y0, ym, z0, zm]`: Specific thicknesses for the 6 boundaries. |
202
- | **`model_gradient_sampling_interval`**| `int` | Scalar | Wavefield sampling interval during forward propagation (Default: 1).<br>A larger integer reduces the VRAM usage for the saved `Eall` tensor, but may decrease the accuracy of backpropagated gradients. |
203
- | **`use_async_offload`** | `bool` | Scalar | CUDA-only VRAM optimization flag (Default: `False`).<br>If `True`, the internal full wavefield tensor (`Eall`) is asynchronously offloaded to page-locked host memory (`pin_memory` CPU RAM). This drastically reduces GPU VRAM consumption at the cost of slightly slower computation times due to PCIe data transfer latency. On CPU this option is ignored. |
243
+ | **`model_gradient_sampling_interval`**| `int` | Scalar | Wavefield sampling interval during forward propagation (Default: 1).<br>A larger integer reduces VRAM use for `E_saved` and `R_saved`, but uses an explicitly approximate model gradient. The last incomplete sampling block is weighted by its actual length. |
244
+ | **`wavefield_storage_dtype`** | `torch.dtype` / `str` | `float32`, `float16`, or `bfloat16` | Storage format for saved `E_saved` and `R_saved` model-gradient wavefields. FDTD propagation remains float32. `float16` and `bfloat16` halve saved-wavefield memory at the cost of gradient accuracy; `bfloat16` has the safer dynamic range. String aliases such as `"fp16"` and `"bf16"` are accepted. |
245
+ | **`use_async_offload`** | `bool` | Scalar | CUDA-only VRAM optimization flag (Default: `False`).<br>If `True`, `E_saved` and `R_saved` are asynchronously offloaded to page-locked host memory (`pin_memory` CPU RAM). This reduces GPU VRAM consumption at the cost of PCIe transfers. On CPU this option is ignored. |
204
246
 
205
247
  ### 4.1 FWI Gradient Mode
206
248
 
207
249
  `mode` only changes how the model gradients are accumulated during backpropagation:
208
250
 
209
- - `mode=2` (default): Saves Ez in `Eall` and computes relative permittivity/conductivity gradients from Ez only. This keeps the old behavior.
210
- - `mode=3`: Saves Ex, Ey, and Ez in `Eall` and computes relative permittivity/conductivity gradients from all three electric-field components. This is intended for complete 3D Maxwell FWI. The adjoint source polarization is not changed by this option.
251
+ - `mode=2` (default): Saves Ez in `E_saved` and computes relative permittivity/conductivity gradients from Ez only.
252
+ - `mode=3`: Saves Ex, Ey, and Ez in `E_saved` and computes relative permittivity/conductivity gradients from all three electric-field components. This is intended for complete 3D Maxwell FWI. The receiver component is not changed by this option.
253
+
254
+ When `eps_r` or `sigma` requires gradients, `mode=2` is restricted to 2D Ez-TM modeling; use `mode=3` for 3D gradients. Source-waveform gradients are supported. Gradients with respect to `mu_r` are not currently implemented and are rejected explicitly.
255
+
256
+ ### 4.2 Discrete Adjoint Gradient
257
+
258
+ The backward solver applies the exact reverse-mode transpose of each executed operation in reverse order: receiver sampling, source injection, electric CPML, electric update, magnetic CPML, and magnetic update. The derivative transpose is applied to the material-weighted field cotangent, so heterogeneous update coefficients and anisotropic grid spacing are handled by the executed discrete operator. Every electric and magnetic CPML auxiliary state has a separate cotangent recurrence on all six faces.
259
+
260
+ CPML is treated as a fixed numerical boundary. Its boundary material averages are explicitly detached, and CPML cells are excluded from the returned material gradients. This separation must be retained when optimizing a model.
261
+
262
+ Use `model_gradient_sampling_interval=1` and `wavefield_storage_dtype=torch.float32` for a directional derivative check in the physical model region. Temporal subsampling and lower-precision storage deliberately approximate the gradient. Run [the gradient-check notebook](examples/4.GradientCheck.ipynb) after rebuilding the native ABI 6 libraries.
211
263
 
212
264
  ## CPU Backend Build
213
265
 
@@ -253,20 +305,36 @@ For starting a forward simulation from scratch ($t=0$), these three parameters s
253
305
  The function returns a tuple of 5 elements. These are used to extract synthetic data, initiate the gradient flow for backpropagation, or serve as initial parameters (`E`, `H`, `PML`) for subsequent time-stepped calculations.
254
306
 
255
307
  ```python
256
- return Eall, (Ex, Ey, Ez), (Hx, Hy, Hz), (x0EPhi1...zmHPhi2), receiver_amplitudes
308
+ return E_saved, (Ex, Ey, Ez), (Hx, Hy, Hz), (x0EPhi1...zmHPhi2), receiver_amplitudes
257
309
  ```
258
310
 
259
- 1. **`Eall`**: The electric field history saved for gradient calculation.
311
+ 1. **`E_saved`**: The pre-update electric field history `E^n` saved for gradient calculation and diagnostics. An internal `R_saved` tensor stores the corresponding discrete right-hand side `R^n`.
260
312
  * **Shape when `mode=2`**: `(nt_saved, nstep, nx, ny, nz)`, storing Ez only.
261
313
  * **Shape when `mode=3`**: `(3, nt_saved, nstep, nx, ny, nz)`, storing components in `[Ex, Ey, Ez]` order.
262
314
  * `nt_saved` depends on `nt` and `model_gradient_sampling_interval`.
315
+ * Dtype is selected by `wavefield_storage_dtype`.
316
+ * Set `save_forward_wavefield_path="/path/to/output"` to save this tensor as a CPU-loadable `.pt` file. Load it with `torch.load(path, map_location="cpu")`.
263
317
  2. **`(Ex, Ey, Ez)`**: The 3D electric field state at the final time step.
264
318
  3. **`(Hx, Hy, Hz)`**: The 3D magnetic field state at the final time step.
265
319
  4. **`(PML_Tuple)`**: A tuple of 24 Tensors recording the final time step state of the PML auxiliary $\Phi$ variables.
266
320
  5. **`receiver_amplitudes`**: **The core output.** The waveform signals recorded by the receivers over the entire simulation time.
267
321
  * **Shape**: `(nstep, nt, nrx)`
268
- * **Meaning**: `[Shot Index, Time Step, Receiver Index]`. Note that this output is already sliced to extract only the component specified by `reciever_direction`.
322
+ * **Meaning**: `[Shot Index, Time Step, Receiver Index]`. This output is sliced to the component specified by `receiver_component` (or its deprecated alias `reciever_direction`).
323
+
324
+
325
+ ## Tests and Verification
326
+
327
+ Fast unit tests and the complete numerical verification notebooks are kept in
328
+ the single [`tests`](tests) directory:
329
+
330
+ ```bash
331
+ python -m unittest discover -s tests -p "test_*.py"
332
+ python tests/run_notebook.py tests/00_local_backend_and_contracts.ipynb
333
+ ```
269
334
 
335
+ Run notebooks `00` through `09` in numeric order, followed by
336
+ `99_verification_summary.ipynb`. See [`tests/README.md`](tests/README.md) for
337
+ the complete matrix and CUDA verification options.
270
338
 
271
339
  # Cite information
272
340
  If you find our codes useful, please kindly cite this article. Thanks.
@@ -49,7 +49,7 @@ import matplotlib.pyplot as plt
49
49
 
50
50
  # Set up the parameters and models
51
51
  device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
52
- dx=0.02
52
+ dx=0.02 # Or [dx, dy, dz], for example [0.02, 0.015, 0.01]
53
53
  dt=3e-11
54
54
  nt=2000
55
55
  er = torch.ones(100, 100,1) * 2
@@ -61,7 +61,9 @@ receiver_location=torch.tensor([[[10,90,0]]],device=device,dtype=torch.int)
61
61
  freq=2e8
62
62
  peak_time = 1 / freq
63
63
  source_amplitudes = torch.zeros((1,nt,1),device=device)
64
- source_amplitudes[0,:,0]=DeepGPR.ricker(freq, nt, dt, peak_time).to(device)
64
+ source_amplitudes[0,:,0]=DeepGPR.wavelet.ricker(
65
+ freq, nt, dt, peak_time, device=device
66
+ )
65
67
 
66
68
 
67
69
  #forward modeling
@@ -88,6 +90,28 @@ plt.show()
88
90
 
89
91
  There are more examples in the ./examples.
90
92
 
93
+ ## Source Wavelets
94
+
95
+ Wavelets are available from the `DeepGPR.wavelet` module. Every function
96
+ returns a one-dimensional PyTorch tensor and accepts optional `dtype` and
97
+ `device` arguments.
98
+
99
+ ```python
100
+ ricker = DeepGPR.wavelet.ricker(freq, nt, dt, peak_time, device=device)
101
+ gaussian = DeepGPR.wavelet.gaussian(freq, nt, dt, peak_time, device=device)
102
+ derivative = DeepGPR.wavelet.gaussian_derivative(
103
+ freq, nt, dt, peak_time, device=device
104
+ )
105
+ morlet = DeepGPR.wavelet.morlet(
106
+ freq, nt, dt, peak_time, cycles=3.0, device=device
107
+ )
108
+ burst = DeepGPR.wavelet.sine_burst(
109
+ freq, nt, dt, peak_time, cycles=3.0, device=device
110
+ )
111
+ ```
112
+
113
+ `DeepGPR.ricker(...)` remains available as a backward-compatible alias.
114
+
91
115
 
92
116
 
93
117
  The following figures present representative 2D and 3D full-waveform inversion (FWI) examples. For each case, the true model, initial model, and inverted result are shown to evaluate the reconstruction performance of the proposed method.
@@ -125,31 +149,48 @@ def compute(device, dx=None, dt=None,
125
149
  E=None, H=None, PML=None,
126
150
  pmlthick=10, source_direction=2, reciever_direction=2,
127
151
  model_gradient_sampling_interval=1,
152
+ wavefield_storage_dtype=torch.float32,
128
153
  use_async_offload=False,
129
154
  fdtd_order=2,
130
155
  mode=2,
131
- debug=False):
156
+ debug=False,
157
+ print_parameters=False,
158
+ save_forward_wavefield_path=None):
132
159
  ```
160
+
161
+ Set `print_parameters=True` on an ordinary call to print the normalized
162
+ configuration and memory estimate immediately before the native solver starts:
163
+
164
+ ```python
165
+ result = DeepGPR.compute(
166
+ device="cuda:0",
167
+ # Other model, source, and acquisition arguments...
168
+ print_parameters=True,
169
+ )
170
+ ```
171
+
133
172
  ## 📥 Input Parameters
134
173
  ### 1. Basic Physics & Grid Parameters
135
174
 
136
175
  | Parameter | Data Type | Description |
137
176
  | :--- | :--- | :--- |
138
177
  | **`device`** | `torch.device` / `str` | PyTorch computation device, e.g., `'cuda:0'` or `'cpu'`. CUDA loads `deepgpr.so/.dll`; CPU loads `deepgpr_cpu.so/.dll/.dylib`. |
139
- | **`dx`** | `float` | Spatial grid step size (assuming an isotropic grid, i.e., $dx = dy = dz$). Typically in meters (m). |
140
- | **`dt`** | `float` | Time step size. **Note**: Must strictly satisfy the CFL (Courant-Friedrichs-Lewy) stability condition, or an exception will be raised. Typically in seconds (s). |
178
+ | **`dx`** | `float` / 3-value `list`, `tuple`, or `Tensor` | Grid spacing in meters. A scalar uses an isotropic grid ($dx = dy = dz$). Three values specify independent $(dx, dy, dz)$ spacings for the finite differences, CFL condition, CPML coefficients, and source scaling. |
179
+ | **`dt`** | `float` | Time step size. It is checked against a material-aware CFL limit that includes the selected 2/4/8-order stencil. Typically in seconds (s). |
141
180
  | **`fdtd_order`** | `int` | Spatial finite-difference order used by the FDTD field updates. Supported values are `2`, `4`, and `8`; default is `2` for compatibility with earlier versions. |
142
181
  | **`mode`** | `int` | FWI gradient mode. `2` keeps the previous Ez-only model-gradient calculation. `3` uses Ex, Ey, and Ez electric-field contributions for relative permittivity and conductivity gradients. |
143
- | **`debug`** | `bool` | Runs expensive NaN/Inf and zero-field validation checks when `True`. The default `False` keeps these checks disabled for faster production runs. |
182
+ | **`debug`** | `bool` | Runs expensive NaN/Inf and zero-field validation checks when `True`. Backward validation covers material, source, and initial-state gradients actually requested by autograd. The default `False` keeps these checks disabled for faster production runs. |
183
+ | **`print_parameters`** | `bool` | Prints a preflight summary before native FDTD execution. The summary includes all simulation options and a tensor-payload memory estimate for model/state tensors, CPML, saved `E_saved`/`R_saved` wavefields, receiver buffers, gradients, low-precision snapshots, and CUDA offload buffers. CPU and CUDA estimates are reported separately with a 20% capacity margin. |
184
+ | **`save_forward_wavefield_path`** | `str` / path-like / `None` | Directory used to save `E_saved` after a successful forward run. The default `None` performs no file I/O. Files use the local 24-hour start time, for example `forward_wavefield_14-35.pt`; a numeric suffix prevents overwriting when multiple runs start in the same minute. |
144
185
  ### 2. Medium Model Parameters
145
186
 
146
187
  This section defines the electromagnetic properties of the simulation space. For 2D simulations, set `nz=1`.
147
188
 
148
189
  | Parameter | Data Type | Shape | Description |
149
190
  | :--- | :--- | :--- | :--- |
150
- | **`er`** | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permittivity ($\epsilon_r$). Values must be $\ge 1$. |
151
- | **`se`** | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Electrical conductivity ($\sigma$). Values must be non-negative. |
152
- | **`mr`** | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permeability ($\mu_r$). Optional; defaults to 1 for the entire space. Shape must match `er`. |
191
+ | **`eps_r`** (`er`) | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permittivity ($\epsilon_r$). Values must be $\ge 1$. `er` is the deprecated alias. |
192
+ | **`sigma`** (`se`) | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Electrical conductivity ($\sigma$). Values must be non-negative. `se` is the deprecated alias. |
193
+ | **`mu_r`** (`mr`) | `Tensor` (float) | `(nx, ny, nz)` or `(nx, ny)` | Relative permeability ($\mu_r$). Optional; defaults to 1 for the entire space. `mr` is the deprecated alias. |
153
194
 
154
195
  > **Dimension Key**: `nx`, `ny`, and `nz` represent the number of grid cells along the X, Y, and Z axes, respectively.
155
196
 
@@ -163,7 +204,7 @@ This section defines the geometric observation system (coordinates) and the exci
163
204
  | **`source_location`** | `Tensor` (int) | `(nstep, nsr, 3)` | Grid coordinate indices of the sources.<br>The last dimension corresponds to `[x_idx, y_idx, z_idx]`. |
164
205
  | **`receiver_location`** | `Tensor` (int) | `(nstep, nrx, 3)` | Grid coordinate indices of the receivers.<br>The last dimension corresponds to `[x_idx, y_idx, z_idx]`. |
165
206
  | **`source_direction`** | `int` | Scalar | Polarization direction/component of the source excitation.<br>`0` = X, `1` = Y, `2` = Z (e.g., exciting $E_z$). |
166
- | **`reciever_direction`** | `int` | Scalar | The component direction recorded by the receivers. (Note: typo in variable name is kept as-is to match code).<br>`0` = $E_x$, `1` = $E_y$, `2` = $E_z$. |
207
+ | **`receiver_component`** (`reciever_direction`) | `int` | Scalar | The component recorded by the receivers.<br>`0` = $E_x$, `1` = $E_y$, `2` = $E_z$. The misspelled name remains as a deprecated alias. |
167
208
 
168
209
  > **Core Shape Definitions**:
169
210
  > * `nstep`: Number of shots/batches (independent simulation tasks running in parallel).
@@ -176,15 +217,26 @@ This section defines the geometric observation system (coordinates) and the exci
176
217
  | Parameter | Data Type | Format | Description |
177
218
  | :--- | :--- | :--- | :--- |
178
219
  | **`pmlthick`** | `int` / `list` / `Tensor`| Scalar or list of 6 | Thickness (in grid layers) of the PML (Perfectly Matched Layer) absorbing boundaries.<br>- Integer `p`: All six boundaries have thickness `p` (Z-boundaries are ignored in 2D).<br>- List `[x0, xm, y0, ym, z0, zm]`: Specific thicknesses for the 6 boundaries. |
179
- | **`model_gradient_sampling_interval`**| `int` | Scalar | Wavefield sampling interval during forward propagation (Default: 1).<br>A larger integer reduces the VRAM usage for the saved `Eall` tensor, but may decrease the accuracy of backpropagated gradients. |
180
- | **`use_async_offload`** | `bool` | Scalar | CUDA-only VRAM optimization flag (Default: `False`).<br>If `True`, the internal full wavefield tensor (`Eall`) is asynchronously offloaded to page-locked host memory (`pin_memory` CPU RAM). This drastically reduces GPU VRAM consumption at the cost of slightly slower computation times due to PCIe data transfer latency. On CPU this option is ignored. |
220
+ | **`model_gradient_sampling_interval`**| `int` | Scalar | Wavefield sampling interval during forward propagation (Default: 1).<br>A larger integer reduces VRAM use for `E_saved` and `R_saved`, but uses an explicitly approximate model gradient. The last incomplete sampling block is weighted by its actual length. |
221
+ | **`wavefield_storage_dtype`** | `torch.dtype` / `str` | `float32`, `float16`, or `bfloat16` | Storage format for saved `E_saved` and `R_saved` model-gradient wavefields. FDTD propagation remains float32. `float16` and `bfloat16` halve saved-wavefield memory at the cost of gradient accuracy; `bfloat16` has the safer dynamic range. String aliases such as `"fp16"` and `"bf16"` are accepted. |
222
+ | **`use_async_offload`** | `bool` | Scalar | CUDA-only VRAM optimization flag (Default: `False`).<br>If `True`, `E_saved` and `R_saved` are asynchronously offloaded to page-locked host memory (`pin_memory` CPU RAM). This reduces GPU VRAM consumption at the cost of PCIe transfers. On CPU this option is ignored. |
181
223
 
182
224
  ### 4.1 FWI Gradient Mode
183
225
 
184
226
  `mode` only changes how the model gradients are accumulated during backpropagation:
185
227
 
186
- - `mode=2` (default): Saves Ez in `Eall` and computes relative permittivity/conductivity gradients from Ez only. This keeps the old behavior.
187
- - `mode=3`: Saves Ex, Ey, and Ez in `Eall` and computes relative permittivity/conductivity gradients from all three electric-field components. This is intended for complete 3D Maxwell FWI. The adjoint source polarization is not changed by this option.
228
+ - `mode=2` (default): Saves Ez in `E_saved` and computes relative permittivity/conductivity gradients from Ez only.
229
+ - `mode=3`: Saves Ex, Ey, and Ez in `E_saved` and computes relative permittivity/conductivity gradients from all three electric-field components. This is intended for complete 3D Maxwell FWI. The receiver component is not changed by this option.
230
+
231
+ When `eps_r` or `sigma` requires gradients, `mode=2` is restricted to 2D Ez-TM modeling; use `mode=3` for 3D gradients. Source-waveform gradients are supported. Gradients with respect to `mu_r` are not currently implemented and are rejected explicitly.
232
+
233
+ ### 4.2 Discrete Adjoint Gradient
234
+
235
+ The backward solver applies the exact reverse-mode transpose of each executed operation in reverse order: receiver sampling, source injection, electric CPML, electric update, magnetic CPML, and magnetic update. The derivative transpose is applied to the material-weighted field cotangent, so heterogeneous update coefficients and anisotropic grid spacing are handled by the executed discrete operator. Every electric and magnetic CPML auxiliary state has a separate cotangent recurrence on all six faces.
236
+
237
+ CPML is treated as a fixed numerical boundary. Its boundary material averages are explicitly detached, and CPML cells are excluded from the returned material gradients. This separation must be retained when optimizing a model.
238
+
239
+ Use `model_gradient_sampling_interval=1` and `wavefield_storage_dtype=torch.float32` for a directional derivative check in the physical model region. Temporal subsampling and lower-precision storage deliberately approximate the gradient. Run [the gradient-check notebook](examples/4.GradientCheck.ipynb) after rebuilding the native ABI 6 libraries.
188
240
 
189
241
  ## CPU Backend Build
190
242
 
@@ -230,20 +282,36 @@ For starting a forward simulation from scratch ($t=0$), these three parameters s
230
282
  The function returns a tuple of 5 elements. These are used to extract synthetic data, initiate the gradient flow for backpropagation, or serve as initial parameters (`E`, `H`, `PML`) for subsequent time-stepped calculations.
231
283
 
232
284
  ```python
233
- return Eall, (Ex, Ey, Ez), (Hx, Hy, Hz), (x0EPhi1...zmHPhi2), receiver_amplitudes
285
+ return E_saved, (Ex, Ey, Ez), (Hx, Hy, Hz), (x0EPhi1...zmHPhi2), receiver_amplitudes
234
286
  ```
235
287
 
236
- 1. **`Eall`**: The electric field history saved for gradient calculation.
288
+ 1. **`E_saved`**: The pre-update electric field history `E^n` saved for gradient calculation and diagnostics. An internal `R_saved` tensor stores the corresponding discrete right-hand side `R^n`.
237
289
  * **Shape when `mode=2`**: `(nt_saved, nstep, nx, ny, nz)`, storing Ez only.
238
290
  * **Shape when `mode=3`**: `(3, nt_saved, nstep, nx, ny, nz)`, storing components in `[Ex, Ey, Ez]` order.
239
291
  * `nt_saved` depends on `nt` and `model_gradient_sampling_interval`.
292
+ * Dtype is selected by `wavefield_storage_dtype`.
293
+ * Set `save_forward_wavefield_path="/path/to/output"` to save this tensor as a CPU-loadable `.pt` file. Load it with `torch.load(path, map_location="cpu")`.
240
294
  2. **`(Ex, Ey, Ez)`**: The 3D electric field state at the final time step.
241
295
  3. **`(Hx, Hy, Hz)`**: The 3D magnetic field state at the final time step.
242
296
  4. **`(PML_Tuple)`**: A tuple of 24 Tensors recording the final time step state of the PML auxiliary $\Phi$ variables.
243
297
  5. **`receiver_amplitudes`**: **The core output.** The waveform signals recorded by the receivers over the entire simulation time.
244
298
  * **Shape**: `(nstep, nt, nrx)`
245
- * **Meaning**: `[Shot Index, Time Step, Receiver Index]`. Note that this output is already sliced to extract only the component specified by `reciever_direction`.
299
+ * **Meaning**: `[Shot Index, Time Step, Receiver Index]`. This output is sliced to the component specified by `receiver_component` (or its deprecated alias `reciever_direction`).
300
+
301
+
302
+ ## Tests and Verification
303
+
304
+ Fast unit tests and the complete numerical verification notebooks are kept in
305
+ the single [`tests`](tests) directory:
306
+
307
+ ```bash
308
+ python -m unittest discover -s tests -p "test_*.py"
309
+ python tests/run_notebook.py tests/00_local_backend_and_contracts.ipynb
310
+ ```
246
311
 
312
+ Run notebooks `00` through `09` in numeric order, followed by
313
+ `99_verification_summary.ipynb`. See [`tests/README.md`](tests/README.md) for
314
+ the complete matrix and CUDA verification options.
247
315
 
248
316
  # Cite information
249
317
  If you find our codes useful, please kindly cite this article. Thanks.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "DeepGPR"
7
- version = "0.0.14"
7
+ version = "0.0.16"
8
8
  authors = [
9
9
  { name = "Lei Liu", email = "liulei990222@gmail.com" }
10
10
  ]
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
 
9
9
  _FLOAT_P = ctypes.POINTER(ctypes.c_float)
10
10
  _INT_P = ctypes.POINTER(ctypes.c_int)
11
+ _VOID_P = ctypes.c_void_p
11
12
 
12
13
  _PACKAGE_DIR = Path(__file__).resolve().parent
13
14
  _LIB_DIR = _PACKAGE_DIR / "lib"
@@ -154,8 +155,17 @@ def _configure_deepgpr_library(lib: ctypes.CDLL) -> None:
154
155
  if getattr(lib, "_deepgpr_argtypes_configured", False):
155
156
  return
156
157
 
158
+ lib.deepgpr_abi_version.argtypes = []
159
+ lib.deepgpr_abi_version.restype = ctypes.c_int
160
+ abi_version = int(lib.deepgpr_abi_version())
161
+ if abi_version != 6:
162
+ raise RuntimeError(
163
+ f"Incompatible DeepGPR native ABI {abi_version}; expected ABI 6. "
164
+ "Rebuild the CPU/CUDA shared libraries from the current sources."
165
+ )
166
+
157
167
  lib.forward.argtypes = [
158
- _FLOAT_P, _FLOAT_P, _FLOAT_P, _FLOAT_P,
168
+ _FLOAT_P, _FLOAT_P, _FLOAT_P, _VOID_P, _VOID_P,
159
169
  _FLOAT_P, _FLOAT_P, _FLOAT_P,
160
170
  _FLOAT_P, _FLOAT_P, _FLOAT_P,
161
171
  _FLOAT_P, _FLOAT_P, _FLOAT_P,
@@ -175,16 +185,18 @@ def _configure_deepgpr_library(lib: ctypes.CDLL) -> None:
175
185
  _FLOAT_P, _FLOAT_P, _FLOAT_P, _FLOAT_P,
176
186
  _FLOAT_P, _FLOAT_P, _FLOAT_P, _FLOAT_P,
177
187
 
178
- ctypes.c_float, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_float,
179
- _INT_P, _FLOAT_P,
188
+ ctypes.c_float, ctypes.c_int, ctypes.c_int, ctypes.c_int,
189
+ ctypes.c_float, ctypes.c_float, ctypes.c_float,
190
+ _INT_P, _FLOAT_P, ctypes.c_int,
180
191
  ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
181
192
  _INT_P, _FLOAT_P,
182
- ctypes.c_int, ctypes.c_int, ctypes.c_int,
193
+ ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
194
+ ctypes.c_int,
183
195
  ]
184
196
  lib.forward.restype = None
185
197
 
186
198
  lib.backward.argtypes = [
187
- _FLOAT_P, _FLOAT_P, _FLOAT_P, _FLOAT_P,
199
+ _FLOAT_P, _FLOAT_P, _FLOAT_P, _VOID_P, _VOID_P,
188
200
  _FLOAT_P, _FLOAT_P, _FLOAT_P,
189
201
  _FLOAT_P, _FLOAT_P, _FLOAT_P,
190
202
  _FLOAT_P, _FLOAT_P, _FLOAT_P,
@@ -204,12 +216,15 @@ def _configure_deepgpr_library(lib: ctypes.CDLL) -> None:
204
216
  _FLOAT_P, _FLOAT_P, _FLOAT_P, _FLOAT_P,
205
217
  _FLOAT_P, _FLOAT_P, _FLOAT_P, _FLOAT_P,
206
218
 
207
- ctypes.c_float, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_float,
219
+ ctypes.c_float, ctypes.c_int, ctypes.c_int, ctypes.c_int,
220
+ ctypes.c_float, ctypes.c_float, ctypes.c_float,
208
221
  ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
209
222
  _INT_P, _FLOAT_P,
210
223
  ctypes.c_int,
224
+ ctypes.c_int, _INT_P, ctypes.c_int, _FLOAT_P, ctypes.c_int,
211
225
  _FLOAT_P, _FLOAT_P,
212
- ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
226
+ ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
227
+ ctypes.c_int,
213
228
  ]
214
229
  lib.backward.restype = None
215
230
 
@@ -245,7 +260,7 @@ def _load_deepgpr_library(kind: str) -> ctypes.CDLL:
245
260
  load_errors.append(f"{path}: {exc}")
246
261
  continue
247
262
 
248
- _require_exported_symbols(lib, ("forward", "backward"), path)
263
+ _require_exported_symbols(lib, ("forward", "backward", "deepgpr_abi_version"), path)
249
264
  _configure_deepgpr_library(lib)
250
265
  lib._deepgpr_path = str(path.resolve())
251
266
  _LOADED_LIBS[kind] = lib
@@ -321,6 +336,7 @@ def set_library_fdtd_order(lib: ctypes.CDLL, fdtd_order: int) -> None:
321
336
  from .common import *
322
337
  from .compute2 import *
323
338
  from .multiscale import *
339
+ from . import wavelet as wavelet
324
340
  from .wavelet import *
325
341
 
326
342