Continuum Configuration

ContinuumConfiguration defines wavelength regions where the continuum is modeled, and attaches a functional form to each region. The continuum is evaluated at pixel centers (not integrated), since it varies slowly enough that sub-pixel variation is negligible. Continua are also not convolved with the LSF.


Creating a Continuum Configuration

Automatic from Line Centers

The easiest approach is the ContinuumConfiguration.from_lines class method, which automatically creates regions around each set of line centers, merges overlapping regions, and assigns the same functional form to every region:

from unite.continuum import ContinuumConfiguration, Linear
from astropy import units as u

lc = LineConfiguration(...)
cc = ContinuumConfiguration.from_lines(
    lc.centers, # Or any array of wavelenths definign cents.
    width=30_000 * u.km / u.s,  # Total width of each region in velocity units, default is 30,000 km/s
    form=Linear(),  # Functional for in each region, default is Linear which takes no arguments
)

Manual Regions

For full control, define regions explicitly:

from unite.continuum import ContinuumConfiguration, ContinuumRegion, Linear, PowerLaw

regions = [
    ContinuumRegion(6400 * u.AA, 6700 * u.AA, form=Linear()),
    ContinuumRegion(4800 * u.AA, 5100 * u.AA, form=PowerLaw()),
]
cc = ContinuumConfiguration(regions)

Regions can be given an optional name which is used as the suffix for auto-created parameter tokens (e.g. scale_blue, beta_blue). Region names must be unique within a configuration.

regions = [
    ContinuumRegion(6400 * u.AA, 6700 * u.AA, form=Linear(), name='red'),
    ContinuumRegion(4800 * u.AA, 5100 * u.AA, form=PowerLaw(), name='blue'),
]
# → auto-created params: 'scale_red', 'angle_red', 'scale_blue', 'beta_blue', …

You can also pass the form as a string instead of an instance — it will be resolved from the built-in registry:

regions = [
    ContinuumRegion(6400 * u.AA, 6700 * u.AA, form='Linear'),
    ContinuumRegion(4800 * u.AA, 5100 * u.AA, form='PowerLaw'),
]

For programmatic access to the registry, use get_form():

from unite.continuum import get_form

form = get_form('Polynomial', degree=3)

Combining Continuum Configurations

Two Continuum Configurations can be combined with the + operator. This is useful for building up a configuration iteratively, or for merging configurations defined in separate YAML files.

cc1 = ContinuumConfiguration.load('continuum1.yaml')
cc2 = ContinuumConfiguration.load('continuum2.yaml')
cc_combined = cc1 + cc2

Continuum Parameters and Priors

Each continuum region must be specified with the low and high wavelength bounds (as astropy.units.Quantity), and a functional form.

Each continuum form generates model parameters with default priors. When a region is added to a ContinuumConfiguration, any parameter not explicitly provided receives an auto-created token with the form’s default prior.

As with lines parameters, a shared token is the same parameter in the model. These are the following token types:

Token

Role

Unit

NormWavelength

Rest-wavelength at which continuum is scaled

same unit as low bound

Scale

Continuum height at normalization wavelength

internal units

ContShape

Arbitrary profile parameter (β, angle, etc.)

per-parameter basis

The normalization wavelength for each region defaults to the region midpoint (as a Fixed prior).

Note

Scales are relative to a scale computed based on the spectrum. See Flux and Error Scales in Building the Model for details.

Custom Priors on Continuum Parameters

Similar to line parameters, you can override the default priors by passing a dict of continuum tokens.

from unite import continuum
from unite.prior import TruncatedNormal, Uniform, Fixed
from astropy import units as u

region = continuum.ContinuumRegion(
    1.0 * u.um, 1.5 * u.um,
    form=continuum.PowerLaw(),
    params={
        'scale': continuum.Scale('pl', prior=TruncatedNormal(low = 0, high = 2.0, loc=1, scale=0.1)),
        'beta': continuum.ContShape('pl', prior=Uniform(-3.0, 0.0)),
        # 'norm_wav' keeps its default Fixed(region_center) prior
    },
)
cc = continuum.ContinuumConfiguration([region])

Invalid parameter names are caught when assembling the configuration:

region = continuum.ContinuumRegion(
    1.0 * u.um, 2.0 * u.um,
    form=continuum.Linear(),
    params={'amplitude': continuum.Scale('amp', prior=Uniform(0, 10))},
)
cc = continuum.ContinuumConfiguration([region])
# Raises ValueError — Linear has no 'amplitude' parameter

Parameter Naming

Each continuum parameter token accepts a semantic label as its first argument. A type-specific prefix is automatically prepended to form the final site name:

  • Scale('pl') → site name 'scale_pl' (prefix: scale_)

  • ContShape('pl') → site name 'beta_pl' (prefix: beta_ for PowerLaw, angle_ for Linear, etc.)

  • NormWavelength('pl') → site name 'norm_wav_pl' (prefix: norm_wav_)

Pass only the semantic label, not the full prefixed name. Good examples: 'pl' (power law), 'poly' (polynomial), 'red' (red region), 'uv' (UV region).

Alternatively, use region names for automatic naming. If you don’t provide explicit tokens, the region’s name parameter is used as a suffix:

regions = [
    ContinuumRegion(0.9 * u.um, 1.4 * u.um, form=PowerLaw(), name='red'),
    # Auto-creates tokens: scale_red, beta_red, norm_wav_red
]

Sharing Parameters Across Regions

Pass the same Parameter instance in the params dict of multiple regions. The ContinuumConfiguration detects shared identity and creates a single numpyro site, coupling those regions to one sampled value.

This is essential for fitting a global continuum model (e.g. a single power law) across disjoint spectral windows:

from unite import continuum
from unite.prior import Uniform, Fixed
from astropy import units as u

pl = continuum.PowerLaw()
shared_scale = continuum.Scale('pl', prior=Uniform(0, 10))
shared_beta  = continuum.ContShape('pl', prior=Uniform(-5, 5))
shared_nw    = continuum.NormWavelength('pl', prior=Fixed(1.25))

cc = continuum.ContinuumConfiguration([
    continuum.ContinuumRegion(
        0.9 * u.um, 1.4 * u.um, form=pl,
        params={
            'scale': shared_scale,
            'beta': shared_beta,
            'norm_wav': shared_nw
        },
    ),
    continuum.ContinuumRegion(
        1.7 * u.um, 2.5 * u.um, form=pl,
        params={
            'scale': shared_scale,
            'beta': shared_beta,
            'norm_wav': shared_nw
        },
    ),
])

Continuum Forms

Each region has a functional form that defines how the continuum varies with wavelength. All forms share a unified parameter interface: a norm_wav parameter (the wavelength at which the continuum is normalized), a scale parameter (the normalization of the continuum), and any additional form-specific shape parameters.

Forms have two kinds of configuration:

  • Constructor parameters — set when you create the form (e.g., polynomial degree, knot vector). These are static and define the shape of the functional form.

  • Model parameters — sampled during inference (e.g., scale, angle, temperature). These receive default priors that can be overridden via ContinuumRegion(params={...})

Here are the built-in forms, their constructor parameters, and their model parameters. The LSF column indicates whether analytic LSF convolution is applied when the model is evaluated with the instrument’s line-spread function:

Form

Constructor args

Additional Model parameters

LSF

Linear

angle

Yes (exact)

Polynomial

degree

c1cN

Yes (exact)

Chebyshev

order, stretch

c1cN

Yes (exact)

Bernstein

degree, stretch

c1cN

Yes (exact)

PowerLaw

beta

No

BSpline

knots, degree

c1

No

Blackbody

temperature

No

ModifiedBlackbody

temperature, beta

No

AttenuatedBlackbody

lambda_ext

temperature, tau_ext, alpha

No

Forms marked Yes (exact) are polynomial-based and have their coefficients analytically convolved with the Gaussian LSF before evaluation — a polynomial convolved with a Gaussian is still a polynomial of the same degree, so no extra basis functions are needed. See Polynomial for the derivation of the coefficient transform. Forms marked No ignore the LSF — their curvature is assumed to vary slowly enough that the unconvolved value is a good approximation at the spectral resolution of most instruments. For BSpline, the non-polynomial basis makes analytic convolution impractical; for PowerLaw and the blackbody family, the nonlinear functional forms do not admit closed-form convolution.

Linear

\(f(\lambda) = \text{scale} + \tan(\theta) \times (\lambda - \lambda_\text{norm})\)

from unite.continuum import Linear
form = Linear()

Model parameters: scale, angle.

PowerLaw

\(f(\lambda) = \text{scale} \times (\lambda / \lambda_\text{norm})^\beta\)

from unite.continuum import PowerLaw
form = PowerLaw()

Model parameters: scale, beta.

Polynomial

\(f(\lambda) = \text{scale} + c_1 x + c_2 x^2 + \ldots\) where \(x = \lambda - \lambda_\text{norm}\)

from unite.continuum import Polynomial
form = Polynomial(degree=2)   # quadratic

Constructor parameter: degree (default 1).

Model parameters: scale, c1, c2, …. cn where n is the degree.

Chebyshev

Chebyshev (first-kind) polynomial expansion. Guarantees orthogonality and better numerical stability. The x-coordinate is normalized to \([-1, 1]\) and then scaled by stretch.

Note

Exercise extreme caution when using non-unity stretch, especially less than one. This can lead to large instability outside the nominal range.

from unite.continuum import Chebyshev
form = Chebyshev(order=3, stretch=1)  # half_width in same units as region bounds

Constructor parameters: order (default 2), stretch (default 1.0)

Model parameters: scale, c1, c2, …. cn where n is the degree.

Clamped BSpline

B-spline continuum with a user-defined knot vector. Spline is automatically clamped at the region edges. Therefore all knots should be within the region bounds.

import jax.numpy as jnp
from unite.continuum import BSpline

# Knots (ends are automatically clamped)
knots = jnp.array([4050, 5000] * u.AA)
form = BSpline(knots=knots, degree=3)

Constructor parameters: knots (knot vector), degree (default 3 for cubic).

Model parameters: scale, c1, c2, …. cn where n is the degree.

Bernstein

Bernstein polynomial basis. The x-coordinate is normalised to \([-1, 1]\), scaled by stretch, then shifted to \([0, 1]\) assuming it was still \([-1, 1]\) after stretching. Guaranteed positive when all coefficients are positive.

from unite.continuum import Bernstein
form = Bernstein(degree=4, stretch=1)

Constructor parameters: degree (default 4), stretch (default 1.0, same caveats as Chebyshev).

Model parameters: scale, c1, c2, …. cn where n is the degree.

Blackbody

Planck function normalised at a reference wavelength: \(f(\lambda) = \text{scale} \times B_\lambda(T) / B_{\lambda_\text{norm}}, T)\)

from unite.continuum import Blackbody
form = Blackbody()

Model parameters: scale, temperature.

When fitting a single blackbody across disjoint spectral windows, share a ContinuumNormalizationWavelength token to enforce a consistent reference wavelength — see Sharing Parameters.

ModifiedBlackbody

Blackbody with a power-law modifier: \(f(\lambda) = \text{scale} \times \times B_\lambda(T) / B_{\lambda_\text{norm}}, T) \times (\lambda / \lambda_\text{norm})^\beta\)

from unite.continuum import ModifiedBlackbody
form = ModifiedBlackbody()

Model parameters: scale, temperature, beta.

Setting \(\beta = 0\) recovers a pure Blackbody.

AttenuatedBlackbody

Dust-attenuated blackbody with a power-law extinction curve:
\(f(\lambda) = \text{scale} \times B_\lambda(T) / B_{\lambda_\text{norm}}, T) \times \exp\!\bigl(-\tau_{\text{ext}} [(\lambda/\lambda_{\text{ext}})^\alpha - (\lambda_\text{norm}/\lambda_{\text{ext}})^\alpha]\bigr)\)

Extinction is normalised at norm_wav, so scale is the observed (attenuated) flux there.

from unite.continuum import AttenuatedBlackbody
from astropy import units as u

form = AttenuatedBlackbody()                         # default: lambda_V = 0.55 μm (V band)
form = AttenuatedBlackbody(lambda_ext=4400 * u.AA)  # B Band

Constructor parameter: lambda_ext — extinction reference wavelength (default 0.55 μm). Accepts a bare float (interpreted as microns) or an astropy.units.Quantity with any length unit.

Model parameters: scale, temperature, tau_ext, alpha.


Serialization

ContinuumConfiguration supports standalone YAML serialization:

cc.save('continuum.yaml')
cc2 = ContinuumConfiguration.load('continuum.yaml')

See Configuration Serialization for the full workflow and YAML format examples.