Prepare an Occam2D Inversion#

This tutorial shows how to prepare a 2-D Occam2D inversion from a cleaned EDI survey. It follows the practical pyCSAMT v2 workflow:

  1. load and inspect the EDI survey;

  2. run QC and optional static-shift correction;

  3. choose the data modes, frequency band, error floors, and mesh controls;

  4. build native Occam2D input files;

  5. validate the run directory before launching the external solver;

  6. optionally run Occam2D and inspect the result.

Occam2D is an external inversion program. pyCSAMT can prepare, validate, run, load, and plot the workflow files, but an actual inversion run requires a compiled Occam2D executable.

What You Will Learn#

After this tutorial you should be able to:

When To Use Occam2D#

Occam2D is a good first production backend when:

  • stations form a mostly 2-D profile;

  • the geologic target is expected to vary mainly with profile distance and depth;

  • the deliverable should be a smooth resistivity section;

  • TE and TM apparent resistivity and phase curves are available;

  • you want native Occam-style files that can be archived and reviewed.

Occam2D is not the best first choice when:

  • the survey geometry is strongly 3-D;

  • station coordinates are unreliable;

  • source effects dominate the selected frequency band;

  • only a few stations remain after QC;

  • the target is a sharp blocky body that must be represented with hard boundaries rather than smooth gradients.

For backend choice, see Choosing A Model Backend.

Prepare the Survey#

Start from the same EDI loading and QC path used in the previous tutorials.

 1from pathlib import Path
 2
 3from pycsamt.api import read_edis
 4from pycsamt.emtools.qc import station_confidence_table
 5
 6run_root = Path("runs")
 7run_root.mkdir(exist_ok=True)
 8
 9survey = read_edis(
10    "data/AMT/WILLY_DATA/L18PLT",
11    recursive=False,
12    strict=False,
13    progress=False,
14)
15sites = survey.collection
16
17confidence = station_confidence_table(
18    sites,
19    method="composite",
20    api=True,
21)
22print(confidence)

Example confidence output from the bundled WILLY L18PLT line:

station  confidence  coverage
18-001A    0.709038       1.0
18-002U    0.774634       1.0
18-003A    0.713303       1.0
18-004A    0.732182       1.0
18-005U    0.771060       1.0

Do not prepare inversion files until station names, coordinates, frequency coverage, and obvious quality problems have been checked. Occam2D will happily invert a bad file set if the native files are syntactically valid.

Apply Optional Static-Shift Correction#

If the QC review supports a static-shift correction, apply it to a copy of the survey and keep the original data available.

 1from pycsamt.emtools.ss import correct_ss_ama
 2
 3corrected = correct_ss_ama(
 4    sites,
 5    sort_by="name",
 6    half_window=3,
 7    weights="tri",
 8    max_skew=None,
 9    inplace=False,
10    recursive=False,
11    verbose=1,
12)

If static-shift evidence is weak, use sites directly for the first trial and compare that model with a corrected run later.

The previous tutorial showed that L18PLT has high skew and large exploratory static-shift factors. For a conservative first Occam2D input build, it is often better to start from sites and keep the corrected copy for a comparison run.

Choose an Occam Configuration#

OccamConfig is the source-of-truth object for a native Occam2D run. It controls data rows, error floors, mesh geometry, startup options, filenames, and executable discovery.

 1from pycsamt.models.occam2d import OccamConfig
 2
 3cfg = OccamConfig(
 4    modes=["TE", "TM"],
 5    freq_min=0.1,
 6    freq_max=1000.0,
 7    error_floor_rho=0.05,
 8    error_floor_phase=0.5,
 9    n_layers=32,
10    n_airlayers=4,
11    cell_size_horizontal=100.0,
12    cell_size_vertical_top=25.0,
13    depth_scale=1.15,
14    target_misfit=1.0,
15    max_iterations=80,
16    initial_rho=100.0,
17    data_file="OccamDataFile.dat",
18    mesh_file="Occam2DMesh",
19    model_file="Occam2DModel",
20    startup_file="Startup",
21    binary_name="Occam2D",
22)

Important options:

modes

["TE"], ["TM"], or ["TE", "TM"]. TE uses the Zxy component and TM uses Zyx. Each selected mode writes apparent resistivity and phase rows.

freq_min and freq_max

Frequency limits in hertz. Use these to exclude unstable short-period or long-period bands before writing Occam data.

error_floor_rho and error_floor_phase

Minimum data uncertainties. These are critical because Occam seeks a normalized misfit. Unrealistically small errors force the model to chase noise.

n_layers and cell_size_horizontal

Main controls on mesh size and parameter count. Smaller cells and more layers increase detail but also increase runtime and non-uniqueness.

target_misfit and max_iterations

Startup controls for the external solver.

Write the configuration next to the run so it can be reviewed later:

1workdir = run_root / "L18PLT_occam2d_native"
2workdir.mkdir(parents=True, exist_ok=True)
3
4cfg.to_template(workdir / "occam2d.yml")

Build Native Occam2D Files#

Use pycsamt.models.occam2d.InputBuilder to write the four native input files required before the external executable can run.

 1from pycsamt.models.occam2d import InputBuilder
 2
 3builder = InputBuilder(
 4    sites,
 5    workdir=workdir,
 6    config=cfg,
 7    verbose=1,
 8)
 9
10builder.build(title="L18PLT pyCSAMT v2 Occam2D preparation")
11print(builder.summary())

For the tutorial configuration, the builder writes a complete native input set:

InputBuilder summary
  workdir   : runs/L18PLT_occam2d_native
  sites     : 28
  freqs     : 39
  data pts  : 4368
  mesh      : 42 x 36 cells
  params    : 512
  modes     : ['TE', 'TM']

The build chain is:

  1. OccamData.from_edi converts the site collection into Occam data rows.

  2. OccamMesh.from_data builds a finite-element mesh from station offsets.

  3. OccamModel.from_mesh maps mesh cells to inversion parameters.

  4. OccamStartup.from_model writes the iteration-zero startup vector and inversion controls.

Expected files:

runs/L18PLT_occam2d_native/
  occam2d.yml
  OccamDataFile.dat
  Occam2DMesh
  Occam2DModel
  Startup

The figures in this tutorial are generated by docs/scripts/generate_tutorial_occam2d.py. The first figure shows how the selected TE/TM apparent-resistivity and phase rows are distributed by station.

Occam2D data rows by station and data type for the L18PLT tutorial line.

Inspect the Built Objects#

Before running the solver, inspect the generated data, mesh, model, and startup objects.

1print("sites:", builder.data.n_sites)
2print("frequencies:", builder.data.n_frequencies)
3print("data rows:", builder.data.n_data)
4print("mesh cells:", builder.mesh.n_xcells, "x", builder.mesh.n_zcells)
5print("parameters:", builder.model.n_params)
6print("startup file:", cfg.startup_file)

Example output:

sites: 28
frequencies: 39
data rows: 4368
mesh cells: 42 x 36
parameters: 512
startup file: Startup

The selected period band and mesh skeleton provide a quick visual audit before you send the directory to an Occam2D executable:

Selected Occam2D frequency band and mesh skeleton for the L18PLT tutorial line.

If the number of data rows is unexpectedly small, check modes, freq_min, freq_max, and station filtering. If the parameter count is very large, increase cell_size_horizontal or reduce n_layers for the first trial.

Validate File Types#

pyCSAMT can detect native Occam2D file types. Use this to catch path mistakes before sending a run to a cluster or external workstation.

 1from pycsamt.models.occam2d.validation import detect_file_type
 2
 3for filename in (
 4    cfg.data_file,
 5    cfg.mesh_file,
 6    cfg.model_file,
 7    cfg.startup_file,
 8):
 9    path = workdir / filename
10    print(path.name, "->", detect_file_type(path))

Example output:

OccamDataFile.dat  -> data
Occam2DMesh        -> mesh
Occam2DModel       -> model
Startup            -> startup

The data file is much larger than the mesh, model, and startup files because it stores every selected station-frequency response row:

Relative sizes of native Occam2D files written by InputBuilder.

This does not prove the geophysics is correct, but it confirms that the native files look like the expected Occam file classes.

Build a TM-Only First Trial#

For many CSAMT and AMT profiles, a TM-only first run is easier to interpret than a joint TE/TM run. Use a separate work directory for the experiment.

 1tm_workdir = run_root / "L18PLT_occam2d_tm"
 2
 3tm_builder = InputBuilder(
 4    sites,
 5    workdir=tm_workdir,
 6    config=OccamConfig(),
 7    verbose=1,
 8)
 9
10tm_builder.build(
11    modes=["TM"],
12    freq_min=0.2,
13    freq_max=500.0,
14    error_floor_rho=0.07,
15    error_floor_phase=1.0,
16    n_layers=28,
17    cell_size=150.0,
18    title="L18PLT TM-only Occam2D first trial",
19)
20
21print(tm_builder.summary())

Example output:

InputBuilder summary
  workdir   : runs/L18PLT_occam2d_tm
  sites     : 28
  freqs     : 35
  data pts  : 1960
  mesh      : 42 x 33 cells
  params    : 448
  modes     : ['TM']

Keep each experiment in its own directory. Names such as L18PLT_occam2d_tm and L18PLT_occam2d_te_tm are much easier to audit than repeatedly overwriting run01.

Run Occam2D#

When a compiled Occam2D executable is available, launch the solver with pycsamt.models.occam2d.OccamRunner.

 1from pycsamt.models.occam2d import OccamRunner
 2
 3runner = OccamRunner(
 4    workdir=workdir,
 5    binary_path="/usr/local/bin/Occam2D",
 6    startup_file=cfg.startup_file,
 7    verbose=1,
 8)
 9
10exit_code = runner.run(
11    max_iter=80,
12    target_misfit=1.0,
13    auto_compile=False,
14)
15print("Occam2D exit code:", exit_code)

OccamRunner does not build input files. It only runs a prepared directory. If the executable is not available, stop after the build step and move the native directory to the environment where Occam2D can run.

Load Finished Results#

After an external run completes, load the directory with pycsamt.models.occam2d.InversionResult.

1from pycsamt.models.occam2d import InversionResult
2
3result = InversionResult(workdir)
4
5print("iteration files:", len(result.iter_files))
6print("response files:", len(result.resp_files))
7print("final RMS:", result.final_rms)
8print("model grid:", None if result.rho_2d is None else result.rho_2d.shape)

rho_2d stores log10 resistivity on the Occam mesh. Convert to ohm metres only when you need physical resistivity values:

1import numpy as np
2
3if result.rho_2d is not None:
4    rho_ohm_m = 10.0 ** result.rho_2d
5    print(np.nanmin(rho_ohm_m), np.nanmax(rho_ohm_m))

Plot Model and Misfit#

Use the Occam plotting helpers after a completed run.

 1import matplotlib.pyplot as plt
 2from pycsamt.models.occam2d import PlotMisfit, PlotModel, PlotPseudo
 3
 4figure_dir = workdir / "figures"
 5figure_dir.mkdir(exist_ok=True)
 6
 7fig = PlotModel(result).plot()
 8fig.savefig(figure_dir / "occam2d_model.png", dpi=200, bbox_inches="tight")
 9plt.close(fig)
10
11fig = PlotMisfit(result).plot()
12fig.savefig(figure_dir / "occam2d_misfit.png", dpi=200, bbox_inches="tight")
13plt.close(fig)
14
15fig = PlotPseudo(result).plot()
16fig.savefig(figure_dir / "occam2d_observed_pseudo.png", dpi=200, bbox_inches="tight")
17plt.close(fig)

If a plot helper reports that a response, iteration, or log file is missing, check that the external solver completed and that output files were written in the same directory as the input files.

Backend-Neutral Preparation#

The backend-neutral inversion API can prepare the same native files while returning a common inversion result object. Keep run_external=False when you only want to build and validate files.

 1from pycsamt.inversion import InversionConfig, run_inversion
 2
 3inv_cfg = InversionConfig(
 4    method="mt",
 5    dimension="2d",
 6    backend="occam2d",
 7    data=sites,
 8    workdir=run_root / "L18PLT_occam2d_workflow",
 9    run_external=False,
10    error_floor=0.05,
11    phase_error=0.5,
12    backend_options={
13        "config": {
14            "modes": ["TE", "TM"],
15            "freq_min": 0.1,
16            "freq_max": 1000.0,
17            "n_layers": 32,
18            "target_misfit": 1.0,
19            "initial_rho": 100.0,
20        },
21    },
22)
23
24workflow_result = run_inversion(inv_cfg)
25print(workflow_result.status)
26print(workflow_result.files)
27print(workflow_result.warnings)

Use the native InputBuilder path when you want direct control over Occam2D objects. Use the backend-neutral path when a larger application, agent, or pipeline needs one interface for several inversion engines.

CLI Equivalent#

Build Occam2D inputs from the command line:

1pycsamt invert build data/AMT/WILLY_DATA/L18PLT \
2    --solver occam2d \
3    --workdir runs/L18PLT_occam2d_cli \
4    --modes TE,TM \
5    --freq 0.1:1000 \
6    --error-floor-rho 0.05 \
7    --error-floor-phase 0.5 \
8    --n-layers 32 \
9    --cell-size 100

Inspect the working directory:

1pycsamt invert status runs/L18PLT_occam2d_cli
2pycsamt invert status runs/L18PLT_occam2d_cli --format json

Run the external solver when the executable is available:

1pycsamt invert run runs/L18PLT_occam2d_cli \
2    --solver occam2d \
3    --max-iter 80 \
4    --target-misfit 1.0

Summarize and plot results:

1pycsamt invert results runs/L18PLT_occam2d_cli
2pycsamt invert plot model runs/L18PLT_occam2d_cli \
3    --save runs/L18PLT_occam2d_cli/model.png \
4    --dpi 200
5pycsamt invert plot misfit runs/L18PLT_occam2d_cli \
6    --save runs/L18PLT_occam2d_cli/misfit.png \
7    --dpi 200

Preparation Checklist#

Before launching a production Occam2D run, confirm that:

  • EDI files have been loaded and validated;

  • station order and coordinates are correct;

  • QC and static-shift decisions are documented;

  • TE/TM mode choice is intentional;

  • frequency limits exclude noisy or source-affected bands;

  • error floors are realistic;

  • mesh size is reasonable for the station spacing and target depth;

  • OccamDataFile.dat, Occam2DMesh, Occam2DModel, and Startup are present;

  • the raw, corrected, and inversion-input directories are separate;

  • the external Occam2D executable can be found in the run environment.

Troubleshooting#

The data file has too few rows

Check modes, freq_min, freq_max, and whether the selected stations contain finite impedance values for the requested components.

The mesh is too large

Increase cell_size_horizontal, reduce n_layers, or start with a narrower frequency band for the first trial.

The runner cannot find Occam2D

Pass binary_path explicitly, place Occam2D in the run directory, or make sure the executable is on PATH.

The RMS does not approach the target

Revisit error floors, static-shift correction, bad stations, mode choice, and whether the structure is too 3-D for a 2-D smooth inversion.

The final model is too smooth

That is often expected for Occam inversion. Compare residuals, try different error floors, and consider whether another backend is more appropriate for sharp boundaries.

See Also#

Read an EDI Survey

Load input EDI files.

Inspect and QC a Survey

Review data quality before inversion.

Correct Static Shift

Review static-shift correction.

Occam2D

Full Occam2D backend documentation.

Choosing A Model Backend

Decide between Occam2D, ModEM, MARE2DEM, and other engines.

Inversion API

Inversion API reference.

Inversion Commands

Inversion CLI reference.