Instrument

Base Classes and Calibration Tokens

Abstract base class for dispersers and calibration parameter tokens.

class unite.instrument.base.RScale(name=None, *, prior=None)[source]

Bases: Parameter

Multiplicative scale on the disperser resolving power R.

Nominal value is 1 (no correction). The model applies R_eff(λ) = R_nominal(λ) * r_scale.

Parameters:
namestr, optional

Human-readable label. When attached to a Disperser, the site name is auto-derived as 'r_scale_{disperser_name}' if not provided.

priorPrior, optional

Prior on the R scale factor. Defaults to Fixed(1.0) (fixed at nominal).

Parameters:
  • name (str | None)

  • prior (Prior | None)

class unite.instrument.base.FluxScale(name=None, *, prior=None)[source]

Bases: Parameter

Multiplicative flux normalisation between dispersers.

Nominal value is 1. The model divides observed flux by flux_scale, allowing relative flux calibration across multiple gratings.

Parameters:
namestr, optional

Human-readable label. When attached to a Disperser, the site name is auto-derived as 'flux_scale_{disperser_name}' if not provided.

priorPrior, optional

Prior on the flux scale factor. Defaults to Fixed(1.0) (fixed at nominal).

Parameters:
  • name (str | None)

  • prior (Prior | None)

class unite.instrument.base.PixOffset(name=None, *, prior=None)[source]

Bases: Parameter

Pixel displacement of the spectrum on the detector relative to the wavelength calibration.

Nominal value is 0 (no displacement). A positive value indicates the spectrum is displaced toward longer wavelengths on the detector: the model subtracts pix_offset * dlam_dpix from the calibrated pixel-edge wavelengths, shifting them blueward to compensate.

For example, if a disperser consistently returns redshifts that are too high compared to a reference, the spectrum is displaced redward by some number of pixels — set pix_offset to that (positive) number to correct the wavelength solution.

Parameters:
namestr, optional

Human-readable label. When attached to a Disperser, the site name is auto-derived as 'pix_offset_{disperser_name}' if not provided.

priorPrior, optional

Prior on the pixel offset. Defaults to Fixed(0.0) (no displacement).

Parameters:
  • name (str | None)

  • prior (Prior | None)

class unite.instrument.base.Disperser(unit, *, name='', r_scale=None, flux_scale=None, pix_offset=None)[source]

Bases: ABC

Abstract base class for dispersive optical elements.

A disperser maps wavelength coordinates to instrumental quantities such as resolving power and plate scale. Every concrete subclass must implement R() and dlam_dpix(), and must carry a unit attribute that records the wavelength unit the disperser expects.

Calibration is encoded by optional tokens (RScale, FluxScale, PixOffset):

  • r_scale — multiplicative scale on resolving power (R).

  • flux_scale — multiplicative flux normalisation.

  • pix_offset — pixel displacement of the spectrum on the detector (positive = redward; wavelength grid is corrected blueward).

None on any slot means that parameter is absent from the model entirely (equivalent to a fixed nominal value but without a token). To create a token that is fixed at its nominal value, pass e.g. r_scale=RScale() — the default prior is Fixed(1.0).

Sharing: two dispersers that reference the same token instance share a single parameter in the model (identity-based, like FWHM/ Redshift on lines).

Parameters:
unitastropy.units.UnitBase

The wavelength unit that this disperser operates in (e.g. u.Angstrom, u.nm, u.um). All wavelength values passed to R() and dlam_dpix() are assumed to be in this unit.

namestr, optional

Human-readable label (e.g. 'G235H'). Used in repr and as the default Spectrum.name when this disperser is attached.

r_scaleRScale, optional

Token for the resolving-power scale. None → not in model.

flux_scaleFluxScale, optional

Token for the flux normalisation. None → not in model.

pix_offsetPixOffset, optional

Token for the detector pixel displacement. None → not in model.

Parameters:
abstractmethod R(wavelength)[source]

Return the resolving power at the given wavelengths.

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:
wavelengthArrayLike

Wavelength values in the unit specified by self.unit.

Returns:
ArrayLike

Resolving power R = λ / Δλ evaluated at each wavelength.

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)

abstractmethod dlam_dpix(wavelength)[source]

Return the linear dispersion (wavelength per pixel).

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:
wavelengthArrayLike

Wavelength values in the unit specified by self.unit.

Returns:
ArrayLike

Dispersion dλ/dpix evaluated at each wavelength.

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)

property has_calibration_params: bool

True if any calibration token is attached.

Instrument Configuration

Instrument configuration: disperser models with calibration tokens.

InstrumentConfig collects one Disperser per observing configuration. Each disperser carries optional RScale / FluxScale / PixOffset tokens (r_scale, flux_scale, pix_offset) for shared calibration parameters.

Sharing tokens

Pass the same token instance to multiple dispersers to share a single parameter in the fitted model:

from unite.instrument import InstrumentConfig, RScale
from unite.instrument.nirspec import G235H, G395H
from unite.prior import TruncatedNormal

r = RScale(prior=TruncatedNormal(1.0, 0.05, 0.8, 1.2))
cfg = InstrumentConfig([
    G235H(r_scale=r),
    G395H(r_scale=r),   # same r — shared parameter
])

Degeneracy warning

A multi-disperser fit is only identified if at least one disperser has flux_scale=None (flux anchor) and at least one has pix_offset=None (pixel-offset anchor). validate() issues UserWarning when these conditions are not met. Validation is called automatically when this object is passed to Configuration.

Serialization

to_dict() hoists all unique calibration tokens to a top-level calib_params section keyed by token name. Disperser entries reference tokens by name. Shared tokens round-trip correctly — the same object is reconstructed for both entries.

Examples

>>> from unite.instrument import InstrumentConfig, RScale, FluxScale
>>> from unite.instrument.nirspec import G235H, G395H
>>> from unite.prior import TruncatedNormal
>>> r = RScale(prior=TruncatedNormal(1.0, 0.05, 0.8, 1.2))
>>> flux_0 = FluxScale(prior=TruncatedNormal(1.0, 0.1, 0.5, 2.0))
>>> cfg = InstrumentConfig([
...     G235H(r_scale=r),
...     G395H(r_scale=r, flux_scale=flux_0),
... ])
>>> cfg.names
['G235H', 'G395H']
class unite.instrument.config.InstrumentConfig(dispersers)[source]

Bases: object

Configuration for a multi-disperser spectral dataset.

An ordered collection of Disperser objects, one per observing disperser. Each disperser carries optional CalibParam tokens for calibration parameters.

The configuration is data-free: it describes which dispersers are used and how they are calibrated. Actual spectral data arrays are attached later via make_spectrum().

Parameters:
disperserssequence of Disperser

One entry per disperser. Names (disperser.name) must be unique.

Raises:
ValueError

If any disperser names are duplicated or empty.

Parameters:

dispersers (Sequence[Disperser])

validate()[source]

Check for flux and pixel-offset degeneracies.

Issues a UserWarning for each calibration axis (flux, dispersion) where no disperser is anchored (token None).

Return type:

None

property names: list[str]

Names of all dispersers in this configuration.

to_dict()[source]

Serialize to a YAML-safe dictionary.

Shared CalibParam tokens are hoisted to a top-level calib_params section. Disperser entries reference tokens by name, so sharing is preserved on round-trip.

Return type:

dict

Returns:
dict

Contains 'calib_params' and 'entries' keys.

classmethod from_dict(d)[source]

Deserialize from a dictionary.

Return type:

InstrumentConfig

Parameters:
ddict

As produced by to_dict().

Returns:
InstrumentConfig
Parameters:

d (dict)

to_yaml()[source]

Serialize to a YAML string.

Return type:

str

Returns:
str
classmethod from_yaml(text)[source]

Deserialize from a YAML string.

Return type:

InstrumentConfig

Parameters:
textstr

YAML string as produced by to_yaml().

Returns:
InstrumentConfig
Parameters:

text (str)

save(path)[source]

Save to a YAML file.

Return type:

None

Parameters:
pathstr or Path

Output file path.

Parameters:

path (str | Path)

classmethod load(path)[source]

Load from a YAML file.

Return type:

InstrumentConfig

Parameters:
pathstr or Path

Path to a YAML file written by save().

Returns:
InstrumentConfig
Parameters:

path (str | Path)

Generic Implementations

Generic disperser implementations.

These are the building blocks for custom instruments.

Import from this module directly:

from unite.instrument.generic import GenericDisperser, SimpleDisperser

The Spectrum class lives in unite.spectrum.

class unite.instrument.generic.GenericDisperser(R_func, dlam_dpix_func, unit, *, name='', r_scale=None, flux_scale=None, pix_offset=None)[source]

Bases: Disperser

A disperser defined by user-supplied, JAX-jittable callables.

This is the most flexible concrete disperser: you provide arbitrary functions for R(λ) and dλ/dpix(λ) and they are forwarded directly.

Parameters:
R_funcCallable[[ArrayLike], ArrayLike]

A JAX-jittable function that returns the resolving power for a given array of wavelengths.

dlam_dpix_funcCallable[[ArrayLike], ArrayLike]

A JAX-jittable function that returns the linear dispersion for a given array of wavelengths.

unitastropy.units.UnitBase

The wavelength unit the functions expect.

namestr, optional

Human-readable label.

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the pixel shift.

Parameters:
  • R_func (Callable[[ArrayLike], ArrayLike])

  • dlam_dpix_func (Callable[[ArrayLike], ArrayLike])

  • unit (u.UnitBase)

  • name (str)

  • r_scale (RScale | None)

  • flux_scale (FluxScale | None)

  • pix_offset (PixOffset | None)

Examples

>>> import jax.numpy as jnp
>>> from astropy import units as u
>>> d = GenericDisperser(
...     R_func=lambda w: jnp.full_like(w, 2700.0),
...     dlam_dpix_func=lambda w: w / 2700.0,
...     unit=u.Angstrom,
... )
>>> d.R(jnp.array([5000.0]))
Array([2700.], dtype=float32)
R(wavelength)[source]

Return the resolving power by evaluating the stored callable.

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)

dlam_dpix(wavelength)[source]

Return the linear dispersion by evaluating the stored callable.

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)

class unite.instrument.generic.SimpleDisperser(wavelength, *, R=None, dlam=None, dvel=None, name='', r_scale=None, flux_scale=None, pix_offset=None)[source]

Bases: Disperser

A disperser defined on a pixel-sampled wavelength grid.

The wavelength array is interpreted as a sequence of pixel centers so that dλ/dpix is computed directly from the spacing of the array (via jnp.gradient).

The resolving power is derived from exactly one of three keyword arguments:

  • R — resolving power, scalar or array matching wavelength.

  • dlam — spectral resolution element Δλ (same unit as wavelength).

  • dvel — velocity fwhm in km/s. Converted via R = c / dvel.

A scalar value produces a constant R, Δλ, or Δv across the grid (and the corresponding resolving-power array is derived accordingly). An array value must have the same length as wavelength.

Parameters:
wavelengthu.Quantity

Pixel-center wavelengths. Must be 1-D.

RArrayLike, optional

Resolving power (scalar or per-pixel array).

dlamu.Quantity, optional

Spectral resolution Δλ, must be in wavelength units (scalar or per-pixel array)

dvelu.Quantity, optional

Velocity resolution in velocity units (scalar or per-pixel array).

namestr, optional

Human-readable label.

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the pixel shift.

Raises:
ValueError

If zero or more than one of R, dlam, dvel is provided, or if an array argument has the wrong length.

Parameters:
  • wavelength (u.Quantity)

  • R (ArrayLike | None)

  • dlam (u.Quantity | None)

  • dvel (u.Quantity | None)

  • name (str)

  • r_scale (RScale | None)

  • flux_scale (FluxScale | None)

  • pix_offset (PixOffset | None)

Notes

When R() or dlam_dpix() is called at wavelengths that differ from the stored grid, the values are linearly interpolated with jnp.interp.

R(wavelength)[source]

Return the resolving power, interpolated onto wavelength.

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)

dlam_dpix(wavelength)[source]

Return the linear dispersion, interpolated onto wavelength.

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)

NIRSpec Dispersers

JWST NIRSpec disperser implementations.

This module provides NIRSpecDisperser, a concrete Disperser for every NIRSpec grating/prism configuration. Two resolving-power calibrations are available:

  • "uniform" or "uniformly-illuminated" — the official JDOX tabulated R(λ) curves shipped with the package as FITS files. This calibration is for uniformly illuminated sources.

  • "point" or "point-source" — polynomial R(λ) fits from de Graaff et al. (2025). This calibration is tailored for point-source observations.

Both calibrations share the same JDOX linear-dispersion (dλ/dpix) tables.

In addition to the generic NIRSpecDisperser class, convenience subclasses are provided for each grating:

These fix the grating name while still accepting r_source and calibration token kwargs.

class unite.instrument.nirspec.disperser.G140H(r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)

Bases: NIRSpecDisperser

NIRSpec G140H disperser.

Convenience subclass of NIRSpecDisperser with the grating fixed to "g140h". Only r_source and calibration tokens need to be specified.

Parameters:
r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use (default "point").

namestr, optional

Human-readable label. Defaults to "G140H".

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Examples

>>> d = G140H()
>>> d.grating
'g140h'
class unite.instrument.nirspec.disperser.G140M(r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)

Bases: NIRSpecDisperser

NIRSpec G140M disperser.

Convenience subclass of NIRSpecDisperser with the grating fixed to "g140m". Only r_source and calibration tokens need to be specified.

Parameters:
r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use (default "point").

namestr, optional

Human-readable label. Defaults to "G140M".

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Examples

>>> d = G140M()
>>> d.grating
'g140m'
class unite.instrument.nirspec.disperser.G235H(r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)

Bases: NIRSpecDisperser

NIRSpec G235H disperser.

Convenience subclass of NIRSpecDisperser with the grating fixed to "g235h". Only r_source and calibration tokens need to be specified.

Parameters:
r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use (default "point").

namestr, optional

Human-readable label. Defaults to "G235H".

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Examples

>>> d = G235H()
>>> d.grating
'g235h'
class unite.instrument.nirspec.disperser.G235M(r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)

Bases: NIRSpecDisperser

NIRSpec G235M disperser.

Convenience subclass of NIRSpecDisperser with the grating fixed to "g235m". Only r_source and calibration tokens need to be specified.

Parameters:
r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use (default "point").

namestr, optional

Human-readable label. Defaults to "G235M".

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Examples

>>> d = G235M()
>>> d.grating
'g235m'
class unite.instrument.nirspec.disperser.G395H(r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)

Bases: NIRSpecDisperser

NIRSpec G395H disperser.

Convenience subclass of NIRSpecDisperser with the grating fixed to "g395h". Only r_source and calibration tokens need to be specified.

Parameters:
r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use (default "point").

namestr, optional

Human-readable label. Defaults to "G395H".

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Examples

>>> d = G395H()
>>> d.grating
'g395h'
class unite.instrument.nirspec.disperser.G395M(r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)

Bases: NIRSpecDisperser

NIRSpec G395M disperser.

Convenience subclass of NIRSpecDisperser with the grating fixed to "g395m". Only r_source and calibration tokens need to be specified.

Parameters:
r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use (default "point").

namestr, optional

Human-readable label. Defaults to "G395M".

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Examples

>>> d = G395M()
>>> d.grating
'g395m'
class unite.instrument.nirspec.disperser.PRISM(r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)

Bases: NIRSpecDisperser

NIRSpec PRISM disperser.

Convenience subclass of NIRSpecDisperser with the grating fixed to "prism". Only r_source and calibration tokens need to be specified.

Parameters:
r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use (default "point").

namestr, optional

Human-readable label. Defaults to "PRISM".

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Examples

>>> d = PRISM()
>>> d.grating
'prism'
class unite.instrument.nirspec.disperser.NIRSpecDisperser(grating, r_source='point', *, name='', r_scale=None, flux_scale=None, pix_offset=None)[source]

Bases: Disperser

Disperser for a JWST NIRSpec grating or prism configuration.

Parameters:
gratingstr

NIRSpec grating name. One of "prism", "g140m", "g140h", "g235m", "g235h", "g395m", "g395h" (case-insensitive).

r_source"uniform", "uniformly-illuminated", "point", or "point-source", optional

Which resolving-power calibration to use. "uniform" or "uniformly-illuminated" interpolates the tabulated JDOX R(λ) curve for uniformly illuminated sources; "point" or "point-source" (default) evaluates the polynomial fit from de Graaff et al. (2025), which is tailored for point-source observations.

namestr, optional

Human-readable label. Defaults to the grating name (upper-case).

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the detector pixel displacement.

Parameters:

Notes

The linear dispersion dλ/dpix is always taken from the JDOX dispersion tables regardless of the r_source choice.

All wavelengths are in microns (astropy.units.um).

Examples

>>> d = NIRSpecDisperser("g235h")
>>> d.grating
'g235h'
>>> d.r_source
'point'
>>> d_uniform = NIRSpecDisperser("prism", r_source="uniform")
>>> d_uniform.r_source
'uniform'
R(wavelength)[source]

Return the resolving power at the given wavelengths (µm).

dlam_dpix(wavelength)[source]

Return dλ/dpix in µm/pixel, interpolated from the JDOX table.

SDSS Disperser

SDSS spectrograph disperser implementation.

Provides SDSSDisperser, a concrete Disperser for SDSS optical spectra. The resolving power R(λ) is derived from the wdisp column (wavelength dispersion per pixel, in Angstroms) of standard SDSS spec-*.fits files at load time via from_sdss_fits().

Before data is loaded, the disperser can be constructed with default or placeholder R values, allowing calibration tokens to be configured and the disperser configuration to be serialized.

Examples

>>> from unite.instrument.sdss import SDSSDisperser
>>> d = SDSSDisperser()
>>> d.unit
Unit("Angstrom")
class unite.instrument.sdss.disperser.SDSSDisperser(name='', r_scale=None, flux_scale=None, pix_offset=None)[source]

Bases: Disperser

Disperser for SDSS optical spectra.

The SDSS spectrographs use a log-linear wavelength grid. The resolving power and linear dispersion are set from data at spectrum load time (from_sdss_fits()), but the disperser can also be pre-constructed with calibration tokens for serialization.

Parameters:
wavelengthArrayLike, optional

Pixel-center wavelengths in Angstroms. When not provided, R() and dlam_dpix() return placeholder values (constant R=2000, dlam_dpix=1.0).

wdispArrayLike, optional

Wavelength dispersion per pixel (Angstroms/pixel) from the SDSS wdisp column. Same length as wavelength. The resolving power is computed as R(λ) = λ / (2.355 * wdisp) (since wdisp is sigma in wavelength units per pixel, and FWHM = 2.355 * sigma).

namestr, optional

Human-readable label. Defaults to 'SDSS'.

r_scaleRScale, optional

Token for the resolving-power scale.

flux_scaleFluxScale, optional

Token for the flux normalisation.

pix_offsetPixOffset, optional

Token for the pixel shift.

Parameters:
R(wavelength)[source]

Return the resolving power at the given wavelengths (Angstrom).

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:
wavelengthArrayLike

Wavelength values in Angstroms.

Returns:
ArrayLike

Resolving power R at each wavelength.

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)

dlam_dpix(wavelength)[source]

Return dλ/dpix in Angstrom/pixel.

Return type:

Array | ndarray | bool | number | bool | int | float | complex

Parameters:
wavelengthArrayLike

Wavelength values in Angstroms.

Returns:
ArrayLike

Linear dispersion at each wavelength.

Parameters:

wavelength (Array | ndarray | bool | number | bool | int | float | complex)