specsr.data.grid

The common wavelength grid, and flux-conserving resampling onto it.

Why this module exists

The original preprocessing interpolated both members of each pair onto linspace(1.0, 5.0, 2500) — a constant Δλ = 0.001601 µm grid. Measured against native DR4 sampling:

arm

median Δλ (µm)

effect of the 2,500-point linear grid

prism

0.00613

3.8x upsampling — the wanted direction

G140M

0.00064

2.50x downsampling

G235M

0.00107

1.50x downsampling

G395M

0.00180

0.89x — roughly preserved

Below ~3.2 µm the high-resolution reference ended up with fewer than two samples per R~1000 resolution element, i.e. undersampled. The model was therefore trained against targets whose resolution the gridding had already reduced, and the grid — not the model — set the ceiling on achievable sharpness. This is the likeliest explanation for super-resolved spectra looking coarser than the high-resolution spectra they were trained against.

The rule

Regrid the low-resolution data up onto the high-resolution sampling. Never resample the high-resolution spectra down.

A logarithmic grid holds lambda / delta_lambda constant, which is the natural sampling for a spectrograph: resolution scales with wavelength, so a fixed fractional step matches the instrument everywhere instead of being too coarse in the blue and wasteful in the red.

Flux is an area, not a sample

The physically meaningful quantity in an emission line is its integrated flux — the area under the curve, in erg s^-1 cm^-2 — not the height of the curve at particular abscissae. Resampling must therefore integrate, not interpolate. resample_flux_conserving() is the default for that reason.

Measured on native G140M sampling with R~1000 lines:

grid and method

integrated flux error

line peak retained

old linear, interpolation

-1.196%

79.2%

old linear, conserving

-0.011%

72.4%

log R=4000, interpolation

-0.098%

90.0%

log R=4000, conserving

-0.032%

97.8%

Two things follow. The old pipeline discarded roughly a fifth of every emission line’s peak and about 1% of its integrated flux before the model saw the data. And on a grid that is too coarse there is no good option: conserving the integral costs peak height, and preserving peak height costs the integral. Only a sufficiently fine grid lets both be kept, which is why the grid — not the resampling method — was the real defect.

Module Attributes

DEFAULT_GRID

The project default.

Functions

resample_flux_conserving(wave_in, flux_in, ...)

Resample a spectrum onto wave_out, conserving integrated flux.

sampling_report(native_delta_lambda[, grid])

Human-readable check that grid does not downsample any input arm.

Classes

LogWavelengthGrid([lambda_min, lambda_max, ...])

A logarithmic (constant resolving power) wavelength grid.

class specsr.data.grid.LogWavelengthGrid(lambda_min=1.0, lambda_max=5.3, resolving_power=4000.0)[source]

Bases: object

A logarithmic (constant resolving power) wavelength grid.

Variables:
  • lambda_max (lambda_min,) – Bounds in microns.

  • resolving_power (float) – The constant lambda / delta_lambda of the grid. This is a sampling density, not the instrument’s spectral resolution: it must comfortably exceed the native sampling of every arm, or the grid degrades the data it is meant to carry.

Parameters:
lambda_min: float = 1.0
lambda_max: float = 5.3
resolving_power: float = 4000.0
property n_samples: int

Number of grid points implied by the resolving power.

centers()[source]

Grid centres, shape (n_samples,).

Return type:

ndarray

edges()[source]

Bin edges, shape (n_samples + 1,).

Geometric mid-points, so that edges are evenly spaced in log wavelength exactly as the centres are.

Return type:

ndarray

samples_per_resolution_element(wavelength_um, R_instrument=1000.0)[source]

How many grid samples span one instrumental resolution element.

Nyquist needs >= 2; below that the grid cannot represent the features present in the data.

Parameters:

R_instrument (float)

specsr.data.grid.DEFAULT_GRID = LogWavelengthGrid(1.0-5.3 um, R=4000, N=6671)

The project default. R = 4000 is the smallest round value at or above the finest native sampling encountered anywhere in DR4 (lambda/dlambda = 3737, in G235M), so nothing is downsampled. Range extends to 5.3 um to cover G395M.

specsr.data.grid.resample_flux_conserving(wave_in, flux_in, wave_out, err_in=None, fill=nan)[source]

Resample a spectrum onto wave_out, conserving integrated flux.

Each output bin receives the integral of the input over the overlapping portion of each input bin, divided by the output bin width. Equivalently: the output is the average flux density over the output bin, so sum(flux * bin_width) is preserved up to the edges of the covered range.

Why not plain interpolation

Linear interpolation samples the curve at new abscissae; it does not integrate. On a narrow emission line, interpolation can land between the peak samples and systematically lose line flux, while a flux-conserving rebin preserves the line’s integrated flux by construction. Accurate line fluxes are the quantity the science case depends on, so the conserving form is the right default.

param wave_in:

Input grid and flux density, same length. wave_in must be increasing.

param flux_in:

Input grid and flux density, same length. wave_in must be increasing.

param wave_out:

Output grid centres.

param err_in:

Optional uncertainty on flux_in. Propagated by summing in quadrature over the same overlaps, which is correct for independent input samples and mildly conservative when they are correlated.

param fill:

Value for output bins with no input coverage.

rtype:

flux_out or (flux_out, err_out) if err_in was given.

Parameters:
specsr.data.grid.sampling_report(native_delta_lambda, grid=None)[source]

Human-readable check that grid does not downsample any input arm.

native_delta_lambda maps an arm name to (lambda_min, lambda_max, median_delta_lambda) in microns. Use this before committing to a grid: a grid that downsamples its inputs silently caps the achievable resolution.

Parameters:
Return type:

str