6.2.8. MARE2DEM#
pycsamt.models.mare2dem provides the pyCSAMT integration layer for
MARE2DEM, a 2.5-D finite-element electromagnetic modelling and inversion
code. MARE2DEM supports magnetotelluric (MT) and controlled-source
electromagnetic (CSEM) workflows, adaptive triangular meshes,
topography, and MPI execution.
pyCSAMT does not vendor the compiled MARE2DEM executable. Instead, it provides the tools needed to manage a MARE2DEM project around that executable:
configuration templates for source, binary, MPI, and file-name settings;
source download/build/location helpers;
native
.emdata,.resistivity,.poly,.settings, log, group RMS, and data-group readers/writers;survey builders for MT and CSEM synthetic or prepared data files;
ZMM-to-MARE2DEM MT data conversion;
geometry utilities for topography, UTM conversion, profile projection, polygon simplification, triangle-region assignment, and area-of-interest estimation;
input builders, runners, result loaders, plotting helpers, model-difference utilities, merge tools, and synthetic-noise helpers.
This page is a practical guide to those pieces. It focuses on how a user should prepare, run, inspect, and archive a MARE2DEM project from pyCSAMT.
6.2.8.1. When To Use MARE2DEM#
Use the MARE2DEM integration when a project needs a native 2.5-D finite-element workflow rather than a pyCSAMT built-in inversion. Common reasons include:
a survey geometry that is naturally profile-based but not adequately treated by a simple 1-D or 2-D approximation;
MT, CSEM, or combined data that must be represented in MARE2DEM’s native
.emdataformat;topography, seafloor, receiver, or transmitter geometry that should be explicitly represented;
adaptive triangular discretization through Triangle/PSLG
.polygeometry;existing MARE2DEM project files that need Python-side validation, conversion, plotting, or result loading;
an HPC workflow where native input files are prepared locally, run on a cluster, and loaded back into pyCSAMT afterward.
MARE2DEM is not the fastest path for every inversion. If you only need a high-level backend-neutral 2-D run, start with Choosing A Model Backend. MARE2DEM is most valuable when the native file set and engine-specific control are part of the scientific workflow.
6.2.8.2. Package Map#
The public MARE2DEM package surface is intentionally broad. It includes low level file readers, high level lifecycle helpers, and project utilities.
Area |
Main objects |
Purpose |
|---|---|---|
Configuration |
|
Stores source location, compiler overrides, binary/MPI settings, inversion controls, initial model value, and default file names. |
Source and binary |
|
Locates, downloads, builds, and reports status for the external MARE2DEM source tree and executable. |
Build inputs |
|
Writes a MARE2DEM run directory from an existing |
Execute |
|
Builds the command line, handles MPI options, runs the executable from
the working directory, and can return an |
Load results |
|
Scans output directories, parses iteration logs, exposes RMS history, convergence state, final model, observed data, and response data. |
Native I/O |
|
Reads and writes MARE2DEM-native project files. |
Data management |
|
Builds, converts, and merges MT/CSEM data products. |
Geometry |
|
Handles topography, survey projection, coordinate conversion, polygon cleanup, and triangle-region utilities. |
QC and interpretation |
|
Supports synthetic tests, model comparison, convergence review, survey layout QC, and mesh/geometry inspection. |
6.2.8.3. Configuration#
Mare2DEMConfig is the source-of-truth object for MARE2DEM runs. It is a
plain dataclass, so it can be created in Python, written as a
configuration file template, edited outside Python, and loaded again.
1>>> from pycsamt.models.mare2dem import Mare2DEMConfig
2
3>>> cfg = Mare2DEMConfig(
4... source_dir="/opt/mare2dem/source",
5... binary="MARE2DEM",
6... use_mpi=True,
7... n_procs=16,
8... mpi_command="mpirun",
9... max_iterations=120,
10... target_rms=1.0,
11... initial_rho=10.0,
12... data_file="line12.emdata",
13... resistivity_file="line12.resistivity",
14... settings_file="line12.settings",
15... )
16
17>>> cfg.to_template("runs/line12_mare2dem_v01/mare2dem.yml")
18>>> loaded = Mare2DEMConfig.from_file("runs/line12_mare2dem_v01/mare2dem.yml")
19>>> print(loaded.binary, loaded.max_iterations, loaded.resistivity_stem)
20MARE2DEM 120 line12
The configuration groups five concerns.
Concern |
Fields |
Meaning |
|---|---|---|
Source management |
|
Where the Fortran source lives and which compiler commands should be used when building. |
Binary and MPI |
|
How pyCSAMT should locate and launch the executable. |
Inversion control |
|
Iteration limit and normalized RMS target written into the native model/settings files. |
Initial model |
|
Starting homogeneous half-space resistivity, in ohm metres. |
File names |
|
Native file names used by builders and runners. |
The resistivity_stem property returns the stem of
resistivity_file. MARE2DEM receives this stem on the command line and then
derives related filenames from it. For example, line12.resistivity is
passed as line12, which is exactly what loaded.resistivity_stem
printed above.
6.2.8.4. Source And Binary Management#
SourceManager handles the external source tree. It resolves a source
directory in this order:
the explicit
source_dirargument passed toSourceManager;Mare2DEMConfig.source_dir;the
PYCSAMT_MARE2DEM_SOURCEenvironment variable;the package
_source/directory when it is writable, which is common in editable development installs;a platform user-data directory.
Unlike Occam2D, pyCSAMT does not bundle MARE2DEM’s Fortran source. The
package _source/ directory may exist as an empty download destination,
but it is not a populated source tree until SourceManager.download succeeds. There is no
compiled binary to discover on a typical machine, and none is bundled for
this documentation build either – the examples below load a bundled,
already-finished sample run instead (see Inspect Results), the same way
ModEM does for ModEM.
This is a genuinely working, tested path, not an aspirational one: a real
MARE2DEM binary has been built from this exact source-management code
(inside WSL2 Ubuntu with Intel’s free oneAPI toolchain) and its forward
solve validated against the analytic half-space MT response to within the
binary’s own ~0.2-0.8% adaptive-mesh-refinement error – see
MARE2DEM for the full build transcript and
Mare2DEMAdapter’s module
docstring for the physics-validation account (a separate concern from
building the binary: that adapter is the AI-inversion forward-solver
integration in pycsamt.forward.maxwell, distinct from the classical
inversion workflow this page documents, and talks to the same compiled
MARE2DEM executable through its own PSLG-based mesh convention).
The complete supported build procedure, including Intel oneAPI, MKL, WSL,
download behavior, and the pycsamt build wrapper, is documented at
MARE2DEM. In a Linux or WSL installation that uses the
standard oneAPI location, the short form is:
source /opt/intel/oneapi/setvars.sh
pycsamt build mare2dem --auto-install -y \
--source-dir /opt/mare2dem/source
On macOS or a non-standard Linux installation, source the corresponding
setvars.sh path for that oneAPI installation before running the same
pycsamt build command. Choose a writable --source-dir and reuse that
exact path as Mare2DEMConfig.source_dir; /opt/mare2dem/source is only
an example and may require administrator-created directory permissions.
Do not run that command from native Windows PowerShell or cmd.exe;
MARE2DEM cannot be compiled natively on Windows. Enter WSL first and keep the
source, build, and run paths visible inside that WSL environment.
Use status before downloading or building, and again afterward to record
the resolved source and executable paths:
1>>> from pycsamt.models.mare2dem import Mare2DEMConfig, SourceManager
2
3>>> cfg = Mare2DEMConfig(source_dir="/opt/mare2dem/source")
4>>> source = SourceManager(config=cfg, verbose=1)
5
6>>> source.print_status()
7MARE2DEM SourceManager status
8────────────────────────────────────────
9 source_dir : /opt/mare2dem/source
10 downloaded : False
11 built : False
12 binary : (not found)
13 FC compiler : mpifort
14 CC compiler : mpicc
15 MKLROOT : (not found — required)
When source code is not present, download can use Git or a source archive.
When source code is present but the binary is missing, build compiles the
external code. These direct Python calls are equivalent to the lifecycle
managed by the build wrapper and are useful when a cluster needs a custom
Make include file:
1>>> from pycsamt.models.mare2dem import SourceManager
2
3>>> source = SourceManager(source_dir="/opt/mare2dem/source", verbose=1)
4
5>>> # Network access and compiler availability are environment-dependent.
6>>> # source.download(method="auto")
7>>> # binary = source.build(clean_first=False)
8>>> # print(binary)
MARE2DEM requires an MPI Fortran/C toolchain and Intel MKL for
ScaLAPACK/BLACS. Prefer Intel mpiifx/mpiicx (current oneAPI
releases) or the classic mpiifort/mpiicc (older ones) with oneAPI
initialized; generic mpifort/mpicc may be detected by
SourceManager but do not remove the MKL requirement and are not a
guarantee of a usable build. The missing MKLROOT in the status above is
that prerequisite being detected before compilation. See
MARE2DEM for a real, successful build transcript and the
toolchain-detection bugs a real build run against a current oneAPI
installation surfaced and fixed.
6.2.8.5. Binary Resolution#
Mare2DEMRunner resolves the executable through:
Mare2DEMConfig.binaryonPATH(an explicit executable path is also accepted byshutil.which);<source_dir>/<binary>;the platform user-data binary location.
Use runner.command as a dry-run check before launching an inversion.
Note that command() formats the configured command but does not prove that
the executable or MPI launcher exists. Use SourceManager.resolve_binary
for the executable preflight performed by run():
1>>> from pycsamt.models.mare2dem import (
2... Mare2DEMConfig,
3... Mare2DEMRunner,
4... SourceManager,
5... )
6
7>>> cfg = Mare2DEMConfig(
8... binary="MARE2DEM",
9... use_mpi=True,
10... n_procs=8,
11... mpi_command="mpirun",
12... resistivity_file="line12.resistivity",
13... )
14
15>>> runner = Mare2DEMRunner("runs/line12_mare2dem_v01/native", config=cfg)
16>>> binary = SourceManager(config=cfg).resolve_binary()
17>>> if binary is None:
18... raise FileNotFoundError(
19... "MARE2DEM was not built; see the MARE2DEM compilation guide"
20... )
21>>> print(binary)
22/opt/mare2dem/source/MARE2DEM
23>>> print(runner.command("line12"))
24mpirun -np 8 MARE2DEM line12
For cluster workflows, put the command string and the loaded module list in
the run provenance manifest. The same native directory can then be
executed by a job scheduler and loaded later with InversionResult.
6.2.8.6. Native Files#
MARE2DEM projects revolve around native files. pyCSAMT treats these as first-class scientific records, not temporary build artifacts.
File |
Reader/writer |
Role |
|---|---|---|
|
|
Observed or synthetic MT/CSEM/DC data, receiver/transmitter metadata, UTM origin, frequencies, and data rows. |
|
|
Predicted response data produced by the engine. |
|
|
Resistivity parameters, free/fixed flags, bounds, prejudice values, and references to data, settings, and polygon files. |
|
|
Triangle PSLG geometry: nodes, segments, holes, and regions. |
|
|
Parallel decomposition and inversion settings. |
|
|
Group definitions used for grouped RMS diagnostics. |
Group RMS logs |
|
Per-group RMS evolution, useful for diagnosing which data families are controlling the inversion. |
Iteration logs |
|
Iteration number, RMS misfit, roughness, Lagrange multiplier, and convergence state. |
The validation helpers classify common MARE2DEM files by filename pattern alone – unlike the Occam2D and ModEM validators, they do not need the file to exist or read its contents, so they also work as a naming-convention check before a file has been written:
1>>> from pycsamt.models.mare2dem import detect_file_type, is_response_file
2
3>>> print(detect_file_type("line12.emdata"))
4Mare2DEMFileType.EMDATA
5>>> print(detect_file_type("line12.resistivity"))
6Mare2DEMFileType.RESISTIVITY
7>>> print(is_response_file("line12_MARE2DEM.emdata"))
8True
6.2.8.7. Build A Run Directory#
InputBuilder writes a minimal MARE2DEM input set. It can start from an
existing .emdata file, an in-memory EMDataFile, or MT/CSEM survey
configuration objects. This example starts from a real bundled synthetic MT
line:
1>>> from pycsamt.models.mare2dem import InputBuilder, Mare2DEMConfig
2
3>>> cfg = Mare2DEMConfig(
4... initial_rho=10.0,
5... max_iterations=120,
6... target_rms=1.0,
7... data_file="line12.emdata",
8... resistivity_file="line12.resistivity",
9... settings_file="line12.settings",
10... )
11
12>>> builder = InputBuilder(config=cfg, verbose=1)
13>>> files = builder.build(
14... "data/mare2dem/demo_mt_inversion/demo_mt_synth.emdata",
15... workdir="runs/line12_mare2dem_v01/native",
16... )
17
18>>> print(files["data"].name, files["model"].name, files["settings"].name)
19line12.emdata line12.resistivity line12.settings
The builder writes:
the data file, copied or generated into the run directory;
a homogeneous starting
.resistivityfile based oninitial_rho;a
.settingsfile.
For production inversions, the generated starting model is often only the
first step. Review the .resistivity and .poly inputs before launching
the external code.
6.2.8.8. Create MT Data#
MTSurveyConfig and make_data_file are useful for synthetic tests or
for constructing simple MT-native files from survey arrays.
1>>> import numpy as np
2
3>>> from pycsamt.models.mare2dem import MTSurveyConfig, make_data_file
4
5>>> mt = MTSurveyConfig(
6... frequencies=np.logspace(-3, 3, 25),
7... rx_y=np.linspace(-6000.0, 6000.0, 31),
8... rx_type="land",
9... lTE=True,
10... lTM=True,
11... lTipper=False,
12... )
13
14>>> em = make_data_file(
15... "runs/line12_mare2dem_v01/native/line12_mt.emdata",
16... topo=0.0,
17... mt=mt,
18... )
19
20>>> print(em.n_data)
213100
For real MT processing, pyCSAMT also includes ZMM readers and converters
(read_zmm, make_mt_data_from_zmm). No ZMM sample ships with pyCSAMT,
so they are not exercised with captured output here – read_zmm(path)
returns a ZMMStation with .name, .latitude/.longitude, and
impedance/tipper arrays, and make_mt_data_from_zmm accepts a list of such
files plus error-floor and topography options, mirroring make_data_file.
Check the generated data rows carefully. Conversion utilities help with file mechanics, but the interpreter still owns station selection, component choice, frequency band selection, and error-floor policy.
6.2.8.9. Create CSEM Data#
CSEMSurveyConfig builds a controlled-source survey with transmitter and
receiver layout parameters.
1>>> import numpy as np
2
3>>> from pycsamt.models.mare2dem import CSEMSurveyConfig, make_data_file
4
5>>> csem = CSEMSurveyConfig(
6... frequencies=np.array([0.25, 0.5, 1.0, 2.0]),
7... rx_y=np.linspace(-4000.0, 4000.0, 17),
8... tx_y=np.array([-2500.0, 0.0, 2500.0]),
9... rx_type="marine",
10... tx_type="edipole",
11... lEx=True,
12... lEy=True,
13... lBx=True,
14... lBy=True,
15... )
16
17>>> em = make_data_file(
18... "runs/csem_line_v01/native/csem_line.emdata",
19... topo=-1000.0,
20... csem=csem,
21... )
22
23>>> print(em.n_data)
241632
rx_type and tx_type look like the same kind of setting but are not:
rx_type is a placement mode ("land", "marine", "amphibious"),
while tx_type is a dipole physics type and only understands
"edipole" or "bdipole". Passing a placement word such as
"marine" as tx_type does not raise an error – it is silently
written to the .emdata file’s Type column as the literal string
"marine", which downstream pyCSAMT code that reads that column back
(for example the merge tools in Merge And Noise Utilities) treats as
"bdipole", since only an exact "edipole" match counts otherwise. A
towed marine CSEM transmitter is normally an electric dipole, so the
correct value here is tx_type="edipole", not tx_type="marine".
For CSEM projects, geometry QC is essential. Plot receiver/transmitter
locations, confirm signs and offsets, and record whether coordinates are
local profile coordinates, UTM coordinates, or a transformed system – see
Plotting And QC below for PlotSurveyLayout.
6.2.8.10. Merge And Noise Utilities#
MARE2DEM workflows often require combining data families or creating synthetic observations from a forward response. pyCSAMT provides utilities for both. Merging a real bundled MT line with a real bundled CSEM line demonstrates a genuine joint-data use case:
1>>> from pycsamt.models.mare2dem import merge_data_files
2
3>>> merged = merge_data_files(
4... [
5... "data/mare2dem/demo_mt_inversion/demo_mt_synth.emdata",
6... "data/mare2dem/demo_csem/demo_csem.emdata",
7... ],
8... "runs/joint_v01/native/joint.emdata",
9... )
10
11>>> print(merged.n_data)
12852
Synthetic noise is useful for controlled tests and algorithm validation.
Setting seed makes the result reproducible – record it in the run
provenance manifest alongside the noise parameters:
1>>> from pycsamt.models.mare2dem import (
2... NoiseConfig,
3... add_synthetic_noise,
4... read_emdata,
5... write_emdata,
6... )
7
8>>> response = read_emdata("data/mare2dem/demo_mt_inversion/demo.6.resp")
9>>> noise = NoiseConfig(
10... mt_rel_noise=0.05,
11... mt_abs_noise_tipper=0.01,
12... )
13
14>>> synthetic = add_synthetic_noise(response, noise, seed=42)
15>>> write_emdata(synthetic, "runs/synthetic_v01/native/synthetic.emdata")
16>>> print(synthetic.n_data, response.n_data)
17572 572
Do not treat noisy synthetic data as field data. Keep synthetic sources, random seeds, and noise parameters in the provenance notes.
6.2.8.11. Geometry And Topography#
The MARE2DEM integration includes geometry helpers because geometry mistakes are one of the easiest ways to produce convincing but wrong models.
Helper |
Use |
|---|---|
|
Read and interpolate topography/seafloor profiles and compute local slopes. |
|
Convert coordinates with |
|
Estimate profile orientation and project stations onto a survey line. |
|
Simplify polylines and remove collinear polygon nodes. |
|
Find segment intersections with bounding-box pre-filtering. |
|
Compute area-weighted centroids and triangle geometry summaries. |
|
Estimate a practical modelling area from survey geometry. |
|
Assign finite-element regions for Triangle-based meshes. |
Example profile projection:
1>>> import numpy as np
2
3>>> from pycsamt.models.mare2dem import (
4... get_line_orientation,
5... lonlat_to_utm,
6... project_onto_line,
7... )
8
9>>> lon = np.array([11.501, 11.507, 11.514])
10>>> lat = np.array([3.842, 3.845, 3.849])
11
12>>> east, north, zone, south_hemi = lonlat_to_utm(lon, lat)
13>>> azimuth = get_line_orientation(north, east)
14>>> cross_profile, along_profile = project_onto_line(
15... north,
16... east,
17... north[0],
18... east[0],
19... azimuth,
20... )
21
22>>> print(f"zone {zone}, southern hemisphere: {south_hemi}")
23zone 32, southern hemisphere: False
24>>> print(along_profile)
25[ 0. 744.48607367 1639.22643014]
lonlat_to_utm returns south_hemi as a plain boolean, not the "N"/
"S" letter some other UTM tools use – False here means the three
points (all near latitude 3.8°) are in the northern hemisphere, matching
their positive latitude.
For production documentation, include a small figure or table showing original coordinates, projected distances, and topography values. That record is often as important as the inversion result.
6.2.8.12. Grid And Mesh Utilities#
The package includes helpers for constructing MARE2DEM geometry and models
from gridded resistivity information. grid_to_mare2dem takes the grid as
three 2-D arrays shaped like a numpy.meshgrid output – Y and Z
are coordinate grids, not axis vectors, and Rho is the cell-centred
resistivity on that same grid:
1>>> import numpy as np
2
3>>> from pycsamt.models.mare2dem import grid_to_mare2dem
4
5>>> y_c = np.linspace(-2000.0, 2000.0, 20)
6>>> z_c = np.linspace(50.0, 1000.0, 12)
7>>> Y, Z = np.meshgrid(y_c, z_c)
8>>> Rho = np.full((12, 20), 100.0)
9>>> Rho[3:6, 8:12] = 10.0 # a shallow conductive block
10
11>>> files = grid_to_mare2dem(
12... Y, Z, Rho,
13... out_dir="runs/grid_model_v01/native",
14... model_name="line12",
15... data_file="line12.emdata",
16... )
17
18>>> print(files["poly"].name, files["resistivity"].name)
19line12.poly line12.0.resistivity
Always inspect the written .poly and .resistivity files before using
them in an inversion – plot_poly (see Plotting And QC) is the fastest
way to confirm the padding and region layout came out as expected.
6.2.8.13. Run MARE2DEM#
Once native files are prepared and the executable is available, use
Mare2DEMRunner to launch the run. Keep source_dir in the saved
configuration pointed at the same source tree used by
MARE2DEM; otherwise the runner cannot find a locally built
binary that is not also on PATH.
1>>> from pycsamt.models.mare2dem import (
2... Mare2DEMConfig,
3... Mare2DEMRunner,
4... SourceManager,
5... )
6
7>>> cfg = Mare2DEMConfig.from_file("runs/line12_mare2dem_v01/mare2dem.yml")
8>>> runner = Mare2DEMRunner("runs/line12_mare2dem_v01/native", config=cfg, verbose=1)
9
10>>> # Resolve the binary before submitting a long job.
11>>> source = SourceManager(config=cfg)
12>>> binary = source.resolve_binary()
13>>> if binary is None:
14... raise FileNotFoundError(
15... "MARE2DEM was not built; see the MARE2DEM compilation guide"
16... )
17>>> print(binary)
18/opt/mare2dem/source/MARE2DEM
19
20>>> # Inspect the logical command before running it.
21>>> print(runner.command(cfg.resistivity_stem))
22mpirun -np 16 MARE2DEM line12
command() deliberately shows cfg.binary rather than the absolute path
resolved above. During execution, run() resolves that name through
SourceManager and launches the resulting executable.
Run only after confirming that the resistivity stem names matching native
files in runner.workdir. Pass the stem ("line12"), not a path to a
file in another directory; the runner intentionally strips directory and
suffix components and executes from workdir:
1result = runner.run(
2 cfg.resistivity_stem,
3 use_mpi=True,
4 n_procs=16,
5 extra_args=None,
6 timeout=None,
7 load_result=True,
8)
The run block is intentionally not a doctest: it launches an MPI inversion
that may run for hours. A non-zero solver exit raises
subprocess.CalledProcessError; a configured timeout raises
subprocess.TimeoutExpired. With load_result=True, a successful run
returns InversionResult. Set it to False
when a scheduler or a later process will load the completed directory.
MARE2DEM is normally an MPI program. Before local execution, verify that
cfg.mpi_command resolves in the same shell environment as Python and that
cfg.n_procs is appropriate for the allocation. Do not set
use_mpi=False unless the executable was specifically built for
single-process operation.
For long inversions, prefer scheduler-managed execution. Build the native
directory with pyCSAMT, submit the command on the cluster, then use
InversionResult after the files are complete.
6.2.8.14. Inspect Results#
InversionResult scans a run directory for recognized output files and
exposes the main products.
None of the sections above actually launched MARE2DEM – there is no
compiled binary in a documentation-build environment. From here on, the
examples load a genuinely finished run instead: the bundled
data/mare2dem/demo_mt_inversion sample, a real converged 6-iteration MT
inversion.
1>>> from pycsamt.models.mare2dem import InversionResult
2
3>>> result = InversionResult("data/mare2dem/demo_mt_inversion")
4
5>>> print(result.converged, result.final_rms, result.n_iterations)
6True 1.002 6
result.model is populated from whichever .resistivity file the
directory scan happens to encounter first – it is not guaranteed to be
the highest-numbered iteration. This sample directory keeps both
demo.0.resistivity (the starting half-space) and demo.6.resistivity
(the converged final model), and because "demo.0" sorts before
"demo.6", result.model here silently resolves to the starting
model:
1>>> print(result.model.resistivity_file, result.model.iteration)
2demo.0.resistivity 0
Nothing raises or warns about this. When a directory holds more than one
resistivity snapshot, load the specific file you actually want with
read_resistivity instead of trusting result.model:
1>>> from pycsamt.models.mare2dem import read_resistivity
2
3>>> model_final = read_resistivity(
4... "data/mare2dem/demo_mt_inversion/demo.6.resistivity"
5... )
6>>> print(model_final.resistivity_file, model_final.num_regions)
7demo.6.resistivity 6540
The log parser exposes the RMS, roughness, and Lagrange-multiplier (\(\log_{10}\mu\)) history per iteration.
1>>> from pycsamt.models.mare2dem import Mare2DEMLog
2
3>>> log = Mare2DEMLog("data/mare2dem/demo_mt_inversion/demo.logfile")
4
5>>> print(log.final_rms, log.converged)
61.002 True
7>>> print(log.rms_history())
8[6.875, 3.336, 1.719, 1.074, 1.001, 1.002]
9>>> print([round(rec.roughness, 3) for rec in log.iterations])
10[1.407, 7.278, 20.39, 36.76, 39.39, 37.44]
Mare2DEMLog has no roughness_history() method – roughness (and
rms, lambda_) live on each entry of log.iterations, a list of
IterationRecord objects, one per completed iteration. The trade-off here
is exactly the one the objective function implies: as RMS drops by
more than 6x (6.875 to 1.001) over the first five iterations, roughness
climbs nearly 28-fold (1.41 to a peak of 39.4) – the smoothest model that
still fits the data is far less smooth than the uniform starting
half-space, and roughness keeps climbing even in iteration 6, where RMS has
already flattened.
Group RMS logs should be reviewed when the inversion combines data types, components, stations, or source groups.
1>>> from pycsamt.models.mare2dem import read_group_rms_log
2
3>>> group_log = read_group_rms_log(
4... "data/mare2dem/demo_csem_mt/demo.group_rms.log"
5... )
6>>> print(group_log.headers)
7['Iteration', 'Total RMS', 'CSEM', 'MT']
8>>> print(group_log.rms_log[-1])
9[20. 0.998 0.912 1.038]
A low total RMS can hide a poor fit to one group. Inspect total RMS, group RMS, response residuals, and model roughness together – here the joint run’s total RMS (0.998) is a blend that looks slightly better than either the CSEM group (0.912) or the MT group (1.038) taken alone.
6.2.8.15. Plotting And QC#
The plotting helpers are designed for quality control and interpretation.
All figures below use the same converged demo_mt_inversion result loaded
above, plus the bundled CSEM sample for survey-layout QC.
Plot helper |
Use |
|---|---|
|
RMS and convergence history from logs. |
|
Receiver/transmitter layout in map/profile coordinates. |
|
Receiver geometry and parameter checks. |
|
Transmitter geometry and parameter checks. |
|
PSLG/ |
|
Model-section visualization support. |
|
Observed/predicted response inspection. |
1>>> from pycsamt.models.mare2dem import Mare2DEMLog, PlotConvergence
2
3>>> log = Mare2DEMLog("data/mare2dem/demo_mt_inversion/demo.logfile")
4>>> fig = PlotConvergence(log).plot()
5>>> fig.savefig("runs/line12_mare2dem_v01/figures/convergence.png", dpi=200)
A clean, fast convergence – RMS falls from 6.9 to 1.0 in just 6 iterations and stays there. This is a useful contrast with ModEM’s bundled sample, which stalls at RMS 3.06 after 74 iterations: convergence behaviour varies enormously between real runs, which is exactly why it needs checking every time rather than assumed.#
PlotModel draws a true triangulated section when Triangle’s
.node/.ele mesh files are present next to the .resistivity file.
This bundled sample does not include them (they are regenerated by Triangle
during a run, not archived here), so PlotModel falls back to a plain
histogram of every region’s resistivity – and that fallback has two rough
edges worth knowing about: it labels the axis "log10 rho" while actually
plotting linear values, and it does not separate free parameter
regions from fixed ones such as the boundary padding cell held at
\(10^{12}\,\Omega\cdot\mathrm{m}\). Filtering to free parameters and
taking log10 explicitly gives a far more useful view of the same file:
1>>> import numpy as np
2
3>>> from pycsamt.models.mare2dem import read_resistivity
4
5>>> model_final = read_resistivity(
6... "data/mare2dem/demo_mt_inversion/demo.6.resistivity"
7... )
8>>> rho = np.asarray(model_final.resistivity)[:, 0]
9>>> free = np.asarray(model_final.free_parameter).ravel()
10>>> free_rho = rho[free != 0] # drop air/ocean/boundary regions
11>>> print(free_rho.size, round(free_rho.min(), 3), round(free_rho.max(), 3))
126538 0.307 242.08
The converged model’s free-parameter resistivity is clearly bimodal: one mode around 0.6-1 ohm-m and a second, broader mode around 8-15 ohm-m, with a long tail out to 242 ohm-m. Neither mode is visible at all in the library’s default (unfiltered, linear-axis) fallback histogram, where the single \(10^{12}\) boundary region compresses everything else into one bin.#
1>>> from pycsamt.models.mare2dem import PlotResponse
2
3>>> fig = PlotResponse(result).plot()
4>>> fig.savefig("runs/line12_mare2dem_v01/figures/response.png", dpi=200)
Station-level response fits for the first four MT receivers. MT01, MT03, and MT04 track the observed apparent resistivity and phase closely, but MT02’s predicted TE and TM curves sit roughly one log10 unit away from its own observations – a ten-fold resistivity mismatch at a single station, invisible in the overall RMS of 1.002. This is precisely the “low total RMS can hide a poor fit to one group” caution from Inspect Results, playing out at the station level instead of the group level.#
1>>> from pathlib import Path
2
3>>> from pycsamt.models.mare2dem import PlotSurveyLayout, read_emdata
4
5>>> em = read_emdata("data/mare2dem/demo_csem/demo_csem.emdata")
6>>> fig = PlotSurveyLayout(em).plot()
7>>> ax = fig.axes[0]
8>>> ax.set_title(Path(em.path).name, fontsize=9) # default title is the full path
9>>> fig.savefig("runs/csem_line_v01/figures/survey_layout.png", dpi=200)
PlotSurveyLayout titles the figure with str(em.path) – the full
path used to read the file, absolute if that is what was passed in. Resetting
the title to just the filename keeps the figure shareable without leaking a
local directory layout.
Receivers and transmitters for the bundled CSEM demo, interleaved along one straight ~26 km line. The equal-aspect map view is correct, not a rendering glitch – a genuinely linear towed/fixed CSEM deployment looks exactly this flat when Easting and Northing share a scale.#
1>>> from pycsamt.models.mare2dem import plot_poly
2
3>>> ax = plot_poly(
4... "data/mare2dem/demo_mt_inversion/demo.poly",
5... savefig="runs/line12_mare2dem_v01/figures/poly.png",
6... dpi=200,
7... )
The coarse boundary PSLG behind the triangulated model – padding
triangles reaching out to +-1000 km and 1000 km depth, with a dense
cluster of small seed regions near \(y=0\) where the actual survey
sits. Triangle refines this outline into the fine mesh MARE2DEM solves
on; plot_poly shows the outline pyCSAMT wrote, before that
refinement.#
Before presenting a final model, review at least:
station and transmitter geometry;
topography or bathymetry representation;
mesh or polygon geometry;
RMS history and convergence status;
group RMS history;
observed/predicted response fits;
model smoothness, bounds, and fixed/free parameter behavior.
6.2.8.16. Model Comparison#
diff_resistivity compares two .resistivity files, typically to
inspect how a model changed between iterations, parameter choices, or
preprocessing decisions. Comparing the bundled sample’s starting and final
models quantifies the same change Inspect Results described qualitatively:
1>>> from pycsamt.models.mare2dem import diff_resistivity
2
3>>> diff = diff_resistivity(
4... "data/mare2dem/demo_mt_inversion/demo.0.resistivity",
5... "data/mare2dem/demo_mt_inversion/demo.6.resistivity",
6... "runs/comparison_v01/demo_iter6_minus_iter0.resistivity",
7... )
8>>> print(diff)
9ResistivityFile(num_regions=6540, anisotropy='isotropic', target_misfit=1.0)
Use model differences as diagnostics, not as standalone geological evidence. Pair them with data-fit changes and model regularization changes.
6.2.8.17. Recommended Run Layout#
Keep source/build material separate from scientific run directories.
1external/
2 mare2dem/
3 source/
4 build_notes.md
5
6runs/
7 survey_alpha/
8 line12_mare2dem_v01/
9 mare2dem.yml
10 provenance.yml
11 native/
12 line12.emdata
13 line12.poly
14 line12.resistivity
15 line12.settings
16 demo.logfile
17 line12_MARE2DEM.emdata
18 line12.group_rms.log
19 qc/
20 survey_layout.png
21 convergence.png
22 response.png
23 exports/
24 line12_snapshot.zip
Use versioned run names. MARE2DEM uses simple native filenames, so running
several experiments in one directory can easily mix stale outputs with new
inputs – and, as Inspect Results showed, can leave InversionResult
loading the wrong iteration’s model without any error.
6.2.8.18. Pre-Run Checklist#
Before launch:
load
Mare2DEMConfigfrom the edited template;confirm source directory and executable resolution;
check
runner.commandand record it;confirm MPI process count and scheduler settings;
verify that
.emdata,.resistivity,.settings, and.polyfiles exist when required;inspect station, transmitter, and profile coordinates;
inspect topography or seafloor representation;
confirm frequency band, components, and error floors;
confirm
tx_typeis a real dipole type ("edipole"/"bdipole"), not a placement word borrowed fromrx_type;move old output files out of the run directory;
record pyCSAMT version, MARE2DEM source/binary path, and compiler/MPI context.
6.2.8.19. Post-Run Checklist#
After completion:
read the main log before plotting;
check
result.converged,result.final_rms, and iteration count;if more than one
.resistivitysnapshot is in the directory, load the intended one explicitly withread_resistivityrather than trustingresult.model;review RMS and roughness histories per iteration, from
log.iterations;review group RMS where groups are used;
confirm response-file timestamps match the intended run;
compare observed and predicted responses – per station, not only in aggregate;
inspect the final model against bounds and fixed/free parameter flags;
archive native input and output files with the configuration and provenance.
6.2.8.20. Common Mistakes#
- Using a stale response file
If the binary fails, an old
*_MARE2DEM.emdatafile can remain in the directory. Check timestamps and logs before loading results.- Passing the wrong stem
MARE2DEM receives the resistivity stem, not usually the full project path.
Mare2DEMRunnernormalizes the stem, but users should still keep filenames consistent.- Mixing coordinate systems
Longitude/latitude, UTM, and local profile distance are different records. State which system is used in every generated native file.
- Treating total RMS as enough
Total RMS can hide poor fits to one component, station range, source, or data family – station MT02 in Plotting And QC fit ten times worse than the model’s overall RMS suggested. Review group RMS and response residuals per station.
- Ignoring build provenance
MARE2DEM behavior can depend on source revision, compiler, MKL/ScaLAPACK, MPI runtime, and cluster environment. Record them.
- Trusting
result.modelin a multi-iteration directory InversionResultkeeps the first.resistivityfile its directory scan happens to encounter, not the highest-numbered one. When a directory holds more than one snapshot – common right after a run, or in an archived comparison folder – load the specific file withread_resistivityinstead.- Confusing
tx_typewithrx_type rx_typedescribes placement ("land","marine","amphibious");tx_typedescribes dipole physics ("edipole","bdipole") and silently falls back to"bdipole"for anything else it is given, with no error.
6.2.8.21. Next Steps#
Prepare A MARE2DEM Inversion walks through preparing a real MARE2DEM run end to end from EDI data, including Y/Z grid design and the
.polytriangulation boundary.Configuration And File I/O for source-of-truth configuration and archive practice.
Compiling the External Solvers downloads and builds
MARE2DEM(Linux/macOS/WSL only) viaSourceManager.Choosing A Model Backend for deciding when MARE2DEM is the right integration.
Inversion Concepts for regularized inversion and misfit concepts.
pycsamt.models for generated API reference pages.