2.20. pycsamt.forward#

Forward-model configuration, synthetic models, 1-D/2-D/3-D EM forward responses, grids, noise models, batch execution, and plotting helpers.

Physics-based EM forward solvers and synthetic training-data generation.

The package provides 1-D electromagnetic forward solvers and synthetic dataset generation for AI/ML training.

This package has no machine-learning dependencies — only NumPy and SciPy are required. It provides the building blocks consumed by pycsamt.ai for training data generation.

2.20.1. Submodules#

em1d

Three 1-D solvers: MT1DForward, TEM1DForward, CSAMT1DForward.

synthetic

LayeredModel — the central data object for all 1-D models, with random, blocky, smooth, and from_geology constructors.

noise

Noise models for synthetic data: GaussianNoise, FieldRealisticNoise, MultiplicativeNoise.

batch

generate_dataset() — parallelised (model, response) pair generation for large training sets.

2.20.2. Quick start#

>>> import numpy as np
>>> from pycsamt.forward import MT1DForward, LayeredModel
>>> model = LayeredModel([100, 10, 500], [300, 800])
>>> resp = MT1DForward(np.logspace(-3, 4, 30)).run(model)
>>> resp.rho_a.shape
(30,)
class pycsamt.forward.MT1DForward(freqs)#

Bases: _Base1DForward

1-D magnetotelluric forward solver (plane-wave, isotropic earth).

Uses the exact Wait (1954) recursive impedance algorithm. Runs in O(n_freq × n_layers) time — fast enough for generating millions of synthetic training samples.

Parameters:

freqs (array-like) – Frequencies [Hz] at which to evaluate the response. Typical range: 1e-4 – 1e5 Hz for MT/AMT.

Examples

>>> import numpy as np
>>> from pycsamt.forward.em1d import MT1DForward
>>> from pycsamt.forward.synthetic import LayeredModel
>>> freqs = np.logspace(-3, 4, 30)
>>> model = LayeredModel(resistivity=[100, 10, 500], thickness=[500, 1000])
>>> resp = MT1DForward(freqs).run(model)
>>> resp.rho_a.shape
(30,)
run(model)#

Compute the MT 1-D response for model.

Parameters:

model (LayeredModel) – Input earth model.

Returns:

Fields populated: z, rho_a, phase, freqs.

Return type:

ForwardResponse

class pycsamt.forward.TEM1DForward(times, loop_radius=50.0, moment=1.0, n_freqs=64, n_lam=100)#

Bases: _Base1DForward

1-D central-loop TEM forward solver (step-off waveform).

Computes the vertical magnetic field via empymod’s validated digital-linear-filter Hankel and Fourier transforms (Werthmüller, 2017), rather than a hand-rolled quadrature.

Note

An earlier from-scratch implementation (fixed-order Gauss-Legendre Hankel transform + naive trapezoidal cosine transform) did not converge at realistic time ranges – its own frequency-domain kernel grew unboundedly at high frequency instead of decaying, and refining either quadrature’s resolution changed the answer’s sign, not just its accuracy, at several requested times. That implementation is gone; empymod is a peer-reviewed, independently validated EM modelling library built specifically for this class of problem, and is verified byte-for-byte against Ward & Hohmann (1988)’s own closed-form half-space dBz/dt formula (their eq. 4.70) as part of this module’s test suite.

The loop itself is represented as a small tangential electric bipole at the loop radius, scaled by the loop’s circumference – the same “loop as a scaled dipole segment” construction empymod uses to reproduce Ward & Hohmann’s own central-loop figures (4.7-4.8), valid by the axisymmetry of a horizontal circular loop.

Parameters:
  • times (array-like) – Measurement times [s] for the step-off response. Typical range: 1e-6 – 1e-2 s.

  • loop_radius (float) – Transmitter loop radius [m]. Default 50 m.

  • moment (float) – Transmitter magnetic moment [A·m²]. Default 1 A·m².

  • n_freqs (int) – Unused. Accepted only so existing callers (e.g. pycsamt.inversion.backends.builtin.Builtin1DBackend, which threads backend_options straight through) do not break; empymod’s own filters pick their resolution automatically.

  • n_lam (int) – Unused. Accepted only so existing callers (e.g. pycsamt.inversion.backends.builtin.Builtin1DBackend, which threads backend_options straight through) do not break; empymod’s own filters pick their resolution automatically.

References

Ward & Hohmann (1988), Electromagnetic Methods in Applied Geophysics, Vol. 1.

Werthmüller, D. (2017). An open-source full 3D electromagnetic modeler for 1D VTI media in Python: empymod. Geophysics, 82(6), WB9-WB19.

run(model)#

Compute the TEM 1-D step-off response for model.

Parameters:

model (LayeredModel) – Input earth model.

Returns:

Fields populated: dBz_dt, hz_freq, times.

Return type:

ForwardResponse

class pycsamt.forward.CSAMT1DForward(freqs, source_offset=None, dipole_length=1000.0)#

Bases: _Base1DForward

1-D controlled-source AMT forward solver.

In the far-field (source–receiver offset r ≫ skin depth δ) the CSAMT response approximates the MT plane-wave response. A first- order near-field correction factor after Zonge & Hughes (1991) is applied when source_offset is supplied.

Parameters:
  • freqs (array-like) – Frequencies [Hz].

  • source_offset (float or None) – Source–receiver distance [m]. If None, the far-field approximation is used without correction.

  • dipole_length (float) – Electric dipole length [m]. Default 1000 m.

References

Zonge, K.L. & Hughes, L.J. (1991). Controlled source audio- frequency magnetotellurics. In Electromagnetic Methods in Applied Geophysics, 2B, 713-809.

run(model)#

Compute the CSAMT 1-D response for model.

Returns:

Fields populated: z, rho_a, phase.

Return type:

ForwardResponse

class pycsamt.forward.ForwardResponse(method='MT1D', freqs=None, times=None, z=None, rho_a=None, phase=None, dBz_dt=None, hz_freq=None, model=None)#

Bases: object

Container for the output of a 1-D forward solver.

Parameters:
  • method (str) – Solver identifier ('MT1D', 'TEM1D', 'CSAMT1D').

  • freqs (ndarray or None) – Frequencies in Hz (MT/CSAMT).

  • times (ndarray or None) – Times in seconds (TEM).

  • z (ndarray or None) – Complex surface impedance (n_freq,) for MT/CSAMT [V/A].

  • rho_a (ndarray or None) – Apparent resistivity (n_freq,) [Ω·m].

  • phase (ndarray or None) – Impedance phase (n_freq,) [degrees, 0–90° for normal models].

  • dBz_dt (ndarray or None) – dBz/dt step-off response (n_times,) [T/s] for TEM.

  • hz_freq (ndarray or None) – Complex frequency-domain H_z (n_freq,) for TEM.

  • model (LayeredModel or None) – The input model that produced this response.

method: str = 'MT1D'#
freqs: ndarray | None = None#
times: ndarray | None = None#
z: ndarray | None = None#
rho_a: ndarray | None = None#
phase: ndarray | None = None#
dBz_dt: ndarray | None = None#
hz_freq: ndarray | None = None#
model: object = None#
to_array(*, log_rho=True, include_phase=True)#

Flatten to a 1-D feature vector for ML input.

For MT/CSAMT returns [log10(rho_a), phase_deg] concatenated (length 2 × n_freq); for TEM returns log10(|dBz_dt|) (length n_times).

Parameters:
  • log_rho (bool) – Apply log₁₀ to apparent resistivity (recommended).

  • include_phase (bool) – Include phase alongside ρ_a (MT only).

Return type:

ndarray

plot(ax=None, **kwargs)#

Quick diagnostic plot. Returns the Axes used.

class pycsamt.forward.MT2DForward(freqs, grid, *, verbose=True)#

Bases: object

2-D magnetotelluric finite-difference forward solver.

Solves both TE and TM modes for a list of frequencies on the provided Grid2D and returns a ForwardResponse2D with apparent resistivity and phase at all station positions.

Parameters:
  • freqs (array-like) – Frequencies [Hz] at which to evaluate the response.

  • grid (Grid2D) – 2-D resistivity model and station layout.

  • verbose (bool) – Print per-frequency progress.

Examples

Uniform halfspace — should recover the 1-D MT response:

>>> import numpy as np
>>> from pycsamt.forward.grid2d import Grid2D
>>> from pycsamt.forward.em2d import MT2DForward

>>> freqs = np.logspace(-2, 3, 10)
>>> g = Grid2D.halfspace(rho=100.0, nx=30, nz=20,
...                       x_max=5_000.0, z_max=3_000.0,
...                       n_stations=5)
>>> fwd = MT2DForward(freqs, g)
>>> resp = fwd.run()
>>> resp.rho_a_te.shape
(10, 5)

2-D model with conductive anomaly:

>>> g2 = Grid2D.with_anomaly(bg_rho=100.0, anomaly_rho=2.0,
...     anomaly_bounds=(1500.0, 3500.0, 300.0, 900.0),
...     nx=40, nz=25, x_max=6000.0, z_max=3000.0, n_stations=10)
>>> resp2 = MT2DForward(freqs, g2).run()
run()#

Run the forward solver for all frequencies.

Return type:

ForwardResponse2D

class pycsamt.forward.ForwardResponse2D(freqs, stations_x, zxy, zyx, rho_a_te, phase_te, rho_a_tm, phase_tm, residual_te, residual_tm, grid)#

Bases: object

Output of the 2-D MT forward solver.

All impedance and apparent-resistivity arrays have shape (n_freqs, n_stations).

Parameters:
  • freqs (ndarray, shape (n_freqs,)) – Evaluation frequencies [Hz].

  • stations_x (ndarray, shape (n_stations,)) – Station x-positions [m].

  • zxy (ndarray, shape (n_freqs, n_stations), complex) – TE-mode surface impedance Z_xy = E_y / H_x [V/A].

  • zyx (ndarray, shape (n_freqs, n_stations), complex) – TM-mode surface impedance Z_yx = E_x / H_y [V/A] (negative by convention: Z_yx = −Z_xy for a 1-D earth).

  • rho_a_te (ndarray, shape (n_freqs, n_stations)) – TE apparent resistivity [Ω·m].

  • phase_te (ndarray, shape (n_freqs, n_stations)) – TE impedance phase [degrees, 0–90° for a normal model].

  • rho_a_tm (ndarray, shape (n_freqs, n_stations)) – TM apparent resistivity [Ω·m].

  • phase_tm (ndarray, shape (n_freqs, n_stations)) – TM impedance phase [degrees].

  • residual_te (ndarray, shape (n_freqs,)) – Relative linear-system residual norm(A @ x - b) / norm(b) of the direct sparse solve for each mode, one value per frequency. This measures the achieved numerical accuracy of scipy.sparse.linalg.spsolve; it is not an iteration count, since the solver is direct.

  • residual_tm (ndarray, shape (n_freqs,)) – Relative linear-system residual norm(A @ x - b) / norm(b) of the direct sparse solve for each mode, one value per frequency. This measures the achieved numerical accuracy of scipy.sparse.linalg.spsolve; it is not an iteration count, since the solver is direct.

  • grid (Grid2D) – The model grid used for the forward run.

freqs: ndarray#
stations_x: ndarray#
zxy: ndarray#
zyx: ndarray#
rho_a_te: ndarray#
phase_te: ndarray#
rho_a_tm: ndarray#
phase_tm: ndarray#
residual_te: ndarray#
residual_tm: ndarray#
grid: Grid2D#
property n_freqs: int#
property n_stations: int#
property periods: ndarray#

Periods [s].

station_response(station)#

Return all response arrays for one station as a dict.

Parameters:

station (int)

Return type:

dict

to_feature_array(*, mode='both', log_rho=True, include_phase=True)#

Flatten to a 2-D feature matrix for ML training.

Parameters:
  • mode ({'te', 'tm', 'both'}) – Which mode(s) to include.

  • log_rho (bool) – Return log₁₀(ρ_a) instead of ρ_a.

  • include_phase (bool) – Concatenate phase alongside ρ_a.

Return type:

ndarray, shape (n_stations, n_features)

plot(station=0, ax=None, *, figsize=(9, 5))#

Quick diagnostic plot of ρ_a and phase for one station.

Parameters:
  • station (int) – Station index.

  • ax (array of Axes or None) – Two axes (rho, phase). Created when not provided.

  • figsize (tuple)

Returns:

axes

Return type:

ndarray of Axes, shape (2,)

class pycsamt.forward.LayeredModel(resistivity, thickness, depth=None, name='')#

Bases: object

1-D layered earth model.

The earth has n_layers layers. The last layer is the halfspace (infinite thickness). Thicknesses apply to layers 0 … n−2.

Parameters:
  • resistivity (array-like, shape (n_layers,)) – Layer resistivities in Ω·m, ordered top → bottom.

  • thickness (array-like, shape (n_layers-1,)) – Layer thicknesses in metres. The halfspace has no thickness.

  • depth (ndarray or None) – Top-of-layer depths [m]; computed from thickness if not given.

  • name (str) – Optional label (e.g. geological scenario name).

Examples

>>> from pycsamt.forward.synthetic import LayeredModel
>>> m = LayeredModel(
...     resistivity=[100, 10, 500],
...     thickness=[300, 800],
... )
>>> m.n_layers
3
>>> m.depth
array([   0.,  300., 1100.])
resistivity: ndarray#
thickness: ndarray#
depth: ndarray = None#
name: str = ''#
property n_layers: int#

Number of layers including the halfspace.

property conductivity: ndarray#

Layer conductivities σ = 1/ρ [S/m].

to_vector(*, log_rho=True)#

Flatten to a 1-D parameter vector suitable for ML targets.

The vector layout is [ρ₀, ρ₁, …, ρ_{n-1}, h₀, h₁, …, h_{n-2}] where ρ values are log₁₀(Ω·m) when log_rho is True.

Parameters:

log_rho (bool) – If True (default), resistivities are stored as log₁₀(ρ). This normalises the dynamic range and improves neural network convergence.

Returns:

v

Return type:

ndarray, shape (2 * n_layers - 1,)

classmethod from_vector(v, n_layers, *, log_rho=True, name='')#

Reconstruct a LayeredModel from a flat parameter vector.

Parameters:
  • v (ndarray, shape (2 * n_layers - 1,)) – Parameter vector as produced by to_vector().

  • n_layers (int) – Number of layers.

  • log_rho (bool) – Whether resistivities in v are in log₁₀ space.

  • name (str)

Return type:

LayeredModel

classmethod random(n_layers=5, *, rho_range=(1.0, 10000.0), depth_max=2000.0, seed=None, name='random')#

Generate a random layered model with log-uniform resistivities.

Layer thicknesses are drawn from a Dirichlet-like distribution that sums to depth_max so that the model spans the full target depth range.

Parameters:
  • n_layers (int) – Number of layers (including halfspace).

  • rho_range ((low, high)) – Resistivity bounds in Ω·m.

  • depth_max (float) – Total depth spanned by the first (n-1) layers [m].

  • seed (int, Generator, or None) – Random seed for reproducibility.

  • name (str)

Return type:

LayeredModel

classmethod blocky(n_layers=4, *, rho_background=100.0, rho_anomaly=5.0, anomaly_layer=1, depth_max=1000.0, equal_thickness=True, seed=None, name='blocky')#

Build a model with a single conductive (or resistive) anomaly embedded in a resistive (or conductive) background.

Parameters:
  • rho_background (float) – Background layer resistivity [Ω·m].

  • rho_anomaly (float) – Anomaly layer resistivity [Ω·m].

  • anomaly_layer (int) – Zero-based index of the anomalous layer.

  • n_layers (int)

  • depth_max (float)

  • equal_thickness (bool)

  • seed (int | Generator | None)

  • name (str)

Return type:

LayeredModel

classmethod smooth(n_layers=10, *, rho_surface=100.0, rho_deep=10.0, depth_max=5000.0, perturbation=0.2, seed=None, name='smooth')#

Build a model with a smooth gradient from surface to depth, plus small random perturbations.

Parameters:
  • rho_surface (float) – Resistivity at the surface [Ω·m].

  • rho_deep (float) – Resistivity at depth_max [Ω·m].

  • perturbation (float) – Fractional standard deviation of log₁₀(ρ) noise added to the gradient. 0 = pure gradient.

  • n_layers (int)

  • depth_max (float)

  • seed (int | Generator | None)

  • name (str)

Return type:

LayeredModel

classmethod from_geology(name, *, seed=None)#

Generate a random model using a predefined geological prior.

Parameters:
  • name (str) – One of: 'sedimentary', 'crystalline', 'geothermal', 'marine', 'permafrost'.

  • seed (int | Generator | None)

Raises:

KeyError – If name is not in GEOLOGY_PRIORS.

Return type:

LayeredModel

plot(ax=None, *, log_scale=True, depth_max=None, label=None, **kwargs)#

Plot the 1-D resistivity–depth profile.

Parameters:
  • ax (Axes or None) – Target axes. Created if not given.

  • log_scale (bool) – Use log₁₀ scale on the resistivity axis.

  • depth_max (float or None) – Maximum depth shown. Defaults to 1.2× the deepest interface.

  • label (str or None) – Line label for legend.

Returns:

ax

Return type:

Axes

class pycsamt.forward.Grid2D(dx, dz, resistivity, x_stations, n_pad=0, name='')#

Bases: object

Non-uniform 2-D finite-difference grid for MT forward modelling.

Parameters:
  • dx (array-like, shape (nx,)) – Cell widths in the horizontal (x) direction [m].

  • dz (array-like, shape (nz,)) – Cell heights in the vertical (z, depth) direction [m].

  • resistivity (array-like, shape (nz, nx)) – Cell resistivities [Ω·m], top-to-bottom, left-to-right.

  • x_stations (array-like, shape (n_stations,)) – Horizontal positions of MT stations [m]. Must lie within [x_nodes[0], x_nodes[-1]].

  • n_pad (int) – Number of padding cells on each side (left, right) and at the bottom that were added during construction. Used internally to identify the core model region. Set to 0 if no padding is present.

  • name (str) – Optional label for the model.

Variables:
  • nx (int) – Total number of cells in x (includes padding).

  • nz (int) – Total number of cells in z (includes padding).

  • x_nodes (ndarray, shape (nx+1,)) – x-coordinates of vertical node lines [m].

  • z_nodes (ndarray, shape (nz+1,)) – z-coordinates of horizontal node lines (depth) [m].

  • x_centers (ndarray, shape (nx,)) – x-coordinates of cell centres [m].

  • z_centers (ndarray, shape (nz,)) – z-coordinates (depth) of cell centres [m].

  • core_x_slice (slice) – Slice that selects core (non-padding) columns from any [:, j] array.

  • core_z_slice (slice) – Slice that selects core (non-padding) rows from any [i, :] array.

Examples

Uniform halfspace:

>>> g = Grid2D.halfspace(rho=100.0, nx=30, nz=20,
...                       x_max=5_000.0, z_max=3_000.0,
...                       n_stations=10, station_x_max=4_000.0)
>>> g.resistivity.shape
(20, 30)

One conductive anomaly:

>>> g = Grid2D.with_anomaly(
...     bg_rho=500.0,
...     anomaly_rho=5.0,
...     anomaly_bounds=(1_000.0, 3_000.0, 200.0, 800.0),
...     nx=40, nz=25,
...     x_max=6_000.0, z_max=4_000.0,
...     n_stations=12,
... )

1-D layered model extended horizontally:

>>> from pycsamt.forward.synthetic import LayeredModel
>>> m = LayeredModel([100, 10, 500], [300, 800])
>>> g = Grid2D.from_1d_layers(m, nx=40, x_max=6_000.0, n_stations=12)
dx: ndarray#
dz: ndarray#
resistivity: ndarray#
x_stations: ndarray#
n_pad: int = 0#
name: str = ''#
property nx: int#

Number of cells in x.

property nz: int#

Number of cells in z (depth).

property n_stations: int#

Number of MT stations.

property n_nodes: int#

Total FD node count (nx+1) × (nz+1).

property x_nodes: ndarray#

x-coordinates of vertical node lines [m], shape (nx+1,).

property z_nodes: ndarray#

Depth coordinates of horizontal node lines [m], shape (nz+1,).

property x_centers: ndarray#

x-coordinates of cell centres [m], shape (nx,).

property z_centers: ndarray#

Depth coordinates of cell centres [m], shape (nz,).

property x_extent: float#

Total horizontal extent of the grid [m].

property z_extent: float#

Total depth extent of the grid [m].

property core_x_slice: slice#

Column slice that selects only core (non-padding) cells.

property core_z_slice: slice#

Row slice that selects only core (non-padding) cells.

property core_resistivity: ndarray#

Resistivity array clipped to the core (non-padding) region.

property core_x_offset: float#

x-coordinate where the core (non-padding) region begins [m].

Padding is built identically on both sides of the grid (the same cell-width list, left side reversed), so this is also where along-profile chainage – 0 at the first core column – and the grid’s own absolute x coordinate (0 at the outer edge of the left padding) part ways. See value_at().

value_at(x_m, z_m, *, chainage=False)#

Return the resistivity of the cell nearest (x_m, z_m).

Parameters:
  • x_m (float) – Horizontal position, metres. In the grid’s own absolute coordinates (0 at the outer edge of the left padding) by default.

  • z_m (float) – Depth, metres, positive downward.

  • chainage (bool, default False) – When True, x_m is along-profile chainage instead – 0 at the first core (station-carrying) column, matching pycsamt.geology.Borehole’s x convention. Set this when sampling ground truth for a borehole or any other profile-relative position.

Return type:

float

Examples

>>> g = Grid2D.halfspace(rho=100.0, nx=20, x_max=2000.0, n_stations=5)
>>> g.value_at(0.0, 50.0, chainage=True)
100.0
column_profile(col)#

Return the 1-D resistivity/thickness profile of column col.

Used by the FD solver to compute Dirichlet boundary conditions at the left and right edges from the 1-D MT response of the edge columns.

Parameters:

col (int) – Column index (0 = leftmost, nx-1 = rightmost).

Returns:

  • rho (ndarray, shape (nz,)) – Layer resistivities top-to-bottom [Ω·m].

  • thick (ndarray, shape (nz-1,)) – Layer thicknesses [m]. (Bottom layer is a halfspace.)

Return type:

tuple[ndarray, ndarray]

station_cell_indices()#

Return the column index of the cell that contains each station.

Returns:

indices

Return type:

ndarray of int, shape (n_stations,)

station_node_indices()#

Return the index of the nearest surface node for each station.

Surface nodes are at z = 0 and numbered 0, 1, …, nx.

Returns:

indices

Return type:

ndarray of int, shape (n_stations,)

property conductivity: ndarray#

Cell conductivities [S/m], shape (nz, nx).

harmonic_mean_x()#

Harmonic-mean conductivity at x-directed cell interfaces.

Used by the TM-mode FD assembler.

Returns:

sigma_hx[i, j] is the harmonic mean of sigma[i, j] and sigma[i, j+1].

Return type:

ndarray, shape (nz, nx-1)

harmonic_mean_z()#

Harmonic-mean conductivity at z-directed cell interfaces.

Returns:

sigma_hz[i, j] is the harmonic mean of sigma[i, j] and sigma[i+1, j].

Return type:

ndarray, shape (nz-1, nx)

plot(ax=None, *, log_scale=True, cmap='jet_r', clip_core=True, show_stations=True, figsize=(10, 5), vmin=None, vmax=None)#

Plot the 2-D resistivity model.

Parameters:
  • ax (Axes or None) – Target axes. Created when not given.

  • log_scale (bool) – Plot log₁₀(ρ) rather than ρ.

  • cmap (str) – Matplotlib colormap name.

  • clip_core (bool) – Clip to the non-padding core region.

  • show_stations (bool) – Mark station positions on the surface.

  • figsize ((float, float)) – Figure size when ax is not provided.

  • vmin (float or None) – Colormap limits in log₁₀(Ω·m) space when log_scale is True.

  • vmax (float or None) – Colormap limits in log₁₀(Ω·m) space when log_scale is True.

Returns:

ax

Return type:

Axes

classmethod halfspace(rho=100.0, *, nx=40, nz=30, x_max=10000.0, z_max=5000.0, n_pad=8, pad_factor=1.3, n_stations=10, station_x_max=None, name='halfspace')#

Create a uniform resistivity halfspace grid.

Parameters:
  • rho (float) – Background resistivity [Ω·m].

  • nx (int) – Number of core cells in x (padding added on both sides).

  • nz (int) – Number of core cells in z (padding added at the bottom).

  • x_max (float) – Total horizontal extent of the core region [m].

  • z_max (float) – Total depth of the core region [m].

  • n_pad (int) – Number of padding cells on each side (left, right, bottom).

  • pad_factor (float) – Exponential growth factor for padding cell sizes.

  • n_stations (int) – Number of equally spaced stations across the core region.

  • station_x_max (float or None) – Right-most station position [m]. Defaults to x_max.

  • name (str) – Label for the model.

Return type:

Grid2D

classmethod from_1d_layers(model, *, nx=40, x_max=10000.0, n_pad=8, pad_factor=1.3, n_stations=10, station_x_max=None, dz_min=10.0, name='')#

Build a 2-D grid by extending a 1-D LayeredModel.

Every column has the same 1-D layer stack. This is useful as a background model for synthetic anomaly tests and inversion starting models.

Parameters:
  • model (LayeredModel) – Source 1-D model. model.thickness defines the cell thicknesses; the deepest cell becomes the halfspace.

  • nx (int) – Number of core cells in x.

  • x_max (float) – Horizontal extent of the core region [m].

  • n_pad (int) – Padding cells on each side.

  • pad_factor (float) – Padding growth factor.

  • n_stations (int) – Number of equally spaced surface stations.

  • station_x_max (float or None) – Right-most station x [m].

  • dz_min (float) – Minimum cell height [m]; avoids excessively thin cells at depth.

  • name (str) – Model label.

Return type:

Grid2D

classmethod with_anomaly(bg_rho=100.0, anomaly_rho=5.0, anomaly_bounds=(2000.0, 4000.0, 200.0, 800.0), *, nx=50, nz=35, x_max=8000.0, z_max=4000.0, n_pad=8, pad_factor=1.3, n_stations=12, station_x_max=None, name='')#

Create a background halfspace with one rectangular anomaly.

Parameters:
  • bg_rho (float) – Background resistivity [Ω·m].

  • anomaly_rho (float) – Anomaly resistivity [Ω·m].

  • anomaly_bounds ((x_lo, x_hi, z_lo, z_hi)) – Anomaly extent in core coordinates [m]: horizontal from x_lo to x_hi, depth from z_lo to z_hi.

  • nx (int) – Core cell counts.

  • nz (int) – Core cell counts.

  • x_max (float) – Core region extents [m].

  • z_max (float) – Core region extents [m].

  • n_pad (int) – Padding cells per side.

  • pad_factor (float) – Padding growth factor.

  • n_stations (int) – Number of equally spaced surface stations.

  • station_x_max (float or None) – Right-most station x [m].

  • name (str) – Model label.

Return type:

Grid2D

Examples

Resistive body at mid-depth:

>>> g = Grid2D.with_anomaly(
...     bg_rho=50.0, anomaly_rho=2000.0,
...     anomaly_bounds=(2000.0, 4000.0, 500.0, 1500.0),
... )
classmethod layered_with_fault(model, *, fault_x_m, apparent_dip_deg, throw_m, downthrown_side='right', nx=60, nz=50, x_max=10000.0, z_max=5000.0, n_pad=10, pad_factor=1.35, n_stations=10, station_x_max=None, name='')#

Extend a 1-D LayeredModel across x on a uniform fine grid (as halfspace() does, not from_1d_layers()’s coarse one-cell-per-layer discretisation – accurate forward modelling near a thin near-surface layer needs cells much thinner than the layer itself), then offset it across one dipping fault plane.

The fault plane migrates laterally with depth (z / tan(apparent_dip_deg)), so a column’s resistivity comes from evaluating the same 1-D layer stack at a shifted depth (z - throw_m on the downthrown block) rather than a fixed per-column swap – the whole stack drops by throw_m, it is not replaced by a different one.

Parameters:
  • model (LayeredModel) – Background 1-D layer stack.

  • fault_x_m (float) – Along-profile position where the fault reaches the surface, in the same core coordinates as station_x_max (0 at the left edge of the core region, not the padded grid).

  • apparent_dip_deg (float) – Dip of the fault as it appears in this 2-D section, degrees below horizontal, (0, 90]. This is not necessarily the fault’s true 3-D dip unless the profile runs perpendicular to strike – see pycsamt.geology.FaultTrace for the general apparent-dip relationship.

  • throw_m (float) – Vertical displacement of the layer stack on the downthrown side, metres (magnitude only; direction is carried by downthrown_side).

  • downthrown_side ({'right', 'left'}) – Which side of fault_x_m – toward increasing or decreasing x – has the layer stack shifted deeper.

  • nx (int) – Core cell counts, horizontal and vertical.

  • nz (int) – Core cell counts, horizontal and vertical.

  • x_max (float) – Core region extent, metres.

  • z_max (float) – Core region extent, metres.

  • n_pad (int) – Padding cells on each side (left, right, bottom).

  • pad_factor (float) – Padding growth factor.

  • n_stations (int) – Number of equally spaced surface stations.

  • station_x_max (float or None) – Right-most station x [m]. Defaults to x_max.

  • name (str) – Model label.

Return type:

Grid2D

Examples

>>> from pycsamt.forward.synthetic import LayeredModel
>>> from pycsamt.forward.grid2d import Grid2D
>>> m = LayeredModel(resistivity=[80.0, 300.0, 3000.0], thickness=[60.0, 260.0])
>>> g = Grid2D.layered_with_fault(
...     m, fault_x_m=1200.0, apparent_dip_deg=65.0,
...     throw_m=700.0, downthrown_side="right",
...     nx=60, nz=50, x_max=2400.0, z_max=1600.0, n_stations=25,
... )
>>> g.resistivity.shape
(60, 80)
classmethod random(*, nx=40, nz=30, x_max=8000.0, z_max=4000.0, n_pad=8, pad_factor=1.3, n_stations=10, rho_min=1.0, rho_max=10000.0, n_layers=4, lateral_variation=True, seed=None, name='random')#

Generate a random layered model with optional lateral variation.

Resistivities for n_layers horizontal layers are drawn from a log-uniform prior. When lateral_variation is True, each column’s resistivity is perturbed by ±30 % in log₁₀ space.

Parameters:
  • nx (int) – Core cell counts.

  • nz (int) – Core cell counts.

  • x_max (float) – Core extents [m].

  • z_max (float) – Core extents [m].

  • n_pad (int) – Padding cells per side.

  • rho_min (float) – Resistivity bounds [Ω·m].

  • rho_max (float) – Resistivity bounds [Ω·m].

  • n_layers (int) – Number of horizontal layers.

  • lateral_variation (bool) – Add column-wise log-normal perturbation.

  • seed (int or Generator or None)

  • name (str)

  • pad_factor (float)

  • n_stations (int)

Return type:

Grid2D

pycsamt.forward.make_padding(core_spacing, n_pad, factor=1.3)#

Return cell widths for one padding strip.

The first padding cell has width core_spacing * factor and each subsequent cell is factor times wider.

Parameters:
  • core_spacing (float) – Width/depth of the last core cell adjacent to the padding strip [m].

  • n_pad (int) – Number of padding cells.

  • factor (float) – Growth factor between successive padding cells. Values in [1.2, 1.5] give good results for MT; 1.3 is a safe default.

Returns:

Cell widths ordered from the core edge outward.

Return type:

numpy.ndarray, shape (n_pad,)

class pycsamt.forward.GaussianNoise(level=0.05, apply_to='rho_phase', phase_level=None)#

Bases: _BaseNoiseModel

Add relative Gaussian noise to an EM forward response.

For MT/CSAMT the noise is applied to log₁₀(ρ_a) and the phase independently. For TEM it is applied to log₁₀(|dBz/dt|).

Parameters:
  • level (float) – Relative noise standard deviation (e.g. 0.05 = 5 %).

  • apply_to ({'rho_phase', 'z', 'both', 'rho', 'phase'}) – Which quantities to perturb. 'rho_phase', 'both' and 'z' are equivalent: they jointly perturb ρₐ and phase, which together determine the perturbed complex impedance Z. 'rho' and 'phase' perturb only one of the two. Default 'rho_phase'.

  • phase_level (float or None) – Separate noise level for phase in degrees. If None, level is used scaled to the typical 45° phase range.

Examples

>>> import numpy as np
>>> from pycsamt.forward.em1d import MT1DForward
>>> from pycsamt.forward.synthetic import LayeredModel
>>> from pycsamt.forward.noise import GaussianNoise
>>> m = LayeredModel([100, 10, 500], [300, 800])
>>> resp = MT1DForward(np.logspace(-3, 4, 20)).run(m)
>>> noisy = GaussianNoise(level=0.05).apply(resp, seed=0)
>>> noisy.rho_a.shape
(20,)
apply(response, *, seed=None)#

Return a new ForwardResponse with noise added.

Parameters:
  • response (ForwardResponse) – Clean forward response.

  • seed (int or None) – Random seed for reproducibility.

Return type:

ForwardResponse

class pycsamt.forward.FieldRealisticNoise(base_level=0.02, powerline_freq=50.0, powerline_level=0.3, dead_band=True, dead_band_freq_range=(0.0003, 0.001), dead_band_level=0.15)#

Bases: _BaseNoiseModel

Frequency-dependent noise model for MT/CSAMT training data.

Simulates three dominant noise sources present in field data:

  1. Power-line harmonics — Gaussian spikes at 50 Hz, 100 Hz, 150 Hz (or 60/120/180 Hz for North American data).

  2. MT dead band — elevated noise at ~1 000–3 000 s period (≈ 3×10⁻⁴ – 10⁻³ Hz) due to reduced natural source energy.

  3. Background floor — minimum noise level applied uniformly.

Parameters:
  • base_level (float) – Background relative noise floor. Default 0.02 (2 %).

  • powerline_freq (float) – Fundamental power-line frequency [Hz]. 50 or 60.

  • powerline_level (float) – Additional noise at power-line harmonics (relative).

  • dead_band (bool) – If True, inflate noise in the MT dead band.

  • dead_band_freq_range ((low, high)) – Frequency range [Hz] of the dead band. Default (3e-4, 1e-3).

  • dead_band_level (float) – Additional noise level in the dead band. Default 0.15.

noise_profile(freqs)#

Return the frequency-dependent noise level array.

Parameters:

freqs (ndarray) – Frequencies [Hz].

Returns:

sigma – Relative noise standard deviation at each frequency.

Return type:

ndarray, same shape as freqs

apply(response, *, seed=None)#

Return a new ForwardResponse with noise added.

Parameters:
  • response (ForwardResponse) – Clean forward response.

  • seed (int or None) – Random seed for reproducibility.

Return type:

ForwardResponse

plot_profile(freqs, ax=None)#

Visualise the noise level vs frequency. Returns Axes.

Parameters:

freqs (ndarray)

class pycsamt.forward.MultiplicativeNoise(sigma_log10=0.05)#

Bases: _BaseNoiseModel

Log-space (multiplicative) Gaussian noise.

Equivalent to a log-normal perturbation on the data value. Appropriate when the data spans many orders of magnitude.

Parameters:

sigma_log10 (float) – Standard deviation of the log₁₀ perturbation. sigma_log10=0.05 corresponds to ≈ ±12 % variation.

apply(response, *, seed=None)#

Return a new ForwardResponse with noise added.

Parameters:
  • response (ForwardResponse) – Clean forward response.

  • seed (int or None) – Random seed for reproducibility.

Return type:

ForwardResponse

pycsamt.forward.add_gaussian_noise(response, level=0.05, *, seed=None)#

Convenience wrapper: add Gaussian noise at a given relative level.

Parameters:
Returns:

New response with noise applied.

Return type:

ForwardResponse

pycsamt.forward.add_noise(response, noise_model='gaussian', level=0.05, *, seed=None, **kwargs)#

Apply a named or pre-built noise model to a forward response.

Parameters:
  • response (ForwardResponse)

  • noise_model (str or noise instance) – 'gaussian', 'multiplicative', 'field', or an already-instantiated noise object.

  • level (float) – Base noise level (used when constructing named models).

  • seed (int or None)

  • **kwargs – Extra keyword arguments forwarded to the noise model constructor.

Return type:

ForwardResponse

pycsamt.forward.generate_dataset(solver='mt1d', n_samples=10000, *, freqs=None, times=None, n_layers=(3, 7), rho_range=(1.0, 10000.0), depth_max=2000.0, loop_radius=50.0, noise_level=0.05, noise_type='gaussian', geology=None, include_phase=True, seed=None, n_jobs=1, output=None, verbose=True)#

Generate a batch of synthetic (data, model) pairs for ML training.

Parameters:
  • solver ({'mt1d', 'tem1d', 'csamt1d'}) – Forward solver to use.

  • n_samples (int) – Total number of samples.

  • freqs (ndarray or None) – Frequencies [Hz] for MT/CSAMT. If None, uses np.logspace(-3, 4, 30) for MT.

  • times (ndarray or None) – Times [s] for TEM. If None, uses np.logspace(-6, -2, 25).

  • n_layers (int or (lo, hi)) – Fixed number of layers, or a range from which the count is drawn uniformly at random per sample.

  • rho_range ((low, high)) – Resistivity bounds in Ω·m.

  • depth_max (float) – Maximum depth of the model [m].

  • loop_radius (float) – TEM transmitter loop radius [m].

  • noise_level (float) – Relative noise standard deviation. 0 = noise-free.

  • noise_type (str) – Noise model: 'gaussian', 'multiplicative', 'field'.

  • geology (str or None) – If given, models are drawn from LayeredModel.from_geology() using this geological scenario name.

  • include_phase (bool) – Include impedance phase in the MT feature vector.

  • seed (int or None) – Base random seed. Worker seeds are derived deterministically.

  • n_jobs (int) – Number of parallel worker processes. -1 uses all CPU cores.

  • output (str or None) – If given, save the dataset to a .npz file at this path.

  • verbose (bool) – Print progress.

Return type:

ForwardDataset

Examples

>>> import numpy as np
>>> from pycsamt.forward.batch import generate_dataset
>>> ds = generate_dataset(n_samples=100, seed=0)
>>> ds.X.shape[0]
100
class pycsamt.forward.ForwardDataset(X, y, freqs=None, times=None, meta=None, solver='mt1d')#

Bases: object

Numpy container for a batch of (features, targets) EM samples.

Parameters:
  • X (ndarray, shape (n_samples, n_features)) – Model responses (log-scaled data).

  • y (ndarray, shape (n_samples, n_params)) – Model parameters (log10 resistivity + thickness).

  • freqs (ndarray or None) – Frequencies used (MT/CSAMT).

  • times (ndarray or None) – Times used (TEM).

  • meta (structured ndarray) – Per-sample metadata (n_layers, noise_level, …).

  • solver (str) – Solver used to generate this dataset.

X: ndarray#
y: ndarray#
freqs: ndarray | None = None#
times: ndarray | None = None#
meta: ndarray | None = None#
solver: str = 'mt1d'#
save(path)#

Save to a compressed .npz file.

Parameters:

path (str)

Return type:

None

classmethod load(path)#

Load from a .npz file produced by save().

Parameters:

path (str)

Return type:

ForwardDataset

split(val_frac=0.1, test_frac=0.1, seed=None)#

Split into train / validation / test sets.

Returns:

(train, val, test)

Return type:

tuple of ForwardDataset

Parameters:
pycsamt.forward.generate_dataset_3d(solver='mt1d', n_surveys=1000, *, n_stations=25, n_layers=4, freqs=None, extent=10000.0, corr_length=2000.0, log_rho_mean=2.0, log_rho_std=0.5, thickness_range=(100.0, 2000.0), station_layout='grid', noise_level=0.03, noise_type='gaussian', include_phase=True, seed=None, n_jobs=1, output=None, verbose=True)#

Generate a pseudo-3D synthetic dataset for GCNInverter3D training.

Each survey realisation consists of n_stations MT soundings whose per-layer log-resistivities are drawn from a spatially correlated Gaussian random field (GRF) with a squared-exponential covariance:

\[C(\mathbf{x}_i,\mathbf{x}_j) = \exp\!\left(-\frac{\|\mathbf{x}_i-\mathbf{x}_j\|^2} {2\,\ell^2}\right)\]

where \(\ell\) is corr_length. Layer thicknesses are drawn log-uniformly per survey but kept spatially constant (flat-layer assumption within one survey). All surveys share the same fixed station grid so that a single adjacency matrix covers the whole training set.

Parameters:
  • solver (str) – Forward solver. Currently only 'mt1d' is supported.

  • n_surveys (int) – Number of independent survey realisations.

  • n_stations (int) – Number of MT stations per survey.

  • n_layers (int) – Fixed number of earth layers (including halfspace).

  • freqs (ndarray or None) – Frequencies [Hz]. Defaults to np.logspace(-3, 4, 30).

  • extent (float) – Side length of the square survey area in metres.

  • corr_length (float) – Spatial correlation length of the GRF in metres. Set smaller than station spacing for uncorrelated models; larger for smooth lateral variation.

  • log_rho_mean (float) – Mean of log₁₀(ρ) for all layers (default 2 → 100 Ω·m).

  • log_rho_std (float) – Marginal standard deviation of the GRF in log₁₀(ρ) units.

  • thickness_range ((lo, hi)) – Bounds [m] for log-uniform layer thickness draws.

  • station_layout ('grid' or 'random') – 'grid' places stations on a regular grid of ⌈√n_stations⌉² points (default). 'random' draws uniform positions within [0, extent]².

  • noise_level (float) – Relative noise standard deviation applied to each response.

  • noise_type (str) – Noise model: 'gaussian', 'multiplicative', 'field'.

  • include_phase (bool) – Include impedance phase in the feature vector.

  • seed (int or None) – Base random seed. Worker seeds are derived deterministically.

  • n_jobs (int) – Number of parallel worker processes. -1 uses all CPU cores.

  • output (str or None) – If given, save the dataset to a compressed .npz file.

  • verbose (bool) – Print progress.

Returns:

Container with arrays X (n_surveys, n_stations, n_features), y (n_surveys, n_stations, n_params), and coords (n_stations, 2).

Return type:

SurveyDataset3D

Examples

>>> import numpy as np
>>> from pycsamt.forward.batch import generate_dataset_3d
>>> from pycsamt.ai.nets.gcn import build_adjacency
>>> ds = generate_dataset_3d(
...     n_surveys=500,
...     n_stations=16,
...     n_layers=4,
...     corr_length=2000.0,
...     seed=0,
... )
>>> ds.X.shape
(500, 16, 60)
>>> A = build_adjacency(ds.coords, radius=3000.0)
>>> from pycsamt.ai.inversion.inv3d import GCNInverter3D
>>> inv = GCNInverter3D(n_features=ds.n_features, n_layers=4)
>>> inv.fit(ds.X, ds.y, adjacency=A, epochs=5, verbose=False)
class pycsamt.forward.SurveyDataset3D(X, y, coords, freqs=None, meta=None, solver='mt1d')#

Bases: object

Numpy container for a pseudo-3D multi-station survey dataset.

Designed for training GCNInverter3D. All surveys share the same fixed station grid so that a single pre-computed adjacency matrix covers the whole dataset.

Parameters:
  • X (ndarray, shape (n_surveys, n_stations, n_features)) – Per-station MT/CSAMT feature vectors (log-scaled data).

  • y (ndarray, shape (n_surveys, n_stations, n_params)) – Per-station model parameters [log10(ρ), thickness].

  • coords (ndarray, shape (n_stations, 2)) – Station (easting, northing) coordinates in metres.

  • freqs (ndarray or None) – Frequencies [Hz] used by the forward solver.

  • meta (structured ndarray or None) – Per-survey metadata (corr_length, noise_level).

  • solver (str) – Solver used to generate this dataset.

X: ndarray#
y: ndarray#
coords: ndarray#
freqs: ndarray | None = None#
meta: ndarray | None = None#
solver: str = 'mt1d'#
property n_surveys: int#

Number of synthetic survey realisations.

property n_stations: int#

Number of stations per survey.

property n_features: int#

Feature dimensionality per station.

property n_params: int#

Parameter dimensionality per station (2 × n_layers 1).

save(path)#

Save to a compressed .npz file.

Parameters:

path (str)

Return type:

None

classmethod load(path)#

Load from a .npz file produced by save().

Parameters:

path (str)

Return type:

SurveyDataset3D

split(val_frac=0.1, test_frac=0.1, seed=None)#

Split into train / validation / test sets along the survey axis.

Returns:

(train, val, test)

Return type:

tuple of SurveyDataset3D

Parameters:
class pycsamt.forward.Grid3D(dx, dy, dz, resistivity, stations_xy, n_pad=0, name='')#

Bases: object

Non-uniform 3-D finite-difference grid for MT forward modelling.

Parameters:
  • dx (array-like, shape (nx,)) – Cell widths in x (easting) [m], including padding.

  • dy (array-like, shape (ny,)) – Cell widths in y (northing) [m], including padding.

  • dz (array-like, shape (nz,)) – Cell heights in z (depth) [m], including padding.

  • resistivity (array-like, shape (nz, ny, nx)) – Cell resistivities [Ω·m], top→bottom, front→back, left→right.

  • stations_xy (array-like, shape (n_stations, 2)) – Surface station (x, y) coordinates [m]. Must lie within the grid extent.

  • n_pad (int) – Padding cells added on each side in x and y and at the bottom in z during construction.

  • name (str) – Optional label.

Examples

Uniform halfspace:

>>> g = Grid3D.halfspace(rho=100.0, nx=20, ny=20, nz=15,
...     x_max=8_000.0, y_max=8_000.0, z_max=4_000.0,
...     nx_stations=5, ny_stations=5)
>>> g.resistivity.shape   # includes padding
(23, 36, 36)

3-D conductive block:

>>> g = Grid3D.block_anomaly(
...     bg_rho=500.0, anomaly_rho=5.0,
...     bounds=(2_000.0, 6_000.0, 2_000.0, 6_000.0, 300.0, 1_500.0),
...     nx=25, ny=25, nz=18,
...     x_max=8_000.0, y_max=8_000.0, z_max=5_000.0,
...     nx_stations=5, ny_stations=5)
dx: ndarray#
dy: ndarray#
dz: ndarray#
resistivity: ndarray#
stations_xy: ndarray#
n_pad: int = 0#
name: str = ''#
property nx: int#
property ny: int#
property nz: int#
property n_stations: int#
property x_nodes: ndarray#
property y_nodes: ndarray#
property z_nodes: ndarray#
property x_centers: ndarray#
property y_centers: ndarray#
property z_centers: ndarray#
property x_extent: float#
property y_extent: float#
property z_extent: float#
xz_slice(yi)#

Extract an XZ (east-west) 2-D slice at y-cell yi.

Returns a Grid2D whose horizontal axis is x (easting) and vertical axis is z (depth). The station x-positions are those of all stations whose y-coordinate falls in cell yi.

Returns:

  • grid2d (Grid2D)

  • station_indices (ndarray of int) – Global indices (into stations_xy) of stations in this y-row. grid2d.x_stations[k] is the x-coordinate of stations_xy[station_indices[k]].

Parameters:

yi (int)

Return type:

tuple[Grid2D, ndarray]

yz_slice(xi)#

Extract a YZ (north-south) 2-D slice at x-cell xi.

The Grid2D horizontal axis is mapped to y (northing); its dx array contains dy and its resistivity is resistivity[:, :, xi].

Returns:

  • grid2d (Grid2D)

  • station_indices (ndarray of int) – Global indices of stations in this x-column. grid2d.x_stations[k] is the y-coordinate of the station (used as the horizontal axis in the yz-plane solver).

Parameters:

xi (int)

Return type:

tuple[Grid2D, ndarray]

column_profile_3d(xi, yi)#

Return the 1-D resistivity/thickness profile at cell (xi, yi).

Parameters:
  • xi (int Column index (x direction).)

  • yi (int Row index (y direction).)

Returns:

  • rho (ndarray, shape (nz,))

  • thick (ndarray, shape (nz-1,))

Return type:

tuple[ndarray, ndarray]

property conductivity: ndarray#

Cell conductivities [S/m], shape (nz, ny, nx).

plot(*, cmap='jet_r', log_scale=True, clip_core=True, show_stations=True, figsize=(13, 4.5), vmin=None, vmax=None)#

Plot orthogonal slices: XZ (mid-y), YZ (mid-x), XY (mid-z).

Parameters:
Returns:

  • fig (matplotlib.figure.Figure)

  • axes (ndarray of Axes, shape (3,))

classmethod halfspace(rho=100.0, *, nx=20, ny=20, nz=15, x_max=8000.0, y_max=8000.0, z_max=4000.0, n_pad=8, pad_factor=1.3, nx_stations=5, ny_stations=5, name='halfspace')#

Create a uniform resistivity 3-D halfspace with a regular station grid.

Parameters:
  • rho (float) – Background resistivity [Ω·m].

  • nx (int) – Core cell counts (padding added automatically).

  • ny (int) – Core cell counts (padding added automatically).

  • nz (int) – Core cell counts (padding added automatically).

  • x_max (float) – Core horizontal extents [m].

  • y_max (float) – Core horizontal extents [m].

  • z_max (float) – Core depth extent [m].

  • n_pad (int) – Padding cells per side (x, y) and at the bottom (z).

  • pad_factor (float) – Exponential growth factor for padding cells.

  • nx_stations (int) – Regular station grid dimensions.

  • ny_stations (int) – Regular station grid dimensions.

  • name (str)

Return type:

Grid3D

classmethod block_anomaly(bg_rho=100.0, anomaly_rho=5.0, bounds=(2000.0, 6000.0, 2000.0, 6000.0, 300.0, 1500.0), *, nx=25, ny=25, nz=18, x_max=8000.0, y_max=8000.0, z_max=5000.0, n_pad=8, pad_factor=1.3, nx_stations=5, ny_stations=5, name='')#

Create a background halfspace with one rectangular 3-D anomaly.

Parameters:
  • bg_rho (float) – Background resistivity [Ω·m].

  • anomaly_rho (float) – Anomaly resistivity [Ω·m].

  • bounds ((x_lo, x_hi, y_lo, y_hi, z_lo, z_hi)) – Anomaly extents in core coordinates [m].

  • nx (int Core cell counts.)

  • ny (int Core cell counts.)

  • nz (int Core cell counts.)

  • x_max (float Core extents [m].)

  • y_max (float Core extents [m].)

  • z_max (float Core extents [m].)

  • n_pad (int, float)

  • pad_factor (int, float)

  • nx_stations (int)

  • ny_stations (int)

  • name (str)

Return type:

Grid3D

Examples

3-D conductive fault zone:

>>> g = Grid3D.block_anomaly(
...     bg_rho=500.0, anomaly_rho=3.0,
...     bounds=(2_500., 5_500., 2_500., 5_500., 200., 1_800.))
classmethod random_layered(*, n_layers=4, nx=20, ny=20, nz=15, x_max=8000.0, y_max=8000.0, z_max=4000.0, n_pad=8, pad_factor=1.3, nx_stations=5, ny_stations=5, rho_min=1.0, rho_max=10000.0, lateral_variation=True, corr_length=2000.0, seed=None, name='random_3d')#

Generate a random layered 3-D model with optional lateral variation.

Resistivities for n_layers horizontal layers are drawn from a log-uniform prior. When lateral_variation is True, each column’s resistivity is smoothly perturbed using a Gaussian random field with correlation length corr_length.

Parameters:
  • n_layers (int Number of horizontal layers.)

  • nx (as above.)

  • ny (as above.)

  • nz (as above.)

  • x_max (as above.)

  • y_max (as above.)

  • z_max (as above.)

  • n_pad (as above.)

  • pad_factor (as above.)

  • rho_min (float Resistivity bounds [Ω·m].)

  • rho_max (float Resistivity bounds [Ω·m].)

  • lateral_variation (bool)

  • corr_length (float GRF correlation length [m].)

  • seed (int or Generator or None.)

  • name (str)

  • nx_stations (int)

  • ny_stations (int)

Return type:

Grid3D

class pycsamt.forward.MT3DForward(freqs, grid, *, method='quasi3d', verbose=True)#

Bases: object

Quasi-3D magnetotelluric forward solver.

Runs MT2DForward on orthogonal XZ and YZ cross-sections extracted from a Grid3D model and assembles the result into an approximate full impedance tensor.

Parameters:
  • freqs (array-like) – Frequencies [Hz].

  • grid (Grid3D) – 3-D resistivity model with surface station positions.

  • method ({'quasi3d'}) – Forward solver method. Currently only 'quasi3d' is implemented. A true 3-D FD Yee-grid solver ('fd3d') is planned for a future phase.

  • verbose (bool) – Print per-profile progress.

Examples

Halfspace — should recover the 1-D MT response:

>>> import numpy as np
>>> from pycsamt.forward.grid3d import Grid3D
>>> from pycsamt.forward.em3d import MT3DForward

>>> freqs = np.logspace(-1, 2, 8)
>>> g = Grid3D.halfspace(rho=100.0, nx=16, ny=16, nz=12,
...     x_max=5_000.0, y_max=5_000.0, z_max=3_000.0,
...     nx_stations=3, ny_stations=3)
>>> resp = MT3DForward(freqs, g).run()
>>> resp.rho_a_xy.shape
(8, 9)

3-D conductive block:

>>> g = Grid3D.block_anomaly(bg_rho=500.0, anomaly_rho=5.0, nx=20, ny=20, nz=15,
...     x_max=6_000.0, y_max=6_000.0, z_max=4_000.0,
...     nx_stations=4, ny_stations=4)
>>> resp = MT3DForward(freqs, g, verbose=False).run()
>>> resp.rho_a_xy.std(axis=1).max() > 1.0   # lateral variation present
True
run()#

Run the forward solver and return the full impedance tensor.

Return type:

ForwardResponse3D

class pycsamt.forward.ForwardResponse3D(freqs, stations_xy, zxy, zyx, zxx, zyy, rho_a_xy, phase_xy, rho_a_yx, phase_yx, rho_a_xx, phase_xx, rho_a_yy, phase_yy, method='quasi3d', grid=None)#

Bases: object

Full (approximate) impedance tensor from the 3-D MT forward solver.

All arrays have shape (n_freqs, n_stations).

Parameters:
  • freqs (ndarray, shape (n_freqs,))

  • stations_xy (ndarray, shape (n_stations, 2)) – Station (x, y) positions [m].

  • zxy (ndarray of complex) – Impedance tensor components [V/A].

  • zyx (ndarray of complex) – Impedance tensor components [V/A].

  • zxx (ndarray of complex) – Impedance tensor components [V/A].

  • zyy (ndarray of complex) – Impedance tensor components [V/A].

  • rho_a_xy (ndarray) – Apparent resistivities [Ω·m].

  • rho_a_yx (ndarray) – Apparent resistivities [Ω·m].

  • rho_a_xx (ndarray) – Apparent resistivities [Ω·m].

  • rho_a_yy (ndarray) – Apparent resistivities [Ω·m].

  • phase_xy (ndarray) – Impedance phases [degrees].

  • phase_yx (ndarray) – Impedance phases [degrees].

  • phase_xx (ndarray) – Impedance phases [degrees].

  • phase_yy (ndarray) – Impedance phases [degrees].

  • method (str) – Solver method ('quasi3d').

  • grid (Grid3D) – Source model.

freqs: ndarray#
stations_xy: ndarray#
zxy: ndarray#
zyx: ndarray#
zxx: ndarray#
zyy: ndarray#
rho_a_xy: ndarray#
phase_xy: ndarray#
rho_a_yx: ndarray#
phase_yx: ndarray#
rho_a_xx: ndarray#
phase_xx: ndarray#
rho_a_yy: ndarray#
phase_yy: ndarray#
method: str = 'quasi3d'#
grid: Grid3D = None#
property n_freqs: int#
property n_stations: int#
property periods: ndarray#
to_feature_array(*, components='xy_yx', log_rho=True, include_phase=True)#

Flatten to a 2-D feature matrix for ML input.

Parameters:
  • components (str) – Comma/underscore-separated component names. E.g. "xy_yx" (default) uses Z_xy and Z_yx; "xy" uses only Z_xy; "all" uses all four.

  • log_rho (bool) – Return log₁₀(ρ_a) instead of ρ_a.

  • include_phase (bool) – Concatenate phase alongside ρ_a.

Returns:

X

Return type:

ndarray, shape (n_stations, n_features)

to_survey_dataset(y_models=None, *, components='xy_yx')#

Wrap this response as a single-survey SurveyDataset3D.

The result can be concatenated with other surveys to build a training dataset for GCNInverter3D.

Parameters:
  • y_models (ndarray or None) – Per-station model target vectors, shape (n_stations, n_params). When None a zero placeholder is used.

  • components (str) – Passed to to_feature_array().

Returns:

Single-survey dataset (n_surveys = 1).

Return type:

SurveyDataset3D

station_response(station)#

Return all tensor components for one station as a dict.

Parameters:

station (int)

Return type:

dict

class pycsamt.forward.ForwardConfig(solver='mt1d', freq_min=0.0001, freq_max=10000.0, n_freqs=30, time_min=1e-06, time_max=0.01, n_times=25, loop_radius=50.0, n_layers_min=3, n_layers_max=7, rho_min=1.0, rho_max=10000.0, depth_max=2000.0, geology=None, n_samples=10000, noise_level=0.05, noise_type='gaussian', include_phase=True, seed=None, n_jobs=1, output_dir='.', output_name='forward_dataset', verbose=True)#

Bases: object

Collect settings that define a 1-D forward modelling / dataset run.

ForwardConfig is the central configuration object for generate_dataset() and the three 1-D solvers. All fields are plain Python scalars so that the dataclass can be serialised to Python, JSON, and YAML without any numpy dependencies.

The recommended workflow is:

  1. Generate a template with write_template().

  2. Edit the values in the generated file.

  3. Load the edited file with from_file().

  4. Optionally call validate() to catch range errors early.

  5. Pass to_dataset_kwargs() to generate_dataset().

Parameters:
  • solver ({'mt1d', 'csamt1d', 'tem1d'}) – Forward solver.

  • freq_min (float) – Frequency grid bounds [Hz] for MT/CSAMT.

  • freq_max (float) – Frequency grid bounds [Hz] for MT/CSAMT.

  • n_freqs (int) – Number of logarithmically spaced frequency points.

  • time_min (float) – Time-gate grid bounds [s] for TEM.

  • time_max (float) – Time-gate grid bounds [s] for TEM.

  • n_times (int) – Number of logarithmically spaced time-gate points.

  • loop_radius (float) – TEM transmitter loop radius [m].

  • n_layers_min (int) – Layer count range (inclusive). Equal values fix the count.

  • n_layers_max (int) – Layer count range (inclusive). Equal values fix the count.

  • rho_min (float) – Resistivity bounds [Ω·m] for the random prior.

  • rho_max (float) – Resistivity bounds [Ω·m] for the random prior.

  • depth_max (float) – Maximum total depth [m] across all layers except the halfspace.

  • geology (str or None) – Geological prior scenario name; overrides rho/depth bounds when set.

  • n_samples (int) – Number of (model, response) pairs to generate.

  • noise_level (float) – Relative noise standard deviation (0 = noise-free).

  • noise_type (str) – Noise model: 'gaussian', 'multiplicative', 'field'.

  • include_phase (bool) – Include impedance phase in the MT/CSAMT feature vector.

  • seed (int or None) – Global random seed for reproducibility.

  • n_jobs (int) – Parallel worker count (-1 = all cores).

  • output_dir (str or None) – Directory for the saved .npz dataset. None skips saving.

  • output_name (str) – Base file name (without extension) for the saved dataset.

  • verbose (bool) – Print progress and summary.

Examples

Default MT1D configuration:

>>> cfg = ForwardConfig()
>>> cfg.solver
'mt1d'

Fixed 5-layer configuration with geology prior:

>>> cfg = ForwardConfig(
...     solver="mt1d",
...     n_layers_min=5,
...     n_layers_max=5,
...     geology="sedimentary",
...     n_samples=10_000,
...     seed=0,
... )

Generate a template file and round-trip:

>>> path = ForwardConfig.write_template("forward_config.py")
>>> cfg = ForwardConfig.from_file(path)
>>> cfg.solver
'mt1d'
solver: str = 'mt1d'#
freq_min: float = 0.0001#
freq_max: float = 10000.0#
n_freqs: int = 30#
time_min: float = 1e-06#
time_max: float = 0.01#
n_times: int = 25#
loop_radius: float = 50.0#
n_layers_min: int = 3#
n_layers_max: int = 7#
rho_min: float = 1.0#
rho_max: float = 10000.0#
depth_max: float = 2000.0#
geology: str | None = None#
n_samples: int = 10000#
noise_level: float = 0.05#
noise_type: str = 'gaussian'#
include_phase: bool = True#
seed: int | None = None#
n_jobs: int = 1#
output_dir: str | None = '.'#
output_name: str = 'forward_dataset'#
verbose: bool = True#
validate()#

Check parameter ranges and raise ValueError on errors.

Call before a long dataset generation run to catch obvious mistakes (e.g. freq_min >= freq_max) before any compute is spent.

Raises:

ValueError – Descriptive message pointing to the offending parameter.

Return type:

None

freq_grid()#

Return the logarithmically spaced frequency array [Hz].

This is the array that would be passed as freqs to the MT/CSAMT solvers and to generate_dataset().

Return type:

numpy.ndarray, shape (n_freqs,)

time_grid()#

Return the logarithmically spaced time-gate array [s].

This is the array that would be passed as times to the TEM solver and to generate_dataset().

Return type:

numpy.ndarray, shape (n_times,)

to_dataset_kwargs()#

Assemble keyword arguments for generate_dataset().

The returned dict is ready to be unpacked directly:

from pycsamt.forward.batch import generate_dataset

ds = generate_dataset(**cfg.to_dataset_kwargs())
Returns:

All keyword arguments for generate_dataset, with numpy arrays built from the stored scalar parameters.

Return type:

dict

to_template(path='forward_config.py', *, fmt=None)#

Write this configuration to an annotated source-of-truth file.

Parameters:
  • path (path-like, default "forward_config.py") – Destination file. Suffixes .py, .json, .yml, and .yaml select the output format automatically.

  • fmt ({"py", "json", "yml", "yaml"}, optional) – Explicit format override. When omitted, the suffix of path is used. Paths without a suffix default to Python.

Returns:

Path of the generated file.

Return type:

pathlib.Path

classmethod write_template(path='forward_config.py', *, fmt=None)#

Generate a documented source-of-truth configuration file.

Creates a file with default parameter values and an inline comment for every parameter explaining its role. Edit the generated file, then load it with from_file().

Parameters:
  • path (path-like, default "forward_config.py") – Destination file. The suffix determines the output format (.py, .json, .yml).

  • fmt ({"py", "json", "yml", "yaml"}, optional) – Explicit format override.

Returns:

Path of the generated file.

Return type:

pathlib.Path

Examples

>>> from pycsamt.forward.config import ForwardConfig
>>> path = ForwardConfig.write_template("my_forward.yml")
>>> path.suffix
'.yml'
classmethod from_file(path, *, strict=True)#

Load a configuration from a source-of-truth file.

Parameters:
  • path (path-like) – Python, JSON, YML, or YAML file generated by write_template() or following the same structure.

  • strict (bool, default True) – If True, unknown keys raise ValueError. If False, unknown keys are silently ignored.

Returns:

Configuration populated from the file values.

Return type:

ForwardConfig

Examples

>>> ForwardConfig.write_template("forward_config.json")
PosixPath('forward_config.json')
>>> cfg = ForwardConfig.from_file("forward_config.json")
>>> cfg.solver
'mt1d'
classmethod read(path, *, strict=True)#

Alias — matches the convention used by ModEmConfig and OccamConfig.

Parameters:
Return type:

ForwardConfig

summary()#

Return a human-readable multi-line summary of the configuration.

Return type:

str

class pycsamt.forward.ForwardConfig2D(freq_min=0.001, freq_max=1000.0, n_freqs=20, nx=40, nz=30, x_max=10000.0, z_max=6000.0, n_pad=8, pad_factor=1.3, bg_rho=100.0, model_type='halfspace', anomaly_rho=5.0, anomaly_x_lo=2000.0, anomaly_x_hi=6000.0, anomaly_z_lo=300.0, anomaly_z_hi=1500.0, n_stations=10, station_x_max=None, verbose=True)#

Bases: object

Collect settings for a 2-D MT finite-difference forward run.

The configuration covers four areas: frequency grid, FD grid geometry, starting earth model, and station layout.

Parameters:
  • freq_min (float) – Frequency range [Hz].

  • freq_max (float) – Frequency range [Hz].

  • n_freqs (int) – Number of logarithmically spaced frequencies.

  • nx (int) – Core cell counts (x, z).

  • nz (int) – Core cell counts (x, z).

  • x_max (float) – Core region extents [m].

  • z_max (float) – Core region extents [m].

  • n_pad (int) – Padding cells per side.

  • pad_factor (float) – Padding growth factor.

  • bg_rho (float) – Background resistivity [Ω·m].

  • model_type ({'halfspace', 'anomaly', 'random'}) – Grid constructor to use.

  • anomaly_rho (float) – Anomaly resistivity [Ω·m] (model_type=’anomaly’ only).

  • anomaly_x_lo (float) – Horizontal anomaly bounds [m].

  • anomaly_x_hi (float) – Horizontal anomaly bounds [m].

  • anomaly_z_lo (float) – Depth anomaly bounds [m].

  • anomaly_z_hi (float) – Depth anomaly bounds [m].

  • n_stations (int) – Number of equally spaced surface stations.

  • station_x_max (float or None) – Right-most station position [m].

  • verbose (bool)

Examples

Default halfspace:

>>> cfg = ForwardConfig2D()
>>> cfg.model_type
'halfspace'
>>> cfg.validate()

Template round-trip:

>>> import tempfile, os
>>> with tempfile.NamedTemporaryFile(suffix='.yml', delete=False) as f:
...     path = f.name
>>> _ = ForwardConfig2D.write_template(path)
>>> cfg2 = ForwardConfig2D.from_file(path)
>>> os.unlink(path)
freq_min: float = 0.001#
freq_max: float = 1000.0#
n_freqs: int = 20#
nx: int = 40#
nz: int = 30#
x_max: float = 10000.0#
z_max: float = 6000.0#
n_pad: int = 8#
pad_factor: float = 1.3#
bg_rho: float = 100.0#
model_type: str = 'halfspace'#
anomaly_rho: float = 5.0#
anomaly_x_lo: float = 2000.0#
anomaly_x_hi: float = 6000.0#
anomaly_z_lo: float = 300.0#
anomaly_z_hi: float = 1500.0#
n_stations: int = 10#
station_x_max: float | None = None#
verbose: bool = True#
validate()#

Raise ValueError on invalid parameter values.

Return type:

None

freq_grid()#

Return the logarithmically spaced frequency array [Hz].

Return type:

ndarray

to_grid(seed=None)#

Construct a Grid2D from this config.

Parameters:

seed (int or None) – Random seed (used only for model_type='random').

Return type:

Grid2D

to_solver_kwargs()#

Return kwargs for MT2DForward.

Return type:

dict[str, Any]

to_template(path='forward_config_2d.py', *, fmt=None)#

Write to an annotated source-of-truth file.

Parameters:
Return type:

Path

classmethod write_template(path='forward_config_2d.py', *, fmt=None)#

Generate a documented source-of-truth file with default values.

Parameters:
  • path (path-like)

  • fmt ({"py", "json", "yml", "yaml"}, optional)

Return type:

pathlib.Path

classmethod from_file(path, *, strict=True)#

Load from a generated source-of-truth file.

Parameters:
  • path (path-like)

  • strict (bool)

Return type:

ForwardConfig2D

classmethod read(path, *, strict=True)#

Load from a generated source-of-truth file.

Parameters:
  • path (path-like)

  • strict (bool)

Return type:

ForwardConfig2D

summary()#
Return type:

str

class pycsamt.forward.ForwardConfig3D(freq_min=0.01, freq_max=1000.0, n_freqs=15, method='quasi3d', nx=20, ny=20, nz=15, x_max=8000.0, y_max=8000.0, z_max=4000.0, n_pad=8, pad_factor=1.3, bg_rho=100.0, model_type='halfspace', anomaly_rho=5.0, anomaly_x_lo=2000.0, anomaly_x_hi=6000.0, anomaly_y_lo=2000.0, anomaly_y_hi=6000.0, anomaly_z_lo=300.0, anomaly_z_hi=1500.0, n_layers=4, lateral_variation=True, corr_length=2000.0, nx_stations=5, ny_stations=5, verbose=True)#

Bases: object

Collect settings for a quasi-3D MT forward modelling run.

Parameters:
  • freq_min (float) – Frequency range [Hz].

  • freq_max (float) – Frequency range [Hz].

  • n_freqs (int)

  • method (str) – Forward method (‘quasi3d’).

  • nx (int) – Core cell counts.

  • ny (int) – Core cell counts.

  • nz (int) – Core cell counts.

  • x_max (float) – Core extents [m].

  • y_max (float) – Core extents [m].

  • z_max (float) – Core extents [m].

  • n_pad (int)

  • pad_factor (float)

  • bg_rho (float)

  • model_type ({'halfspace', 'block_anomaly', 'random_layered'})

  • anomaly_rho (float)

  • anomaly_x_lo (float)

  • anomaly_x_hi (float)

  • anomaly_y_lo (float)

  • anomaly_y_hi (float)

  • anomaly_z_lo (float)

  • anomaly_z_hi (float)

  • n_layers (int)

  • lateral_variation (bool)

  • corr_length (float)

  • nx_stations (int)

  • ny_stations (int)

  • verbose (bool)

Examples

Default halfspace:

>>> cfg = ForwardConfig3D()
>>> cfg.validate()
>>> grid = cfg.to_grid()

Block anomaly:

>>> cfg = ForwardConfig3D(
...     model_type='block_anomaly',
...     anomaly_rho=5.0,
...     anomaly_x_lo=2000., anomaly_x_hi=6000.,
...     anomaly_y_lo=2000., anomaly_y_hi=6000.,
...     anomaly_z_lo=300.,  anomaly_z_hi=1500.,
... )
freq_min: float = 0.01#
freq_max: float = 1000.0#
n_freqs: int = 15#
method: str = 'quasi3d'#
nx: int = 20#
ny: int = 20#
nz: int = 15#
x_max: float = 8000.0#
y_max: float = 8000.0#
z_max: float = 4000.0#
n_pad: int = 8#
pad_factor: float = 1.3#
bg_rho: float = 100.0#
model_type: str = 'halfspace'#
anomaly_rho: float = 5.0#
anomaly_x_lo: float = 2000.0#
anomaly_x_hi: float = 6000.0#
anomaly_y_lo: float = 2000.0#
anomaly_y_hi: float = 6000.0#
anomaly_z_lo: float = 300.0#
anomaly_z_hi: float = 1500.0#
n_layers: int = 4#
lateral_variation: bool = True#
corr_length: float = 2000.0#
nx_stations: int = 5#
ny_stations: int = 5#
verbose: bool = True#
validate()#

Raise ValueError on invalid parameters.

Return type:

None

freq_grid()#

Return the logarithmically spaced frequency array [Hz].

Return type:

ndarray

to_grid(seed=None)#

Construct a Grid3D from this config.

Parameters:

seed (int or None) – Random seed (used only for model_type='random_layered').

Return type:

Grid3D

to_solver_kwargs()#

Return kwargs for MT3DForward.

Return type:

dict[str, Any]

to_template(path='forward_config_3d.py', *, fmt=None)#

Write an annotated source-of-truth file.

Parameters:
Return type:

Path

classmethod write_template(path='forward_config_3d.py', *, fmt=None)#

Generate a documented source-of-truth configuration file.

Parameters:
  • path (path-like)

  • fmt ({"py", "json", "yml", "yaml"}, optional)

Return type:

pathlib.Path

classmethod from_file(path, *, strict=True)#

Load from a source-of-truth file.

Parameters:
  • path (path-like)

  • strict (bool)

Return type:

ForwardConfig3D

classmethod read(path, *, strict=True)#

Load from a source-of-truth file.

Parameters:
  • path (path-like)

  • strict (bool)

Return type:

ForwardConfig3D

summary()#
Return type:

str

pycsamt.forward.plot_response_1d(response, *, modes='both', show_te=<object object>, show_tm=<object object>, color_te=<object object>, color_tm=<object object>, lw=<object object>, marker_te=<object object>, marker_tm=<object object>, ms=<object object>, label_te=<object object>, label_tm=<object object>, title='', figsize=(7, 5.5), axes=None)#

Plot 1-D MT/CSAMT apparent resistivity and phase vs period (or frequency).

Reads visual defaults from PYCSAMT_STYLE and axis behaviour from PYCSAMT_CONTROL.

Parameters:
  • response (ForwardResponse) – Output of MT1DForward or CSAMT1DForward.

  • modes ({'te', 'tm', 'both'} or sequence thereof) – Which modes to plot. For a 1-D response response.rho_a and response.phase hold a single polarisation; pass 'both' (or ['te', 'tm']) to show both ρ_a curves with TE/TM styling on the same axes.

  • show_te (bool) – Override visibility of each mode independently.

  • show_tm (bool) – Override visibility of each mode independently.

  • color_te (colour spec) – Line colours. Default: PYCSAMT_STYLE.mt.te.color / PYCSAMT_STYLE.mt.tm.color.

  • color_tm (colour spec) – Line colours. Default: PYCSAMT_STYLE.mt.te.color / PYCSAMT_STYLE.mt.tm.color.

  • lw (float) – Line width.

  • marker_te (str) – Marker styles.

  • marker_tm (str) – Marker styles.

  • ms (float) – Marker size.

  • label_te (str) – Legend labels.

  • label_tm (str) – Legend labels.

  • title (str) – Figure suptitle.

  • figsize ((float, float))

  • axes (array-like of Axes or None) – Two pre-existing axes (rho, phase). Created when not given.

Returns:

axes[ax_rho, ax_phase]

Return type:

ndarray of Axes, shape (2,)

pycsamt.forward.plot_model_1d(models, labels=None, *, log_rho=True, depth_max=None, lw=<object object>, alpha=<object object>, title='', figsize=(3.8, 5.5), ax=None)#

Plot one or more 1-D layered earth models as resistivity-depth profiles.

Multiple models are coloured with PYCSAMT_STYLE.multiline so the gradient or cycle palette matches all other multi-model plots.

Parameters:
  • models (LayeredModel or list of LayeredModel) – One model or a list to overlay.

  • labels (list of str, optional) – Legend labels; defaults to model name attributes.

  • log_rho (bool) – Use log₁₀ scale on the resistivity axis.

  • depth_max (float or None) – Maximum depth shown [m]. Defaults to the deepest interface × 1.2.

  • lw (float) – Line width. Default: PYCSAMT_STYLE.multiline.lw.

  • alpha (float) – Line alpha. Default: PYCSAMT_STYLE.multiline.alpha.

  • title (str)

  • figsize ((float, float))

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.forward.plot_response_and_model_1d(response, model=None, *, title='', figsize=(10, 5), gridspec_kw=None)#

3-panel figure combining model depth profile, ρ_a, and phase.

This is the canonical “validate and save” view for a 1-D forward run.

Parameters:
Return type:

Figure

pycsamt.forward.plot_model_2d(grid, *, cmap='jet_r', log_scale=True, clip_core=True, vmin=None, vmax=None, show_stations=True, station_preset='inversion', title='', figsize=(11, 4), ax=None)#

Plot the 2-D resistivity model on a colour map.

Station markers and labels are rendered using PYCSAMT_STATION_RENDERING.

Parameters:
  • grid (Grid2D)

  • cmap (str) – Colourmap name. "jet_r" is the geophysical convention (blue = conductive, red = resistive).

  • log_scale (bool) – Display log₁₀(ρ) rather than ρ.

  • clip_core (bool) – Clip to the non-padding core region.

  • vmin (float or None) – Colour limits in log₁₀(Ω·m) when log_scale is True.

  • vmax (float or None) – Colour limits in log₁₀(Ω·m) when log_scale is True.

  • show_stations (bool) – Render station markers and labels on the surface.

  • station_preset (str) – "inversion" or "pseudosection" or "survey".

  • title (str)

  • figsize ((float, float))

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.forward.plot_pseudosection_2d(response, *, mode='te', quantity='rho_a', cmap=<object object>, vmin=None, vmax=None, n_contours=0, show_stations=True, station_preset='pseudosection', title='', figsize=(11, 5), ax=None)#

Plot a 2-D MT pseudo-section (period × station distance).

The colour shows log₁₀(ρ_a) or phase at each (period, station) point. Station markers are rendered via PYCSAMT_STATION_RENDERING.

Parameters:
  • response (ForwardResponse2D)

  • mode ({'te', 'tm'}) – Which polarisation mode to display.

  • quantity ({'rho_a', 'phase'}) – Quantity to colour.

  • cmap (str) – Colour map. Defaults to "jet_r" for ρ_a and "RdBu_r" for phase.

  • vmin (float or None) – Colour limits.

  • vmax (float or None) – Colour limits.

  • n_contours (int) – Number of contour lines overlaid on the colour map (0 = none).

  • show_stations (bool) – Add station axis with markers.

  • station_preset (str)

  • title (str)

  • figsize ((float, float))

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.forward.plot_response_profiles(response, *, mode='te', quantity='rho_a', freq_indices=None, n_freqs_shown=5, lw=<object object>, alpha=<object object>, title='', figsize=(9, 4), ax=None)#

Plot ρ_a (or phase) vs station distance at selected frequencies.

Each frequency is a separate line coloured by PYCSAMT_STYLE.multiline, making it easy to see how the lateral anomaly signature changes with depth (period).

Parameters:
  • response (ForwardResponse2D)

  • mode ({'te', 'tm'})

  • quantity ({'rho_a', 'phase'})

  • freq_indices (sequence of int or None) – Indices into response.freqs to display. When None, n_freqs_shown equally spaced indices are chosen automatically.

  • n_freqs_shown (int) – Number of frequency curves when freq_indices is None.

  • lw (float) – Line width. Default: PYCSAMT_STYLE.multiline.lw.

  • alpha (float) – Line alpha. Default: PYCSAMT_STYLE.multiline.alpha.

  • title (str)

  • figsize ((float, float))

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.forward.plot_model_3d(grid3d, *, cmap='jet_r', log_scale=True, clip_core=True, vmin=None, vmax=None, show_stations=True, title='', figsize=(13, 4.5))#

Three orthogonal slice panels for a 3-D resistivity model.

Displays the XZ (mid-y), YZ (mid-x), and XY (mid-z) cross-sections of grid3d as colour maps. Station positions are overlaid on the XY (map-view) panel.

Parameters:
  • grid3d (Grid3D)

  • cmap (str)

  • log_scale (bool)

  • clip_core (bool)

  • vmin (float or None) – Colour limits in log₁₀(Ω·m) when log_scale is True.

  • vmax (float or None) – Colour limits in log₁₀(Ω·m) when log_scale is True.

  • show_stations (bool)

  • title (str)

  • figsize ((float, float))

Returns:

axes[ax_xz, ax_yz, ax_xy]

Return type:

ndarray of Axes, shape (3,)

pycsamt.forward.plot_response_map_3d(response3d, *, freq_idx=0, component='xy', quantity='rho_a', cmap=<object object>, vmin=None, vmax=None, show_labels=True, marker_size=120.0, title='', figsize=(7, 6), ax=None)#

Map-view scatter of ρ_a or phase at one frequency.

Each station is drawn as a coloured symbol at its (x, y) surface position.

Parameters:
Returns:

ax

Return type:

Axes

pycsamt.forward.plot_response_section_3d(response3d, *, component='xy', quantity='rho_a', y_row=None, cmap=<object object>, vmin=None, vmax=None, n_contours=0, show_stations=True, station_preset='pseudosection', title='', figsize=(11, 5), ax=None)#

Period × station pseudo-section for one 3-D response component.

Stations are sorted along x and projected onto a profile at a selected y-row (default: mid-y row).

Parameters:
  • response3d (ForwardResponse3D)

  • component ({'xy', 'yx', 'xx', 'yy'})

  • quantity ({'rho_a', 'phase'})

  • y_row (int or None) – Index of the y-row. None → midpoint row.

  • cmap (str or _UNSET)

  • vmin (float or None)

  • vmax (float or None)

  • n_contours (int)

  • show_stations (bool)

  • station_preset (str)

  • title (str)

  • figsize ((float, float))

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.forward.plot_tensor_components_3d(response3d, *, freq_idx=0, quantity='rho_a', cmap=<object object>, vmin=None, vmax=None, marker_size=100.0, title='', figsize=(12, 10))#

2 × 2 map panel showing all four impedance tensor components.

Panels are arranged as: [[Z_xx, Z_xy], [Z_yx, Z_yy]]

Parameters:
Returns:

axes

Return type:

ndarray of Axes, shape (2, 2)

2.20.3. Forward Modules#

pycsamt.forward.config

Configuration for pyCSAMT 1-D forward modelling and dataset generation.

pycsamt.forward.config2d

Configuration for pyCSAMT 2-D MT forward modelling.

pycsamt.forward.config3d

Configuration for pyCSAMT quasi-3D MT forward modelling.

pycsamt.forward.em1d

1-D electromagnetic forward solvers.

pycsamt.forward.em2d

2-D magnetotelluric finite-difference forward solver.

pycsamt.forward.em3d

Quasi-3D magnetotelluric forward solver.

pycsamt.forward.grid2d

2-D resistivity grid for MT forward modelling.

pycsamt.forward.grid3d

3-D resistivity grid for MT forward modelling.

pycsamt.forward.synthetic

Synthetic 1-D earth model generators.

pycsamt.forward.noise

Noise models for synthetic EM training data.

pycsamt.forward.batch

Parallelised synthetic EM dataset generator.

pycsamt.forward.plot

Visualisation for pyCSAMT 1-D and 2-D forward models and responses.

2.20.4. Specialized References#