2.25.3.4. pycsamt.ai.losses#
Model, spatial, response, boundary, and uncertainty objectives for AI-assisted inversion.
Loss terms for model, spatial, response, boundary, and uncertainty constraints.
Loss implementations belong here when they express inversion semantics rather than a specific training loop. Backend-specific adapters may use NumPy, PyTorch, or TensorFlow internally, but importing this package must remain safe when optional machine-learning dependencies are unavailable.
Status#
All five submodules are implemented: masked, weighted data-fit
losses on canonical resistivity grids (L_model), gradient/
total-variation regularizers (L_grad_x, L_grad_z, L_TV),
uncertainty-normalized complex-impedance residuals (L_response),
masked-target boundary constraints, and heteroscedastic Gaussian
NLL / coverage-calibration diagnostics for the M9 uncertainty
milestone in the AI-inversion plan.
- class pycsamt.ai.losses.ModelLossResult(value, kind, reduction, n_valid, weight_sum)#
Bases:
objectImmutable scalar result of a model data-fit loss.
- Parameters:
value (float) – Reduced loss value.
nanwhen no cell was included andreduction="mean".kind ({"l1", "l2", "huber"}) – Elementwise loss family that produced
value.reduction ({"mean", "sum"}) – Reduction applied over valid, weighted cells.
n_valid (int) – Number of cells included after masking.
weight_sum (float) – Sum of weights over included cells. Equals
n_validwhen no explicit weights were supplied.
Examples
>>> import numpy as np >>> result = model_l2_loss(np.array([1.0, 2.0]), np.array([1.0, 0.0])) >>> result.value, result.n_valid (2.0, 2)
- class pycsamt.ai.losses.ModelLoss(kind='l2', delta=1.0, reduction='mean', weights=None)#
Bases:
objectConfigurable, callable masked model data-fit loss.
Bundles a loss family, reduction, and optional fixed per-cell weights so the same configuration can be reused across batches in a training loop.
- Parameters:
kind ({"l1", "l2", "huber"}, default="l2") – Elementwise loss family.
delta (float, default=1.0) – Huber transition point. Ignored unless
kind="huber".reduction ({"mean", "sum"}, default="mean") – Reduction applied over valid, weighted cells.
weights (ndarray or None, optional) – Fixed per-cell weights reused on every call, broadcastable to the input shape. A
weightsargument passed directly to__call__()overrides this default for that call only.
Examples
>>> import numpy as np >>> loss = ModelLoss(kind="l1") >>> loss(np.array([1.0, 3.0]), np.array([1.0, 1.0])).value 1.0
- classmethod with_depth_weights(n_depth, *, dimension=2, kind='l2', delta=1.0, reduction='mean')#
Build a loss weighted by inverse depth on the leading axis.
- Parameters:
n_depth (int) – Number of depth cells along the grid’s leading axis.
dimension ({2, 3}, default=2) – Grid rank, matching
dimension: 2 for(z, x)grids, 3 for(z, y, x)grids. Used only to reshape the depth weights for broadcasting.kind (str) – Forwarded to the constructor.
delta (float) – Forwarded to the constructor.
reduction (str) – Forwarded to the constructor.
- Returns:
Loss whose stored weights broadcast against grids shaped
(n_depth, ...).- Return type:
Examples
>>> loss = ModelLoss.with_depth_weights(3, dimension=2) >>> loss.weights.shape (3, 1)
- pycsamt.ai.losses.model_l1_loss(y_pred, y_true, *, valid=None, weights=None, reduction='mean')#
Masked, weighted mean/summed absolute error.
- Parameters:
y_pred (array-like) – Predicted and true model values sharing one shape, typically log-resistivity on a canonical geological grid.
y_true (array-like) – Predicted and true model values sharing one shape, typically log-resistivity on a canonical geological grid.
valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of both inputs.
weights (array-like or None, optional) – Non-negative per-cell weights broadcastable to the shared shape, e.g. from
depth_weights().reduction ({"mean", "sum"}, default="mean") – Whether to divide by the total weight or return the raw sum.
- Returns:
Reduced
L1loss with provenance.- Return type:
Examples
>>> import numpy as np >>> model_l1_loss(np.array([1.0, 3.0]), np.array([1.0, 1.0])).value 1.0
- pycsamt.ai.losses.model_l2_loss(y_pred, y_true, *, valid=None, weights=None, reduction='mean')#
Masked, weighted mean/summed squared error.
- Parameters:
y_pred (array-like) – Predicted and true model values sharing one shape.
y_true (array-like) – Predicted and true model values sharing one shape.
valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of both inputs.
weights (array-like or None, optional) – Non-negative per-cell weights broadcastable to the shared shape.
reduction ({"mean", "sum"}, default="mean") – Whether to divide by the total weight or return the raw sum.
- Returns:
Reduced
L2loss with provenance.- Return type:
Examples
>>> import numpy as np >>> model_l2_loss(np.array([1.0, 3.0]), np.array([1.0, 1.0])).value 2.0
- pycsamt.ai.losses.model_huber_loss(y_pred, y_true, *, delta=1.0, valid=None, weights=None, reduction='mean')#
Masked, weighted Huber loss, robust to outlier cells.
- Parameters:
y_pred (array-like) – Predicted and true model values sharing one shape.
y_true (array-like) – Predicted and true model values sharing one shape.
delta (float, default=1.0) – Positive transition point between the quadratic and linear regimes.
valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of both inputs.
weights (array-like or None, optional) – Non-negative per-cell weights broadcastable to the shared shape.
reduction ({"mean", "sum"}, default="mean") – Whether to divide by the total weight or return the raw sum.
- Returns:
Reduced Huber loss with provenance.
- Return type:
Examples
>>> import numpy as np >>> small = model_huber_loss( ... np.array([0.5]), np.array([0.0]), delta=1.0 ... ).value >>> large = model_huber_loss( ... np.array([5.0]), np.array([0.0]), delta=1.0 ... ).value >>> round(small, 3), round(large, 3) (0.125, 4.5)
- pycsamt.ai.losses.depth_weights(n_depth)#
Return inverse-depth weights normalized to sum to one.
Shallower cells (small index) receive more weight than deeper ones, matching the intuition that shallow structure is easier to recover and should not dominate a training loss.
- Parameters:
n_depth (int) – Number of depth cells, at least one.
- Returns:
Weights proportional to
1 / (1 + depth_index).- Return type:
ndarray, shape (n_depth,)
Examples
>>> import numpy as np >>> weights = depth_weights(2) >>> np.round(weights, 6) array([0.666667, 0.333333])
- class pycsamt.ai.losses.SpatialLossResult(value, kind, label, reduction, n_valid, weight_sum)#
Bases:
objectImmutable scalar result of a spatial regularization loss.
- Parameters:
value (float) – Reduced penalty value.
nanwhen no difference was included andreduction="mean".kind ({"l1", "l2"}) – Elementwise penalty applied to each spatial difference.
label (str) – Loss identity, e.g.
"grad_axis0"or"tv".reduction ({"mean", "sum"}) – Reduction applied over valid, weighted differences.
n_valid (int) – Number of differences included after masking.
weight_sum (float) – Sum of weights over included differences.
Examples
>>> import numpy as np >>> grid = np.array([[0.0, 1.0, 3.0], [0.0, 0.0, 0.0]]) >>> result = gradient_smoothness_loss(grid, axis=1) >>> result.label 'grad_axis1'
- class pycsamt.ai.losses.SpatialLoss(lambda_x=1.0, lambda_z=1.0, lambda_tv=0.0, kind='l2', reduction='mean')#
Bases:
objectConfigurable combination of gradient and TV regularizers.
Combines the
lambda_x * L_grad_x + lambda_z * L_grad_z + lambda_tv * L_TVterms of the staged inversion objective for a canonical 2-D(z, x)grid, where depth is axis 0 and the horizontal direction is axis 1.- Parameters:
lambda_x (float, default=1.0) – Weight applied to the horizontal-gradient term.
lambda_z (float, default=1.0) – Weight applied to the depth-gradient term.
lambda_tv (float, default=0.0) – Weight applied to the total-variation term.
kind ({"l1", "l2"}, default="l2") – Elementwise penalty shared by the two gradient terms. The total-variation term always uses
"l1", matching its standard definition.reduction ({"mean", "sum"}, default="mean") – Reduction applied within each enabled term.
Examples
>>> import numpy as np >>> grid = np.array([[0.0, 1.0], [0.0, 3.0]]) >>> loss = SpatialLoss( ... lambda_x=1.0, lambda_z=0.0, lambda_tv=0.0, kind="l1" ... ) >>> loss(grid) 2.0
- pycsamt.ai.losses.gradient_smoothness_loss(y_pred, *, axis, kind='l2', valid=None, weights=None, reduction='mean')#
Penalize first-difference magnitude along one grid axis.
- Parameters:
y_pred (array-like) – Predicted model values on a canonical geological grid.
axis (int) – Grid axis along which to difference, e.g.
0for depth or-1for the horizontal direction. Negative axes are supported.kind ({"l1", "l2"}, default="l2") – Elementwise penalty applied to each difference.
valid (array-like of bool or None, optional) – Cell mask applied before differencing. A difference is kept only if both of its endpoint cells are valid and finite.
weights (array-like or None, optional) – Non-negative per-cell weights broadcastable to
y_pred. A difference is weighted by the minimum of its two endpoint weights.reduction ({"mean", "sum"}, default="mean") – Reduction applied over included differences.
- Returns:
Reduced smoothness penalty, e.g.
L_grad_xorL_grad_z.- Return type:
Examples
>>> import numpy as np >>> grid = np.array([[0.0, 1.0, 3.0], [0.0, 0.0, 0.0]]) >>> gradient_smoothness_loss(grid, axis=1, kind="l1").value 0.75
- pycsamt.ai.losses.total_variation_loss(y_pred, *, kind='l1', valid=None, weights=None, reduction='mean')#
Penalize anisotropic total variation over every spatial axis.
Computes
gradient_smoothness_loss()along each axis ofy_predand combines them, matching the standard anisotropic total-variation definition (a per-axis sum of directional gradients, as opposed to an isotropic pointwise gradient norm).- Parameters:
y_pred (array-like) – Predicted model values on a canonical geological grid.
kind ({"l1", "l2"}, default="l1") – Elementwise penalty applied to each difference.
valid (array-like of bool or None, optional) – Cell mask shared by every axis.
weights (array-like or None, optional) – Non-negative per-cell weights shared by every axis.
reduction ({"mean", "sum"}, default="mean") – Reduction applied over all included differences from every axis combined.
- Returns:
Reduced total-variation penalty,
L_TV, labeled"tv".- Return type:
Examples
>>> import numpy as np >>> grid = np.array([[0.0, 1.0], [0.0, 3.0]]) >>> total_variation_loss(grid).value 1.5
- class pycsamt.ai.losses.ResponseLossResult(value, kind, reduction, n_valid, weight_sum, normalized)#
Bases:
objectImmutable scalar result of a response-consistency loss.
- Parameters:
value (float) – Reduced loss value.
nanwhen no cell was included andreduction="mean".kind ({"l1", "l2"}) – Elementwise penalty applied to each residual magnitude.
reduction ({"mean", "sum"}) – Reduction applied over valid cells.
n_valid (int) – Number of impedance cells included after masking.
weight_sum (float) – Number of included cells; kept for interface parity with other loss results in this package.
normalized (bool) – Whether residuals were divided by a positive standard error before the penalty was applied.
Examples
>>> import numpy as np >>> pred = np.array([1 + 1j, 2 + 2j]) >>> obs = np.array([1 + 1j, 0 + 0j]) >>> response_residual_loss(pred, obs).value 4.0
- class pycsamt.ai.losses.ResponseLoss(kind='l2', reduction='mean')#
Bases:
objectConfigurable, callable response-consistency loss.
- Parameters:
kind ({"l1", "l2"}, default="l2") – Elementwise penalty applied to each residual magnitude.
reduction ({"mean", "sum"}, default="mean") – Reduction applied over included cells.
Examples
>>> import numpy as np >>> loss = ResponseLoss() >>> pred = np.array([1 + 1j, 2 + 2j]) >>> obs = np.array([1 + 1j, 0 + 0j]) >>> loss(pred, obs).value 4.0
- pycsamt.ai.losses.response_residual_loss(predicted, observed, *, errors=None, valid=None, kind='l2', reduction='mean')#
Compare predicted and observed complex impedance responses.
- Parameters:
predicted (array-like of complex) – Forward-simulated impedance. Any shape is accepted; the canonical layout is
(station, frequency, component).observed (array-like of complex) – Observed impedance with the same shape.
errors (array-like or None, optional) – Positive absolute standard errors, same shape as
predicted. When given, each residual is divided by its error before the elementwise penalty, matching the normalized-RMS EM data-misfit convention. Entries with a non-finite or non-positive error are excluded rather than raising.valid (array-like of bool or None, optional) – Explicit observation mask, combined with finite-value masking of
predicted,observed, anderrors.kind ({"l1", "l2"}, default="l2") – Elementwise penalty applied to each residual magnitude.
reduction ({"mean", "sum"}, default="mean") – Reduction applied over included cells.
- Returns:
Reduced response-consistency penalty,
L_response.- Return type:
Examples
>>> import numpy as np >>> pred = np.array([1 + 1j, 2 + 2j]) >>> obs = np.array([1 + 1j, 0 + 0j]) >>> response_residual_loss(pred, obs, kind="l2").value 4.0 >>> response_residual_loss( ... pred, obs, errors=np.array([1.0, 2.0]), kind="l2" ... ).value 1.0
- pycsamt.ai.losses.response_loss_from_contracts(forward, observed, *, kind='l2', reduction='mean', use_errors=True)#
Compute
L_responsedirectly from canonical result/survey.Requires exact station, component, and frequency alignment between
forwardandobservedrather than silently interpolating or reordering either axis, per the survey-matching principle in the AI-inversion plan.- 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.
reduction ({"mean", "sum"}, default="mean") – Reduction applied over included cells.
use_errors (bool, default=True) – Normalize residuals by
observed.impedance_errorwhen it is available.
- Returns:
Reduced response-consistency penalty,
L_response.- Return type:
- Raises:
TypeError – If
forwardorobservedhas the wrong type.ValueError – If station names, components, or frequencies are not identical and identically ordered on both inputs.
Examples
>>> import numpy as np >>> from pycsamt.ai.data import SurveyData >>> from pycsamt.forward.maxwell import ForwardResult, SolverDiagnostics >>> z = np.array([[[1 + 1j]]]) >>> observed = SurveyData(z, [10.0], ["S1"], ["zxy"], [[0, 0]]) >>> diagnostics = SolverDiagnostics([[True]], [[1]], [[0.0]], 0.01) >>> forward = ForwardResult( ... "a" * 64, ... [10.0], ... ["S1"], ... ["zxy"], ... z, ... None, ... "demo", ... "1", ... diagnostics, ... ) >>> response_loss_from_contracts(forward, observed).value 0.0
- class pycsamt.ai.losses.BoundaryLoss(kind='l2', delta=1.0, reduction='mean')#
Bases:
objectConfigurable, callable boundary-condition penalty.
- Parameters:
kind ({"l1", "l2", "huber"}, default="l2") – Elementwise penalty applied to each boundary-cell residual.
delta (float, default=1.0) – Huber transition point. Ignored unless
kind="huber".reduction ({"mean", "sum"}, default="mean") – Reduction applied over included boundary cells.
Examples
>>> import numpy as np >>> loss = BoundaryLoss(kind="l1") >>> grid = np.array([[1.0, 1.0], [3.0, 3.0]]) >>> air = np.array([[True, True], [False, False]]) >>> loss(grid, boundary_mask=air, target=0.0).value 1.0
- pycsamt.ai.losses.boundary_condition_loss(y_pred, *, boundary_mask, target, kind='l2', delta=1.0, valid=None, weights=None, reduction='mean')#
Penalize predicted values that violate a boundary constraint.
- Parameters:
y_pred (array-like) – Predicted model values on a canonical geological grid.
boundary_mask (array-like of bool, same shape as
y_pred) – Cells subject to the boundary constraint, e.g. air cells above topography or the outer mesh padding. At least one cell must be selected.target (float or array-like) – Required value on
boundary_maskcells, e.g. a fixed air resistivity. A scalar is broadcast to the grid shape.kind ({"l1", "l2", "huber"}, default="l2") – Elementwise penalty, as in
model_l2_loss().delta (float, default=1.0) – Huber transition point. Ignored unless
kind="huber".valid (array-like of bool or None, optional) – Additional cell mask combined with
boundary_maskand with finite-value masking ofy_pred.weights (array-like or None, optional) – Non-negative per-cell weights broadcastable to the grid shape.
reduction ({"mean", "sum"}, default="mean") – Reduction applied over included boundary cells.
- Returns:
Reduced boundary-condition penalty.
- Return type:
Examples
>>> import numpy as np >>> grid = np.array([[1.0, 1.0], [3.0, 3.0]]) >>> air = np.array([[True, True], [False, False]]) >>> boundary_condition_loss( ... grid, boundary_mask=air, target=0.0, kind="l1" ... ).value 1.0
- class pycsamt.ai.losses.UncertaintyLossResult(value, kind, reduction, n_valid, weight_sum)#
Bases:
objectImmutable scalar result of an uncertainty-aware loss.
- Parameters:
value (float) – Reduced loss value.
nanwhen no cell was included andreduction="mean".kind ({"gaussian_nll", "calibration"}) – Loss family that produced
value.reduction ({"mean", "sum"}) – Reduction applied over valid, weighted cells.
n_valid (int) – Number of cells included after masking.
weight_sum (float) – Sum of weights over included cells. Equals
n_validwhen no explicit weights were supplied.
Examples
>>> import numpy as np >>> pred = np.array([0.0, 1.0]) >>> true = np.array([0.0, 0.0]) >>> log_var = np.array([0.0, 0.0]) >>> result = gaussian_nll_loss(pred, true, log_var) >>> result.kind, result.n_valid ('gaussian_nll', 2)
- class pycsamt.ai.losses.UncertaintyLoss(reduction='mean')#
Bases:
objectConfigurable, callable heteroscedastic Gaussian NLL loss.
Wraps
gaussian_nll_loss()for reuse across training batches. Calibration is scored separately withcalibration_loss()over binned coverage, since it summarizes predictive intervals across many held-out realizations rather than acting as a per-cell training term.- Parameters:
reduction ({"mean", "sum"}, default="mean") – Reduction applied over included cells.
Examples
>>> import numpy as np >>> loss = UncertaintyLoss() >>> pred = np.array([0.0, 1.0]) >>> true = np.array([0.0, 0.0]) >>> log_var = np.array([0.0, 0.0]) >>> round(loss(pred, true, log_var).value, 6) 1.168939
- pycsamt.ai.losses.gaussian_nll_loss(y_pred, y_true, log_variance, *, valid=None, weights=None, reduction='mean')#
Compute a heteroscedastic Gaussian negative log-likelihood.
Each cell contributes
0.5 * ((y_pred - y_true)**2 / variance + log_variance + log(2*pi))withvariance = exp(log_variance). Parameterizing the log-variance rather than the variance itself keeps it unconstrained in sign whilevariancestays positive.- Parameters:
y_pred (array-like) – Predicted mean values.
y_true (array-like) – True values, same shape as
y_pred.log_variance (array-like) – Predicted log-variance, same shape as
y_pred. Any finite real value is valid.valid (array-like of bool or None, optional) – Explicit cell mask, combined with finite-value masking of all three inputs.
weights (array-like or None, optional) – Non-negative per-cell weights broadcastable to
y_pred.reduction ({"mean", "sum"}, default="mean") – Reduction applied over included cells.
- Returns:
Reduced negative log-likelihood.
- Return type:
Examples
>>> import numpy as np >>> pred = np.array([0.0, 1.0]) >>> true = np.array([0.0, 0.0]) >>> log_var = np.array([0.0, 0.0]) >>> round(gaussian_nll_loss(pred, true, log_var).value, 6) 1.168939
- pycsamt.ai.losses.calibration_loss(coverage, nominal_levels, *, kind='l2', valid=None, weights=None, reduction='mean')#
Penalize deviation between empirical and nominal coverage.
- Parameters:
coverage (array-like) – Empirical coverage observed at each nominal level, in
[0, 1].nominal_levels (array-like) – Declared confidence levels in
[0, 1], same shape ascoverage.kind ({"l1", "l2"}, default="l2") – Elementwise penalty applied to each calibration residual.
valid (array-like of bool or None, optional) – Explicit level mask, combined with finite-value masking of both inputs.
weights (array-like or None, optional) – Non-negative per-level weights broadcastable to
coverage.reduction ({"mean", "sum"}, default="mean") – Reduction applied over included levels.
- Returns:
Reduced calibration penalty.
- Return type:
Examples
>>> import numpy as np >>> coverage = np.array([0.4, 0.9]) >>> nominal = np.array([0.5, 0.8]) >>> round(calibration_loss(coverage, nominal).value, 6) 0.01
|
Masked, weighted data-fit losses on canonical resistivity grids. |
|
Spatial regularization losses on predicted resistivity grids. |
|
Electromagnetic response-consistency losses. |
|
Boundary-condition losses on predicted resistivity grids. |
|
Uncertainty-aware losses for calibrated inversion outputs. |