2.20.4.1. pycsamt.forward.maxwell#
Maxwell solver contracts, meshes, adapters, backend discovery, benchmarks, batch execution, caching, and external solver integration.
Solver-neutral contracts and adapters for multidimensional Maxwell physics.
This package is the integration boundary for verified 2-D and 3-D EM solver backends. It will own common problem/result contracts, backend capability reporting, convergence diagnostics, and canonical benchmark definitions.
Existing pycsamt.forward.em2d and pycsamt.forward.em3d APIs are
not re-exported here. A backend is exposed through this namespace only after
its dimensional coupling and numerical validation gates are documented and
tested. Importing the package must not import optional solver dependencies.
- class pycsamt.forward.maxwell.MaxwellMesh(x_edges_m, z_edges_m, y_edges_m=None, crs=None)#
Bases:
objectDescribe a rectilinear finite-volume or finite-element mesh.
- Parameters:
x_edges_m (array-like) – Strictly increasing cell-edge coordinates in metres. Depth
zincreases downward.z_edges_m (array-like) – Strictly increasing cell-edge coordinates in metres. Depth
zincreases downward.y_edges_m (array-like or None, optional) – Second horizontal axis. Omit for a 2-D mesh.
crs (str or None, optional) – Coordinate reference system for horizontal coordinates.
Examples
>>> mesh = MaxwellMesh([0, 100, 250], [0, 50, 150]) >>> mesh.shape, mesh.dimension ((2, 2), 2) >>> mesh.cell_centres_m["x"].tolist() [50.0, 175.0]
- property dimension: int#
Return the spatial dimension.
- Returns:
Mesh dimension.
- Return type:
{2, 3}
Examples
>>> MaxwellMesh([0, 1, 2], [0, 1, 2]).dimension 2
- property shape: tuple[int, ...]#
Return canonical cell-array shape.
Examples
>>> MaxwellMesh([0, 1, 2], [0, 1, 2, 3]).shape (3, 2)
- property cell_widths_m: Mapping[str, ndarray]#
Return read-only cell widths keyed by axis.
- Returns:
Keys are
xandz, plusyfor 3-D.- Return type:
mapping
Examples
>>> mesh = MaxwellMesh([0, 2, 5], [0, 1, 3]) >>> mesh.cell_widths_m["x"].tolist() [2.0, 3.0]
- property cell_centres_m: Mapping[str, ndarray]#
Return read-only cell centres keyed by axis.
- Returns:
Centre coordinates in metres.
- Return type:
mapping
Examples
>>> mesh = MaxwellMesh([0, 2, 4], [0, 10, 20]) >>> mesh.cell_centres_m["z"].tolist() [5.0, 15.0]
- to_dict()#
Return a JSON-compatible mesh representation.
- Returns:
Versioned mesh state.
- Return type:
Examples
>>> MaxwellMesh([0, 1, 2], [0, 1, 2]).to_dict()["schema_version"] 1
- class pycsamt.forward.maxwell.ReceiverSet(coordinates_m, names, orientation_deg=0.0)#
Bases:
objectDefine named receiver locations in mesh coordinates.
- Parameters:
Examples
>>> receivers = ReceiverSet([[50, 0], [150, 0]], ["S00", "S01"]) >>> receivers.dimension, receivers.count (2, 2)
- property count: int#
Return the number of receivers.
- Returns:
Receiver count.
- Return type:
Examples
>>> ReceiverSet([[0, 0]], ["S00"]).count 1
- property dimension: int#
Return the coordinate dimension.
- Returns:
Number of coordinate columns.
- Return type:
{2, 3}
Examples
>>> ReceiverSet([[0, 0, 0]], ["S00"]).dimension 3
- to_dict()#
Return JSON-compatible receiver state.
- Returns:
Versioned receiver coordinates and names.
- Return type:
Examples
>>> ReceiverSet([[0, 0]], ["S00"]).to_dict()["names"] ['S00']
- class pycsamt.forward.maxwell.MaxwellProblem(mesh, conductivity_s_m, frequencies_hz, receivers, components=('zxy', 'zyx'), active_cells=None, time_dependence='exp(+iwt)', magnetic_permeability_h_m=1.2566370614359173e-06, metadata=<factory>)#
Bases:
objectDefine one isotropic frequency-domain MT boundary-value problem.
- Parameters:
mesh (MaxwellMesh) – Rectilinear simulation mesh.
conductivity_s_m (array-like) – Positive isotropic conductivity in S/m, shaped like
mesh.frequencies_hz (array-like, shape (n_frequency,)) – Positive, unique frequencies. Input order is retained.
receivers (ReceiverSet) – Observation locations with the same dimension as
mesh.components (sequence of {"zxx", "zxy", "zyx", "zyy"}) – Requested impedance components in explicit output order.
active_cells (array-like of bool or None, optional) – Cells participating in the physical model. Air cells can remain active with a small conductivity or be marked inactive for capable backends.
time_dependence ({"exp(+iwt)", "exp(-iwt)"}, default="exp(+iwt)") – Complex phasor convention.
magnetic_permeability_h_m (float, default=1.25663706212e-6) – Uniform scalar permeability in H/m.
metadata (mapping, optional) – Finite JSON-compatible provenance; never interpreted by a backend.
Examples
>>> mesh = MaxwellMesh([0, 100, 200], [0, 50, 100]) >>> receivers = ReceiverSet([[50, 0]], ["S00"]) >>> problem = MaxwellProblem( ... mesh, np.full(mesh.shape, 0.01), [10, 1], receivers ... ) >>> problem.problem_hash == problem.problem_hash True
- mesh: MaxwellMesh#
- receivers: ReceiverSet#
- property problem_hash: str#
Return a deterministic SHA-256 digest of all physical inputs.
- Returns:
Digest suitable for cache keys.
- Return type:
Examples
>>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> p = MaxwellProblem( ... mesh, np.ones(mesh.shape), [1], ReceiverSet([[0, 0]], ["S"]) ... ) >>> len(p.problem_hash) 64
- provenance()#
Return JSON-compatible problem provenance excluding large arrays.
- Returns:
Mesh, receiver, convention, components, and metadata state.
- Return type:
Examples
>>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> p = MaxwellProblem( ... mesh, np.ones(mesh.shape), [1], ReceiverSet([[0, 0]], ["S"]) ... ) >>> p.provenance()["components"] ['zxy', 'zyx']
- to_npz(path)#
Write a pickle-free problem archive.
- Parameters:
path (str or pathlib.Path) – Destination archive.
- Returns:
Destination path.
- Return type:
Examples
>>> from tempfile import TemporaryDirectory >>> p = MaxwellProblem( ... MaxwellMesh([0, 1, 2], [0, 1, 2]), ... np.ones((2, 2)), ... [1], ... ReceiverSet([[0, 0]], ["S"]), ... ) >>> with TemporaryDirectory() as d: ... restored = MaxwellProblem.from_npz(p.to_npz(Path(d) / "p.npz")) >>> restored.problem_hash == p.problem_hash True
- classmethod from_npz(path)#
Restore and validate a problem archive.
- Parameters:
path (str or pathlib.Path) – Archive written by
to_npz().- Returns:
Restored problem.
- Return type:
Examples
>>> from tempfile import TemporaryDirectory >>> p = MaxwellProblem( ... MaxwellMesh([0, 1, 2], [0, 1, 2]), ... np.ones((2, 2)), ... [1], ... ReceiverSet([[0, 0]], ["S"]), ... ) >>> with TemporaryDirectory() as d: ... q = MaxwellProblem.from_npz(p.to_npz(Path(d) / "p.npz")) >>> np.array_equal(q.conductivity_s_m, p.conductivity_s_m) True
- class pycsamt.forward.maxwell.SolverDiagnostics(converged, iterations, relative_residual, runtime_s, messages=())#
Bases:
objectRecord convergence information for every frequency and source solve.
- Parameters:
converged (array-like of bool, shape (n_frequency, n_source)) – Whether each linear solve met its tolerance.
iterations (array-like of int, same shape) – Iteration count; zero is valid for direct solvers.
relative_residual (array-like of float, same shape) – Final non-negative relative residual.
runtime_s (float) – Total non-negative solver runtime in seconds.
messages (sequence of str, optional) – Backend messages for failed or noteworthy solves.
Examples
>>> d = SolverDiagnostics( ... [[True], [False]], [[4], [20]], [[1e-8], [1e-2]], 0.5 ... ) >>> d.success, d.maximum_relative_residual (False, 0.01)
- property success: bool#
Return whether every solve converged.
- Returns:
True only when all convergence flags are true.
- Return type:
Examples
>>> SolverDiagnostics([[True]], [[1]], [[1e-9]], 0).success True
- property maximum_relative_residual: float#
Return the largest reported relative residual.
- Returns:
Worst solve residual.
- Return type:
Examples
>>> SolverDiagnostics( ... [[True]], [[1]], [[1e-7]], 0 ... ).maximum_relative_residual 1e-07
- to_dict()#
Return JSON-compatible convergence diagnostics.
- Returns:
Versioned diagnostic state.
- Return type:
Examples
>>> SolverDiagnostics([[True]], [[2]], [[1e-8]], 0.1).to_dict()[ ... "runtime_s" ... ] 0.1
- classmethod from_dict(data)#
Restore validated convergence diagnostics.
- Parameters:
data (mapping) – State returned by
to_dict().- Returns:
Restored diagnostic record.
- Return type:
Examples
>>> state = SolverDiagnostics([[True]], [[2]], [[1e-8]], 0.1).to_dict() >>> SolverDiagnostics.from_dict(state).success True
- class pycsamt.forward.maxwell.ForwardResult(problem_hash, frequencies_hz, receiver_names, components, impedance_v_a, valid, backend_name, backend_version, diagnostics, metadata=<factory>)#
Bases:
objectStore canonical impedance predictions from a Maxwell backend.
- Parameters:
problem_hash (str) – Hash of the exact
MaxwellProblemsolved.frequencies_hz (array-like) – Frequency vector in problem order.
receiver_names (sequence of str) – Explicit station and tensor-component axes.
components (sequence of str) – Explicit station and tensor-component axes.
impedance_v_a (complex array, shape (station, frequency, component)) – Predicted SI impedance.
valid (bool array or None, optional) – Validity mask with the same shape. Defaults to finite predictions.
backend_name (str) – Solver identity required for reproducibility.
backend_version (str) – Solver identity required for reproducibility.
diagnostics (SolverDiagnostics) – Per-solve convergence record.
metadata (mapping, optional) – Additional finite JSON-compatible backend provenance.
Examples
>>> d = SolverDiagnostics([[True]], [[3]], [[1e-9]], 0.01) >>> r = ForwardResult( ... "a" * 64, [1], ["S"], ["zxy"], [[[1 + 2j]]], None, "demo", "1", d ... ) >>> r.shape, r.success ((1, 1, 1), True)
- diagnostics: SolverDiagnostics#
- property shape: tuple[int, int, int]#
Return canonical impedance shape.
Examples
>>> d = SolverDiagnostics([[True]], [[0]], [[0]], 0) >>> ForwardResult( ... "0" * 64, [1], ["S"], ["zxy"], [[[1j]]], None, "b", "1", d ... ).shape (1, 1, 1)
- property success: bool#
Return whether all solves converged and predictions are valid.
- Returns:
Combined numerical and observation validity status.
- Return type:
Examples
>>> d = SolverDiagnostics([[True]], [[0]], [[0]], 0) >>> ForwardResult( ... "0" * 64, [1], ["S"], ["zxy"], [[[1j]]], None, "b", "1", d ... ).success True
- validate_against(problem)#
Raise if this result does not exactly match a problem contract.
- Parameters:
problem (MaxwellProblem) – Expected input problem.
- Raises:
ValueError – If hash or any output axis differs.
- Return type:
None
Examples
>>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> p = MaxwellProblem( ... mesh, ... np.ones((2, 2)), ... [1], ... ReceiverSet([[0, 0]], ["S"]), ... ("zxy",), ... ) >>> d = SolverDiagnostics([[True]], [[0]], [[0]], 0) >>> ForwardResult( ... p.problem_hash, ... [1], ... ["S"], ... ["zxy"], ... [[[1j]]], ... None, ... "b", ... "1", ... d, ... ).validate_against(p)
- provenance()#
Return JSON-compatible solver and output-axis provenance.
- Returns:
Problem identity, axes, backend, diagnostics, and metadata.
- Return type:
Examples
>>> d = SolverDiagnostics([[True]], [[0]], [[0]], 0) >>> r = ForwardResult( ... "0" * 64, [1], ["S"], ["zxy"], [[[1j]]], None, "b", "1", d ... ) >>> r.provenance()["backend_name"] 'b'
- to_npz(path)#
Write a versioned, pickle-free result archive.
- Parameters:
path (str or pathlib.Path) – Destination archive.
- Returns:
Destination path.
- Return type:
Examples
>>> from tempfile import TemporaryDirectory >>> d = SolverDiagnostics([[True]], [[0]], [[0]], 0) >>> r = ForwardResult( ... "0" * 64, [1], ["S"], ["zxy"], [[[1j]]], None, "b", "1", d ... ) >>> with TemporaryDirectory() as directory: ... restored = ForwardResult.from_npz( ... r.to_npz(Path(directory) / "r.npz") ... ) >>> restored.backend_name, restored.shape ('b', (1, 1, 1))
- classmethod from_npz(path)#
Restore and validate a result archive without pickle.
- Parameters:
path (str or pathlib.Path) – Archive written by
to_npz().- Returns:
Restored canonical result.
- Return type:
Examples
>>> from tempfile import TemporaryDirectory >>> d = SolverDiagnostics([[True]], [[0]], [[0]], 0) >>> r = ForwardResult( ... "0" * 64, [1], ["S"], ["zxy"], [[[1j]]], None, "b", "1", d ... ) >>> with TemporaryDirectory() as directory: ... restored = ForwardResult.from_npz( ... r.to_npz(Path(directory) / "r.npz") ... ) >>> np.array_equal(restored.impedance_v_a, r.impedance_v_a) True
- class pycsamt.forward.maxwell.TriMesh(nodes_m, triangles, region_ids=None, boundary_segments=None, crs=None)#
Bases:
objectDescribe an unstructured 2-D triangular finite-element mesh.
- Parameters:
nodes_m (array-like, shape (n_nodes, 2)) – Node
(x, z)coordinates in metres. Depthzincreases downward, matchingMaxwellMesh.triangles (array-like of int, shape (n_triangles, 3)) – 0-based node-index connectivity, one row per triangle.
region_ids (array-like of int, shape (n_triangles,), optional) – Material/region label per triangle. Defaults to all zeros (a single region) when omitted.
boundary_segments (array-like of int, shape (n_segments, 2), optional) – 0-based node-index pairs marking PSLG boundary edges (region divides, survey extent). Purely descriptive; not required for a valid mesh.
crs (str or None, optional) – Coordinate reference system for horizontal coordinates.
Examples
>>> mesh = TriMesh( ... nodes_m=[[0, 0], [100, 0], [50, 50], [150, 60]], ... triangles=[[0, 1, 2], [1, 3, 2]], ... ) >>> mesh.dimension, mesh.n_nodes, mesh.n_triangles (2, 4, 2) >>> mesh.shape (2,)
- property dimension: int#
Return the spatial dimension.
- Returns:
Always 2 for this contract.
- Return type:
Examples
>>> TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]).dimension 2
- property n_nodes: int#
Return the number of mesh nodes.
Examples
>>> TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]).n_nodes 3
- property n_triangles: int#
Return the number of mesh triangles.
Examples
>>> TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]).n_triangles 1
- property shape: tuple[int]#
Return canonical per-triangle array shape.
- Returns:
(n_triangles,), mirroringshape’s role forTriProblemconductivity/active-cell arrays.- Return type:
Examples
>>> TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]).shape (1,)
- property triangle_centroids_m: ndarray#
Return the
(x, z)centroid of every triangle.- Returns:
Mean of each triangle’s three node coordinates.
- Return type:
ndarray, shape (n_triangles, 2)
Examples
>>> mesh = TriMesh([[0, 0], [3, 0], [0, 3]], [[0, 1, 2]]) >>> mesh.triangle_centroids_m.tolist() [[1.0, 1.0]]
- property triangle_areas_m2: ndarray#
Return the area of every triangle.
- Returns:
Positive triangle areas in square metres.
- Return type:
ndarray, shape (n_triangles,)
Examples
>>> mesh = TriMesh([[0, 0], [4, 0], [0, 3]], [[0, 1, 2]]) >>> mesh.triangle_areas_m2.tolist() [6.0]
- to_dict()#
Return a JSON-compatible mesh representation.
- Returns:
Versioned mesh state.
- Return type:
Examples
>>> TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]).to_dict()[ ... "schema_version" ... ] 1
- classmethod from_dict(data)#
Restore a validated mesh from serialized state.
- Parameters:
data (mapping) – State returned by
to_dict().- Returns:
Restored immutable mesh.
- Return type:
Examples
>>> mesh = TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]) >>> TriMesh.from_dict(mesh.to_dict()).n_triangles 1
- class pycsamt.forward.maxwell.TriProblem(mesh, conductivity_s_m, frequencies_hz, receivers, components=('zxy', 'zyx'), active_cells=None, time_dependence='exp(+iwt)', magnetic_permeability_h_m=1.2566370614359173e-06, metadata=<factory>)#
Bases:
objectDefine one isotropic frequency-domain MT problem on a triangular mesh.
- Parameters:
mesh (TriMesh) – Unstructured simulation mesh.
conductivity_s_m (array-like, shape (n_triangles,)) – Positive isotropic conductivity in S/m, one value per triangle.
frequencies_hz (array-like, shape (n_frequency,)) – Positive, unique frequencies. Input order is retained.
receivers (ReceiverSet) – Observation locations with the same dimension as
mesh.components (sequence of {"zxy", "zyx"}) – Requested impedance components; only the 2-D pair is valid since
TriMeshis 2-D-only in this release.active_cells (array-like of bool or None, optional) – Triangles participating in the physical model.
time_dependence ({"exp(+iwt)", "exp(-iwt)"}, default="exp(+iwt)") – Complex phasor convention.
magnetic_permeability_h_m (float, default=1.25663706212e-6) – Uniform scalar permeability in H/m.
metadata (mapping, optional) – Finite JSON-compatible provenance; never interpreted by a backend.
Examples
>>> from pycsamt.forward.maxwell.contracts import ReceiverSet >>> mesh = TriMesh([[0, 0], [200, 0], [100, 100]], [[0, 1, 2]]) >>> receivers = ReceiverSet([[100, 0]], ["S00"]) >>> problem = TriProblem(mesh, [0.01], [10, 1], receivers) >>> problem.problem_hash == problem.problem_hash True
- receivers: ReceiverSet#
- property problem_hash: str#
Return a deterministic SHA-256 digest of all physical inputs.
- Returns:
Digest suitable for cache keys.
- Return type:
Examples
>>> from pycsamt.forward.maxwell.contracts import ReceiverSet >>> mesh = TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]) >>> p = TriProblem(mesh, [1], [1], ReceiverSet([[0, 0]], ["S"])) >>> len(p.problem_hash) 64
- provenance()#
Return JSON-compatible problem provenance excluding large arrays.
- Returns:
Mesh, receiver, convention, components, and metadata state.
- Return type:
Examples
>>> from pycsamt.forward.maxwell.contracts import ReceiverSet >>> mesh = TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]) >>> p = TriProblem(mesh, [1], [1], ReceiverSet([[0, 0]], ["S"])) >>> p.provenance()["components"] ['zxy', 'zyx']
- to_npz(path)#
Write a pickle-free problem archive.
- Parameters:
path (str or pathlib.Path) – Destination archive.
- Returns:
Destination path.
- Return type:
Examples
>>> from tempfile import TemporaryDirectory >>> from pycsamt.forward.maxwell.contracts import ReceiverSet >>> mesh = TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]) >>> p = TriProblem(mesh, [1], [1], ReceiverSet([[0, 0]], ["S"])) >>> with TemporaryDirectory() as d: ... restored = TriProblem.from_npz(p.to_npz(Path(d) / "p.npz")) >>> restored.problem_hash == p.problem_hash True
- classmethod from_npz(path)#
Restore and validate a problem archive.
- Parameters:
path (str or pathlib.Path) – Archive written by
to_npz().- Returns:
Restored problem.
- Return type:
Examples
>>> from tempfile import TemporaryDirectory >>> from pycsamt.forward.maxwell.contracts import ReceiverSet >>> mesh = TriMesh([[0, 0], [1, 0], [0, 1]], [[0, 1, 2]]) >>> p = TriProblem(mesh, [1], [1], ReceiverSet([[0, 0]], ["S"])) >>> with TemporaryDirectory() as d: ... q = TriProblem.from_npz(p.to_npz(Path(d) / "p.npz")) >>> np.array_equal(q.conductivity_s_m, p.conductivity_s_m) True
- class pycsamt.forward.maxwell.BackendCapabilities(name, version, dimensions, components, time_conventions=('exp(+iwt)',), supports_nonuniform_mesh=True, supports_inactive_cells=False, supports_topography=False, supports_anisotropy=False, maximum_cells=None, maximum_frequencies=None, verified_benchmarks=())#
Bases:
objectDeclare a Maxwell adapter’s supported physical and numerical scope.
- Parameters:
name (str) – Stable backend identifier and adapter/solver version.
version (str) – Stable backend identifier and adapter/solver version.
dimensions (tuple containing 2 and/or 3) – Spatial problem dimensions genuinely supported.
components (tuple of str) – Impedance tensor components the adapter can return.
time_conventions (tuple of str, default=("exp(+iwt)",)) – Phasor conventions accepted without conversion.
supports_nonuniform_mesh (bool, default=True) – Whether variable cell widths are supported.
supports_inactive_cells (bool, default=False) – Whether
MaxwellProblem.active_cellsis honored.supports_topography (bool, default=False) – Whether an inactive/air mask may describe non-flat terrain.
supports_anisotropy (bool, default=False) – Reserved declaration for future tensor-conductivity contracts.
maximum_cells (int or None, optional) – Enforced adapter limits. None means no declared limit.
maximum_frequencies (int or None, optional) – Enforced adapter limits. None means no declared limit.
verified_benchmarks (tuple of str, optional) – Stable benchmark identifiers passed by this adapter version.
Examples
>>> capability = BackendCapabilities("mt2d", "1.0", (2,), ("zxy", "zyx")) >>> capability.supports_dimension(2), capability.supports_component("zxy") (True, True)
- supports_dimension(dimension)#
Return whether a spatial dimension is supported.
Examples
>>> BackendCapabilities("b", "1", (2,), ("zxy",)).supports_dimension(3) False
- supports_component(component)#
Return whether an impedance component is supported.
- Parameters:
component (str) – Canonical tensor component name.
- Returns:
Capability status.
- Return type:
Examples
>>> BackendCapabilities("b", "1", (2,), ("zxy",)).supports_component( ... "ZYX" ... ) False
- assess(problem)#
Assess a problem without invoking the numerical backend.
- Parameters:
problem (MaxwellProblem or TriProblem) – Validated problem contract, rectilinear or triangular. Both expose the same
mesh/frequencies_hz/components/time_dependence/active_cellsshape this method reads; onlycell_widths_mis rectilinear-only, so the nonuniform-mesh check below is skipped (not applicable) for an unstructuredTriMesh.- Returns:
All hard errors plus advisory validation warnings.
- Return type:
Examples
>>> from .contracts import MaxwellMesh, ReceiverSet >>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> problem = MaxwellProblem( ... mesh, np.ones((2, 2)), [1], ReceiverSet([[0, 0]], ["S"]) ... ) >>> BackendCapabilities("b", "1", (2,), ("zxy", "zyx")).assess( ... problem ... ).compatible True
- to_dict()#
Return a JSON-compatible capability declaration.
- Returns:
Versioned capability state.
- Return type:
Examples
>>> BackendCapabilities("b", "1", (2,), ("zxy",)).to_dict()[ ... "dimensions" ... ] [2]
- class pycsamt.forward.maxwell.CompatibilityReport(backend_name, compatible, errors=(), warnings=())#
Bases:
objectDescribe whether a backend can solve a particular problem.
- Parameters:
Examples
>>> report = CompatibilityReport("demo", False, ("3-D unsupported",)) >>> report.require() Traceback (most recent call last): ... ValueError: backend 'demo' is incompatible: 3-D unsupported
- require()#
Raise a consolidated error when the report is incompatible.
- Raises:
ValueError – If one or more hard incompatibilities were found.
- Return type:
None
Examples
>>> CompatibilityReport("demo", True).require()
- class pycsamt.forward.maxwell.MaxwellBackend(*args, **kwargs)#
Bases:
ProtocolRuntime-checkable interface implemented by Maxwell adapters.
Examples
A conforming adapter exposes immutable capabilities and a solve method.
>>> class Demo: ... capabilities = BackendCapabilities( ... "demo", "1", (2,), ("zxy", "zyx") ... ) ... ... def solve(self, problem): ... raise NotImplementedError >>> isinstance(Demo(), MaxwellBackend) True
- property capabilities: BackendCapabilities#
Return the adapter’s immutable capability declaration.
- solve(problem)#
Solve a compatible problem and return canonical output.
- Parameters:
problem (MaxwellProblem)
- Return type:
- class pycsamt.forward.maxwell.BackendRegistration(capabilities, factory, availability_probe=None)#
Bases:
objectStore one lazy backend factory and its availability probe.
- Parameters:
capabilities (BackendCapabilities) – Static capability declaration, available without importing the solver.
factory (callable) – Factory returning a
MaxwellBackend.availability_probe (callable or None, optional) – Lightweight function returning
(available, reason).
Examples
>>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> registration = BackendRegistration(cap, lambda: object()) >>> registration.availability() (True, None)
- capabilities: BackendCapabilities#
- factory: Callable[[...], MaxwellBackend]#
- availability()#
Return whether the optional backend can currently be created.
- Returns:
available (bool) – Probe status.
reason (str or None) – Human-readable reason when unavailable.
- Return type:
Examples
>>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> BackendRegistration( ... cap, lambda: None, lambda: (False, "missing") ... ).availability() (False, 'missing')
- create(**options)#
Create and validate a backend instance.
- Parameters:
**options (Any) – Backend-specific constructor options.
- Returns:
Conforming adapter instance.
- Return type:
- Raises:
RuntimeError – If unavailable or the factory violates its registration.
Examples
>>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> class Demo: ... capabilities = cap ... ... def solve(self, problem): ... raise NotImplementedError >>> BackendRegistration(cap, Demo).create().capabilities.name 'demo'
- class pycsamt.forward.maxwell.BackendRegistry#
Bases:
objectThread-safe registry of lazy Maxwell backend factories.
Examples
>>> registry = BackendRegistry() >>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> registry.register(BackendRegistration(cap, lambda: None)) >>> registry.names() ('demo',)
- register(registration, *, replace=False)#
Register a backend under its declared capability name.
- Parameters:
registration (BackendRegistration) – Lazy backend definition.
replace (bool, default=False) – Explicitly replace an existing registration.
- Raises:
ValueError – If the name already exists and replacement was not requested.
- Return type:
None
Examples
>>> registry = BackendRegistry() >>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> registry.register(BackendRegistration(cap, lambda: None))
- unregister(name)#
Remove and return a registration.
- Parameters:
name (str) – Backend identifier.
- Returns:
Removed registration.
- Return type:
Examples
>>> registry = BackendRegistry() >>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> registry.register(BackendRegistration(cap, lambda: None)) >>> registry.unregister("demo").capabilities.name 'demo'
- get(name)#
Return one registration without creating its backend.
- Parameters:
name (str) – Backend identifier.
- Returns:
Lazy registration.
- Return type:
Examples
>>> registry = BackendRegistry() >>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> registry.register(BackendRegistration(cap, lambda: None)) >>> registry.get("DEMO").capabilities.version '1'
- names(*, available_only=False)#
Return sorted registered backend names.
- Parameters:
available_only (bool, default=False) – Exclude registrations whose availability probe fails.
- Returns:
Stable sorted names.
- Return type:
Examples
>>> BackendRegistry().names() ()
- describe()#
Return immutable availability and capability summaries.
- Returns:
Backend names mapped to JSON-compatible summaries.
- Return type:
mapping
Examples
>>> BackendRegistry().describe() == {} True
- create(name, **options)#
Create a named backend through its lazy factory.
- Parameters:
name (str) – Registered backend identifier.
**options – Backend-specific constructor options.
- Returns:
Validated adapter.
- Return type:
Examples
See
BackendRegistration.create()for a complete adapter example.
- pycsamt.forward.maxwell.register_backend(registration, *, replace=False)#
Register a lazy backend in the process-wide registry.
- Parameters:
registration (BackendRegistration) – Backend definition.
replace (bool, default=False) – Explicitly replace an existing name.
- Return type:
None
Examples
Prefer a private
BackendRegistryin isolated applications and tests; this function is intended for adapter package initialization.
- pycsamt.forward.maxwell.unregister_backend(name)#
Remove a backend from the process-wide registry.
- Parameters:
name (str) – Backend identifier.
- Returns:
Removed definition.
- Return type:
Examples
This operation is primarily useful for plugin teardown and tests.
- pycsamt.forward.maxwell.create_backend(name, **options)#
Create a registered Maxwell adapter lazily.
- Parameters:
name (str) – Backend identifier.
**options – Backend-specific constructor options.
- Returns:
Validated backend instance.
- Return type:
Examples
Backend packages register their factories before this function is called.
- pycsamt.forward.maxwell.list_backends(*, available_only=False)#
Describe registered Maxwell backends without creating them.
- Parameters:
available_only (bool, default=False) – Exclude unavailable optional backends.
- Returns:
Immutable capability and availability summaries.
- Return type:
mapping
Examples
>>> isinstance(list_backends(), Mapping) True
- exception pycsamt.forward.maxwell.MaxwellAdapterError#
Bases:
RuntimeErrorBase exception raised by the validated adapter execution layer.
Examples
>>> error = MaxwellAdapterError("solver failed") >>> str(error) 'solver failed'
- exception pycsamt.forward.maxwell.IncompatibleProblemError#
Bases:
MaxwellAdapterErrorIndicate that declared backend capabilities reject a problem.
Examples
>>> isinstance( ... IncompatibleProblemError("unsupported"), MaxwellAdapterError ... ) True
- exception pycsamt.forward.maxwell.BackendExecutionError#
Bases:
MaxwellAdapterErrorWrap an exception raised inside a numerical backend.
Examples
>>> isinstance(BackendExecutionError("failed"), MaxwellAdapterError) True
- exception pycsamt.forward.maxwell.InvalidBackendResultError#
Bases:
MaxwellAdapterErrorIndicate malformed, mislabeled, or mismatched backend output.
Examples
>>> isinstance(InvalidBackendResultError("bad axes"), MaxwellAdapterError) True
- exception pycsamt.forward.maxwell.SolverConvergenceError#
Bases:
MaxwellAdapterErrorIndicate that a valid result violates the convergence policy.
Examples
>>> isinstance( ... SolverConvergenceError("residual too large"), MaxwellAdapterError ... ) True
- class pycsamt.forward.maxwell.AdapterPolicy(require_convergence=True, maximum_relative_residual=None, require_all_valid=True, emit_capability_warnings=True, wrap_backend_exceptions=True)#
Bases:
objectConfigure solver-independent result acceptance rules.
- Parameters:
require_convergence (bool, default=True) – Reject a result when any solve reports non-convergence.
maximum_relative_residual (float or None, optional) – Reject a result whose worst reported residual exceeds this value. None delegates residual acceptance entirely to the backend.
require_all_valid (bool, default=True) – Reject results containing masked or non-finite observations.
emit_capability_warnings (bool, default=True) – Emit advisory messages from capability assessment.
wrap_backend_exceptions (bool, default=True) – Wrap ordinary backend exceptions in
BackendExecutionError.
Examples
>>> policy = AdapterPolicy(maximum_relative_residual=1e-6) >>> policy.maximum_relative_residual 1e-06
- class pycsamt.forward.maxwell.BaseMaxwellAdapter(capabilities, policy=None)#
Bases:
ABCBase class enforcing common preflight and postflight validation.
- Parameters:
capabilities (BackendCapabilities) – Immutable declaration for the exact backend version.
policy (AdapterPolicy or None, optional) – Result acceptance policy. Defaults to
AdapterPolicy.
Notes
Implementations override only
_solve_backend(). They must return a canonicalForwardResult; all validation is performed bysolve().Examples
See
CallableMaxwellAdapterfor a minimal concrete adapter.- property capabilities: BackendCapabilities#
Return the immutable backend capability declaration.
- Returns:
Physical and numerical scope of this adapter.
- Return type:
Examples
>>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> CallableMaxwellAdapter( ... cap, lambda problem: None ... ).capabilities is cap True
- property policy: AdapterPolicy#
Return the immutable result acceptance policy.
- Returns:
Policy applied after every solve.
- Return type:
Examples
>>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> CallableMaxwellAdapter( ... cap, lambda problem: None ... ).policy.require_convergence True
- assess(problem)#
Assess a problem against declared backend capabilities.
- Parameters:
problem (MaxwellProblem) – Candidate simulation problem.
- Returns:
Consolidated errors and warnings without invoking the solver.
- Return type:
Examples
>>> from .contracts import MaxwellMesh, ReceiverSet >>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> problem = MaxwellProblem( ... mesh, ... np.ones((2, 2)), ... [1], ... ReceiverSet([[0, 0]], ["S"]), ... ("zxy",), ... ) >>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> CallableMaxwellAdapter(cap, lambda problem: None).assess( ... problem ... ).compatible True
- solve(problem)#
Validate, execute, and verify one Maxwell problem.
- Parameters:
problem (MaxwellProblem) – Solver-neutral simulation input.
- Returns:
Canonical, problem-matched impedance result.
- Return type:
- Raises:
IncompatibleProblemError – If preflight capability checks fail.
BackendExecutionError – If the numerical backend raises an ordinary exception.
InvalidBackendResultError – If returned output violates the result contract or problem axes.
SolverConvergenceError – If convergence, residual, or validity policy fails.
Examples
Concrete execution examples require a backend callback; see
CallableMaxwellAdapter.
- solve_many(problems)#
Solve problems sequentially while preserving input order.
- Parameters:
problems (iterable of MaxwellProblem) – Finite problem stream. Execution stops at the first failure.
- Returns:
Results in exactly the supplied order.
- Return type:
Examples
An empty collection performs no backend calls:
>>> cap = BackendCapabilities("demo", "1", (2,), ("zxy",)) >>> CallableMaxwellAdapter(cap, lambda problem: None).solve_many([]) ()
- class pycsamt.forward.maxwell.CallableMaxwellAdapter(capabilities, solver, policy=None)#
Bases:
BaseMaxwellAdapterAdapt a trusted callable to the validated Maxwell backend interface.
- Parameters:
capabilities (BackendCapabilities) – Static declaration matching callback output identity.
solver (callable) – Function accepting
MaxwellProblemand returningForwardResult.policy (AdapterPolicy or None, optional) – Solver-independent acceptance policy.
Examples
>>> from .contracts import MaxwellMesh, ReceiverSet, SolverDiagnostics >>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> problem = MaxwellProblem( ... mesh, np.ones((2, 2)), [1], ReceiverSet([[0, 0]], ["S"]), ("zxy",) ... ) >>> cap = BackendCapabilities( ... "demo", "1", (2,), ("zxy",), verified_benchmarks=("half-space",) ... ) >>> def solver(value): ... diagnostics = SolverDiagnostics([[True]], [[0]], [[0]], 0) ... return ForwardResult( ... value.problem_hash, ... value.frequencies_hz, ... value.receivers.names, ... value.components, ... [[[1j]]], ... None, ... "demo", ... "1", ... diagnostics, ... ) >>> CallableMaxwellAdapter(cap, solver).solve(problem).success True
- class pycsamt.forward.maxwell.MeshDesign(horizontal_padding_cells=6, bottom_padding_cells=8, air_layers=8, padding_expansion=1.35, air_expansion=1.25, air_conductivity_s_m=1e-08, minimum_cells_per_skin_depth=4.0, maximum_adjacent_ratio=1.5, maximum_aspect_ratio=20.0)#
Bases:
objectConfigure geometric padding, air treatment, and quality targets.
- Parameters:
horizontal_padding_cells (int or pair of int, default=6) – Number of padding cells before/after each horizontal core axis. A scalar is applied symmetrically.
bottom_padding_cells (int, default=8) – Number of cells beneath the geological model.
air_layers (int, default=8) – Number of cells above the geological reference surface.
padding_expansion (float, default=1.35) – Geometric growth factor away from the core mesh.
air_expansion (float, default=1.25) – Geometric growth factor upward through the air.
air_conductivity_s_m (float, default=1e-8) – Positive numerical conductivity assigned to air cells.
minimum_cells_per_skin_depth (float, default=4.0) – Advisory resolution target evaluated at the smallest skin depth.
maximum_adjacent_ratio (float, default=1.5) – Advisory upper bound for adjacent cell-width ratios.
maximum_aspect_ratio (float, default=20.0) – Advisory upper bound across cell widths.
Examples
>>> design = MeshDesign(horizontal_padding_cells=(3, 5), air_layers=4) >>> design.horizontal_padding (3, 5)
- property horizontal_padding: tuple[int, int]#
Return normalized before/after horizontal padding counts.
Examples
>>> MeshDesign(horizontal_padding_cells=3).horizontal_padding (3, 3)
- to_dict()#
Return a JSON-compatible design representation.
- Returns:
Versioned design state.
- Return type:
Examples
>>> MeshDesign(air_layers=2).to_dict()["air_layers"] 2
- class pycsamt.forward.maxwell.MeshQuality(cell_count, minimum_cell_width_m, maximum_cell_width_m, maximum_aspect_ratio, maximum_adjacent_ratio, minimum_skin_depth_m, cells_per_minimum_skin_depth, warnings=())#
Bases:
objectSummarize numerical mesh quality and skin-depth resolution.
- Parameters:
cell_count (int) – Total number of cells including air and padding.
minimum_cell_width_m (float) – Extreme cell widths over all axes.
maximum_cell_width_m (float) – Extreme cell widths over all axes.
maximum_aspect_ratio (float) – Ratio of maximum to minimum cell width.
maximum_adjacent_ratio (float) – Worst neighboring width expansion on any axis.
minimum_skin_depth_m (float) – Smallest skin depth across the requested physics range.
cells_per_minimum_skin_depth (float) – Skin depth divided by the largest core cell width.
Examples
>>> quality = MeshQuality(10, 1, 5, 5, 1.2, 100, 20, ()) >>> quality.acceptable True
- class pycsamt.forward.maxwell.SolverMeshModel(mesh, conductivity_s_m, earth_mask, core_slices, design, quality, source_shape)#
Bases:
objectStore a padded mesh, conductivity, regions, and construction record.
- Parameters:
mesh (MaxwellMesh) – Solver-neutral padded mesh.
conductivity_s_m (ndarray) – Positive conductivity shaped like
mesh. Air cells contain the configured small numerical conductivity.earth_mask (ndarray of bool) – True for cells on or below local terrain.
core_slices (tuple of slice) – Geological core location in canonical array order.
design (MeshDesign) – Construction settings.
quality (MeshQuality) – Mesh-quality diagnostics for the requested frequency range.
source_shape (tuple of int) – Original geological model shape.
Examples
Instances are normally created with
build_solver_mesh().- mesh: MaxwellMesh#
- design: MeshDesign#
- quality: MeshQuality#
- property air_mask: ndarray#
Return the read-only complement of the earth mask.
- Returns:
Air-region mask shaped like
mesh.- Return type:
ndarray of bool
Examples
air_maskandearth_maskalways partition the complete mesh.
- property model_hash: str#
Return a deterministic digest of mesh, model, regions, and design.
- Returns:
SHA-256 digest suitable for provenance checks.
- Return type:
Examples
A valid model hash always contains 64 hexadecimal characters.
- assess_receivers(receivers)#
Return receiver-placement errors without modifying coordinates.
- Parameters:
receivers (ReceiverSet) – Candidate locations in the mesh coordinate system.
- Returns:
Empty when all receivers lie inside mesh bounds and no receiver is below the discretized local terrain surface.
- Return type:
Examples
Use this check before
to_problem()when receiver coordinates are assembled independently from the mesh.
- to_problem(frequencies_hz, receivers, *, components=('zxy', 'zyx'), mark_air_inactive=False, time_dependence='exp(+iwt)', magnetic_permeability_h_m=1.2566370614359173e-06, metadata=None)#
Create a validated Maxwell problem from this mesh model.
- Parameters:
frequencies_hz (sequence of float) – Positive simulation frequencies.
receivers (ReceiverSet) – Receiver locations matching the mesh dimension.
components (sequence of str, default=("zxy", "zyx")) – Requested canonical impedance components.
mark_air_inactive (bool, default=False) – Use
earth_maskas active cells. Keep false for formulations that solve conductive air explicitly.time_dependence (str, default="exp(+iwt)") – Complex phasor convention.
magnetic_permeability_h_m (float, default=4e-7*pi) – Uniform magnetic permeability.
metadata (mapping or None, optional) – Additional problem provenance.
- Returns:
Solver-neutral problem ready for adapter assessment.
- Return type:
Examples
The generated problem includes
mesh_model_hashin its metadata.
- provenance()#
Return JSON-compatible mesh-construction provenance.
- Returns:
Mesh, design, quality, source shape, and core slices.
- Return type:
Examples
The returned schema version is currently one.
- to_npz(path)#
Persist a solver mesh model without enabling pickle.
- Parameters:
path (str or pathlib.Path) – Destination archive.
- Returns:
Requested destination.
- Return type:
Examples
Archives can be restored with
from_npz().
- classmethod from_npz(path)#
Restore and validate a solver mesh archive without pickle.
- Parameters:
path (str or pathlib.Path) – Archive written by
to_npz().- Returns:
Restored immutable mesh model.
- Return type:
Examples
Restored arrays remain read-only after construction.
- pycsamt.forward.maxwell.skin_depth_m(resistivity_ohm_m, frequency_hz)#
Calculate electromagnetic skin depth for a non-magnetic conductor.
- Parameters:
resistivity_ohm_m (array-like) – Positive resistivity and frequency, broadcast using NumPy rules.
frequency_hz (array-like) – Positive resistivity and frequency, broadcast using NumPy rules.
- Returns:
Skin depth in metres,
sqrt(rho / (pi * mu0 * f)).- Return type:
ndarray
Examples
>>> round(float(skin_depth_m(100, 1))) 5033 >>> skin_depth_m([100, 400], 1).shape (2,)
- pycsamt.forward.maxwell.build_solver_mesh(grid, *, conductivity_s_m=None, resistivity_ohm_m=None, frequencies_hz, topography=None, design=None)#
Build a padded Maxwell mesh from a geological cell-centre model.
- Parameters:
grid (pycsamt.ai.geology.GeologyGrid) – Source grid in canonical geological order. Its upper cell edge must be depth zero, the reference used by topography and receiver coordinates.
conductivity_s_m (array-like or None) – Supply exactly one positive model shaped like
grid.resistivity_ohm_m (array-like or None) – Supply exactly one positive model shaped like
grid.frequencies_hz (sequence of float) – Frequencies used only for skin-depth quality diagnostics.
topography (pycsamt.ai.geology.TopographicSurface or None, optional) – Terrain aligned to
grid. When omitted, the geological top edge is treated as a flat earth surface.design (MeshDesign or None, optional) – Padding and quality configuration.
- Returns:
Padded conductivity, earth/air regions, core mapping, and diagnostics.
- Return type:
Examples
>>> from pycsamt.ai.geology import GeologyGrid >>> grid = GeologyGrid.regular_2d(nx=3, nz=2, dx_m=100, dz_m=50) >>> model = build_solver_mesh( ... grid, ... resistivity_ohm_m=np.full(grid.shape, 100), ... frequencies_hz=[10, 1], ... design=MeshDesign( ... horizontal_padding_cells=1, ... bottom_padding_cells=1, ... air_layers=1, ... ), ... ) >>> model.mesh.shape, model.core_slices ((4, 5), (slice(1, 3, None), slice(1, 4, None)))
- exception pycsamt.forward.maxwell.CacheCorruptionError#
Bases:
RuntimeErrorIndicate that a cached archive failed integrity validation.
Examples
>>> isinstance(CacheCorruptionError("bad checksum"), RuntimeError) True
- exception pycsamt.forward.maxwell.CacheLockTimeoutError#
Bases:
TimeoutErrorIndicate that a cache-key lock could not be acquired in time.
Examples
>>> isinstance(CacheLockTimeoutError("busy"), TimeoutError) True
- class pycsamt.forward.maxwell.CacheEntry(key, archive_path, size_bytes, modified_time_s)#
Bases:
objectDescribe one complete cache entry.
- Parameters:
key (str) – Problem SHA-256 digest.
archive_path (pathlib.Path) – Result archive location.
size_bytes (int) – Combined archive and checksum size.
modified_time_s (float) – Archive modification time as Unix seconds.
Examples
>>> entry = CacheEntry("0" * 64, Path("result.npz"), 10, 1.0) >>> entry.size_bytes 10
- class pycsamt.forward.maxwell.CacheStatistics(entry_count, total_bytes, orphan_count, corrupt_count)#
Bases:
objectSummarize the current on-disk cache state.
- Parameters:
entry_count (int) – Counts and storage for normal, incomplete, and quarantined files.
total_bytes (int) – Counts and storage for normal, incomplete, and quarantined files.
orphan_count (int) – Counts and storage for normal, incomplete, and quarantined files.
corrupt_count (int) – Counts and storage for normal, incomplete, and quarantined files.
Examples
>>> CacheStatistics(2, 100, 0, 1).entry_count 2
- class pycsamt.forward.maxwell.MaxwellResultCache(root, *, lock_timeout_s=300.0, poll_interval_s=0.05, stale_lock_s=3600.0, quarantine_corrupt=True)#
Bases:
objectManage a validated, content-addressed result cache.
- Parameters:
root (str or pathlib.Path) – Dedicated cache directory. It is created when absent.
lock_timeout_s (float, default=300) – Maximum wait for another worker holding the same problem key.
poll_interval_s (float, default=0.05) – Delay between lock acquisition attempts.
stale_lock_s (float, default=3600) – Age after which an abandoned lock can be removed.
quarantine_corrupt (bool, default=True) – Move corrupt files under
root/corrupt. When false, reads raiseCacheCorruptionErrorand leave the entry untouched.
Examples
>>> from tempfile import TemporaryDirectory >>> with TemporaryDirectory() as directory: ... cache = MaxwellResultCache(directory) ... cache.statistics().entry_count 0
- property root: Path#
Return the resolved cache root.
- Returns:
Dedicated cache directory.
- Return type:
Examples
The returned path is always absolute.
- contains(problem)#
Return whether a complete entry exists for a problem.
- Parameters:
problem (MaxwellProblem) – Problem whose content hash identifies the entry.
- Returns:
True when both archive and checksum sidecar exist.
- Return type:
Examples
A newly created cache contains no problems.
- get(problem)#
Load and validate a cached result.
- Parameters:
problem (MaxwellProblem) – Exact problem expected by the caller.
- Returns:
Valid result, or None when no complete entry exists. Corruption is quarantined and treated as a miss when configured.
- Return type:
ForwardResult or None
- Raises:
CacheCorruptionError – If validation fails and quarantine is disabled.
Examples
Cache misses return None rather than raising
KeyError.
- put(problem, result, *, overwrite=False)#
Validate and atomically store one result.
- Parameters:
problem (MaxwellProblem) – Exact simulation input.
result (ForwardResult) – Canonical result matching
problem.overwrite (bool, default=False) – Replace an existing complete entry. Otherwise the validated existing entry is retained.
- Returns:
Metadata for the stored or retained entry.
- Return type:
Examples
Invalid problem/result pairs are rejected before writing an archive.
- get_or_solve(problem, backend)#
Return a hit or solve and cache one problem under a key lock.
- Parameters:
problem (MaxwellProblem) – Simulation input and cache identity.
backend (MaxwellBackend) – Conforming backend used only after a cache miss.
- Returns:
Valid cached or newly computed result.
- Return type:
Notes
The second read after locking prevents duplicate concurrent work.
Examples
Backend invocation is skipped whenever a validated hit exists.
- entry(key)#
Return filesystem metadata for a complete entry.
- Parameters:
key (str) – Problem SHA-256 digest.
- Returns:
Entry paths, size, and modification time.
- Return type:
- Raises:
KeyError – If archive or checksum sidecar is missing.
Examples
This method does not deserialize the result archive.
- entries()#
Return complete entries sorted by problem key.
- Returns:
Stable snapshot of complete cache entries.
- Return type:
tuple of CacheEntry
Examples
An empty cache returns an empty tuple.
- remove(problem)#
Remove one problem entry under its key lock.
- Parameters:
problem (MaxwellProblem) – Exact problem identifying the entry.
- Returns:
True when at least one entry file was removed.
- Return type:
Examples
Removing an absent problem is a no-op returning False.
- prune(maximum_bytes)#
Remove oldest entries until storage is within a byte budget.
- Parameters:
maximum_bytes (int) – Non-negative archive and checksum budget.
- Returns:
Entries removed, oldest first.
- Return type:
tuple of CacheEntry
Examples
prune(0)removes every complete entry but leaves infrastructure.
- statistics()#
Inspect complete, orphaned, and quarantined cache files.
- Returns:
Current cache counts and complete-entry storage.
- Return type:
Examples
Statistics inspect metadata without deserializing archives.
- class pycsamt.forward.maxwell.BenchmarkThresholds(maximum_normalized_rms=0.05, maximum_amplitude_relative_error=0.05, maximum_phase_error_deg=2.0, minimum_valid_fraction=1.0, require_convergence=True)#
Bases:
objectDefine quantitative acceptance limits for one benchmark.
- Parameters:
maximum_normalized_rms (float, default=0.05) – Maximum complex root-sum-square error normalized by the reference.
maximum_amplitude_relative_error (float, default=0.05) – Maximum pointwise relative impedance-amplitude error.
maximum_phase_error_deg (float, default=2.0) – Maximum absolute circular phase error in degrees.
minimum_valid_fraction (float, default=1.0) – Minimum fraction of output values marked valid.
require_convergence (bool, default=True) – Require every solve in backend diagnostics to converge.
Examples
>>> limits = BenchmarkThresholds(maximum_phase_error_deg=1) >>> limits.maximum_phase_error_deg 1.0
- to_dict()#
Return JSON-compatible acceptance limits.
- Returns:
Versioned threshold state.
- Return type:
Examples
>>> BenchmarkThresholds().to_dict()["schema_version"] 1
- classmethod from_dict(data)#
Restore validated benchmark thresholds.
- Parameters:
data (mapping) – State returned by
to_dict().- Returns:
Restored limits.
- Return type:
Examples
>>> limits = BenchmarkThresholds(maximum_normalized_rms=0.1) >>> BenchmarkThresholds.from_dict(limits.to_dict()) == limits True
- class pycsamt.forward.maxwell.BenchmarkMetrics(normalized_rms, maximum_amplitude_relative_error, maximum_phase_error_deg, valid_fraction, converged)#
Bases:
objectStore errors measured against one analytic reference.
- Parameters:
normalized_rms (float) – Complex normalized root-sum-square error.
maximum_amplitude_relative_error (float) – Worst pointwise relative amplitude error.
maximum_phase_error_deg (float) – Worst circular phase difference.
valid_fraction (float) – Fraction of output values marked valid and finite.
converged (bool) – Whether all backend solves converged.
Examples
>>> BenchmarkMetrics(0.01, 0.02, 0.5, 1, True).converged True
- class pycsamt.forward.maxwell.BenchmarkOutcome(benchmark_name, benchmark_hash, backend_name, backend_version, passed, metrics, failures=())#
Bases:
objectRecord the auditable outcome of one backend benchmark.
- Parameters:
benchmark_name (str) – Stable case identity and full content digest.
benchmark_hash (str) – Stable case identity and full content digest.
backend_name (str) – Exact adapter identity from the result.
backend_version (str) – Exact adapter identity from the result.
passed (bool) – Whether every configured acceptance criterion passed.
metrics (BenchmarkMetrics) – Quantitative comparison with the reference.
Examples
>>> metrics = BenchmarkMetrics(0, 0, 0, 1, True) >>> outcome = BenchmarkOutcome( ... "half-space", "0" * 64, "demo", "1", True, metrics ... ) >>> outcome.passed True
- metrics: BenchmarkMetrics#
- to_dict()#
Return a JSON-compatible benchmark outcome.
- Returns:
Case identity, backend identity, metrics, and failures.
- Return type:
Examples
>>> metrics = BenchmarkMetrics(0, 0, 0, 1, True) >>> value = BenchmarkOutcome( ... "case", "0" * 64, "demo", "1", True, metrics ... ) >>> value.to_dict()["passed"] True
- class pycsamt.forward.maxwell.MaxwellBenchmark(name, description, problem, reference_impedance_v_a, thresholds=BenchmarkThresholds(maximum_normalized_rms=0.05, maximum_amplitude_relative_error=0.05, maximum_phase_error_deg=2.0, minimum_valid_fraction=1.0, require_convergence=True), tags=(), metadata=<factory>)#
Bases:
objectDefine one immutable problem and its expected impedance.
- Parameters:
name (str) – Stable identifier and scientific purpose.
description (str) – Stable identifier and scientific purpose.
problem (MaxwellProblem) – Exact solver input.
reference_impedance_v_a (complex ndarray) – Analytic reference with canonical problem output shape.
thresholds (BenchmarkThresholds, optional) – Quantitative acceptance criteria.
tags (sequence of str, optional) – Searchable labels such as
analyticandhalf-space.metadata (mapping, optional) – Finite JSON-compatible provenance.
Examples
Cases are normally built with
half_space_benchmark()orlayered_earth_benchmark().- problem: MaxwellProblem#
- thresholds: BenchmarkThresholds = BenchmarkThresholds(maximum_normalized_rms=0.05, maximum_amplitude_relative_error=0.05, maximum_phase_error_deg=2.0, minimum_valid_fraction=1.0, require_convergence=True)#
- property benchmark_hash: str#
Return a deterministic digest of case inputs and thresholds.
- Returns:
SHA-256 benchmark identity.
- Return type:
Examples
Benchmark hashes contain 64 hexadecimal characters.
- evaluate(result)#
Compare one canonical result with the analytic reference.
- Parameters:
result (ForwardResult) – Backend result for this exact problem.
- Returns:
Metrics and every failed acceptance criterion.
- Return type:
- Raises:
ValueError – If the result belongs to a different problem or output axes.
Examples
Exact references produce zero error when wrapped as backend results.
- run(backend)#
Execute and evaluate this case with a conforming backend.
- Parameters:
backend (MaxwellBackend) – Backend compatible with the benchmark problem.
- Returns:
Auditable validation outcome.
- Return type:
Examples
Backend exceptions propagate so infrastructure failures cannot be mistaken for numerical benchmark failures.
- class pycsamt.forward.maxwell.BenchmarkReport(outcomes)#
Bases:
objectAggregate ordered outcomes from one backend benchmark run.
- Parameters:
outcomes (sequence of BenchmarkOutcome) – Non-empty ordered outcomes from one backend version.
Examples
>>> metrics = BenchmarkMetrics(0, 0, 0, 1, True) >>> outcome = BenchmarkOutcome( ... "case", "0" * 64, "demo", "1", True, metrics ... ) >>> BenchmarkReport((outcome,)).passed True
- outcomes: tuple[BenchmarkOutcome, ...]#
- property passed: bool#
Return whether every benchmark passed.
- Returns:
Aggregate acceptance status.
- Return type:
Examples
A report fails if any contained outcome fails.
- pycsamt.forward.maxwell.half_space_impedance(resistivity_ohm_m, frequencies_hz, *, time_dependence='exp(+iwt)')#
Return analytic plane-wave impedance of a uniform half-space.
- Parameters:
resistivity_ohm_m (float) – Positive half-space resistivity.
frequencies_hz (array-like) – Positive frequencies.
time_dependence ({"exp(+iwt)", "exp(-iwt)"}) – Complex phasor convention.
- Returns:
sqrt(i*omega*mu0*rho)or its conjugate convention.- Return type:
ndarray of complex
Examples
>>> round(float(np.angle(half_space_impedance(100, 1), deg=True))) 45
- pycsamt.forward.maxwell.layered_earth_impedance(resistivity_ohm_m, thickness_m, frequencies_hz, *, time_dependence='exp(+iwt)')#
Return analytic 1-D MT impedance by upward layer recursion.
- Parameters:
resistivity_ohm_m (sequence of float) – Layer resistivities from surface to basal half-space.
thickness_m (sequence of float) – Thickness of every layer except the basal half-space.
frequencies_hz (array-like) – Positive evaluation frequencies.
time_dependence ({"exp(+iwt)", "exp(-iwt)"}) – Complex phasor convention.
- Returns:
Surface impedance in frequency order.
- Return type:
ndarray of complex
Examples
A single layer reduces exactly to the half-space expression:
>>> np.allclose( ... layered_earth_impedance([100], [], [1, 10]), ... half_space_impedance(100, [1, 10]), ... ) True
- pycsamt.forward.maxwell.half_space_benchmark(mesh, receivers, frequencies_hz, *, resistivity_ohm_m=100.0, components=('zxy', 'zyx'), time_dependence='exp(+iwt)', thresholds=None)#
Build a uniform-earth analytic benchmark.
- Parameters:
mesh (MaxwellMesh, ReceiverSet) – Solver geometry and observation locations.
receivers (MaxwellMesh, ReceiverSet) – Solver geometry and observation locations.
frequencies_hz (sequence of float) – Positive benchmark frequencies.
resistivity_ohm_m (float, default=100) – Uniform earth resistivity.
components (sequence of str, default=("zxy", "zyx")) – Requested components. Diagonal 3-D components are excluded because their analytic reference is zero and relative metrics are undefined.
time_dependence (str, default="exp(+iwt)") – Complex phasor convention.
thresholds (BenchmarkThresholds or None, optional) – Acceptance limits.
- Returns:
Executable half-space case.
- Return type:
Examples
>>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> receivers = ReceiverSet([[0.5, 0]], ["S"]) >>> half_space_benchmark(mesh, receivers, [1]).name 'half-space'
- pycsamt.forward.maxwell.layered_earth_benchmark(mesh, receivers, frequencies_hz, resistivity_ohm_m, thickness_m, *, components=('zxy', 'zyx'), time_dependence='exp(+iwt)', thresholds=None)#
Build a laterally uniform layered-earth benchmark.
- Parameters:
mesh (MaxwellMesh) – Solver geometry, observations, and frequencies.
receivers (ReceiverSet) – Solver geometry, observations, and frequencies.
frequencies_hz (Sequence[float]) – Solver geometry, observations, and frequencies.
resistivity_ohm_m (sequence of float) – Layer resistivities ending with a basal half-space.
thickness_m (sequence of float) – Finite-layer thicknesses. Every cumulative interface must coincide with a mesh z edge.
components (Sequence[str]) – Output components, phasor convention, and acceptance limits.
time_dependence (str) – Output components, phasor convention, and acceptance limits.
thresholds (BenchmarkThresholds | None) – Output components, phasor convention, and acceptance limits.
- Returns:
Executable analytic layered-earth case.
- Return type:
Examples
>>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> receivers = ReceiverSet([[0.5, 0]], ["S"]) >>> case = layered_earth_benchmark(mesh, receivers, [1], [10, 100], [1]) >>> case.name 'layered-earth'
- pycsamt.forward.maxwell.run_benchmarks(backend, benchmarks)#
Run an ordered benchmark collection with one backend version.
- Parameters:
backend (MaxwellBackend) – Backend under validation.
benchmarks (sequence of MaxwellBenchmark) – Non-empty cases with unique names.
- Returns:
Aggregate and per-case outcomes.
- Return type:
Examples
Backend and numerical exceptions propagate rather than becoming false benchmark failures.
- exception pycsamt.forward.maxwell.ExecutableNotFoundError#
Bases:
MaxwellAdapterErrorIndicate that a configured external solver executable is missing.
Examples
>>> isinstance(ExecutableNotFoundError("missing"), MaxwellAdapterError) True
- exception pycsamt.forward.maxwell.ExternalProcessError(message, attempts)#
Bases:
BackendExecutionErrorIndicate that every attempt to run an external solver process failed.
- Parameters:
message (str) – Human-readable summary, normally including the last attempt’s exit code and a tail of its captured stderr.
attempts (tuple of ExternalRunResult) – Every attempt made, in order, including the failing ones.
- Return type:
None
Examples
>>> error = ExternalProcessError("occam2d failed after 1 attempt", ()) >>> error.attempts ()
- class pycsamt.forward.maxwell.ExternalRunPolicy(executable, search_paths=(), timeout_s=None, max_attempts=1, retry_backoff_s=1.0, workdir=None, keep_workdir_on_failure=True, extra_env=<factory>, capture_output=True)#
Bases:
objectConfigure how an external solver executable is located and run.
- Parameters:
executable (str) – Executable name resolved via
PATHandsearch_paths, or an absolute/relative path to the external solver binary.search_paths (sequence of str, optional) – Additional directories checked, in order, after
PATHand before giving up.timeout_s (float or None, default=None) – Maximum wall-clock time allowed per attempt.
Nonedisables the per-attempt timeout.max_attempts (int, default=1) – Total attempts per solve, including the first. Values above one retry a failed or timed-out run.
retry_backoff_s (float, default=1.0) – Base delay before each retry; attempt n (n > 1) waits
retry_backoff_s * nseconds before relaunching.workdir (str or pathlib.Path or None, optional) – Fixed working directory reused across solves and owned by the caller; it is created if missing and never deleted by this adapter.
Nonecreates and deletes a private temporary directory per solve.keep_workdir_on_failure (bool, default=True) – Preserve a private temporary working directory (see
workdir) when every attempt fails, so its contents can be inspected. Has no effect whenworkdiris caller-supplied, since that directory is always preserved.extra_env (mapping of str to str, optional) – Extra environment variables merged over the current process environment for the subprocess only.
capture_output (bool, default=True) – Capture stdout/stderr for diagnostics instead of inheriting the parent process streams.
Examples
>>> policy = ExternalRunPolicy("occam2d", max_attempts=2, timeout_s=60.0) >>> policy.max_attempts, policy.timeout_s (2, 60.0)
- class pycsamt.forward.maxwell.ExternalRunResult(command, returncode, stdout, stderr, runtime_s, attempt, workdir)#
Bases:
objectRecord one external-process execution attempt.
- Parameters:
command (sequence of str) – Exact argv executed.
returncode (int) – Process exit status;
-1denotes a timeout.stdout (str) – Captured output; empty when
capture_outputwasFalseor the process timed out before producing output.stderr (str) – Captured output; empty when
capture_outputwasFalseor the process timed out before producing output.runtime_s (float) – Wall-clock duration of this attempt, in seconds.
attempt (int) – 1-based attempt number.
workdir (str or pathlib.Path) – Directory the process was launched from.
Examples
>>> result = ExternalRunResult(("occam2d",), 0, "done", "", 0.5, 1, ".") >>> result.success True
- property success: bool#
Return whether the process exited with status zero.
- Returns:
Trueonly for a normal, non-timed-out, zero exit status.- Return type:
Examples
>>> ExternalRunResult(("a",), 1, "", "", 0.0, 1, ".").success False
- tail(*, stream='stderr', lines=20)#
Return the last lines of captured stdout or stderr.
- Parameters:
stream ({"stderr", "stdout"}, default="stderr") – Which captured stream to summarize.
lines (int, default=20) – Maximum number of trailing lines returned.
- Returns:
Newline-joined tail, or an empty string when nothing was captured.
- Return type:
- Raises:
ValueError – If
streamis not"stderr"or"stdout".
Examples
>>> result = ExternalRunResult( ... ("a",), 1, "", "line1\nline2\nline3", 0.0, 1, "." ... ) >>> result.tail(lines=2) 'line2\nline3'
- to_dict()#
Return a JSON-compatible representation of this attempt.
- Returns:
Command, exit status, captured output, timing, and attempt number. Useful for batch-run failure manifests.
- Return type:
Examples
>>> ExternalRunResult(("a",), 0, "ok", "", 1.0, 1, ".").to_dict()[ ... "success" ... ] True
- class pycsamt.forward.maxwell.BaseExternalMaxwellAdapter(capabilities, run_policy, policy=None)#
Bases:
BaseMaxwellAdapterBase class for adapters that run a trusted external solver process.
Concrete adapters wrap file-based external tools (for example ModEM, Occam2D, or MARE2DEM) launched as subprocesses rather than called as an in-process Python function. This class owns the shared, solver-independent mechanics: working-directory lifecycle, executable resolution, subprocess execution with timeout and retry, and captured diagnostics. A concrete subclass implements only three solver-specific extension points:
_prepare_inputs(problem, workdir)Write the external tool’s input files for
problemintoworkdirand return a context object (any value) carried through to the other two extension points._build_command(problem, workdir, executable, context)Return the argv sequence that runs the external tool against the files written by
_prepare_inputs._parse_result(problem, workdir, run_result, context)Read the external tool’s output files from
workdirand return a canonicalForwardResult.run_resultis the successfulExternalRunResult.
- Parameters:
capabilities (BackendCapabilities) – Immutable declaration for the exact external solver version.
run_policy (ExternalRunPolicy) – Executable resolution, timeout, retry, and working-directory rules.
policy (AdapterPolicy or None, optional) – Solver-independent result acceptance policy (convergence, residual, and validity checks applied after
_parse_resultreturns).
Examples
A minimal concrete adapter (see the module docstring for context) would look like:
class DemoExternalAdapter(BaseExternalMaxwellAdapter): def _prepare_inputs(self, problem, workdir): (workdir / "input.txt").write_text(str(problem.problem_hash)) return None def _build_command(self, problem, workdir, executable, context): return [str(executable), "input.txt", "output.txt"] def _parse_result( self, problem, workdir, run_result, context ): ... # read workdir / "output.txt" and build a ForwardResult
- property run_policy: ExternalRunPolicy#
Return the immutable external-process execution policy.
- Returns:
Executable resolution, timeout, retry, and working-directory rules used by every
solve()call.- Return type:
Examples
See
BaseExternalMaxwellAdapterfor a complete subclass example.
- resolve_executable()#
Resolve this adapter’s configured executable to a concrete path.
- Returns:
Resolved executable path.
- Return type:
- Raises:
ExecutableNotFoundError – If the executable cannot be found on
PATHor inExternalRunPolicy.search_paths.
Examples
See
BaseExternalMaxwellAdapterfor a complete subclass example.
- pycsamt.forward.maxwell.resolve_executable(name_or_path, *, search_paths=())#
Resolve an external solver executable to a concrete file path.
- Parameters:
- Returns:
Resolved, existing executable path.
- Return type:
- Raises:
ExecutableNotFoundError – If the executable cannot be found as a direct path, on
PATH, or in any ofsearch_paths.
Examples
>>> resolve_executable("nope") Traceback (most recent call last): ... pycsamt.forward.maxwell.external.ExecutableNotFoundError: ...
- pycsamt.forward.maxwell.probe_executable_version(executable, *, version_args=('--version',), version_pattern=None, timeout_s=5.0)#
Best-effort external solver version string for provenance.
- Parameters:
executable (str) – Resolved or resolvable executable path or name.
version_args (sequence of str, default=("--version",)) – Arguments appended to the executable when probing.
version_pattern (str, optional) – Regular expression whose first capture group extracts the version from the combined stdout/stderr. When omitted, the first non-empty output line is returned verbatim.
timeout_s (float, default=5.0) – Maximum time allowed for the probe process.
- Returns:
Detected version string, or
Nonewhen the probe process cannot be started, times out, or produces no matching output. This function never raises for an ordinary probe failure; a concrete adapter must still supply a non-emptybackend_versiontoForwardResultwhen this returnsNone.- Return type:
str or None
Examples
>>> probe_executable_version("does-not-exist-xyz") is None True
- pycsamt.forward.maxwell.make_availability_probe(name_or_path, *, search_paths=())#
Build a zero-argument availability probe for backend registration.
- Parameters:
name_or_path (str) – Executable resolved the same way as
resolve_executable().search_paths (sequence of str, optional) – Extra directories checked before giving up.
- Returns:
Zero-argument function returning
(available, reason), directly usable asavailability_probe.- Return type:
callable
Examples
>>> probe = make_availability_probe("does-not-exist-xyz") >>> probe()[0] False
- class pycsamt.forward.maxwell.MT2DAdapter(*, version='1.0', policy=None, verbose=False)#
Bases:
BaseMaxwellAdapterValidated 2-D MT adapter over the in-repo finite-difference solver.
- Parameters:
version (str, default="1.0") – Adapter version reported in every
ForwardResult. Bump this when the translation logic in this module changes in a way that could alter numerical output.policy (AdapterPolicy or None, optional) – Solver-independent result acceptance policy. Defaults to
AdapterPolicy.verbose (bool, default=False) – Forwarded to
MT2DForward; prints per-frequency progress when true.
Examples
>>> import numpy as np >>> from pycsamt.forward.maxwell import ( ... MaxwellMesh, ... MaxwellProblem, ... ReceiverSet, ... ) >>> mesh = MaxwellMesh( ... np.linspace(0, 10_000, 41), np.linspace(0, 5_000, 31) ... ) >>> problem = MaxwellProblem( ... mesh, ... np.full(mesh.shape, 1.0 / 100.0), ... [10.0, 1.0], ... ReceiverSet([[5_000.0, 0.0]], ["S00"]), ... ("zxy", "zyx"), ... ) >>> adapter = MT2DAdapter(verbose=False) >>> result = adapter.solve(problem) >>> result.shape (1, 2, 2)
- assess(problem)#
Assess a problem, adding this solver’s surface-only checks.
- Parameters:
problem (MaxwellProblem) – Candidate simulation problem.
- Returns:
The generic capability report from
assess(), extended with two solver-specific checks: every receiver must be at the surface, and the permeability must be the vacuum value the wrapped solver hardcodes.- Return type:
Examples
See
MT2DAdapterfor a complete solve example; a problem with a buried receiver is rejected before the solver runs.
- pycsamt.forward.maxwell.register_mt2d_backend(*, replace=False)#
Register
MT2DAdapterin the process-wide backend registry.- Parameters:
replace (bool, default=False) – Explicitly replace an existing
"mt2d"registration.- Return type:
None
Examples
>>> from pycsamt.forward.maxwell import create_backend, list_backends >>> register_mt2d_backend(replace=True) >>> "mt2d" in list_backends() True >>> create_backend("mt2d").capabilities.name 'mt2d'
- class pycsamt.forward.maxwell.MT3DAdapter(*, version='1.0-research', policy=None, max_cells=6000)#
Bases:
BaseMaxwellAdapterResearch-only 3-D MT adapter (see module docstring for scope).
- Parameters:
version (str, default="1.0-research") – Adapter version reported in every
ForwardResult.policy (AdapterPolicy or None, optional) – Solver-independent result acceptance policy.
max_cells (int, default=6000) – Safety ceiling on total mesh cells (
maximum_cells). A direct sparse solve becomes impractically slow well before typical production 3-D mesh sizes; seedocs/source/development/adr/AI-INVERSION-M6-3D-ADR.md. Raise this only if you have confirmed the resulting solve time is acceptable for your use.
Examples
>>> import numpy as np >>> from pycsamt.forward.maxwell import ( ... MaxwellMesh, ... MaxwellProblem, ... ReceiverSet, ... ) >>> mesh = MaxwellMesh( ... np.linspace(0, 4000, 9), ... np.linspace(0, 3000, 11), ... np.linspace(0, 4000, 9), ... ) >>> problem = MaxwellProblem( ... mesh, ... np.full(mesh.shape, 1.0 / 100.0), ... [1.0], ... ReceiverSet([[2000.0, 2000.0, 0.0]], ["S00"]), ... ("zxy", "zyx"), ... ) >>> result = MT3DAdapter().solve(problem) >>> result.shape (1, 1, 2)
- assess(problem)#
Assess a problem, adding this solver’s research-only checks.
- Parameters:
problem (MaxwellProblem) – Candidate simulation problem.
- Returns:
The generic capability report from
assess(), extended with surface-receiver, horizontal-bounds, and vacuum-permeability checks.- Return type:
Examples
See
MT3DAdapterfor a complete solve example.
- class pycsamt.forward.maxwell.ModEm3DAdapter(*, config=None, run_policy=None, policy=None, predicted_data_filename=None, version='modem-v6.2.6-adapter-1.0')#
Bases:
BaseExternalMaxwellAdapterAdapter wrapping the external ModEM 3-D forward solver.
- Parameters:
config (ModEmConfig or None, optional) – ModEM configuration (executable names, MPI settings, units, sign convention). Defaults to
ModEmConfig(mode="3d").run_policy (ExternalRunPolicy or None, optional) – Executable resolution, timeout, retry, and working-directory rules. When omitted, a policy is built from
config(config.mpi_commandifconfig.use_mpielseconfig.binary_3d), with the vendoredpycsamt/models/modem/_source/3Ddirectory as a search path fallback.policy (AdapterPolicy or None, optional) – Solver-independent result acceptance policy.
predicted_data_filename (str or None, optional) – Exact name of ModEM’s predicted-response output file within the run’s working directory. When omitted, the adapter auto-detects it as the only
*.datfile other than the one it wrote itself, and raises a clear error if that is ambiguous (zero or more than one candidate) — set this explicitly once you know your ModEM build’s naming convention.version (str, default="modem-v6.2.6-adapter-1.0") – Adapter version reported in every
ForwardResult. Encodes both the vendored ModEM release this adapter targets and this adapter code’s own revision.
Examples
>>> from pycsamt.forward.maxwell import ExternalRunPolicy >>> adapter = ModEm3DAdapter( ... run_policy=ExternalRunPolicy("does-not-exist-xyz") ... ) >>> adapter.capabilities.name 'modem3d'
- assess(problem)#
Assess a problem, adding this adapter’s mapping checks.
- Parameters:
problem (MaxwellProblem) – Candidate simulation problem.
- Returns:
The generic capability report from
assess(), extended with surface-receiver, horizontal-bounds, surface-aligned-mesh, and vacuum-permeability checks.- Return type:
Examples
See
ModEm3DAdapterfor construction; a problem with a buried receiver is rejected before any file is written.
- pycsamt.forward.maxwell.register_modem3d_backend(*, config=None, replace=False)#
Register
ModEm3DAdapterin the process-wide registry.- Parameters:
config (ModEmConfig or None, optional) – Configuration used both to build the registered adapter and to derive its availability probe (whether the configured executable can currently be resolved).
replace (bool, default=False) – Explicitly replace an existing
"modem3d"registration.
- Return type:
None
Examples
>>> register_modem3d_backend(replace=True) >>> from pycsamt.forward.maxwell import list_backends >>> "modem3d" in list_backends() True >>> list_backends()["modem3d"]["available"] False
- class pycsamt.forward.maxwell.Mare2DEMAdapter(*, config=None, run_policy=None, policy=None, std_err=1.0, response_filename=None, version='mare2dem-adapter-1.0')#
Bases:
BaseExternalMaxwellAdapterAdapter wrapping the external MARE2DEM 2.5-D triangular-mesh solver.
- Parameters:
config (Mare2DEMConfig or None, optional) – MARE2DEM configuration (executable name, MPI settings). Defaults to
Mare2DEMConfig().run_policy (ExternalRunPolicy or None, optional) – Executable resolution, timeout, retry, and working-directory rules. Built from
configwhen omitted (config.mpi_commandifconfig.use_mpielseconfig.binary).policy (AdapterPolicy or None, optional) – Solver-independent result acceptance policy.
std_err (float, default=1.0) – Placeholder standard error written for every forward-request DATA row (MARE2DEM’s file format requires a nonzero value even though it is unused for a zero-iteration forward evaluation).
response_filename (str or None, optional) – Exact name of MARE2DEM’s predicted-response output file. When omitted, the adapter globs the working directory for a
*.respfile, falling back to*_MARE2DEM.emdata, and raises a clear error if that is ambiguous.version (str, default="mare2dem-adapter-1.0") – Adapter version reported in every
ForwardResult.
Examples
>>> from pycsamt.forward.maxwell import ExternalRunPolicy >>> adapter = Mare2DEMAdapter( ... run_policy=ExternalRunPolicy("does-not-exist-xyz") ... ) >>> adapter.capabilities.name 'mare2dem'
- assess(problem)#
Assess a problem, adding this adapter’s mesh/region checks.
- Parameters:
problem (TriProblem) – Candidate simulation problem.
- Returns:
The generic capability report from
assess(), extended with a triangular-mesh-type check and a per-region uniform-conductivity check.- Return type:
Examples
See
Mare2DEMAdapterfor construction; a problem whose conductivity varies within one mesh region is rejected before any file is written.
- pycsamt.forward.maxwell.register_mare2dem_backend(*, config=None, replace=False)#
Register
Mare2DEMAdapterin the process-wide registry.- Parameters:
config (Mare2DEMConfig or None, optional) – Configuration used both to build the registered adapter and to derive its availability probe (whether the configured executable can currently be resolved).
replace (bool, default=False) – Explicitly replace an existing
"mare2dem"registration.
- Return type:
None
Examples
>>> register_mare2dem_backend(replace=True) >>> from pycsamt.forward.maxwell import list_backends >>> "mare2dem" in list_backends() True
- class pycsamt.forward.maxwell.TriFEM2DAdapter(*, version='1.0', policy=None, node_tolerance_m=0.001, verbose=False)#
Bases:
BaseMaxwellAdapterIn-house 2-D triangular-mesh MT FEM adapter (research-only).
- Parameters:
version (str, default="1.0") – Adapter version reported in every
ForwardResult.policy (AdapterPolicy or None, optional) – Solver-independent result acceptance policy.
node_tolerance_m (float, default=1e-3) – Maximum distance between a receiver and its assumed mesh node.
verbose (bool, default=False) – Forwarded to
Tri2DFEMForward.
Examples
>>> import numpy as np >>> from pycsamt.forward.maxwell import ReceiverSet >>> from pycsamt.forward.maxwell.contracts_tri import TriMesh, TriProblem >>> nodes = [[-1000, 0], [1000, 0], [1000, 1000], [-1000, 1000]] >>> mesh = TriMesh( ... nodes, [[0, 1, 2], [0, 2, 3]], ... boundary_segments=[[0, 1], [1, 2], [2, 3], [3, 0]], ... ) >>> problem = TriProblem( ... mesh, np.full(2, 0.01), [1.0], ReceiverSet([[-1000, 0]], ["S00"]) ... ) >>> adapter = TriFEM2DAdapter() >>> adapter.capabilities.name 'trifem2d'
- assess(problem)#
Assess a problem, adding this solver’s mesh/receiver checks.
- Parameters:
problem (TriProblem) – Candidate simulation problem.
- Returns:
The generic capability report from
assess(), extended with boundary-segment, receiver-node-matching, surface, and permeability checks.- Return type:
Examples
See
TriFEM2DAdapterfor construction; a receiver not coinciding with any mesh node is rejected before the solver runs.
- pycsamt.forward.maxwell.register_trifem2d_backend(*, replace=False)#
Register
TriFEM2DAdapterin the process-wide backend registry.- Parameters:
replace (bool, default=False) – Explicitly replace an existing
"trifem2d"registration.- Return type:
None
Examples
>>> from pycsamt.forward.maxwell import create_backend, list_backends >>> register_trifem2d_backend(replace=True) >>> "trifem2d" in list_backends() True >>> create_backend("trifem2d").capabilities.name 'trifem2d'
- pycsamt.forward.maxwell.build_graded_tri_mesh(x_range_m, z_range_m, station_x_m, *, surface_cell_m, growth_rate=1.3, max_cell_m=None, min_angle=30.0, topo_x_m=None, topo_z_m=None)#
Build a real, graded, quality FEM mesh via Shewchuk’s Triangle.
- Parameters:
x_range_m ((float, float)) – Domain extent.
z_range_m[0]should be 0 (surface) whentopo_z_mis omitted; ignored (the top follows the topography instead) when it is given.z_range_m[1](the domain bottom) must always be deeper than everytopo_z_msample.z_range_m ((float, float)) – Domain extent.
z_range_m[0]should be 0 (surface) whentopo_z_mis omitted; ignored (the top follows the topography instead) when it is given.z_range_m[1](the domain bottom) must always be deeper than everytopo_z_msample.station_x_m (array-like) – Receiver x-positions; included as explicit PSLG vertices so every receiver sits exactly on a mesh node. Placed at
z=0unlesstopo_z_mis given, in which case each station’s z is interpolated from the topography polyline instead.topo_x_m (array-like, optional) – Topography polyline (z positive down, so a ridge is negative) sampled at
topo_x_m. When given, the mesh’s top boundary follows this polyline (merged withstation_x_m, sorted and deduplicated by x, each station’s elevation interpolated from it) instead of a flat surface. Both must be given together, the same length, and finite. This only builds the mesh; using stations away fromz=0for a real solve also needsTriFEM2DAdapter’s local-surface-aware boundary conditions (already the default there – see that module’s own docstring).topo_z_m (array-like, optional) – Topography polyline (z positive down, so a ridge is negative) sampled at
topo_x_m. When given, the mesh’s top boundary follows this polyline (merged withstation_x_m, sorted and deduplicated by x, each station’s elevation interpolated from it) instead of a flat surface. Both must be given together, the same length, and finite. This only builds the mesh; using stations away fromz=0for a real solve also needsTriFEM2DAdapter’s local-surface-aware boundary conditions (already the default there – see that module’s own docstring).surface_cell_m (float) – Target triangle edge length at a station (the finest part of the mesh). Smaller values give a finer, more expensive mesh near the receivers. Choose this relative to skin depth for FEM solver accuracy, not just picture quality: a first layer around ~0.03-0.05 skin depths (at the highest simulated frequency) is what
TriFEM2DAdapter’s own analytic half-space benchmark needs to stay under 5% relative impedance error (see that module’s “Station field extraction” docstring section for the same sensitivity on a structured mesh).growth_rate (float, default=1.3) – Geometric growth factor of the target edge length per
surface_cell_mof distance from the nearest station. Must be greater than 1 (grading away from the receivers, never toward them).max_cell_m (float or None, optional) – Upper bound on the graded target edge length, reached far from every station / at depth. Defaults to
25 * surface_cell_m.min_angle (float, default=30.0) – Minimum triangle angle in degrees, Triangle’s own quality constraint (same default as
pycsamt.models.mare2dem.triangle_exec.run_triangle()).
- Returns:
Graded mesh with one region per triangle (
region_ids = 1..n_triangles, matching the oldbuild_delaunay_meshcontract soMare2DEMAdapter’s per-triangle-region convention still holds), ready for a per-triangle heterogeneous resistivity field.- Return type:
- Raises:
ValueError – If the ranges are not increasing, a size parameter is non-positive, or
station_x_mis empty or outsidex_range_m.
Examples
>>> mesh = build_graded_tri_mesh( ... (0.0, 1000.0), (0.0, 500.0), [200.0, 500.0, 800.0], ... surface_cell_m=20.0, ... ) >>> mesh.n_triangles > 0 True >>> sorted(mesh.region_ids.tolist()) == list( ... range(1, mesh.n_triangles + 1) ... ) True
- exception pycsamt.forward.maxwell.BatchAbortedError(report)#
Bases:
RuntimeErrorRaised when
BatchPolicy.stop_on_first_failureaborts early.- Parameters:
report (BatchReport) – Partial report covering every problem resolved before the abort; every problem submitted after the triggering failure (in the sequential case) was never attempted.
- Return type:
None
Examples
>>> report = BatchReport(1, (), (), FailureManifest()) >>> error = BatchAbortedError(report) >>> error.report is report True
- class pycsamt.forward.maxwell.BatchPolicy(max_attempts=1, retry_backoff_s=1.0, retry_on=(<class 'pycsamt.forward.maxwell.adapters.BackendExecutionError'>, <class 'pycsamt.forward.maxwell.adapters.SolverConvergenceError'>), stop_on_first_failure=False, max_workers=1)#
Bases:
objectConfigure retries and concurrency for
solve_batch().- Parameters:
max_attempts (int, default=1) – Attempts per problem, including the first. Values above one retry a solve that raised an exception in
retry_on.retry_backoff_s (float, default=1.0) – Base delay before each retry; attempt n (n > 1) waits
retry_backoff_s * nseconds before the next attempt.retry_on (tuple of exception types, optional) – Exception types treated as transient and worth retrying. Defaults to
BackendExecutionErrorandSolverConvergenceError(which coversExternalProcessErroras a subclass). Anything else, includingIncompatibleProblemErrorandInvalidBackendResultError, is recorded as a terminal failure after its first occurrence. Note thatBaseMaxwellAdapterwraps ordinary exceptions raised inside a solve intoBackendExecutionErrorby default (its ownAdapterPolicy.wrap_backend_exceptions), so a deterministic bug in a backend’s own code is retried like any otherBackendExecutionErrorunless that adapter was built withwrap_backend_exceptions=False.stop_on_first_failure (bool, default=False) – Raise
BatchAbortedErroras soon as any problem exhausts its attempts, instead of recording it and continuing. Withmax_workers > 1this only stops further submissions; futures already in flight are still allowed to finish.max_workers (int, default=1) – Number of solves run concurrently in a thread pool. Values above one only help when the backend releases the GIL during its work (true of scipy sparse solves and of any
BaseExternalMaxwellAdapter, which spends most of its time waiting on a subprocess).
Examples
>>> policy = BatchPolicy(max_attempts=3, retry_backoff_s=0.5) >>> policy.max_attempts 3
- class pycsamt.forward.maxwell.BatchReport(total, solved, cache_hits, failed)#
Bases:
objectSummarize one
solve_batch()run.- Parameters:
total (int) – Number of problems submitted to the batch. Can exceed
len(solved) + len(failed)whenBatchPolicy.stop_on_first_failureended the run before every problem was attempted.solved (tuple of str) – Problem hashes with a valid result, whether freshly computed or already cached.
cache_hits (tuple of str) – Subset of
solvedthat were already present in the cache (skipped re-solving). Empty when no cache was used.failed (FailureManifest) – Every problem that exhausted its attempts.
Examples
>>> report = BatchReport(1, ("a" * 64,), (), FailureManifest()) >>> report.success_fraction 1.0
- failed: FailureManifest#
- class pycsamt.forward.maxwell.FailureManifest(failures=())#
Bases:
objectOrdered, JSON-persistable record of every terminal batch failure.
- Parameters:
failures (sequence of ProblemFailure, optional) – Failures in the order they were recorded. Problem hashes must be unique within one manifest.
Examples
>>> failure = ProblemFailure("a" * 64, 1, "X", "boom") >>> manifest = FailureManifest((failure,)) >>> len(manifest), bool(manifest) (1, True)
- failures: tuple[ProblemFailure, ...] = ()#
- property hashes: frozenset[str]#
Return every failed problem’s hash.
Examples
>>> failure = ProblemFailure("a" * 64, 1, "X", "boom") >>> "a" * 64 in FailureManifest((failure,)).hashes True
- to_dict()#
Return a JSON-serializable, schema-versioned representation.
- Returns:
Every failure, in recorded order.
- Return type:
Examples
>>> FailureManifest().to_dict()["schema_version"] 1
- classmethod from_dict(data)#
Restore a validated manifest.
- Parameters:
data (mapping) – State previously returned by
to_dict().- Returns:
Restored manifest.
- Return type:
- Raises:
ValueError – If the schema version is unsupported.
Examples
>>> state = FailureManifest().to_dict() >>> FailureManifest.from_dict(state).failures ()
- to_json_file(path)#
Write this manifest as indented, deterministic JSON.
- Parameters:
path (str or pathlib.Path) – Destination file.
- Returns:
The destination path.
- Return type:
Examples
>>> from tempfile import TemporaryDirectory >>> failure = ProblemFailure("a" * 64, 1, "X", "boom") >>> manifest = FailureManifest((failure,)) >>> with TemporaryDirectory() as directory: ... target = Path(directory) / "failures.json" ... _ = manifest.to_json_file(target) ... restored = FailureManifest.from_json_file(target) >>> restored == manifest True
- classmethod from_json_file(path)#
Load a manifest written by
to_json_file().- Parameters:
path (str or pathlib.Path) – Source file.
- Returns:
Restored manifest.
- Return type:
Examples
See
to_json_file()for a complete round trip.
- class pycsamt.forward.maxwell.ProblemFailure(problem_hash, attempts, error_type, message)#
Bases:
objectRecord one problem’s terminal solve failure.
- Parameters:
problem_hash (str) – Identity of the
MaxwellProblemthat failed.attempts (int) – Number of attempts made before this failure was recorded.
error_type (str) – Exception class name, for quick triage without deserializing.
message (str) – Human-readable exception message.
Examples
>>> failure = ProblemFailure("a" * 64, 2, "BackendExecutionError", "x") >>> failure.attempts 2
- to_dict()#
Return a JSON-serializable representation.
- Returns:
Problem identity, attempt count, and exception details.
- Return type:
Examples
>>> ProblemFailure("a" * 64, 1, "X", "y").to_dict()["attempts"] 1
- classmethod from_dict(data)#
Restore a validated failure record.
- Parameters:
data (mapping) – State previously returned by
to_dict().- Returns:
Restored, validated record.
- Return type:
Examples
>>> state = ProblemFailure("a" * 64, 1, "X", "y").to_dict() >>> ProblemFailure.from_dict(state).error_type 'X'
- pycsamt.forward.maxwell.solve_batch(problems, backend, *, cache=None, policy=None, on_result=None, on_failure=None)#
Solve many problems robustly, with retries and a failure manifest.
- Parameters:
problems (iterable of MaxwellProblem) – Problems to solve. Consumed fully before returning; order is preserved in the returned report only through per-problem hashes, not positionally.
backend (MaxwellBackend) – Conforming backend used for every problem.
cache (MaxwellResultCache or None, optional) – When given, problems already cached are skipped (resumability) and freshly computed results are written back to it.
policy (BatchPolicy or None, optional) – Retry, concurrency, and abort configuration. Defaults to
BatchPolicy.on_result (callable, optional) – Invoked with
(problem, result)for every problem that succeeds, in completion order. Useful for streaming results into a dataset without holding them all in memory.on_failure (callable, optional) – Invoked with
(problem, failure)for every problem that exhausts its attempts, in completion order.
- Returns:
Totals, solved/cache-hit problem hashes, and a
FailureManifestof every terminal failure.- Return type:
- Raises:
TypeError – If
backendorpolicyhas the wrong type.BatchAbortedError – If
policy.stop_on_first_failureis set and any problem exhausts its attempts.
Examples
>>> import numpy as np >>> from pycsamt.forward.maxwell import ( ... CallableMaxwellAdapter, ... BackendCapabilities, ... ForwardResult, ... MaxwellMesh, ... MaxwellProblem, ... ReceiverSet, ... SolverDiagnostics, ... ) >>> mesh = MaxwellMesh([0, 1, 2], [0, 1, 2]) >>> problem = MaxwellProblem( ... mesh, np.ones((2, 2)), [1], ReceiverSet([[0.5, 0]], ["S"]) ... ) >>> def solver(value): ... diagnostics = SolverDiagnostics([[True]], [[0]], [[0]], 0) ... return ForwardResult( ... value.problem_hash, ... value.frequencies_hz, ... value.receivers.names, ... value.components, ... [[[1j, 1j]]], ... None, ... "demo", ... "1", ... diagnostics, ... ) >>> cap = BackendCapabilities("demo", "1", (2,), ("zxy", "zyx")) >>> backend = CallableMaxwellAdapter(cap, solver) >>> report = solve_batch([problem], backend) >>> report.success_fraction 1.0
Maxwell Modules#
|
Solver-neutral contracts for frequency-domain Maxwell simulations. |
|
Construct solver meshes from geological models and topography. |
|
Validated execution layer for solver-specific Maxwell adapters. |
|
Capability checks and lazy registration for Maxwell solver backends. |
|
Canonical analytic benchmarks for Maxwell backend validation. |
|
Validated adapter for the in-repo 2-D MT finite-difference solver. |
|
Research-only, small-grid 3-D MT finite-difference adapter. |
|
External ModEM adapter: the trusted production 3-D backend. |
|
Adapter foundation for trusted external Maxwell solver executables. |
|
Robust, resumable batch solving with retries and failure manifests. |
|
Content-addressed cache for canonical Maxwell forward results. |