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: object

Immutable scalar result of a model data-fit loss.

Parameters:
  • value (float) – Reduced loss value. nan when no cell was included and reduction="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_valid when 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)
value: float#
kind: str#
reduction: str#
n_valid: int#
weight_sum: float#
class pycsamt.ai.losses.ModelLoss(kind='l2', delta=1.0, reduction='mean', weights=None)#

Bases: object

Configurable, 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 weights argument 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
kind: str = 'l2'#
delta: float = 1.0#
reduction: str = 'mean'#
weights: ndarray | None = None#
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:

ModelLoss

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 L1 loss with provenance.

Return type:

ModelLossResult

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 L2 loss with provenance.

Return type:

ModelLossResult

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:

ModelLossResult

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: object

Immutable scalar result of a spatial regularization loss.

Parameters:
  • value (float) – Reduced penalty value. nan when no difference was included and reduction="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'
value: float#
kind: str#
label: str#
reduction: str#
n_valid: int#
weight_sum: float#
class pycsamt.ai.losses.SpatialLoss(lambda_x=1.0, lambda_z=1.0, lambda_tv=0.0, kind='l2', reduction='mean')#

Bases: object

Configurable combination of gradient and TV regularizers.

Combines the lambda_x * L_grad_x + lambda_z * L_grad_z + lambda_tv * L_TV terms 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
lambda_x: float = 1.0#
lambda_z: float = 1.0#
lambda_tv: float = 0.0#
kind: str = 'l2'#
reduction: str = 'mean'#
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. 0 for depth or -1 for 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_x or L_grad_z.

Return type:

SpatialLossResult

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 of y_pred and 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:

SpatialLossResult

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: object

Immutable scalar result of a response-consistency loss.

Parameters:
  • value (float) – Reduced loss value. nan when no cell was included and reduction="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
value: float#
kind: str#
reduction: str#
n_valid: int#
weight_sum: float#
normalized: bool#
class pycsamt.ai.losses.ResponseLoss(kind='l2', reduction='mean')#

Bases: object

Configurable, 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
kind: str = 'l2'#
reduction: str = 'mean'#
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, and errors.

  • 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:

ResponseLossResult

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_response directly from canonical result/survey.

Requires exact station, component, and frequency alignment between forward and observed rather 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_error when it is available.

Returns:

Reduced response-consistency penalty, L_response.

Return type:

ResponseLossResult

Raises:
  • TypeError – If forward or observed has 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: object

Configurable, 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
kind: str = 'l2'#
delta: float = 1.0#
reduction: str = 'mean'#
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_mask cells, 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_mask and with finite-value masking of y_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:

ModelLossResult

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: object

Immutable scalar result of an uncertainty-aware loss.

Parameters:
  • value (float) – Reduced loss value. nan when no cell was included and reduction="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_valid when 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)
value: float#
kind: str#
reduction: str#
n_valid: int#
weight_sum: float#
class pycsamt.ai.losses.UncertaintyLoss(reduction='mean')#

Bases: object

Configurable, callable heteroscedastic Gaussian NLL loss.

Wraps gaussian_nll_loss() for reuse across training batches. Calibration is scored separately with calibration_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
reduction: str = 'mean'#
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)) with variance = exp(log_variance). Parameterizing the log-variance rather than the variance itself keeps it unconstrained in sign while variance stays 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:

UncertaintyLossResult

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 as coverage.

  • 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:

UncertaintyLossResult

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

pycsamt.ai.losses.model

Masked, weighted data-fit losses on canonical resistivity grids.

pycsamt.ai.losses.spatial

Spatial regularization losses on predicted resistivity grids.

pycsamt.ai.losses.response

Electromagnetic response-consistency losses.

pycsamt.ai.losses.boundary

Boundary-condition losses on predicted resistivity grids.

pycsamt.ai.losses.uncertainty

Uncertainty-aware losses for calibrated inversion outputs.