Forward Plotting#
Forward plots are quality-control tools. They help you see whether a model, grid, response, or dataset looks physically plausible before it is used for inversion, AI training, reports, or tutorials.
In pyCSAMT, plotting functions are deliberately lightweight wrappers around Matplotlib. They do not run the solver. They inspect objects that already exist:
Most plotting functions return Matplotlib Axes objects, while composite
figures return a Figure or an array of axes. This makes it easy to customize
labels, save files, or place plots inside your own dashboards and notebooks.
Plot Selection Guide#
Use this table to choose the right function.
Function |
Input |
Use |
|---|---|---|
|
|
Apparent resistivity and phase curves for MT/CSAMT responses. |
|
|
Layered resistivity-depth profiles. |
|
|
One-page validation view for a 1-D forward run. |
|
|
2-D resistivity model with optional station markers. |
|
|
Period-by-station pseudo-section for TE or TM response quantities. |
|
|
Lateral profiles at selected frequencies. |
|
|
Orthogonal XZ, YZ, and XY slices through a 3-D volume. |
|
|
Map view of one tensor component at one frequency. |
|
|
Pseudo-section through one y-row of a 3-D station layout. |
|
|
Four-panel tensor component map. |
The plotting helpers use project style controls from pycsamt.api.style and
axis controls from pycsamt.api.control. For example, apparent resistivity is
displayed in a log-style geophysical convention, and station markers use the
shared station rendering presets.
Saving Figures#
Because the plotting functions return Matplotlib objects, save figures using standard Matplotlib methods.
1import matplotlib.pyplot as plt
2
3axes = plot_response_1d(response)
4fig = axes[0].get_figure()
5fig.savefig("response_1d.png", dpi=200, bbox_inches="tight")
6
7plt.close(fig)
For composite functions that return a figure directly:
1fig = plot_response_and_model_1d(response, model=model)
2fig.savefig("model_and_response.png", dpi=200, bbox_inches="tight")
1-D Response Plots#
Use plot_response_1d for MT and CSAMT apparent resistivity and phase curves.
It returns two axes: [ax_rho, ax_phase].
1import numpy as np
2
3from pycsamt.forward import LayeredModel, MT1DForward, plot_response_1d
4
5model = LayeredModel(
6 resistivity=[100.0, 10.0, 500.0],
7 thickness=[300.0, 800.0],
8 name="conductive_middle_layer",
9)
10
11response = MT1DForward(
12 freqs=np.logspace(-3, 4, 40),
13).run(model)
14
15axes = plot_response_1d(
16 response,
17 modes="both",
18 title="MT1D response",
19)
Read this plot as a quick physical sanity check:
a simple halfspace should produce a smooth response;
a conductive layer should lower apparent resistivity over its sensitivity band;
phase should remain finite and plausible;
sharp noise or oscillation in a clean synthetic response usually signals an input or solver setup problem.
plot_response_1d expects frequency-domain responses. TDEM responses can be
quickly inspected with the response object’s own plot method:
1import numpy as np
2
3from pycsamt.forward import LayeredModel, TEM1DForward
4
5model = LayeredModel([60.0, 250.0, 900.0], [120.0, 700.0])
6response = TEM1DForward(np.logspace(-6, -3, 25)).run(model)
7
8ax = response.plot()
1-D Model Plots#
Use plot_model_1d to inspect one or more layered models. It returns one
axis.
1from pycsamt.forward import LayeredModel, plot_model_1d
2
3truth = LayeredModel([80.0, 25.0, 600.0], [250.0, 900.0], name="truth")
4start = LayeredModel([100.0, 50.0, 500.0], [300.0, 1000.0], name="start")
5
6ax = plot_model_1d(
7 [truth, start],
8 labels=["truth", "starting model"],
9 depth_max=2000.0,
10 title="Layered models",
11)
This is especially useful when preparing a synthetic recovery test. Plot the truth model and the starting model together so it is clear how much prior information the inversion receives.
1-D Composite View#
plot_response_and_model_1d creates the canonical validation figure for a
single 1-D forward run. It returns a Matplotlib Figure.
1import numpy as np
2
3from pycsamt.forward import (
4 LayeredModel,
5 MT1DForward,
6 plot_response_and_model_1d,
7)
8
9model = LayeredModel([100.0, 20.0, 800.0], [300.0, 1000.0])
10response = MT1DForward(np.logspace(-3, 4, 40)).run(model)
11
12fig = plot_response_and_model_1d(
13 response,
14 model=model,
15 title="Forward validation",
16)
If model is omitted, the function returns a two-panel response-only figure.
2-D Grid Model Plots#
Use plot_model_2d to display a pycsamt.forward.Grid2D. By default,
it clips padding cells and displays the core model region.
1from pycsamt.forward import Grid2D, plot_model_2d
2
3grid = Grid2D.with_anomaly(
4 bg_rho=500.0,
5 anomaly_rho=5.0,
6 anomaly_bounds=(2000.0, 6000.0, 300.0, 1500.0),
7 nx=50,
8 nz=35,
9 x_max=10000.0,
10 z_max=6000.0,
11 n_pad=8,
12 n_stations=16,
13)
14
15ax = plot_model_2d(
16 grid,
17 clip_core=True,
18 show_stations=True,
19 station_preset="inversion",
20 title="2-D anomaly model",
21)
Important options:
log_scaleWhen
True, the colour scale is \(\log_{10}\rho\). This is usually best for resistivity models.clip_coreWhen
True, padding cells are hidden. Useclip_core=Falsewhen debugging whether padding is large enough.show_stationsWhen
True, stations are drawn along the surface. If stations appear outside the core model, revisit grid construction.
2-D Pseudo-Sections#
Use plot_pseudosection_2d to show a period-by-station view of a 2-D MT
response. It works on pycsamt.forward.ForwardResponse2D.
1from pycsamt.forward import MT2DForward, plot_pseudosection_2d
2
3response = MT2DForward(
4 freqs=[1.0, 10.0, 100.0],
5 grid=grid,
6 verbose=False,
7).run()
8
9ax = plot_pseudosection_2d(
10 response,
11 mode="te",
12 quantity="rho_a",
13 n_contours=6,
14 title="TE apparent resistivity",
15)
Valid mode values are "te" and "tm". Valid quantity values are
"rho_a" and "phase". Apparent resistivity uses a jet_r style colour
map by default, while phase uses RdBu_r.
Pseudo-sections are not subsurface images. They display response variations as a function of period and station distance. Use them to see whether a target creates a coherent response pattern, not to interpret target geometry directly.
2-D Lateral Response Profiles#
Use plot_response_profiles to inspect how the response varies along the
profile at selected frequencies.
1from pycsamt.forward import plot_response_profiles
2
3ax = plot_response_profiles(
4 response,
5 mode="te",
6 quantity="rho_a",
7 freq_indices=[0, 1, 2],
8 title="Lateral response profiles",
9)
If freq_indices is omitted, the function chooses a small number of
approximately evenly spaced frequencies. This plot is useful for detecting
whether an anomaly response is localized, broad, shifted, or absent.
3-D Model Slice Plots#
Use plot_model_3d to inspect a pycsamt.forward.Grid3D. It returns
three axes: [ax_xz, ax_yz, ax_xy].
1from pycsamt.forward import Grid3D, plot_model_3d
2
3grid3d = Grid3D.block_anomaly(
4 bg_rho=500.0,
5 anomaly_rho=20.0,
6 bounds=(2000.0, 6000.0, 2000.0, 6000.0, 300.0, 1500.0),
7 nx=20,
8 ny=20,
9 nz=15,
10 x_max=8000.0,
11 y_max=8000.0,
12 z_max=4000.0,
13 n_pad=6,
14 nx_stations=5,
15 ny_stations=5,
16)
17
18axes = plot_model_3d(
19 grid3d,
20 clip_core=True,
21 show_stations=True,
22 title="3-D block anomaly",
23)
The three panels show:
XZ slice through the middle y position;
YZ slice through the middle x position;
XY slice through the middle z position.
Station positions are overlaid on the XY panel.
3-D Response Maps#
Use plot_response_map_3d to display one response component at one frequency
as a map-view station scatter plot.
1from pycsamt.forward import MT3DForward, plot_response_map_3d
2
3response3d = MT3DForward(
4 freqs=[1.0, 10.0, 100.0],
5 grid=grid3d,
6).run()
7
8ax = plot_response_map_3d(
9 response3d,
10 freq_idx=0,
11 component="xy",
12 quantity="rho_a",
13 show_labels=True,
14 title="Zxy apparent resistivity map",
15)
Valid components are "xy", "yx", "xx", and "yy". Valid
quantities are "rho_a" and "phase".
3-D Response Sections#
Use plot_response_section_3d for a period-by-station pseudo-section through
one y-row of the station layout.
1from pycsamt.forward import plot_response_section_3d
2
3ax = plot_response_section_3d(
4 response3d,
5 component="xy",
6 quantity="rho_a",
7 y_row=None,
8 n_contours=5,
9)
When y_row=None, the middle y-row is selected. Use an explicit row index to
inspect different profile lines through the station grid.
3-D Tensor Component Panels#
Use plot_tensor_components_3d to compare all four tensor components at one
frequency.
1from pycsamt.forward import plot_tensor_components_3d
2
3axes = plot_tensor_components_3d(
4 response3d,
5 freq_idx=0,
6 quantity="rho_a",
7 title="Tensor component comparison",
8)
The panels are arranged as:
1Zxx Zxy
2Zyx Zyy
For quasi-3-D responses, interpret diagonal components with care. The quasi-3-D solver is useful for survey design and synthetic AI examples, but it is not a substitute for a production full-3-D modelling engine.
Plotting Noisy Responses#
Always plot a few clean and noisy responses before training an AI model. This prevents accidental use of unrealistic noise levels or corrupted axes.
1import numpy as np
2
3from pycsamt.forward import (
4 FieldRealisticNoise,
5 LayeredModel,
6 MT1DForward,
7 plot_response_1d,
8)
9
10model = LayeredModel([100.0, 20.0, 800.0], [300.0, 1000.0])
11clean = MT1DForward(np.logspace(-3, 4, 40)).run(model)
12noisy = FieldRealisticNoise(base_level=0.05).apply(clean, seed=10)
13
14axes = plot_response_1d(clean, label_te="clean", color_te="0.2")
15plot_response_1d(
16 noisy,
17 label_te="noisy",
18 color_te="firebrick",
19 axes=axes,
20)
When overlaying multiple responses, keep labels explicit. A noisy synthetic curve should still look like a possible field response. If the curve is dominated by spikes or negative-looking artefacts, reduce the noise level or review the noise model.
Plotting Dataset Samples#
ForwardDataset stores feature vectors, not full response objects. For
dataset QA, plot feature vectors directly or regenerate selected examples from
the original configuration. A simple feature plot can still catch many issues.
1import matplotlib.pyplot as plt
2import numpy as np
3
4def plot_dataset_sample(dataset, index=0):
5 fig, ax = plt.subplots(figsize=(7, 3), constrained_layout=True)
6 ax.plot(np.asarray(dataset.X[index]).ravel(), lw=1.2)
7 ax.set_xlabel("Feature index")
8 ax.set_ylabel("Feature value")
9 ax.set_title(f"{dataset.solver} sample {index}")
10 return ax
11
12ax = plot_dataset_sample(dataset, index=0)
For MT/CSAMT datasets with phase included, the first half of the feature vector is log apparent resistivity and the second half is phase. Splitting the feature vector before plotting often makes the QA clearer.
1def plot_mt_feature_sample(dataset, index=0):
2 n_freqs = len(dataset.freqs)
3 x = dataset.X[index]
4 log_rho = x[:n_freqs]
5 phase = x[n_freqs:2 * n_freqs]
6
7 fig, axes = plt.subplots(2, 1, figsize=(7, 5), sharex=True)
8 period = 1.0 / dataset.freqs
9 axes[0].semilogx(period, log_rho)
10 axes[0].set_ylabel("log10 rho_a")
11 axes[1].semilogx(period, phase)
12 axes[1].set_ylabel("phase")
13 axes[1].set_xlabel("period (s)")
14 return axes
Plotting Checklist#
Use plots to check:
simple halfspace responses are smooth;
apparent resistivity and phase use the expected axes and units;
conductive anomalies produce plausible low-resistivity response zones;
station positions align with the model;
padding cells are not dominating the displayed interpretation region;
noisy synthetic samples still look like possible field data;
TE/TM and tensor component labels are consistent;
pseudo-section patterns move sensibly with frequency or period;
dataset feature vectors match the model architecture expected by AI code.
Common Mistakes#
Interpreting pseudo-sections as geologyPseudo-sections display response patterns. They are not inversion models.
Forgetting that 2-D and 3-D response arrays are frequency-firstPlotting functions use physical response arrays with shape
(n_freqs, n_stations). Some machine-learning and inversion handoff arrays are station-first.Plotting padded cells as interpretationPadding is numerical buffer. Use
clip_core=Truefor interpretation andclip_core=Falseonly for grid debugging.Comparing figures with different colour limitsUse common
vminandvmaxwhen comparing models or responses.Skipping noisy-sample plotsNoise settings that look reasonable numerically can still produce unrealistic synthetic data.
Next Pages#
Solvers And Grids explains the model and response containers used by the plotting functions.
Synthetic Datasets And Noise explains how generated datasets store features.
From Forward Modelling To Inversion explains how to use plots during synthetic recovery tests.