16.1. Overview#

16.1.1. Forward And Model Integrations#

The pycsamt.forward and pycsamt.models packages are related, but they should not be treated as the same layer.

Package

Main responsibility

Typical use

pycsamt.forward

Compute controlled forward responses inside Python using pyCSAMT model containers, solvers, dataset generators, noise models, and plotting helpers.

Test survey design, generate training data, validate inversion assumptions, compare response behaviour, and build reproducible synthetic examples.

pycsamt.models

Integrate external modelling and inversion engines such as Occam2D, ModEM, and MARE2DEM.

Prepare native engine files, run external binaries, load engine outputs, and manage backend-specific inversion projects.

Use pycsamt.forward when you need a controlled synthetic dataset or response from a known model. Use pycsamt.models when you need to prepare, run, or load a native external-engine project. The two layers meet when a forward synthetic experiment is converted into an inversion benchmark: the known model gives the reference answer, and the inversion workflow tests whether that answer can be recovered from the predicted data.

16.1.2. Package Map#

The public forward API is built around a small set of object families.

Object family

Main objects

Role

Configuration

ForwardConfig, ForwardConfig2D, ForwardConfig3D

Store reproducible frequency/time axes, model ranges, station layouts, solver options, noise settings, and output choices.

Model containers

LayeredModel, Grid2D, Grid3D

Represent 1-D layered earths, 2-D profile grids, and quasi-3-D resistivity volumes.

Solvers

MT1DForward, CSAMT1DForward, TEM1DForward, MT2DForward, MT3DForward

Compute predicted electromagnetic responses from model containers.

Response containers

ForwardResponse, ForwardResponse2D, ForwardResponse3D

Hold predicted apparent resistivity, phase, transient values, impedance tensor components, survey geometry, and array conversion helpers.

Noise models

GaussianNoise, MultiplicativeNoise, FieldRealisticNoise

Convert ideal synthetic responses into more realistic observations.

Dataset containers

ForwardDataset, SurveyDataset3D

Store synthetic feature and target arrays, metadata, splits, and compressed .npz archives.

Plotting helpers

plot_response_1d, plot_model_2d, plot_pseudosection_2d, plot_model_3d, plot_response_map_3d, and related functions

Inspect models, responses, tensor components, noisy examples, and generated datasets before they are trusted downstream.

16.1.3. Core Workflow#

A typical forward modelling workflow has six stages. The smallest version starts with a 1-D layered earth because the geometry is easy to audit: resistivity is a function of depth only, \(\rho=\rho(z)\), and the MT solver predicts the impedance \(Z(f)\) for each frequency. The reported curves are then derived in the usual way from impedance magnitude and phase, with apparent resistivity proportional to \(|Z|^2/f\). In other words, the model is compact, but the response still carries the frequency-dependent signature that an inversion or interpretation workflow would need.

 1>>> import numpy as np
 2
 3>>> from pycsamt.forward import LayeredModel, MT1DForward, plot_response_and_model_1d
 4
 5>>> freqs = np.logspace(-2, 3, 40)
 6>>> model = LayeredModel(
 7...     resistivity=[100.0, 15.0, 800.0],
 8...     thickness=[250.0, 700.0],
 9... )
10
11>>> solver = MT1DForward(freqs)
12>>> response = solver.run(model)
13>>> print(f"response: {response.method}, {response.freqs.size} frequencies")
14response: MT1D, 40 frequencies
15>>> print(f"rho_a range: {response.rho_a.min():.2f}-{response.rho_a.max():.2f} Ohm.m")
16rho_a range: 20.57-475.29 Ohm.m
17>>> print(f"phase range: {response.phase.min():.2f}-{response.phase.max():.2f} deg")
18phase range: 18.14-61.70 deg
19
20>>> fig = plot_response_and_model_1d(response, model)
21>>> fig.savefig("runs/forward/mt1d_response.png", dpi=200)
MT1D layered-earth model with apparent resistivity and phase response.

A three-layer MT1D example generated from the code above. The conductive middle layer lowers \(\rho_a\) over part of the period range, while the phase curve records the transition between layers.#

For production work, the same idea should be driven by a configuration file and archived with outputs:

  1. define the physical question and target dimensionality;

  2. create a ForwardConfig* template;

  3. build a model or grid from the configuration;

  4. run the selected solver;

  5. apply a documented noise model when simulating observations;

  6. plot and archive the model, response, configuration, and metadata.

ForwardConfig is the object that carries stages 1-2 and part of stage 5: it records the sampled frequency grid (or time gate grid for TEM), the solver name, and the noise settings that a collaborator would need to reproduce the run, rather than leaving them scattered across a script. Forward Configuration covers all three ForwardConfig* classes, template files, and validation in depth; the compact version below only threads a single config through stages 3-5 for the same three-layer model as above, this time with a documented noise model applied.

 1>>> from pycsamt.forward import (
 2...     ForwardConfig, LayeredModel, MT1DForward, GaussianNoise,
 3...     plot_response_and_model_1d,
 4... )
 5
 6>>> cfg = ForwardConfig(
 7...     solver="mt1d", freq_min=1e-2, freq_max=1e3, n_freqs=40,
 8...     noise_level=0.05, seed=0,
 9... )
10>>> cfg.validate()
11>>> freqs = cfg.freq_grid()
12>>> print(f"freqs: {freqs.size} points, {freqs.min():.2e}-{freqs.max():.2e} Hz")
13freqs: 40 points, 1.00e-02-1.00e+03 Hz
14
15>>> model = LayeredModel(resistivity=[100.0, 15.0, 800.0], thickness=[250.0, 700.0])
16>>> clean = MT1DForward(freqs).run(model)
17>>> noisy = GaussianNoise(level=cfg.noise_level).apply(clean, seed=cfg.seed)
18>>> print(f"clean rho_a[0] = {clean.rho_a[0]:.2f} Ohm.m, noisy rho_a[0] = {noisy.rho_a[0]:.2f} Ohm.m")
19clean rho_a[0] = 475.29 Ohm.m, noisy rho_a[0] = 482.22 Ohm.m
20>>> print(f"clean phase[0] = {clean.phase[0]:.2f} deg, noisy phase[0] = {noisy.phase[0]:.2f} deg")
21clean phase[0] = 33.22 deg, noisy phase[0] = 30.39 deg
22
23>>> fig = plot_response_and_model_1d(noisy, model, title="Noisy MT1D response (5% Gaussian)")
24>>> fig.savefig("runs/forward/mt1d_noisy_response.png", dpi=200)
MT1D layered-earth model with a 5 percent Gaussian-noise response overlay.

Same three-layer model, now with a noise model applied to \(\rho_a\) and phase independently, seeded for reproducibility. noise_level=0.05 is a standard deviation in \(\log_{10}\rho_a\)-space, not a direct linear-relative percentage – for this seeded draw the realized linear relative scatter in \(\rho_a\) across all 40 points has a standard deviation of about 9.0% (largest single deviation 23%), and phase scatter has a standard deviation of about 2.4° (matching the default phase_level = noise_level * 45). Neither shows any systematic dependence on period – each frequency’s perturbation is drawn independently, so the noisiest point in a given realization is wherever the random draw happened to land largest, not a fixed feature of the curve.#

Both examples share the same physical model and solver; the difference is entirely in what gets carried forward for someone else to reproduce. The first is fine for a quick, throwaway check. The second – config, seed, and noise level all explicit and archivable – is the version worth keeping in a project.

16.1.4. Choosing A Path#

Different users enter the forward section with different goals.

Goal

Start here

Then read

Understand the physics and vocabulary

Forward Modelling Concepts

Solvers And Grids, Forward Plotting

Build a reproducible synthetic run

Forward Configuration

Solvers And Grids, From Forward Modelling To Inversion

Generate training data for AI inversion

Synthetic Datasets And Noise

Forward Configuration, Forward Plotting, AI And Model-Zoo Agents

Compare 1-D, 2-D, and quasi-3-D behaviour

Solvers And Grids

Forward Modelling Concepts, Forward Plotting

Prepare an inversion benchmark

From Forward Modelling To Inversion

Classical model integrations, Pipeline System

Diagnose a generated dataset or response

Forward Plotting

Synthetic Datasets And Noise, From Forward Modelling To Inversion

Get a response with a checkable accuracy claim, or a production-backed forward check

Maxwell Adapter Layer

Maxwell Adapters, Maxwell Analytic Benchmarks

16.1.5. Relationship To Theory#

Forward modelling is practical, but it is not detached from theory. When a plot or synthetic response looks surprising, the relevant background pages are: