Catalog PyPI
PACKAGE EXPLORERView in registry

adtlert

PyPI

CUDA-accelerated differentiable 2.5D and 3D electrical resistivity tomography on PyTorch

computingelectricalgeophysicsinverseproblemspytorchresistivityscientifictomography
Versions collected1
Direct dependencies15From release 0.2.1
Last observed20 Oct, 19:55 UTC

README

Version 0.2.1 documentation

AD-TLERT

Differentiable 2.5D ERT and tetrahedral 3D DC forward tooling on Torch.

The repository contains:

  • adtlert/: the core numerical implementation.
  • example/single_time/: ADTLERT/pyGIMLi forward examples and ADTLERT/pyGIMLi/ResIPy single-time inversion examples.
  • example/window_363/: ADTLERT/pyGIMLi/ResIPy inversion examples for 365 time steps using 3-step sliding windows (363 windows).
  • parflow_models/ and resistivity_models_2d/: input data used by the examples.

See example/README.md for commands and dependency requirements.

Install

ADTLERT is a CUDA library: every sparse solve runs on NVIDIA cuDSS and the adjoint contractions run on the GPU. It requires Linux, an NVIDIA GPU, and a driver that supports CUDA 12. cuDSS and nvmath-python are installed as regular dependencies; the fused sensitivity kernels are written in Triton, which ships with PyTorch:

python -m pip install adtlert

For development from a checkout, use uv. The terrain examples read ParFlow PFB files and build Triangle inversion meshes:

uv sync --extra examples

Tests

uv run pytest            # CPU-only tests plus, on a CUDA machine, the GPU tests
uv run pytest -m "not gpu"

The suite checks mesh/FEM building blocks, adjoint consistency (dot tests), the exact Jacobian against finite differences and the resistivity-scaling identity, the Triton sensitivity kernel against an einsum reference (bitwise identical across autotune configurations), deterministic reductions, the inversion loops, and the implicit-neural-representation training.

Core Forward API

import torch

from adtlert.forward import ERTForward2p5D

forward = ERTForward2p5D.from_mesh_survey(mesh, survey)
conductivity = torch.as_tensor(1.0 / resistivity, dtype=torch.float32)
response = forward.solve(conductivity)

rhoa = response.apparent_resistivity
jacobian = forward.jacobian(conductivity)

For matrix-free differentiation, use the PyTorch autograd bridge. Its reverse pass calls forward.vjp(...), while PyTorch forward AD calls forward.jvp(...); no explicit Jacobian is materialized:

from adtlert.forward import apparent_resistivity_autograd

conductivity = conductivity.requires_grad_()
rhoa = apparent_resistivity_autograd(conductivity, forward, currents=1.0)
loss = data_misfit(rhoa, observed_rhoa)
loss.backward()
conductivity_gradient = conductivity.grad

True 3D DC forward modelling uses a tetrahedral mesh and three-coordinate electrodes. It follows pyGIMLi's 3D singularity-removal formulation, with an analytic half-space primary field and a mixed far-field boundary:

from adtlert.forward import ERTForward3D
from adtlert.mesh import Mesh3D
from adtlert.survey import Survey

mesh3d = Mesh3D.from_file("mesh3d.vtu")
survey3d = Survey.from_arrays(electrode_xyz, abmn)
forward3d = ERTForward3D.from_mesh_survey(mesh3d, survey3d)
response3d, jacobian3d = forward3d.solve_with_jacobian(conductivity3d)

For topography, use quadratic tetrahedra and numerical geometric factors. The surface is inferred from upward-facing exterior triangles; pass surface_face_mask= to Mesh3D.from_arrays when boundary classification must be explicit:

forward3d = ERTForward3D.from_mesh_survey(
    mesh3d,
    survey3d,
    element_order=2,
    geometric_factor_mode="auto",  # numerical for terrain, analytic when flat
)

For large explicit sensitivities, jacobian(..., batch_size=32, cell_batch_size=20000) bounds temporary field memory. Assembly routing, active AB/MN electrode sets, numerical geometric factors, sparse plans, and fixed GPU right-hand sides are cached across inversion iterations.

The initial 3D implementation supports flat-surface half-space models, four-node tetrahedral geometry, P1 or ten-node P2 basis functions, NVIDIA cuDSS sparse factorization, analytic or terrain-aware numerical geometric factors, exact adjoint cell sensitivities, first-order face-neighbor regularization, and the existing single-time inversion API. Large-scale matrix-free time-lapse inversion remains on the 3D roadmap.

ERTForwardModeling provides a small wrapper for mesh/data-like objects:

from adtlert.forward import ERTForwardModeling

modeling = ERTForwardModeling(mesh=mesh, data=scheme)
log_rhoa, jac = modeling.forward_and_jacobian(log_resistivity, log_transform=True)

Terrain Workflow

import numpy as np

from adtlert.workflows import (
    build_terrain_forward_case,
    discover_resistivity_slices,
    parse_pftcl,
    read_slope_x,
    run_terrain_forward,
    run_terrain_forward_series,
    save_terrain_forward_dat,
    save_terrain_forward_npz,
)

grid = parse_pftcl("parflow_models/sc2d_6.out.pftcl")
slope_x = read_slope_x("parflow_models/sc2d_6.out.slope_x.pfb", y_index=2)
rho_2d = np.load("resistivity_models_2d/resistivity2d_y2_t04536.npy")

case = build_terrain_forward_case(rho_2d, grid, slope_x, y_index=2)
rhoa = run_terrain_forward(case)
save_terrain_forward_dat("synthetic_ert_terrain_vardz_t04536.dat", case, rhoa)
save_terrain_forward_npz("synthetic_ert_terrain_vardz_t04536.npz", case, rhoa)

pairs = discover_resistivity_slices("resistivity_models_2d", y_index=2)
manifest, failures = run_terrain_forward_series(
    pairs,
    grid,
    slope_x,
    "result/timelapsedERT_forward",
    y_index=2,
)

Inversion API

import numpy as np

from adtlert.inversion import ERTInversion, InversionConfig

config = InversionConfig(
    max_iterations=8,
    data_std=0.05,
    regularization=1.0e-2,
    spatial_regularization="first_order",
    model_bounds=(10.0, 20000.0),
)

initial_model = np.full(forward.mesh.cell_count, np.median(observed_rhoa))
result = (
    ERTInversion(
        forward=forward,
        observed_data=observed_rhoa,
        config=config,
    )
    .setup()
    .run(initial_model)
)

final_model = result.final_model
predicted_rhoa = result.predicted_data
coverage = result.coverage
chi2_history = result.iteration_chi2

Windowed time-lapse inversion:

from adtlert.inversion import WindowedTimeLapseERTInversion

config = InversionConfig(
    max_iterations=6,
    data_std=np.log1p(relative_error_by_time),
    regularization=50.0,
    temporal_regularization=10.0,
    temporal_regularization_mode="separate",
    spatial_regularization="first_order",
    model_bounds=(0.001, 1.0e4),
    max_log_step=None,
    line_search=True,
)

result = (
    WindowedTimeLapseERTInversion(
        forward=forward,
        observed_data=observed_rhoa_by_time,
        config=config,
        window_size=3,
        window_step=1,
    )
    .setup()
    .run(initial_model)
)

final_models = result.final_models

Validation

uv run pytest
uv run python -m compileall adtlert
uv build

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.2.13 Oct 202615

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
vcholerae1/ad-tlert

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 19:55 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:dfad57bf6529ed2800876bb56ef4fc3edc8f7a5013d5676876c05fe6067f5080