pycsamt.ai.validation.residuals#

Complex-response residual diagnostics for EM forward comparisons.

These diagnostics break the L_response penalty from pycsamt.ai.losses.response down by station, frequency, and component, matching the Inversion and Field rows of the validation matrix in the AI-inversion plan: “residual maps by station/ frequency/component”. Inputs share the canonical impedance shape (station, frequency, component) used by ForwardResult and SurveyData.

All functions operate on plain NumPy arrays so the module stays importable without an optional deep-learning backend.

Functions

response_residual_report(predicted, observed, *)

Break complex-impedance residuals down by station/frequency/ component.

response_residual_report_from_contracts(...)

Build a ResponseResidualReport directly from a ForwardResult/SurveyData pair.

Classes

ResponseResidualReport(overall, by_station, ...)

Immutable per-axis complex-impedance residual diagnostics.

class pycsamt.ai.validation.residuals.ResponseResidualReport(overall, by_station, by_frequency, by_component, station_names, frequencies_hz, components, shape)[source]

Bases: object

Immutable per-axis complex-impedance residual diagnostics.

Parameters:
  • overall (ResponseLossResult) – Reduced L_response penalty over every included cell.

  • by_station (ndarray) – Masked mean per-cell penalty aggregated over the other two axes, in the same units as overall.value (e.g. mean squared residual for kind="l2", not RMS). nan where an axis position has no valid cell.

  • by_frequency (ndarray) – Masked mean per-cell penalty aggregated over the other two axes, in the same units as overall.value (e.g. mean squared residual for kind="l2", not RMS). nan where an axis position has no valid cell.

  • by_component (ndarray) – Masked mean per-cell penalty aggregated over the other two axes, in the same units as overall.value (e.g. mean squared residual for kind="l2", not RMS). nan where an axis position has no valid cell.

  • station_names (tuple of str or None) – Optional station labels, length shape[0].

  • frequencies_hz (ndarray or None) – Optional frequency labels, length shape[1].

  • components (tuple of str or None) – Optional component labels, length shape[2].

  • shape (tuple of int) – (station, frequency, component) shape of the compared arrays.

Examples

>>> import numpy as np
>>> pred = np.array([[[1 + 0j], [2 + 0j]], [[0j], [0j]]])
>>> obs = np.array([[[1 + 0j], [0j]], [[0j], [3 + 0j]]])
>>> report = response_residual_report(pred, obs)
>>> report.shape
(2, 2, 1)
>>> report.by_station.tolist()
[2.0, 4.5]
overall: ResponseLossResult
by_station: ndarray
by_frequency: ndarray
by_component: ndarray
station_names: tuple[str, ...] | None
frequencies_hz: ndarray | None
components: tuple[str, ...] | None
shape: tuple[int, int, int]
pycsamt.ai.validation.residuals.response_residual_report(predicted, observed, *, errors=None, valid=None, kind='l2', station_names=None, frequencies_hz=None, components=None)[source]

Break complex-impedance residuals down by station/frequency/ component.

Parameters:
  • predicted (array-like of complex) – Forward-simulated impedance, canonical shape (station, frequency, component).

  • observed (array-like of complex) – Observed impedance with the same shape.

  • errors (array-like or None, optional) – Positive absolute standard errors used to normalize residuals, as in response_residual_loss().

  • valid (array-like of bool or None, optional) – Explicit observation mask.

  • kind ({"l1", "l2"}, default="l2") – Elementwise penalty applied to each residual magnitude.

  • station_names (sequence of str or None, optional) – Optional axis labels attached to the returned report.

  • components (sequence of str or None, optional) – Optional axis labels attached to the returned report.

  • frequencies_hz (array-like or None, optional) – Optional frequency labels attached to the returned report.

Returns:

Combined per-axis residual diagnostics.

Return type:

ResponseResidualReport

Examples

>>> import numpy as np
>>> pred = np.array([[[1 + 0j], [2 + 0j]], [[0j], [0j]]])
>>> obs = np.array([[[1 + 0j], [0j]], [[0j], [3 + 0j]]])
>>> report = response_residual_report(pred, obs)
>>> report.by_frequency.tolist()
[0.0, 6.5]
pycsamt.ai.validation.residuals.response_residual_report_from_contracts(forward, observed, *, kind='l2', use_errors=True)[source]

Build a ResponseResidualReport directly from a ForwardResult/SurveyData pair.

Requires exact station, component, and frequency alignment, as in response_loss_from_contracts().

Parameters:
  • forward (ForwardResult) – Predicted impedance from a Maxwell backend adapter.

  • observed (SurveyData) – Observed survey impedance to compare against.

  • kind ({"l1", "l2"}, default="l2") – Elementwise penalty applied to each residual magnitude.

  • use_errors (bool, default=True) – Normalize residuals by observed.impedance_error when it is available.

Returns:

Combined per-axis residual diagnostics, labeled with the survey’s station names, frequencies, and components.

Return type:

ResponseResidualReport