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, withrandom,blocky,smooth, andfrom_geologyconstructors.- 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:
_Base1DForward1-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:
- class pycsamt.forward.TEM1DForward(times, loop_radius=50.0, moment=1.0, n_freqs=64, n_lam=100)#
Bases:
_Base1DForward1-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;
empymodis 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
empymoduses 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 threadsbackend_optionsstraight 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 threadsbackend_optionsstraight 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:
- class pycsamt.forward.CSAMT1DForward(freqs, source_offset=None, dipole_length=1000.0)#
Bases:
_Base1DForward1-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:
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:
- 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:
objectContainer 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.
- 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 returnslog10(|dBz_dt|)(length n_times).
- plot(ax=None, **kwargs)#
Quick diagnostic plot. Returns the Axes used.
- class pycsamt.forward.MT2DForward(freqs, grid, *, verbose=True)#
Bases:
object2-D magnetotelluric finite-difference forward solver.
Solves both TE and TM modes for a list of frequencies on the provided
Grid2Dand returns aForwardResponse2Dwith apparent resistivity and phase at all station positions.- Parameters:
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:
- 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:
objectOutput 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 ofscipy.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 ofscipy.sparse.linalg.spsolve; it is not an iteration count, since the solver is direct.grid (Grid2D) – The model grid used for the forward run.
- station_response(station)#
Return all response arrays for one station as a dict.
- to_feature_array(*, mode='both', log_rho=True, include_phase=True)#
Flatten to a 2-D feature matrix for ML training.
- Parameters:
- Return type:
ndarray, shape (n_stations, n_features)
- class pycsamt.forward.LayeredModel(resistivity, thickness, depth=None, name='')#
Bases:
object1-D layered earth model.
The earth has
n_layerslayers. 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.])
- 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 isTrue.
- classmethod from_vector(v, n_layers, *, log_rho=True, name='')#
Reconstruct a
LayeredModelfrom 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:
- 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:
- Return type:
- 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:
- Return type:
- 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:
- Return type:
- classmethod from_geology(name, *, seed=None)#
Generate a random model using a predefined geological prior.
- Parameters:
- Raises:
KeyError – If name is not in
GEOLOGY_PRIORS.- Return type:
- plot(ax=None, *, log_scale=True, depth_max=None, label=None, **kwargs)#
Plot the 1-D resistivity–depth profile.
- class pycsamt.forward.Grid2D(dx, dz, resistivity, x_stations, n_pad=0, name='')#
Bases:
objectNon-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)
- 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 –
0at the first core column – and the grid’s own absolute x coordinate (0at the outer edge of the left padding) part ways. Seevalue_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 (
0at 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 –0at the first core (station-carrying) column, matchingpycsamt.geology.Borehole’sxconvention. Set this when sampling ground truth for a borehole or any other profile-relative position.
- Return type:
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.
- 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 = 0and numbered0, 1, …, nx.- Returns:
indices
- Return type:
ndarray of int, shape (n_stations,)
- harmonic_mean_x()#
Harmonic-mean conductivity at x-directed cell interfaces.
Used by the TM-mode FD assembler.
- harmonic_mean_z()#
Harmonic-mean conductivity at z-directed cell interfaces.
- 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:
- 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.thicknessdefines 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:
- 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:
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
LayeredModelacross x on a uniform fine grid (ashalfspace()does, notfrom_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_mon the downthrown block) rather than a fixed per-column swap – the whole stack drops bythrow_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 (
0at 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 – seepycsamt.geology.FaultTracefor 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:
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_layershorizontal layers are drawn from a log-uniform prior. When lateral_variation isTrue, 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:
- 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 * factorand each subsequent cell isfactortimes wider.- Parameters:
- 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:
_BaseNoiseModelAdd 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
ForwardResponsewith noise added.- Parameters:
response (ForwardResponse) – Clean forward response.
seed (int or None) – Random seed for reproducibility.
- Return type:
- 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:
_BaseNoiseModelFrequency-dependent noise model for MT/CSAMT training data.
Simulates three dominant noise sources present in field data:
Power-line harmonics — Gaussian spikes at 50 Hz, 100 Hz, 150 Hz (or 60/120/180 Hz for North American data).
MT dead band — elevated noise at ~1 000–3 000 s period (≈ 3×10⁻⁴ – 10⁻³ Hz) due to reduced natural source energy.
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
ForwardResponsewith noise added.- Parameters:
response (ForwardResponse) – Clean forward response.
seed (int or None) – Random seed for reproducibility.
- Return type:
- class pycsamt.forward.MultiplicativeNoise(sigma_log10=0.05)#
Bases:
_BaseNoiseModelLog-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.05corresponds to ≈ ±12 % variation.
- apply(response, *, seed=None)#
Return a new
ForwardResponsewith noise added.- Parameters:
response (ForwardResponse) – Clean forward response.
seed (int or None) – Random seed for reproducibility.
- Return type:
- pycsamt.forward.add_gaussian_noise(response, level=0.05, *, seed=None)#
Convenience wrapper: add Gaussian noise at a given relative level.
- Parameters:
response (ForwardResponse)
level (float) – Relative noise standard deviation.
seed (int or None)
- Returns:
New response with noise applied.
- Return type:
- 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:
- 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.
-1uses all CPU cores.output (str or None) – If given, save the dataset to a
.npzfile at this path.verbose (bool) – Print progress.
- Return type:
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:
objectNumpy 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.
- 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
GCNInverter3Dtraining.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.
-1uses all CPU cores.output (str or None) – If given, save the dataset to a compressed
.npzfile.verbose (bool) – Print progress.
- Returns:
Container with arrays
X(n_surveys, n_stations, n_features),y(n_surveys, n_stations, n_params), andcoords(n_stations, 2).- Return type:
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:
objectNumpy 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.
- class pycsamt.forward.Grid3D(dx, dy, dz, resistivity, stations_xy, n_pad=0, name='')#
Bases:
objectNon-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)
- xz_slice(yi)#
Extract an XZ (east-west) 2-D slice at y-cell yi.
Returns a
Grid2Dwhose 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 ofstations_xy[station_indices[k]].
- Parameters:
yi (int)
- Return type:
- yz_slice(xi)#
Extract a YZ (north-south) 2-D slice at x-cell xi.
The
Grid2Dhorizontal axis is mapped to y (northing); itsdxarray containsdyand its resistivity isresistivity[:, :, xi].
- column_profile_3d(xi, yi)#
Return the 1-D resistivity/thickness profile at cell (xi, yi).
- 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).
- 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:
- 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].)
nx_stations (int)
ny_stations (int)
name (str)
- Return type:
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:
- class pycsamt.forward.MT3DForward(freqs, grid, *, method='quasi3d', verbose=True)#
Bases:
objectQuasi-3D magnetotelluric forward solver.
Runs
MT2DForwardon orthogonal XZ and YZ cross-sections extracted from aGrid3Dmodel 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:
- 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:
objectFull (approximate) impedance tensor from the 3-D MT forward solver.
All arrays have shape
(n_freqs, n_stations).- Parameters:
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.
- to_feature_array(*, components='xy_yx', log_rho=True, include_phase=True)#
Flatten to a 2-D feature matrix for ML input.
- Parameters:
- 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). WhenNonea zero placeholder is used.components (str) – Passed to
to_feature_array().
- Returns:
Single-survey dataset (
n_surveys = 1).- Return type:
- 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:
objectCollect settings that define a 1-D forward modelling / dataset run.
ForwardConfigis the central configuration object forgenerate_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:
Generate a template with
write_template().Edit the values in the generated file.
Load the edited file with
from_file().Optionally call
validate()to catch range errors early.Pass
to_dataset_kwargs()togenerate_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
.npzdataset.Noneskips 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'
- validate()#
Check parameter ranges and raise
ValueErroron 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:
- 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:
- 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:
- 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.yamlselect 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:
- 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:
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 raiseValueError. IfFalse, unknown keys are silently ignored.
- Returns:
Configuration populated from the file values.
- Return type:
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:
- 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:
objectCollect 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)
- validate()#
Raise
ValueErroron invalid parameter values.- Return type:
None
- to_solver_kwargs()#
Return kwargs for
MT2DForward.
- to_template(path='forward_config_2d.py', *, fmt=None)#
Write to an annotated source-of-truth file.
- 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:
- classmethod from_file(path, *, strict=True)#
Load from a generated source-of-truth file.
- Parameters:
path (path-like)
strict (bool)
- Return type:
- 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:
objectCollect 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., ... )
- validate()#
Raise
ValueErroron invalid parameters.- Return type:
None
- to_solver_kwargs()#
Return kwargs for
MT3DForward.
- to_template(path='forward_config_3d.py', *, fmt=None)#
Write an annotated source-of-truth file.
- 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:
- classmethod from_file(path, *, strict=True)#
Load from a source-of-truth file.
- Parameters:
path (path-like)
strict (bool)
- Return type:
- 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_STYLEand axis behaviour fromPYCSAMT_CONTROL.- Parameters:
response (ForwardResponse) – Output of
MT1DForwardorCSAMT1DForward.modes ({'te', 'tm', 'both'} or sequence thereof) – Which modes to plot. For a 1-D response
response.rho_aandresponse.phasehold 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.
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.multilineso 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)
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:
response (ForwardResponse)
model (LayeredModel or None) – If given, plotted in the left panel. When
None, only the two response panels are shown.title (str) – Figure suptitle.
gridspec_kw (dict or None) – Passed to
subplots().
- 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)
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)
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.freqsto display. WhenNone, 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)
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:
- 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)
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]]
2.20.3. Forward Modules#
|
Configuration for pyCSAMT 1-D forward modelling and dataset generation. |
|
Configuration for pyCSAMT 2-D MT forward modelling. |
|
Configuration for pyCSAMT quasi-3D MT forward modelling. |
|
1-D electromagnetic forward solvers. |
|
2-D magnetotelluric finite-difference forward solver. |
|
Quasi-3D magnetotelluric forward solver. |
|
2-D resistivity grid for MT forward modelling. |
|
3-D resistivity grid for MT forward modelling. |
|
Synthetic 1-D earth model generators. |
|
Noise models for synthetic EM training data. |
|
Parallelised synthetic EM dataset generator. |
|
Visualisation for pyCSAMT 1-D and 2-D forward models and responses. |