DeepGPR 0.0.20__tar.gz → 0.1.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 (78) hide show
  1. deepgpr-0.1.0/CHANGELOG.md +285 -0
  2. {deepgpr-0.0.20 → deepgpr-0.1.0}/MANIFEST.in +7 -4
  3. deepgpr-0.1.0/PKG-INFO +260 -0
  4. deepgpr-0.1.0/README.md +224 -0
  5. deepgpr-0.1.0/pyproject.toml +108 -0
  6. deepgpr-0.1.0/requirements.txt +5 -0
  7. deepgpr-0.1.0/src/DeepGPR/__init__.py +133 -0
  8. deepgpr-0.1.0/src/DeepGPR/_version.py +3 -0
  9. deepgpr-0.1.0/src/DeepGPR/common.py +51 -0
  10. deepgpr-0.1.0/src/DeepGPR/compute2.py +89 -0
  11. deepgpr-0.1.0/src/DeepGPR/config/__init__.py +11 -0
  12. deepgpr-0.1.0/src/DeepGPR/config/constants.py +235 -0
  13. deepgpr-0.1.0/src/DeepGPR/config/defaults.py +75 -0
  14. deepgpr-0.1.0/src/DeepGPR/inversion/__init__.py +5 -0
  15. deepgpr-0.1.0/src/DeepGPR/inversion/regularization.py +148 -0
  16. {deepgpr-0.0.20 → deepgpr-0.1.0}/src/DeepGPR/lib/deepgpr.cu +76 -23
  17. {deepgpr-0.0.20 → deepgpr-0.1.0}/src/DeepGPR/lib/deepgpr.dll +0 -0
  18. {deepgpr-0.0.20 → deepgpr-0.1.0}/src/DeepGPR/lib/deepgpr.h +36 -0
  19. {deepgpr-0.0.20 → deepgpr-0.1.0}/src/DeepGPR/lib/deepgpr.so +0 -0
  20. {deepgpr-0.0.20 → deepgpr-0.1.0}/src/DeepGPR/lib/deepgpr_cpu.c +594 -432
  21. deepgpr-0.1.0/src/DeepGPR/lib/deepgpr_cpu.dll +0 -0
  22. deepgpr-0.1.0/src/DeepGPR/lib/deepgpr_cpu.dylib +0 -0
  23. deepgpr-0.1.0/src/DeepGPR/lib/deepgpr_cpu.so +0 -0
  24. deepgpr-0.1.0/src/DeepGPR/lib/libomp.dylib +0 -0
  25. deepgpr-0.1.0/src/DeepGPR/multiscale.py +7 -0
  26. deepgpr-0.1.0/src/DeepGPR/native/__init__.py +27 -0
  27. deepgpr-0.1.0/src/DeepGPR/native/abi.py +222 -0
  28. deepgpr-0.1.0/src/DeepGPR/native/bridge.py +268 -0
  29. deepgpr-0.1.0/src/DeepGPR/native/loader.py +291 -0
  30. deepgpr-0.1.0/src/DeepGPR/postprocessing/__init__.py +12 -0
  31. deepgpr-0.1.0/src/DeepGPR/postprocessing/filters.py +158 -0
  32. deepgpr-0.1.0/src/DeepGPR/postprocessing/io.py +92 -0
  33. deepgpr-0.1.0/src/DeepGPR/preprocessing/__init__.py +29 -0
  34. deepgpr-0.1.0/src/DeepGPR/preprocessing/grid.py +119 -0
  35. deepgpr-0.1.0/src/DeepGPR/preprocessing/model_setup.py +426 -0
  36. deepgpr-0.1.0/src/DeepGPR/preprocessing/stability.py +117 -0
  37. deepgpr-0.1.0/src/DeepGPR/preprocessing/wavelets.py +173 -0
  38. deepgpr-0.1.0/src/DeepGPR/py.typed +0 -0
  39. deepgpr-0.1.0/src/DeepGPR/solver/__init__.py +44 -0
  40. deepgpr-0.1.0/src/DeepGPR/solver/autograd.py +650 -0
  41. deepgpr-0.1.0/src/DeepGPR/solver/fields.py +151 -0
  42. deepgpr-0.1.0/src/DeepGPR/solver/memory.py +403 -0
  43. deepgpr-0.1.0/src/DeepGPR/solver/modeling.py +495 -0
  44. deepgpr-0.1.0/src/DeepGPR/solver/pml.py +441 -0
  45. deepgpr-0.1.0/src/DeepGPR/solver/storage.py +329 -0
  46. deepgpr-0.1.0/src/DeepGPR/utils/__init__.py +38 -0
  47. deepgpr-0.1.0/src/DeepGPR/utils/devices.py +23 -0
  48. deepgpr-0.1.0/src/DeepGPR/utils/exceptions.py +37 -0
  49. deepgpr-0.1.0/src/DeepGPR/utils/formatting.py +25 -0
  50. deepgpr-0.1.0/src/DeepGPR/utils/logger.py +67 -0
  51. deepgpr-0.1.0/src/DeepGPR/utils/tensor_checks.py +90 -0
  52. deepgpr-0.1.0/src/DeepGPR/utils/validators.py +98 -0
  53. deepgpr-0.1.0/src/DeepGPR/visualization/__init__.py +5 -0
  54. deepgpr-0.1.0/src/DeepGPR/visualization/plotting.py +209 -0
  55. deepgpr-0.1.0/src/DeepGPR/wavelet.py +21 -0
  56. deepgpr-0.1.0/src/DeepGPR.egg-info/PKG-INFO +260 -0
  57. deepgpr-0.1.0/src/DeepGPR.egg-info/SOURCES.txt +60 -0
  58. deepgpr-0.1.0/src/DeepGPR.egg-info/requires.txt +15 -0
  59. deepgpr-0.0.20/PKG-INFO +0 -411
  60. deepgpr-0.0.20/README.md +0 -388
  61. deepgpr-0.0.20/pyproject.toml +0 -63
  62. deepgpr-0.0.20/src/DeepGPR/__init__.py +0 -376
  63. deepgpr-0.0.20/src/DeepGPR/common.py +0 -1002
  64. deepgpr-0.0.20/src/DeepGPR/compute2.py +0 -1566
  65. deepgpr-0.0.20/src/DeepGPR/lib/deepgpr_cpu.dll +0 -0
  66. deepgpr-0.0.20/src/DeepGPR/lib/deepgpr_cpu.dylib +0 -0
  67. deepgpr-0.0.20/src/DeepGPR/lib/deepgpr_cpu.so +0 -0
  68. deepgpr-0.0.20/src/DeepGPR/lib/libomp.dylib +0 -0
  69. deepgpr-0.0.20/src/DeepGPR/multiscale.py +0 -78
  70. deepgpr-0.0.20/src/DeepGPR/requirements.txt +0 -1
  71. deepgpr-0.0.20/src/DeepGPR/wavelet.py +0 -140
  72. deepgpr-0.0.20/src/DeepGPR.egg-info/PKG-INFO +0 -411
  73. deepgpr-0.0.20/src/DeepGPR.egg-info/SOURCES.txt +0 -24
  74. deepgpr-0.0.20/src/DeepGPR.egg-info/requires.txt +0 -4
  75. {deepgpr-0.0.20 → deepgpr-0.1.0}/license +0 -0
  76. {deepgpr-0.0.20 → deepgpr-0.1.0}/setup.cfg +0 -0
  77. {deepgpr-0.0.20 → deepgpr-0.1.0}/src/DeepGPR.egg-info/dependency_links.txt +0 -0
  78. {deepgpr-0.0.20 → deepgpr-0.1.0}/src/DeepGPR.egg-info/top_level.txt +0 -0
@@ -0,0 +1,285 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] — Structural refactor and numerical fixes
8
+
9
+ This release reorganises the Python package into a layered, typed and
10
+ documented code base and fixes the defects found in the review
11
+ ([`docs/AUDIT.md`](docs/AUDIT.md)).
12
+
13
+ **What changes numerically** (all deliberate, see [Numerical fixes](#numerical-fixes)):
14
+
15
+ | Area | Change | Size |
16
+ |---|---|---|
17
+ | Forward modelling | none, except ε0/µ0 in the CPML profiles now CODATA 2018 | ~5·10⁻¹⁰ relative in the coefficients; no forward output changed in the verification runs |
18
+ | CPU gradients | fixed summation order (deterministic) instead of OpenMP atomics | rounding level (≤ ~7·10⁻⁶ relative on random test problems, same size as the old run-to-run noise) |
19
+ | `design_fir_filter` / `apply_filter` | correct taps for an even filter length | filtered data changes when `int(fs/cutoff)` is even |
20
+ | `hilbert_transform` | correct analytic signal for an odd trace length | envelopes change for odd `nt`; even `nt` unchanged |
21
+ | `TVRegularization(method="isotropic")` | standard isotropic TV | different penalty; `"anisotropic"` (default) unchanged |
22
+
23
+ `legacy=True` (filters, envelope) and `method="legacy_isotropic"` (TV)
24
+ reproduce DeepGPR 0.0.20 bitwise. The refactor itself was first verified to be
25
+ bitwise identical to 0.0.20 before any of these fixes were applied — see
26
+ [Verification](#verification).
27
+
28
+ ### Architecture
29
+
30
+ | Before | After | Responsibility |
31
+ |---|---|---|
32
+ | `__init__.py` (library loading, 81/86-argument `argtypes` lists) | `native/loader.py`, `native/abi.py`, `native/bridge.py` | locate/load/configure the CPU and CUDA libraries; one declarative ABI table; argument marshalling |
33
+ | `common.py` (1026 lines, mixed concerns) | `preprocessing/{grid,stability,model_setup}.py`, `solver/{pml,fields}.py`, `inversion/regularization.py`, `utils/tensor_checks.py` | validation & PML extension, CFL, CPML, field state, TV, diagnostics |
34
+ | `compute2.py` (1585 lines) | `solver/{modeling,autograd,storage,memory}.py`, `postprocessing/io.py` | `compute()`, autograd bridge, history storage & INT8, memory report, file output |
35
+ | `wavelet.py` | `preprocessing/wavelets.py` | source wavelets |
36
+ | `multiscale.py` | `postprocessing/filters.py` | FIR low-pass, envelope |
37
+ | inline literals | `config/constants.py`, `config/defaults.py` | physical constants, ABI codes, defaults |
38
+ | — | `utils/{logger,exceptions,validators,formatting,devices}.py` | logging, errors, validation |
39
+ | — | `visualization/plotting.py` | model / radargram / trace plots |
40
+
41
+ `DeepGPR.common`, `DeepGPR.compute2`, `DeepGPR.multiscale` and `DeepGPR.wavelet`
42
+ remain importable as thin compatibility modules, and every name previously
43
+ reachable as `DeepGPR.<name>` for the public API is still exported.
44
+
45
+ ### Key refactoring decisions
46
+
47
+ - **Native changes kept minimal.** The FDTD, CPML and material-gradient
48
+ kernels are unchanged. The native sources changed only for the fixes below:
49
+ error reporting (CPU and CUDA), shared constants in `deepgpr.h`, and the
50
+ deterministic CPU adjoint. The C ABI stays at version 6; new functions are
51
+ additive and probed at run time, so binaries built from older sources still
52
+ load (without the new guarantees).
53
+ - **One source of truth for the C ABI.** `native/abi.py` lists every exported
54
+ function once, parameter by parameter, mirroring `deepgpr.h`. `argtypes`
55
+ and the call-argument order are both generated from it, replacing two
56
+ hand-maintained 80+-item positional lists. `tests/test_native_abi.py` parses
57
+ the header and fails on any drift (runs without PyTorch).
58
+ - **Named native arguments.** `solver/autograd.py` builds a
59
+ `{c_parameter_name: value}` mapping (`build_forward_arguments`,
60
+ `build_backward_arguments`); a missing or misspelled parameter raises
61
+ instead of silently shifting every following argument.
62
+ - **Typed solver state.** The 77 positional arguments of the autograd
63
+ `Function` became a frozen `SolverConfig` dataclass, a `SolverTensors`
64
+ dataclass and the 30 differentiable state tensors.
65
+ - **Named tuple for model preparation.** `initialization()` returns a
66
+ `PreparedModel` `NamedTuple`: identical tuple behaviour, readable fields.
67
+ - **Constants, not magic numbers.** Physical constants, CPML profile
68
+ parameters, stencil coefficient sums, ABI bit fields, INT8 limits, memory
69
+ margins, Hamming coefficients and API defaults live in `config/`.
70
+ - **Duplicate code removed.** Six copy-pasted CPML face blocks, six
71
+ CPML-phi allocation blocks, 30 `.contiguous()` lines, two 60-line `ctypes`
72
+ call sites and the per-face capability checks were replaced by loops/tables.
73
+
74
+ ### Numerical fixes
75
+
76
+ - **Deterministic CPU adjoint.** The CPU backward scattered the transposed
77
+ curl stencils with `#pragma omp atomic`, so gradients differed in the last
78
+ bits from run to run whenever `OMP_NUM_THREADS > 1`. It now runs in two
79
+ passes: every source point stores its stencil weight per (target field,
80
+ axis) channel, then every target point gathers the transposed stencil in a
81
+ fixed order. No OpenMP atomics remain in `deepgpr_cpu.c`; source injection
82
+ and receiver adjoints run shots in parallel and sources/receivers
83
+ sequentially. Results are **bitwise identical for any thread count**, and
84
+ the backend reports this through `deepgpr_deterministic_adjoint()`. The
85
+ gradient is still the exact discrete transpose (dot-product test below).
86
+ Cost: six field-sized buffers per shot during backward (included in the
87
+ memory estimate as "CPU adjoint stencil weights"). The CUDA adjoint still
88
+ uses `atomicAdd` and is not bitwise reproducible.
89
+ - **Native errors reach Python.** CUDA API and launch errors used to be
90
+ printed to stderr while the call returned normally. Both backends now keep a
91
+ per-thread error message (`deepgpr_last_error`, `deepgpr_clear_last_error`,
92
+ `deepgpr_supports_error_reporting`); the bridge clears it before every native
93
+ call and raises `NativeLibraryError` with the message afterwards. The CPU
94
+ backend also reports allocation failures (it used to fall back silently to
95
+ a less accurate R history) and size overflows. INT8 parameter errors in the
96
+ CUDA backend are reported the same way.
97
+ - **One set of physical constants.** ε0, µ0 and the 100 S/m PEC threshold are
98
+ defined once in `lib/deepgpr.h` (CODATA 2018) and used by the CPU and CUDA
99
+ backends; `DeepGPR.config.constants` uses the same values (previously
100
+ µ0 = 4π·10⁻⁷ in Python). `tests/test_native_abi.py` fails if they drift.
101
+ - **FIR design.** `design_fir_filter` replaced tap `(numtaps-1)//2` with the
102
+ sinc peak `2 fc/fs` also for an even number of taps, where no sample lies
103
+ at the centre. The override is now applied only for odd lengths.
104
+ - **Analytic-signal envelope.** For an odd trace length `hilbert_transform`
105
+ did not double bin `nt//2`, which is a positive frequency there. The
106
+ highest positive bin is now doubled (as `scipy.signal.hilbert`). Removing
107
+ the DC bin stays the default (`remove_dc=True`).
108
+ - **Isotropic TV.** `method="isotropic"` (or `"iso"`) now computes the
109
+ standard `Σ_cells sqrt(|∇m|² + ε)` with forward differences and a Neumann
110
+ far boundary; the smoothing `epsilon` is a constructor argument. The old
111
+ formula `sqrt(Lx² + Ly² + Lz² + ε)` is available as
112
+ `method="legacy_isotropic"`. Unknown method names now raise `ValueError`
113
+ (they used to select the old formula silently).
114
+
115
+ **Prebuilt binaries.** The Linux CPU library in `src/DeepGPR/lib` is built
116
+ from the 0.1.0 sources. The CUDA (`deepgpr.so`, `deepgpr.dll`) and the Windows /
117
+ macOS CPU binaries are still the 0.0.20 builds until the *Build Native
118
+ Libraries* workflow runs; they load and compute as before but without error
119
+ reporting (and, for the CPU ones, without the deterministic adjoint).
120
+ `tools/run_local_validation.sh` rebuilds the CPU and CUDA libraries locally
121
+ and runs the full validation.
122
+
123
+ ### Fixed
124
+
125
+ - **Caller gradients were overwritten.** The native adjoint advances the
126
+ incoming state cotangents in place; the old code passed autograd's buffers
127
+ straight through (`.contiguous()` is a no-op on contiguous tensors), so
128
+ user-supplied `grad_outputs`, retained gradients and hook arguments were
129
+ silently modified. They are now copied first (only cotangents that autograd
130
+ actually supplies; absent ones are allocated as zeros, as before). Returned
131
+ gradients are unchanged; the cost is one transient copy of the E/H/CPML state
132
+ size during backward, small next to the saved wavefield history.
133
+ - **Second backward crashed** with `AttributeError: 'NoneType' object has no
134
+ attribute 'contiguous'` (e.g. `retain_graph=True`). It now raises a clear
135
+ `RuntimeError` explaining that the history was released.
136
+ - **FDTD-order race.** `set_fdtd_order` sets process-global native state; two
137
+ threads using different orders could run with the wrong stencil. Calls now
138
+ go through an `FdtdOrderGate`: same-order calls stay concurrent, different
139
+ orders are serialised.
140
+ - **CUDA device not restored on error.** Stream/device bookkeeping around the
141
+ native call is now a `try/finally` context manager.
142
+ - **Library loading was not thread-safe** on first use; it is now locked.
143
+ - **`apply_filter` returned NaNs silently** when `cutoff >= fs` (a 0- or
144
+ 1-tap FIR divides by zero); it now raises `ValueError`, as it does when the
145
+ filter is longer than the trace.
146
+ - **`TVRegularization()(None, None)`** raised `AttributeError`; now `ValueError`.
147
+ - **Forward-wavefield file naming race.** Two processes saving in the same
148
+ minute could pick the same filename; names are now reserved atomically
149
+ (same `forward_wavefield_HH-MM[_NN].pt` scheme). Partially written files are
150
+ removed on failure.
151
+ - **Invalid requirement shipped in the wheel.** `src/DeepGPR/requirements.txt`
152
+ contained `torch+cu` and was packaged via `MANIFEST.in`; removed.
153
+ - Error message of `apply_filter` used full-width punctuation (`。`).
154
+
155
+ ### Changed
156
+
157
+ - `print()` diagnostics replaced by the `DeepGPR` logger: the waveform
158
+ broadcast "Tips" message (was printed on every call) is `INFO`, the saved
159
+ wavefield path is `INFO`, NaN/Inf reports are `ERROR`. Call
160
+ `DeepGPR.configure_logging("INFO")` to see them. `print_parameters=True`
161
+ still prints its report to stdout, byte-for-byte as before.
162
+ - Errors raised by DeepGPR now use a small hierarchy
163
+ (`DeepGPRError`, `NativeLibraryError`, `NativeLibraryNotFoundError`,
164
+ `CFLConditionError`, `NonFiniteTensorError`). Each class also derives from
165
+ the built-in type raised before (`RuntimeError`, `FileNotFoundError`,
166
+ `ValueError`, `FloatingPointError`), so existing `except` clauses still match.
167
+ All messages are unchanged.
168
+ - `pmlthick` accepts NumPy integer scalars (`np.int64(10)`), which previously
169
+ raised `TypeError`.
170
+ - `model_gradient_sampling_interval=True` is rejected (it was silently
171
+ treated as `1`).
172
+ - A warning is logged when conductivity exceeds 100 S/m: the native solvers
173
+ treat such cells as perfect conductors (no update, no gradient).
174
+ - `scipy` moved from a required to an optional (`examples`) dependency; it was
175
+ never imported.
176
+ - Version is single-sourced from `DeepGPR/_version.py`; `py.typed` marker added.
177
+
178
+ ### Performance
179
+
180
+ The deterministic CPU adjoint is also faster, because it replaces contended
181
+ atomic updates with plain gathers. Forward + backward wall time, prebuilt
182
+ 0.0.20 library vs 0.1.0 (2-core test machine, 150 time steps, 10-cell CPML):
183
+
184
+ | Problem | 1 thread | 2 threads |
185
+ |---|---|---|
186
+ | 2D 320×320, order 2 | 3.81 s → 3.16 s | 2.30 s → 1.70 s |
187
+ | 2D 320×320, order 8 | 6.50 s → 5.01 s | 3.79 s → 2.81 s |
188
+ | 3D 68³, order 2 | 19.41 s → 13.26 s | 11.91 s → 8.00 s |
189
+ | 3D 68³, order 4 | 35.93 s → 20.54 s | 22.17 s → 14.16 s |
190
+
191
+ The CUDA kernels are unchanged. Python-side overhead was reduced where it was
192
+ measurable:
193
+
194
+ - `debug=True` NaN/Inf validation scans each tensor once (`isfinite`) instead
195
+ of twice (`isnan` + `isinf`), halving device→host synchronisations; the
196
+ detailed breakdown is only computed for failing tensors.
197
+ - `checkpoint_initial_field` no longer computes (and discards) the full CPML
198
+ coefficient profiles; it only needs the face geometry.
199
+ - The memory estimate uses `dtype.itemsize` instead of allocating three
200
+ temporary tensors per call.
201
+ - All validation scalars in `initialization` are still gathered in a single
202
+ device→host transfer (the new PEC check is part of the same `torch.stack`).
203
+
204
+ ### Added
205
+
206
+ - `DeepGPR.max_stable_time_step(dx, nx, ny, nz, fdtd_order=, min_er_mr=)`.
207
+ - `DeepGPR.estimate_compute_memory(...)` (previously private).
208
+ - `DeepGPR.visualization`: `plot_model`, `plot_bscan`, `plot_trace`,
209
+ `plot_model_comparison`.
210
+ - `DeepGPR.configure_logging()` / `DeepGPR.get_logger()`.
211
+ - `DeepGPR.__version__`.
212
+ - `tests/test_native_abi.py` (header ↔ ABI table and constants, marshalling,
213
+ order gate, native error reporting, library configuration; no PyTorch
214
+ required).
215
+ - `tests/test_signal_and_regularization.py` (FIR, envelope and TV against
216
+ independent NumPy implementations; legacy switches).
217
+ - `tests/test_cpu_determinism.py` (bitwise equality for 1, 2 and 5 threads).
218
+ - `tools/verify_equivalence.py` (comparison with a reference release: forward
219
+ and gradient tolerances, legacy reproduction, determinism, error reporting).
220
+ - `legacy=` keyword for `design_fir_filter`, `apply_filter`,
221
+ `hilbert_transform`; `remove_dc=` for `hilbert_transform`; `epsilon=` and
222
+ `method="legacy_isotropic"` for `TVRegularization`.
223
+ - Native functions `deepgpr_supports_error_reporting`, `deepgpr_last_error`,
224
+ `deepgpr_clear_last_error`, `deepgpr_deterministic_adjoint` (ABI 6, additive).
225
+ - `.github/workflows/quality.yml` (ruff, ABI test, CPU unit tests, equivalence).
226
+ - `requirements.txt`, `requirements-dev.txt`, ruff/mypy/pytest configuration.
227
+
228
+ ### Removed / internal changes
229
+
230
+ - `from DeepGPR import *` no longer leaks `torch`, `nn`, `F`, `math`, `ctypes`,
231
+ `datetime`, `Path`, `warnings` and `numbers` (they were accidentally part of
232
+ `__all__`).
233
+ - The autograd `Function` `DeepGPR.DeepGPR` has a new internal call signature
234
+ (`apply(config, tensors, eps_r, sigma, source, *states)`). It was never a
235
+ documented entry point; use `DeepGPR.compute`.
236
+ - Monkey-patching `DeepGPR.compute2.<name>` no longer affects `compute()`;
237
+ patch `DeepGPR.solver.modeling` / `DeepGPR.solver.autograd` (the test-suite
238
+ was updated accordingly).
239
+
240
+ ### Verification
241
+
242
+ **Numerical fixes** (CPU library built from the 0.1.0 sources):
243
+
244
+ | Check | Scope | Result |
245
+ |---|---|---|
246
+ | Thread determinism | 21 native cases (2D/3D, order 2/4/8, fp32/fp16/bf16, coincident sources and receivers), `OMP_NUM_THREADS` = 1, 2, 3, 4, 7, 8; plus 144 full `compute` + backward cases at 1 vs 2 threads | **bitwise identical** for every thread count (the 0.0.20 library differed in 662 of 1,386 buffers between 1 and 4 threads) |
247
+ | Exact transpose | adjoint dot-product test `<A x, y> = <x, Aᵀ y>` for the full state/receiver map, 12 configurations (2D/3D, order 2/4/8, all-face and partial CPML) | relative mismatch ≤ 8.3·10⁻⁶ (0.0.20: ≤ 1.2·10⁻⁵), i.e. float32 round-off |
248
+ | Agreement with 0.0.20 | same 144 end-to-end cases | forward outputs bitwise identical; gradients within 7·10⁻⁶ relative |
249
+ | Error reporting | failing native backward through the Python bridge | `NativeLibraryError` with the native message; message cleared afterwards |
250
+
251
+ **Refactor** (before the numerical fixes), against commit `73b2b50`:
252
+
253
+ | Check | Scope | Result |
254
+ |---|---|---|
255
+ | End-to-end `compute` + adjoint, real CPU library | 155 cases: 2D/3D × order 2/4/8 × fp32/fp16/bf16 history × sampling 1/2 × int/list PML × shared/per-source waveforms, plus `mu_r`, forward-only, checkpoint continuation (2D/3D), 2D mode 3, no/one-sided PML, float64 inputs, 2D waveform arrays, single shot | 10,065 outputs & gradients **bitwise identical**; `print_parameters` report identical |
256
+ | Native marshalling (original `ctypes` binding vs ABI table) | 56 cases incl. no-PML, partial PML, all storage types | 3,808 buffers **bitwise identical**; injected argument swaps detected |
257
+ | Python numerics | CPML coefficients/descriptors, CPML state allocation & validation, CFL, PML parsing, wavelets, FIR design, INT8 layout/encoding, storage codes, memory estimate, TV, `initialization`, `checkpoint_initial_field` (incl. error types & messages) | 52,910 comparisons identical |
258
+ | ABI table vs `deepgpr.h` | all 9 exported functions | identical names, kinds and order |
259
+ | `compute()` option resolution (incl. CUDA-only paths) | 25,613 combinations: CPU/CUDA × 2D/3D × history dtype × compression × INT8 block × conversion backend × INT8 reduction × async offload × history on/off, plus argument validation | identical exceptions and messages; the 544 accepted combinations resolve to identical native storage settings |
260
+
261
+ The end-to-end and numerics checks above used a NumPy implementation of the
262
+ PyTorch tensor API. Both package versions used this compatibility layer and
263
+ the same native library, with `OMP_NUM_THREADS=1`.
264
+
265
+ **Real PyTorch and CUDA** (PyTorch 1.11 / CUDA 11.5 on an RTX 4090, CPU and
266
+ CUDA libraries rebuilt from the 0.1.0 sources, reference = commit `73b2b50`,
267
+ which is byte-identical to the 0.0.21 PyPI release):
268
+
269
+ | Check | Scope | Result |
270
+ |---|---|---|
271
+ | `tools/verify_equivalence.py` | CPU and CUDA: 2D/3D × order 2/4/8 × fp32/fp16/bf16 × sampling 1/3, no/asymmetric PML, forward-only, mode 3, checkpoint segments; CUDA INT8 (2D/3D), async offload, fp16 conversion backends; legacy switches; error reporting; CPU determinism for 1/2/4/8 threads | all passed; forward outputs **bitwise identical**, gradients within 1.9·10⁻⁶ (CPU) / 4.7·10⁻⁷ (CUDA) relative |
272
+ | Unit tests | 105 tests, `OMP_NUM_THREADS` = 1, 2, 4, 8 | all passed (1 skip: needs two GPUs) |
273
+ | Verification notebooks `tests/00`–`09`, `99` | `DEEPGPR_REQUIRE_CUDA=1` | 108 checks passed, CUDA parity evidence present |
274
+ | CI simulation | Python 3.12, PyTorch 2.x CPU: unit tests and `verify_equivalence.py --device cpu --quick`; `ruff check src/DeepGPR tools` | all passed |
275
+
276
+ The isotropic-TV comparisons use a float32 tolerance because
277
+ `TVRegularization` accumulates its penalty in float32, as in 0.0.20.
278
+ The checkpoint-vs-full-history gradient check in
279
+ `test_external_pml.py` scales its absolute tolerance with the gradient
280
+ magnitude, because the deterministic CPU adjoint sums in a different (fixed)
281
+ order than the OpenMP-atomic 0.0.20 adjoint.
282
+
283
+ ## [0.0.20] and earlier
284
+
285
+ See the Git history.
@@ -1,17 +1,20 @@
1
1
  include README.md
2
+ include CHANGELOG.md
2
3
  include license
3
4
  include pyproject.toml
5
+ include requirements.txt
4
6
 
5
- recursive-include src/DeepGPR *.py *.txt *.cu *.h *.c *.dll *.so *.dylib
7
+ recursive-include src/DeepGPR *.py *.typed *.cu *.h *.c *.dll *.so *.dylib
6
8
 
7
- prune .agents
8
- prune .codex
9
- prune .github
9
+ # Exclude local state and repository-only resources.
10
+ prune .*
11
+ prune validation
10
12
  prune Fig
11
13
  prune docs
12
14
  prune examples
13
15
  prune scripts
14
16
  prune tests
17
+ prune tools
15
18
  prune website
16
19
 
17
20
  global-exclude .DS_Store
deepgpr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,260 @@
1
+ Metadata-Version: 2.4
2
+ Name: DeepGPR
3
+ Version: 0.1.0
4
+ Summary: PyTorch and CUDA for GPR FWI
5
+ Author-email: Lei Liu <liulei990222@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/songc0a/DeepGPR
8
+ Project-URL: Repository, https://github.com/songc0a/DeepGPR
9
+ Project-URL: Documentation, https://songc0a.github.io/DeepGPR/
10
+ Project-URL: Changelog, https://github.com/songc0a/DeepGPR/blob/main/CHANGELOG.md
11
+ Keywords: ground penetrating radar,GPR,FDTD,full waveform inversion,PyTorch,CUDA
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: MacOS :: MacOS X
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Scientific/Engineering :: Physics
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: license
22
+ Requires-Dist: numpy
23
+ Requires-Dist: matplotlib
24
+ Requires-Dist: torch
25
+ Provides-Extra: examples
26
+ Requires-Dist: scipy; extra == "examples"
27
+ Requires-Dist: scikit-image; extra == "examples"
28
+ Requires-Dist: jupyter; extra == "examples"
29
+ Provides-Extra: dev
30
+ Requires-Dist: ruff>=0.5; extra == "dev"
31
+ Requires-Dist: mypy>=1.8; extra == "dev"
32
+ Requires-Dist: pytest>=7; extra == "dev"
33
+ Requires-Dist: nbformat; extra == "dev"
34
+ Requires-Dist: nbclient; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # DeepGPR
38
+
39
+ **Official website / documentation:** [https://songc0a.github.io/DeepGPR/](https://songc0a.github.io/DeepGPR/)
40
+
41
+ DeepGPR provides a wave propagation module for PyTorch, designed for applications such as Ground Penetrating Radar (GPR) imaging and inversion. Its core concepts are derived from Deepwave. You can use it to perform both forward modeling and backpropagation—thereby enabling the simulation of wave propagation to generate synthetic data—as well as for Full Waveform Inversion (FWI). Furthermore, you can integrate this wave propagation functionality into a larger operational pipeline—incorporating various wavelets, loss functions, and other components—to achieve end-to-end forward and reverse propagation, powered by automatic differentiation and our high-performance operators.
42
+
43
+ ## Features
44
+
45
+ - 2D and 3D forward modeling of Maxwell's equations with the Finite-Difference Time-Domain (FDTD) method, for single and multiple excitations.
46
+ - Gradients of the receiver data with respect to relative permittivity, conductivity, the initial wavefield and the source amplitudes, computed by an exact discrete adjoint.
47
+ - Automatic extension of the physical model into a CPML with an independent thickness on every face. Supply only air and the target region; material gradients keep the input model shape.
48
+ - CUDA GPU backend and a C/OpenMP CPU backend (selected automatically with `device='cpu'`).
49
+ - Spatial FDTD order 2, 4 or 8 (`fdtd_order`), and gradient mode 2 (Ez only) or 3 (Ex, Ey, Ez).
50
+ - Large models: checkpointing, DDP, temporal sub-sampling, FP16/BF16 or GPU-native INT8 wavefield histories and asynchronous offload to host memory.
51
+
52
+ ## System requirements
53
+
54
+ - **OS**: Linux, Windows and macOS for CPU execution; Linux and Windows for CUDA execution
55
+ - **Python**: 3.8+
56
+ - **Libraries**: `torch`, `numpy`, `matplotlib` (the examples additionally use `scikit-image`)
57
+ - **Hardware**: an NVIDIA GPU with sufficient memory for CUDA execution; CPU execution works without a GPU
58
+
59
+ ## Installation
60
+
61
+ Install a PyTorch build for your platform first ([pytorch.org](https://pytorch.org/get-started/locally/)): a CUDA-enabled build for GPU use, or a CPU build. Then
62
+
63
+ ```bash
64
+ pip install DeepGPR # runtime
65
+ pip install "DeepGPR[examples]" # + scipy, scikit-image, jupyter for the notebooks
66
+ ```
67
+
68
+ For development from a clone:
69
+
70
+ ```bash
71
+ pip install -r requirements-dev.txt
72
+ pip install -e .
73
+ ```
74
+
75
+ The prebuilt native libraries ship in `src/DeepGPR/lib`. To rebuild them see [`docs/BUILDING.md`](docs/BUILDING.md).
76
+
77
+ ## Quick start: a small forward-modeling test
78
+
79
+ ```python
80
+ import torch
81
+ import matplotlib.pyplot as plt
82
+ import DeepGPR
83
+
84
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
85
+ dx = 0.02 # or [dx, dy, dz], for example [0.02, 0.015, 0.01]
86
+ dt = 3e-11
87
+ nt = 2000
88
+
89
+ eps_r = torch.ones(100, 100, 1) * 2
90
+ eps_r[50:, :] = 5
91
+ sigma = torch.zeros_like(eps_r)
92
+ eps_r.requires_grad_()
93
+
94
+ source_location = torch.tensor([[[10, 10, 0]]], device=device, dtype=torch.int)
95
+ receiver_location = torch.tensor([[[10, 90, 0]]], device=device, dtype=torch.int)
96
+ freq = 2e8
97
+ source_amplitudes = torch.zeros((1, nt, 1), device=device)
98
+ source_amplitudes[0, :, 0] = DeepGPR.wavelet.ricker(freq, nt, dt, 1 / freq, device=device)
99
+
100
+ E_saved, E, H, pml_state, receivers = DeepGPR.compute(
101
+ device=device, dx=dx, dt=dt,
102
+ source_amplitudes=source_amplitudes,
103
+ source_location=source_location,
104
+ receiver_location=receiver_location,
105
+ eps_r=eps_r, sigma=sigma,
106
+ fdtd_order=2,
107
+ )
108
+
109
+ receivers.square().sum().backward()
110
+
111
+ fig, ax = plt.subplots(1, 2, figsize=(10, 3))
112
+ DeepGPR.visualization.plot_trace(receivers[0, :, 0], dt=dt, ax=ax[0], title="Receiver data")
113
+ DeepGPR.visualization.plot_model(eps_r.grad, ax=ax[1], title="Gradient", cmap="seismic")
114
+ plt.show()
115
+ ```
116
+
117
+ ![result](Fig/example.png)
118
+
119
+ More examples are in [`examples/`](examples) (forward modeling, 2D FWI, 3D FWI).
120
+
121
+ ## Source wavelets
122
+
123
+ Wavelets live in `DeepGPR.wavelet`. Every function returns a one-dimensional
124
+ tensor and accepts optional `dtype` and `device` arguments.
125
+
126
+ ```python
127
+ ricker = DeepGPR.wavelet.ricker(freq, nt, dt, peak_time, device=device)
128
+ gaussian = DeepGPR.wavelet.gaussian(freq, nt, dt, peak_time, device=device)
129
+ derivative = DeepGPR.wavelet.gaussian_derivative(freq, nt, dt, peak_time, device=device)
130
+ morlet = DeepGPR.wavelet.morlet(freq, nt, dt, peak_time, cycles=3.0, device=device)
131
+ burst = DeepGPR.wavelet.sine_burst(freq, nt, dt, peak_time, cycles=3.0, device=device)
132
+ ```
133
+
134
+ `DeepGPR.ricker(...)` remains available as a backward-compatible alias.
135
+
136
+ ## Full-waveform inversion results
137
+
138
+ The figures show the true model, the initial model and the inverted result.
139
+
140
+ ### 2D FWI
141
+
142
+ | True model | Initial model | Inverted result |
143
+ | --- | --- | --- |
144
+ | ![2D true model](Fig/2dfwitrue.png) | ![2D initial model](Fig/2dfwiinit.png) | ![2D inverted result](Fig/2dfwipred.png) |
145
+
146
+ ### 3D FWI (central slice)
147
+
148
+ | Model | Central slice |
149
+ | --- | --- |
150
+ | True model | ![3D true model central slice](Fig/3dfwitrue.png) |
151
+ | Initial model | ![3D initial model central slice](Fig/3dfwiinit.png) |
152
+ | Inverted result | ![3D inverted result central slice](Fig/3dfwipred.png) |
153
+
154
+ ## API overview
155
+
156
+ | Function | Purpose |
157
+ | --- | --- |
158
+ | `DeepGPR.compute(...)` | FDTD forward modeling with autograd (full reference: [`docs/API.md`](docs/API.md)) |
159
+ | `DeepGPR.checkpoint_initial_field(...)` | zero E/H/CPML states for segment-wise (checkpointed) runs |
160
+ | `DeepGPR.wavelet.*` | source wavelets |
161
+ | `DeepGPR.TVRegularization` | total-variation penalty for εr and σ (`"anisotropic"`, `"isotropic"`) |
162
+ | `DeepGPR.apply_filter`, `DeepGPR.hilbert_transform` | low-pass FIR and envelope for multi-scale FWI |
163
+ | `DeepGPR.max_stable_time_step(...)` | largest CFL-stable `dt` for a grid, order and material |
164
+ | `DeepGPR.estimate_compute_memory(...)` | memory estimate (also printed by `print_parameters=True`) |
165
+ | `DeepGPR.decompress_wavefield_history(...)` | decode a packed INT8 history for diagnostics |
166
+ | `DeepGPR.visualization.plot_*` | models, radargrams, traces, true/initial/inverted comparison |
167
+ | `DeepGPR.configure_logging(level)` | enable DeepGPR log output |
168
+
169
+ ### Logging and errors
170
+
171
+ DeepGPR logs through the standard `logging` module under the `DeepGPR`
172
+ logger and never configures the root logger. Enable console output with
173
+
174
+ ```python
175
+ DeepGPR.configure_logging("INFO") # or "DEBUG" for per-call solver details
176
+ ```
177
+
178
+ Errors raised by the package derive from `DeepGPR.DeepGPRError` **and** from the
179
+ built-in type used before 0.1.0, e.g. `DeepGPR.CFLConditionError` is also a
180
+ `ValueError` and `DeepGPR.NativeLibraryError` is also a `RuntimeError`.
181
+ Failures inside the native CPU/CUDA code (allocation, CUDA API or launch
182
+ errors) are raised as `NativeLibraryError` with the native message.
183
+
184
+ ## Project structure
185
+
186
+ ```
187
+ src/DeepGPR/
188
+ ├── __init__.py public API
189
+ ├── _version.py package version
190
+ ├── config/ physical constants, native ABI codes, API defaults
191
+ ├── native/ library loader, declarative C ABI table, ctypes bridge
192
+ ├── preprocessing/ input validation, grid/PML normalisation, CFL, wavelets
193
+ ├── solver/ CPML, field state, history storage, autograd bridge, compute()
194
+ ├── postprocessing/ FIR filter, envelope, forward-wavefield files
195
+ ├── inversion/ TV regularisation
196
+ ├── visualization/ matplotlib helpers
197
+ ├── utils/ logging, exceptions, validators, diagnostics
198
+ ├── lib/ deepgpr.cu, deepgpr_cpu.c, deepgpr.h and prebuilt binaries
199
+ ├── common.py, compute2.py, multiscale.py, wavelet.py compatibility modules
200
+ docs/ API reference, build guide, code audit
201
+ examples/ forward, 2D FWI and 3D FWI notebooks
202
+ tests/ unit tests, verification notebooks, benchmarks
203
+ tools/ verify_equivalence.py
204
+ ```
205
+
206
+ Data flow of one `compute` call:
207
+ `preprocessing.model_setup.initialization` (validate, CFL, extend into CPML) →
208
+ `solver.pml` (CPML coefficients and state) → `solver.autograd.DeepGPR`
209
+ (native forward; native adjoint on `backward`) → receiver data, final states
210
+ and the saved history.
211
+
212
+ ## Tests and verification
213
+
214
+ ```bash
215
+ python -m unittest discover -s tests -p "test_*.py" # unit tests
216
+ python -m unittest tests/test_native_abi.py # ABI check, no PyTorch needed
217
+ python tests/run_notebook.py tests/00_local_backend_and_contracts.ipynb
218
+ python tools/verify_equivalence.py --reference-src /path/to/DeepGPR-0.0.20/src/DeepGPR
219
+ bash tools/run_local_validation.sh /path/to/DeepGPR-0.0.20 # rebuild CPU+CUDA libs, run everything
220
+ ```
221
+
222
+ Run the notebooks `00` to `09` in numeric order, followed by
223
+ `99_verification_summary.ipynb`. See [`tests/README.md`](tests/README.md) for
224
+ the complete matrix and CUDA options.
225
+
226
+ **Reproducibility.** The CPU backend gives bitwise identical forward results
227
+ and gradients for any `OMP_NUM_THREADS`. The CUDA adjoint accumulates with
228
+ atomics and can differ in the last bits between runs.
229
+
230
+ **Reproducing results from DeepGPR ≤ 0.0.20.** 0.1.0 corrects the FIR design
231
+ for even filter lengths, the envelope for odd trace lengths and the isotropic
232
+ TV (see [`CHANGELOG.md`](CHANGELOG.md)). Use `apply_filter(..., legacy=True)`,
233
+ `design_fir_filter(..., legacy=True)`, `hilbert_transform(..., legacy=True)`
234
+ and `TVRegularization(method="legacy_isotropic")` to reproduce earlier numbers.
235
+
236
+ ## Development
237
+
238
+ - Code style: PEP 8, enforced with `ruff check src/DeepGPR tools` (configuration in `pyproject.toml`).
239
+ - Type hints throughout; `mypy` configuration in `pyproject.toml`.
240
+ - Changes are recorded in [`CHANGELOG.md`](CHANGELOG.md); known issues and recommended native work in [`docs/AUDIT.md`](docs/AUDIT.md).
241
+ - Changes to `lib/deepgpr.h` must be mirrored in `src/DeepGPR/native/abi.py`; `tests/test_native_abi.py` enforces it.
242
+
243
+ ## Citation
244
+
245
+ If you find our code useful, please cite:
246
+
247
+ ```bibtex
248
+ @article{liu2026fast,
249
+ title = {Fast ground penetrating radar dual-parameter full waveform inversion method accelerated by hybrid compilation of CUDA kernel function and PyTorch},
250
+ author = {Liu, Lei and Song, Chao and He, Liangsheng and Wang, Silin and Feng, Xuan and Liu, Cai},
251
+ journal = {Computers \& Geosciences},
252
+ pages = {106101},
253
+ year = {2026},
254
+ publisher = {Elsevier}
255
+ }
256
+ ```
257
+
258
+ ## License
259
+
260
+ MIT — see [`license`](license).