solprop.processing.spectra#

Spectral post-processing: instrument resolution and explicit bandpass windows.

Two operations that belong to the spectrum rather than to the accessor that fetched it: smoothing a spectral energy density to the resolution of a real instrument, and building the Planck-tapered window of a wavelength bandpass.

Neither is ever applied on its own. A window is a keyword, it is a wavelength range, and the array it produces is returned to the caller to multiply; no window adjusts its own limits as a side channel.

Module Attributes

Bandpass

A wavelength range to taper between, or a ready-made window on the stored axis.

DEFAULT_BANDPASS_BINS

Widest default bandpass edge, in frequency bins (Luna's winwidth = :auto).

DEFAULT_BANDPASS_FRACTION

Widest default bandpass edge as a fraction of the pass band's angular width.

Functions

bandpass(grid, wavelength_limits, *[, width])

Return a Planck-tapered bandpass window on the stored frequency axis.

specres(x, density, resolution, *[, ...])

Smooth a spectral density to an instrument resolution on a uniform axis.

solprop.processing.spectra.Bandpass = tuple[float, float] | jaxtyping.Float[ndarray, 'n_omega']#

A wavelength range to taper between, or a ready-made window on the stored axis.

solprop.processing.spectra.DEFAULT_BANDPASS_BINS = 64#

Widest default bandpass edge, in frequency bins (Luna’s winwidth = :auto).

solprop.processing.spectra.DEFAULT_BANDPASS_FRACTION = 0.25#

Widest default bandpass edge as a fraction of the pass band’s angular width.

Each Planck edge is antisymmetric about its midpoint, so it transmits half its width: a window with edges f times the band’s width has an integrated width 1 + f times the band’s. A quarter keeps that excess to 25 % while each edge still spans a quarter of the band, so the window stays smooth on the band’s own scale; and it leaves every band wider than 64 / 0.25 = 256 bins on the 64-bin edge it always had.

solprop.processing.spectra.bandpass(grid, wavelength_limits, *, width=None)[source]#

Return a Planck-tapered bandpass window on the stored frequency axis.

The window is one inside the wavelength range and zero outside it, joined on each side by a Planck taper of the given width in angular frequency. The default edge is the narrower of DEFAULT_BANDPASS_BINS (64) bin spacings and DEFAULT_BANDPASS_FRACTION (a quarter) of the pass band’s angular width, so a narrow band on a short time window is not swamped by edges wider than itself. It is returned rather than applied, because nothing in solprop.processing windows a spectrum unless it was asked to.

Parameters:
gridsolprop.grid.TimeGrid

The grid whose omega axis the window is built on.

wavelength_limitstuple of float

The pass band, m, in either order.

widthfloat or None

Width of each taper edge, rad/s; None gives the smaller of 64 bin spacings and a quarter of the pass band’s angular width.

Returns:
numpy.ndarray

Window values in [0, 1], shape (n_omega,), in storage order.

Raises:
solprop.exceptions.ProcessingError

If a limit is not positive, or the width is not positive – including the default width of an empty pass band (equal limits).

Examples

>>> import numpy as np
>>> from solprop.processing._examples import example_grid
>>> from solprop.processing.spectra import bandpass
>>> grid = example_grid()
>>> window = bandpass(grid, (700e-9, 900e-9))
>>> float(window.max()), float(window.min())
(1.0, 0.0)
solprop.processing.spectra.specres(x, density, resolution, *, weights=None, spec_range=None, samples=10)[source]#

Smooth a spectral density to an instrument resolution on a uniform axis.

The output axis is uniform with samples points per resolution element, and each input bin is spread over it by a unit-area Gaussian whose full width at half maximum is resolution, weighted by the width of the bin it came from:

S_out(x_j) = sum_k S(x_k) w_k G(x_j - x_k).

Because the kernel integrates to one, the integral of the density – the energy – is preserved to round-off wherever the signal is well inside the range. The input axis need not be uniform, which is what makes this work directly on a wavelength axis derived from a uniform frequency grid: pass the bin widths as weights.

Non-positive output values are not replaced with the smallest positive one to keep a logarithmic plot away from zero: that would change the data.

Parameters:
xnumpy.ndarray

Input abscissa, shape (n_x,), strictly increasing.

densitynumpy.ndarray

Spectral density on that abscissa, shape (..., n_x), per unit of x.

resolutionfloat

Instrument resolution as a full width at half maximum, in the units of x.

weightsnumpy.ndarray or None

Integration measure of each input bin, in the units of x; None uses the central differences of x.

spec_rangetuple of float or None

Limits of the output axis, clipped to the range of x; None uses all of it.

samplesint

Output points per resolution element, at least one.

Returns:
x_outnumpy.ndarray

Uniform output abscissa, shape (n_out,).

density_outnumpy.ndarray

The smoothed density, shape (..., n_out).

Raises:
solprop.exceptions.ProcessingError

If the resolution or the sampling is not positive, if x does not increase, or if the requested range does not overlap the data.

Examples

>>> import numpy as np
>>> from solprop.processing.spectra import specres
>>> x = np.linspace(400e-9, 1200e-9, 2001)
>>> density = np.exp(-0.5 * ((x - 800e-9) / 20e-9) ** 2)
>>> smooth_x, smooth = specres(x, density, 50e-9)
>>> bool(smooth.max() < density.max())
True