Skip to content

Getting Started

Terminal window
uv pip install tide-GPR

Or with pip:

Terminal window
pip install tide-GPR
Terminal window
git clone https://github.com/vcholerae1/tide.git
cd tide
uv build
DependencyVersion
Python≥ 3.12
PyTorch≥ 2.12
CUDA Toolkitoptional, for GPU support
CMake≥ 3.28, optional, for building from source
2d_forward.py
import torch
import tide
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dtype = torch.float32
ny, nx = 96, 96
epsilon = torch.full((ny, nx), 4.0, device=device, dtype=dtype)
sigma = torch.zeros_like(epsilon)
mu = torch.ones_like(epsilon)
nt = 300
dt = 4e-11
src = tide.ricker(freq=8e8, length=nt, dt=dt, device=device, dtype=dtype).view(1, 1, nt)
src_loc = torch.tensor([[[20, 48]]], device=device, dtype=torch.long)
rec_loc = torch.tensor([[[20, 60]]], device=device, dtype=torch.long)
model = tide.EMModel(epsilon, sigma, mu)
operator = tide.MaxwellTM(
tide.Discretization(0.02, dt, boundary=tide.CPML(10)),
tide.Experiment(tide.Acquisition(src_loc, rec_loc), src),
execution=tide.ExecutionOptions(fallback=tide.FallbackPolicy.REFERENCE),
)
receivers = operator(model).receiver_data
print(receivers.shape) # [nt, n_shots, n_receivers]
3d_forward.py
import torch
import tide
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dtype = torch.float32
nz, ny, nx = 32, 32, 32
epsilon = torch.full((nz, ny, nx), 4.0, device=device, dtype=dtype)
sigma = torch.zeros_like(epsilon)
mu = torch.ones_like(epsilon)
nt = 200
dt = 4e-11
src = tide.ricker(freq=1e8, length=nt, dt=dt, device=device, dtype=dtype).view(1, 1, nt)
src_loc = torch.tensor([[[16, 16, 16]]], device=device, dtype=torch.long)
rec_loc = torch.tensor([[[16, 16, 20]]], device=device, dtype=torch.long)
model = tide.EMModel(epsilon, sigma, mu)
operator = tide.Maxwell3D(
tide.Discretization(
[0.03, 0.03, 0.03],
dt,
boundary=tide.CPML(6),
),
tide.Experiment(tide.Acquisition(src_loc, rec_loc), src),
execution=tide.ExecutionOptions(
backend=tide.BackendPreference.NATIVE,
fallback=tide.FallbackPolicy.REFERENCE,
),
)
rec = operator(model).receiver_data
print(rec.shape)
from tide import backend_utils
print("backend available:", backend_utils.is_backend_available())
print("library path: ", backend_utils.get_library_path())
  • source_amplitude must be [n_shots, n_sources, nt].
  • source_location and receiver_location must be [n_shots, n_points, ndim].

Coordinates must satisfy 0 <= index < model_size for each spatial dimension.

TIDE adjusts the internal time step using CFL and resamples time signals. Consider reducing dt or coarsening grid spacing.

The minimal example has one shot, one source, and one receiver. Its result shape is therefore [300, 1, 1]. The first axis is physical time sampled every 4e-11 seconds. The other axes identify shot and receiver.

The returned tensor is differentiable. Confirm this before building an inversion:

epsilon = epsilon.clone().requires_grad_(True)
result = operator(tide.EMModel(epsilon, sigma, mu))
objective = result.receiver_data.square().mean()
objective.backward()
print("loss:", float(objective.detach()))
print("gradient shape:", epsilon.grad.shape)
print("finite gradient:", bool(torch.isfinite(epsilon.grad).all()))

This is a computational check, not a physical validation. A finite gradient can still be wrong because of units, geometry, insufficient resolution, or a misinterpreted component.

Change a compact region of relative permittivity and compare traces:

epsilon_perturbed = epsilon.detach().clone()
epsilon_perturbed[45:55, 50:60] = 7.0
baseline = operator(tide.EMModel(epsilon.detach(), sigma, mu)).receiver_data
perturbed = operator(
tide.EMModel(epsilon_perturbed, sigma, mu)
).receiver_data
difference = perturbed - baseline
print("maximum trace change:", float(difference.abs().max()))

This exercise establishes the core forward-modeling contract: the operator and experiment stay fixed, the material model changes, and receiver data records the effect.

The source and returned receiver tensors use the dt supplied to Discretization. TIDE may choose a smaller internal time step to satisfy the CFL condition:

from tide.cfl import cfl_condition
inner_dt, step_ratio = cfl_condition(
grid_spacing=0.02,
dt=dt,
max_vel=299_792_458.0,
)
print(inner_dt, step_ratio)

When step_ratio > 1, propagation performs several internal updates per user sample. TIDE upsamples the source and downsamples receiver data automatically. The output still contains nt samples, but runtime increases with the ratio.

Before replacing the homogeneous model with survey data:

  1. Estimate the direct-arrival time from source-receiver distance and expected material velocity.
  2. Confirm the trace becomes active near that time.
  3. Move the receiver farther away and confirm the arrival is later.
  4. Increase CPML width and confirm the direct arrival stays fixed while late boundary energy changes.
  5. Repeat with a finer grid or higher stencil order and compare phase.

These checks reveal coordinate, unit, boundary, and dispersion errors earlier than an inversion loop will.