Skip to content

virgil.spectra

Wavelength-dependent fluxes. A component's flux can be a number or a spectrum; inside a System each component is weighted by its spectrum at each sample's wavelength, as in SPARCO.

Wavelength-dependent fluxes for source components.

A component's flux is either a number (achromatic) or a spectrum from this module, which gives its weight at each wavelength. Inside a System the visibility is then V(λ) = Σ f_i(λ) V_i / Σ f_i(λ), as in SPARCO (Kluska et al. 2014):

star = UniformDisk(0.5, flux=PowerLaw(1.0, index=-4.0, wavel0=1.65e-6))
ring = GaussianDisk(5.0, flux=BlackBody(0.3, temperature=1200.0))
wind = GaussianDisk(
    2.0,
    flux=Sum(
        continuum=PowerLaw(0.3, wavel0=2.15e-6),
        brg=GaussianLine(0.5, line_wavel=2.1661e-6, fwhm=1.0e-9),
    ),
)
free = PointSource(flux=Nodes(values, channel_wavelengths))

Spectrum parameters are reached by path like any other, e.g. "ring.flux.temperature" or "wind.flux.brg.amplitude". Flux ratios are relative: component i's fraction of the total at the reference wavelength is f_i / Σ f. Every spectrum has a reference wavelength wavel0, and its reference flux (spectrum(), used when rendering images) is its value there. Only the evaluated flux must be non-negative: a part of a Sum may be negative (absorption) as long as the total is not, which is_physical checks at wavel0, the nodes and the line centres.

BlackBody

Bases: Spectrum

Planck spectrum ratio * B_λ(T, λ) / B_λ(T, wavel0).

A blackbody at temperature in F_λ, normalized to ratio at wavel0, as SPARCO uses for dust and companions (Hillen et al. 2016, https://arxiv.org/abs/1603.03023). At long wavelengths it tends to the Rayleigh-Jeans PowerLaw with index -4.

Parameters:

Name Type Description Default
ratio float or array - like

Flux at the reference wavelength, relative to the other components.

required
temperature float or array - like

Temperature in kelvin.

required
wavel0 float or array - like

Reference wavelength in metres (default 1.65e-6, H band).

1.65e-06

Examples:

>>> round(float(BlackBody(0.2, 1500.0)(1.65e-6)), 6)
0.2

ratio = np.asarray(ratio, dtype=float) instance-attribute

temperature = np.asarray(temperature, dtype=float) instance-attribute

wavel0 = np.asarray(wavel0, dtype=float) instance-attribute

__init__(ratio, temperature, wavel0=1.65e-06)

__call__(wavel=None)

is_physical()

__check_init__()

GaussianLine

Bases: _Line

A Gaussian emission or absorption line.

amplitude * exp(-4 ln 2 ((λ - line_wavel) / fwhm)²), with the full width at half maximum fwhm. On its own a line is the whole flux of a component, so amplitude must be non-negative; inside a Sum with a continuum a negative amplitude is an absorption line.

Parameters:

Name Type Description Default
amplitude float

Flux at the line centre, relative to the other components.

required
line_wavel float

Line centre in metres (shifted by any velocity: λ₀(1 + v/c)).

required
fwhm float

Full width at half maximum in metres.

required
wavel0 float

Reference wavelength in metres; the line centre by default.

None

Examples:

>>> line = GaussianLine(0.4, line_wavel=2.1661e-6, fwhm=1.0e-9)
>>> round(float(line(2.1661e-6 + 0.5e-9)), 4)
0.2

LorentzianLine

Bases: _Line

A Lorentzian emission or absorption line.

amplitude / (1 + 4 ((λ - line_wavel) / fwhm)²). Its wings fall off slowly, so over a wide band it adds a near-constant pedestal. Parameters are as for GaussianLine.

Examples:

>>> line = LorentzianLine(0.4, line_wavel=2.1661e-6, fwhm=1.0e-9)
>>> round(float(line(2.1661e-6 + 0.5e-9)), 4)
0.2

Nodes

Bases: Spectrum

A spectrum through free values at fixed wavelengths.

Linear or natural-cubic interpolation between the nodes. Beyond the end nodes the flux is held at the end values (outside="constant") or a fixed number (e.g. outside=0.0 for an excess that lives only in a line window, on top of a continuum in a Sum; give the end nodes the value 0 to keep the spectrum continuous). Fit the values with a prior of their shape; a smooth spectrum is a prior on them, not a feature of this class.

Parameters:

Name Type Description Default
values (array - like, shape(n))

Flux at each node, relative to the other components. They may be negative (an absorption excess) as long as a component's total flux is not.

required
wavel (array - like, shape(n))

Node wavelengths in metres: finite, positive and strictly increasing.

required
kind (linear, cubic)

Interpolation; "cubic" is a natural cubic spline and needs at least three nodes.

"linear"
outside constant or float

The flux beyond the end nodes (default "constant").

'constant'
wavel0 float

Reference wavelength in metres; the first node by default.

None

Examples:

>>> spectrum = Nodes([0.2, 0.4], [2.0e-6, 2.2e-6])
>>> round(float(spectrum(2.1e-6)), 6)
0.3
>>> excess = Nodes([0.0, 0.5, 0.0], [2.16e-6, 2.166e-6, 2.172e-6], outside=0.0)
>>> float(excess(2.0e-6))
0.0

wavel0 instance-attribute

values = np.asarray(values, dtype=float) instance-attribute

wavel = np.asarray(wavel, dtype=float) instance-attribute

kind = kind class-attribute instance-attribute

outside = outside if outside == 'constant' else float(outside) class-attribute instance-attribute

__init__(values, wavel, kind='linear', outside='constant', wavel0=None)

__check_init__()

PowerLaw

Bases: Spectrum

Power-law spectrum ratio * (λ / wavel0) ** index.

Parameters:

Name Type Description Default
ratio float or array - like

Flux at the reference wavelength, relative to the other components.

required
index float or array - like

Spectral index (default 0, i.e. achromatic). A star in the Rayleigh-Jeans regime of F_λ has index -4.

0.0
wavel0 float or array - like

Reference wavelength in metres (default 1.65e-6, H band).

1.65e-06

Examples:

>>> round(float(PowerLaw(0.2, index=-4.0, wavel0=1.6e-6)(3.2e-6)), 6)
0.0125

ratio = np.asarray(ratio, dtype=float) instance-attribute

index = np.asarray(index, dtype=float) instance-attribute

wavel0 = np.asarray(wavel0, dtype=float) instance-attribute

__init__(ratio, index=0.0, wavel0=1.65e-06)

__call__(wavel=None)

is_physical()

__check_init__()

Scaled

Bases: Spectrum

A fixed spectral shape with a free reference flux, ratio at wavel0.

The value is ratio * template(λ) / template(). Any spectrum, e.g. a library SED or a Sum with lines, can then be a companion in the grids, limits and detection statistics, whose flux axis is comp.flux.ratio. The template's parameters are held fixed: no gradient flows to them, so do not give priors to paths such as comp.flux.template.index. Only ratio is free.

Parameters:

Name Type Description Default
template Spectrum

The shape; only its shape matters, and its normalization must be positive.

required
ratio float or array - like

The flux at the template's wavel0 (default 1).

1.0

Examples:

>>> template = PowerLaw(0.5, index=-2.0, wavel0=2.0e-6)
>>> round(float(Scaled(template, 0.1)(4.0e-6)), 6)
0.025
>>> round(float(Scaled(template, 0.1)()), 6)
0.1

template = template instance-attribute

ratio = np.asarray(ratio, dtype=float) instance-attribute

wavel0 property

__init__(template, ratio=1.0)

__call__(wavel=None)

__check_init__()

Spectrum

Bases: Base

Base class for component spectra.

Subclasses implement _at(wavel), the flux at wavel (metres, any shape), and have a reference wavelength wavel0; calling the spectrum with no wavelength gives the flux there. _params_valid checks the parameters' domains and _check_wavel lists the wavelengths where the flux must be non-negative.

__call__(wavel=None)

is_physical()

Whether the spectrum is valid, as a (traceable) boolean.

Its parameters are in their domains and its flux is non-negative at its characteristic wavelengths (wavel0, nodes, line centres).

Sum

Bases: Spectrum

The sum of named spectra, e.g. a continuum plus lines.

Parts are reached by name, like the components of a System: "star.flux.continuum.index". Only the total must be non-negative, so a part may be an absorption line or a negative node excess.

Parameters:

Name Type Description Default
parts dict

{name: Spectrum}, positionally; or give them by keyword.

None
wavel0 float

Reference wavelength in metres, where the reference flux is the sum of the parts; the first part's wavel0 by default.

None

Examples:

>>> flux = Sum(
...     continuum=PowerLaw(1.0, wavel0=2.2e-6),
...     brg=GaussianLine(-0.3, line_wavel=2.1661e-6, fwhm=1.0e-9),
... )
>>> round(float(flux(2.1661e-6)), 4)  # continuum minus the absorption
0.7
>>> round(float(flux()), 4)  # the reference flux, at 2.2 µm
1.0

names = tuple(parts) class-attribute instance-attribute

parts = tuple(parts.values()) instance-attribute

wavel0 = np.asarray(wavel0, dtype=float) instance-attribute

components property

The parts as a {name: spectrum} dictionary, in order.

__init__(parts=None, /, *, wavel0=None, **named)

__getattr__(name)

flux_at(flux, wavel=None)

Evaluate a number or a spectrum at wavel (None = reference).

reference_flux(flux)

The reference flux of a number or a spectrum.

For a spectrum this is flux(None), its value at wavel0 (the ratio for PowerLaw and BlackBody).