2.26.5.20. pycsamt.agents.inv2d_agent#

pycsamt.agents.inv2d_agent#

Inv2DAgent — U-Net based 2-D MT profile inversion.

Wraps EMInverter2D:

  • Assembles a 2-D pseudosection image from the observed Sites (station × frequency × impedance component) as the U-Net input.

  • Generates a matching synthetic 2-D training dataset, using one of two physics modes (see below).

  • Trains the U-Net and produces a resistivity section output: (n_depth × n_stations) in log₁₀ Ω·m.

  • Visualises the result with plot_inversion_result_2d().

The U-Net treats the whole profile at once, so it naturally captures lateral continuity — a key advantage over station-by-station 1-D inversion.

Physics modes#

physics="mt1d" (default)

Tiles independent 1-D forward models into profile-shaped arrays (generate_dataset()). This is the original smoke/demo path from the AI-inversion implementation plan: nothing enforces lateral continuity between the tiled 1-D columns, so the plan explicitly does not call it genuine 2-D inversion. Kept unconditionally as the default and as an explicit fallback, per the plan’s requirement.

physics="mt2d"

Generates genuinely 2-D correlated training models and solves them with the verified MT2DAdapter (generate_2d_maxwell_dataset()), and can add spatial regularization (fit()’s lambda_x/lambda_z/lambda_tv). Only the TE-mode (zxy) response is requested here, even though pycsamt.ai.training.dataset2d now validates the TM-mode (zyx) response too (see its module docstring): this agent’s feature layout is still TE-only, so requesting zyx here would only cost an extra, unused forward solve per realization, not change accuracy. When a held-out synthetic split is available, a genuine (non-fabricated) recovery check against known-truth resistivity is added to the result via recovery_report(), since the field survey has no ground truth to check against.

physics="mt2d_tri"

Generates triangular-mesh correlated training models (generate_2d_tri_maxwell_dataset()) solved with Mare2DEMAdapterunverified physics, see that adapter’s own module docstring; it needs a real compiled MARE2DEM binary this environment does not have. Uses the same synthetic uniform station_spacing_m geometry physics="mt2d" already documents (real station coordinates are not read here either). Output is a per-triangle log10(resistivity) field, not a (depth, station) grid, so it is trained and rendered differently from the other two modes: a graph-convolutional inverter (GCNInverter3D with n_layers=1, reused rather than duplicated – its architecture has nothing 3-D/station-specific about it, only its n_layers naming, here repurposed to mean “one scalar per graph node” instead of “one depth column per station”) over the mesh’s triangle-centroid adjacency graph, rendered with draw_tri_mesh(). Not plotted via plot_inversion_result_2d() (that function is rectilinear-only) or draped with real topography (that needs real station coordinates, which this mode – like physics="mt2d" – does not read).

Requires PyTorch or TensorFlow (physics="mt2d"’s spatial regularization is PyTorch-only, and physics="mt2d_tri"’s GCN inverter always needs one; see EMInverter2D.fit/GCNInverter3D).

Classes

Inv2DAgent(*[, api_key, model, ...])

2-D MT profile inversion using a U-Net convolutional architecture.

class pycsamt.agents.inv2d_agent.Inv2DAgent(*, api_key=None, model=None, llm_provider='claude', n_depth=40, n_freqs=32, freqs=None, depth_max=None, n_components=2, arch='unet', n_train_profiles=200, n_stations_per_profile=20, epochs=30, patience=None, physics='mt1d', station_spacing_m=500.0, correlation_length_x_m=(500.0, 2000.0), correlation_length_z_m=(100.0, 500.0), log_resistivity_mean=2.0, log_resistivity_std=0.5, mesh_safety_factor=8.0, max_mesh_cells=200000, lambda_x=0.0, lambda_z=0.0, lambda_tv=0.0, mesh_target_cell_m=100.0, field_grid_cell_m=50.0, topo_x_m=None, topo_z_m=None, gcn_hidden=(64, 32, 16), gcn_adjacency_radius_m=300.0, mare2dem_adapter=None, verbose=False)[source]

Bases: BaseAgent

2-D MT profile inversion using a U-Net convolutional architecture.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • n_depth (int) – Number of depth cells in the output section (default 40).

  • n_freqs (int) – Number of input frequencies (default 32).

  • freqs (array-like, optional — execution-time frequency override) – Explicit positive frequency grid in hertz.

  • depth_max (float, optional — cumulative model depth in metres) – Maximum cumulative model depth in metres. None preserves the legacy frequency-derived parameterization.

  • n_components (int) – Number of channels in the input pseudosection (default 2: log10 apparent resistivity and phase for the xy component).

  • arch (str) – U-Net variant (default "unet").

  • n_train_profiles (int) – Number of synthetic 2-D profiles for training (default 200).

  • n_stations_per_profile (int) – Stations per synthetic profile (default 20).

  • epochs (int) – Training epochs (default 30).

  • patience (int or None) – Early-stopping patience. None uses one fifth of epochs with a minimum of five. Set greater than epochs only for a controlled fixed-epoch experiment; validation-based stopping is normally safer.

  • physics ({"mt1d", "mt2d", "mt2d_tri"}, default "mt1d") – Synthetic training-data physics; see the module docstring.

  • station_spacing_m (float, default 500.0) – Uniform synthetic station spacing used by physics="mt2d" and physics="mt2d_tri"; the actual survey’s real station geometry is not read by either.

  • correlation_length_x_m ((float, float)) – physics="mt2d"/"mt2d_tri" only: horizontal/vertical correlation length ranges forwarded to Maxwell2DDatasetConfig/ MaxwellTri2DDatasetConfig.

  • correlation_length_z_m ((float, float)) – physics="mt2d"/"mt2d_tri" only: horizontal/vertical correlation length ranges forwarded to Maxwell2DDatasetConfig/ MaxwellTri2DDatasetConfig.

  • log_resistivity_mean (float) – physics="mt2d"/"mt2d_tri" only: affine map from the standardized correlated field to log10(resistivity_ohm_m).

  • log_resistivity_std (float) – physics="mt2d"/"mt2d_tri" only: affine map from the standardized correlated field to log10(resistivity_ohm_m).

  • mesh_safety_factor (float, int) – physics="mt2d" only: forwarded to Maxwell2DDatasetConfig.

  • max_mesh_cells (float, int) – physics="mt2d" only: forwarded to Maxwell2DDatasetConfig.

  • lambda_x (float, default 0.0) – Spatial-regularization weights forwarded to fit(). Zero by default, so nothing changes unless explicitly requested. physics="mt2d" only.

  • lambda_z (float, default 0.0) – Spatial-regularization weights forwarded to fit(). Zero by default, so nothing changes unless explicitly requested. physics="mt2d" only.

  • lambda_tv (float, default 0.0) – Spatial-regularization weights forwarded to fit(). Zero by default, so nothing changes unless explicitly requested. physics="mt2d" only.

  • mesh_target_cell_m (float) – physics="mt2d_tri" only: forwarded to MaxwellTri2DDatasetConfig.

  • field_grid_cell_m (float) – physics="mt2d_tri" only: forwarded to MaxwellTri2DDatasetConfig.

  • topo_x_m (array-like, optional) – physics="mt2d_tri" only: real topography (z positive down) forwarded to MaxwellTri2DDatasetConfig/ build_graded_tri_mesh(). Both default to None (flat surface at z=0, unchanged from before this parameter existed) – when given, training stations sit at their true interpolated elevation instead. This builds a real topography-following training mesh; it is unrelated to the topography execute()-time input key above, which only re-renders an already-flat mt1d/mt2d prediction in an absolute-elevation display frame.

  • topo_z_m (array-like, optional) – physics="mt2d_tri" only: real topography (z positive down) forwarded to MaxwellTri2DDatasetConfig/ build_graded_tri_mesh(). Both default to None (flat surface at z=0, unchanged from before this parameter existed) – when given, training stations sit at their true interpolated elevation instead. This builds a real topography-following training mesh; it is unrelated to the topography execute()-time input key above, which only re-renders an already-flat mt1d/mt2d prediction in an absolute-elevation display frame.

  • gcn_hidden (tuple of int, default (64, 32, 16)) – physics="mt2d_tri" only: hidden-layer widths forwarded to GCNInverter3D.

  • gcn_adjacency_radius_m (float, default 300.0) – physics="mt2d_tri" only: triangle-centroid adjacency radius forwarded to build_adjacency(). Must be set relative to the actual mesh scale, not left at the default: it is a hard cutoff in the same metres as mesh_target_cell_m, and if it is smaller than the typical distance between neighbouring triangle centroids, build_adjacency returns the identity matrix (every triangle connected only to itself) and the GCN silently degenerates into a per-triangle lookup with no spatial message-passing at all – no error is raised. A radius of roughly 1.5-2x mesh_target_cell_m is a reasonable starting point; verify with build_adjacency(mesh.triangle_centroids_m, radius).sum() > mesh.n_triangles (more than just the self-loops) before trusting a training run.

  • mare2dem_adapter (object, optional) – physics="mt2d_tri" only: pre-built Mare2DEMAdapter (e.g. pointed at a specific compiled binary). Defaults to Mare2DEMAdapter(), resolved from the environment.

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str, optional)

  • topography (bool or dict, optional) – Extract terrain from sites and render the predicted section in an absolute-elevation frame. A mapping can provide elevation_m and chainage_km plus exaggeration and interp_method.

  • period_range ([T_min, T_max], optional)

  • freqs

  • depth_max

  • keys

  • ----------------

  • ρ (pred_section ndarray (n_depth × n_stations) — log₁₀) – (physics="mt1d"/"mt2d" only)

  • "mesh"/"log10_resistivity" (pred_triangles dict with) – (physics="mt2d_tri" only)

  • (km) (depths_km ndarray — depth axis) – (physics="mt1d"/"mt2d" only)

  • list[str] (station_names)

  • metadata (topography dict or None — resolved terrain) – (physics="mt1d"/"mt2d" only)

  • only) (rms_global float (physics="mt1d"/"mt2d")

  • GCNInverter3D (inverter EMInverter2D or)

  • dict (figure_paths)

  • dict

  • "mt1d" (physics str —) – "mt2d_tri", mode used

  • "mt2d""mt2d_tri", mode used

  • or"mt2d_tri", mode used

  • known-truth (mt2d_tri_recovery dict or None — held-out) – recovery metrics (physics="mt2d" only)

  • known-truth – recovery metrics (physics="mt2d_tri" only)

  • verbose (bool | int | str)

SYSTEM_PROMPT: str = 'You are an expert in 2-D MT inversion using deep learning (U-Net architecture).\nGiven a 2-D AI inversion result, write 4-5 sentences that:\n1. Describe the input pseudosection geometry (stations × frequencies).\n2. Interpret the dominant structural features in the resistivity section.\n3. Assess lateral continuity and compare to classical smoothness-constrained results.\n4. Identify artefacts or stations with poor convergence.\n5. Recommend follow-up (regularisation, 3-D verification, drilling targets).\nReply in plain scientific English.\n'

Override in subclasses to give the LLM its domain expertise.

execute(input_data)[source]

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult