adtlert 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 (37) hide show
  1. adtlert-0.1.0/.gitignore +26 -0
  2. adtlert-0.1.0/LICENSE +21 -0
  3. adtlert-0.1.0/PKG-INFO +262 -0
  4. adtlert-0.1.0/README.md +220 -0
  5. adtlert-0.1.0/adtlert/__init__.py +43 -0
  6. adtlert-0.1.0/adtlert/fem/__init__.py +72 -0
  7. adtlert-0.1.0/adtlert/fem/assembly.py +220 -0
  8. adtlert-0.1.0/adtlert/fem/boundary.py +176 -0
  9. adtlert-0.1.0/adtlert/fem/p1.py +134 -0
  10. adtlert-0.1.0/adtlert/fem/p2.py +120 -0
  11. adtlert-0.1.0/adtlert/fem/tet_p1.py +141 -0
  12. adtlert-0.1.0/adtlert/forward/__init__.py +22 -0
  13. adtlert-0.1.0/adtlert/forward/autograd.py +149 -0
  14. adtlert-0.1.0/adtlert/forward/ert2p5d.py +4694 -0
  15. adtlert-0.1.0/adtlert/forward/ert3d.py +603 -0
  16. adtlert-0.1.0/adtlert/forward/integration.py +56 -0
  17. adtlert-0.1.0/adtlert/forward/modeling.py +469 -0
  18. adtlert-0.1.0/adtlert/inversion/__init__.py +101 -0
  19. adtlert-0.1.0/adtlert/inversion/core.py +3142 -0
  20. adtlert-0.1.0/adtlert/inversion/misfit.py +290 -0
  21. adtlert-0.1.0/adtlert/inversion/optimizers.py +106 -0
  22. adtlert-0.1.0/adtlert/inversion/petrophysics.py +484 -0
  23. adtlert-0.1.0/adtlert/inversion/regularization.py +715 -0
  24. adtlert-0.1.0/adtlert/mesh/__init__.py +27 -0
  25. adtlert-0.1.0/adtlert/mesh/core.py +1000 -0
  26. adtlert-0.1.0/adtlert/mesh/core3d.py +264 -0
  27. adtlert-0.1.0/adtlert/survey/__init__.py +5 -0
  28. adtlert-0.1.0/adtlert/survey/core.py +87 -0
  29. adtlert-0.1.0/adtlert/utils/__init__.py +8 -0
  30. adtlert-0.1.0/adtlert/utils/dtypes.py +22 -0
  31. adtlert-0.1.0/adtlert/utils/progress.py +227 -0
  32. adtlert-0.1.0/adtlert/utils/torch_runtime.py +337 -0
  33. adtlert-0.1.0/adtlert/workflows/__init__.py +47 -0
  34. adtlert-0.1.0/adtlert/workflows/terrain.py +1173 -0
  35. adtlert-0.1.0/pyproject.toml +81 -0
  36. adtlert-0.1.0/tests/test_autograd_2p5d.py +249 -0
  37. adtlert-0.1.0/tests/test_ert3d.py +212 -0
@@ -0,0 +1,26 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ .DS_Store
9
+ ._*
10
+ *:Zone.Identifier
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ .venv-resipy/
15
+ .cache/
16
+ .tmp_wine_bin/
17
+
18
+ 2d_resistivity_model/
19
+ models_1year_1day/
20
+ .codex/
21
+ .agents/
22
+ result/
23
+ ProcessedData/
24
+
25
+ # Local research workspace; publish curated scripts from example/ instead.
26
+ paper/
adtlert-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pu Yang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
adtlert-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: adtlert
3
+ Version: 0.1.0
4
+ Summary: Differentiable 2.5D and 3D electrical resistivity tomography on PyTorch
5
+ Project-URL: Homepage, https://github.com/Vcholerae1/AD-TLERT
6
+ Project-URL: Documentation, https://github.com/Vcholerae1/AD-TLERT#readme
7
+ Project-URL: Repository, https://github.com/Vcholerae1/AD-TLERT
8
+ Project-URL: Issues, https://github.com/Vcholerae1/AD-TLERT/issues
9
+ Author-email: Pu Yang <165551159+Vcholerae1@users.noreply.github.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: electrical resistivity tomography,geophysics,inverse problems,pytorch,scientific computing
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Physics
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: meshio>=5.3.5
22
+ Requires-Dist: numpy<3,>=1.26
23
+ Requires-Dist: scipy<2,>=1.16
24
+ Requires-Dist: torch<3,>=2.10
25
+ Provides-Extra: cuda12
26
+ Requires-Dist: cupy-cuda12x<15,>=14; (sys_platform == 'linux') and extra == 'cuda12'
27
+ Requires-Dist: nvidia-cudss-cu12<1,>=0.7.1.6; (sys_platform == 'linux') and extra == 'cuda12'
28
+ Requires-Dist: nvmath-python[cu12]<2,>=0.8; (sys_platform == 'linux') and extra == 'cuda12'
29
+ Provides-Extra: examples
30
+ Requires-Dist: ipykernel>=7.2; extra == 'examples'
31
+ Requires-Dist: joblib>=1.5; extra == 'examples'
32
+ Requires-Dist: matplotlib>=3.10; extra == 'examples'
33
+ Requires-Dist: pandas>=2.2; extra == 'examples'
34
+ Requires-Dist: pftools>=1.3.14; extra == 'examples'
35
+ Requires-Dist: pygimli<2,>=1.6; extra == 'examples'
36
+ Requires-Dist: pyhydrogeophysx>=0.1; extra == 'examples'
37
+ Requires-Dist: triangle>=20250106; extra == 'examples'
38
+ Provides-Extra: terrain
39
+ Requires-Dist: pftools>=1.3.14; extra == 'terrain'
40
+ Requires-Dist: triangle>=20250106; extra == 'terrain'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # AD-TLERT
44
+
45
+ Differentiable 2.5D ERT and tetrahedral 3D DC forward tooling on Torch.
46
+
47
+ The repository contains:
48
+
49
+ - `adtlert/`: the core numerical implementation.
50
+ - `example/single_time/`: ADTLERT/pyGIMLi forward examples and
51
+ ADTLERT/pyGIMLi/ResIPy single-time inversion examples.
52
+ - `example/window_363/`: ADTLERT/pyGIMLi/ResIPy inversion examples for 365
53
+ time steps using 3-step sliding windows (363 windows).
54
+ - `parflow_models/` and `resistivity_models_2d/`: input data used by the
55
+ examples.
56
+
57
+ See [`example/README.md`](example/README.md) for commands and backend
58
+ requirements.
59
+
60
+ ## Install
61
+
62
+ Install the CPU/SciPy build from PyPI:
63
+
64
+ ```bash
65
+ python -m pip install adtlert
66
+ ```
67
+
68
+ CUDA 12 acceleration through NVIDIA cuDSS is an optional Linux extra:
69
+
70
+ ```bash
71
+ python -m pip install "adtlert[cuda12]"
72
+ ```
73
+
74
+ The CUDA driver must support CUDA 12, and only one CuPy CUDA variant may be
75
+ installed in an environment. CPU-only installations do not install CuPy,
76
+ cuDSS, or nvmath-python.
77
+
78
+ For development from a checkout, use `uv`. The terrain examples read ParFlow
79
+ PFB files and build Triangle inversion meshes:
80
+
81
+ ```bash
82
+ uv sync --extra cuda12 --extra examples
83
+ ```
84
+
85
+ ## Core Forward API
86
+
87
+ ```python
88
+ import torch
89
+
90
+ from adtlert.forward import ERTForward2p5D
91
+
92
+ forward = ERTForward2p5D.from_mesh_survey(mesh, survey)
93
+ conductivity = torch.as_tensor(1.0 / resistivity, dtype=torch.float32)
94
+ response = forward.solve(conductivity)
95
+
96
+ rhoa = response.apparent_resistivity
97
+ jacobian = forward.jacobian(conductivity)
98
+ ```
99
+
100
+ For matrix-free differentiation, use the PyTorch autograd bridge. Its reverse
101
+ pass calls `forward.vjp(...)`, while PyTorch forward AD calls
102
+ `forward.jvp(...)`; no explicit Jacobian is materialized:
103
+
104
+ ```python
105
+ from adtlert.forward import apparent_resistivity_autograd
106
+
107
+ conductivity = conductivity.requires_grad_()
108
+ rhoa = apparent_resistivity_autograd(conductivity, forward, currents=1.0)
109
+ loss = data_misfit(rhoa, observed_rhoa)
110
+ loss.backward()
111
+ conductivity_gradient = conductivity.grad
112
+ ```
113
+
114
+ True 3D DC forward modelling uses a tetrahedral mesh and three-coordinate
115
+ electrodes. It follows pyGIMLi's 3D singularity-removal formulation, with an
116
+ analytic half-space primary field and a mixed far-field boundary:
117
+
118
+ ```python
119
+ from adtlert.forward import ERTForward3D
120
+ from adtlert.mesh import Mesh3D
121
+ from adtlert.survey import Survey
122
+
123
+ mesh3d = Mesh3D.from_file("mesh3d.vtu")
124
+ survey3d = Survey.from_arrays(electrode_xyz, abmn)
125
+ forward3d = ERTForward3D.from_mesh_survey(mesh3d, survey3d)
126
+ response3d, jacobian3d = forward3d.solve_with_jacobian(conductivity3d)
127
+ ```
128
+
129
+ For topography, use quadratic tetrahedra and numerical geometric factors. The
130
+ surface is inferred from upward-facing exterior triangles; pass
131
+ `surface_face_mask=` to `Mesh3D.from_arrays` when boundary classification must
132
+ be explicit:
133
+
134
+ ```python
135
+ forward3d = ERTForward3D.from_mesh_survey(
136
+ mesh3d,
137
+ survey3d,
138
+ element_order=2,
139
+ geometric_factor_mode="auto", # numerical for terrain, analytic when flat
140
+ linear_solver_backend="cudss", # or "scipy"; "auto" selects an available GPU
141
+ )
142
+ ```
143
+
144
+ For large explicit sensitivities, `jacobian(..., batch_size=32,
145
+ cell_batch_size=20000)` bounds temporary field memory. Assembly routing,
146
+ active AB/MN electrode sets, numerical geometric factors, sparse plans, and
147
+ fixed GPU right-hand sides are cached across inversion iterations.
148
+
149
+ The initial 3D implementation supports flat-surface half-space models,
150
+ four-node tetrahedral geometry, P1 or ten-node P2 basis functions, SciPy or
151
+ NVIDIA cuDSS sparse factorization, analytic or terrain-aware numerical
152
+ geometric factors, exact adjoint cell sensitivities, first-order face-neighbor
153
+ regularization, and the existing single-time inversion API. Large-scale
154
+ matrix-free time-lapse inversion remains on the 3D roadmap.
155
+
156
+ `ERTForwardModeling` provides a small wrapper for mesh/data-like objects:
157
+
158
+ ```python
159
+ from adtlert.forward import ERTForwardModeling
160
+
161
+ modeling = ERTForwardModeling(mesh=mesh, data=scheme)
162
+ log_rhoa, jac = modeling.forward_and_jacobian(log_resistivity, log_transform=True)
163
+ ```
164
+
165
+ ## Terrain Workflow
166
+
167
+ ```python
168
+ import numpy as np
169
+
170
+ from adtlert.workflows import (
171
+ build_terrain_forward_case,
172
+ discover_resistivity_slices,
173
+ parse_pftcl,
174
+ read_slope_x,
175
+ run_terrain_forward,
176
+ run_terrain_forward_series,
177
+ save_terrain_forward_dat,
178
+ save_terrain_forward_npz,
179
+ )
180
+
181
+ grid = parse_pftcl("parflow_models/sc2d_6.out.pftcl")
182
+ slope_x = read_slope_x("parflow_models/sc2d_6.out.slope_x.pfb", y_index=2)
183
+ rho_2d = np.load("resistivity_models_2d/resistivity2d_y2_t04536.npy")
184
+
185
+ case = build_terrain_forward_case(rho_2d, grid, slope_x, y_index=2)
186
+ rhoa = run_terrain_forward(case)
187
+ save_terrain_forward_dat("synthetic_ert_terrain_vardz_t04536.dat", case, rhoa)
188
+ save_terrain_forward_npz("synthetic_ert_terrain_vardz_t04536.npz", case, rhoa)
189
+
190
+ pairs = discover_resistivity_slices("resistivity_models_2d", y_index=2)
191
+ manifest, failures = run_terrain_forward_series(
192
+ pairs,
193
+ grid,
194
+ slope_x,
195
+ "result/timelapsedERT_forward",
196
+ y_index=2,
197
+ )
198
+ ```
199
+
200
+ ## Inversion API
201
+
202
+ ```python
203
+ import numpy as np
204
+
205
+ from adtlert.inversion import ERTInversion, InversionConfig
206
+
207
+ config = InversionConfig(
208
+ max_iterations=8,
209
+ data_std=0.05,
210
+ regularization=1.0e-2,
211
+ spatial_regularization="first_order",
212
+ model_bounds=(10.0, 20000.0),
213
+ )
214
+
215
+ initial_model = np.full(forward.mesh.cell_count, np.median(observed_rhoa))
216
+ result = ERTInversion(
217
+ forward=forward,
218
+ observed_data=observed_rhoa,
219
+ config=config,
220
+ ).setup().run(initial_model)
221
+
222
+ final_model = result.final_model
223
+ predicted_rhoa = result.predicted_data
224
+ coverage = result.coverage
225
+ chi2_history = result.iteration_chi2
226
+ ```
227
+
228
+ Windowed time-lapse inversion:
229
+
230
+ ```python
231
+ from adtlert.inversion import WindowedTimeLapseERTInversion
232
+
233
+ config = InversionConfig(
234
+ max_iterations=6,
235
+ data_std=np.log1p(relative_error_by_time),
236
+ regularization=50.0,
237
+ temporal_regularization=10.0,
238
+ temporal_regularization_mode="separate",
239
+ spatial_regularization="first_order",
240
+ model_bounds=(0.001, 1.0e4),
241
+ max_log_step=None,
242
+ line_search=True,
243
+ )
244
+
245
+ result = WindowedTimeLapseERTInversion(
246
+ forward=forward,
247
+ observed_data=observed_rhoa_by_time,
248
+ config=config,
249
+ window_size=3,
250
+ window_step=1,
251
+ ).setup().run(initial_model)
252
+
253
+ final_models = result.final_models
254
+ ```
255
+
256
+ ## Validation
257
+
258
+ ```bash
259
+ uv run pytest
260
+ uv run python -m compileall adtlert
261
+ uv build
262
+ ```
@@ -0,0 +1,220 @@
1
+ # AD-TLERT
2
+
3
+ Differentiable 2.5D ERT and tetrahedral 3D DC forward tooling on Torch.
4
+
5
+ The repository contains:
6
+
7
+ - `adtlert/`: the core numerical implementation.
8
+ - `example/single_time/`: ADTLERT/pyGIMLi forward examples and
9
+ ADTLERT/pyGIMLi/ResIPy single-time inversion examples.
10
+ - `example/window_363/`: ADTLERT/pyGIMLi/ResIPy inversion examples for 365
11
+ time steps using 3-step sliding windows (363 windows).
12
+ - `parflow_models/` and `resistivity_models_2d/`: input data used by the
13
+ examples.
14
+
15
+ See [`example/README.md`](example/README.md) for commands and backend
16
+ requirements.
17
+
18
+ ## Install
19
+
20
+ Install the CPU/SciPy build from PyPI:
21
+
22
+ ```bash
23
+ python -m pip install adtlert
24
+ ```
25
+
26
+ CUDA 12 acceleration through NVIDIA cuDSS is an optional Linux extra:
27
+
28
+ ```bash
29
+ python -m pip install "adtlert[cuda12]"
30
+ ```
31
+
32
+ The CUDA driver must support CUDA 12, and only one CuPy CUDA variant may be
33
+ installed in an environment. CPU-only installations do not install CuPy,
34
+ cuDSS, or nvmath-python.
35
+
36
+ For development from a checkout, use `uv`. The terrain examples read ParFlow
37
+ PFB files and build Triangle inversion meshes:
38
+
39
+ ```bash
40
+ uv sync --extra cuda12 --extra examples
41
+ ```
42
+
43
+ ## Core Forward API
44
+
45
+ ```python
46
+ import torch
47
+
48
+ from adtlert.forward import ERTForward2p5D
49
+
50
+ forward = ERTForward2p5D.from_mesh_survey(mesh, survey)
51
+ conductivity = torch.as_tensor(1.0 / resistivity, dtype=torch.float32)
52
+ response = forward.solve(conductivity)
53
+
54
+ rhoa = response.apparent_resistivity
55
+ jacobian = forward.jacobian(conductivity)
56
+ ```
57
+
58
+ For matrix-free differentiation, use the PyTorch autograd bridge. Its reverse
59
+ pass calls `forward.vjp(...)`, while PyTorch forward AD calls
60
+ `forward.jvp(...)`; no explicit Jacobian is materialized:
61
+
62
+ ```python
63
+ from adtlert.forward import apparent_resistivity_autograd
64
+
65
+ conductivity = conductivity.requires_grad_()
66
+ rhoa = apparent_resistivity_autograd(conductivity, forward, currents=1.0)
67
+ loss = data_misfit(rhoa, observed_rhoa)
68
+ loss.backward()
69
+ conductivity_gradient = conductivity.grad
70
+ ```
71
+
72
+ True 3D DC forward modelling uses a tetrahedral mesh and three-coordinate
73
+ electrodes. It follows pyGIMLi's 3D singularity-removal formulation, with an
74
+ analytic half-space primary field and a mixed far-field boundary:
75
+
76
+ ```python
77
+ from adtlert.forward import ERTForward3D
78
+ from adtlert.mesh import Mesh3D
79
+ from adtlert.survey import Survey
80
+
81
+ mesh3d = Mesh3D.from_file("mesh3d.vtu")
82
+ survey3d = Survey.from_arrays(electrode_xyz, abmn)
83
+ forward3d = ERTForward3D.from_mesh_survey(mesh3d, survey3d)
84
+ response3d, jacobian3d = forward3d.solve_with_jacobian(conductivity3d)
85
+ ```
86
+
87
+ For topography, use quadratic tetrahedra and numerical geometric factors. The
88
+ surface is inferred from upward-facing exterior triangles; pass
89
+ `surface_face_mask=` to `Mesh3D.from_arrays` when boundary classification must
90
+ be explicit:
91
+
92
+ ```python
93
+ forward3d = ERTForward3D.from_mesh_survey(
94
+ mesh3d,
95
+ survey3d,
96
+ element_order=2,
97
+ geometric_factor_mode="auto", # numerical for terrain, analytic when flat
98
+ linear_solver_backend="cudss", # or "scipy"; "auto" selects an available GPU
99
+ )
100
+ ```
101
+
102
+ For large explicit sensitivities, `jacobian(..., batch_size=32,
103
+ cell_batch_size=20000)` bounds temporary field memory. Assembly routing,
104
+ active AB/MN electrode sets, numerical geometric factors, sparse plans, and
105
+ fixed GPU right-hand sides are cached across inversion iterations.
106
+
107
+ The initial 3D implementation supports flat-surface half-space models,
108
+ four-node tetrahedral geometry, P1 or ten-node P2 basis functions, SciPy or
109
+ NVIDIA cuDSS sparse factorization, analytic or terrain-aware numerical
110
+ geometric factors, exact adjoint cell sensitivities, first-order face-neighbor
111
+ regularization, and the existing single-time inversion API. Large-scale
112
+ matrix-free time-lapse inversion remains on the 3D roadmap.
113
+
114
+ `ERTForwardModeling` provides a small wrapper for mesh/data-like objects:
115
+
116
+ ```python
117
+ from adtlert.forward import ERTForwardModeling
118
+
119
+ modeling = ERTForwardModeling(mesh=mesh, data=scheme)
120
+ log_rhoa, jac = modeling.forward_and_jacobian(log_resistivity, log_transform=True)
121
+ ```
122
+
123
+ ## Terrain Workflow
124
+
125
+ ```python
126
+ import numpy as np
127
+
128
+ from adtlert.workflows import (
129
+ build_terrain_forward_case,
130
+ discover_resistivity_slices,
131
+ parse_pftcl,
132
+ read_slope_x,
133
+ run_terrain_forward,
134
+ run_terrain_forward_series,
135
+ save_terrain_forward_dat,
136
+ save_terrain_forward_npz,
137
+ )
138
+
139
+ grid = parse_pftcl("parflow_models/sc2d_6.out.pftcl")
140
+ slope_x = read_slope_x("parflow_models/sc2d_6.out.slope_x.pfb", y_index=2)
141
+ rho_2d = np.load("resistivity_models_2d/resistivity2d_y2_t04536.npy")
142
+
143
+ case = build_terrain_forward_case(rho_2d, grid, slope_x, y_index=2)
144
+ rhoa = run_terrain_forward(case)
145
+ save_terrain_forward_dat("synthetic_ert_terrain_vardz_t04536.dat", case, rhoa)
146
+ save_terrain_forward_npz("synthetic_ert_terrain_vardz_t04536.npz", case, rhoa)
147
+
148
+ pairs = discover_resistivity_slices("resistivity_models_2d", y_index=2)
149
+ manifest, failures = run_terrain_forward_series(
150
+ pairs,
151
+ grid,
152
+ slope_x,
153
+ "result/timelapsedERT_forward",
154
+ y_index=2,
155
+ )
156
+ ```
157
+
158
+ ## Inversion API
159
+
160
+ ```python
161
+ import numpy as np
162
+
163
+ from adtlert.inversion import ERTInversion, InversionConfig
164
+
165
+ config = InversionConfig(
166
+ max_iterations=8,
167
+ data_std=0.05,
168
+ regularization=1.0e-2,
169
+ spatial_regularization="first_order",
170
+ model_bounds=(10.0, 20000.0),
171
+ )
172
+
173
+ initial_model = np.full(forward.mesh.cell_count, np.median(observed_rhoa))
174
+ result = ERTInversion(
175
+ forward=forward,
176
+ observed_data=observed_rhoa,
177
+ config=config,
178
+ ).setup().run(initial_model)
179
+
180
+ final_model = result.final_model
181
+ predicted_rhoa = result.predicted_data
182
+ coverage = result.coverage
183
+ chi2_history = result.iteration_chi2
184
+ ```
185
+
186
+ Windowed time-lapse inversion:
187
+
188
+ ```python
189
+ from adtlert.inversion import WindowedTimeLapseERTInversion
190
+
191
+ config = InversionConfig(
192
+ max_iterations=6,
193
+ data_std=np.log1p(relative_error_by_time),
194
+ regularization=50.0,
195
+ temporal_regularization=10.0,
196
+ temporal_regularization_mode="separate",
197
+ spatial_regularization="first_order",
198
+ model_bounds=(0.001, 1.0e4),
199
+ max_log_step=None,
200
+ line_search=True,
201
+ )
202
+
203
+ result = WindowedTimeLapseERTInversion(
204
+ forward=forward,
205
+ observed_data=observed_rhoa_by_time,
206
+ config=config,
207
+ window_size=3,
208
+ window_step=1,
209
+ ).setup().run(initial_model)
210
+
211
+ final_models = result.final_models
212
+ ```
213
+
214
+ ## Validation
215
+
216
+ ```bash
217
+ uv run pytest
218
+ uv run python -m compileall adtlert
219
+ uv build
220
+ ```
@@ -0,0 +1,43 @@
1
+ """Top-level package for differentiable time-lapse ERT on Torch."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from adtlert.fem import P1ElementData, build_p1_element_data, p1_shape_functions
6
+ from adtlert.forward import (
7
+ ERT2p5DApparentResistivityFunction,
8
+ ERTForward2p5D,
9
+ ERTForwardModeling,
10
+ ForwardResponse,
11
+ apparent_resistivity_autograd,
12
+ )
13
+ from adtlert.inversion import (
14
+ ERTInversion,
15
+ InversionConfig,
16
+ TimeLapseERTInversion,
17
+ WindowedTimeLapseERTInversion,
18
+ )
19
+ from adtlert.mesh import Mesh
20
+ from adtlert.survey import Survey
21
+
22
+ try:
23
+ __version__ = version("adtlert")
24
+ except PackageNotFoundError: # pragma: no cover - source tree without installation
25
+ __version__ = "0+unknown"
26
+
27
+ __all__ = [
28
+ "ERTForward2p5D",
29
+ "ERT2p5DApparentResistivityFunction",
30
+ "ERTForwardModeling",
31
+ "ERTInversion",
32
+ "ForwardResponse",
33
+ "InversionConfig",
34
+ "Mesh",
35
+ "P1ElementData",
36
+ "Survey",
37
+ "TimeLapseERTInversion",
38
+ "WindowedTimeLapseERTInversion",
39
+ "apparent_resistivity_autograd",
40
+ "build_p1_element_data",
41
+ "p1_shape_functions",
42
+ "__version__",
43
+ ]
@@ -0,0 +1,72 @@
1
+ """Finite-element basis and quadrature utilities."""
2
+
3
+ from adtlert.fem.assembly import (
4
+ COORouting,
5
+ assemble_global_bcoo,
6
+ assemble_helmholtz_operator,
7
+ assemble_local_mass,
8
+ assemble_local_mass_p2,
9
+ assemble_local_stiffness,
10
+ assemble_local_stiffness_p2,
11
+ build_coo_routing,
12
+ build_coo_routing_from_connectivity,
13
+ )
14
+ from adtlert.fem.boundary import (
15
+ BoundaryRouting,
16
+ assemble_boundary_bcoo,
17
+ assemble_local_boundary_mass,
18
+ assemble_local_boundary_mass_p2,
19
+ build_boundary_routing,
20
+ build_boundary_routing_from_connectivity,
21
+ robin_boundary_coefficients,
22
+ )
23
+ from adtlert.fem.p1 import (
24
+ P1ElementData,
25
+ build_p1_element_data,
26
+ p1_shape_functions,
27
+ reference_shape_gradients,
28
+ triangle_quadrature,
29
+ )
30
+ from adtlert.fem.p2 import P2ElementData, build_p2_element_data, p2_shape_functions, reference_shape_gradients_p2
31
+ from adtlert.fem.tet_p1 import (
32
+ TetrahedronP1Data,
33
+ TetrahedronP2Data,
34
+ build_tetrahedron_p1_data,
35
+ build_tetrahedron_p2_data,
36
+ p2_tetrahedron_shape_values,
37
+ p2_triangle_mass_template,
38
+ )
39
+
40
+ __all__ = [
41
+ "BoundaryRouting",
42
+ "COORouting",
43
+ "assemble_boundary_bcoo",
44
+ "P1ElementData",
45
+ "assemble_global_bcoo",
46
+ "assemble_helmholtz_operator",
47
+ "assemble_local_boundary_mass",
48
+ "assemble_local_boundary_mass_p2",
49
+ "assemble_local_mass",
50
+ "assemble_local_mass_p2",
51
+ "assemble_local_stiffness",
52
+ "assemble_local_stiffness_p2",
53
+ "build_boundary_routing",
54
+ "build_boundary_routing_from_connectivity",
55
+ "build_p1_element_data",
56
+ "build_p2_element_data",
57
+ "build_coo_routing",
58
+ "build_coo_routing_from_connectivity",
59
+ "p1_shape_functions",
60
+ "p2_shape_functions",
61
+ "reference_shape_gradients",
62
+ "reference_shape_gradients_p2",
63
+ "robin_boundary_coefficients",
64
+ "triangle_quadrature",
65
+ "P2ElementData",
66
+ "TetrahedronP1Data",
67
+ "TetrahedronP2Data",
68
+ "build_tetrahedron_p1_data",
69
+ "build_tetrahedron_p2_data",
70
+ "p2_tetrahedron_shape_values",
71
+ "p2_triangle_mass_template",
72
+ ]