Synthetic Datasets And Noise#
Synthetic datasets are central to pyCSAMT v2. They are used for AI-assisted inversion, regression tests, solver benchmarks, survey design experiments, and documentation examples. A useful synthetic dataset is not just a collection of curves. It is a reproducible experiment that records:
the model family used to draw earth models;
the forward solver;
the frequency or time axis;
the feature transform;
the target vector layout;
the noise model;
the random seed;
the train, validation, and test split.
The forward package currently provides two main dataset containers:
Container |
Shape |
Main use |
|---|---|---|
|
|
1-D MT, CSAMT, and TDEM model-response pairs. |
|
|
Pseudo-3-D multi-station datasets for spatial AI models. |
The dataset generators are intentionally separate from the interactive forward solvers. Use individual solver objects when you need to inspect one model in detail. Use dataset generators when you need many reproducible samples.
1-D Dataset Generation#
Use pycsamt.forward.generate_dataset() to create batches of independent
1-D forward responses. It supports solver="mt1d", solver="csamt1d",
and solver="tem1d".
1import numpy as np
2
3from pycsamt.forward import generate_dataset
4
5dataset = generate_dataset(
6 solver="mt1d",
7 n_samples=1000,
8 freqs=np.logspace(-3, 4, 40),
9 n_layers=(3, 7),
10 rho_range=(1.0, 10000.0),
11 depth_max=3000.0,
12 noise_level=0.05,
13 noise_type="field",
14 include_phase=True,
15 seed=42,
16 n_jobs=1,
17 output="runs/forward/mt1d_dataset.npz",
18)
19
20print(dataset)
21print(dataset.X.shape)
22print(dataset.y.shape)
Each sample is generated by:
drawing a
pycsamt.forward.LayeredModel;running the selected 1-D solver;
optionally applying noise;
converting the response into a feature vector;
converting the model into a target vector.
The generated dataset is a pycsamt.forward.ForwardDataset.
ForwardDataset Contract#
ForwardDataset stores the data needed for 1-D AI training or numerical
experiments.
Attribute |
Shape |
Meaning |
|---|---|---|
|
|
Feature vectors generated from synthetic responses. |
|
|
Target model vectors. Shorter models are padded with |
|
|
Frequency axis for MT and CSAMT datasets. |
|
|
Time gates for TDEM datasets. |
|
structured array or |
Per-sample metadata, currently including layer count and noise level. |
|
string |
Solver name used to generate the dataset. |
For MT and CSAMT, the default feature vector contains log-scaled apparent resistivity and phase:
1[log10(rho_a(f_0)), ..., log10(rho_a(f_n)),
2 phase(f_0), ..., phase(f_n)]
When include_phase=False, only log10(rho_a) is used.
For TDEM, the feature vector contains log-scaled transient decay values:
1[log10(abs(dBz_dt(t_0))), ..., log10(abs(dBz_dt(t_n)))]
The target vector is produced from the layered model:
1[log10(rho_0), log10(rho_1), ..., log10(rho_n),
2 thickness_0, thickness_1, ..., thickness_n_minus_1]
When n_layers is a range, different samples can have different target
lengths. generate_dataset pads shorter targets with NaN so the final
y array is rectangular. Training code should mask these padded values
rather than treating them as physical targets.
Saving, Loading, And Splitting#
Datasets can be saved as compressed .npz files. Passing output=... to
generate_dataset saves the dataset automatically, or you can call
dataset.save yourself.
1from pycsamt.forward import ForwardDataset
2
3dataset.save("runs/forward/mt1d_dataset.npz")
4
5loaded = ForwardDataset.load("runs/forward/mt1d_dataset.npz")
6
7train, val, test = loaded.split(
8 val_frac=0.1,
9 test_frac=0.1,
10 seed=0,
11)
12
13print(len(train), len(val), len(test))
Always split with a fixed seed when reporting model performance. This keeps the training, validation, and test partitions reproducible across machines.
Configuration-Driven Generation#
For repeatable studies, prefer a configuration file over a long script. The
pycsamt.forward.ForwardConfig object records the same options accepted
by generate_dataset and can write annotated templates.
1from pycsamt.forward import ForwardConfig, generate_dataset
2
3ForwardConfig.write_template("runs/forward/mt1d_config.yml")
4
5cfg = ForwardConfig.from_file("runs/forward/mt1d_config.yml")
6dataset = generate_dataset(**cfg.to_dataset_kwargs())
This pattern makes it easier to archive the exact generation recipe alongside
the resulting .npz file.
TDEM Datasets#
TDEM datasets use time gates and solver="tem1d". The target layout remains
the same layered-earth vector, but dataset.times is populated instead of
dataset.freqs.
1import numpy as np
2
3from pycsamt.forward import generate_dataset
4
5tdem_dataset = generate_dataset(
6 solver="tem1d",
7 n_samples=250,
8 times=np.logspace(-6, -3, 25),
9 n_layers=4,
10 rho_range=(5.0, 3000.0),
11 depth_max=1500.0,
12 loop_radius=40.0,
13 noise_level=0.03,
14 noise_type="gaussian",
15 seed=11,
16 n_jobs=1,
17 verbose=True,
18)
19
20print(tdem_dataset.times.shape)
21print(tdem_dataset.X.shape)
TDEM generation can be slower than MT/CSAMT generation because the solver uses
numerical integration. Start with a small n_samples value when designing a
new TDEM dataset recipe.
Geological Priors#
Instead of drawing all layer resistivities from a broad uniform range, you can
ask generate_dataset to draw from a named geological prior. The underlying
model generator uses pycsamt.forward.LayeredModel.from_geology().
1from pycsamt.forward import generate_dataset
2
3geothermal = generate_dataset(
4 solver="mt1d",
5 n_samples=500,
6 geology="geothermal",
7 noise_level=0.04,
8 noise_type="field",
9 seed=5,
10)
Named priors are useful when the AI model should learn a specific geological setting rather than an overly broad synthetic universe. Keep a broad test set when possible, because overly narrow priors can make a model look better than it really is.
Noise Models#
Noise makes synthetic data more realistic and prevents AI models from learning only idealized solver curves. pyCSAMT keeps noise separate from the forward solver: first compute a clean physical response, then perturb it.
Noise model |
How it behaves |
Good use |
|---|---|---|
|
Adds random perturbations in log-response space. For MT/CSAMT it
perturbs |
Baseline training, regression tests, quick robustness checks. |
|
Applies log-normal style perturbations, useful for values spanning many orders of magnitude. |
Dynamic-range heavy data such as TDEM decays. |
|
Uses frequency-dependent noise with power-line harmonics and optional MT dead-band inflation. |
MT/CSAMT training datasets intended to resemble field behaviour. |
You can use named noise models through generate_dataset:
1dataset = generate_dataset(
2 solver="mt1d",
3 n_samples=1000,
4 noise_level=0.05,
5 noise_type="field",
6 seed=42,
7)
Or apply noise directly to a single response:
1import numpy as np
2
3from pycsamt.forward import FieldRealisticNoise, LayeredModel, MT1DForward
4
5model = LayeredModel([100.0, 20.0, 800.0], [300.0, 1000.0])
6response = MT1DForward(np.logspace(-3, 4, 40)).run(model)
7
8noise = FieldRealisticNoise(
9 base_level=0.03,
10 powerline_freq=50.0,
11 dead_band=True,
12)
13
14noisy_response = noise.apply(response, seed=42)
15profile = noise.noise_profile(response.freqs)
FieldRealisticNoise requires a frequency-domain response. Use Gaussian or
multiplicative noise for TDEM responses.
Clean And Noisy Dataset Pairs#
For benchmarking, it is often useful to generate one clean dataset and one noisy dataset with the same model draw settings. Use different output paths and keep the same seed.
1import numpy as np
2
3from pycsamt.forward import generate_dataset
4
5common = dict(
6 solver="mt1d",
7 n_samples=500,
8 freqs=np.logspace(-3, 4, 30),
9 n_layers=4,
10 rho_range=(1.0, 10000.0),
11 depth_max=2500.0,
12 include_phase=True,
13 seed=100,
14 n_jobs=1,
15)
16
17clean = generate_dataset(
18 **common,
19 noise_level=0.0,
20 output="runs/forward/clean_mt1d.npz",
21)
22
23noisy = generate_dataset(
24 **common,
25 noise_level=0.05,
26 noise_type="gaussian",
27 output="runs/forward/noisy_mt1d.npz",
28)
This does not guarantee identical sample ordering if implementation details change in the future, but it is the intended reproducible pattern within the current generator.
Pseudo-3-D Survey Datasets#
pycsamt.forward.generate_dataset_3d() creates multi-station survey
datasets for graph-style or spatial AI models. It currently uses 1-D MT
responses at each station, but draws station models from a spatially correlated
Gaussian random field. This creates lateral structure in the target model while
keeping the forward computation lightweight.
1from pycsamt.forward import generate_dataset_3d
2
3surveys = generate_dataset_3d(
4 solver="mt1d",
5 n_surveys=500,
6 n_stations=25,
7 n_layers=4,
8 extent=10000.0,
9 corr_length=2000.0,
10 noise_level=0.03,
11 noise_type="gaussian",
12 include_phase=True,
13 seed=7,
14 output="runs/forward/survey3d_dataset.npz",
15)
16
17print(surveys)
18print(surveys.X.shape)
19print(surveys.y.shape)
20print(surveys.coords.shape)
SurveyDataset3D has this contract:
Attribute |
Shape |
Meaning |
|---|---|---|
|
|
Per-station feature vectors. |
|
|
Per-station layered model targets. |
|
|
Fixed station coordinates shared by all surveys. |
|
|
Frequency axis used by every station response. |
|
structured array |
Per-survey correlation length and noise level. |
All surveys share the same station layout. That is intentional: graph models
can build one adjacency matrix from surveys.coords and reuse it for all
survey realizations.
1from pycsamt.forward import SurveyDataset3D
2
3loaded = SurveyDataset3D.load("runs/forward/survey3d_dataset.npz")
4train, val, test = loaded.split(seed=0)
5
6print(train.X.shape)
7print(train.coords[:3])
The corr_length parameter controls lateral smoothness. Values shorter than
station spacing create rapidly varying station models. Values much longer than
station spacing create smoother surveys.
2-D And Quasi-3-D Solver Datasets#
The high-level batch generator is focused on 1-D and pseudo-3-D AI datasets.
When you need full 2-D finite-difference or quasi-3-D response datasets, build
a small custom loop around pycsamt.forward.Grid2D,
pycsamt.forward.MT2DForward, pycsamt.forward.Grid3D, or
pycsamt.forward.MT3DForward.
1import numpy as np
2
3from pycsamt.forward import Grid2D, MT2DForward
4
5freqs = np.logspace(-1, 3, 12)
6samples = []
7
8for seed in range(10):
9 grid = Grid2D.random(
10 nx=40,
11 nz=25,
12 x_max=6000.0,
13 z_max=3000.0,
14 n_stations=12,
15 seed=seed,
16 )
17 response = MT2DForward(freqs=freqs, grid=grid, verbose=False).run()
18 samples.append(response.to_feature_array(mode="both"))
19
20X = np.stack(samples, axis=0)
21print(X.shape)
This approach keeps large numerical experiments explicit. It also lets you store exactly the grid, response, and metadata needed by the study.
Dataset QA#
Before using a synthetic dataset for training, reporting, or benchmarking, perform basic quality assurance.
1import numpy as np
2
3def check_forward_dataset(dataset):
4 print(dataset)
5 print("X:", dataset.X.shape, dataset.X.dtype)
6 print("y:", dataset.y.shape, dataset.y.dtype)
7 print("finite X:", np.isfinite(dataset.X).all())
8 print("target NaN count:", np.isnan(dataset.y).sum())
9 if dataset.meta is not None:
10 print("layer counts:", np.unique(dataset.meta["n_layers"]))
11 print("noise levels:", np.unique(dataset.meta["noise_level"]))
12
13check_forward_dataset(dataset)
Checklist:
plot random samples before training;
confirm there are no
NaNor infinite values inX;confirm
NaNvalues inyare only padding;verify the feature length expected by the model architecture;
inspect clean and noisy examples side by side;
keep frequency or time axes with the dataset;
keep train, validation, and test splits deterministic;
save the generation configuration and code version;
record the noise model and random seed.
Recommended Archive Layout#
A robust synthetic dataset directory should contain more than the .npz
file.
1synthetic_mt1d_v001/
2 config.yml
3 dataset.npz
4 split_seed.txt
5 qa_summary.txt
6 plots/
7 random_samples.png
8 feature_histograms.png
9 noise_profile.png
10 notes.md
For production AI experiments, treat this directory as an immutable artifact. If you change the frequency grid, prior, noise model, target layout, or split seed, create a new dataset version.
Common Mistakes#
Training on clean data onlyClean data is useful for debugging, but a model trained only on clean curves usually fails on field-like data.
Ignoring padded target valuesVariable-layer datasets contain
NaNpadding iny. Loss functions must mask those values.Changing the frequency grid between train and testThe feature vector layout depends on the axis. A model trained on one frequency grid should not be evaluated on another without an explicit preprocessing strategy.
Using field noise for TDEMFieldRealisticNoiseis frequency-domain. Use Gaussian or multiplicative noise for TDEM.Mixing station layouts in one graph datasetSurveyDataset3Dassumes a fixed station coordinate array so one adjacency matrix can be reused. Use separate datasets when layouts differ.
Next Pages#
Solvers And Grids explains the model containers and solver outputs.
Forward Plotting explains how to inspect generated samples.
From Forward Modelling To Inversion explains how to use synthetic responses in inversion recovery tests.