Source code for rubin_sim.phot_utils.spectral_resampling

__all__ = ("spectres",)

import warnings

import numpy as np

# Taken from https://github.com/ACCarnall/SpectRes
# (which is under GPL 3), cite https://arxiv.org/abs/1705.05165


def make_bins(wavs):
    """Given a series of wavelength points, find the edges and widths
    of corresponding wavelength bins."""
    edges = np.zeros(wavs.shape[0] + 1)
    widths = np.zeros(wavs.shape[0])
    edges[0] = wavs[0] - (wavs[1] - wavs[0]) / 2
    widths[-1] = wavs[-1] - wavs[-2]
    edges[-1] = wavs[-1] + (wavs[-1] - wavs[-2]) / 2
    edges[1:-1] = (wavs[1:] + wavs[:-1]) / 2
    widths[:-1] = edges[1:-1] - edges[:-2]

    return edges, widths


[docs] def spectres(new_wavs, spec_wavs, spec_fluxes, spec_errs=None, fill=0, verbose=True): """ Function for resampling spectra (and optionally associated uncertainties) onto a new wavelength basis. If performance is an issue, https://github.com/ACCarnall/SpectRes includes a version that is wrapped in numba and should be much faster. Parameters ---------- new_wavs : numpy.ndarray Array containing the new wavelength sampling desired for the spectrum or spectra. spec_wavs : numpy.ndarray 1D array containing the current wavelength sampling of the spectrum or spectra. spec_fluxes : numpy.ndarray Array containing spectral fluxes at the wavelengths specified in spec_wavs, last dimension must correspond to the shape of spec_wavs. Extra dimensions before this may be used to include multiple spectra. spec_errs : numpy.ndarray (optional) Array of the same shape as spec_fluxes containing uncertainties associated with each spectral flux value. fill : float (optional) Where new_wavs extends outside the wavelength range in spec_wavs this value will be used as a filler in new_fluxes and new_errs. verbose : bool (optional) Setting verbose to False will suppress the default warning about new_wavs extending outside spec_wavs and "fill" being used. Returns ------- new_fluxes : numpy.ndarray Array of resampled flux values, last dimension is the same length as new_wavs, other dimensions are the same as spec_fluxes. new_errs : numpy.ndarray Array of uncertainties associated with fluxes in new_fluxes. Only returned if spec_errs was specified. """ # Rename the input variables for clarity within the function. old_wavs = spec_wavs old_fluxes = spec_fluxes old_errs = spec_errs # Make arrays of edge positions and widths for the old and new bins old_edges, old_widths = make_bins(old_wavs) new_edges, new_widths = make_bins(new_wavs) # Generate output arrays to be populated new_fluxes = np.zeros(old_fluxes[..., 0].shape + new_wavs.shape) if old_errs is not None: if old_errs.shape != old_fluxes.shape: raise ValueError("If specified, spec_errs must be the same shape " "as spec_fluxes.") else: new_errs = np.copy(new_fluxes) start = 0 stop = 0 # Calculate new flux and uncertainty values, looping over new bins for j in range(new_wavs.shape[0]): # Add filler values if new_wavs extends outside of spec_wavs if (new_edges[j] < old_edges[0]) or (new_edges[j + 1] > old_edges[-1]): new_fluxes[..., j] = fill if spec_errs is not None: new_errs[..., j] = fill if (j == 0 or j == new_wavs.shape[0] - 1) and verbose: warnings.warn( "Spectres: new_wavs contains values outside the range " "in spec_wavs, new_fluxes and new_errs will be filled " "with the value set in the 'fill' keyword argument " "(by default 0).", category=RuntimeWarning, ) continue # Find first old bin which is partially covered by the new bin while old_edges[start + 1] <= new_edges[j]: start += 1 # Find last old bin which is partially covered by the new bin while old_edges[stop + 1] < new_edges[j + 1]: stop += 1 # If new bin is fully inside an old bin start and stop are equal if stop == start: new_fluxes[..., j] = old_fluxes[..., start] if old_errs is not None: new_errs[..., j] = old_errs[..., start] # Otherwise multiply the first and last old bin widths by P_ij else: start_factor = (old_edges[start + 1] - new_edges[j]) / (old_edges[start + 1] - old_edges[start]) end_factor = (new_edges[j + 1] - old_edges[stop]) / (old_edges[stop + 1] - old_edges[stop]) old_widths[start] *= start_factor old_widths[stop] *= end_factor # Populate new_fluxes spectrum and uncertainty arrays f_widths = old_widths[start : stop + 1] * old_fluxes[..., start : stop + 1] new_fluxes[..., j] = np.sum(f_widths, axis=-1) new_fluxes[..., j] /= np.sum(old_widths[start : stop + 1]) if old_errs is not None: e_wid = old_widths[start : stop + 1] * old_errs[..., start : stop + 1] new_errs[..., j] = np.sqrt(np.sum(e_wid**2, axis=-1)) new_errs[..., j] /= np.sum(old_widths[start : stop + 1]) # Put back the old bin widths to their initial values old_widths[start] /= start_factor old_widths[stop] /= end_factor # If errors were supplied return both new_fluxes and new_errs. if old_errs is not None: return new_fluxes, new_errs # Otherwise just return the new_fluxes spectrum array else: return new_fluxes