Source code for optixstuff.speckle
"""Speckle field abstractions.
A speckle field is the stochastic, time-varying residual-starlight pattern
left by wavefront errors that wavefront control does not null. It sits *on
top of* the deterministic coronagraphic leakage floor (the YIP
``stellar_intens`` map already applied in ``coronagraphoto.star_rate``), so
it is an instrument effect, not an astrophysical scene source -- it lives on
:class:`optixstuff.OpticalPath`, next to the coronagraph, rather than in the
skyscapes scene.
The standard linear speckle model is ``I(t) = |E_nom + G eps(t)|^2``, where
``E_nom`` is the static coherent residual field (``|E_nom|^2`` is the
deterministic floor), ``G`` maps wavefront-error modes to the focal-plane
field, and ``eps(t)`` are the drifting mode coefficients. Concrete fields
differ only in how they source ``G`` and ``eps`` (analytic, replayed
intensity cubes, fitted reduced-order, or learned); they share the
:class:`AbstractSpeckleField` contract below.
"""
import abc
import equinox as eqx
from equinox import AbstractVar
from jax.typing import ArrayLike
from jaxtyping import Array
[docs]
class AbstractSpeckleField(eqx.Module):
"""Abstract interface for time-varying speckle fields.
Implementations can be backed by an analytic generator (physicaloptix),
replayed designer/testbed intensity cubes, a fitted reduced-order model,
or a learned generator. All produce a coronagraph-plane contrast map
through :meth:`realize`.
All wavelength arguments are in nanometres; ``time_s`` is seconds since
mission start. The returned map is on the field's native coronagraph
plane at :attr:`pixel_scale_lod`; the caller resamples it to the
detector grid (as for the coronagraph ``stellar_intens`` map).
"""
pixel_scale_lod: AbstractVar[float]
"""Native pixel scale in lambda/D per pixel."""
epoch_jd: AbstractVar[float]
"""Julian Date that maps to ``time_s = 0`` -- the realization's clock
origin. Consumers (e.g. ``coronagraphoto.speckle_rate``) convert an
observation's absolute JD to the elapsed seconds :meth:`realize` expects
via ``(start_time_jd - epoch_jd)``; anchoring the clock here keeps the
physical interface in elapsed seconds and avoids feeding large absolute
JDs into the temporal synthesis."""
[docs]
@abc.abstractmethod
def realize(
self,
*,
wavelength_nm: ArrayLike,
time_s: ArrayLike = 0.0,
) -> Array:
"""Speckle contrast *delta* at a given time and wavelength.
Returns the wavefront-error-induced excess over the deterministic
coronagraphic floor -- i.e. ``I(t) - |E_nom|^2``, expanded as
``2 Re(E_nom* . G eps(t)) + |G eps(t)|^2``, in contrast units
(fraction of the host-star flux per pixel). It must not include the
``|E_nom|^2`` floor itself: ``star_rate`` already applies that via
the YIP ``stellar_intens`` map, so re-emitting it here would double
count. An incoherent-halo implementation returns only the strictly
positive ``|G eps(t)|^2`` term (no speckle pinning); a coherent
implementation adds the cross term, which carries the bright-tail
pinning and requires the complex ``E_nom``.
Evolution is driven by ``time_s``, not a per-call PRNG key: the
realization is fixed at construction so the map is deterministic and
differentiable, and temporal correlation survives across an exposure
/ roll sequence.
Args:
wavelength_nm: Wavelength in nanometres.
time_s: Time since mission start in seconds.
Returns:
2D float contrast-delta array on the native coronagraph plane.
"""
...