pycsamt.ai.inversion.hybrid2d#

Hybrid AI + physics 2-D MT inversion.

HybridInverter2D runs a two-stage workflow:

Stage 1 — AI initial 2-D section

A pre-trained EMInverter2D maps the EM profile panel directly to a 2-D resistivity section:

X_panel --> EMInverter2D --> rho_2d_0

Stage 2 — Joint physics refinement

Starting from rho_2d_0, the same joint physics-informed optimisation used by PINNInverter2D refines all stations simultaneously:

\[\mathcal{L}(\theta) = \mathcal{L}_{\rm data} + \lambda_z \mathcal{L}_{\rm vert} + \lambda_x \mathcal{L}_{\rm lat}\]

Because the AI starting model is physically plausible, Stage 2 converges significantly faster than a randomly initialised PINN.

Example

>>> from pycsamt.ai.inversion import (
...     EMInverter2D,
...     HybridInverter2D,
... )
>>> ai2d = EMInverter2D.load(
...     "checkpoints/unet2d.npz"
... )
>>> inv = HybridInverter2D(
...     "edi/profile1/",
...     ai_inverter=ai2d,
...     epochs=150,
...     smoothness_weight=0.005,
... )
>>> inv.fit()
HybridInverter2D(n_stations=20, fitted)
>>> section = inv.resistivity_section()
>>> s1 = inv.stage1_section()

Classes

HybridInverter2D(sites, ai_inverter, *[, ...])

Two-stage hybrid AI + physics 2-D inversion.

class pycsamt.ai.inversion.hybrid2d.HybridInverter2D(sites, ai_inverter, *, n_layers=None, depth_max=2000.0, n_freqs=32, mode='te', smoothness_weight=0.005, lateral_weight=0.003, epochs=150, lr=0.005, comp_te='xy', comp_tm='yx', device=None, recursive=True, on_dup='replace', verbose=0)[source]

Bases: BaseHybridInverter

Two-stage hybrid AI + physics 2-D inversion.

Parameters:
  • sites (Any) – Path, EDIFile, EDICollection, Site, Sites, APISurvey, or iterable.

  • ai_inverter (EMInverter2D or str or Path) – Pre-trained EMInverter2D (fitted) or path to a .npz checkpoint.

  • n_layers (int or None) – Layers per station in Stage 2. Defaults to ai_inverter.n_depth.

  • depth_max (float, default 2000.0) – Total depth for uniform thickness init when Stage 1 does not provide thicknesses.

  • n_freqs (int, default 32) – Frequency-grid size for the panel and the shared optimisation grid.

  • mode ({'te', 'tm', 'both'}, default 'te') – Data polarisation used in Stage 2.

  • smoothness_weight (float, default 0.005) – Vertical smoothness weight.

  • lateral_weight (float, default 0.003) – Lateral smoothness weight.

  • epochs (int, default 150) – Adam iterations for Stage 2.

  • lr (float, default 5e-3) – Adam learning rate for Stage 2.

  • comp_te (str, default 'xy')

  • comp_tm (str, default 'yx')

  • device (str or None)

  • recursive (bool, default True)

  • on_dup (str, default 'replace')

  • verbose (int, default 0)

fit(*, verbose=True, log_every=50)[source]

Run both inversion stages.

Parameters:
  • verbose (bool, default True)

  • log_every (int, default 50)

Return type:

self

resistivity_section(*, as_log10=True)[source]

Return the Stage-2 2-D resistivity section.

Parameters:

as_log10 (bool, default True)

Return type:

ndarray (n_layers, n_stations)

thickness_section()[source]

Return Stage-2 layer thicknesses in metres.

Return type:

ndarray (n_layers-1, n_stations)

stage1_section(*, as_log10=True)[source]

Return the Stage-1 AI 2-D section.

Parameters:

as_log10 (bool, default True)

Return type:

ndarray (n_layers, n_stations)

convergence_curve()[source]

Return Stage-2 Adam loss history.

Returns:

Columns: epoch, loss.

Return type:

pandas.DataFrame

residuals(stage=2)[source]

Observed vs predicted data fit.

Parameters:

stage ({1, 2}, default 2) – Which stage’s models to evaluate.

Returns:

Columns: station, freq, rho_obs, rho_pred, phase_obs, phase_pred.

Return type:

pandas.DataFrame

property stations: list[str][source]

Station names in profile order.

property n_sites: int[source]

Number of loaded stations.