16.5. Maxwell Adapter Layer#

Solvers And Grids covers MT1DForward, MT2DForward, and MT3DForward: fast, in-process solvers over LayeredModel, Grid2D, and Grid3D with no formal validation harness of their own beyond the tests that ship with pycsamt. pycsamt.forward.maxwell sits beside that layer, not above it – it wraps some of those exact same solvers (and two production external ones) behind a solver-neutral contract that checks a problem’s compatibility before solving, validates a backend’s result after solving, and can prove the whole chain against a closed-form answer. The six pages under this heading cover that layer in depth: Maxwell Problem, Mesh, And Result Contracts, Maxwell Solver Meshes, Maxwell Backend Registry, Maxwell Adapters, Maxwell Analytic Benchmarks, and Maxwell Caching And Batch Solving. This page is the map between them, and the answer to the first real question: when does any of this matter more than calling MT2DForward directly?

16.5.1. Legacy And Validated Engines#

Layer

Main responsibility

Typical use

pycsamt.forward.em1d/em2d/em3d

Fast, in-process 1-D/2-D/quasi-3-D solvers over LayeredModel, Grid2D, and Grid3D. No capability declaration, benchmark record, or cache of their own.

Prototyping, survey design, and generating a synthetic dataset at scale through Synthetic Datasets And Noise’s ForwardDataset.

pycsamt.forward.maxwell

Solver-neutral problem/result contracts, capability-checked adapters over both in-repository research solvers and production external ones (ModEM, MARE2DEM), analytic benchmarks, content-addressed caching, and resumable batch solving.

A response whose accuracy is a checked claim rather than an assumption; AI-inversion training data; a production-grade forward check before committing to a full external inversion project.

Both layers can answer the same physical question for the same model – Maxwell AdaptersMT2DAdapter literally wraps MT2DForward. What pycsamt.forward.maxwell adds is not new physics; it is a validation boundary, a benchmark record, and machinery for running that boundary at scale. Reach for the legacy solvers directly when a quick, throwaway check is enough. Reach for this layer when the result needs to be defensible on its own – reused in a report, fed into training data, or compared across solver versions.

16.5.2. Package Map#

Concern

Modules

Page

Problem, mesh, and result contracts

contracts.py, contracts_tri.py

Maxwell Problem, Mesh, And Result Contracts

Structured and triangular mesh building

mesh.py, tri_mesh_gen.py

Maxwell Solver Meshes

Backend capability and registry

backends.py

Maxwell Backend Registry

Adapter validation boundary and five concrete adapters

adapters.py, external.py, mt2d.py, mt3d.py, tri_fem2d.py, modem3d.py, mare2dem.py

Maxwell Adapters

Analytic benchmarks

benchmarks.py

Maxwell Analytic Benchmarks

Result caching and batch solving

cache.py, batch.py

Maxwell Caching And Batch Solving

Every module above is reachable from pycsamt.forward.maxwell directly except the five concrete adapter modules and external.py, which are imported from their own submodules – importing the package itself never pulls in an optional solver or an external executable, only the contracts and registry machinery that describe them.

16.5.3. Core Workflow#

The clearest way to see that this layer adds a boundary rather than a second solver is to run the identical model both ways and compare the raw impedance. MT2DAdapter builds the exact same Grid2D internally that a direct MT2DForward call would use, so the two paths have nothing left to disagree about:

>>> import numpy as np
>>> from pycsamt.forward.grid2d import Grid2D
>>> from pycsamt.forward.em2d import MT2DForward
>>> from pycsamt.forward.maxwell import MaxwellMesh, MaxwellProblem, ReceiverSet
>>> from pycsamt.forward.maxwell.mt2d import MT2DAdapter

>>> x_edges = np.linspace(0, 10_000, 41)
>>> z_edges = np.linspace(0, 5_000, 31)
>>> resistivity = np.full((30, 40), 100.0)
>>> resistivity[8:14, 15:26] = 8.0
>>> frequencies = np.geomspace(300, 1, 8)
>>> station_x = np.linspace(2_000, 8_000, 5)

>>> grid = Grid2D(
...     dx=np.diff(x_edges), dz=np.diff(z_edges), resistivity=resistivity,
...     x_stations=station_x, n_pad=0, name="overview-demo",
... )
>>> legacy = MT2DForward(frequencies, grid, verbose=False).run()

>>> mesh = MaxwellMesh(x_edges, z_edges)
>>> receivers = ReceiverSet(
...     [[x, 0.0] for x in station_x], [f"S{i:02d}" for i in range(5)],
... )
>>> problem = MaxwellProblem(
...     mesh, 1.0 / resistivity, frequencies, receivers, ("zxy", "zyx"),
... )
>>> result = MT2DAdapter(verbose=False).solve(problem)
>>> zxy_maxwell = result.impedance_v_a[:, :, 0].T
>>> zyx_maxwell = result.impedance_v_a[:, :, 1].T
>>> float(np.max(np.abs(legacy.zxy - zxy_maxwell)))
0.0
>>> float(np.max(np.abs(legacy.zyx - zyx_maxwell)))
0.0

Zero, not merely small – the two arrays are computed by the same finite- difference code underneath. What result carries that legacy does not is a problem hash tying it to this exact input, a solver diagnostics record, and a backend identity that passed through preflight assessment and postflight validation before ever reaching this comparison. The executed apparent-resistivity and phase figure for this same shallow-conductor setup already lives in Maxwell Adapters; nothing about rerunning it here would show anything new, which is itself evidence for the point being made – the physics did not change, only what is checked around it.

16.5.4. Choosing A Path#

Goal

Start here

Then read

Fast, approximate response for prototyping or survey design

Solvers And Grids

Synthetic Datasets And Noise, Forward Plotting

A response with a checkable accuracy claim

Maxwell Adapters

Maxwell Analytic Benchmarks

Understand the problem/mesh/result data model

Maxwell Problem, Mesh, And Result Contracts

Maxwell Solver Meshes

Build a mesh from real geology or topography

Maxwell Solver Meshes

Maxwell Adapters

Pick or register a backend by capability, not by import

Maxwell Backend Registry

Maxwell Adapters

Solve many problems reliably, or reuse results across runs

Maxwell Caching And Batch Solving

Maxwell Analytic Benchmarks

Generate AI-inversion training data from this layer

Solver-neutral Maxwell contracts

2-D Maxwell training-data generation, Architecture roadmap

Run a full external inversion project, not just a forward check

Overview

ModEM, MARE2DEM

16.5.5. Relationship To Theory#

Maxwell Forward Modelling and Solver Contracts derives the physics and the contract decisions this whole layer is built on – why the mesh scale follows skin depth, what a capability declaration is actually claiming, and why postflight validation has to check axis identity rather than trust a returned array’s shape. Read it when a page here states a rule without re-deriving it, which is deliberate: these pages point to that derivation rather than repeating it. Solver-neutral Maxwell contracts and Architecture roadmap cover the same modules from the opposite direction – not “how do I get one validated response” but “how do these guarantees let a training pipeline trust ten thousand of them unattended.”