2.26. pycsamt.agents#
AI-assisted workflow agents for loading, quality control, processing, inversion preparation, interpretation, reporting, and orchestration.
AI-assisted MT, AMT, and CSAMT workflow automation.
All agents are lazy-loaded: importing this module costs nothing unless
you actually instantiate an agent. This keeps the base pycsamt import fast
even when LLM libraries (anthropic, openai, google-generativeai)
are not installed.
2.26.1. Agent catalogue#
ContextInputAgentTranslate a natural-language request into a structured workflow config.
MTLoaderAgentLoad EDI / AVG / J files into a
Sitesobject with a per-station QC report.AgentCoordinatorChain agents into named workflows with checkpointing and cost tracking.
DataQCAgentSNR section, dead-band detection, per-station quality scores.
StaticShiftAgentStatic-shift detection and correction (AMA / LOESS / spatial median).
PhaseAnalysisAgentPhase tensor, strike, dimensionality, Mohr circles, Argand diagrams.
ForwardModelAgent1-D, 2-D, and 3-D MT forward modelling.
InversionPrepAgent/Occam2DAgent/ModEmAgent/Mare2DEMAgentWrite Occam2D, ModEM3D, or MARE2DEM inversion input files; optionally run the binary and scan results.
InversionEvaluationAgentCompute RMS, residual PT section, misfit pseudosection.
InterpretationAgentMap resistivity ranges to lithology; correlate with borehole logs.
ReportAgentAssemble all figures and tables into Markdown / HTML / PDF.
CodeGenerationAgentEmit a standalone reproducible Python script from the workflow config.
WorkflowOrchestratorAgentNL → classify workflow → build and run the correct agent chain.
AgentMasterOne-line front door over the orchestrator:
AgentMaster(provider="anthropic").run("...").DenoisingAgentRPCA / Hampel / EMAP / AI-CAE denoising.
AnomalyDetectionAgentUnsupervised CAE anomaly flagging per (station, frequency).
AIInversionAgentEnd-to-end 1-D AI inversion (ResNet / CNN / FCN).
Inv2DAgentU-Net 2-D profile inversion with lateral continuity.
Inv3DAgentGCN 3-D spatial inversion using inter-station graph message-passing.
EnsembleAgentEnsemble 1-D inversion with conformal uncertainty bands.
JointInversionAgentDRCNN multi-modal joint inversion (MT + TEM / CSAMT / gravity).
ModelZooAgentBrowse, download, and deploy pre-trained EM inverter checkpoints.
PINNInversionAgentPhysics-informed 1-D/2-D/3-D MT inversion via Adam (no labelled training data required).
HybridInversionAgentTwo-stage AI warm-start + physics refinement for 1-D, 2-D, and 3-D MT inversion.
IoTFieldAgentMonitor an IoT-enabled AMT/MT/CSAMT/CSEM field acquisition from its edge telemetry: monitoring status, sync/power summaries, provenance manifest, and dashboard figures.
2.26.2. Supporting classes#
AgentResultStandardised return type for every agent.
BaseAgentAbstract base class; inherit to build custom agents.
2.26.3. Quick start#
Without an LLM key (regex fallback, no cost):
from pycsamt.agents import (
ContextInputAgent,
MTLoaderAgent,
AgentCoordinator,
)
ctx = ContextInputAgent() # no api_key → pure regex
loader = MTLoaderAgent()
coord = AgentCoordinator("mt_qc")
coord.add_step("parse", ctx, description="Parse request into config")
coord.add_step(
"load",
loader,
input_fn=lambda r: {"path": r["parse"]["config"]["data_path"]},
description="Load EDI files and QC scan",
)
result = coord.execute(
{"request": "Load /data/EDIs, QC, period range 1e-4 to 1 s"},
dry_run=True,
)
With a Claude API key:
ctx = ContextInputAgent(api_key="sk-ant-…", llm_provider="claude")
res = ctx.execute({"request": "Load L22PLT EDIs, run PT analysis …"})
print(res["config"])
print(res.llm_interpretation)
2.26.4. LLM providers supported#
Anthropic Claude (default) —
llm_provider="claude"OpenAI —
llm_provider="openai"Google Gemini —
llm_provider="gemini"DeepSeek —
llm_provider="deepseek"MiniMax —
llm_provider="minimax"
Install the relevant client library before use:
pip install anthropic / pip install openai /
pip install google-generativeai
DeepSeek and MiniMax reuse the openai package with a
custom base_url; no extra install is needed.
- class pycsamt.agents.AgentConfig#
Bases:
objectGlobal LLM configuration singleton for all pycsamt agents.
BaseAgentcallsresolve()on every instantiation andget_rate()+_add_spend()on every LLM call, so keys, rates, and the budget cap set here apply automatically to every agent in the session.:param (none — use
configure()after construction):- Variables:
provider (str or None) – Currently active LLM provider.
model (str or None) – Resolved model (explicit override or provider default).
api_key (str or None) – Resolved key for the active provider (stored key → env var → None).
is_configured (bool) –
Truewhen a provider and a resolvable key are both present.spent_usd (float) – Accumulated LLM spend this session.
remaining_usd (float or None) – Remaining budget, or
Nonewhen no cap is set.
Examples
from pycsamt.api.agents import AGENT_CONFIG # one-call setup AGENT_CONFIG.configure(provider="claude", api_key="sk-ant-…") # pricing — override a rate (USD / 1 M tokens) AGENT_CONFIG.set_rate( "claude", "claude-opus-4-8", input=12.0, output=60.0 ) # pricing — add a model not in the built-in table AGENT_CONFIG.set_rate("openai", "gpt-5-turbo", input=5.0, output=20.0) # budget cap AGENT_CONFIG.set_budget(usd=5.0) print(AGENT_CONFIG.remaining_usd) # 5.0 # inspect print(AGENT_CONFIG.info()) # reset AGENT_CONFIG.reset()
- configure(*, provider, api_key, model=None)#
Set the active provider, key, and optional model in one call.
- Parameters:
- Returns:
self — allows chaining.
- Return type:
Examples
AGENT_CONFIG.configure(provider="claude", api_key="sk-ant-…") AGENT_CONFIG.configure( provider="openai", api_key="sk-…", model="gpt-4o-mini", )
- set_key(provider, api_key)#
Store an API key for provider without changing the active provider.
Useful for pre-loading keys for multiple providers so you can
switch()between them without re-supplying credentials.- Parameters:
- Returns:
self.
- Return type:
Examples
AGENT_CONFIG.set_key("claude", "sk-ant-…") AGENT_CONFIG.set_key("openai", "sk-…") AGENT_CONFIG.set_key("gemini", "AIza…") AGENT_CONFIG.switch("claude")
- switch(provider, *, model=None)#
Switch the active provider.
The key for provider must have been stored via
configure()orset_key(), or be available as an environment variable.- Parameters:
- Returns:
self.
- Return type:
Examples
AGENT_CONFIG.switch("openai") AGENT_CONFIG.switch("gemini", model="gemini-2.0-flash")
- reset(*, keys=True)#
Clear the active configuration, custom rates, and budget.
- Parameters:
keys (bool) – When
True(default) also wipe all stored per-provider keys. PassFalseto clear only the active provider / model while keeping stored keys available for futureswitch()calls.- Returns:
self.
- Return type:
Notes
Custom rates and the budget cap/counter are always reset. Call
reset_budget()to zero only the spend counter.
- set_rate(provider, model, *, input, output)#
Override or add the cost rate for a specific provider + model.
Custom rates take precedence over the built-in table for every cost estimate made by agents in this session.
- Parameters:
- Returns:
self.
- Return type:
Examples
# provider lowered their price AGENT_CONFIG.set_rate( "claude", "claude-opus-4-8", input=12.0, output=60.0 ) # a new model not yet in the built-in table AGENT_CONFIG.set_rate( "openai", "gpt-5-turbo", input=5.0, output=20.0 )
- get_rate(provider, model)#
Return the resolved
{"input": …, "output": …}rate.Resolution order: custom override → built-in table (exact match → prefix match) → provider default.
- Parameters:
- Return type:
dict with keys
"input"and"output"(USD / 1 M tokens).
Examples
rate = AGENT_CONFIG.get_rate("claude", "claude-sonnet-4-6") # {"input": 3.0, "output": 15.0}
- estimate_cost(provider, model, input_tokens, output_tokens)#
Compute the USD cost for one LLM call using the resolved rate.
Respects any rate overrides set via
set_rate().- Parameters:
- Return type:
float — estimated cost in USD.
Examples
cost = AGENT_CONFIG.estimate_cost( "claude", "claude-sonnet-4-6", 500, 200 )
- list_rates(provider=None)#
Return the effective rate table (custom overrides merged with built-in).
- Parameters:
provider (str or None) – When given, return only that provider’s table. When
None, return all providers.- Return type:
dict — same structure as the built-in table.
Examples
import pprint pprint.pprint(AGENT_CONFIG.list_rates("claude"))
- set_budget(*, usd)#
Set a session spend cap.
Once
spent_usdreaches usd, any subsequent LLM call will raiseBudgetExceededErrorbefore the API call is made.- Parameters:
usd (float) – Maximum spend in USD for this session.
- Returns:
self.
- Return type:
Examples
AGENT_CONFIG.set_budget(usd=2.0) # … after several agent calls … print( f"${AGENT_CONFIG.spent_usd:.4f} used, " f"${AGENT_CONFIG.remaining_usd:.4f} left" )
- reset_budget(*, cap=False)#
Reset the session spend counter.
- Parameters:
cap (bool) – When
Truealso remove the budget cap (set_budgetmust be called again to re-enable it). DefaultFalse— zeroes the counter but keeps the existing cap.- Returns:
self.
- Return type:
Examples
AGENT_CONFIG.reset_budget() # zero counter, keep cap AGENT_CONFIG.reset_budget(cap=True) # zero counter and remove cap
- property api_key: str | None#
Resolved key for the active provider.
Resolution order: stored key → environment variable →
None.
- resolve(provider, api_key, model)#
Resolve the effective
(provider, api_key, model)for an agent.Called automatically by
__init__. Users should not call this directly.2.26. Resolution rules#
- If
api_keyis explicitly given: Use
provider,api_key, andmodel(or provider default) exactly as supplied. The global config is ignored.- If
api_keyisNone: If
providerequals the default"claude"and the global config has an active provider, inherit that provider.Look up the key: stored key → env var.
Inherit the model from the global config when the effective provider matches the globally active provider.
- If
- info()#
Return a summary dict suitable for display or logging.
The API key is masked to its last four characters for safety.
- Returns:
Keys:
provider,model,has_key,key_source,stored_providers,custom_rate_models,budget_usd,spent_usd,remaining_usd.- Return type:
- using(*, provider=None, api_key=None, model=None)#
Temporarily override the global config inside a
withblock.All arguments are optional; omitted values are left unchanged. The original config (including custom rates and budget) is restored on exit even if an exception occurs.
- Parameters:
- Yields:
AgentConfig – self with the override applied.
- Return type:
Generator[AgentConfig, None, None]
Examples
with AGENT_CONFIG.using(provider="gemini", api_key="AIza…"): r = DataQCAgent().execute(data) # original provider / key / model restored here
- offline()#
Context manager: force truly offline mode in the current thread.
While active,
_resolve_key()will not inspect environment variables, so agents created inside the block receiveapi_key=Noneeven whenANTHROPIC_API_KEY(or similar) is set in the OS environment. Usesthreading.localso concurrent requests in other threads are unaffected.Examples
with AGENT_CONFIG.offline(): agent = DataQCAgent() # api_key will be None
- Return type:
Generator[AgentConfig, None, None]
- exception pycsamt.agents.BudgetExceededError(spent, budget)#
Bases:
RuntimeErrorRaised when an LLM call would be made after the session budget is used.
- pycsamt.agents.configure_agents(*, provider, api_key, model=None)#
Configure
AGENT_CONFIGin one call and return it.Equivalent to
AGENT_CONFIG.configure(...).- Parameters:
- Return type:
Examples
from pycsamt.agents import configure_agents configure_agents(provider="claude", api_key="sk-ant-…")
- pycsamt.agents.reset_agents(*, keys=True)#
Reset
AGENT_CONFIGto its unconfigured state.Equivalent to
AGENT_CONFIG.reset(...).- Parameters:
keys (bool) – When
True(default) also wipe stored per-provider keys.- Return type:
- class pycsamt.agents.AgentResult(status, summary, data=<factory>, warnings=<factory>, llm_interpretation=None, elapsed_seconds=0.0, cost_estimate_usd=0.0, error=None, error_fix_hint=None)#
Bases:
objectStandardised output returned by every pycsamt agent.
- Variables:
status (str) –
"success"|"failed"|"needs_review"summary (str) – One-sentence human-readable description of what happened.
data (dict) – Agent-specific outputs (arrays, paths, dataframes, figures …).
warnings (list of str) – Non-fatal issues encountered during execution.
llm_interpretation (str or None) – Free-text interpretation written by the LLM, when available.
elapsed_seconds (float)
cost_estimate_usd (float) – Estimated LLM API cost for this execution.
error (str or None) – Exception message if
status == "failed".error_fix_hint (str or None) – Suggested remediation for the error.
- Parameters:
Examples
>>> result = some_agent.execute({"path": "/data/EDIs"}) >>> result.status 'success' >>> result["sites"] # dict-like access to data <Sites 25 stations> >>> result.get("n_stations", 0) 25
- classmethod failed(error, *, hint=None, elapsed=0.0)#
Convenience constructor for failure results.
- Parameters:
- Return type:
- class pycsamt.agents.BaseAgent(name, *, api_key=None, model=None, llm_provider='claude', section_preset='pseudosection', verbose=False)#
Bases:
ABCAbstract base class for all pycsamt agents.
- Parameters:
name (str) – Human-readable agent name used in logs and reports.
api_key (str or None) – LLM API key. When
Nonethe agent runs without LLM support and everyllm_interpretationfield will beNone.model (str or None) – LLM model identifier. Defaults to the provider’s recommended model.
llm_provider ({"claude", "openai", "gemini", "deepseek", "minimax"}) – Which provider to use. Default
"claude".section_preset (str) – Which
PYCSAMT_SECTIONpreset governs figures produced by this agent. Default"pseudosection".verbose (bool, int, or str) – Progress verbosity forwarded to the underlying dataset-generation and training calls — see
pycsamt.utils.progress.normalize_verbose(). DefaultFalse(silent), matching this agent framework’s historical behaviour of returning results/logs rather than printing.
Examples
Subclass and implement
execute():class MyAgent(BaseAgent): SYSTEM_PROMPT = "You are an expert MT data analyst." def execute(self, input_data): t0 = time.time() # ... do work ... interp = self.query_llm("Interpret these results: ...") return AgentResult( status="success", summary="Analysis complete.", data={"result": 42}, llm_interpretation=interp, elapsed_seconds=time.time() - t0, cost_estimate_usd=self._last_cost, )
- SYSTEM_PROMPT: str = 'You are a geophysics expert specialising in magnetotelluric (MT/AMT/CSAMT) data processing and interpretation.'#
Override in subclasses to give the LLM its domain expertise.
- abstractmethod execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- query_llm(prompt, system_message=None, *, temperature=0.2, max_tokens=1024)#
Send prompt to the configured LLM and return the response text.
Returns
Nonewhen no API key is configured or all retries fail. Accumulates token cost intoself._last_cost.
- extract_json(text)#
Extract the first JSON object / array from text.
Returns
Nonewhen no valid JSON is found.
- class pycsamt.agents.AgentCoordinator(workflow_name='pycsamt_workflow', *, checkpoint_dir=None, verbose=True)#
Bases:
objectOrchestrate a sequence of pycsamt agents as a named workflow.
- Parameters:
Examples
Build and run a QC workflow:
from pycsamt.agents import AgentCoordinator, MTLoaderAgent, DataQCAgent coord = AgentCoordinator("mt_qc") coord.add_step("load", MTLoaderAgent(...)) coord.add_step( "qc", DataQCAgent(...), input_fn=lambda r: {"sites": r["load"]["sites"]}, ) result = coord.execute({"path": "/data/EDIs"}) print(result.summary)
Dry-run preview:
result = coord.preview({"path": "/data/EDIs"}) print(result["plan"])
- add_step(name, agent, *, input_fn=None, description='', required=True)#
Append a workflow step. Returns
selffor chaining.- Parameters:
name (str)
agent (BaseAgent)
input_fn (callable(prev_results_dict) → dict, optional) – Maps accumulated results to this step’s input dict.
prev_results_dictkeys are earlier step names; each value is the step’sAgentResult.description (str)
required (bool)
- Returns:
selffor method chaining.- Return type:
- preview(config)#
Return an execution plan and estimated cost without running anything.
- Parameters:
config (dict) – The workflow configuration that would be passed to
execute().- Returns:
data["plan"]contains the formatted plan string.data["steps"]is a list of step metadata dicts.- Return type:
- execute(config, *, dry_run=False, resume=False)#
Run all workflow steps sequentially.
- Parameters:
- Returns:
datacontains one key per step name with itsAgentResult.- Return type:
- reset_checkpoints()#
Delete all saved checkpoints for this workflow.
- Return type:
None
- class pycsamt.agents.WorkflowStep(name, agent, *, input_fn=None, description='', required=True)#
Bases:
objectOne step in an
AgentCoordinatorworkflow.- Parameters:
name (str) – Unique identifier used for checkpointing and logging.
agent (BaseAgent) – The agent instance that runs this step.
input_fn (callable or None) –
input_fn(prev_results)→ dict fed toagent.execute(). WhenNonethe coordinator passes the raw workflow config.description (str) – Human-readable description shown in the dry-run preview.
required (bool) – When
Falsea failure skips the step rather than aborting.
- pycsamt.agents.estimate_cost(provider, model, input_tokens, output_tokens)#
Compute the USD cost for one LLM call.
Delegates to
estimate_cost(), so any per-model overrides set viaAGENT_CONFIG.set_rate(...)are used.- Parameters:
- Returns:
Estimated cost in USD.
- Return type:
Examples
>>> estimate_cost("claude", "claude-sonnet-4-6", 500, 200) 0.00451...
- pycsamt.agents.format_cost(usd)#
Return a human-readable cost string.
Examples
>>> format_cost(0.00012) '$0.000120' >>> format_cost(1.5) '$1.5000'
- pycsamt.agents.get_rate(provider, model)#
Return the resolved
{"input": …, "output": …}rate.Delegates to
get_rate(), so any per-model overrides set viaAGENT_CONFIG.set_rate(...)take effect.
- class pycsamt.agents.WorkflowPlan(request, workflow_type, data_path='', output_dir='', parameters=<factory>, risk_flags=<factory>, requires_human_review=False, expected_outputs=<factory>, provider='offline', grounding_citations=<factory>)#
Bases:
objectValidated intermediate representation of a workflow request.
Produced by
ContextInputAgentand consumed byWorkflowOrchestratorAgent.- Parameters:
request (str) – Original natural-language request.
workflow_type (str) – One of the recognised workflow identifiers (see
VALID_WORKFLOWS).data_path (str) – Path to EDI files or survey directory.
output_dir (str) – Destination for outputs.
parameters (dict) – Parsed parameters (period_range, component, etc.).
risk_flags (list of str) – Warnings or risks the user should review.
requires_human_review (bool) – True when ambiguous parameters were inferred.
expected_outputs (list of str) – Files / artefacts the workflow should produce.
provider (str) – LLM provider used for parsing, or
'offline'.grounding_citations (list of str) – Source paths retrieved by RAG to ground this plan, when retrieval was available (empty offline or on a cold index).
- classmethod from_config(config, request='', provider='offline', citations=None)#
Build a
WorkflowPlanfrom a config dict as returned byContextInputAgent.
- pycsamt.agents.validate_workflow_plan(plan, *, raise_on_error=False)#
Validate plan and optionally raise on errors.
- Parameters:
plan (WorkflowPlan)
raise_on_error (bool) – When True, raises
ValueErrorif errors exist.
- Returns:
errors – Empty when the plan is valid.
- Return type:
- class pycsamt.agents.AgentMaster(provider='claude', *, api_key=None, model=None, default_workflow='qc')#
Bases:
PyCSAMTObjectPlain-language entry point to the agent workflows.
- Parameters:
provider (str, default
"claude") – LLM provider. Friendly aliases are accepted:"anthropic"→"claude"and"google"→"gemini". Without an API key the agents fall back to the rule-based (regex/keyword) path, soAgentMaster()works offline.api_key (str, optional) – Provider API key. When omitted, the provider’s environment variable is used if set; otherwise the rule-based fallback runs at zero cost.
model (str, optional) – Provider model override (defaults per provider).
default_workflow (str, default
"qc") – Workflow used when a request cannot be classified.
Examples
Plan first (no files touched), then execute:
>>> from pycsamt.agents import AgentMaster >>> master = AgentMaster(provider="anthropic") >>> plan = master.plan( ... "QC the EDI files and prepare a short report", ... data_path="data/edi/", ... ) >>> plan["workflow_type"] 'qc' >>> report = master.run( ... "Load data/edi/, flag stations with RMS > 2, build an Occam2D " ... "input for profile L22, launch inversion, and produce a PDF " ... "report." ... )
See also
pycsamt.agents.WorkflowOrchestratorAgentThe dispatcher this façade drives; use it directly for structured workflow configurations.
- property orchestrator: WorkflowOrchestratorAgent#
The lazily-built orchestrator behind this façade.
- run(request, *, data_path=None, output_dir=None, dry_run=False, **extra)#
Route request to the right specialist agent and run it.
requestis first classified byIntentRouter— a question is answered byPackageQAAgent, a code request generates a script viaCodeGenerationAgent, a metrics request is computed byMetricsAgent, and a workflow / plot request runs the full pipeline throughWorkflowOrchestratorAgent. A meta (capability) or clarify (ambiguous) request returns immediately with no data path required.- Parameters:
request (str) – Plain-language description of what to do. Paths mentioned in the text are extracted when possible; pass
data_path/output_direxplicitly for scripts and CI.data_path (str, optional) – Survey input (EDI/AVG/J directory or file).
output_dir (str, optional) – Where products (figures, inputs, reports) are written.
dry_run (bool, default False) – For workflow / plot requests only: preview the selected chain without reading or writing. Ignored for questions, code, metrics, meta, and clarify requests, which never touch disk regardless.
**extra – Additional orchestrator payload fields, passed through for workflow / plot requests only.
- Returns:
Status, per-step outputs, reasoning, and cost tracking.
- Return type:
- class pycsamt.agents.ContextInputAgent(*, api_key=None, model=None, llm_provider='claude', use_rag=True)#
Bases:
BaseAgentParse a natural-language MT workflow request into a structured config.
- Parameters:
Examples
With an API key:
agent = ContextInputAgent(api_key="sk-ant-…") result = agent.execute( { "request": "Load EDIs from /data/L22PLT, QC them, " "period range 1e-4 to 1 s, save to /out/qc/" } ) cfg = result["config"] # cfg["workflow"] == "qc" # cfg["data_path"] == "/data/L22PLT" # cfg["period_range"] == [0.0001, 1.0]
Without an API key (regex fallback):
agent = ContextInputAgent() # no key → regex mode result = agent.execute({"request": "…"})
- SYSTEM_PROMPT: str = 'You are an expert MT/AMT/CSAMT workflow configuration interpreter for pycsamt v2.\n\nGiven a natural-language processing request, extract a structured JSON configuration dictionary with the following schema (include only keys that are clearly mentioned or can be reasonably inferred):\n\n{\n "workflow": string — one of:\n qc, static_shift, phase_analysis, forward,\n inversion_prep, pre_inversion, inversion_eval,\n interpretation, report, full,\n ai_inversion, inv1d, inv2d, inv3d,\n ensemble_inversion, joint_inversion,\n modem, mare2dem, occam2d,\n tipper, sensitivity, rotation,\n freq_decimation, batch, comparison,\n full_ai_workflow,\n "data_path": string — absolute or relative path to EDI\n file(s) or directory,\n "output_dir": string — where to write results / figures,\n "period_range": [T_min_seconds, T_max_seconds],\n "component": string — "xy"|"yx"|"all"|"off_diagonal",\n "station": string or null,\n "inversion_code": string — "occam2d"|"modem"|"mare2dem"|null,\n "depth_max_km": float or null,\n "n_periods": int or null,\n "verbose": bool\n}\n\nRules:\n- Choose "ai_inversion" for CNN / 1-D neural-network / deep-learning\n inversion requests.\n- Choose "inv2d" for U-Net / 2-D neural / profile AI inversion.\n- Choose "inv3d" for GCN / graph-convolutional / 3-D AI inversion.\n- Choose "ensemble_inversion" for ensemble / uncertainty / Bayesian.\n- Choose "joint_inversion" for joint / multi-modal / TEM+MT.\n- Choose "full_ai_workflow" when both AI inversion and full pipeline\n are requested together.\n- If a period range is given in frequency (Hz), convert to period\n (s = 1/f).\n- Preserve the full absolute path exactly as given.\n- Return ONLY the JSON object — no markdown fences, no prose.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.IntentRouter(*, api_key=None, model=None, llm_provider='claude')#
Bases:
BaseAgentClassify a chat message into a
RouterDecision.- Parameters:
Examples
Offline:
router = IntentRouter() d = router.route("what does StaticShiftAgent do?") d.intent # 'question' d.needs_data # False
Online:
router = IntentRouter(api_key="sk-ant-...", llm_provider="claude") d = router.route("run QC on /data/willy") d.intent # 'workflow'
- SYSTEM_PROMPT: str = 'You are the top-level intent router for the pycsamt v2 magnetotelluric (MT)\nassistant. Classify the user\'s message into exactly one INTENT.\n\nINTENTS:\n- "question": the user asks ABOUT pycsamt — concepts, what a class/function\n does, how something works, which method to use, definitions. They want an\n explanation, not execution.\n- "code": the user wants a Python script / function / notebook generated.\n- "plot": the user wants a figure/plot/visualisation produced from data.\n- "workflow": the user wants to RUN a processing pipeline on their data\n (QC, static-shift, phase analysis, an inversion, report, etc.).\n- "meta": greetings, "what can you do", "introduce yourself", capability\n questions about the assistant itself, and requests to LIST/ENUMERATE the\n agents, tasks or workflows the assistant can run ("list the agents", "which\n workflows are available", "what tasks can you perform"). A question about\n what ONE named agent/function does is a "question", not "meta".\n- "metrics": the user asks for a COMPUTED VALUE of their survey line(s) and\n wants the number back inline — strike, azimuth/bearing, dimensionality,\n skew, station count, period/frequency range, coordinates/length, quality\n score, or a one-line summary ("what\'s the strike of L22PLT?", "azimuth of\n all lines", "how many stations", "tell me about this line"). This is NOT a\n plot/figure request and NOT "run an analysis".\n\nReturn ONLY a JSON object:\n{\n "intent": one of question|code|plot|workflow|meta|metrics,\n "workflow": one of [qc, static_shift, phase_analysis, forward, pre_inversion, inversion_eval, interpretation, report, full, ai_inversion, inv2d, inv3d, ensemble_inversion, joint_inversion, pinn_inversion, hybrid_inversion, modem, occam2d, tipper, sensitivity, rotation, freq_decimation, batch, comparison, code_gen, denoise, rhophi, phase_psection, pt_psection, tipper_plot, phase_tensor_map, pt_strip, pt_strip_grid, station_response, strike_profile, strike, dimensionality, validator, coords, elevation, converter, batch_export, freq_editor, layered_model, corr_ss_ama, corr_ss_loess, corr_ss_bilateral, corr_ss_refmedian, corr_ss_emap, corr_notch, corr_smooth_logfreq, corr_smooth_rho_phase, corr_rotate_angle, corr_rotate_strike, corr_rotate_pt_strike, corr_rotate_profile, corr_antisymmetrize, corr_coord_projection, corr_coord_spacing, corr_coord_snap, corr_coord_elevation, corr_coord_shift, corr_coord_interpolate, corr_near_field, corr_strat_qc, corr_strat_static_shift, corr_strat_noise, corr_strat_freq_filter, corr_strat_full] or null (only for workflow/plot/code),\n "confidence": float 0..1,\n "clarification": a single question to ask IF the request is too ambiguous\n to route, else null,\n "reasoning": one short sentence\n}\n\nRules:\n- "How do I run an inversion?" is a QUESTION (asking for guidance).\n- "Run an inversion on /data/x" is a WORKFLOW (asking to execute).\n- "Write code to run an inversion" is CODE.\n- Prefer "question" when the user clearly wants to learn, not execute.\n- Set a low confidence (<0.5) and provide "clarification" when genuinely\n ambiguous.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.PackageQAAgent(*, api_key=None, model=None, llm_provider='claude', use_rag=True)#
Bases:
BaseAgentAnswer free-form questions about pycsamt v2.
- Parameters:
Notes
Uses query-adaptive context injection: selects the most relevant documentation tiers (agents, Sites data model, usage examples) based on keywords in the question, rather than always dumping the full reference. This keeps the LLM focused on what’s relevant.
Examples
Offline (no LLM):
agent = PackageQAAgent() r = agent.execute({"question": ("What does StaticShiftAgent do?")}) print(r["answer"])
Online (Claude):
agent = PackageQAAgent( api_key="sk-ant-...", llm_provider="claude", ) r = agent.execute({"question": ("How do I access impedance Z?")}) print(r["answer"])
- SYSTEM_PROMPT: str = 'You are a helpful expert on the pycsamt v2 Python library for magnetotelluric data processing.\n\nThe pycsamt v2 API reference extracted from the live package is provided below. Use it as your *primary source of truth*. If an answer is not in the reference, say so — do not invent class names, parameters, or behaviours that are not listed.\n\n---\nPYCSAMT v2 — PACKAGE OVERVIEW\n==============================\npycsamt is a Python library for magnetotelluric (MT/AMT/CSAMT) data processing, correction, and AI-assisted inversion. Agents are self-contained workflow components; Sites is the main data container for loaded EDI data.\n\nWorkflow keywords (pass as config["workflow"])\n----------------------------------------------\nqc Data quality control + station flagging\nstatic_shift Galvanic static-shift detection + correction\nphase_analysis Phase tensor, dimensionality, strike analysis\nforward Forward model computation\npre_inversion Pre-processing before inversion\ninversion_eval Evaluate inversion results\ninterpretation Geological interpretation of resistivity models\nreport Generate PDF/HTML processing report\nfull Full pipeline: load -> qc -> correct -> invert -> report\nai_inversion 1-D neural network / CNN inversion (EMInverter1D)\ninv1d Alias for ai_inversion\ninv2d 2-D U-Net profile inversion\ninv3d 3-D GCN graph-convolutional inversion\nensemble_inversion Ensemble / uncertainty-quantified inversion\npinn_inversion Physics-Informed Neural Network inversion\nhybrid_inversion PINN + AI inverter hybrid (requires checkpoint)\norchestrated_code_gen Generate reproducible Python script for a workflow\ntipper Tipper / induction arrow analysis\nmodem ModEM 3-D inversion interface\noccam2d Occam2D regularised 2-D inversion interface\n\n\nAgent class reference\n=====================\nWorkflowOrchestratorAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', default_workflow: \'str\' = \n Intelligently route an NL request to the correct agent chain.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n default_workflow : str\n Fallback when NL classification is ambiguous (default ``"qc"``).\n Input keys:\n ----------\n ``request`` : str — natural-language processing request\n ``config`` : dict, optional — pre-built config (skips NL parsing)\n ``dry_run`` : bool — preview without executing (default False)\n ``output_dir`` : str\n Output data keys:\n ----------------\n ``workflow_type`` str\n ``reasoning`` str\n ``coordinator`` AgentCoordinator instance\n ``result`` AgentResult from the coordinator\n\nContextInputAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', use_rag: \'bool\' = True) ->\n Parse a natural-language MT workflow request into a structured config.\n Parameters:\n ----------\n api_key : str or None\n LLM API key. When ``None`` the regex fallback is used exclusively.\n model, llm_provider : str\n Passed to :class:`~pycsamt.agents._base.BaseAgent`.\n\nMTLoaderAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', recursive: \'bool\' = True, \n Load MT data from any pycsamt-supported format and assess quality.\n Parameters:\n ----------\n api_key : str or None\n model, llm_provider : str\n recursive : bool\n When loading a directory, recurse into sub-directories.\n on_dup : str\n Duplicate-station handling: ``"replace"`` (default) or ``"skip"``.\n\nDataQCAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', method: \'str\' = \'composite\n Run data quality control on a MT/AMT dataset.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n LLM configuration (optional).\n method : str\n Confidence scoring method: ``"composite"`` (default), ``"presence"``,\n ``"snr"``, or ``"spatial"``.\n min_frac_ok : float\n Minimum fraction of OK frequencies for a station to pass (0–1).\n min_snr_med : float\n Minimum median SNR for a station to pass.\n max_skew_med : float\n Maximum median |β| skewness for a station to pass.\n Input keys:\n ----------\n ``sites`` : Sites or ``path`` : str\n EDI data to assess.\n ``output_dir`` : str, optional\n Where to save QC figures.\n ``period_range`` : [T_min, T_max], optional\n Restrict QC to this period window.\n Output data keys:\n ----------------\n ``qc_table`` pandas DataFrame — per-station metrics\n ``flags`` pandas DataFrame — pass / fail per station\n ``confidence_table`` pandas DataFrame — per-station confidence scores\n ``freq_conf_table`` pandas DataFrame — per-frequency confidence\n ``n_flagged`` int\n ``flagged_stations`` list[str]\n ``figures`` dict — matplotlib Figure objects\n\nStaticShiftAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', method: \'str\' = \'ama\', hal\n Detect and correct galvanic static shift in MT/AMT data.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n method : {"ama", "loess", "refmedian", "bilateral"}\n Correction algorithm. Default ``"ama"`` (adaptive moving average).\n half_window : int\n Spatial half-window for AMA / LOESS smoothing.\n pband : (T_min, T_max) or None\n Period band used to estimate shift factors.\n inplace : bool\n Modify the input Sites in-place. Default ``False`` (returns a copy).\n Input keys:\n ----------\n ``sites`` / ``path`` : Sites or str\n ``method`` : str, optional — overrides constructor default\n Output data keys:\n ----------------\n ``corrected_sites`` Sites with static shift removed\n ``shift_factors`` dict {station: factor}\n ``rho_before`` ndarray (n_freq × n_sta) — log₁₀ ρa before\n ``rho_after`` ndarray — log₁₀ ρa after\n ``delta_stats`` dict — min/max/mean shift magnitude\n ``figures`` dict — matplotlib Figure objects\n\nDenoisingAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', method: \'str\' = \'rpca\', ra\n Denoise MT impedance data using classical or AI-based methods.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n method : str\n ``"rpca"`` (default), ``"hampel"``, ``"emap"``, ``"pipeline"``,\n or ``"ai"`` / ``"ai_cae"`` (requires PyTorch/TF).\n rank : int\n RPCA rank for off-diagonal denoising (default 2).\n half_window : int\n Hampel filter half-window (default 3).\n Input keys:\n ----------\n ``sites`` / ``path`` : Sites or str\n ``method`` : str, optional — overrides constructor default\n ``output_dir`` : str, optional\n Output data keys:\n ----------------\n ``denoised_sites`` Sites with denoised impedance\n ``snr_before`` ndarray — per-(station, freq) SNR proxy before\n ``snr_after`` ndarray — per-(station, freq) SNR proxy after\n ``snr_gain`` float — mean SNR improvement\n ``n_recovered`` int — frequencies recovered above SNR threshold\n ``figures`` dict\n ``figure_paths`` dict\n\nPhaseAnalysisAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', skew_th: \'float\' = 5.0, el\n Run a full phase tensor, strike, and dimensionality survey analysis.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n skew_th : float\n Skewness |β| threshold for 3-D classification (°).\n ellipt_th : float\n Ellipticity λ threshold for 2-D classification.\n band : (T_min, T_max) or None\n Period band for strike estimation.\n Input keys:\n ----------\n ``sites`` / ``path`` : Sites or str\n ``period_range`` : [T_min, T_max], optional\n ``output_dir`` : str, optional\n ``run_mohr`` : bool, optional — also produce Mohr circles (default False)\n Output data keys:\n ----------------\n ``pt_table`` pandas DataFrame — full PT metrics per (station, period)\n ``strike_consensus`` float — consensus strike angle (°)\n ``strike_iqr`` float — IQR of strike across all stations\n ``dim_table`` pandas DataFrame — per-(station, period) classification\n ``n_1d``, ``n_2d``, ``n_3d`` int — count of observations per class\n ``figures`` dict — matplotlib Figure objects\n\nForwardModelAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', dim: \'int\' = 1, freqs: \'An\n Run a 1-D, 2-D, or 3-D MT forward model.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n dim : {1, 2, 3}\n Forward solver dimensionality.\n freqs : array-like or None\n Frequencies (Hz). Defaults to 40 log-spaced points 10⁻⁴–10³ Hz.\n Input keys:\n ----------\n ``model`` : dict or LayeredModel or None\n **1-D / 2-D from 1-D layers:**\n ``{"resistivities": [...], "thicknesses": [...]}``.\n \n **2-D grid type override:**\n add ``"type": "halfspace" | "anomaly"`` and grid parameters such as\n ``"bg_rho"``, ``"anomaly_rho"``, ``"anomaly_bounds"``.\n Output data keys:\n ----------------\n ``dim`` int\n ``layered_model`` LayeredModel (1-D / 2-D from 1-D)\n ``grid`` Grid2D or Grid3D (2-D / 3-D)\n ``response`` ForwardResponse / ForwardResponse2D / ForwardResponse3D\n ``rho_a`` ndarray — 1-D ρa\n ``phase`` ndarray — 1-D phase (°)\n ``rho_a_te`` ndarray (n_freqs, n_stations) — 2-D TE\n\nInterpretationAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', context: \'str\' = \'\') -> \'N\n Interpret a resistivity model in terms of geological formations.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n context : str\n Optional geological context passed to the LLM\n (e.g. ``"Semi-arid Precambrian terrain, looking for aquifers"``).\n Input keys:\n ----------\n ``model`` : dict or LayeredModel\n ``{"resistivities": [...], "thicknesses": [...]}``\n ``rms`` : float, optional\n ``context`` : str, optional — overrides constructor default\n ``sites`` / ``path`` : optional — for period range context\n Output data keys:\n ----------------\n ``layer_interpretations`` list of dict\n ``summary_text`` str\n ``dominant_lithology`` str\n ``formation_depths_m`` list[float]\n\nReportAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', report_title: \'str\' = \'MT/\n Generate a structured MT survey report from agent results.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n report_title : str\n Title for the report.\n formats : list of {"md", "html", "pdf"}\n Output formats. Default ``["md", "html"]``.\n Input keys:\n ----------\n ``results`` : dict\n Keyed by agent step name → :class:`AgentResult`.\n Expected keys: ``"load"``, ``"qc"``, ``"static_shift"``,\n ``"phase_analysis"``, ``"forward"`` (all optional).\n ``output_dir`` : str\n Output data keys:\n ----------------\n ``report_md`` str — full markdown text\n ``report_html`` str or None\n ``report_path_md`` str — path to .md file\n ``report_path_html`` str or None\n\nAIInversionAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', arch: \'str\' = \'resnet\', n_\n Train an AI inverter on synthetic data then predict on observed sites.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n arch : {"resnet", "cnn1d", "fcn"}\n Neural network architecture.\n n_layers : int\n Number of model layers the inverter will predict (default 5).\n n_train_samples : int\n Number of synthetic training samples (default 2 000).\n epochs : int\n Training epochs (default 30). Increase for better models.\n freqs : array-like or None\n Frequencies used for both training synthesis and observed data\n Input keys:\n ----------\n ``sites`` / ``path`` : Sites or str — observed data\n ``output_dir`` : str, optional\n Output data keys:\n ----------------\n ``inverter`` :class:`~pycsamt.ai.inversion.inv1d.EMInverter1D`\n ``predictions`` dict {station: ndarray of log₁₀ ρ values}\n ``best_model`` dict with "resistivity" and "thickness" for first station\n ``rms_per_station`` dict {station: float}\n ``rms_global`` float\n ``train_history`` dict (loss curves)\n ``figures`` dict\n\nPINNInversionAgent(*, dim: \'int\' = 1, n_layers: \'int\' = 10, depth_max: \'float\' = 2000.0, smoothness_weight: \'float\' = 0.01, lateral_weight\n PINN-based MT inversion without labelled data.\n Parameters:\n ----------\n dim : {1, 2, 3}\n Dimensionality. Default ``1``.\n n_layers : int\n Number of layers including the halfspace.\n Default ``10``.\n depth_max : float\n Maximum investigation depth in metres.\n Default ``2000.0``.\n smoothness_weight : float\n Vertical regularisation weight.\n Default ``0.01``.\n Input keys:\n ----------\n ``sites`` / ``path`` observed data\n ``output_dir`` optional figure/save dir\n Output data keys:\n ----------------\n ``inverter`` fitted inverter object\n ``section`` ndarray (n_layers, n_stations)\n log10-rho section matrix\n ``models`` list of LayeredModel (1-D)\n ``n_stations`` int\n ``rms_per_station`` dict {station: float}\n ``rms_global`` float\n\nHybridInversionAgent(*, dim: \'int\' = 1, max_iter: \'int\' = 200, smoothness_weight: \'float\' = 0.005, lateral_weight: \'float\' = 0.005, graph_we\n Two-stage AI + physics MT inversion.\n Parameters:\n ----------\n dim : {1, 2, 3}\n Dimensionality. Default ``1``.\n max_iter : int\n Physics refinement iterations (Stage 2).\n Default ``200``.\n smoothness_weight : float\n Vertical regularisation weight.\n Default ``0.005``.\n lateral_weight : float\n Lateral smoothness weight (2-D only).\n Default ``0.005``.\n Input keys:\n ----------\n ``sites`` / ``path`` observed data\n ``ai_inverter`` fitted AI inverter object\n or path to checkpoint\n ``checkpoint`` alias for ``ai_inverter``\n ``output_dir`` optional save directory\n ``dim``, ``max_iter``,\n ``smoothness_weight``,\n Output data keys:\n ----------------\n ``inverter`` fitted HybridInverterXD\n ``section`` ndarray (n_layers, n_stations)\n Stage-2 log10-rho section\n ``stage1_section`` ndarray — Stage-1 section\n ``models`` list of LayeredModel (1-D)\n ``stage1_models`` list of LayeredModel (1-D)\n ``n_stations`` int\n\nInv2DAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', n_depth: \'int\' = 40, n_fre\n 2-D MT profile inversion using a U-Net convolutional architecture.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n n_depth : int\n Number of depth cells in the output section (default 40).\n n_freqs : int\n Number of input frequencies (default 32).\n freqs : array-like or None\n Explicit positive frequency grid in hertz.\n depth_max : float or None\n Maximum cumulative model depth in metres. ``None`` preserves the\n legacy frequency-derived parameterization.\n n_components : int\n Input keys:\n ----------\n ``sites`` / ``path`` : Sites or str\n ``output_dir`` : str, optional\n ``topography`` : bool or dict, optional\n Extract terrain from ``sites`` and render the predicted section in an\n absolute-elevation frame. A mapping can provide ``elevation_m`` and\n ``chainage_km`` plus ``exaggeration`` and ``interp_method``.\n ``period_range`` : [T_min, T_max], optional\n Output data keys:\n ----------------\n ``pred_section`` ndarray (n_depth × n_stations) — log₁₀ ρ\n (``physics="mt1d"``/``"mt2d"`` only)\n ``pred_triangles`` dict with ``"mesh"``/``"log10_resistivity"``\n (``physics="mt2d_tri"`` only)\n ``depths_km`` ndarray — depth axis (km)\n (``physics="mt1d"``/``"mt2d"`` only)\n ``station_names`` list[str]\n\nInv3DAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', n_layers: \'int\' = 5, n_fre\n 3-D MT profile inversion using a graph-convolutional network (GCN).\n Parameters:\n ----------\n api_key, model, llm_provider : str\n n_layers : int\n Number of depth layers per station (default 5).\n n_freqs : int\n Number of frequencies used for feature extraction (default 32).\n freqs : array-like or None\n Explicit positive frequency grid in hertz. When supplied, it replaces\n the legacy ``10^-4``--``10^3`` Hz grid and determines ``n_freqs``.\n depth_max : float or None\n Maximum cumulative model depth in metres. When supplied, layer\n thicknesses are geometrically graded and normalized to this depth.\n Input keys:\n ----------\n ``sites`` / ``path`` : Sites or str — observed MT dataset\n ``coords`` : ndarray (n_stations, 2), optional — station (x, y) in metres.\n Auto-extracted from EDI lat/lon when absent.\n ``adjacency`` : ndarray (n_stations, n_stations), optional — pre-computed\n normalised adjacency; overrides *radius* when supplied.\n ``output_dir`` : str, optional\n ``freqs`` : array-like, optional — execution-time frequency-grid override.\n Output data keys:\n ----------------\n ``pred_rho`` ndarray (n_sta, n_layers) — log₁₀ρ\n ``pred_thick`` ndarray (n_sta, n_layers-1) — log₁₀h (metres)\n ``pred_uncertainty`` ndarray (n_sta, n_layers) or None — MC-dropout std\n ``depths_km`` ndarray — depth axis at station midpoints (km)\n ``frequency_grid_hz`` ndarray — effective feature/forward frequency grid\n ``configured_depth_max_m`` float or None — explicit depth contract\n ``topography`` dict or None — resolved terrain metadata\n\nCodeGenerationAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', script_title: \'str\' = \'pyc\n Generate a reproducible Python script from a completed workflow.\n Parameters:\n ----------\n api_key, model, llm_provider : str\n When an API key is provided the LLM refines and annotates the\n generated code. Otherwise the agent uses static templates.\n Input keys:\n ----------\n ``workflow_config`` : dict\n The config dict produced by :class:`ContextInputAgent`.\n ``results`` : dict\n The agent results dict from :class:`AgentCoordinator`.\n Output data keys:\n ----------------\n ``code`` str — Python source code\n\n\nSites / EDI data model reference\n================================\nSites(edic: \'EDICollection | Sequence[EDIFile | Any | Site]\', *, on_loss: \'str\' = \'warn\') -> \'None\'\n Container for multiple :class:`~pycsamt.site.base.Site` objects with convenient indexing, selection, and bulk edit operations.\n Constructor parameters:\n ----------\n edic : pycsamt.seg.collection.EDICollection or sequence of\n pycsamt.seg.edi.EDIFile, pycsamt.emtf.document.EMTF, or\n :class:`Site`\n Parsed EDI collection, or any sequence mixing EDI objects,\n EMTF XML documents, and already-constructed :class:`Site`\n instances. Items are wrapped into :class:`Site` instances\n (existing ``Site`` items are used as-is) in the order\n provided.\n Attributes:\n ----------\n _items : list of Site\n Internal sequence of sites. This is considered private.\n Iterate over ``Sites`` instead of accessing it directly.\n Key methods:\n .from_any(source: \'Any\', topo_src: \'Any | None\' = None) -> \'Sites\'\n Construct a container from heterogeneous inputs by using a normalized loading session.\n .write(outdir: \'str | Path\', *, template: \'str\' = \'{station}.edi\', exist_ok: \'bool\' = \n Write one EDI file per site to a directory.\n .select(names: \'Sequence[str] | None\' = None, predicate: \'Callable[[Site], bool] | None\n Filter sites by explicit names or by a boolean predicate.\n .edit_all(*, rename: \'Callable[[str], str] | None\' = None, freq_slice: \'slice | None\' = N\n Bulk-edit all sites with optional rename, frequency slicing, and tensor masking.\n .closest(lat: \'float\', lon: \'float\', tol: \'float | None\' = None) -> \'Site | None\'\n Find the closest site to a target coordinate using geodetic distance.\n .to_profile(origin: \'tuple[float, float]\', azimuth: \'float\', *, crs: \'int | None\' = None) -\n Convert sites to a 1D profile aligned with a specified azimuth, returning either a rich Profile object or a lightweight fallback.\n .to_edicollection(*, copy: \'bool\' = False, progress: \'bool | str\' = False, verbose: \'int\' = 0) ->\n Return the underlying EDI objects as an ``EDICollection``.\n .to_edis(*, copy: \'bool\' = False, progress: \'bool | str\' = False, verbose: \'int\' = 0) ->\n Return the underlying EDI objects as a list.\n .as_list() -> \'list[EDIFile]\'\n Return the underlying list of EDI objects.\n .get(name: \'str\') -> \'Site | None\'\n Safe lookup by case-insensitive station name.\n .by_index(i: \'int\') -> \'Site\'\n Retrieve a site by zero-based index.\n .map(fn: \'Callable[[Site], Any]\') -> \'list[Any]\'\n Apply a function to every site and collect the results.\n\nKey data attributes (set after construction)\n--------------------------------------------\nsites[i] Site object for station i\nsites[i].Z Impedance tensor: shape (n_freq, 2, 2), complex\nsites[i].freq Frequencies array (Hz)\nsites[i].rho Apparent resistivity (Ohm.m), shape (n_freq, 2, 2)\nsites[i].phase Phase (degrees), shape (n_freq, 2, 2)\nsites[i].id Station name / identifier\nlen(sites) Number of stations loaded\n\n\nensure_sites(source) — universal EDI loader\n-------------------------------------------\nAccepts: a Sites object, a directory path (str/Path),\n a list of EDI file paths, or an EDICollection.\nReturns: Sites object.\nImport: from pycsamt.ai.inversion._sites_bridge import ensure_sites\nUse case: any agent or function that needs to accept\n flexible EDI input without caring about the format.\n\n\nwrite_sites(sites, dest, exist_ok=True) — EDI exporter\n-------------------------------------------------------\nWrites one corrected EDI file per site to dest/.\nImport: from pycsamt.site.export import write_sites\nUse case: export corrected Sites after static shift or QC.\n\n\nUsage examples (copy-paste ready)\n=================================\n# 1. Load EDI files and run quality control\nfrom pycsamt.agents import MTLoaderAgent, DataQCAgent\nloader = MTLoaderAgent()\nsites = loader.execute({"path": "/data/L22PLT"})["sites"]\nqc = DataQCAgent()\nreport = qc.execute({"sites": sites})\n\n# 2. Correct static shift\nfrom pycsamt.agents import StaticShiftAgent\nagent = StaticShiftAgent(method="ama")\nresult = agent.execute({"sites": sites})\ncorrected = result["corrected_sites"]\nfactors = result["shift_factors"] # {station: factor}\n\n# 3. Full orchestrated workflow (with LLM router)\nfrom pycsamt.agents import WorkflowOrchestratorAgent\norch = WorkflowOrchestratorAgent(api_key="sk-...")\nresult = orch.execute({\n "config": {"workflow": "qc"},\n "data_path": "/data/L22PLT",\n "output_dir": "/out/qc/",\n})\n\n# 4. Load EDI collection -> Sites\nfrom pycsamt.edi import EDICollection\ncol = EDICollection("/data/L22PLT")\nsites = col.to_sites() # -> Sites object\n\n# 5. Universal loader (accepts dir, list, or Sites)\nfrom pycsamt.ai.inversion._sites_bridge import ensure_sites\nsites = ensure_sites("/data/L22PLT") # dir\nsites = ensure_sites(["a.edi", "b.edi"]) # list\n\n# 6. Access impedance tensor data\nsite0 = sites[0] # first station\nZ = site0.Z # (n_freq, 2, 2) complex\nfreq = site0.freq # frequencies (Hz)\nrho = site0.rho # apparent resistivity\nphase = site0.phase # phase (degrees)\n\n# 7. Export corrected EDI files\nfrom pycsamt.site.export import write_sites\nwrite_sites(corrected, "/out/corrected_edis/")\n\n# 8. PINN inversion\nfrom pycsamt.agents import PINNInversionAgent\nagent = PINNInversionAgent(epochs=200, n_layers=8)\nresult = agent.execute({"sites": sites})\nmodel = result["model"] # layered resistivity model\n\n# 9. Generate workflow script\nfrom pycsamt.agents import CodeGenerationAgent\ncg = CodeGenerationAgent()\nresult = cg.execute({\n "sites": sites,\n "workflow": "qc",\n "output_dir": "/out/",\n})\nprint(result["code"]) # Python source\n\n\nemtools.ss / Static-shift reference\n===================================\nread_edis -- load many EDI files\n---------------------------------\nfrom pycsamt.api import read_edis\n\nSignature:\n read_edis(sources, *, recursive=True,\n strict=False, on_dup="replace",\n progress="auto", leave=False,\n verbose=0) -> APISurvey\n\nsources : str, Path, list, or glob pattern\nReturns APISurvey\n .collection -> Sites / EDICollection\n .name -> survey name string\n\nTypical use:\n survey = read_edis("L22PLT/")\n sites = survey.collection\n\n\nestimate_ss_ama -- AMA static-shift factors\n--------------------------------------------\nfrom pycsamt.emtools.ss import estimate_ss_ama\n\nSignature:\n estimate_ss_ama(sites, *, sort_by="lon",\n half_window=3, weights="tri",\n pband=None, max_skew=6.0,\n robust_freq="median",\n robust_overall="median",\n recursive=True, on_dup="replace",\n strict=False, verbose=0,\n api=None) -> DataFrame\n\nsites : Sites, str, Path, list, EDICollection\nsort_by : \'lon\'|\'lat\'|\'name\' (station order)\nhalf_window : k neighbours each side (default 3)\nweights : \'tri\'|\'gauss\'|\'uniform\'\npband : (p_min_s, p_max_s) period band\nmax_skew : |beta| threshold (default 6.0)\napi : wrap result in APIFrame when True\n\nReturns DataFrame (one row per station):\n station -- station name\n delta_log10_rho -- log10 shift (pos=above trend)\n fac_rho -- 10^(-delta) rho factor\n fac_z -- 10^(-0.5*delta) Z factor\n n_used -- frequencies used\n\nTypical use:\n tbl = estimate_ss_ama(\n sites, half_window=3, sort_by="lon"\n )\n print(\n tbl[["station","delta_log10_rho","fac_z"]]\n )\n\n\ncorrect_ss_ama -- estimate + apply AMA correction\n--------------------------------------------------\nfrom pycsamt.emtools.ss import correct_ss_ama\n\nSignature:\n correct_ss_ama(sites, *, sort_by="lon",\n half_window=3, weights="tri",\n pband=None, max_skew=6.0,\n robust_freq="median",\n robust_overall="median",\n inplace=False, recursive=True,\n on_dup="replace", strict=False,\n verbose=0) -> Sites\n\nCalls estimate_ss_ama then\napply_ss_factors(key="fac_z").\ninplace=False returns a corrected copy.\n\nTypical use:\n sites_corr = correct_ss_ama(\n sites, half_window=3, sort_by="lon"\n )\n\n\nplot_ss_summary -- four-panel correction figure\n------------------------------------------------\nfrom pycsamt.emtools.ss import plot_ss_summary\n\nSignature:\n plot_ss_summary(\n logRho_before, logRho_after, *,\n freqs, station_labels=None, ...\n ) -> matplotlib.Figure\n\nPanels: (a) Before, (b) After pseudosections,\n (c) Delta section, (d) Per-station bars.\n\nlogRho_before, logRho_after : ndarray (n_st, n_f)\n log10 apparent-resistivity arrays.\nfreqs : ndarray (n_f,) Hz.\nstation_labels : list of str or None.\n\nplot_ss_1d_curves -- per-station sounding grid\n-----------------------------------------------\nfrom pycsamt.emtools.ss import plot_ss_1d_curves\n\nSignature:\n plot_ss_1d_curves(\n logRho_before, logRho_after, *,\n freqs, station_labels=None,\n n_cols=4, max_stations=16, ...\n ) -> matplotlib.Figure\n\nGrid of subplots (one per station) showing\nbefore/after log10-rho sounding curves.\n\nBuild logRho arrays from Sites:\n from pycsamt.emtools._core import (\n _get_z_block, _name, _iter_items,\n )\n\n def collect_logRho(S):\n rows, freqs = [], None\n for i, ed in enumerate(_iter_items(S)):\n Z, z, fr = _get_z_block(ed)\n if Z is None:\n continue\n rxy = (\n 0.2*np.abs(z[:,0,1])**2\n /(fr+1e-24)\n )\n ryx = (\n 0.2*np.abs(z[:,1,0])**2\n /(fr+1e-24)\n )\n rows.append(\n np.log10(\n np.sqrt(rxy*ryx)+1e-24\n )\n )\n freqs = fr\n return np.array(rows), freqs\n\n logRho_b, freqs = collect_logRho(sites)\n logRho_a, _ = collect_logRho(sites_corr)\n labels = [\n _name(ed, i)\n for i, ed in enumerate(_iter_items(sites))\n ]\n fig = plot_ss_summary(\n logRho_b, logRho_a,\n freqs=freqs, station_labels=labels,\n )\n fig.savefig("ss_summary.png", dpi=150)\n\n\n# Static-shift correction full workflow\nfrom pycsamt.api import read_edis\nfrom pycsamt.emtools.ss import (\n estimate_ss_ama,\n correct_ss_ama,\n plot_ss_summary,\n plot_ss_1d_curves,\n)\nfrom pycsamt.agents import StaticShiftAgent\nfrom pycsamt.emtools._core import (\n _get_z_block, _name, _iter_items,\n)\nimport numpy as np\n\n# 1. Load EDI files\nsurvey = read_edis("L22PLT/")\nsites = survey.collection\n\n# 2. Inspect shift factors (optional)\nss_table = estimate_ss_ama(\n sites,\n half_window=3,\n sort_by="lon",\n weights="tri",\n max_skew=6.0,\n)\nprint(\n ss_table[[\n "station","delta_log10_rho","fac_z"\n ]]\n)\n\n# 3. Correct Z tensor in place\nsites_corr = correct_ss_ama(\n sites,\n half_window=3,\n sort_by="lon",\n)\n\n# 4. (Alternative) via StaticShiftAgent\nagent = StaticShiftAgent(method="ama")\nresult = agent.execute({"sites": sites})\nsites_corr2 = result["corrected_sites"]\nfactors = result["shift_factors"]\n\n# 5. Build log10-rho arrays for plots\ndef collect_logRho(S):\n rows, freqs = [], None\n for i, ed in enumerate(_iter_items(S)):\n Z, z, fr = _get_z_block(ed)\n if Z is None:\n continue\n rxy = (\n 0.2*np.abs(z[:,0,1])**2/(fr+1e-24)\n )\n ryx = (\n 0.2*np.abs(z[:,1,0])**2/(fr+1e-24)\n )\n rows.append(\n np.log10(np.sqrt(rxy*ryx)+1e-24)\n )\n freqs = fr\n return np.array(rows), freqs\n\nlogRho_b, freqs = collect_logRho(sites)\nlogRho_a, _ = collect_logRho(sites_corr)\nlabels = [\n _name(ed, i)\n for i, ed in enumerate(_iter_items(sites))\n]\n\n# 6. Summary figure\nfig_sum = plot_ss_summary(\n logRho_b, logRho_a,\n freqs=freqs,\n station_labels=labels,\n)\nfig_sum.savefig(\n "ss_summary.png", dpi=150,\n bbox_inches="tight",\n)\n\n# 7. Per-station 1-D sounding curves\nfig_1d = plot_ss_1d_curves(\n logRho_b, logRho_a,\n freqs=freqs,\n station_labels=labels,\n)\nfig_1d.savefig(\n "ss_1d_curves.png", dpi=150,\n bbox_inches="tight",\n)\n\n---\n\nAnswer in clear technical English. Include short code examples when helpful.'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at 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)
- Return type:
- class pycsamt.agents.MTLoaderAgent(*, api_key=None, model=None, llm_provider='claude', recursive=True, on_dup='replace')#
Bases:
BaseAgentLoad MT data from any pycsamt-supported format and assess quality.
- Parameters:
Examples
>>> agent = MTLoaderAgent() >>> result = agent.execute({"path": "/data/AMT/WILLY_DATA/L22PLT"}) >>> result.status 'success' >>> result["n_stations"] 25 >>> result["quality_table"].head() station has_z ... qc_score 0 22-22BF True ... 88
- SYSTEM_PROMPT: str = 'You are an expert MT/AMT/CSAMT data quality analyst.\nGiven a per-station data quality summary, write 2–3 concise sentences that:\n1. State the overall data quality.\n2. Flag any stations or frequency ranges that need attention.\n3. Recommend the next processing step.\nReply in plain English — no bullet points, no markdown.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.DataQCAgent(*, api_key=None, model=None, llm_provider='claude', method='composite', min_frac_ok=0.6, min_snr_med=2.0, max_skew_med=6.0)#
Bases:
BaseAgentRun data quality control on a MT/AMT dataset.
- Parameters:
api_key (str) – LLM configuration (optional).
model (str) – LLM configuration (optional).
llm_provider (str) – LLM configuration (optional).
method (str) – Confidence scoring method:
"composite"(default),"presence","snr", or"spatial".min_frac_ok (float) – Minimum fraction of OK frequencies for a station to pass (0–1).
min_snr_med (float) – Minimum median SNR for a station to pass.
max_skew_med (float) – Maximum median |β| skewness for a station to pass.
keys (Output data)
----------
sites (Sites or
path: str) – EDI data to assess.output_dir (str, optional) – Where to save QC figures.
period_range ([T_min, T_max], optional) – Restrict QC to this period window.
keys
----------------
metrics (qc_table pandas DataFrame — per-station)
station (flags pandas DataFrame — pass / fail per)
scores (confidence_table pandas DataFrame — per-station confidence)
confidence (freq_conf_table pandas DataFrame — per-frequency)
int (n_flagged)
list[str] (flagged_stations)
objects (figures dict — matplotlib Figure)
set) (figure_paths dict — saved file paths (when output_dir)
Examples
>>> agent = DataQCAgent() >>> result = agent.execute( ... {"path": "/data/L22PLT", "output_dir": "/out/qc"} ... ) >>> result["n_flagged"] 2 >>> result["figures"]["confidence_section"] <Figure …>
- SYSTEM_PROMPT: str = 'You are an expert MT/AMT/CSAMT data quality analyst for pycsamt v2.\nGiven a survey QC summary, write 3–4 sentences that:\n1. State the overall data quality (good / moderate / poor).\n2. Identify specific stations or frequency bands that need attention.\n3. Explain the likely cause (instrument noise, EM interference, near-field).\n4. Recommend the most important next processing step.\nReply in plain English. No bullet points, no markdown headings.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.StaticShiftAgent(*, api_key=None, model=None, llm_provider='claude', method='ama', half_window=3, pband=None, inplace=False)#
Bases:
BaseAgentDetect and correct galvanic static shift in MT/AMT data.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
method (str, optional — overrides constructor default) – Correction algorithm. Default
"ama"(adaptive moving average).half_window (int) – Spatial half-window for AMA / LOESS smoothing.
pband ((T_min, T_max) or None) – Period band used to estimate shift factors.
inplace (bool) – Modify the input Sites in-place. Default
False(returns a copy).keys (Output data)
----------
path (sites /)
method
output_dir (str, optional)
keys
----------------
removed (corrected_sites Sites with static shift)
{station (shift_factors dict)
before (rho_before ndarray (n_freq × n_sta) — log₁₀ ρa)
after (rho_after ndarray — log₁₀ ρa)
magnitude (delta_stats dict — min/max/mean shift)
objects (figures dict — matplotlib Figure)
paths (figure_paths dict — saved file)
Examples
>>> agent = StaticShiftAgent(method="ama") >>> result = agent.execute( ... {"path": "/data/L22PLT", "output_dir": "/out/ss"} ... ) >>> result["delta_stats"] {'mean': 0.18, 'max': 0.42, 'n_shifted': 7}
- SYSTEM_PROMPT: str = 'You are an expert in galvanic distortion and static-shift correction for magnetotelluric data.\nGiven a static-shift correction summary, write 3–4 sentences that:\n1. State whether significant static shift was detected.\n2. Identify stations with the largest corrections and their magnitude.\n3. Assess whether the correction method was appropriate for this dataset.\n4. Recommend any follow-up action (e.g., additional spatial filtering).\nReply in plain English. No bullet points or markdown.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.PhaseAnalysisAgent(*, api_key=None, model=None, llm_provider='claude', skew_th=5.0, ellipt_th=0.1, band=None)#
Bases:
BaseAgentRun a full phase tensor, strike, and dimensionality survey analysis.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
skew_th (float) – Skewness |β| threshold for 3-D classification (°).
ellipt_th (float) – Ellipticity λ threshold for 2-D classification.
band ((T_min, T_max) or None) – Period band for strike estimation.
keys (Output data)
----------
path (sites /)
period_range ([T_min, T_max], optional)
output_dir (str, optional)
run_mohr (bool, optional — also produce Mohr circles (default False))
run_fingerprint (bool, optional — produce fingerprint grid (default True))
keys
----------------
(station (pt_table pandas DataFrame — full PT metrics per)
period)
(°) (strike_consensus float — consensus strike angle)
stations (strike_iqr float — IQR of strike across all)
per-(station (dim_table pandas DataFrame —)
classification (period))
n_1d
n_2d
class (n_3d int — count of observations per)
objects (figures dict — matplotlib Figure)
paths (figure_paths dict — saved file)
Examples
>>> agent = PhaseAnalysisAgent() >>> result = agent.execute( ... {"path": "/data/L22PLT", "output_dir": "/out/pt"} ... ) >>> result["strike_consensus"] 42.5
- SYSTEM_PROMPT: str = 'You are an expert in MT phase tensor analysis and geological interpretation.\nGiven a survey phase tensor summary, write 4–5 sentences that:\n1. State the dominant dimensionality (1-D, 2-D, or 3-D) with evidence.\n2. Report the consensus geoelectric strike direction and its reliability.\n3. Identify periods / depth ranges where 3-D structure becomes significant.\n4. Note any anomalous stations (high skew, low ellipticity).\n5. Recommend whether to rotate data to strike before inversion.\nReply in plain English. No bullet points or markdown.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.ForwardModelAgent(*, api_key=None, model=None, llm_provider='claude', dim=1, freqs=None)#
Bases:
BaseAgentRun a 1-D, 2-D, or 3-D MT forward model.
- Parameters:
api_key (str)
model (dict or LayeredModel or None)
llm_provider (str)
dim (int, optional — overrides constructor dim for this call) – Forward solver dimensionality.
freqs (array-like, optional — overrides constructor default) – Frequencies (Hz). Defaults to 40 log-spaced points 10⁻⁴–10³ Hz.
keys (Output data)
----------
model –
1-D / 2-D from 1-D layers:
{"resistivities": [...], "thicknesses": [...]}.2-D grid type override: add
"type": "halfspace" | "anomaly"and grid parameters such as"bg_rho","anomaly_rho","anomaly_bounds".3-D grid type:
"type": "halfspace" | "block_anomaly"with grid parameters.dim
nx (int / float, optional (2-D grid))
nz (int / float, optional (2-D grid))
x_max (int / float, optional (2-D grid))
z_max (int / float, optional (2-D grid))
ny (int / float (3-D))
y_max (int / float (3-D))
nx_stations (int / float (3-D))
ny_stations (int / float (3-D))
n_stations (int, optional — number of surface receivers (2-D))
method (str, optional —
"quasi3d"(default) for 3-D solver)path (sites /)
freqs
output_dir (str, optional)
component (
"xy"(default) or"yx"(1-D component selection))keys
----------------
int (dim)
1-D) (layered_model LayeredModel (1-D / 2-D from)
3-D) (grid Grid2D or Grid3D (2-D /)
ForwardResponse3D (response ForwardResponse / ForwardResponse2D /)
ρa (rho_a ndarray — 1-D)
(°) (phase ndarray — 1-D phase)
(n_freqs (rho_a_xy ndarray)
TE (n_stations) — 2-D)
phase (phase_yx ndarray — 3-D YX)
TM (rho_a_tm ndarray — 2-D)
phase
(n_freqs
XY (n_stations) — 3-D)
phase
YX (rho_a_yx ndarray — 3-D)
phase
ndarray (freqs)
None (rms float or)
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert in MT forward modelling and resistivity earth models.\nGiven a forward model result, write 3-4 sentences that:\n1. Describe the model geometry (dimensionality, layers / grid, resistivity range).\n2. Comment on the synthetic ρa and phase response (frequency range, lateral variation for 2D/3D).\n3. If observed data are provided, interpret the data-model misfit (1-D only).\n4. Suggest which model parameters to adjust to better fit the data or geology.\nReply in plain English. No bullet points or markdown.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.InversionPrepAgent(*, api_key=None, model=None, llm_provider='claude', code='occam2d', error_floor=0.05)#
Bases:
BaseAgentPrepare MT data files for 2-D / 3-D inversion codes.
2.26. Currently supported output formats#
"occam2d"— Occam2D DataFile format"modem"— ModEM3D data file (Phase 3)
2.26. Input keys#
sites/path: Sites or strcode: str —"occam2d"(default) or"modem"period_range: [T_min, T_max], optionalcomponent: str —"xy","yx", or"both"error_floor: float — minimum relative error floor (default 0.05)output_dir: str2.26. Output data keys#
data_file_pathstr — path to the written data filen_periodsintn_stationsintcodestr- SYSTEM_PROMPT: str = 'You are an expert MT inversion specialist.\nGiven a dataset summary and chosen inversion code, recommend:\n1. Appropriate period band and error floor.\n2. Mesh geometry (cell sizes, padding, depth extent).\n3. Regularisation starting values.\n4. Any pre-processing steps still needed before inversion.\nReply in 4–5 sentences, plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.InversionEvaluationAgent(*, api_key=None, model=None, llm_provider='claude')#
Bases:
BaseAgentEvaluate inversion quality: RMS, residual PT, misfit section.
2.26. Input keys#
sites_obs/path_obs: Sites or str — observed datasites_mod/path_mod: Sites or str — model-predicted responsesoutput_dir: str, optionalcomponent: str — default"xy"2.26. Output data keys#
rms_per_stationdict {station: rms}rms_globalfloatresidual_pt_tablepandas DataFramefiguresdictfigure_pathsdict- SYSTEM_PROMPT: str = 'You are an expert MT inversion quality assessor.\nGiven a misfit summary, write 3–4 sentences that:\n1. State whether the inversion converged acceptably (RMS 0.8–1.5 = good).\n2. Identify stations or period bands with elevated misfit.\n3. Diagnose likely causes (3-D effects, noise, model inadequacy).\n4. Recommend whether to re-run with adjusted regularisation.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.InterpretationAgent(*, api_key=None, model=None, llm_provider='claude', context='')#
Bases:
BaseAgentInterpret a resistivity model in terms of geological formations.
- Parameters:
api_key (str)
model (dict or LayeredModel)
llm_provider (str)
context (str, optional — overrides constructor default) – Optional geological context passed to the LLM (e.g.
"Semi-arid Precambrian terrain, looking for aquifers").keys (Output data)
----------
model –
{"resistivities": [...], "thicknesses": [...]}rms (float, optional)
context
path (sites /)
output_dir (str, optional)
keys
----------------
dict (layer_interpretations list of)
str (dominant_lithology)
str
list[float] (formation_depths_m)
- SYSTEM_PROMPT: str = 'You are an expert hydrogeologist and applied geophysicist specialising in electrical resistivity interpretation.\nGiven a 1-D or 2-D resistivity model from MT/AMT inversion, write a geological interpretation that:\n1. Identifies the likely lithological units (e.g. weathered zone, basement,\n aquifer, clay layer) based on resistivity ranges.\n2. Estimates formation depths and thicknesses.\n3. Discusses implications for groundwater, mineral exploration, or hazard\n assessment depending on the survey context.\n4. States uncertainties and what additional data would reduce them.\nReply in plain scientific English, 5–8 sentences. No bullet points.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.ReportAgent(*, api_key=None, model=None, llm_provider='claude', report_title='MT/AMT Survey Report', formats=None)#
Bases:
BaseAgentGenerate a structured MT survey report from agent results.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
report_title (str) – Title for the report.
formats (list of {"md", "html", "pdf"}) – Output formats. Default
["md", "html"].keys (Output data)
----------
results (dict) – Keyed by agent step name →
AgentResult. Expected keys:"load","qc","static_shift","phase_analysis","forward"(all optional).output_dir (str)
title (str, optional — overrides constructor default)
keys
----------------
text (report_md str — full markdown)
None (report_path_html str or)
file (report_path_md str — path to .md)
None
name (sections dict — section text keyed by)
Examples
>>> agent = ReportAgent(api_key="sk-ant-…") >>> result = agent.execute( ... { ... "results": {"load": load_result, "qc": qc_result}, ... "output_dir": "/out/report", ... "title": "WILLY_DATA AMT Survey — L22PLT", ... } ... ) >>> print(result["report_path_md"]) /out/report/survey_report.md
- SYSTEM_PROMPT: str = 'You are a geophysics technical writer specialising in MT surveys.\nWrite clear, concise report sections in formal scientific English.\nUse complete sentences. No markdown headings inside your response.\nKeep each section to 3–5 sentences.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.CodeGenerationAgent(*, api_key=None, model=None, llm_provider='claude', script_title='pycsamt MT Processing Workflow')#
Bases:
BaseAgentGenerate a reproducible Python script from a completed workflow.
- Parameters:
api_key (str) – When an API key is provided the LLM refines and annotates the generated code. Otherwise the agent uses static templates.
model (str) – When an API key is provided the LLM refines and annotates the generated code. Otherwise the agent uses static templates.
llm_provider (str) – When an API key is provided the LLM refines and annotates the generated code. Otherwise the agent uses static templates.
script_title (str)
keys (Output data)
----------
workflow_config (dict) – The config dict produced by
ContextInputAgent.results (dict) – The agent results dict from
AgentCoordinator.output_dir (str, optional)
keys
----------------
code (code str — Python source)
file (script_path str or None — path to saved .py)
Examples
>>> agent = CodeGenerationAgent() >>> result = agent.execute( ... { ... "workflow_config": cfg, ... "results": coord_results, ... "output_dir": "/out", ... } ... ) >>> print(result["script_path"]) /out/workflow_script.py
- SYSTEM_PROMPT: str = 'You are an expert Python developer specialising in geophysics scripting.\nGiven a pycsamt workflow configuration and execution log, generate a clean,\nwell-commented Python script that reproduces the workflow step by step.\nUse pycsamt v2 public API only. Add a one-line comment above each major\nblock. Do not use the agents/ subpackage — call emtools, forward, and\nsite functions directly. Output only valid Python code.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.WorkflowOrchestratorAgent(*, api_key=None, model=None, llm_provider='claude', default_workflow='qc')#
Bases:
BaseAgentIntelligently route an NL request to the correct agent chain.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
default_workflow (str) – Fallback when NL classification is ambiguous (default
"qc").keys (Output data)
----------
request (str — natural-language processing request)
config (dict, optional — pre-built config (skips NL parsing))
dry_run (bool — preview without executing (default False))
output_dir (str)
data_path (str — EDI path (overrides extracted path))
keys
----------------
str (reasoning)
str
instance (coordinator AgentCoordinator)
coordinator (result AgentResult from the)
metadata (steps list of step)
Examples
Dry-run preview:
agent = WorkflowOrchestratorAgent() r = agent.execute( { "request": "Load L22PLT EDIs, run full phase tensor analysis", "dry_run": True, } ) print(r["workflow_type"]) # "phase_analysis"
Full run with LLM:
agent = WorkflowOrchestratorAgent(api_key="sk-ant-…") r = agent.execute( { "request": "Clean and denoise the WILLY data, then run AI inversion", "data_path": "/data/WILLY_DATA", } )
- SYSTEM_PROMPT: str = 'You are a pycsamt MT workflow routing expert.\nGiven a natural-language MT processing request,\nreturn a JSON object with:\n{\n "workflow_type": one of:\n "qc", "phase_analysis", "pre_inversion",\n "ai_inversion", "inv2d", "inv3d",\n "ensemble_inversion", "joint_inversion",\n "modem", "mare2dem", "full", "full_ai_workflow",\n "pinn_inversion", "hybrid_inversion",\n "tipper", "sensitivity", "rotation",\n "freq_decimation", "batch", "comparison",\n "denoise", "static_shift", "inversion_eval",\n "code_gen", "report", "forward",\n "interpretation",\n "reasoning": one sentence explaining the choice\n}\n\nRules:\n- "qc" if quality, cleaning, or flagging only.\n- "denoise" if denoising or noise removal only.\n- "static_shift" if static-shift or galvanic\n distortion only (no full inversion).\n- "phase_analysis" if phase tensor, strike,\n dimensionality, or Mohr circles.\n- "pre_inversion" if Occam2D, mesh preparation.\n- "inversion_eval" if evaluating an existing\n inversion result, RMS, misfit, or residuals.\n- "ai_inversion" if 1-D AI or neural inversion.\n- "inv3d" if GCN or 3-D AI inversion.\n- "inv2d" if U-Net or 2-D profile AI inversion.\n- "ensemble_inversion" if ensemble or uncertainty.\n- "joint_inversion" if multi-modal or TEM+MT.\n- "modem" if ModEM or 3-D conventional inversion.\n- "mare2dem" if MARE2DEM or 2.5-D FEM inversion.\n- "pinn_inversion" if PINN or physics-informed.\n- "hybrid_inversion" if two-stage or AI warm-start.\n- "tipper" if tipper or induction arrows.\n- "sensitivity" if sensitivity kernels or DOI.\n- "rotation" if tensor rotation or strike frame.\n- "freq_decimation" if period selection/decimation.\n- "batch" if batch processing multiple profiles.\n- "comparison" if comparing inversion results.\n- "code_gen" if generating a Python script.\n- "report" if generating a survey report only.\n- "forward" if forward modelling or synthetic data.\n- "interpretation" if geological interpretation.\n- "full" if complete pipeline or multiple methods.\nDefault to "qc" when uncertain.\nReturn ONLY the JSON.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.DenoisingAgent(*, api_key=None, model=None, llm_provider='claude', method='rpca', rank=2, half_window=3)#
Bases:
BaseAgentDenoise MT impedance data using classical or AI-based methods.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
method (str, optional — overrides constructor default) –
"rpca"(default),"hampel","emap","pipeline", or"ai"/"ai_cae"(requires PyTorch/TF).rank (int) – RPCA rank for off-diagonal denoising (default 2).
half_window (int) – Hampel filter half-window (default 3).
keys (Output data)
----------
path (sites /)
method
output_dir (str, optional)
period_range ([T_min, T_max], optional)
keys
----------------
impedance (denoised_sites Sites with denoised)
per-(station (snr_after ndarray —)
before (freq) SNR proxy)
per-(station
after (freq) SNR proxy)
improvement (snr_gain float — mean SNR)
threshold (n_recovered int — frequencies recovered above SNR)
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert MT noise analysis and denoising specialist.\nGiven a denoising result summary, write 3–4 sentences that:\n1. State which noise sources were addressed (powerline, cultural, source effects).\n2. Quantify the improvement (e.g. SNR gain, number of frequencies recovered).\n3. Identify any remaining problematic frequencies or stations.\n4. Recommend follow-up processing steps.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.AIInversionAgent(*, api_key=None, model=None, llm_provider='claude', arch='resnet', n_layers=5, n_train_samples=2000, epochs=30, freqs=None, pretrained=None)#
Bases:
BaseAgentTrain an AI inverter on synthetic data then predict on observed sites.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
arch (optional overrides) – Neural network architecture.
n_layers (int) – Number of model layers the inverter will predict (default 5).
n_train_samples (optional overrides) – Number of synthetic training samples (default 2 000).
epochs (optional overrides) – Training epochs (default 30). Increase for better models.
freqs (array-like or None) – Frequencies used for both training synthesis and observed data interpolation. Default: 40 log-spaced 10⁻⁴–10³ Hz.
pretrained (str or None) – Path to a pre-trained model checkpoint. When set, skips training.
keys (Output data)
----------
path (sites /)
output_dir (str, optional)
arch
epochs
n_train_samples
keys
----------------
:param
inverterEMInverter1D: :parampredictionsdict {station: :typepredictionsdict {station: ndarray of log₁₀ ρ values} :parambest_modeldict with “resistivity” and “thickness” for first station: :paramrms_per_stationdict {station: :typerms_per_stationdict {station: float} :paramrms_globalfloat: :paramtrain_historydict (loss curves): :paramfiguresdict: :paramfigure_pathsdict:Examples
>>> agent = AIInversionAgent(arch="resnet", n_layers=5, epochs=30) >>> result = agent.execute( ... { ... "path": "/data/L22PLT", ... "output_dir": "/out/ai_inv", ... } ... ) >>> result["rms_global"] 0.24
- SYSTEM_PROMPT: str = 'You are an expert in AI-based MT inversion and deep learning for geophysics.\nGiven an AI inversion result, write 4–5 sentences that:\n1. Describe the neural network architecture used and training convergence.\n2. State the prediction quality (RMS, layer count, depth range).\n3. Identify stations where the AI prediction is most / least reliable.\n4. Compare AI results with classical Bostick depth estimates if available.\n5. Recommend next steps (fine-tuning, ensemble, switch to 2-D inversion).\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- classmethod from_pretrained(model_name, *, api_key=None, model=None, llm_provider='claude', cache_dir=None, force_download=False)#
Return an
AIInversionAgentpre-loaded with a zoo checkpoint.- Parameters:
- Return type:
Examples
>>> agent = AIInversionAgent.from_pretrained("mt1d-resnet-5layer-v1") >>> result = agent.execute({"path": "/data/L22PLT"})
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.AnomalyDetectionAgent(*, api_key=None, model=None, llm_provider='claude', threshold_percentile=95.0, latent_dim=32, epochs=50)#
Bases:
BaseAgentDetect anomalous (station, frequency) observations in MT data.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
threshold_percentile (float) – Percentile of reconstruction errors used as the flagging threshold (default 95 — top 5 % are anomalies).
latent_dim (int) – CAE latent space dimension (default 32).
epochs (int) – Training epochs (default 50).
keys (Output data)
----------
path (sites /)
output_dir (str, optional)
keys
----------------
per-(station (anomaly_scores ndarray —)
error (freq) reconstruction)
anomalous (flags ndarray bool — True =)
{station (flag_table pandas DataFrame)
freq
score
flagged}
int (n_flagged)
list[str] (flagged_stations)
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert in unsupervised anomaly detection for MT/AMT data.\nGiven an anomaly detection result, write 3-4 sentences that:\n1. State how many observations were flagged as anomalous and their distribution.\n2. Identify which stations or frequency bands are most affected.\n3. Diagnose the likely source (powerline harmonics, near-field, 3-D, instrument).\n4. Recommend whether to mask flagged data or apply targeted filtering.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.Occam2DAgent(*, api_key=None, model=None, llm_provider='claude', modes=None, error_floor=0.05, target_rms=1.0)#
Bases:
BaseAgentGenerate a complete Occam2D inversion input file set.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
modes (list of str, optional — overrides constructor default) – Occam2D mode codes. Common choices:
["ZXXR","ZXXI","ZXYR","ZXYI","ZYXR","ZYXI","ZYYR","ZYYI"](all Z),["RhoZXY","PhsZXY","RhoZYX","PhsZYX"](ρa/φ off-diagonal). Default:None→ OccamData auto-selects from available data.error_floor (float) – Minimum relative error floor applied to all data (default 0.05 = 5 %).
target_rms (float) – Target RMS for the startup file (default 1.0).
keys (Output data)
----------
path (sites /)
output_dir (str)
period_range ([T_min, T_max], optional)
modes
title (str, optional)
keys
----------------
OccamDataFile.dat (data_path Path —)
Occam2DMesh (mesh_path Path —)
Occam2DModel (model_path Path —)
OccamStartup (startup_path Path —)
int (n_data)
int
int
str (output_dir)
Examples
>>> agent = Occam2DAgent() >>> result = agent.execute( ... { ... "path": "/data/L22PLT", ... "output_dir": "/out/occam2d", ... } ... ) >>> print(result["data_path"]) /out/occam2d/OccamDataFile.dat
- SYSTEM_PROMPT: str = 'You are an expert in 2-D MT inversion setup using Occam2D.\nGiven the data file statistics and mesh parameters, write 3–4 sentences that:\n1. Confirm the data file contains the expected stations and period bands.\n2. Comment on the mesh geometry (cell sizes, depth extent, padding).\n3. Recommend regularisation parameters (roughness penalty, target RMS).\n4. Note any data gaps or stations that should be excluded.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.ModEmAgent(*, api_key=None, model=None, llm_provider='claude', component_types=None, error_floor=0.05)#
Bases:
BaseAgentWrite a ModEM3D MT data file from EDI sources.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
component_types (list, optional) – Impedance components to include. E.g.
["Full_Impedance", "Full_Vertical_Components"]. Default:None→ ModEmData auto-selects.error_floor (float, optional) – Minimum relative error floor (default 0.05 = 5 %).
keys (Output data)
----------
path (sites /)
output_dir (str)
period_range ([T_min, T_max], optional)
component_types
error_floor
keys
----------------
file (ctrl_path Path or None — inversion-control)
m0.rho) (model_path Path or None — starting model (m0.ws /)
runs) (cov_path Path or None — covariance file (3-D)
file
int (n_periods)
int
str (output_dir)
Examples
>>> agent = ModEmAgent() >>> result = agent.execute( ... { ... "path": "/data/WILLY_DATA", ... "output_dir": "/out/modem", ... } ... ) >>> print(result["data_path"]) /out/modem/ModEM_Data.dat
- SYSTEM_PROMPT: str = 'You are an expert in 3-D MT inversion setup using ModEM3D.\nGiven a ModEM data file summary, write 3–4 sentences that:\n1. Confirm the data contains the expected stations, periods, and components.\n2. Recommend an initial model (background resistivity, layer structure).\n3. Suggest suitable covariance parameters (smoothing length, roughness).\n4. Flag any data issues that could cause convergence problems.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.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)#
Bases:
BaseAgent2-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.
Nonepreserves 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.
Noneuses one fifth ofepochswith a minimum of five. Set greater thanepochsonly 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"andphysics="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 toMaxwell2DDatasetConfig/MaxwellTri2DDatasetConfig.correlation_length_z_m ((float, float)) –
physics="mt2d"/"mt2d_tri"only: horizontal/vertical correlation length ranges forwarded toMaxwell2DDatasetConfig/MaxwellTri2DDatasetConfig.log_resistivity_mean (float) –
physics="mt2d"/"mt2d_tri"only: affine map from the standardized correlated field tolog10(resistivity_ohm_m).log_resistivity_std (float) –
physics="mt2d"/"mt2d_tri"only: affine map from the standardized correlated field tolog10(resistivity_ohm_m).mesh_safety_factor (float, int) –
physics="mt2d"only: forwarded toMaxwell2DDatasetConfig.max_mesh_cells (float, int) –
physics="mt2d"only: forwarded toMaxwell2DDatasetConfig.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 toMaxwellTri2DDatasetConfig.field_grid_cell_m (float) –
physics="mt2d_tri"only: forwarded toMaxwellTri2DDatasetConfig.topo_x_m (array-like, optional) –
physics="mt2d_tri"only: real topography (z positive down) forwarded toMaxwellTri2DDatasetConfig/build_graded_tri_mesh(). Both default toNone(flat surface atz=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 thetopographyexecute()-time input key above, which only re-renders an already-flatmt1d/mt2dprediction in an absolute-elevation display frame.topo_z_m (array-like, optional) –
physics="mt2d_tri"only: real topography (z positive down) forwarded toMaxwellTri2DDatasetConfig/build_graded_tri_mesh(). Both default toNone(flat surface atz=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 thetopographyexecute()-time input key above, which only re-renders an already-flatmt1d/mt2dprediction in an absolute-elevation display frame.gcn_hidden (tuple of int, default (64, 32, 16)) –
physics="mt2d_tri"only: hidden-layer widths forwarded toGCNInverter3D.gcn_adjacency_radius_m (float, default 300.0) –
physics="mt2d_tri"only: triangle-centroid adjacency radius forwarded tobuild_adjacency(). Must be set relative to the actual mesh scale, not left at the default: it is a hard cutoff in the same metres asmesh_target_cell_m, and if it is smaller than the typical distance between neighbouring triangle centroids,build_adjacencyreturns 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-2xmesh_target_cell_mis a reasonable starting point; verify withbuild_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-builtMare2DEMAdapter(e.g. pointed at a specific compiled binary). Defaults toMare2DEMAdapter(), resolved from the environment.keys (Output data)
----------
path (sites /)
output_dir (str, optional)
topography (bool or dict, optional) – Extract terrain from
sitesand render the predicted section in an absolute-elevation frame. A mapping can provideelevation_mandchainage_kmplusexaggerationandinterp_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 usedor –
"mt2d_tri", mode usedknown-truth (mt2d_tri_recovery dict or None — held-out) – recovery metrics (
physics="mt2d"only)known-truth – recovery metrics (
physics="mt2d_tri"only)
- 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)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.Inv3DAgent(*, api_key=None, model=None, llm_provider='claude', n_layers=5, n_freqs=32, freqs=None, depth_max=None, n_train_profiles=150, epochs=30, patience=None, radius=5000.0, hidden=(256, 128, 64), dropout=0.1, n_mc=20, physics='mt1d', correlation_length_x_m=(500.0, 2000.0), correlation_length_y_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=3.0, max_mesh_cells=6000, cells_per_skin_depth=None, geology_grid_nx_ny=6, geology_grid_nz=None, verbose=False)#
Bases:
BaseAgent3-D MT profile inversion using a graph-convolutional network (GCN).
- Parameters:
api_key (str)
model (str)
llm_provider (str)
n_layers (int) – Number of depth layers per station (default 5).
n_freqs (int) – Number of frequencies used for feature extraction (default 32).
freqs (array-like, optional — execution-time frequency-grid override.) – Explicit positive frequency grid in hertz. When supplied, it replaces the legacy
10^-4–10^3Hz grid and determinesn_freqs.depth_max (float, optional — cumulative finite-layer depth in metres.) – Maximum cumulative model depth in metres. When supplied, layer thicknesses are geometrically graded and normalized to this depth.
Nonepreserves the legacy Bostick-derived display parameterization.n_train_profiles (int) – Number of synthetic 3-D training profiles (default 150). With
physics="mt3d", each profile costs a real 3-D Maxwell solve, not a cheap independent 1-D solve — lower this substantially (e.g. 20-40) for that mode.epochs (int) – Training epochs (default 30).
patience (int or None) – Early-stopping patience measured in epochs.
Noneuses one fifth ofepochswith a minimum of five.radius (float) – Maximum inter-station edge distance in metres for the adjacency graph (default 5 000 m). Stations farther apart than radius are disconnected in the graph.
hidden (tuple of int) – GCN hidden-layer sizes (default (256, 128, 64)).
dropout (float) – Dropout probability (default 0.1); also used for MC uncertainty.
n_mc (int) – Number of Monte-Carlo dropout passes for uncertainty estimation. Set to 0 to skip uncertainty (faster, default 20).
physics ({"mt1d", "mt3d"}, default "mt1d") – Synthetic training-data physics; see the module docstring.
correlation_length_x_m (tuple[float, float]) – : (float, float)
physics="mt3d"only: horizontal/horizontal/vertical correlation length ranges forwarded toMaxwell3DDatasetConfig.correlation_length_y_m (tuple[float, float]) – : (float, float)
physics="mt3d"only: horizontal/horizontal/vertical correlation length ranges forwarded toMaxwell3DDatasetConfig.correlation_length_z_m (tuple[float, float]) – : (float, float)
physics="mt3d"only: horizontal/horizontal/vertical correlation length ranges forwarded toMaxwell3DDatasetConfig.log_resistivity_mean (float) –
physics="mt3d"only: affine map from the standardized correlated field tolog10(resistivity_ohm_m).log_resistivity_std (float) –
physics="mt3d"only: affine map from the standardized correlated field tolog10(resistivity_ohm_m).mesh_safety_factor (float, int) –
physics="mt3d"only: forwarded toMaxwell3DDatasetConfig.max_mesh_cellsalso builds the matchingMT3DAdapter(max_cells=...)used to solve.max_mesh_cells (float, int) –
physics="mt3d"only: forwarded toMaxwell3DDatasetConfig.max_mesh_cellsalso builds the matchingMT3DAdapter(max_cells=...)used to solve.cells_per_skin_depth (float or None, default None) –
physics="mt3d"only: opt-in frequency-aware solver core resolution, forwarded toMaxwell3DDatasetConfig— see its docstring for the accuracy-vs-cell-cost trade-off this exists to address.None(the default) preserves this agent’s original behavior (core resolution = the geological grid’s own spacing); this agent’s defaultfreqsgrid spans up to ~1000 Hz (_DEFAULT_FREQS), so enabling this without also raisingmax_mesh_cellsand/or narrowingfreqsrisks a “needs N solver cells” error at high frequencies.geology_grid_nx_ny (int, int or None) –
physics="mt3d"only: resolution of the geological training grid (how finely the true 3-D resistivity volumes are drawn), independent of the solver mesh’s own frequency-aware resolution above.geology_grid_nz=None(default) usesmin(max(n_layers, 4), 8). Raising these increases realism at the cost of more solver cells per realization; seemax_mesh_cellsfor the resulting budget.geology_grid_nz (int, int or None) –
physics="mt3d"only: resolution of the geological training grid (how finely the true 3-D resistivity volumes are drawn), independent of the solver mesh’s own frequency-aware resolution above.geology_grid_nz=None(default) usesmin(max(n_layers, 4), 8). Raising these increases realism at the cost of more solver cells per realization; seemax_mesh_cellsfor the resulting budget.keys (Output data)
----------
path (sites /)
coords (ndarray (n_stations, 2), optional — station (x, y) in metres.) – Auto-extracted from EDI lat/lon when absent.
adjacency (ndarray (n_stations, n_stations), optional — pre-computed) – normalised adjacency; overrides radius when supplied.
output_dir (str, optional)
freqs
depth_max
topography (bool or dict, optional) –
Trueextracts elevation and chainage fromsitesand adds a terrain-draped section. A mapping may instead provideelevation_mand optionallychainage_km, plusexaggerationandinterp_methodrendering options. This is a geometry/visualisation contract; it does not alter the MT forward solver or GCN prediction.period_range ([T_min, T_max], optional)
keys
----------------
(n_sta (adjacency ndarray)
log₁₀ρ (n_layers) —)
(n_sta
(metres) (n_layers-1) — log₁₀h)
(n_sta
std (n_layers) or None — MC-dropout)
(km) (depths_km ndarray — depth axis at station midpoints)
grid (frequency_grid_hz ndarray — effective feature/forward frequency)
contract (configured_depth_max_m float or None — explicit depth)
metadata (topography dict or None — resolved terrain)
list[str] (station_names)
(n_sta
metres (2) —)
(n_sta
n_sta)
float (rms_global)
GCNInverter3D (inverter)
dict (figure_paths)
dict
"mt3d" (physics str — "mt1d" or)
used (mode)
known-truth (mt3d_recovery dict or None — held-out) – recovery metrics (
physics="mt3d"only)losses (training_history dict -- per-epoch training and validation)
stopping (epochs_completed int -- epochs actually completed before)
loss (best_validation_loss float -- minimum finite validation)
Examples
>>> agent = Inv3DAgent(n_layers=5, epochs=20, n_mc=10) >>> result = agent.execute( ... { ... "path": "/data/WILLY_EDIs", ... "output_dir": "/out/inv3d", ... } ... ) >>> result["rms_global"] 0.28
- SYSTEM_PROMPT: str = 'You are an expert in 3-D MT inversion using graph-convolutional deep learning.\nGiven a GCN-based 3-D inversion result, write 4-5 sentences that:\n1. Describe the survey geometry (station count, spatial extent, adjacency radius).\n2. Interpret the dominant 3-D resistivity structures and their spatial continuity.\n3. Assess prediction quality (RMS, depth range) relative to station spacing.\n4. Compare the GCN spatial result to independent 1-D predictions where possible.\n5. Recommend geological follow-up and areas with highest uncertainty.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.EnsembleAgent(*, api_key=None, model=None, llm_provider='claude', n_estimators=5, arch='resnet', n_layers=5, n_train_samples=2000, epochs=30, calibrate=True)#
Bases:
BaseAgentEnsemble 1-D MT inversion with uncertainty bands.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
n_estimators (int) – Number of independent models in the ensemble (default 5).
arch (str) – Network architecture for each estimator (default
"resnet").n_layers (int) – Number of model layers (default 5).
n_train_samples (int) – Synthetic training samples per estimator (default 2 000).
epochs (int) – Training epochs per estimator (default 30).
calibrate (bool) – Apply conformal calibration using 20 % of training data (default True).
keys (Output data)
----------
path (sites /)
output_dir (str, optional)
keys
----------------
EnsembleInverter (ensemble)
{station (pred_hi dict)
{station
{station
{station
coverage (coverage float — empirical 90 % interval)
float (rms_global)
dict (figure_paths)
dict
Examples
>>> agent = EnsembleAgent(n_estimators=3, epochs=20) >>> result = agent.execute( ... {"path": "/data/L22PLT", "output_dir": "/out/ens"} ... ) >>> result["coverage"] # should be ≈ 0.90 after calibration 0.88
- SYSTEM_PROMPT: str = 'You are an expert in Bayesian and ensemble methods for geophysical inversion.\nGiven an ensemble inversion result with uncertainty quantification, write 4-5\nsentences that:\n1. Describe the ensemble configuration (N models, architecture, training data).\n2. State the prediction quality (mean RMS, uncertainty magnitude).\n3. Assess the calibration: are the confidence intervals reliable?\n4. Identify depth ranges or stations where uncertainty is largest.\n5. Recommend whether the uncertainty is small enough for geological interpretation.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.JointInversionAgent(*, api_key=None, model=None, llm_provider='claude', modalities=None, n_layers=5, n_freqs_primary=40, n_freqs_secondary=20, n_train_samples=2000, epochs=30, growth_rate=32)#
Bases:
BaseAgentMulti-modal MT joint inversion using DRCNN.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
modalities (list[str]) – Names of the two modalities, e.g.
["mt", "tem"]. The first entry is the primary modality (loaded fromsites/path); the second is loaded fromsecondary_pathor synthesised when absent.n_layers (int) – Number of depth layers in the output model (default 5).
n_freqs_primary (int) – Frequencies for the primary MT response features (default 40).
n_freqs_secondary (int) – Frequencies for the secondary modality features (default 20).
n_train_samples (int) – Synthetic training samples shared across both modalities (default 2000).
epochs (int) – Training epochs (default 30).
growth_rate (int) – DRCNN dense-block growth rate (default 32).
keys (Output data)
----------
path (sites /)
secondary_path (str, optional — secondary modality EDI/TEM path)
output_dir (str, optional)
period_range ([T_min, T_max], optional)
keys
----------------
JointInverter (inverter)
{station (rms_per_station dict)
{station
float (rms_global)
list[str] (modalities)
dict (figure_paths)
dict
Examples
>>> agent = JointInversionAgent( ... modalities=["mt", "tem"], n_layers=5, epochs=20 ... ) >>> result = agent.execute({"path": "/data/L22PLT"}) >>> result["rms_global"] 0.31
- SYSTEM_PROMPT: str = 'You are an expert in multi-modal geophysical joint inversion using deep learning.\nGiven a joint MT inversion result, write 4-5 sentences that:\n1. Describe the two modalities fused and their complementary depth sensitivities.\n2. Assess the joint prediction quality (RMS, depth range, station count).\n3. Compare the joint result to a single-modality approach where possible.\n4. Identify where the secondary modality most improved the primary inversion.\n5. Recommend validation (borehole, gravity, seismic) and next modelling steps.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.ModelZooAgent(*, api_key=None, model=None, llm_provider='claude', cache_dir=None, force_download=False)#
Bases:
BaseAgentBrowse, download, and run pre-trained EM inverters from the model zoo.
- Parameters:
api_key (str)
model (model_info dict — zoo metadata for the requested)
llm_provider (str)
cache_dir (str or None) – Override default cache
~/.pycsamt/model_zoo/.force_download (bool) – Re-download even if cached (default False).
keys (Output data)
----------
action (str) –
"list"(default),"download", or"predict".model_name (str) – Required for
"download"and"predict"actions. E.g."mt1d-resnet-5layer-v1".path (sites /) – Required for
"predict"action.output_dir (str, optional)
keys
----------------
performed (action str — which action was)
(action="list") (models dict — full registry)
(action="download"/"predict") (``checkpoint_path``str — local path) –
model
{station (predictions dict —)
(action="predict") (rms_global float)
dict (figure_paths)
dict
Examples
List available models:
agent = ModelZooAgent() r = agent.execute({"action": "list"}) for name, desc in r["models"].items(): print(name, "—", desc[:60])
Download a checkpoint:
r = agent.execute( {"action": "download", "model_name": "mt1d-resnet-5layer-v1"} ) print(r["checkpoint_path"])
Predict on observed sites (fine-tune skipped if weights unavailable):
r = agent.execute( { "action": "predict", "model_name": "mt1d-resnet-5layer-v1", "path": "/data/WILLY_EDIs", "output_dir": "/out/zoo_predict", } ) print(r["rms_global"])
- SYSTEM_PROMPT: str = 'You are an expert in pre-trained geophysical AI models.\nGiven a model zoo query result, write 2-3 sentences that:\n1. Describe which pre-trained model was used and its provenance (architecture, training data).\n2. Comment on the prediction quality (RMS, reliability) relative to the expected use case.\n3. Recommend whether the user should fine-tune on their own data or use the pre-trained weights directly.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.TensorRotationAgent(*, api_key=None, model=None, llm_provider='claude', strike_deg=0.0)#
Bases:
BaseAgentRotate impedance tensors and tipper vectors by a fixed strike angle.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
strike_deg (float — counter-clockwise rotation angle (degrees)) – Default rotation angle (degrees). Overridden by
input_data["strike_deg"].keys (Output data)
----------
path (sites /)
strike_deg
output_dir (str — directory for rotated EDI files)
overwrite (bool — allow overwriting existing files (default False))
file_suffix (str — appended to station name, e.g.
"_rot")keys
----------------
applied (strike_deg float — angle)
paths (written_paths list[str] — successfully written EDI)
list[str] (failed_stations)
int (n_written)
quality) (z_diag_reduction float — mean Zxx/Zxy before − after (proxy for rotation)
dict (figure_paths)
dict
Examples
>>> agent = TensorRotationAgent(strike_deg=42.0) >>> r = agent.execute( ... { ... "path": "/data/WILLY_EDIs", ... "output_dir": "/data/WILLY_rotated", ... } ... ) >>> print(r["n_written"], "EDIs written")
- SYSTEM_PROMPT: str = 'You are an expert in MT tensor rotation and coordinate-frame correction.\nGiven a rotation result, write 3-4 sentences that:\n1. State the rotation angle applied and the original coordinate frame.\n2. Describe how the off-diagonal impedances (Zxy, Zyx) changed after rotation.\n3. Assess whether the rotation removed apparent 2-D coupling from the diagonal terms.\n4. Recommend whether further refinement (per-frequency rotation, decomposition) is needed.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.EDIExportAgent(*, api_key=None, model=None, llm_provider='claude', file_pattern='{station}.edi', overwrite=False)#
Bases:
BaseAgentWrite processed Sites to EDI files on disk.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
file_pattern (str, optional — override default) – Filename format using
{station}placeholder. Default"{station}.edi".overwrite (bool, optional — override default) – Overwrite existing files (default False).
keys (Output data)
----------
path (sites /)
output_dir (str — target directory (created if absent))
file_pattern
overwrite
keys
----------------
list[str] (written_paths)
list[tuple[str (failed)
(station (str]] —)
message) (error)
int (n_failed)
int
str (output_dir)
Examples
>>> agent = EDIExportAgent() >>> r = agent.execute( ... { ... "path": "/data/WILLY_EDIs", ... "output_dir": "/out/willy_corrected", ... } ... ) >>> print(r["n_written"], "EDIs exported")
- SYSTEM_PROMPT: str = 'You are an expert in MT data management and EDI file format conventions.\nGiven an EDI export result, write 2-3 sentences that:\n1. Confirm how many files were written and their location.\n2. Note any stations that failed and the likely cause.\n3. Recommend next steps for the exported data (e.g. inversion, external QC).\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.TipperAnalysisAgent(*, api_key=None, model=None, llm_provider='claude', convention='wiese', use_imag=False, period_ref=None)#
Bases:
BaseAgentAnalyse tipper vectors and plot induction arrows.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
convention (str, optional) – Arrow convention. Wiese (default) points toward conductors in the real-part convention; Parkinson points toward them.
use_imag (bool, optional) – Use imaginary tipper parts for deeper structure (default False).
period_ref (arrow_table pandas.DataFrame — induction arrows at) – Reference period (s) for the induction arrow map.
Noneuses the geometric mean of available periods.keys (Output data)
----------
path (sites /)
convention
use_imag
period_ref
output_dir (str, optional)
keys
----------------
per-(station (tipper_table pandas.DataFrame —)
period)
period_ref
map (period_ref float — period used for arrow)
int (n_stations_with_tipper)
dict (figure_paths)
dict
Examples
>>> agent = TipperAnalysisAgent(convention="wiese") >>> r = agent.execute({"path": "/data/WILLY_EDIs"}) >>> r["n_stations_with_tipper"] 12
- SYSTEM_PROMPT: str = 'You are an expert in magnetotelluric tipper analysis and 3-D structure interpretation.\nGiven a tipper analysis result, write 4-5 sentences that:\n1. Describe the general tipper magnitude pattern across the survey (strong/weak, frequency dependence).\n2. Identify stations with anomalously large tipper amplitudes and their likely cause.\n3. Interpret the induction arrow direction(s) — do they point toward or away from a conductor?\n4. Assess the 3-D character of the survey based on tipper consistency along the profile.\n5. Recommend whether a 3-D inversion is warranted or whether 2-D is sufficient.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.SensitivityAgent(*, api_key=None, model=None, llm_provider='claude', component='xy', rho_override=None, depth_max=None)#
Bases:
BaseAgentBostick sensitivity kernels and vertical resolution analysis.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
component (str, optional) – Impedance component for ρa and Bostick depth (default
'xy').rho_override (float, optional) – Fixed background resistivity (Ω·m) for the analytical ΔD formula.
Noneuses the measured ρa per frequency pair.depth_max (float, optional — km) – Clip depth axis at this value in km (default: auto from data).
keys (Output data)
----------
path (sites /)
component
rho_override
period_range ([T_min, T_max], optional)
depth_max
output_dir (str, optional)
keys
----------------
per-(station (resolution_table pandas.DataFrame —)
freq-pair)
{station (doi_per_station dict)
float (mean_doi_km)
dict (figure_paths)
dict
Examples
>>> agent = SensitivityAgent(component="xy") >>> r = agent.execute({"path": "/data/WILLY_EDIs"}) >>> print(r["mean_doi_km"], "km mean DOI")
- SYSTEM_PROMPT: str = "You are an expert in MT data resolution and depth-of-investigation analysis.\nGiven a sensitivity analysis result, write 4-5 sentences that:\n1. Describe the maximum Bostick depth reached by the data at the lowest frequency.\n2. Identify depth ranges with poor resolution (large ΔD) and their cause.\n3. State which stations have the best and worst overall depth coverage.\n4. Advise on whether the target depth is within the data's sensitivity window.\n5. Recommend frequency additions or deletions to improve depth resolution.\nReply in plain scientific English.\n"#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.FrequencyDecimationAgent(*, api_key=None, model=None, llm_provider='claude', n_per_decade=6, snr_threshold=3.0, period_range=None, component='xy')#
Bases:
BaseAgentSelect optimal periods from MT data for inversion.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
n_per_decade (int, optional) – Number of periods to keep per decade of period range (default 6).
snr_threshold (float, optional) – Minimum SNR value to retain a frequency (default 3.0). Frequencies below this are excluded as dead-band.
period_range ([T_min, T_max], optional) – Hard period bounds in seconds.
Noneuses the full data range.component ({'xy', 'yx'}) – Component used for SNR proxy (default
'xy').keys (Output data)
----------
path (sites /)
qc_result (AgentResult or dict, optional — output from DataQCAgent) – (provides per-frequency SNR scores; if absent a proxy is computed)
n_per_decade
snr_threshold
period_range
output_dir (str, optional)
keys
----------------
{station (dead_band_mask dict)
(station (n_original int — total available)
cells (n_selected int — retained)
cells
float (selection_ratio)
{station
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert in MT data selection and frequency decimation for inversion.\nGiven a period decimation result, write 3-4 sentences that:\n1. State how many periods were selected versus available, and the selection ratio.\n2. Identify which frequency bands were excluded and the likely reason (dead band, low SNR).\n3. Confirm whether the selected periods cover the target depth range adequately.\n4. Recommend any additional frequencies that should be included or excluded.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.InversionComparisonAgent(*, api_key=None, model=None, llm_provider='claude')#
Bases:
BaseAgentCompare two resistivity inversion results.
Each result is either an
AgentResultfrom an inversion agent, or a plain dict with keyspred_rhoanddepths_km.- Parameters:
api_key (str)
model (str)
llm_provider (str)
keys (Output data)
----------
result_a (AgentResult or dict) – First model. Must contain
"pred_rho"(n_stations × n_layers) or"predictions"dict {station: log₁₀ρ array}.result_b (AgentResult or dict) – Second model. Same structure as result_a.
label_a (str — name for result_a (default
"Model A"))label_b (str — name for result_b (default
"Model B"))depths_km (ndarray, optional — shared depth axis (km))
output_dir (str, optional)
keys
----------------
sections (correlation float — Pearson ρ between log₁₀ρ)
log₁₀(Ω·m) (rmse float — RMSE in)
(n_layers (difference ndarray)
B (n_stations) — A −)
str (label_b)
str
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert in MT inversion model evaluation and comparison.\nGiven two inversion results, write 4-5 sentences that:\n1. State the overall similarity (correlation, RMSE) between the two models.\n2. Identify depth ranges or stations where the two models disagree most.\n3. Discuss which model is more physically plausible and why.\n4. Recommend which result to use for geological interpretation.\n5. Suggest additional constraints (e.g. borehole, gravity) to discriminate them.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.ResistivityMapAgent(*, api_key=None, model=None, llm_provider='claude', depth_indices=None, interp_method='linear', grid_n=50)#
Bases:
BaseAgentBuild horizontal resistivity depth-slice maps from 1-D inversion results.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
depth_indices (list[int], optional) – Layer indices to map (0-based).
Nonemaps 3 evenly spaced layers.interp_method ({'linear', 'nearest', 'idw'}) – Interpolation method for gridding station values (default
'linear').grid_n (int) – Number of grid cells per axis (default 50).
keys (Output data)
----------
predictions (dict {station: ndarray} — log₁₀ρ per layer) – (from AIInversionAgent, Inv3DAgent, etc.)
station_coords (dict {station: (x, y)} or ndarray (n_sta, 2) — metres)
depths_km (ndarray — depth axis (km))
depth_indices
output_dir (str, optional)
keys
----------------
per-depth (depth_maps list[dict] —)
list[float] (depth_levels_km)
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert in pseudo-3D resistivity interpretation from MT surveys.\nGiven a set of horizontal resistivity maps, write 4-5 sentences that:\n1. Describe the dominant resistivity pattern at each depth level.\n2. Identify lateral contrasts that suggest geological boundaries or structures.\n3. Note any stations that appear anomalous at specific depths.\n4. Discuss the reliability of the interpolation given station spacing.\n5. Recommend drilling targets or geological follow-up based on the maps.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.BatchSurveyAgent(*, api_key=None, model=None, llm_provider='claude', workflow='qc', n_jobs=1)#
Bases:
BaseAgentProcess multiple MT profiles through a shared agent chain.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
workflow (str, optional — override constructor default) – Pre-defined workflow key:
'qc','ai_inversion','phase_analysis','sensitivity','tipper'. Determines which agents are chained for each profile.n_jobs (int, optional) – Parallel workers. -1 = all CPU cores. 1 = sequential (default).
keys (Output data)
----------
profiles (dict {name: path} or list[str]) – Profile names to paths, or a list of paths (names auto-assigned).
workflow
n_jobs
output_dir (str, optional)
call. (Any additional keys are forwarded to each agent's execute())
keys
----------------
{name (profile_results dict)
metrics (summary_table pandas.DataFrame — per-profile)
int (n_failed)
int
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert in large-scale MT survey processing and quality control.\nGiven a batch processing result, write 3-4 sentences that:\n1. Report how many profiles succeeded and how many failed.\n2. Identify which profiles have the worst QC scores or highest RMS.\n3. Highlight any systematic issues across profiles (e.g. dead band at same frequency).\n4. Recommend which profiles need re-processing or manual inspection.\nReply in plain English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.InversionBackendAgent(*, api_key=None, model=None, llm_provider='claude', backend='builtin', dimension='1d', method='mt', n_layers=5, max_iter=80, regularization='smooth', error_floor=0.05)#
Bases:
BaseAgentDrive pycsamt.inversion physics-based backends.
- Parameters:
api_key (str)
model (str)
llm_provider (str)
backend (str, optional overrides) – Inversion backend:
'builtin'(default),'simpeg','pygimli','occam2d','modem'.dimension (str, optional overrides) –
'1d'(default),'2d', or'3d'.method (str, optional overrides) –
'mt'(default),'amt','csamt', or'tdem'.n_layers (int, optional overrides) – Number of depth layers for 1-D inversion (default 5).
max_iter (int, optional overrides) – Maximum inversion iterations (default 80).
regularization (optional overrides) –
'smooth'(default),'damped', or'blocky'.error_floor (optional overrides) – Relative data error floor (default 0.05 = 5 %).
keys (Output data)
----------
path (sites /)
backend
dimension
method
n_layers
max_iter
regularization
error_floor
backend_options (dict, optional — forwarded to InversionConfig)
output_dir (str, optional)
keys
----------------
InversionResult (inversion_result)
float (rms)
int (n_iter)
(n_layers (log_rho_section ndarray)
n_stations)
None (station_names list[str] or)
str (dimension)
str
dict (figure_paths)
dict
- SYSTEM_PROMPT: str = 'You are an expert in MT inversion and subsurface resistivity modelling.\nGiven an inversion result, write 4-5 sentences that:\n1. State the backend used, dimensionality, and convergence (RMS, n_iter).\n2. Describe the recovered resistivity model (range, dominant structures).\n3. Assess the fit quality and whether the RMS target was reached.\n4. Identify stations or depth ranges with elevated misfit.\n5. Recommend regularisation adjustments or mesh refinements for the next run.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.PipelineAgent(*, api_key=None, model=None, llm_provider='claude', preset='basic_qc', param_overrides=None)#
Bases:
BaseAgentLLM-assisted MT processing pipeline selection and interpretation.
Two modes of operation:
Guided mode — pass
"request"in input_data. The LLM recommends a preset and any parameter overrides, then the pipeline is built and run automatically.Direct mode — pass
"preset"(name string) or"steps"(list of step codes) in input_data. No pre-run LLM call is made; the pipeline is built directly from those instructions.In both modes the post-run LLM call interprets the
PipelineResultas a narrative.- Parameters:
api_key (str) – Standard LLM configuration inherited from
BaseAgent.model (str) – Standard LLM configuration inherited from
BaseAgent.llm_provider (str) – Standard LLM configuration inherited from
BaseAgent.preset (str, optional) – Default preset name used when input_data contains neither
"preset"nor"steps"nor"request". Defaults to"basic_qc"(safe first-pass).param_overrides (dict, optional) – Default parameter overrides applied on top of any preset or step list. Format:
{step_code: {param: value}}.keys (Output data)
----------
path (sites /) – Raw MT/AMT sites to process.
request (str, optional) – Natural-language description of dataset and goals. Triggers a pre-run LLM call that recommends preset + parameter overrides.
preset – Named preset — overrides constructor default.
steps (list of str, optional) – Explicit ordered list of step codes. Ignored when
"preset"is set.param_overrides – Per-step parameter overrides — merged on top of constructor defaults.
output_dir (str or None, optional) – Root directory for pipeline output files (EDI, plots, YAML config).
keys
----------------
agents (sites_out Processed Sites — ready for downstream)
:param
pipeline_resultPipelineResultobject: :parampreset_usedName of the preset that was run (or"custom"): :paramsteps_runList of step code strings that were executed: :paramn_sites_inNumber of input stations: :paramn_sites_outNumber of stations after processing: :paramn_errorsNumber of steps that raised an error: :paramrecommendationDict returned by LLM pre-run call (orNone):Examples
Guided mode:
agent = PipelineAgent() result = agent.execute( { "sites": sites, "request": "50 Hz grid noise, possible static shift, Occam2D target", "output_dir": "willy_pipeline/", } ) processed = result["sites_out"] print(result.llm_interpretation)
Direct mode:
agent = PipelineAgent(preset="full_processing") result = agent.execute( { "sites": sites, "param_overrides": {"NR001": {"mains_hz": 60}}, } )
Chain with
Occam2DAgentviaAgentCoordinator:from pycsamt.agents import ( AgentCoordinator, PipelineAgent, Occam2DAgent, ) coord = AgentCoordinator("willy_full") coord.add_step( "pipeline", PipelineAgent(preset="full_processing"), input_fn=lambda r: {"sites": r["load"].data["sites"]}, ) coord.add_step( "invert", Occam2DAgent(), input_fn=lambda r: {"sites": r["pipeline"].data["sites_out"]}, )
- SYSTEM_PROMPT: str = 'You are an expert MT data processing specialist reviewing pipeline execution results.\nGiven a processing summary, write 3–4 sentences that:\n1. Describe the overall data quality change (stations retained, step errors if any).\n2. Highlight which steps were most impactful or took the most time.\n3. Comment on whether the stated processing objectives appear to have been met.\n4. Recommend a concrete follow-up action (further correction, QC plot review,\n or readiness for inversion).\nReply in plain English. No bullet points or markdown.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.Mare2DEMAgent(*, api_key=None, model=None, llm_provider='claude', n_procs=4, use_mpi=True, initial_rho=1.0, target_rms=1.0, max_iterations=150)#
Bases:
BaseAgentOrchestrate a complete MARE2DEM 2.5-D EM inversion workflow.
- Parameters:
api_key (str or None) – LLM API key (Anthropic / OpenAI / Gemini).
model (str or None) – LLM model identifier; defaults to the provider’s recommended model.
llm_provider (str) –
"claude"(default),"openai","gemini","deepseek", or"minimax".n_procs (int, optional) – Number of MPI processes for MARE2DEM (default 4).
use_mpi (bool) – Whether to prefix the binary with
mpirun -np n_procs(defaultTrue). Set toFalseonly for single-process debug builds.initial_rho (float, optional) – Starting half-space resistivity in Ω·m (default 1.0).
target_rms (float, optional) – Target normalised RMS misfit (default 1.0).
max_iterations (int, optional) – Maximum Occam inversion iterations (default 150).
keys (Output data)
----------
path (sites /) – EDI source (directory, files, or a loaded
Sites). The agent converts the impedances to a MARE2DEM.emdatafile viamake_mt_data_from_edi()(TE = Zxy, TM = Zyx). Used whenemdata/mt/csemare not supplied — this is the pathway the workflow orchestrator uses.error_floor (float, optional) – Relative error floor applied to the TE/TM apparent resistivities built from EDI data (default 0.05 = 5 %).
output_modes (str, optional) – Data types written from EDI data:
"all"(default),"TE","TM","TE+tipper", or"all impedance".emdata (str or path-like, optional) – Path to an existing
.emdatadata file. When supplied the agent copies it to the output directory and uses it directly.resistivity (str or path-like, optional) – Path to an existing starting
.resistivityfile. When omitted a homogeneous half-space model is written.mt (dict, optional) – Keyword arguments for
MTSurveyConfigwhen building the data file from survey parameters rather than a pre-existing file.csem (dict, optional) – Keyword arguments for
CSEMSurveyConfig.topo (float or array-like, optional) – Topography for receiver/transmitter placement. Flat scalar (e.g.
-1000.0m for a flat seafloor) or an(n, 2)array of[y, z]pairs. Default0.0(flat surface).output_dir (str) – Directory that will receive all MARE2DEM input files and run output. Created if absent (default
"pycsamt_mare2dem").mode ({"prepare", "run", "report"}) –
Workflow mode:
"prepare"— write input files only (default)."run"— write files then launch MARE2DEM."report"— scan an existing run directory and return results without (re-)running.
source_dir (str or path-like, optional) – Explicit path to the MARE2DEM source tree. Passed to
SourceManager; auto-resolved when omitted.download_source (bool) – When
Trueand the binary is missing,execute()automatically runsSourceManager.download()andSourceManager.build(). Defaults toFalse(raises a warning instead).n_procs – Per-call override for
n_procs.max_iterations – Per-call override.
target_rms – Per-call override.
initial_rho – Per-call override.
keys
----------------
data_path (Path or None)
resistivity_path (Path or None)
settings_path (Path or None)
binary_found (bool)
source_downloaded (bool)
n_mt_receivers (int)
n_csem_transmitters (int)
n_data (int)
final_rms (float or None)
n_iterations (int)
converged (bool)
result (InversionResult or None)
output_dir
Examples
Prepare input files from an existing data file:
>>> agent = Mare2DEMAgent(n_procs=8) >>> result = agent.execute( ... { ... "emdata": "/data/survey.emdata", ... "output_dir": "/run/mare2dem", ... } ... ) >>> print(result["data_path"]) /run/mare2dem/mare2dem.emdata
Build from MT survey parameters (flat seafloor at −1000 m):
>>> import numpy as np >>> result = agent.execute( ... { ... "mt": { ... "frequencies": list(np.logspace(-3, 3, 20)), ... "rx_y": list(np.linspace(-5000, 5000, 20)), ... "rx_type": "marine", ... "lTE": True, ... "lTM": True, ... }, ... "topo": -1000.0, ... "output_dir": "/run/mare2dem_mt", ... } ... )
Run a full inversion (requires compiled MARE2DEM binary):
>>> result = agent.execute( ... { ... "emdata": "survey.emdata", ... "output_dir": "./run", ... "mode": "run", ... "n_procs": 16, ... } ... ) >>> print(result["final_rms"]) 0.98
Report results from a completed run directory:
>>> result = agent.execute( ... { ... "output_dir": "./run", ... "mode": "report", ... } ... ) >>> result["converged"] True
- SYSTEM_PROMPT: str = 'You are an expert in 2.5-D EM inversion using MARE2DEM.\nGiven a summary of the survey geometry, input files, and inversion\noutcome, write 4–5 sentences that:\n1. Assess the receiver and transmitter coverage relative to the target depth.\n2. Comment on the starting resistivity model and Occam regularisation settings.\n3. Evaluate the convergence: is the final RMS close to target? Any stagnation?\n4. Recommend parallel decomposition settings (tx/rx per group) for the cluster size.\n5. Flag any data issues (low-amplitude offsets, asymmetric MT responses, topo artefacts).\nReply in plain English. Be specific about MARE2DEM parameters.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.PINNInversionAgent(*, dim=1, n_layers=10, depth_max=2000.0, smoothness_weight=0.01, lateral_weight=0.005, graph_weight=0.005, radius=5000.0, epochs=None, lr=0.01, solver='mt1d', comp='xy', api_key=None, model=None, llm_provider='claude')#
Bases:
BaseAgentPINN-based MT inversion without labelled data.
Optimises a layered Earth by minimising a physics-informed loss via Adam gradient descent. Supports 1-D per-station, joint 2-D profile, and quasi-3-D graph-coupled inversion.
- Parameters:
dim (overrides) – Dimensionality. Default
1.n_layers (overrides) – Number of layers including the halfspace. Default
10.depth_max (float) – Maximum investigation depth in metres. Default
2000.0.smoothness_weight (float) – Vertical regularisation weight. Default
0.01.lateral_weight (float) – Lateral smoothness weight (2-D only). Default
0.005.graph_weight (float) – Graph-Laplacian spatial weight (3-D only). Default
0.005.radius (float) – Edge radius in metres for the 3-D graph. Default
5000.0.epochs (overrides) – Adam iterations.
Noneuses 500 for 1-D and 300 for 2-D / 3-D.lr (float) – Adam learning rate. Default
1e-2.solver ({"mt1d", "csamt1d"}) – Physics solver. Default
"mt1d".comp (str) – Impedance component (1-D only). Default
"xy".api_key (str | None) – LLM configuration (optional).
model (str | None) – LLM configuration (optional).
llm_provider (str) – LLM configuration (optional).
keys (Output data)
----------
data (sites / path observed)
dir (output_dir optional figure/save)
dim
epochs
n_layers
keys
----------------
object (inverter fitted inverter)
(n_layers (section ndarray) – log10-rho section matrix
n_stations) – log10-rho section matrix
(1-D) (models list of LayeredModel)
int (n_stations)
{station (rms_per_station dict)
float (rms_global)
None (residuals_df pandas.DataFrame or)
None
dict (figure_paths)
dict
Examples
>>> agent = PINNInversionAgent(dim=1, n_layers=10, epochs=200) >>> res = agent.execute({"path": "/data/L22PLT"}) >>> res["rms_global"] 0.18
- SYSTEM_PROMPT: str = 'You are an expert in physics-informed neural-network\ninversion for MT/CSAMT geophysics.\nGiven a PINN inversion result write 4-5 sentences:\n1. State dimensionality (1-D/2-D/3-D) and convergence.\n2. Report final RMS (log10 rho-ohm-m) and data fit.\n3. Describe the resistivity structure recovered.\n4. Flag stations or regions with high residuals.\n5. Recommend adjustments to epochs, regularisation,\n or whether to switch to a hybrid approach.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.HybridInversionAgent(*, dim=1, max_iter=200, smoothness_weight=0.005, lateral_weight=0.005, graph_weight=0.005, radius=5000.0, lr=0.005, solver='mt1d', comp='xy', n_freqs=32, api_key=None, model=None, llm_provider='claude')#
Bases:
BaseAgentTwo-stage AI + physics MT inversion.
Stage 1 applies a pre-trained supervised AI inverter to obtain a starting model. Stage 2 refines it with physics-informed Adam gradient descent.
- Parameters:
dim ({1, 2, 3}) – Dimensionality. Default
1.max_iter (int) – Physics refinement iterations (Stage 2). Default
200.smoothness_weight (float) – Vertical regularisation weight. Default
0.005.lateral_weight (float) – Lateral smoothness weight (2-D only). Default
0.005.graph_weight (float) – Graph-Laplacian weight (3-D only). Default
0.005.radius (float) – Edge radius in metres for 3-D graph. Default
5000.0.lr (float) – Adam learning rate for Stage 2. Default
5e-3.solver ({"mt1d", "csamt1d"}) – Physics solver. Default
"mt1d".comp (str) – Impedance component (1-D only). Default
"xy".n_freqs (int) – Frequency-grid size fed to the 1-D AI inverter. Default
32.api_key (str | None) – LLM configuration (optional).
model (str | None) – LLM configuration (optional).
llm_provider (str) – LLM configuration (optional).
keys (Input)
----------
data (sites / path observed)
object (ai_inverter fitted AI inverter) – or path to checkpoint
ai_inverter (checkpoint alias for)
directory (output_dir optional save)
dim
max_iter
:param : :param
smoothness_weight: :param : :paramlateral_weight: :param : :paramgraph_weight: :typegraph_weight: optional overrides :param Output data keys: :param —————-: :paraminverterfitted HybridInverterXD: :paramsectionndarray (n_layers: Stage-2 log10-rho section :param n_stations): Stage-2 log10-rho section :paramstage1_sectionndarray — Stage-1 section: :parammodelslist of LayeredModel (1-D): :paramstage1_modelslist of LayeredModel (1-D): :paramn_stationsint: :paramrms_per_stationdict {station: :typerms_per_stationdict {station: float} :paramrms_globalfloat (Stage-2): :paramrms_stage1float (Stage-1 for comparison): :paramconvergence_dfpandas.DataFrame or None: :paramresiduals_dfpandas.DataFrame or None: :paramfiguresdict: :paramfigure_pathsdict:Examples
>>> from pycsamt.ai.inversion import EMInverter1D >>> ai = EMInverter1D.load("checkpoint.npz") >>> agent = HybridInversionAgent(dim=1, max_iter=100) >>> res = agent.execute( ... { ... "path": "/data/L22PLT", ... "ai_inverter": ai, ... } ... ) >>> res["rms_global"] 0.14
- SYSTEM_PROMPT: str = 'You are an expert in hybrid AI + physics-informed\ninversion for MT/CSAMT geophysics.\nGiven a hybrid inversion result write 4-5 sentences:\n1. Compare Stage-1 (AI) and Stage-2 (physics) RMS.\n2. Describe how much the physics step improved the fit.\n3. State the recovered resistivity structure.\n4. Flag stations where Stage-2 failed to improve on\n Stage-1 or where residuals remain high.\n5. Recommend whether to retrain the AI component,\n run more physics iterations, or proceed to 2-D.\nReply in plain scientific English.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
2.26.5. Agent Modules#
|
pycsamt.agents._base |
|
pycsamt.agents.context |
|
pycsamt.agents.loader |
|
pycsamt.agents.coordinator |
|
pycsamt.agents.orchestrator |
|
pycsamt.agents.qc |
|
pycsamt.agents.static_shift |
|
pycsamt.agents.phase_analysis |
|
pycsamt.agents.tensor_rotation |
|
pycsamt.agents.tipper_analysis |
|
pycsamt.agents.freq_decimation |
|
pycsamt.agents.denoising |
|
pycsamt.agents.forward |
|
pycsamt.agents.inversion_prep |
|
pycsamt.agents.inversion_eval |
|
pycsamt.agents.inversion_backend |
|
pycsamt.agents.inversion_comparison |
|
pycsamt.agents.occam2d_agent |
|
pycsamt.agents.modem_agent |
|
pycsamt.agents.inv2d_agent |
|
pycsamt.agents.inv3d_agent |
|
pycsamt.agents.ai_inversion |
|
pycsamt.agents.ensemble_agent |
|
pycsamt.agents.joint_agent |
|
pycsamt.agents.model_zoo_agent |
|
pycsamt.agents.anomaly_agent |
|
pycsamt.agents.interpretation |
|
pycsamt.agents.iot_agent |
|
pycsamt.agents.resistivity_map |
|
pycsamt.agents.sensitivity |
|
pycsamt.agents.edi_export |
|
pycsamt.agents.report |
|
pycsamt.agents.code_gen |
|
pycsamt.agents.pipeline_agent |
|
pycsamt.agents.batch_survey |
|
pycsamt.agents.hybrid_agent |
|
pycsamt.agents.mare2dem_agent |
|
One-line front door to the pyCSAMT agent stack. |
|
pycsamt.agents.metrics |
|
pycsamt.agents.package_qa PackageQAAgent — answer free-form questions about the pycsamt v2 package. |
|
pycsamt.agents.pinn_agent |
|
pycsamt.agents.plotting |
|
pycsamt.agents.router |
|
pycsamt.agents.tooling |
|
pycsamt.agents.web |
2.26.6. IoT Field Agent#
- class pycsamt.agents.IoTFieldAgent(*, api_key=None, model=None, llm_provider='claude', method=None, write_manifest=False)#
Bases:
BaseAgentAssess and monitor an IoT-enabled EM field acquisition.
- Parameters:
api_key (str, optional) – LLM configuration. When
api_keyisNonethe agent runs fully offline andllm_interpretationisNone.model (str, optional) – LLM configuration. When
api_keyisNonethe agent runs fully offline andllm_interpretationisNone.llm_provider (str, optional) – LLM configuration. When
api_keyisNonethe agent runs fully offline andllm_interpretationisNone.method (str, optional) – EM method hint (
"amt","mt","csamt","csem","tdem","tem"). Used to seed sessions built from packets and to tag the manifest. When omitted it is inferred from the telemetry.write_manifest (bool, default False) – When
TrueanAcquisitionManifestis built for every run (and written whenmanifest_path/output_diris available).keys (Output data)
----------
order) (One acquisition source is required (tried in this)
session (FieldSession or mapping) – A live session, or a mapping from
to_dict().packets (iterable of TelemetryPacket or mapping) – Raw telemetry to fold into a fresh session.
sites (path / edis /) – An existing survey to seed a re-occupation session via
field_session_from_edis()(no live packets).deployment (DeploymentConfig, optional) – Declared device capabilities; tabulated via
deployment_report().keys
survey_id (str) – Identifier for sessions built from packets/EDIs (default
"iot_survey").now (float) – Reference epoch seconds for live latency / gap calculations.
output_dir (str) – Directory for saved figures and (when requested) the manifest.
manifest (bool) – Force building the acquisition manifest for this run.
manifest_path (str) – Explicit path to write the manifest JSON.
sign_key (str or bytes) – When given with a manifest, the written manifest is HMAC-signed.
sync_references (mapping) – Per-device reference clocks for
batch_assess_sync().energy_configs (iterable of EnergyConfig) – Device energy budgets for
estimate_deployment_energy().figures (bool, default True) – Whether to render dashboard/edge/power/sync figures.
api (bool) – Passed through to pycsamt table builders (API-object vs raw frame).
keys
----------------
:param
sessionthe resolvedFieldSession: :paramstatusMonitoringStatus: :paramstatus_tableone-row monitoring-status table: :paramtelemetry_summarypacket counts by device and topic: :parampacket_tablefull telemetry packet table: :paramstation_tableregistered stations: :parampipeline_inputacquisition hand-off dict for the processing flow: :paramdeployment_tabledevice-capability table (whendeploymentgiven): :paramsync_tableclock-sync table (whensync_referencesgiven): :parampower_tableenergy-budget table (whenenergy_configsgiven): :parammanifestAcquisitionManifest(optional): :parammanifest_pathwritten manifest path (optional): :paramsignaturemanifest HMAC signature dict (whensign_keygiven): :paramlevelmonitoring level string (ok/warn/critical): :paramissueslist of issue strings: :paramn_packets/n_stations/n_devices: :typen_packets/n_stations/n_devices: int :paramfiguresdict of matplotlib Figure objects: :paramfigure_pathsdict of saved figure paths (whenoutput_dirset):Examples
>>> agent = IoTFieldAgent() >>> res = agent.execute({"packets": packets, "output_dir": "/out/iot"}) >>> res["level"] 'warn' >>> res["status"].issues ['battery_min_v below 11.5 V on 2 device(s)'] >>> res["figures"]["dashboard"] <Figure ...>
- SYSTEM_PROMPT: str = 'You are an expert IoT field-operations analyst for pycsamt v2, supervising a\nlive AMT/MT/CSAMT/CSEM acquisition through its edge telemetry stream.\nGiven a survey monitoring summary, write 3-5 sentences that:\n1. State the overall health of the acquisition (healthy / degraded / critical).\n2. Call out the specific stations, channels, or devices that need field\n attention, and the dominant failure mode (packet loss, high latency,\n low battery, clock drift, edge rejections, sensor dropout).\n3. Explain the most likely field cause (power budget, GPS/clock, comms link,\n contact resistance, powerline or near-field interference).\n4. Recommend the single most important field action to take right now.\nReply in plain English. No bullet points, no markdown headings.\n'#
Override in subclasses to give the LLM its domain expertise.
- execute(input_data)#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type: