2.25.3.7. pycsamt.ai.validation#
Synthetic recovery, response residual, uncertainty calibration, and out-of-distribution validation tools.
Scientific validation gates for AI-assisted EM inversion.
This package contains synthetic recovery metrics, complex-response
residual diagnostics, uncertainty calibration, and out-of-distribution
checks. Plotting remains in pycsamt.ai.plot; this package
owns the numerical evidence behind plots.
Status#
All four submodules are implemented: masked RMSE/MAE/R2, structural similarity (SSIM), and depth-profile recovery diagnostics for known-truth synthetic grids (M0 baseline metrics); complex-impedance residual reports broken down by station/frequency/component (Inversion/Field rows of the validation matrix); Gaussian reliability curves with coverage, calibration penalty, and sharpness (Uncertainty row); and Mahalanobis/k-NN out-of-distribution scoring against a reference realization set (OOD sensitivity).
- class pycsamt.ai.validation.RecoveryReport(rmse, mae, r2, ssim, depth_rmse, depth_mae, n_valid, shape)#
Bases:
objectImmutable synthetic-recovery diagnostics for one grid pair.
- Parameters:
rmse (float) – Global masked root-mean-square and mean-absolute error.
mae (float) – Global masked root-mean-square and mean-absolute error.
r2 (float) – Coefficient of determination.
nanwhen the true values are numerically constant, making R² undefined.ssim (float or None) – Structural similarity index (Wang et al., 2004), or
Nonewhen it was not requested or not computable, e.g. the grid is partially masked or smaller than the requested window.depth_rmse (ndarray) – Per-layer RMSE/MAE along the requested depth axis.
depth_mae (ndarray) – Per-layer RMSE/MAE along the requested depth axis.
n_valid (int) – Number of cells included after masking.
Examples
>>> import numpy as np >>> pred = np.array([[1.0, 2.0], [3.0, 4.0]]) >>> true = np.array([[1.0, 2.0], [3.0, 6.0]]) >>> report = recovery_report(pred, true, compute_ssim=False) >>> report.n_valid, report.shape (4, (2, 2))
- pycsamt.ai.validation.recovery_report(y_pred, y_true, *, valid=None, depth_axis=0, compute_ssim=True, ssim_window=7)#
Summarize synthetic-recovery quality for one grid pair.
- Parameters:
y_pred (array-like) – Predicted and true model values sharing one 2-D or 3-D grid shape.
y_true (array-like) – Predicted and true model values sharing one 2-D or 3-D grid shape.
valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of both inputs.
depth_axis (int, default=0) – Grid axis passed to
depth_profile_rmse()anddepth_profile_mae().compute_ssim (bool, default=True) – Attempt
structural_similarity(). Skipped (ssimisNone) when the grid is partially masked or smaller thanssim_window, since SSIM has no defined masking rule.ssim_window (int, default=7) – Window forwarded to
structural_similarity().
- Returns:
Combined recovery diagnostics.
- Return type:
- Raises:
ValueError – If shapes mismatch, inputs are empty, or no cell is valid.
Examples
>>> import numpy as np >>> pred = np.array([[1.0, 2.0], [3.0, 4.0]]) >>> true = np.array([[1.0, 2.0], [3.0, 6.0]]) >>> report = recovery_report(pred, true, compute_ssim=False) >>> round(report.rmse, 6), round(report.mae, 6) (1.0, 0.5) >>> round(report.r2, 6) 0.714286
- pycsamt.ai.validation.structural_similarity(y_pred, y_true, *, window=7, data_range=None)#
Compute the mean structural similarity index (SSIM).
Uses the windowed luminance/contrast/structure formulation of Wang et al. (2004) with a uniform (box) window, evaluated on the interior of the grid to avoid boundary-filter artifacts.
- Parameters:
y_pred (array-like) – Fully finite 2-D or 3-D grids sharing one shape. SSIM has no defined masking rule, so both must be complete.
y_true (array-like) – Fully finite 2-D or 3-D grids sharing one shape. SSIM has no defined masking rule, so both must be complete.
window (int, default=7) – Positive odd window size, no larger than the smallest grid axis.
data_range (float or None, optional) – Dynamic range of the compared values. Defaults to the range spanned by the combined
y_pred/y_truevalues.
- Returns:
Mean SSIM over the interior window positions, at most 1.0 for identical grids.
- Return type:
Examples
>>> import numpy as np >>> grid = np.arange(64, dtype=float).reshape(8, 8) >>> structural_similarity(grid, grid, window=3) 1.0
- pycsamt.ai.validation.depth_profile_rmse(y_pred, y_true, *, axis=0, valid=None)#
Return per-layer masked RMSE along one grid axis.
- Parameters:
y_pred (array-like) – Predicted and true model values sharing one 2-D or 3-D grid shape.
y_true (array-like) – Predicted and true model values sharing one 2-D or 3-D grid shape.
axis (int, default=0) – Grid axis to break down by, typically depth (
z).valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of both inputs.
- Returns:
RMSE for each layer;
nanfor a layer with no valid cells.- Return type:
Examples
>>> import numpy as np >>> pred = np.array([[1.0, 2.0], [3.0, 4.0]]) >>> true = np.array([[1.0, 2.0], [3.0, 6.0]]) >>> depth_profile_rmse(pred, true).tolist() [0.0, 1.4142135623730951]
- pycsamt.ai.validation.depth_profile_mae(y_pred, y_true, *, axis=0, valid=None)#
Return per-layer masked MAE along one grid axis.
- Parameters:
y_pred (array-like) – Predicted and true model values sharing one 2-D or 3-D grid shape.
y_true (array-like) – Predicted and true model values sharing one 2-D or 3-D grid shape.
axis (int, default=0) – Grid axis to break down by, typically depth (
z).valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of both inputs.
- Returns:
MAE for each layer;
nanfor a layer with no valid cells.- Return type:
Examples
>>> import numpy as np >>> pred = np.array([[1.0, 2.0], [3.0, 4.0]]) >>> true = np.array([[1.0, 2.0], [3.0, 6.0]]) >>> depth_profile_mae(pred, true).tolist() [0.0, 1.0]
- class pycsamt.ai.validation.ResponseResidualReport(overall, by_station, by_frequency, by_component, station_names, frequencies_hz, components, shape)#
Bases:
objectImmutable per-axis complex-impedance residual diagnostics.
- Parameters:
overall (ResponseLossResult) – Reduced
L_responsepenalty 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 forkind="l2", not RMS).nanwhere 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 forkind="l2", not RMS).nanwhere 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 forkind="l2", not RMS).nanwhere 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#
- pycsamt.ai.validation.response_residual_report(predicted, observed, *, errors=None, valid=None, kind='l2', station_names=None, frequencies_hz=None, components=None)#
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:
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.response_residual_report_from_contracts(forward, observed, *, kind='l2', use_errors=True)#
Build a
ResponseResidualReportdirectly from aForwardResult/SurveyDatapair.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_errorwhen it is available.
- Returns:
Combined per-axis residual diagnostics, labeled with the survey’s station names, frequencies, and components.
- Return type:
- class pycsamt.ai.validation.ReliabilityCurve(levels, coverage, calibration, sharpness, n_valid, shape)#
Bases:
objectImmutable calibration report for Gaussian predictive intervals.
- Parameters:
levels (ndarray) – Nominal confidence levels in
(0, 1).coverage (ndarray) – Empirical coverage at each level, same shape as
levels.calibration (UncertaintyLossResult) – Reduced deviation between
coverageandlevels, fromcalibration_loss().sharpness (float) – Mean predictive standard deviation over included cells. Lower is sharper (more confident); meaningful only alongside good calibration.
n_valid (int) – Number of cells included after masking.
Examples
>>> import numpy as np >>> true = np.array([0.0, 0.0, 0.0, 0.0, 10.0]) >>> mean = np.zeros(5) >>> std = np.ones(5) >>> curve = reliability_curve(true, mean, std) >>> curve.n_valid 5
- calibration: UncertaintyLossResult#
- pycsamt.ai.validation.reliability_curve(y_true, y_pred_mean, y_pred_std, *, levels=None, valid=None, kind='l2', reduction='mean')#
Build a full calibration report for Gaussian predictive intervals.
- Parameters:
y_true (array-like) – True values.
y_pred_mean (array-like) – Predicted mean and positive standard deviation, same shape as
y_true.y_pred_std (array-like) – Predicted mean and positive standard deviation, same shape as
y_true.levels (array-like or None, optional) – Nominal confidence levels in
(0, 1). Defaults to(0.5, 0.8, 0.9, 0.95, 0.99).valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of all three inputs and with
y_pred_std > 0.kind ({"l1", "l2"}, default="l2") – Elementwise penalty forwarded to
calibration_loss().reduction ({"mean", "sum"}, default="mean") – Reduction forwarded to
calibration_loss().
- Returns:
Combined coverage, calibration penalty, and sharpness.
- Return type:
Examples
>>> import numpy as np >>> true = np.array([0.0, 0.0, 0.0, 0.0, 10.0]) >>> mean = np.zeros(5) >>> std = np.ones(5) >>> curve = reliability_curve(true, mean, std, levels=[0.5]) >>> curve.coverage.tolist(), curve.sharpness ([0.8], 1.0)
- pycsamt.ai.validation.empirical_coverage(y_true, y_pred_mean, y_pred_std, *, levels=None, valid=None)#
Compute empirical coverage of Gaussian predictive intervals.
- Parameters:
y_true (array-like) – True values.
y_pred_mean (array-like) – Predicted mean and positive standard deviation, same shape as
y_true.y_pred_std (array-like) – Predicted mean and positive standard deviation, same shape as
y_true.levels (array-like or None, optional) – Nominal confidence levels in
(0, 1). Defaults to(0.5, 0.8, 0.9, 0.95, 0.99).valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of all three inputs and with
y_pred_std > 0.
- Returns:
levels (ndarray) – The validated nominal levels.
coverage (ndarray, same shape as
levels) – Fraction of included cells whose true value falls inside themean +/- z(level) * stdinterval.n_valid (int) – Number of cells included after masking.
- Return type:
Examples
>>> import numpy as np >>> true = np.array([0.0, 0.0, 0.0, 0.0, 10.0]) >>> mean = np.zeros(5) >>> std = np.ones(5) >>> levels, coverage, n_valid = empirical_coverage( ... true, mean, std, levels=[0.5] ... ) >>> coverage.tolist() [0.8]
- pycsamt.ai.validation.predictive_sharpness(y_pred_std, *, valid=None)#
Return the masked mean predictive standard deviation.
Sharpness summarizes how confident a model’s predictive distribution is, independent of correctness; it is only a meaningful quality signal alongside good calibration.
- Parameters:
y_pred_std (array-like) – Predicted positive standard deviation.
valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking and with
y_pred_std > 0.
- Returns:
Mean predictive standard deviation over included cells.
- Return type:
Examples
>>> import numpy as np >>> predictive_sharpness(np.array([1.0, 2.0, 3.0])) 2.0
- class pycsamt.ai.validation.OODReport(scores, threshold, flagged, method, quantile, n_reference, n_features)#
Bases:
objectImmutable out-of-distribution screening result.
- Parameters:
scores (ndarray) – Per-sample distance from
ood_score().threshold (float) – Score above which a sample is flagged.
flagged (ndarray of bool) – Whether each sample exceeds
threshold, same shape asscores.method ({"mahalanobis", "knn"}) – Distance measure used to compute
scores.quantile (float or None) – Quantile of the reference self-scores used to derive
threshold, orNonewhen an explicitthresholdwas supplied instead.n_reference (int) – Size of the reference set used to define support.
n_features (int) – Size of the reference set used to define support.
Examples
>>> import numpy as np >>> reference = np.array( ... [ ... [0.0, 0.0], ... [1.0, 0.0], ... [0.0, 1.0], ... [-1.0, 0.0], ... [0.0, -1.0], ... [0.5, 0.5], ... ] ... ) >>> x = np.array([[0.0, 0.0], [50.0, 50.0]]) >>> report = flag_out_of_distribution( ... x, reference, method="knn", k=2, quantile=0.5 ... ) >>> report.flagged.tolist() [False, True]
- property fraction_flagged: float#
Return the fraction of scored samples flagged as OOD.
Examples
>>> import numpy as np >>> report = OODReport( ... scores=np.array([0.1, 5.0]), ... threshold=1.0, ... flagged=np.array([False, True]), ... method="knn", ... quantile=None, ... n_reference=10, ... n_features=2, ... ) >>> report.fraction_flagged 0.5
- pycsamt.ai.validation.ood_score(x, reference, *, method='mahalanobis', k=5)#
Score how far new inputs fall from the training distribution.
- Parameters:
x (array-like, shape (n_samples, n_features)) – Inputs to score, e.g. new survey feature vectors.
reference (array-like, shape (n_reference, n_features)) – Training-set feature vectors defining the support region.
method ({"mahalanobis", "knn"}, default="mahalanobis") –
"mahalanobis"measures deviation from the reference mean/covariance and requiresn_reference > n_features."knn"measures Euclidean distance to thek-th nearest reference point and makes no distributional assumption. Ifxshares exact points withreference, their k-NN distance to those points is zero; useflag_out_of_distribution()for a leave-one-out self-score instead of passingreferenceasxhere.k (int, default=5) – Neighbour rank used by
method="knn". Ignored otherwise.
- Returns:
Higher values indicate inputs farther from the training support.
- Return type:
Examples
>>> import numpy as np >>> reference = np.array( ... [ ... [0.0, 0.0], ... [1.0, 0.0], ... [0.0, 1.0], ... [-1.0, 0.0], ... [0.0, -1.0], ... [0.5, 0.5], ... ] ... ) >>> x = np.array([[0.0, 0.0], [50.0, 50.0]]) >>> scores = ood_score(x, reference, method="knn", k=2) >>> scores[0] < scores[1] True
- pycsamt.ai.validation.flag_out_of_distribution(x, reference, *, method='mahalanobis', k=5, quantile=0.99, threshold=None)#
Score inputs and flag those outside the training support.
When
thresholdis not supplied, it is derived as the requestedquantileof the reference set’s own leave-one-out ("knn") or full-sample ("mahalanobis") self-scores, i.e. “how unusual is a typical reference point”.- Parameters:
x (array-like, shape (n_samples, n_features)) – Inputs to score.
reference (array-like, shape (n_reference, n_features)) – Training-set feature vectors defining the support region.
method ({"mahalanobis", "knn"}, default="mahalanobis") – Distance measure, as in
ood_score().k (int, default=5) – Neighbour rank used by
method="knn". Ignored otherwise.quantile (float, default=0.99) – Quantile in
(0, 1)of the reference self-scores used to derivethreshold. Ignored whenthresholdis given.threshold (float or None, optional) – Explicit score threshold. Overrides
quantilewhen given.
- Returns:
Scores, threshold, and per-sample OOD flags.
- Return type:
Examples
>>> import numpy as np >>> reference = np.array( ... [ ... [0.0, 0.0], ... [1.0, 0.0], ... [0.0, 1.0], ... [-1.0, 0.0], ... [0.0, -1.0], ... [0.5, 0.5], ... ] ... ) >>> x = np.array([[0.0, 0.0], [50.0, 50.0]]) >>> report = flag_out_of_distribution(x, reference, k=1) >>> report.method, report.n_reference ('mahalanobis', 6)
|
Synthetic-recovery diagnostics for known-truth geological grids. |
|
Complex-response residual diagnostics for EM forward comparisons. |
|
Uncertainty-calibration diagnostics for predictive intervals. |
|
Out-of-distribution checks against the training realization set. |