Source code for croak.result
"""The :class:`~croak.result.RetrievalResult` returned by every retrieval algorithm."""
from __future__ import annotations
from dataclasses import dataclass, field
import numpy as np
from numpy.typing import NDArray
from .grid import Grid
__all__ = ["RetrievalResult"]
[docs]
@dataclass
class RetrievalResult:
r"""Outcome of a pulse retrieval, uniform across all algorithms.
Attributes
----------
spectrum : numpy.ndarray
Retrieved complex spectrum :math:`\tilde E(\omega)` (centred order).
grid : Grid
Time/frequency grid the retrieval ran on.
delays : numpy.ndarray
Delay axis (s).
interaction : str
Interaction name (``"shg"``/``"sd"``/``"pg"``).
algorithm : str
Algorithm name — one of the keys of
:data:`croak.retrieve.ALGORITHMS` (``"copra"``, ``"copra-jax"``,
``"lbfgs"``, ``"lbfgs-hand"``, ``"lbfgs-ad"``, ``"warm-lbfgs"``,
``"lbfgs-optx"``, ``"lm"``, ``"lm-optx"``, ``"cma-es"``).
error : float
Final FROG error ``R``.
errors : list of float
FROG error at each iteration.
trace : numpy.ndarray
Retrieved (simulated) trace, ``(Nomega, Ndelay)``, optimally scaled.
mu : float or numpy.ndarray
Final intensity scale factor (scalar, or per-frequency in ``Rω`` mode).
thickness : float or None
Fitted dispersive-slab thickness (m) when the retriever fitted it as an
extra parameter (``lbfgs-ad``/``lm`` with ``fit_thickness``); ``None``
when thickness was held fixed.
tau0 : float
Fitted delay-zero offset (s) when the retriever fitted it
(``fit_tau0``); ``0.0`` otherwise. Sign convention: the true delay-zero
of the measured trace lies at ``+tau0``, i.e. the simulated gate uses the
delay phase ``exp(iω(τ − τ0))``.
smear_scale : float or None
Fitted multiplier on the geometric-smearing kernel widths when the
retriever fitted it (``fit_smearing``); ``None`` when the kernel was held
fixed or absent. ``1.0`` means the fit kept the nominal widths.
For a split fit (``fit_smearing_split``) this is the gate-shape
(``p``) channel multiplier.
smear_scale_delta : float or None
Fitted multiplier on the delay (``delta``) kernel width when the two
smearing channels were fitted separately (``fit_smearing_split``);
``None`` for a joint fit (where ``smear_scale`` covers both channels)
or when smearing was not fitted.
stage_boundaries : list of int
Indices into ``errors`` where one stage of a multi-stage solver handed
over to the next, so a convergence plot can mark the join. Empty for the
single-stage solvers. A spliced curve needs this: ``warm-lbfgs`` counts
one COPRA *iteration* (a full local sweep over every delay) before the
handover and one L-BFGS *function evaluation* after it, and those are
not the same unit of work — an unmarked kink there reads as convergence
behaviour rather than as a change of algorithm.
"""
spectrum: NDArray[np.complex128]
grid: Grid
delays: NDArray[np.float64]
interaction: str
algorithm: str
error: float
errors: list[float] = field(default_factory=list)
trace: NDArray[np.float64] | None = None
mu: float | NDArray[np.float64] = 1.0
omega0: float = 0.0
thickness: float | None = None
tau0: float = 0.0
smear_scale: float | None = None
smear_scale_delta: float | None = None
stage_boundaries: list[int] = field(default_factory=list)
# -- derived quantities -------------------------------------------------
@property
def omega(self) -> NDArray[np.float64]:
"""Angular-frequency axis (rad/s, centred)."""
return self.grid.omega
@property
def wavelength(self) -> NDArray[np.float64]:
r"""Absolute wavelength axis (m): :math:`2\pi c/(\omega+\omega_0)`."""
from .maths import wlfreq
return wlfreq(self.grid.omega + self.omega0)
@property
def t(self) -> NDArray[np.float64]:
"""Time axis (s)."""
return self.grid.t
@property
def field(self) -> NDArray[np.complex128]:
"""Retrieved temporal field :math:`E(t)`."""
return self.grid.ifft(self.spectrum)
@property
def intensity_omega(self) -> NDArray[np.float64]:
r"""Spectral intensity :math:`|\tilde E(\omega)|^2`."""
return np.abs(self.spectrum) ** 2
@property
def phase_omega(self) -> NDArray[np.float64]:
r"""Spectral phase :math:`\arg \tilde E(\omega)`, unwrapped."""
return np.unwrap(np.angle(self.spectrum))
@property
def intensity_t(self) -> NDArray[np.float64]:
"""Temporal intensity :math:`|E(t)|^2`."""
return np.abs(self.field) ** 2
@property
def phase_t(self) -> NDArray[np.float64]:
r"""Temporal phase :math:`\arg E(t)`, unwrapped."""
return np.unwrap(np.angle(self.field))
def __repr__(self) -> str:
"""Return a concise summary repr (algorithm, interaction, error)."""
return (
f"RetrievalResult(algorithm={self.algorithm!r}, "
f"interaction={self.interaction!r}, error={self.error:.3e}, "
f"iterations={len(self.errors)})"
)