Template Class SpecsynLib
Defined in File SpecsynLib.hpp
Inheritance Relationships
Base Type
public specsyn::Specsyn(Class Specsyn)
Derived Types
public specsyn::SpecsynLib2D< Policy >(Template Class SpecsynLib2D)public specsyn::SpecsynLibNoWind< Policy >(Template Class SpecsynLibNoWind)public specsyn::SpecsynLibWR< Policy >(Template Class SpecsynLibWR)
Class Documentation
-
template<OOBPolicy Policy>
class SpecsynLib : public specsyn::Specsyn Abstract base for a Specsyn backed by a 3D tensor-grid spectral library.
This class holds the machinery shared by every spectral library that stores its spectra on a 3D tensor grid and interpolates trilinearly within it: the grid’s three axes (dim1_, dim2_, dim3_), the (possibly only partially populated) spectra on that grid (spectra_, viewed through the grid_ mdspan), and the trilinear-interpolation spec() overload that operates on them. It knows nothing about what the three axes actually mean – that mapping (e.g. from a StarData and [Fe/H] to a (FeH, logg, Teff) point, or from a Wolf-Rayet star’s properties to a point in a fundamentally different coordinate system) is entirely the job of a derived class, such as SpecsynLibNoWind (stars without optically thick winds, covering libraries like BOSZ and TLUSTY) or SpecsynLibWR (Wolf-Rayet stars, whose atmospheres are parameterized by different variables entirely).
A default-constructed SpecsynLib has every one of dim1_, dim2_, dim3_, and spectra_ empty, and grid_ default-constructed (an empty view over nothing). Populating all of these – including sizing spectra_ and constructing grid_ to view it – is entirely the responsibility of the derived class’s own constructor, since this base class has no way to know the grid’s shape until the derived class has read its own library file.
- Template Parameters:
Policy – How this SpecsynLib should handle a query point that falls outside its tensor grid, or in an unpopulated gap within it – see OOBPolicy. This is a template parameter, rather than a constructor argument or runtime flag, so that spec() can compile the chosen behavior directly into its hot path instead of branching on it at runtime.
Subclassed by specsyn::SpecsynLib2D< Policy >, specsyn::SpecsynLibNoWind< Policy >, specsyn::SpecsynLibWR< Policy >
Public Functions
-
inline explicit SpecsynLib(const io::SimControls &controls)
Construct an empty SpecsynLib.
Leaves dim1_, dim2_, dim3_, and spectra_ empty, and grid_ default-constructed. See the class-level details for why populating them is left entirely to derived classes.
- Parameters:
controls – Simulation controls; forwarded unchanged to Specsyn’s own constructor – see its comment
-
virtual auto spec(const StarData &props, double feh) const -> std::vector<double> override = 0
Compute a star’s spectrum.
Still pure virtual here (re-declared, rather than simply inherited unimplemented from Specsyn, purely so this class’s own division of labor can be documented at the point where it matters): a derived class implements this to map props and feh onto this library’s own (dim1_, dim2_, dim3_) coordinate system – bounds-checking as appropriate for its own variables – then calls the three-argument spec() below to do the actual trilinear interpolation, and finally applies whatever scaling (e.g. by stellar surface area) is appropriate to convert the library’s stored quantity into a specific luminosity, which this base class knows nothing about.
- Parameters:
props – Stellar properties, as produced by evaluating the Interpolator1D returned by Tracks2D::getIsochrone at this star’s mass
feh – [Fe/H] value of the star; needed because it is not carried by props itself
- Throws:
std::runtime_error – if the star falls outside this library’s grid and Policy is OOBPolicy::raise
- Returns:
The star’s spectrum, evaluated on the wavelength grid returned by wl(), in units of erg/s/Angstrom; a size-0 vector if the star falls outside this library’s grid and Policy is OOBPolicy::silent
-
virtual void resample(const std::vector<double> &wlNew)
Resample every spectrum in this library onto a new wavelength grid.
Resamples every populated (non-empty) point in the tensor grid from this library’s existing wavelength grid (wl_) onto wlNew, via the static resample() overload below (see its own comment for exactly how) – once every populated point has been resampled this way, wl_ itself is replaced with wlNew, so wl() and every subsequent spec() call reflect the new grid. Unpopulated grid points are left as empty vectors, exactly as before.
Virtual so that SpecsynLib2D – whose dim1_ is left empty rather than sized to 1 (see its own comment) – can override this to iterate over (dim2_, dim3_) alone: this base implementation’s own iteration is driven by dim1_.size(), which would silently do nothing at all for a library whose dim1_ is empty, leaving every stored spectrum on its old (now size-mismatched) wavelength grid even as wl_ itself gets updated to wlNew.
- Parameters:
wlNew – The new wavelength grid, in Angstrom
Public Static Functions
-
static auto resample(const std::vector<double> &wl, const std::vector<double> &wlNew, const std::vector<double> &spectrum) -> std::vector<double>
Resample a single spectrum from one wavelength grid onto another.
Builds an Interpolator1D of spectrum versus wl, then for each wlNew[w] integrates it (via Interpolator1D::integ) over the bin surrounding wlNew[w] – bounded by the geometric (log-space) midpoints of wlNew[w] and its neighbors, truncated to wl’s own range – and divides by that bin’s (truncated) width to give resampled[w] as a flux density averaged over the bin, rather than just spectrum’s value at wlNew[w] itself. Point-sampling like that (an earlier version of this function) is fine when upsampling, but can badly misrepresent narrow spectral features when downsampling onto a coarser grid, since the new grid can simply miss them; integrating over each new bin instead only misses a feature if the feature itself falls entirely outside every bin, i.e. in a gap in wlNew’s own coverage. A wlNew[w] whose bin truncates to zero width (entirely outside wl’s range) gets a flux of zero rather than extrapolated. This is not strictly flux-conserving, since spectrum’s own points are treated as samples rather than bin averages to begin with (nothing conservative like PPM interpolation), but it is much closer than point-sampling – and since the spectral libraries this resamples are themselves tabulated as sample points rather than bin averages, a conservative interpolation scheme would not actually gain anything here. This is the single-spectrum core of the member resample() above (which calls this once per populated tensor-grid point), factored out as its own static function – rather than kept private to resample()’s own loop – so that a derived class needing to resample spectra some other way (e.g. SpecsynLibWR, whose spectra aren’t stored on this class’s own wl_ at all until after they’ve each been resampled from their own distinct native grid) can reuse the exact same interpolation logic instead of reimplementing it.
- Parameters:
wl – The wavelength grid spectrum is currently defined on
wlNew – The wavelength grid to resample onto
spectrum – The spectrum to resample, evaluated at wl
- Returns:
spectrum, resampled onto wlNew
Protected Types
-
using SpectraGrid = std::mdspan<std::vector<double>, std::dextents<std::size_t, 3>>
The shape of grid_, the mdspan view onto spectra_.
Protected Functions
-
virtual auto spec(double d1, double d2, double d3) const -> std::vector<double>
Compute a spectrum by trilinear interpolation at a point in the tensor grid.
Callers (i.e. derived classes) are responsible for having already checked that d1, d2, d3 each lie within [dim1_.front(), dim1_.back()], etc. – this method only locates the bracketing grid cell along each axis (via an O(log n) binary search, since none of the three axes can be assumed evenly spaced, accelerated by a per-axis cached index – see dim1Cache_), confirms every one of the 8 neighboring corners actually has a spectrum (interpolating across an unpopulated point would be meaningless), and trilinearly interpolates across them.
- Parameters:
d1 – Query coordinate along dim1_
d2 – Query coordinate along dim2_
d3 – Query coordinate along dim3_
- Throws:
std::runtime_error – if any of those 8 corners is unpopulated and Policy is OOBPolicy::raise
- Returns:
The interpolated spectrum, evaluated on the wavelength grid returned by wl(), with no scaling applied beyond the interpolation itself – any further scaling (e.g. by a star’s surface area) is left entirely to the caller, since this class knows nothing about what quantity is actually stored on the grid; a size-0 vector if any of the 8 tensor-grid corners bracketing (d1, d2, d3) is unpopulated and Policy is OOBPolicy::silent
Protected Attributes
-
std::vector<double> dim1_
Grid points along the tensor grid’s first axis
-
std::vector<double> dim2_
Grid points along the tensor grid’s second axis
-
std::vector<double> dim3_
Grid points along the tensor grid’s third axis
-
std::vector<std::vector<double>> spectra_
Spectra on the (dim1_, dim2_, dim3_) tensor grid.
A flattened array of shape (dim1_.size(), dim2_.size(), dim3_.size()) – viewed through grid_ – holding one spectrum (a vector of the same length as wl_) per populated grid point. Not every point in this outer product need actually have a spectrum in the library; unpopulated points are left as empty (size-0) vectors.
-
SpectraGrid grid_
An mdspan view onto spectra_, shaped (dim1_.size(), dim2_.size(), dim3_.size())
Constructed by the derived class once it has finished sizing spectra_ to its final shape; default-constructed to an empty view over nothing until then. Safe to keep across spectra_’s lifetime as long as the derived class never resizes the outer vector again after constructing grid_ – only mutates individual elements in place – since resizing could reallocate spectra_’s backing storage and leave grid_ dangling.
Protected Static Functions
-
static auto outOfBoundsResult(const std::string &message) -> std::vector<double>
Handle a query point that falls outside this library’s grid.
protected, rather than private, so that a derived class’s own spec() override can use it too for the bounds checks (e.g. on Teff, logg) that are specific to its own variables and therefore not handled by this class’s own spec().
- Parameters:
message – Description of why the point is out of bounds
- Throws:
std::runtime_error – with message, if Policy is OOBPolicy::raise
- Returns:
A size-0 vector, if Policy is OOBPolicy::silent