Source code for optiland.physical_optics.diagnostics

"""Non-mutating diagnostics for sampled scalar optical fields."""

from __future__ import annotations

import math
import warnings
from dataclasses import dataclass
from numbers import Integral, Real
from typing import TYPE_CHECKING

import optiland.backend as be
from optiland.physical_optics.field import ScalarField

if TYPE_CHECKING:
    from optiland._types import ScalarOrArray


[docs] @dataclass(frozen=True, slots=True) class BoundaryDiagnostic: """Intensity fraction in the boundary band of a rectangular field. Attributes: boundary_fraction: Dimensionless boundary intensity divided by total intensity. Zero for a zero field. A NumPy scalar or a differentiable Torch scalar with the field's real dtype and device. edge_width: Width of the band at each edge, in samples. threshold: Finite positive reporting threshold. exceeds_threshold: Whether ``boundary_fraction > threshold``. The record is frozen; its Torch scalar remains a tensor, not a deeply immutable value. A low fraction does not establish alias-free propagation. """ boundary_fraction: ScalarOrArray edge_width: int threshold: float exceeds_threshold: bool
[docs] def boundary_diagnostic( field: ScalarField, *, edge_width: int, threshold: float, warn: bool = False, ) -> BoundaryDiagnostic: """Report intensity reaching the periodic FFT grid boundary. Call this on a propagated field to check its current boundary occupancy. The band is the union of the outer ``edge_width`` rows and columns; corners are counted once. Uniform pixel area cancels from the power ratio, even when ``dx != dy``. Amplitudes are scaled before squaring to avoid intensity overflow or underflow. Neither the field nor its sampling is modified. This is a wraparound-risk indicator, not a proof of alias-free propagation: it cannot detect content that has already wrapped back into the interior or assess spatial-frequency sampling. It never pads or filters the field. The metric retains Torch gradients for nonzero fields. Validation and the Python threshold flag synchronize Torch device execution, even when ``warn=False``; warning formatting also reads the scalar on the host. The zero-field convention has a zero gradient, not a directional limit. Args: field: Scalar field on the active backend, with finite amplitudes. edge_width: Positive integer band width in samples, no greater than half the smaller grid dimension (rounded down). threshold: Finite positive dimensionless fraction threshold. Equality does not trigger the flag or warning. warn: Emit a ``RuntimeWarning`` when the threshold is exceeded. Returns: BoundaryDiagnostic: Frozen record containing the backend metric and the threshold decision. Raises: TypeError: If the field or reporting parameters have invalid types. ValueError: If the width, threshold, or field amplitudes are invalid. RuntimeError: If the field's backend is no longer active. """ if not isinstance(field, ScalarField): raise TypeError("field must be a ScalarField.") field._ensure_active_backend() if isinstance(edge_width, bool) or not isinstance(edge_width, Integral): raise TypeError("edge_width must be an integer number of samples.") edge_width = int(edge_width) if not 1 <= edge_width <= min(field.shape) // 2: raise ValueError("edge_width must be between 1 and half the smaller dimension.") if isinstance(threshold, bool) or not isinstance(threshold, Real): raise TypeError("threshold must be a real scalar.") threshold = float(threshold) if not math.isfinite(threshold) or threshold <= 0: raise ValueError("threshold must be finite and greater than zero.") if not isinstance(warn, bool): raise TypeError("warn must be a bool.") if not bool(be.all(be.isfinite(field.data))): raise ValueError("field amplitudes must be finite.") amplitude = be.abs(field.data) scale = be.max(amplitude) if not bool(be.isfinite(scale)): raise ValueError("field amplitude magnitudes must be finite.") if bool(scale == 0): fraction = be.sum(amplitude**2) else: intensity = (amplitude / scale) ** 2 width = edge_width total_intensity = be.sum(intensity) if 2 * width == min(field.shape): # The band covers the grid; avoid reduction-order roundoff at 1. boundary_intensity = total_intensity else: boundary_intensity = ( be.sum(intensity[:width, :]) + be.sum(intensity[-width:, :]) + be.sum(intensity[width:-width, :width]) + be.sum(intensity[width:-width, -width:]) ) # Keep reduction-order roundoff within the physical fraction bounds. fraction = be.clip(boundary_intensity / total_intensity, 0.0, 1.0) exceeds_threshold = bool(fraction > threshold) if warn and exceeds_threshold: warnings.warn( f"Boundary intensity fraction {float(be.to_numpy(fraction)):.6g} exceeds " f"threshold {threshold:.6g} in the outer {edge_width} samples; " "periodic FFT propagation may suffer wraparound.", RuntimeWarning, stacklevel=2, ) return BoundaryDiagnostic(fraction, edge_width, threshold, exceeds_threshold)