Skip to content

Maxwell Operators

MaxwellTM and Maxwell3D represent fixed electromagnetic experiments. They own discretization, acquisition, source definition, execution policy, storage policy, and gradient sampling. The material model is supplied at evaluation time.

MaxwellTM(
discretization,
experiment,
execution=ExecutionOptions(),
storage=StorageOptions(),
model_gradient_sampling_interval=1,
)
Maxwell3D(
discretization,
experiment,
execution=ExecutionOptions(),
storage=StorageOptions(),
model_gradient_sampling_interval=1,
)

MaxwellTM expects 2D [ny, nx] materials or [batch, ny, nx] batched materials. Maxwell3D expects [nz, ny, nx] or [batch, nz, ny, nx].

result = operator.forward(model)
# Equivalent:
result = operator(model)

The nonlinear map is

F:md.F:m\mapsto d.

result.receiver_data is [nt, shots, receivers] for a shared model and [nt, batch, shots, receivers] for batched models. result.final_state is TMState or EM3DState with named field and CPML state tensors.

model = tide.EMModel(epsilon, sigma, mu)
result = operator(model)
print(result.receiver_data.shape)
print(type(result.final_state).__name__)

A forward call remains differentiable through standard PyTorch autograd when model tensors require gradients.

linearized = operator.linearize(
model,
storage=None,
targets=("epsilon", "sigma"),
)

The session fixes a background model and selected derivative targets. storage overrides the operator’s default for the session. Use the object as a context manager because it may retain forward snapshots, host allocations, or disk files.

with operator.linearize(model, targets=("epsilon",)) as linearized:
primal = linearized.primal

primal is evaluated lazily and cached for the lifetime of the open session.

tangent = linearized.jvp(direction)

direction is an EMDirection with one or more material perturbations. The result applies

J(m)vJ(m)v

and returns TangentResult.receiver_data plus a final tangent state. Missing direction fields mean zero perturbation. Each supplied tensor must match the corresponding background material shape, dtype, and device.

direction = tide.EMDirection(
epsilon=delta_epsilon,
sigma=delta_sigma,
)
with operator.linearize(model) as linearized:
born_receiver = linearized.jvp(direction).receiver_data

This operation is useful for Born modeling, linearized imaging, and the inner action of a Gauss-Newton Hessian product.

gradient = linearized.vjp(receiver_cotangent)

The receiver cotangent must have exactly the same shape as linearized.primal.receiver_data. The method applies

J(m)rJ(m)^\top r

and returns EMGradient. A field is None when it was not selected as a target or no derivative is available for it.

with operator.linearize(model, targets=("epsilon", "sigma")) as linearized:
residual = linearized.primal.receiver_data - observed
gradient = linearized.vjp(residual)
print(gradient.epsilon.shape)
print(gradient.sigma.shape)

For a least-squares loss defined as 0.5 * residual.square().sum(), this VJP is the data-term gradient.

nonlinear_term = linearized.second_vjp(direction, receiver_cotangent)

This applies

(DJ(m)[v])r,(DJ(m)[v])^\top r,

which is the nonlinear-physics contribution to a full Hessian-vector product. It is not the complete Hessian of an arbitrary receiver loss.

For Φ(F(m))\Phi(F(m)), the full product contains

JΦJv+(DJ[v])Φ.J^\top \Phi'' Jv + (DJ[v])^\top \Phi'.

tide.workflow.ReceiverObjective.hvp composes these terms for a receiver objective and supports mode="gauss_newton" or mode="full".

These two gradients are mathematically aligned for a scalar objective:

predicted = operator(model).receiver_data
loss = 0.5 * (predicted - observed).square().sum()
loss.backward()
with operator.linearize(model) as linearized:
residual = linearized.primal.receiver_data - observed
gradient = linearized.vjp(residual)

Use standard autograd for ordinary scalar-loss training loops. Use explicit derivative methods for linear operators, adjoint tests, controlled snapshot reuse, and Hessian-vector composition.

A linearized session caches its primal state. Reuse it for several JVP or VJP actions at the same background model when supported. Do not mutate the background tensors in place while the session is open. Close the session before changing model values.

After close(), access to primal or derivative methods raises. Context-manager exit calls close() automatically.

Operator construction validates dimensional agreement between acquisition and solver. Calls also validate material shapes, location bounds, dtype, and supported backend combinations. A native request can fail because of derivative target, storage mode, callbacks, dispersion, device, or missing ABI symbols.

Use FallbackPolicy.ERROR when backend identity matters. Use the capability matrix to inspect supported combinations and the limitations guide for current boundaries.