optixstuff
==========

.. py:module:: optixstuff

.. autoapi-nested-parse::

   optixstuff -- Hardware abstractions for the HWO simulation suite.



Submodules
----------

.. toctree::
   :maxdepth: 1

   /autoapi/optixstuff/_repr/index
   /autoapi/optixstuff/coronagraph/index
   /autoapi/optixstuff/detector/index
   /autoapi/optixstuff/disperser/index
   /autoapi/optixstuff/exposure/index
   /autoapi/optixstuff/optical_elements/index
   /autoapi/optixstuff/optical_path/index
   /autoapi/optixstuff/primary/index
   /autoapi/optixstuff/yippy_coronagraph/index


Classes
-------

.. autoapisummary::

   optixstuff.AbstractCoronagraph
   optixstuff.AbstractScalarCoronagraph
   optixstuff.AbstractDetector
   optixstuff.Detector
   optixstuff.IdealDetector
   optixstuff.AbstractDisperser
   optixstuff.LensletDisperser
   optixstuff.ExposureConfig
   optixstuff.AbstractOpticalElement
   optixstuff.AbstractUniformElement
   optixstuff.ConstantThroughput
   optixstuff.SpectralThroughput
   optixstuff.OpticalPath
   optixstuff.AbstractPrimary
   optixstuff.SimplePrimary
   optixstuff.YippyCoronagraph


Functions
---------

.. autoapisummary::

   optixstuff.clock_induced_charge
   optixstuff.dark_current
   optixstuff.read_noise


Package Contents
----------------

.. py:class:: AbstractCoronagraph

   Bases: :py:obj:`equinox.Module`


   Abstract interface for coronagraph performance models.

   Provides both scalar performance curves (for ETC use) and 2D PSF
   generation (for image simulation). Implementations can be backed by
   pre-computed interpolation tables (yippy), physical wavefront
   propagation, or analytical models.

   All wavelength arguments are in nanometres throughout.
   All separations are in lambda/D units.


   .. py:attribute:: pixel_scale_lod
      :type:  equinox.AbstractVar[float]

      Native pixel scale in lambda/D per pixel.



   .. py:attribute:: IWA
      :type:  equinox.AbstractVar[float]

      Inner working angle in lambda/D.



   .. py:attribute:: OWA
      :type:  equinox.AbstractVar[float]

      Outer working angle in lambda/D.



   .. py:method:: throughput(separation_lod, wavelength_nm, *, time_s = 0.0)
      :abstractmethod:


      Core (off-axis planet) throughput.

      Args:
          separation_lod: Angular separation in lambda/D.
          wavelength_nm: Wavelength in nanometres.
          time_s: Time since mission start in seconds.

      Returns:
          Fractional throughput in [0, 1].



   .. py:method:: core_area(separation_lod, wavelength_nm, *, time_s = 0.0)
      :abstractmethod:


      Photometric aperture area in (lambda/D)^2.

      Args:
          separation_lod: Angular separation in lambda/D.
          wavelength_nm: Wavelength in nanometres.
          time_s: Time since mission start in seconds.

      Returns:
          Core area in (lambda/D)^2.



   .. py:method:: core_mean_intensity(separation_lod, wavelength_nm, *, time_s = 0.0)
      :abstractmethod:


      Mean stellar intensity within the photometric aperture.

      Args:
          separation_lod: Angular separation in lambda/D.
          wavelength_nm: Wavelength in nanometres.
          time_s: Time since mission start in seconds.

      Returns:
          Mean stellar leakage intensity in (lambda/D)^-2.



   .. py:method:: occulter_transmission(separation_lod, wavelength_nm, *, time_s = 0.0)
      :abstractmethod:


      Off-axis (sky/zodi) transmission through the occulter.

      Args:
          separation_lod: Angular separation in lambda/D.
          wavelength_nm: Wavelength in nanometres.
          time_s: Time since mission start in seconds.

      Returns:
          Fractional sky transmission in [0, 1].



   .. py:method:: on_axis_psf(wavelength_nm, pixel_scale_rad, npixels)
      :abstractmethod:


      On-axis (stellar leakage) PSF.

      Returns the coronagraphic PSF for an on-axis point source,
      normalized to unit stellar flux before the coronagraph.

      Args:
          wavelength_nm: Wavelength in nanometres.
          pixel_scale_rad: Output pixel scale in radians/pixel.
          npixels: Output array side length in pixels. Must be a
              Python int (not a JAX array) as it determines the
              output shape at compile time.

      Returns:
          2D float array of shape (npixels, npixels).



   .. py:method:: off_axis_psf(wavelength_nm, separation_lod, pixel_scale_rad, npixels)
      :abstractmethod:


      Off-axis PSF at a given angular separation.

      Args:
          wavelength_nm: Wavelength in nanometres.
          separation_lod: Source separation in lambda/D.
          pixel_scale_rad: Output pixel scale in radians/pixel.
          npixels: Output array side length in pixels. Must be a
              Python int (not a JAX array) as it determines the
              output shape at compile time.

      Returns:
          2D float array of shape (npixels, npixels).



.. py:class:: AbstractScalarCoronagraph

   Bases: :py:obj:`AbstractCoronagraph`


   Base for ETC-only coronagraph models that lack 2D PSF generation.

   Stubs out the image interface with zero arrays so the class satisfies
   AbstractCoronagraph without requiring a full optical model.


   .. py:method:: on_axis_psf(wavelength_nm, pixel_scale_rad, npixels)

      Return a zero PSF (not implemented for scalar-only models).



   .. py:method:: off_axis_psf(wavelength_nm, separation_lod, pixel_scale_rad, npixels)

      Return a zero PSF (not implemented for scalar-only models).



.. py:class:: AbstractDetector

   Bases: :py:obj:`equinox.Module`


   Abstract interface for a focal-plane detector.

   Provides both scalar noise rates (for ETC use) and stochastic noise
   realization (for image simulation).  All concrete implementations
   must define the hardware parameters listed as ``AbstractVar`` fields.


   .. py:attribute:: pixel_scale_arcsec
      :type:  equinox.AbstractVar[float]

      Detector plate scale in arcsec/pixel.



   .. py:attribute:: quantum_efficiency
      :type:  equinox.AbstractVar[float]

      Baseline quantum efficiency as a fraction in [0, 1].



   .. py:attribute:: dark_current_rate_e_per_s
      :type:  equinox.AbstractVar[float]

      Dark current rate in electrons/pixel/second.



   .. py:attribute:: read_noise_e
      :type:  equinox.AbstractVar[float]

      Read noise in electrons RMS per pixel per read.



   .. py:attribute:: clock_induced_charge_rate_e_per_frame
      :type:  equinox.AbstractVar[float]

      Clock-induced charge in electrons/pixel/frame.



   .. py:attribute:: frame_time_s
      :type:  equinox.AbstractVar[float]

      Integration time per frame/read in seconds.



   .. py:attribute:: read_time_s
      :type:  equinox.AbstractVar[float]

      Time per read cycle in seconds (for RN^2/t_read in ETC).



   .. py:attribute:: dqe
      :type:  equinox.AbstractVar[float]

      QE degradation factor (multiplicative correction over mission life).



   .. py:attribute:: shape
      :type:  equinox.AbstractVar[tuple[int, int]]

      Detector dimensions (ny, nx) in pixels.



   .. py:method:: get_qe(wavelength_nm)
      :abstractmethod:


      Quantum efficiency at a given wavelength.

      Args:
          wavelength_nm: Wavelength in nanometres.

      Returns:
          QE as a fraction in [0, 1].



   .. py:method:: scalar_noise_rate(n_pix, t_photon)
      :abstractmethod:


      Total scalar noise variance rate for the ETC.

      Returns the combined noise variance per unit time (electrons^2/s)
      for a photometric aperture of n_pix pixels.

      Args:
          n_pix: Number of pixels in the photometric aperture.
          t_photon: Photon counting integration time in seconds.

      Returns:
          Noise variance rate in electrons^2/second.



   .. py:method:: readout(image_rate, exposure_time_s, prng_key)
      :abstractmethod:


      Apply stochastic noise realization to a photon rate image.

      Converts photon rates to detected electrons including QE,
      dark current, CIC, and read noise.

      Args:
          image_rate: Incident photon rate array in ph/s/pixel.
          exposure_time_s: ExposureConfig time in seconds.
          prng_key: JAX PRNG key (required, no default).

      Returns:
          Detected electrons array, same shape as image_rate.



   .. py:method:: readout_source_electrons(image_rate, exposure_time_s, prng_key)

      Poisson-sample incident photons and convert to electrons via QE.

      This is the source-dependent half of detector readout: photon
      arrival statistics and quantum-efficiency selection. Dark current,
      CIC, and read noise are handled separately by
      :meth:`readout_noise_electrons` so that multi-source exposures do not
      double-count the source-independent noise floor.

      Args:
          image_rate: Incident photon rate array in ph/s/pixel.
          exposure_time_s: ExposureConfig time in seconds.
          prng_key: JAX PRNG key.

      Returns:
          Photo-electron counts, same shape as image_rate.



   .. py:method:: readout_source_electrons_thinned(image_rate, exposure_time_s, prng_key)

      Fast equivalent of :meth:`readout_source_electrons` via Poisson thinning.

      Distributionally identical to the explicit Poisson-then-Binomial
      chain (Poisson thinning theorem: ``Binomial(Poisson(L), p) ~
      Poisson(L * p)``), but ~3x faster because it skips the Binomial
      draw and never materialises the intermediate photon count. Use in
      performance-critical paths (animation rendering, yield runs)
      when the photon count is not needed downstream.

      The marginal distribution of returned electrons is identical to
      :meth:`readout_source_electrons`. The two methods produce
      different specific realisations even with the same key.

      Args:
          image_rate: Incident photon rate array in ph/s/pixel.
          exposure_time_s: ExposureConfig time in seconds.
          prng_key: JAX PRNG key.

      Returns:
          Photo-electron counts, same shape as image_rate.



   .. py:method:: noise_variance(image_rate, exposure_time_s)

      Deterministic per-pixel total noise variance in electrons^2.

      The expected variance at each pixel for an incident photon-rate image
      -- the deterministic companion to :meth:`readout`. Combines source shot
      noise (Poisson on detected electrons) with the source-independent floor
      (dark current, clock-induced charge, read noise)::

          N = QE * rate * t
              + dark_rate * t
              + CIC_rate * n_frames
              + read_noise^2 * n_frames

      with ``n_frames = ceil(t / frame_time_s)``. Use ``1 / noise_variance(...)``
      as inverse-variance weights for least-squares spectral extraction or its
      GLS covariance, where the shot term carries the wavelength dependence.

      Args:
          image_rate: Incident photon rate [ph/s/pixel], any shape.
          exposure_time_s: Exposure time in seconds.

      Returns:
          Per-pixel noise variance [electrons^2], same shape as image_rate.



   .. py:method:: readout_noise_electrons(exposure_time_s, prng_key)
      :abstractmethod:


      Source-independent detector noise (dark + CIC + read).

      Each call draws a fresh noise realization of shape ``self.shape``;
      consumers add exactly one such draw per exposure regardless of
      how many sources were co-added via :meth:`readout_source_electrons`.

      Args:
          exposure_time_s: ExposureConfig time in seconds.
          prng_key: JAX PRNG key.

      Returns:
          Noise-electron array of shape ``self.shape``.



.. py:class:: Detector(pixel_scale_arcsec, shape, quantum_efficiency = 1.0, dark_current_rate_e_per_s = 0.0, read_noise_e = 0.0, clock_induced_charge_rate_e_per_frame = 0.0, frame_time_s = 1.0, read_time_s = 0.05, dqe = 0.0)

   Bases: :py:obj:`AbstractDetector`


   Full detector model with dark current, CIC, and read noise.

   Suitable for detailed noise simulations where all detector noise
   sources matter.  Uses Poisson statistics for dark/CIC and Gaussian
   for read noise, matching the coronagraphoto convention.

   Warning: ``num_frames = jnp.ceil(exposure_time_s / frame_time_s)`` is
   kept as a float. Never cast it to int inside JIT -- that triggers a
   ConcretizationTypeError when exposure_time_s is traced.


   .. py:attribute:: pixel_scale_arcsec
      :type:  float

      Detector plate scale in arcsec/pixel.



   .. py:attribute:: quantum_efficiency
      :type:  float

      Baseline quantum efficiency as a fraction in [0, 1].



   .. py:attribute:: dark_current_rate_e_per_s
      :type:  float

      Dark current rate in electrons/pixel/second.



   .. py:attribute:: read_noise_e
      :type:  float

      Read noise in electrons RMS per pixel per read.



   .. py:attribute:: clock_induced_charge_rate_e_per_frame
      :type:  float

      Clock-induced charge in electrons/pixel/frame.



   .. py:attribute:: frame_time_s
      :type:  float

      Integration time per frame/read in seconds.



   .. py:attribute:: read_time_s
      :type:  float

      Time per read cycle in seconds (for RN^2/t_read in ETC).



   .. py:attribute:: dqe
      :type:  float

      QE degradation factor (multiplicative correction over mission life).



   .. py:attribute:: shape
      :type:  tuple[int, int]

      Detector dimensions (ny, nx) in pixels.



   .. py:method:: get_qe(wavelength_nm)

      Return constant QE, ignoring wavelength.



   .. py:method:: scalar_noise_rate(n_pix, t_photon)

      Combined dark + CIC noise variance rate.



   .. py:method:: readout_noise_electrons(exposure_time_s, prng_key)

      Dark current + CIC + read noise (source-independent).



   .. py:method:: readout(image_rate, exposure_time_s, prng_key)

      Full detector readout: source electrons + all noise sources.



   .. py:method:: __repr__()

      One-line summary of shape, plate scale, QE, and noise sources.



.. py:class:: IdealDetector(pixel_scale_arcsec, shape, quantum_efficiency = 1.0, dark_current_rate_e_per_s = 0.0, read_noise_e = 0.0, clock_induced_charge_rate_e_per_frame = 0.0, frame_time_s = 1.0, read_time_s = 0.05, dqe = 0.0)

   Bases: :py:obj:`AbstractDetector`


   Detector with constant QE and minimal noise sources.

   Suitable for broadband imager studies where wavelength-dependent
   QE variation is not important and CIC/read noise are negligible.


   .. py:attribute:: pixel_scale_arcsec
      :type:  float

      Detector plate scale in arcsec/pixel.



   .. py:attribute:: quantum_efficiency
      :type:  float

      Baseline quantum efficiency as a fraction in [0, 1].



   .. py:attribute:: dark_current_rate_e_per_s
      :type:  float

      Dark current rate in electrons/pixel/second.



   .. py:attribute:: read_noise_e
      :type:  float

      Read noise in electrons RMS per pixel per read.



   .. py:attribute:: clock_induced_charge_rate_e_per_frame
      :type:  float

      Clock-induced charge in electrons/pixel/frame.



   .. py:attribute:: frame_time_s
      :type:  float

      Integration time per frame/read in seconds.



   .. py:attribute:: read_time_s
      :type:  float

      Time per read cycle in seconds (for RN^2/t_read in ETC).



   .. py:attribute:: dqe
      :type:  float

      QE degradation factor (multiplicative correction over mission life).



   .. py:attribute:: shape
      :type:  tuple[int, int]

      Detector dimensions (ny, nx) in pixels.



   .. py:method:: get_qe(wavelength_nm)

      Return constant QE, ignoring wavelength.



   .. py:method:: scalar_noise_rate(n_pix, t_photon)

      Combined dark + CIC noise variance rate.

      Read noise is not included here as it scales per-read, not per-second.
      Callers add (read_noise_e^2 * n_reads) / t_exp separately.



   .. py:method:: readout_noise_electrons(exposure_time_s, prng_key)

      Dark current only -- IdealDetector has no CIC or read noise.



   .. py:method:: readout(image_rate, exposure_time_s, prng_key)

      Full detector readout: source electrons + dark current.



   .. py:method:: __repr__()

      One-line summary of shape, plate scale, QE, and dark current.



.. py:function:: clock_induced_charge(clock_induced_charge_rate_e_per_frame, num_frames, shape, prng_key)

   Draw clock-induced charge electrons from a Poisson distribution.

   Args:
       clock_induced_charge_rate_e_per_frame: CIC rate in electrons/pixel/frame.
       num_frames: Number of frames (kept as float for JIT safety).
       shape: Detector shape (ny, nx).
       prng_key: PRNG key.

   Returns:
       CIC electrons, shape (ny, nx).


.. py:function:: dark_current(dark_current_rate_e_per_s, exposure_time_s, shape, prng_key)

   Draw dark current electrons from a Poisson distribution.

   Args:
       dark_current_rate_e_per_s: Dark current rate in electrons/s/pixel.
       exposure_time_s: ExposureConfig time in seconds.
       shape: Detector shape (ny, nx).
       prng_key: PRNG key.

   Returns:
       Dark current electrons, shape (ny, nx).


.. py:function:: read_noise(read_noise_e, num_frames, shape, prng_key)

   Draw read noise from a Gaussian distribution.

   Total read noise sigma = sqrt(num_frames) * read_noise_per_read.

   Args:
       read_noise_e: Read noise in electrons/pixel/read.
       num_frames: Number of frames.
       shape: Detector shape (ny, nx).
       prng_key: PRNG key.

   Returns:
       Read noise electrons, shape (ny, nx).


.. py:class:: AbstractDisperser

   Bases: :py:obj:`equinox.Module`


   Interface for a dispersing IFS element (lenslet array, slicer, MSA).

   Only the scalar/ETC face is defined here. Render geometry lives in
   coronachrome and dispatches on the concrete descriptor type.


   .. py:method:: spectral_resolution(wavelength_nm)
      :abstractmethod:


      Resolving power R = lambda / dlambda at the given wavelength.



   .. py:method:: spectral_sampling()
      :abstractmethod:


      Detector pixels per resolution element.



   .. py:method:: n_pix_spread(wavelength_min_nm, wavelength_max_nm)
      :abstractmethod:


      Detector pixels a single spaxel spectrum spans across a band.



   .. py:method:: throughput(wavelength_nm)
      :abstractmethod:


      Disperser optical throughput in [0, 1] at the given wavelength.



.. py:class:: LensletDisperser

   Bases: :py:obj:`AbstractDisperser`


   Lenslet-array IFS disperser (CRISPY heritage).

   Config only. The render geometry (IR build) is performed by coronachrome,
   which reads these fields. Scalar/ETC methods derive from
   ``dispersion_coeffs`` + ``pix_per_reselt`` so the dispersion model is the
   single source of truth.


   .. py:attribute:: pitch_m
      :type:  float


   .. py:attribute:: pixsize_m
      :type:  float


   .. py:attribute:: angle_rad
      :type:  float


   .. py:attribute:: lam_ref_nm
      :type:  float


   .. py:attribute:: pix_per_reselt
      :type:  float


   .. py:attribute:: dispersion_coeffs
      :type:  jax.Array


   .. py:attribute:: psflet_params
      :type:  jax.Array


   .. py:attribute:: grid_kind
      :type:  str


   .. py:attribute:: n_lenslets
      :type:  int


   .. py:attribute:: psflet_kind
      :type:  str


   .. py:attribute:: detector_shape
      :type:  tuple[int, int]


   .. py:attribute:: throughput_value
      :type:  float
      :value: 1.0



   .. py:method:: _dispersion_px(wavelength_nm)

      Spectral-axis detector offset [px] for the wavelength(s).



   .. py:method:: spectral_resolution(wavelength_nm)

      R = (local px per unit log-lambda) / pixels-per-resolution-element.



   .. py:method:: spectral_sampling()

      Detector pixels per resolution element.



   .. py:method:: n_pix_spread(wavelength_min_nm, wavelength_max_nm)

      Spectral trace length [px] across a band, plus a PSFlet-width margin.



   .. py:method:: throughput(wavelength_nm)

      Constant throughput in v1.



.. py:class:: ExposureConfig

   Bases: :py:obj:`equinox.Module`


   The physical parameters defining a single detector integration.

   All fields can be scalars (for a single event) or vectors (for a
   sequence), depending on how the factories are composed.


   .. py:attribute:: start_time_jd
      :type:  jax.numpy.ndarray


   .. py:attribute:: exposure_time_s
      :type:  jax.numpy.ndarray


   .. py:attribute:: central_wavelength_nm
      :type:  jax.numpy.ndarray


   .. py:attribute:: bin_width_nm
      :type:  jax.numpy.ndarray


   .. py:attribute:: position_angle_deg
      :type:  jax.numpy.ndarray


   .. py:method:: in_axes(**vectorized_axes)
      :classmethod:


      Helper to generate in_axes structure for JAX vmap over an ExposureConfig.

      Usage:
          # Vectorize over wavelength (axis 0), keep time constant
          in_axes = ExposureConfig.in_axes(central_wavelength_nm=0, bin_width_nm=0)



.. py:class:: AbstractOpticalElement

   Bases: :py:obj:`equinox.Module`


   Abstract interface for an optical element in the beam path.

   Elements reduce photon flux via wavelength-dependent throughput.
   The ETC calls get_throughput() for scalar efficiency calculations.
   The simulator calls apply() to attenuate 2D photon arrays.

   Both methods are abstract: use AbstractUniformElement for elements
   with spatially uniform throughput, which provides a default apply().


   .. py:method:: get_throughput(wavelength_nm)
      :abstractmethod:


      Fractional throughput at a given wavelength.

      Args:
          wavelength_nm: Wavelength in nanometres.

      Returns:
          Scalar throughput in [0, 1].



   .. py:method:: apply(arr, wavelength_nm)
      :abstractmethod:


      Apply this element to a 2D photon array.

      Args:
          arr: Input photon rate array [ph/s/pixel].
          wavelength_nm: Wavelength in nanometres.

      Returns:
          Attenuated photon rate array, same shape as arr.



.. py:class:: AbstractUniformElement

   Bases: :py:obj:`AbstractOpticalElement`


   Base for elements with spatially uniform throughput.

   Provides a default apply() that multiplies the array by the scalar
   throughput. Override apply() for elements with spatially varying
   transmission (e.g., field-dependent filter transmission maps).


   .. py:method:: apply(arr, wavelength_nm)

      Apply uniform throughput to a photon array.



.. py:class:: ConstantThroughput

   Bases: :py:obj:`AbstractUniformElement`


   An optical element with wavelength-independent throughput.

   Useful for modeling simple attenuators, beamsplitters, or as a
   placeholder during instrument design studies.


   .. py:attribute:: throughput
      :type:  float


   .. py:attribute:: name
      :type:  str


   .. py:method:: get_throughput(wavelength_nm)

      Return constant throughput, ignoring wavelength.



   .. py:method:: __repr__()

      One-line summary of throughput value.



.. py:class:: SpectralThroughput(wavelengths_nm, throughputs)

   Bases: :py:obj:`AbstractUniformElement`


   Wavelength-dependent throughput defined by sampled (wavelength, throughput) pairs.

   Linear interpolation between samples; throughput is zero outside
   the defined wavelength range.

   Represents any tabulated wavelength-dependent throughput in the
   optical path: bandpass filters, dichroics, mirror reflectivity,
   coating losses, ADCs, blocking filters, etc.


   .. py:attribute:: wavelengths_nm
      :type:  jaxtyping.Array


   .. py:attribute:: throughputs
      :type:  jaxtyping.Array


   .. py:attribute:: interp
      :type:  interpax.Interpolator1D


   .. py:method:: get_throughput(wavelength_nm)

      Interpolate throughput at the requested wavelength.



   .. py:method:: __repr__()

      One-line summary of throughput-table extent and sample count.



.. py:class:: OpticalPath

   Bases: :py:obj:`equinox.Module`


   Universal hardware container for a coronagraphic telescope.

   Bundles a primary mirror, ordered chain of attenuating elements,
   a coronagraph, and a detector into a single configuration object.
   This is the interface passed to simulators (coronagraphoto),
   exposure time calculators (jaxEDITH), and IFS instruments (coronachrome).

   Args:
       primary: Primary mirror description.
       attenuating_elements: Ordered tuple of throughput elements
           between the primary and coronagraph (mirrors, filters, etc.).
       coronagraph: Coronagraph performance model.
       detector: Focal-plane detector model.
       disperser: Optional IFS disperser descriptor; None for imaging mode.
       n_channels: Number of parallel identical optical-path copies
           (AYO shorthand, multiplicative factor on count rates;
           not a spectral channel count). Default 1.0.
       npix_multiplier: IFS signal-spread multiplier on detector pixel
           counts. Default 1.0.


   .. py:attribute:: primary
      :type:  optixstuff.primary.AbstractPrimary


   .. py:attribute:: attenuating_elements
      :type:  tuple[optixstuff.optical_elements.AbstractOpticalElement, Ellipsis]


   .. py:attribute:: coronagraph
      :type:  optixstuff.coronagraph.AbstractCoronagraph


   .. py:attribute:: detector
      :type:  optixstuff.detector.AbstractDetector


   .. py:attribute:: disperser
      :type:  optixstuff.disperser.AbstractDisperser | None
      :value: None



   .. py:attribute:: n_channels
      :type:  float
      :value: 1.0



   .. py:attribute:: npix_multiplier
      :type:  float
      :value: 1.0



   .. py:method:: from_default_setup(coronagraph, *, diameter_m = 6.0, obscuration = 0.0, attenuating_throughput = 1.0, detector_shape = (512, 512), pixel_scale_arcsec = 0.01, quantum_efficiency = 0.9, dark_current_rate_e_per_s = 0.0, n_channels = 1.0, npix_multiplier = 1.0)
      :classmethod:


      Build an OpticalPath with reasonable HWO-like defaults.

      Convenience for notebook / dev-script work: spin up a working
      ``OpticalPath`` by specifying only the coronagraph. All other
      parameters get sensible defaults that can be overridden.

      Args:
          coronagraph: One of:
              - an :class:`AbstractCoronagraph` instance (used as-is),
              - a YIP path (str or :class:`pathlib.Path`, wrapped with
                :class:`YippyCoronagraph`),
              - a ``yippy.EqxCoronagraph`` instance (wrapped via
                ``YippyCoronagraph(backend=...)`` so callers can keep
                using existing yippy code without rebuilding).
          diameter_m: Primary mirror diameter [m]. Default ``6.0`` (HWO
              EAC1 baseline).
          obscuration: Linear central-obscuration fraction. Default 0.
          attenuating_throughput: Combined throughput of the optical
              chain (one :class:`ConstantThroughput`). Default
              ``1.0`` -- a perfect path; override for realistic studies.
          detector_shape: Detector ``(ny, nx)`` in pixels. Default
              ``(512, 512)``.
          pixel_scale_arcsec: Detector plate scale [arcsec/px]. Default
              ``0.01``.
          quantum_efficiency: Default ``0.9``.
          dark_current_rate_e_per_s: Default ``0.0`` e-/s/px (perfect detector;
              callers add realistic noise when needed).
          n_channels: AYO parallel-path multiplier. Default ``1.0``.
          npix_multiplier: IFS signal-spread multiplier. Default ``1.0``.

      Returns:
          A ready-to-use :class:`OpticalPath`.



   .. py:method:: system_throughput(wavelength_nm)

      Total throughput of all attenuating elements.

      Args:
          wavelength_nm: Wavelength in nanometres.

      Returns:
          Combined fractional throughput in [0, 1].



   .. py:method:: __repr__()

      Tree-shaped summary of every component.



.. py:class:: AbstractPrimary

   Bases: :py:obj:`equinox.Module`


   Abstract interface for a primary aperture.

   Any concrete implementation must provide the diameter and collecting
   area of the primary mirror as scalar values in SI units. These are
   consumed by exposure time calculators and simulation tools alike.


   .. py:attribute:: diameter_m
      :type:  equinox.AbstractVar[float]

      Primary mirror diameter in metres.



   .. py:attribute:: area_m2
      :type:  equinox.AbstractVar[float]

      Effective collecting area in square metres.



.. py:class:: SimplePrimary(diameter_m, obscuration = 0.0, shape_factor = 1.0)

   Bases: :py:obj:`AbstractPrimary`


   A simple circular primary mirror with a central obscuration.

   Args:
       diameter_m: Primary mirror diameter in metres.
       obscuration: Linear obscuration fraction (0 = no obscuration).
       shape_factor: Fraction of unobscured area that is collecting
           (accounts for struts, segment gaps, etc.). Default 1.0.


   .. py:attribute:: _diameter_m
      :type:  float


   .. py:attribute:: obscuration
      :type:  float


   .. py:attribute:: shape_factor
      :type:  float


   .. py:property:: diameter_m
      :type: float


      Primary mirror diameter in metres.



   .. py:property:: area_m2
      :type: float


      Effective collecting area in square metres.



   .. py:method:: __repr__()

      One-line summary of diameter, obscuration, and effective area.



.. py:class:: YippyCoronagraph(yip_path = None, *, backend = None, **kwargs)

   Bases: :py:obj:`optixstuff.coronagraph.AbstractCoronagraph`


   Coronagraph performance model backed by a yippy YIP interpolation table.

   Wraps a yippy ``EqxCoronagraph`` via composition, adapting its methods
   to the ``AbstractCoronagraph`` interface.  The ``_backend`` field is
   itself an ``eqx.Module``, so its internal JAX arrays flow through
   ``filter_jit`` and ``filter_grad`` normally.

   Construction mirrors ``EqxCoronagraph`` -- pass either a YIP path or
   an existing ``EqxCoronagraph`` instance::

       coro = YippyCoronagraph("/path/to/yip")
       coro = YippyCoronagraph(backend=existing_eqx_coro)


   .. py:attribute:: _backend
      :type:  yippy.EqxCoronagraph


   .. py:property:: pixel_scale_lod
      :type: float


      Native pixel scale in lambda/D per pixel.



   .. py:property:: IWA
      :type: float


      Inner working angle in lambda/D.



   .. py:property:: OWA
      :type: float


      Outer working angle in lambda/D.



   .. py:method:: throughput(separation_lod, wavelength_nm, *, time_s = 0.0)

      Core throughput from the YIP interpolation table.



   .. py:method:: core_area(separation_lod, wavelength_nm, *, time_s = 0.0)

      Photometric aperture area from the YIP interpolation table.



   .. py:method:: core_mean_intensity(separation_lod, wavelength_nm, *, time_s = 0.0)

      Mean stellar leakage from the YIP interpolation table.



   .. py:method:: occulter_transmission(separation_lod, wavelength_nm, *, time_s = 0.0)

      Sky transmission from the YIP interpolation table.



   .. py:method:: on_axis_psf(wavelength_nm, pixel_scale_rad, npixels)

      Stellar leakage PSF from the YIP stellar intensity model.



   .. py:method:: off_axis_psf(wavelength_nm, separation_lod, pixel_scale_rad, npixels)

      Off-axis planet PSF from the YIP PSF interpolator.

      Places the planet along the +x axis by convention.



   .. py:method:: noise_floor_ayo(separation_lod, ppf = 30.0)

      AYO noise floor: core_mean_intensity / ppf.

      This is a convenience passthrough to the backend. Not part of the
      AbstractCoronagraph contract -- downstream ETCs should compute
      noise floors as pure functions.



   .. py:method:: raw_contrast(separation_lod)

      Raw contrast from the YIP interpolation table.



   .. py:method:: stellar_intens(stellar_diam_lod)

      Stellar intensity map for a given stellar angular diameter.



   .. py:property:: psf_shape
      :type: tuple[int, int]


      Shape of the PSF arrays from the YIP file.



   .. py:property:: sky_trans
      :type: jaxtyping.Array


      Full sky transmission map.



   .. py:method:: create_psfs(x_lod, y_lod)

      Batched off-axis PSFs at (x_lod, y_lod) source positions.

      Delegates to the backend yippy create_psfs closure. Returns
      a stack of PSF images, one per input source coordinate.

      Args:
          x_lod: Source x-coordinates in lambda/D, shape (K,).
          y_lod: Source y-coordinates in lambda/D, shape (K,).

      Returns:
          PSF stack of shape (K, ny, nx) where (ny, nx) == self.psf_shape.



   .. py:property:: psf_datacube
      :type: jaxtyping.Array | None


      Pre-computed quarter-symmetric PSF datacube from the backend.

      Returns None if the backing EqxCoronagraph was not built
      with ensure_psf_datacube=True. Consumers that need this for
      disk convolution should construct the backend with the flag set.



   .. py:method:: __repr__()

      One-line summary of YIP backend metadata.



