Class YieldChannel

Class Documentation

class YieldChannel

Nucleosynthetic yield data for one channel (e.g. ccsn), one model (e.g. sukhbold16), over a range of [Fe/H].

Public Types

using Array3D = std::mdspan<const double, std::dextents<std::size_t, 3>>

mdspan view of yieldData_ – see yld()’s own comment

using ActiveArray2D = std::mdspan<const std::uint8_t, std::dextents<std::size_t, 2>>

mdspan view of yieldActive_/yieldActiveOrig_ – see yieldActive_’s own comment

Public Functions

YieldChannel(const YieldChannelDescriptor &descriptor, double fehMin, double fehMax, const std::string &registryName = defaultRegistry)

Construct a YieldChannel from a yield model on disk.

Caches a copy of descriptor in descriptor_ – see descriptor()’s own comment – then reads massesOrig_/isotopesOrig_/ yieldDataOrig_ from disk, but – unlike descriptor.mMin_/ mMax_, which used to be applied immediately here via an end-of-constructor rebuildYieldGrid() call – does not itself populate masses_/isotopes_/yieldData_: a caller must call rebuildYieldGrid() explicitly (with descriptor.mMin_/mMax_, and optionally a synchronized isotope list – see its own comment) before yield() can be called. This lets a caller building several YieldChannel objects at once (see the Yields class) read every channel’s own native isotopesOrig_ first, decide on one synchronized isotope list across all of them, and only then build each channel’s actual yieldData_ against that shared list, rather than building it once here against this channel’s own native isotopes and then immediately rebuilding it again.

Parameters:
  • descriptor – Which channel/model to load, and the mass range it should cover – see YieldChannelDescriptor’s own comment for each member; descriptor.mMin_/mMax_ mean exactly what rebuildYieldGrid()’s own mMin/mMax parameters do

  • fehMin – Minimum [Fe/H] value

  • fehMax – Maximum [Fe/H] value

  • registryName – Name of the yield registry file

Throws:

std::runtime_error – if descriptor.channel_/modelName_ is not found in the registry, if fehMin or fehMax lies outside the [Fe/H] range actually available for that channel/model, or if the underlying HDF5 file cannot be read or is malformed

~YieldChannel() = default
YieldChannel(const YieldChannel&) = delete
YieldChannel(YieldChannel&&) = delete
auto operator=(const YieldChannel&) -> YieldChannel& = delete
auto operator=(YieldChannel&&) -> YieldChannel& = delete
void rebuildYieldGrid(std::optional<double> mMin = std::nullopt, std::optional<double> mMax = std::nullopt, elem::IsotopeList isotopes = {}, const elem::IsotopeList &skippedIsotopes = {})

Rebuild masses_/isotopes_/yieldData_ from massesOrig_/isotopesOrig_/yieldDataOrig_.

Must be called at least once (with whatever mMin/mMax/isotopes a caller wants) before yield() can be called – unlike most of this project’s own analogous rebuild-from-native-data methods (e.g. Extinct::rebuildCache()), the constructor does not call this itself; see its own comment for why. Public so a caller (e.g. from Python, after changing which mass range a channel should cover, or – see the Yields class – to synchronize several channels onto one common isotope list) can rerun this on an already-constructed YieldChannel without building a new one – the same role loadCurve()/rebuildCache() play for Extinct, and loadTable() for Nebular.

masses_ is set to mMin, followed by every value in massesOrig_ strictly between mMin and mMax, followed by mMax – so masses_ is always exactly [mMin, mMax] endpoint- inclusive, whether that’s wider than massesOrig_’s own range (extrapolation), narrower (a truncated sub-range), or identical to it (mMin/mMax left at their defaults).

isotopes_ is set to isotopesOrig_ if isotopes is empty, or to isotopes itself otherwise – letting a caller reorder, subset, or extend (see below) the isotope list this channel’s own yieldData_ is built over, e.g. to match every other channel a Yields is chaining together.

yieldData_ is filled in one (mass, isotope) entry at a time. The mass axis works exactly as it always has (see the previous revision of this comment, before isotope remapping was added): a masses_ entry that exactly matches some value in massesOrig_ (including every value copied verbatim from massesOrig_ above) resolves to an exact copy of that massesOrig_ column; one strictly between two massesOrig_ values is linearly interpolated between them via utils::findBracket; one outside [massesOrig_.front(), massesOrig_.back()] altogether is extrapolated by scaling the nearest massesOrig_ column by the ratio of the requested mass to that column’s own mass. The isotope axis is independent: for each entry in isotopes_, its index in isotopesOrig_ is looked up (by IsotopeData equality, i.e. matching (Z, A), not isotopesOrig_’s own order) to find which yieldDataOrig_ column to read the mass axis above from; an isotopes_ entry with no match in isotopesOrig_ (this channel’s own model never tabulated it) leaves that isotope’s own yieldData_ entries at exactly 0, for every mass and [Fe/H].

A tabulated isotope in skippedIsotopes has no yieldData_ column of its own; instead, since it is treated as decaying instantly, its yield is added to those of its first non-skipped descendants in isotopes_ (see elem::effectiveDaughters()), each weighted by the branching ratio along the path times the ratio of the descendant’s mass number to its own, converting the fraction of nuclei into a fraction of mass. A descendant not in isotopes_ (e.g. excluded by a restricted isotope list) receives nothing.

yieldActive_ is rebuilt alongside yieldData_, one masses_ column at a time, from yieldActiveOrig_ – see yieldActive_’s own comment for the rule.

massCache_ is reset to 0 for every thread once masses_ has a new size: an index cached against the old masses_ could otherwise be at or past the end of a smaller new one, which yield()’s own unchecked utils::findBracket call requires never happens. fehCache_/feH_ are untouched – this method never changes the [Fe/H] axis.

If mMin/mMax are given (not nullopt), descriptor_’s own mMin_/mMax_ are updated to match, once validation above has already succeeded – so descriptor() always reflects the most recent mass range actually requested, even after a caller (e.g. from Python) calls this again with a different one. Leaving mMin/mMax at their own nullopt default leaves descriptor_’s corresponding field untouched, rather than resetting it back to nullopt.

Parameters:
  • mMin – Minimum stellar mass this channel should cover; defaults (nullopt) to massesOrig().front()

  • mMax – Maximum stellar mass this channel should cover; defaults (nullopt) to massesOrig().back()

  • isotopes – The isotope list yieldData_’s third axis should be built over; an empty vector (the default) means “use isotopesOrig_ itself, unchanged” – see below for what a non-empty list does

  • skippedIsotopes – Short-lived isotopes treated as decaying instantly (see Yields::rebuildYieldGrid()’s own comment); must not overlap isotopes. Ignored if isotopes is empty. Empty by default

Throws:

std::invalid_argument – if the resolved mMin or mMax is not finite and strictly positive (a stellar mass), or if the resolved mMin is not strictly less than the resolved mMax

inline auto channel() const

Return which nucleosynthetic channel this object holds.

Returns:

descriptor_.channel_ – the channel named by the descriptor passed to the constructor

inline auto descriptor() const -> YieldChannelDescriptor

Return a copy of the descriptor this channel was built from.

Returns:

A copy of descriptor_: the same descriptor passed to the constructor, except that mMin_/mMax_ instead reflect whichever mass range was actually last requested – either still the constructor’s own descriptor.mMin_/mMax_, if rebuildYieldGrid() has never been called with an explicit mMin/mMax of its own, or whatever it was called with most recently otherwise (see its own comment). Returned by value, not by reference, since descriptor_ can change underneath a caller on the very next rebuildYieldGrid() call.

inline auto masses() const -> const std::vector<double>&

Return the stellar masses this channel’s yields are tabulated at.

Returns:

A const reference to the masses (Msun), ascending – this is the grid yield()/hasYield() actually use, which can extend below/above massesOrig()’s own range (extrapolated) or be a narrower sub-range of it (see rebuildYieldGrid()’s own comment). Empty until rebuildYieldGrid() is called at least once.

inline auto massesOrig() const -> const std::vector<double>&

Return the stellar masses natively tabulated by the underlying model.

Returns:

A const reference to the masses (Msun), ascending, exactly as read from the model’s own HDF5 file – unlike masses(), never affected by the mMin/mMax passed to rebuildYieldGrid(), and already populated once the constructor returns (no need to call rebuildYieldGrid() first)

inline auto isotopes() const -> const elem::IsotopeList&

Return the isotopes this channel’s yieldData_ is actually tabulated for.

Returns:

A const reference to the isotopes, in the same order as yld()’s own third axis – either isotopesOrig() itself, or whatever isotope list was last passed to rebuildYieldGrid(), depending on which it was called with (see its own comment). Empty until rebuildYieldGrid() is called at least once.

inline auto isotopesOrig() const -> const elem::IsotopeList&

Return the isotopes natively tabulated by the underlying model.

Returns:

A const reference to the isotopes, in the same order as yieldDataOrig_’s own third axis, each one a reference into the single, global elem::isotopeTable(), resolved once (via the (Z, A) pairs read from the model’s own HDF5 file) in the constructor – unlike isotopes(), never affected by rebuildYieldGrid(), and already populated once the constructor returns (no need to call rebuildYieldGrid() first)

inline auto feH() const -> const std::vector<double>&

Return the [Fe/H] values this channel’s yields are tabulated at.

Returns:

A const reference to the [Fe/H] values, ascending – see the constructor’s own comment for how this can be wider than [fehMin, fehMax], the range actually requested

inline auto requestedFehMin() const -> double

Return the minimum [Fe/H] this channel was constructed to cover.

Unlike feH().front(), which reports the data actually loaded (which may extend below it – see the constructor’s own comment), this is the range the caller asked for. See Yields::requestedFehMin(), which combines these across channels.

Returns:

The fehMin the constructor was given

inline auto requestedFehMax() const -> double

Return the maximum [Fe/H] this channel was constructed to cover.

Returns:

The fehMax the constructor was given – see requestedFehMin()’s own comment

inline auto yld() const -> Array3D

Return the yield data as a 3D view.

Built fresh from yieldData_ on every call, rather than cached as its own sibling member, mirroring Mesh3DInterpolator’s own x()/y()/z()/f() accessors – avoids storing a pointer-into- self that would need special handling if this class ever did become copyable or movable in the future (it does not currently support either – see the constructor’s own comment on why), and costs nothing extra: an mdspan is just a pointer and three extents, cheap to construct on demand.

Returns:

An mdspan of shape (feH().size(), masses().size(), isotopes().size()) into yieldData_, i.e. yld()[f, m, i] is the yield (Msun) of isotope i, from a star of mass masses()[m], at metallicity feH()[f]

inline auto hasYield(const double mass) const -> bool

Check whether a stellar mass falls within this channel’s mass grid.

Only meaningful once rebuildYieldGrid() has been called at least once; before that, masses() is empty and this returns false unconditionally (no mass is ever in range on an unbuilt grid) rather than reading masses_.front()/back() on an empty vector – unlike yield() (see its own comment), this doesn’t throw, since “no mass is in range yet” is itself a meaningful, correct answer here.

Parameters:

mass – Stellar mass to check (Msun)

Returns:

True if mass lies within [masses().front(), masses().back()], false otherwise

inline auto hasYield(const double mass, const double feH) const -> bool

Check whether a star of a given mass and [Fe/H] returns a non-zero yield through this channel.

Unlike hasYield(mass), which only checks mass against the overall extent of masses(), this also catches gaps within that extent: masses at which a model tabulates a yield, but that yield is zero for every isotope – e.g. the failed supernovae in sukhbold16’s own ccsn tables. A mass strictly between an active and an inactive grid point still counts as having a yield, since yield() interpolates a non-zero value there.

Like hasYield(mass), returns false unconditionally if rebuildYieldGrid() has never been called. Uses massCache_/ fehCache_ exactly as yield() does – see its own comment.

Parameters:
  • mass – Stellar mass to check (Msun)

  • feH – [Fe/H] to check; clamped to [feH().front(), feH().back()] first if outside it

Returns:

False if hasYield(mass) is false; otherwise true if any of the four (feH(), masses()) grid points at the corners of the grid cell containing (feH, mass) has a non-zero yield of any isotope (see yieldActive_’s own comment), false if all four have zero yields of every isotope

inline auto yield(const double mass, const double feH) const -> std::vector<double>

Return the yield of every isotope for a star of given mass and [Fe/H].

Callers must check hasYield(mass) (and that feH lies within feH()’s own range) themselves before calling this – enforced here only via assert(), so a violation is caught in a debug build but costs nothing in an optimized one, and via unchecked mdspan/vector element access throughout, for the same reason. The yieldData_-empty check above is a real, always-on throw rather than another assert(): unlike a bad mass/feH (a caller bug, on an otherwise-usable object), calling this before rebuildYieldGrid() at all means the object has no grid whatsoever yet, which a caller has no way to detect for itself the way it can check hasYield(mass) before calling this.

utils::findBracket handles a singular (size-1) feH() axis – e.g. a Solar-only yield model such as sukhbold16 – automatically, returning weight 0 on one side and 1 on the other with no special-casing needed here; see its own comment.

massCache_/fehCache_ accelerate repeated nearby queries the same way SpecsynLib’s own dim*Cache_ members do for spec() queries (see that class’s own comment) – this is also why this method, though logically const, needs those two members mutable.

Parameters:
  • mass – Stellar mass (Msun); must satisfy hasYield(mass)

  • feH – [Fe/H]; must lie within [feH().front(), feH().back()]

Throws:

std::runtime_error – if rebuildYieldGrid() has never been called (yieldData_ is still empty), so masses_/isotopes_ don’t yet describe any real grid to interpolate on

Returns:

A vector of isotopes().size() yields (Msun), in the same order as isotopes(), bilinearly interpolated from yld() in (feH, mass)