feat(weak-reactions): brought weak reaction code up to a point where it will compile (NOT YET TESTED)

This commit is contained in:
2025-10-08 11:17:35 -04:00
parent 274c566726
commit 13e2ea9ffa
15 changed files with 1452 additions and 153 deletions

View File

@@ -77,66 +77,253 @@ namespace gridfire::reaction {
class Reaction {
public:
/**
* @brief Virtual destructor for correct polymorphic cleanup.
*/
virtual ~Reaction() = default;
/**
* @brief Compute the temperature- and composition-dependent reaction rate.
*
* This is the primary interface used by the network to obtain the rate of a single
* reaction at the given thermodynamic state and composition. The exact units and
* normalization are defined by the concrete implementation (e.g., REACLIB typically
* provides NA<sigma v> with units depending on the reaction order). Implementations may
* use density/electron properties for weak processes or screening, and the composition
* vector for multi-body reactions.
*
* @param T9 Temperature in GK (10^9 K).
* @param rho Mass density (g cm^-3). May be unused for some reaction types.
* @param Ye Electron fraction. May be unused depending on the reaction type.
* @param mue Electron chemical potential. May be unused depending on the reaction type.
* @param Y Composition vector (molar abundances or number fractions) indexed consistently
* with index_to_species_map.
* @param index_to_species_map Mapping from state-vector index to Species, used to interpret Y.
* @return The reaction rate for the forward direction, with units/normalization defined by the
* specific model (implementation must document its convention).
*/
[[nodiscard]] virtual double calculate_rate(
double T9,
double rho,
double Ye,
double mue, const std::vector<double> &Y, const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
double mue,
const std::vector<double> &Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const = 0;
/**
* @brief AD-enabled reaction rate for algorithmic differentiation.
*
* This overload mirrors calculate_rate(double, ...) but operates on CppAD types to
* enable derivative calculations w.r.t. its inputs.
*
* @param T9 Temperature in GK as CppAD::AD<double>.
* @param rho Mass density as CppAD::AD<double>.
* @param Ye Electron fraction as CppAD::AD<double>.
* @param mue Electron chemical potential as CppAD::AD<double>.
* @param Y Composition vector as CppAD::AD<double> values.
* @param index_to_species_map Mapping from state-vector index to Species, used to interpret Y.
* @return The reaction rate as a CppAD::AD<double> value.
*/
[[nodiscard]] virtual CppAD::AD<double> calculate_rate(
CppAD::AD<double> T9,
CppAD::AD<double> rho,
CppAD::AD<double> Ye,
CppAD::AD<double> mue, const std::vector<CppAD::AD<double>>& Y, const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
CppAD::AD<double> mue,
const std::vector<CppAD::AD<double>>& Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const = 0;
/**
* @brief A stable, unique identifier for this reaction instance.
* @return String view of the reaction ID (stable across runs and suitable for lookups).
*/
[[nodiscard]] virtual std::string_view id() const = 0;
/**
* @brief Ordered list of reactant species.
*
* Multiplicity is represented by duplicates, e.g., (p, p) would list H1 twice.
* @return Const reference to the vector of reactants.
*/
[[nodiscard]] virtual const std::vector<fourdst::atomic::Species>& reactants() const = 0;
/**
* @brief Ordered list of product species.
*
* Multiplicity is represented by duplicates if applicable.
* @return Const reference to the vector of products.
*/
[[nodiscard]] virtual const std::vector<fourdst::atomic::Species>& products() const = 0;
/**
* @brief True if the species appears as a reactant or a product.
* @param species Species to test.
* @return Whether the species participates in the reaction (either side).
*/
[[nodiscard]] virtual bool contains(const fourdst::atomic::Species& species) const = 0;
/**
* @brief True if the species appears among the reactants.
* @param species Species to test.
* @return Whether the species is a reactant.
*/
[[nodiscard]] virtual bool contains_reactant(const fourdst::atomic::Species& species) const = 0;
/**
* @brief True if the species appears among the products.
* @param species Species to test.
* @return Whether the species is a product.
*/
[[nodiscard]] virtual bool contains_product(const fourdst::atomic::Species& species) const = 0;
/**
* @brief Whether this object represents a reverse (backward) rate.
*
* Implementations may pair forward/reverse rates for detailed balance. This flag indicates
* that the parameterization corresponds to the reverse direction.
* @return True for a reverse rate, false for a forward rate.
*/
[[nodiscard]] virtual bool is_reverse() const = 0;
/**
* @brief Set of all unique species appearing in the reaction.
* @return Unordered set of all reactants and products (no duplicates).
*/
[[nodiscard]] virtual std::unordered_set<fourdst::atomic::Species> all_species() const = 0;
/**
* @brief Set of unique reactant species.
* @return Unordered set of reactant species (no duplicates).
*/
[[nodiscard]] virtual std::unordered_set<fourdst::atomic::Species> reactant_species() const = 0;
/**
* @brief Set of unique product species.
* @return Unordered set of product species (no duplicates).
*/
[[nodiscard]] virtual std::unordered_set<fourdst::atomic::Species> product_species() const = 0;
/**
* @brief Number of unique species involved in the reaction.
* @return Count of distinct species across reactants and products.
*/
[[nodiscard]] virtual size_t num_species() const = 0;
/**
* @brief Full stoichiometry map for this reaction.
*
* Coefficients are negative for reactants and positive for products; multiplicity is reflected
* in the magnitude (e.g., 2H -> He gives H: -2, He: +1).
* @return Map from Species to integer stoichiometric coefficient.
*/
[[nodiscard]] virtual std::unordered_map<fourdst::atomic::Species, int> stoichiometry() const = 0;
/**
* @brief Stoichiometric coefficient for a particular species.
* @param species Species for which to query the coefficient.
* @return Negative for reactants, positive for products, zero if absent.
*/
[[nodiscard]] virtual int stoichiometry(const fourdst::atomic::Species& species) const = 0;
/**
* @brief Stable content-based hash for this reaction.
*
* Intended for use in caches, sets, and order-independent hashing of Reaction collections.
* Implementations should produce the same value across processes for the same content and seed.
* @param seed Seed value to initialize/mix into the hash.
* @return 64-bit hash value.
*/
[[nodiscard]] virtual uint64_t hash(uint64_t seed) const = 0;
/**
* @brief Q-value of the reaction (typically MeV), positive if exothermic.
* @return Reaction Q-value used for energy accounting.
*/
[[nodiscard]] virtual double qValue() const = 0;
/**
* @brief Convenience: energy generation rate from this reaction (double version).
*
* Default implementation multiplies the scalar rate by the reaction Q-value. Electron
* quantities (Ye, mue) are ignored in this default, so override in derived classes if
* needed. Sign convention follows qValue().
*
* @param T9 Temperature in GK (10^9 K).
* @param rho Mass density (g cm^-3).
* @param Ye Electron fraction (ignored by default implementation).
* @param mue Electron chemical potential (ignored by default implementation).
* @param Y Composition vector.
* @param index_to_species_map Mapping from state-vector index to Species.
* @return Energy generation rate, typically rate * qValue().
*/
[[nodiscard]] virtual double calculate_energy_generation_rate(
double T9,
double rho,
double Ye,
double mue, const std::vector<double>& Y, const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
const double T9,
const double rho,
const double Ye,
double mue,
const std::vector<double>& Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const {
return calculate_rate(T9, rho, 0, 0, Y, index_to_species_map) * qValue();
}
/**
* @brief Convenience: AD-enabled energy generation rate (AD version).
*
* Default implementation multiplies the AD rate by the reaction Q-value. Electron
* quantities (Ye, mue) are ignored in this default, so override if they contribute.
*
* @param T9 Temperature in GK as CppAD::AD<double>.
* @param rho Mass density as CppAD::AD<double>.
* @param Ye Electron fraction as CppAD::AD<double> (ignored by default).
* @param mue Electron chemical potential as CppAD::AD<double> (ignored by default).
* @param Y Composition vector as CppAD::AD<double> values.
* @param index_to_species_map Mapping from state-vector index to Species.
* @return Energy generation rate as CppAD::AD<double>.
*/
[[nodiscard]] virtual CppAD::AD<double> calculate_energy_generation_rate(
const CppAD::AD<double>& T9,
const CppAD::AD<double>& rho,
const CppAD::AD<double> &Ye,
const CppAD::AD<double> &mue, const std::vector<CppAD::AD<double>>& Y, const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
const CppAD::AD<double> &mue,
const std::vector<CppAD::AD<double>>& Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const {
return calculate_rate(T9, rho, {}, {}, Y, index_to_species_map) * qValue();
}
[[nodiscard]] virtual double calculate_forward_rate_log_derivative(double T9, double rho, double Ye, double mue, const fourdst::composition::Composition& comp) const = 0;
/**
* @brief Logarithmic partial derivative of the rate with respect to temperature.
*
* Implementations return d(ln rate)/d(ln T9) or an equivalent measure (as documented by the
* concrete class), evaluated at the provided state.
*
* @param T9 Temperature in GK (10^9 K).
* @param rho Mass density (g cm^-3).
* @param Ye Electron fraction.
* @param mue Electron chemical potential.
* @param comp Composition object providing composition in a convenient form.
* @return The logarithmic temperature derivative of the rate.
*/
[[nodiscard]] virtual double calculate_log_rate_partial_deriv_wrt_T9(
double T9,
double rho,
double Ye,
double mue,
const fourdst::composition::Composition& comp
) const = 0;
/**
* @brief Category of this reaction (e.g., REACLIB, WEAK, LOGICAL_REACLIB).
* @return Enumerated reaction type for runtime dispatch and filtering.
*/
[[nodiscard]] virtual ReactionType type() const = 0;
/**
* @brief Polymorphic deep copy.
* @return A std::unique_ptr owning a new Reaction equal to this one.
*/
[[nodiscard]] virtual std::unique_ptr<Reaction> clone() const = 0;
friend std::ostream& operator<<(std::ostream& os, const Reaction& r) {
@@ -161,15 +348,15 @@ namespace gridfire::reaction {
* @param reverse True if this is a reverse reaction rate.
*/
ReaclibReaction(
const std::string_view id,
const std::string_view peName,
const int chapter,
std::string_view id,
std::string_view peName,
int chapter,
const std::vector<fourdst::atomic::Species> &reactants,
const std::vector<fourdst::atomic::Species> &products,
const double qValue,
const std::string_view label,
double qValue,
std::string_view label,
const RateCoefficientSet &sets,
const bool reverse = false);
bool reverse = false);
/**
* @brief Calculates the reaction rate for a given temperature.
@@ -185,7 +372,10 @@ namespace gridfire::reaction {
double T9,
double rho,
double Ye,
double mue, const std::vector<double> &Y, const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
double mue,
const std::vector<double> &Y,
const std::unordered_map<size_t,
fourdst::atomic::Species>& index_to_species_map
) const override;
/**
@@ -205,7 +395,13 @@ namespace gridfire::reaction {
CppAD::AD<double> mue, const std::vector<CppAD::AD<double>>& Y, const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const override;
[[nodiscard]] double calculate_forward_rate_log_derivative(double T9, double rho, double Ye, double mue, const fourdst::composition::Composition& comp) const override;
[[nodiscard]] double calculate_log_rate_partial_deriv_wrt_T9(
double T9,
double rho,
double Ye,
double mue,
const fourdst::composition::Composition& comp
) const override;
/**
* @brief Gets the reaction name in (projectile, ejectile) notation.
@@ -448,7 +644,7 @@ namespace gridfire::reaction {
double mue, const std::vector<double> &Y, const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const override;
[[nodiscard]] double calculate_forward_rate_log_derivative(
[[nodiscard]] double calculate_log_rate_partial_deriv_wrt_T9(
double T9,
double rho,
double Ye, double mue, const fourdst::composition::Composition& comp
@@ -686,4 +882,3 @@ namespace gridfire::reaction {
ReactionSet packReactionSet(const ReactionSet& reactionSet);
}

View File

@@ -24,29 +24,135 @@
namespace gridfire::rates::weak {
/**
* @class WeakReactionMap
* @brief Index of available weak reactions keyed by species.
*
* Builds an in-memory map from the compiled weak-rate tables and provides
* simple query helpers to retrieve all weak reactions or those that involve
* a particular nuclide.
*
* Implementation summary: the constructor iterates over UNIFIED_WEAK_DATA and
* inserts entries keyed by the parent Species. For each channel (β−, β+, e-capture,
* e+-capture), if the tabulated log10(rate) is above the sentinel (-60), a
* WeakReactionEntry is pushed containing the grids t9, log10(rho*Ye), mu_e, the log10(rate),
* and the corresponding log10(neutrino loss) column.
*/
class WeakReactionMap {
public:
/**
* @brief Construct the map by loading all weak reaction entries.
* @post All valid reactions from the compiled data are available via
* get_all_reactions() and get_species_reactions().
* Implementation: iterates UNIFIED_WEAK_DATA, filters any log(rate) <= -60,
* and groups entries by parent Species.
*/
WeakReactionMap();
~WeakReactionMap() = default;
/**
* @brief Return a flat list of all weak reaction entries.
* @return Vector of WeakReactionEntry records.
* @par Example
* @code
* WeakReactionMap map;
* auto all = map.get_all_reactions();
* // iterate or group as needed
* @endcode
*/
[[nodiscard]] std::vector<WeakReactionEntry> get_all_reactions() const;
/**
* @brief Get all weak reaction entries for a given species.
* @param species Nuclide to query (A,Z).
* @return expected<vector<WeakReactionEntry>, WeakMapError>
* containing reactions on success or SPECIES_NOT_FOUND on failure.
* @par Example
* @code
* using fourdst::atomic::Species;
* WeakReactionMap map;
* Species fe52 = fourdst::atomic::az_to_species(52, 26);
* if (auto res = map.get_species_reactions(fe52); res) {
* for (const auto& e : *res) { } // use e
* } else {
* // handle WeakMapError::SPECIES_NOT_FOUND
* }
* @endcode
*/
[[nodiscard]] std::expected<std::vector<WeakReactionEntry>, WeakMapError> get_species_reactions(
const fourdst::atomic::Species &species) const;
const fourdst::atomic::Species &species
) const;
/**
* @brief Get all weak reaction entries for a given species by name.
* @param species_name Symbolic name (e.g., "Fe52").
* @return expected<vector<WeakReactionEntry>, WeakMapError>
* containing reactions on success or SPECIES_NOT_FOUND on failure.
* @par Example
* @code
* WeakReactionMap map;
* if (auto res = map.get_species_reactions("Fe52"); res) {
* // use *res
* }
* @endcode
*/
[[nodiscard]] std::expected<std::vector<WeakReactionEntry>, WeakMapError> get_species_reactions(
const std::string &species_name) const;
const std::string &species_name
) const;
private:
std::unordered_map<fourdst::atomic::Species, std::vector<WeakReactionEntry>> m_weak_network;
};
/**
* @class WeakReaction
* @brief Concrete Reaction representing a single weak process (beta±, e/e+ capture).
*
* Wraps interpolation logic for tabulated weak rates and provides both scalar and AD
* interfaces for rate and energy generation. The reactants/products are the parent/daughter
* nuclei of the weak process.
*
* @details the product nucleus is resolved from (A,Z) and channel via
* simple charge-changing rules (β−: Z+1; β+: Z1; e capture: Z1; e+ capture: Z+1).
* The reaction ID is formatted like "Parent(channel)Product" with ν/ν̄ decorations, and
* an internal CppAD atomic (AtomicWeakRate) is prepared for AD energy calculations.
*/
class WeakReaction final : public reaction::Reaction {
public:
/**
* @brief Construct a WeakReaction for a specific weak channel and parent species.
* @param species Parent nuclide undergoing the weak process.
* @param type The weak reaction channel (beta, beta+, e capture, e+ capture).
* @param interpolator Reference to a WeakRateInterpolator providing tabulated data.
* @pre The product nuclide must be resolvable for the given (species, type).
* @post Object is ready to compute rates using the provided interpolator.
* @throws std::runtime_error If the product species cannot be resolved for the channel
* (product resolution uses the charge-changing rules described above).
*/
explicit WeakReaction(
const fourdst::atomic::Species &species,
WeakReactionType type,
const WeakRateInterpolator& interpolator
);
/**
* @brief Scalar weak reaction rate λ(T9, rho, Ye, μe) in 1/s.
*
* @details Performs a single interpolation of the weak-rate tables at (T9, log10(rho*Ye), μe).
* If the selected log10(rate) is ≤ sentinel (-60), returns 0; otherwise returns 10^{log10(rate)}.
* On interpolation failure, throws with a message including (A,Z) and the state point.
*
* @param T9 Temperature in GK (1e9 K).
* @param rho Mass density (g cm^-3).
* @param Ye Electron fraction.
* @param mue Electron chemical potential (MeV).
* @param Y Composition vector (unused for weak channels).
* @param index_to_species_map Index-to-species map (unused for weak channels).
* @return Reaction rate (1/s).
* @throws std::runtime_error On interpolation failure.
* @par Example
* @code
* double lambda = rxn.calculate_rate(2.0, 1e8, 0.4, 1.5, {}, {});
* @endcode
*/
[[nodiscard]] double calculate_rate(
double T9,
double rho,
@@ -55,6 +161,26 @@ namespace gridfire::rates::weak {
const std::vector<double> &Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const override;
/**
* @brief AD-enabled weak reaction rate λ(T9, rho, Ye, μe) in 1/s.
*
* @details Current implementation returns 0.0. AD support is provided for the energy-generation
* overload below using an internal CppAD atomic that evaluates both the rate and neutrino
* loss consistently. A future implementation may mirror that atomic here and return the AD rate.
*
* @param T9 Temperature in GK (AD type).
* @param rho Mass density (g cm^-3, AD type).
* @param Ye Electron fraction (AD type).
* @param mue Electron chemical potential (MeV, AD type).
* @param Y Composition vector (unused for weak channels).
* @param index_to_species_map Index-to-species map (unused for weak channels).
* @return Reaction rate (1/s) as CppAD::AD<double> (currently 0.0).
* @par Example
* @code
* using AD = CppAD::AD<double>;
* AD lambda_ad = rxn.calculate_rate(AD(3.0), AD(1e7), AD(0.5), AD(2.0), {}, {});
* @endcode
*/
[[nodiscard]] CppAD::AD<double> calculate_rate(
CppAD::AD<double> T9,
CppAD::AD<double> rho,
@@ -63,20 +189,108 @@ namespace gridfire::rates::weak {
const std::vector<CppAD::AD<double>> &Y,
const std::unordered_map<size_t,fourdst::atomic::Species>& index_to_species_map
) const override;
[[nodiscard]] std::string_view id() const override { return m_id; }
[[nodiscard]] const std::vector<fourdst::atomic::Species> &reactants() const override { return {m_reactant}; }
[[nodiscard]] const std::vector<fourdst::atomic::Species> &products() const override { return {m_product}; }
/**
* @brief Unique identifier string for the weak channel.
* @return A stable string view (e.g., "Fe52(e-,ν)Mn52").
*/
[[nodiscard]] std::string_view id() const override;
/**
* @brief Reactants list (parent nuclide only).
* @return Vector with the parent species.
*/
[[nodiscard]] const std::vector<fourdst::atomic::Species> &reactants() const override;
/**
* @brief Products list (daughter nuclide only).
* @return Vector with the daughter species.
*/
[[nodiscard]] const std::vector<fourdst::atomic::Species> &products() const override;
/**
* @brief Check if a species participates in this weak reaction.
*/
[[nodiscard]] bool contains(const fourdst::atomic::Species &species) const override;
/**
* @brief Check if a species is the reactant (parent).
*/
[[nodiscard]] bool contains_reactant(const fourdst::atomic::Species &species) const override;
/**
* @brief Check if a species is the product (daughter).
*/
[[nodiscard]] bool contains_product(const fourdst::atomic::Species &species) const override;
/**
* @brief Set of both parent and daughter species.
*/
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> all_species() const override;
/**
* @brief Singleton set containing only the parent species.
*/
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> reactant_species() const override;
/**
* @brief Singleton set containing only the daughter species.
*/
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> product_species() const override;
[[nodiscard]] size_t num_species() const override { return 2; } // Always 2 for weak reactions
/**
* @brief Number of unique species involved (always 2 for weak reactions).
*/
[[nodiscard]] size_t num_species() const override;
/**
* @brief Full stoichiometry map: parent -1, daughter +1.
*/
[[nodiscard]] std::unordered_map<fourdst::atomic::Species, int> stoichiometry() const override;
/**
* @brief Stoichiometric coefficient for a species: -1 (parent), +1 (daughter), 0 otherwise.
*/
[[nodiscard]] int stoichiometry(const fourdst::atomic::Species &species) const override;
/**
* @brief Content-based 64-bit hash for this reaction.
*/
[[nodiscard]] uint64_t hash(uint64_t seed) const override;
/**
* @brief Q-value (MeV) based on nuclear mass differences and channel.
*
* Computes Q = (M_parent M_daughter) c^2 converted to MeV. For β+ decay subtract 2 m_e c^2,
* for e+ capture add 2 m_e c^2; for β− and e capture it is just the nuclear mass difference.
* Neutrino rest mass is ignored.
*/
[[nodiscard]] double qValue() const override;
/**
* @brief Net energy generation rate (MeV/s) for this weak reaction.
*
* Interpolates once to obtain both the log10(rate) and the appropriate log10(neutrino-loss)
* for the channel, converts to linear values, computes E_deposited = Q ν_loss, and returns
* λ · E_deposited. Throws on interpolation failure.
*
* Channel mapping for neutrino-loss column:
* - β− decay and e+ capture: use log_antineutrino_loss_bd
* - β+ decay and e capture: use log_neutrino_loss_ec
*
* @param T9 Temperature in GK.
* @param rho Density in g cm^-3.
* @param Ye Electron fraction.
* @param mue Electron chemical potential (MeV).
* @param Y Composition vector (unused for weak channels).
* @param index_to_species_map Index-to-species map (unused for weak channels).
* @return Energy generation rate in MeV/s.
* @throws std::runtime_error On interpolation failure.
* @par Example
* @code
* double eps = rxn.calculate_energy_generation_rate(3.0, 1e7, 0.5, 2.0, {}, {});
* @endcode
*/
[[nodiscard]] double calculate_energy_generation_rate(
double T9,
double rho,
@@ -85,6 +299,16 @@ namespace gridfire::rates::weak {
const std::vector<double>& Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const override;
/**
* @brief AD-enabled net energy generation rate (MeV/s).
*
* Uses an internal CppAD atomic to compute two outputs at once: the rate λ and the neutrino
* loss ν_loss at (T9, log10(rho*Ye), μe). Returns λ · (Q ν_loss). The atomic throws on
* interpolation failure.
*
* @throws std::runtime_error If the atomic rate evaluation fails to interpolate.
*/
[[nodiscard]] CppAD::AD<double> calculate_energy_generation_rate(
const CppAD::AD<double> &T9,
const CppAD::AD<double> &rho,
@@ -93,18 +317,64 @@ namespace gridfire::rates::weak {
const std::vector<CppAD::AD<double>> &Y,
const std::unordered_map<size_t, fourdst::atomic::Species> &index_to_species_map
) const override;
[[nodiscard]] double calculate_forward_rate_log_derivative(
/**
* @brief Logarithmic temperature sensitivity of the rate at the given state.
*
* Implementation status: requests derivative tables from the interpolator and throws on
* failure; otherwise the function is not yet implemented and does not return a value.
* Avoid calling until implemented.
*
* @param T9 Temperature in GK.
* @param rho Density in g cm^-3.
* @param Ye Electron fraction.
* @param mue Electron chemical potential (MeV).
* @param composition Composition context (not used by weak channels presently).
* @return d ln λ / d ln T9.
* @throws std::runtime_error On interpolation failure.
*/
[[nodiscard]] double calculate_log_rate_partial_deriv_wrt_T9(
double T9,
double rho,
double Ye,
double mue,
const fourdst::composition::Composition& composition
) const override;
[[nodiscard]] reaction::ReactionType type() const override { return reaction::ReactionType::WEAK; }
/**
* @brief Reaction type tag for runtime dispatch.
*/
[[nodiscard]] reaction::ReactionType type() const override;
/**
* @brief Polymorphic deep copy.
*/
[[nodiscard]] std::unique_ptr<Reaction> clone() const override;
[[nodiscard]] bool is_reverse() const override { return false; };
/**
* @brief Weak reactions are parameterized in the forward sense (never reverse).
*/
[[nodiscard]] bool is_reverse() const override;
/**
* @brief Access the underlying rate interpolator used by this reaction.
*/
[[nodiscard]] const WeakRateInterpolator& getWeakRateInterpolator() const;
private:
/**
* @brief Internal unified implementation for scalar/AD rate evaluation.
* @tparam T double or CppAD::AD<double>.
* @param T9, rho, Ye, mue Thermodynamic state.
* @param Y Composition vector (unused for weak channels).
* @param index_to_species_map Index-to-species map (unused for weak channels).
* @return Reaction rate (1/s) as T. For double, performs table interpolation and returns
* 0 when the tabulated log10(rate) ≤ sentinel; for AD, calls the atomic and returns
* the first output.
* @pre T9 > 0, rho > 0, 0 < Ye <= 1.
* @post No persistent state is modified.
* @throws std::runtime_error If interpolation fails (double path) or the atomic fails (AD path).
*/
template<IsArithmeticOrAD T>
T calculate_rate(
T T9,
@@ -115,11 +385,37 @@ namespace gridfire::rates::weak {
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const;
/**
* @brief Extract the channel-specific log10(rate) from an interpolated payload.
* Mapping: β→log_beta_minus, β+→log_beta_plus, e capture→log_electron_capture,
* e+ capture→log_positron_capture.
*/
double get_log_rate_from_payload(const WeakRatePayload& payload) const;
/**
* @brief Extract the channel-specific log10(neutrino loss) from a payload.
* Mapping: β−/e+ capture→log_antineutrino_loss_bd; β+/e capture→log_neutrino_loss_ec.
*/
double get_log_neutrino_loss_from_payload(const WeakRatePayload& payload) const;
private:
/**
* @brief CppAD atomic that wraps weak-rate interpolation for AD evaluation.
*
* Forward pass computes two outputs (λ, ν_loss) by interpolating the tables at the
* provided state; reverse pass uses derivative tables to backpropagate adjoints for
* all three inputs (T9, log10(rho*Ye), μe). Sparsity routines declare full dependence
* of both outputs on all inputs.
*/
class AtomicWeakRate final : public CppAD::atomic_base<double> {
public:
/**
* @brief Construct the atomic operation for a specific (A,Z) and channel.
* @param interpolator Rate source.
* @param a Mass number A of the parent.
* @param z Proton number Z of the parent.
* @param type Weak channel.
*/
AtomicWeakRate(
const WeakRateInterpolator& interpolator,
const size_t a,
@@ -132,6 +428,11 @@ namespace gridfire::rates::weak {
m_z(z) ,
m_type(type) {}
/**
* @brief Forward pass: compute rate and neutrino-loss values for AD.
* On failure to interpolate, throws a std::runtime_error with details; sets output
* sparsity such that both outputs depend on all inputs when any input is variable.
*/
bool forward(
size_t p,
size_t q,
@@ -140,6 +441,12 @@ namespace gridfire::rates::weak {
const CppAD::vector<double>& tx,
CppAD::vector<double>& ty
) override;
/**
* @brief Reverse pass: propagate adjoints using tabulated derivatives.
* Uses d log10 columns, converting to linear-scale derivatives via ln(10) scaling and
* chain rule with the forward-pass outputs.
*/
bool reverse(
size_t q,
const CppAD::vector<double>& tx,
@@ -147,11 +454,19 @@ namespace gridfire::rates::weak {
CppAD::vector<double>& px,
const CppAD::vector<double>& py
) override;
/**
* @brief Forward-mode sparsity for Jacobian.
*/
bool for_sparse_jac(
size_t q,
const CppAD::vector<std::set<size_t>>&r,
CppAD::vector<std::set<size_t>>& s
) override;
/**
* @brief Reverse-mode sparsity for Jacobian.
*/
bool rev_sparse_jac(
size_t q,
const CppAD::vector<std::set<size_t>>&rt,
@@ -190,6 +505,9 @@ namespace gridfire::rates::weak {
fourdst::atomic::Species m_reactant;
fourdst::atomic::Species m_product;
std::vector<fourdst::atomic::Species> m_reactants;
std::vector<fourdst::atomic::Species> m_products;
size_t m_reactant_a;
size_t m_reactant_z;
size_t m_product_a;
@@ -200,10 +518,11 @@ namespace gridfire::rates::weak {
const WeakRateInterpolator& m_interpolator;
AtomicWeakRate m_atomic;
mutable AtomicWeakRate m_atomic;
};
// template implementation lives at the end of the header for AD instantiation
template<IsArithmeticOrAD T>
T WeakReaction::calculate_rate(
T T9,
@@ -218,7 +537,7 @@ namespace gridfire::rates::weak {
T rateConstant = static_cast<T>(0.0);
if constexpr (std::is_same_v<T, CppAD::AD<double>>) { // Case where T is an AD type
std::vector<T> ax = {T9, log_rhoYe, mue};
std::vector<T> ay(1);
std::vector<T> ay(2);
m_atomic(ax, ay);
rateConstant = static_cast<T>(ay[0]);
} else { // The case where T is of type double
@@ -251,5 +570,3 @@ namespace gridfire::rates::weak {
}
}

View File

@@ -7,18 +7,79 @@
#include <cstdint>
#include <vector>
#include <expected>
#include <array>
namespace gridfire::rates::weak {
/**
* @class WeakRateInterpolator
* @brief 3D table interpolator for tabulated weak reaction data by isotope.
*
* Builds per-isotope 3D grids over (T9, log10(rho*Ye), mu_e) and provides:
* - Trilinear interpolation of the tabulated log10(rate) and neutrino-loss fields
* into a WeakRatePayload via get_rates().
* - Finite-difference estimates of partial derivatives via get_rate_derivatives().
*
* Implementation summary (constructor): rows are grouped by (A,Z), then each group's unique
* axis values are collected and sorted to form the three axes; the 3D payload array is
* populated at each lattice point with the 6 log10() fields from the raw table.
*/
class WeakRateInterpolator {
public:
/**
* @brief Raw weak-rate table type expected by the constructor.
*
* The size must match the number of rows compiled into the weak-rate library.
*/
using RowDataTable = std::array<RateDataRow, 77400>; // Total number of entries in the weak rate table NOTE: THIS MUST EQUAL THE VALUE IN weak_rate_library.h
/**
* @brief Construct the interpolator from raw weak-rate rows.
*
* Groups rows by isotope (A,Z), extracts unique sorted axes for T9, log10(rho*Ye), and mu_e,
* and fills an internal regular grid with the log10(rate) and neutrino-loss payloads at each node.
* No interpolation occurs at construction time.
*/
explicit WeakRateInterpolator(const RowDataTable& raw_data);
/**
* @brief List isotopes for which tables are available.
* @return Vector of available Species (A,Z) derived from internal tables.
* @throws std::runtime_error If any packed (A,Z) cannot be converted to Species.
* @par Example
* @code
* WeakRateInterpolator interp(rows);
* auto isotopes = interp.available_isotopes();
* @endcode
*/
[[nodiscard]] std::vector<fourdst::atomic::Species> available_isotopes() const;
/**
* @brief Trilinear interpolation of weak-rate payload at a state.
*
* Interpolates the 6 log10() fields (rates and neutrino losses) at the given state
* for the requested isotope. If the isotope is unknown or the state lies outside
* the tabulated ranges, returns an error via std::expected with detailed bounds info.
*
* @param A Mass number of the isotope.
* @param Z Proton number of the isotope.
* @param t9 Temperature in GK (10^9 K).
* @param log_rhoYe Log10 of rho*Ye (cgs density times electron fraction).
* @param mu_e Electron chemical potential (MeV).
* @return expected<WeakRatePayload, InterpolationError>: payload on success;
* InterpolationError::UNKNOWN_SPECIES_ERROR if (A,Z) not present; or
* InterpolationError::BOUNDS_ERROR if any coordinate is outside the table
* (with per-axis bounds included).
* @par Example
* @code
* if (auto res = interp.get_rates(52, 26, 3.0, 6.0, 2.0); res) {
* const WeakRatePayload& p = *res;
* } else {
* // inspect res.error().type and optional bounds info
* }
* @endcode
*/
[[nodiscard]] std::expected<WeakRatePayload, InterpolationError> get_rates(
uint16_t A,
uint8_t Z,
@@ -27,6 +88,28 @@ namespace gridfire::rates::weak {
double mu_e
) const;
/**
* @brief Finite-difference partial derivatives of the log10() fields.
*
* Uses central differences with small fixed (1e-6) perturbations in each variable
* (T9, log10(rho*Ye), mu_e) and returns arrays of d(log10(field))/d(var) for all fields.
* If any perturbed state falls outside the table, returns a BOUNDS_ERROR with per-axis
* bounds; if the isotope is unknown, returns UNKNOWN_SPECIES_ERROR.
*
* @param A Mass number of the isotope.
* @param Z Proton number of the isotope.
* @param t9 Temperature in GK (10^9 K).
* @param log_rhoYe Log10 of rho*Ye (cgs density times electron fraction).
* @param mu_e Electron chemical potential (MeV).
* @return expected<WeakRateDerivatives, InterpolationError>: derivative payload on success;
* otherwise an InterpolationError as described above.
* @par Example
* @code
* if (auto d = interp.get_rate_derivatives(52, 26, 3.0, 6.0, 2.0); d) {
* // use d->d_log_beta_minus[0..2], etc.
* }
* @endcode
*/
[[nodiscard]] std::expected<WeakRateDerivatives, InterpolationError> get_rate_derivatives(
uint16_t A,
uint8_t Z,
@@ -35,8 +118,16 @@ namespace gridfire::rates::weak {
double mu_e
) const;
private:
/**
* @brief Pack (A,Z) into a 32-bit key used for the internal map.
*
* Layout: (A << 8) | Z. To unpack, use (key >> 8) for A and (key & 0xFF) for Z.
*/
static uint32_t pack_isotope_id(uint16_t A, uint8_t Z);
/**
* @brief Per-isotope grids over (T9, log10(rho*Ye), mu_e) with payloads at lattice nodes.
*/
std::unordered_map<uint32_t, IsotopeGrid> m_rate_table;
};

View File

@@ -1,5 +1,14 @@
#pragma once
/**
* @file weak_types.h
* @brief Plain data structures and enums for weak reaction tables, interpolation payloads, and errors.
*
* This header defines the raw row format loaded from the unified weak-rate library, simple
* enumerations for channels and axes, compact payloads for interpolated values and derivatives,
* and error-reporting structures used by the interpolator.
*/
#include <cstdint>
#include <array>
#include <vector>
@@ -8,27 +17,48 @@
#include <ostream>
namespace gridfire::rates::weak {
/**
* @brief One row of the unified weak-rate data table for a specific isotope and state.
*
* Units and meanings:
* - t9: temperature in GK (10^9 K).
* - log_rhoye: base-10 logarithm of rho*Ye where rho is g cm^-3 and Ye is electron fraction.
* - mu_e: electron chemical potential in MeV.
* - log_*: base-10 logarithm of the tabulated rate or neutrino-energy loss term.
*
* Channel mappings:
* - beta-plus (β+): log_beta_plus, neutrino-loss column log_neutrino_loss_ec.
* - electron capture (e cap): log_electron_capture, neutrino-loss column log_neutrino_loss_ec.
* - beta-minus (β−): log_beta_minus, neutrino-loss column log_antineutrino_loss_bd.
* - positron capture (e+ cap): log_positron_capture, neutrino-loss column log_antineutrino_loss_bd.
*/
struct RateDataRow {
uint16_t A;
uint8_t Z;
float t9;
float log_rhoye;
float mu_e;
float log_beta_plus;
float log_electron_capture;
float log_neutrino_loss_ec;
float log_beta_minus;
float log_positron_capture;
float log_antineutrino_loss_bd;
uint16_t A; ///< Mass number.
uint8_t Z; ///< Proton number.
float t9; ///< Temperature in GK.
float log_rhoye; ///< log10(rho*Ye) (cgs density times electron fraction).
float mu_e; ///< Electron chemical potential (MeV).
float log_beta_plus; ///< log10(β+ decay rate).
float log_electron_capture; ///< log10(e capture rate).
float log_neutrino_loss_ec; ///< log10(neutrino loss for β+ and e capture).
float log_beta_minus; ///< log10(β− decay rate).
float log_positron_capture; ///< log10(e+ capture rate).
float log_antineutrino_loss_bd; ///< log10(antineutrino loss for β− and e+ capture).
};
/**
* @brief Weak reaction channel identifiers.
*/
enum class WeakReactionType {
BETA_PLUS_DECAY,
BETA_MINUS_DECAY,
ELECTRON_CAPTURE,
POSITRON_CAPTURE,
BETA_PLUS_DECAY, ///< β+ decay: Z -> Z-1 + e+ + ν_e
BETA_MINUS_DECAY, ///< β− decay: Z -> Z+1 + e + ν̄_e
ELECTRON_CAPTURE, ///< e capture: (Z, e) -> Z-1 + ν_e
POSITRON_CAPTURE, ///< e+ capture: (Z, e+) -> Z+1 + ν̄_e
};
/**
* @brief Enumeration of neutrino flavors (for potential extensions and tagging).
*/
enum class NeutrinoTypes {
ELECTRON_NEUTRINO,
ELECTRON_ANTINEUTRINO,
@@ -38,20 +68,35 @@ namespace gridfire::rates::weak {
TAU_ANTINEUTRINO
};
/**
* @brief Lookup errors for WeakReactionMap queries.
*/
enum class WeakMapError {
SPECIES_NOT_FOUND,
SPECIES_NOT_FOUND, ///< No entries for the requested Species.
UNKNOWN_ERROR
};
/**
* @brief Interpolated weak-rate payload at a single state.
*
* All values are base-10 logarithms of the corresponding rates or neutrino-loss terms.
* Consumers typically convert with pow(10, log_value) and may apply sentinel thresholds
* at the usage site.
*/
struct WeakRatePayload {
double log_beta_plus;
double log_electron_capture;
double log_neutrino_loss_ec;
double log_beta_minus;
double log_positron_capture;
double log_antineutrino_loss_bd;
double log_beta_plus; ///< log10(β+ decay rate).
double log_electron_capture; ///< log10(e capture rate).
double log_neutrino_loss_ec; ///< log10(neutrino loss for β+ and e capture).
double log_beta_minus; ///< log10(β− decay rate).
double log_positron_capture; ///< log10(e+ capture rate).
double log_antineutrino_loss_bd; ///< log10(antineutrino loss for β− and e+ capture).
};
/**
* @brief Partial derivatives of the log10() fields w.r.t. (T9, log10(rho*Ye), mu_e).
*
* Array ordering is [d/dT9, d/dlogRhoYe, d/dMuE] for each corresponding field.
*/
struct WeakRateDerivatives {
// Each array holds [d/dT9, d/dlogRhoYe, d/dMuE]
std::array<double, 3> d_log_beta_plus;
@@ -62,45 +107,74 @@ namespace gridfire::rates::weak {
std::array<double, 3> d_log_antineutrino_loss_bd;
};
/**
* @brief Error categories for interpolation attempts.
*/
enum class InterpolationErrorType {
BOUNDS_ERROR,
UNKNOWN_SPECIES_ERROR,
BOUNDS_ERROR, ///< Query outside the per-axis min/max of the table.
UNKNOWN_SPECIES_ERROR, ///< Requested (A,Z) not present in the tables.
UNKNOWN_ERROR
};
/**
* @brief Human-readable names for InterpolationErrorType.
*/
inline std::unordered_map<InterpolationErrorType, std::string_view> InterpolationErrorTypeMap = {
{InterpolationErrorType::BOUNDS_ERROR, "Bounds Error"},
{InterpolationErrorType::UNKNOWN_SPECIES_ERROR, "Unknown Species Error"},
{InterpolationErrorType::UNKNOWN_ERROR, "Unknown Error"}
};
/**
* @brief Axes of the interpolation table.
*/
enum class TableAxes {
T9,
LOG_RHOYE,
MUE
T9, ///< Temperature in GK.
LOG_RHOYE, ///< log10(rho*Ye).
MUE ///< Electron chemical potential (MeV).
};
/**
* @brief Detailed bounds information for a BOUNDS_ERROR.
*/
struct BoundsErrorInfo {
TableAxes axis;
double axisMinValue;
double axisMaxValue;
double queryValue;
TableAxes axis; ///< Axis on which the error occurred.
double axisMinValue; ///< Minimum tabulated value on the axis.
double axisMaxValue; ///< Maximum tabulated value on the axis.
double queryValue; ///< Requested value.
};
/**
* @brief Interpolation error with optional per-axis bounds details.
*
* For BOUNDS_ERROR, boundsErrorInfo may contain an entry per offending axis.
*/
struct InterpolationError {
InterpolationErrorType type;
InterpolationErrorType type; ///< Error category.
std::optional<std::unordered_map<TableAxes, BoundsErrorInfo>> boundsErrorInfo = std::nullopt;
};
/**
* @brief Regular 3D grid and payloads for a single isotope (A,Z).
*
* Axes are monotonically increasing per dimension. Data vector is laid out in
* row-major order with index computed as:
* index = ((i_t9 * rhoYe_axis.size() + j_rhoYe) * mue_axis.size()) + k_mue
*/
struct IsotopeGrid {
std::vector<double> t9_axis;
std::vector<double> rhoYe_axis;
std::vector<double> mue_axis;
std::vector<double> t9_axis; ///< Unique sorted T9 grid.
std::vector<double> rhoYe_axis;///< Unique sorted log10(rho*Ye) grid.
std::vector<double> mue_axis; ///< Unique sorted mu_e grid.
// index = (i_t9 * logRhoYe_axis.size() + j_rhoYe) + mue_axis.size() + k_mue
std::vector<WeakRatePayload> data;
// index = ((i_t9 * rhoYe_axis.size() + j_rhoYe) * mue_axis.size()) + k_mue
std::vector<WeakRatePayload> data; ///< Payloads at each grid node.
};
/**
* @brief Abbreviated channel name used in printing and IDs.
* @param t Channel enum.
* @return Short name: bp, bm, ec, or pc.
*/
constexpr std::string_view weak_reaction_type_name(const WeakReactionType t) noexcept {
switch (t) {
case WeakReactionType::BETA_PLUS_DECAY: return "bp";
@@ -111,13 +185,25 @@ namespace gridfire::rates::weak {
return "Unknown";
}
/**
* @brief A single weak-reaction data point (type, state, and log values).
*
* All rates and losses are base-10 logarithms. Useful for listing and filtering
* weak entries for a Species.
*
* @par Example
* @code
* WeakReactionEntry e{WeakReactionType::ELECTRON_CAPTURE, 3.0f, 6.0f, 2.0f, -2.3f, -1.7f};
* std::cout << e << "\n"; // prints a compact summary
* @endcode
*/
struct WeakReactionEntry {
WeakReactionType type;
float T9;
float log_rhoYe;
float mu_e;
float log_rate;
float log_neutrino_loss;
WeakReactionType type; ///< Channel.
float T9; ///< Temperature in GK.
float log_rhoYe; ///< log10(rho*Ye).
float mu_e; ///< Electron chemical potential (MeV).
float log_rate; ///< Channel-specific log10(rate).
float log_neutrino_loss; ///< Corresponding log10(neutrino or antineutrino energy loss).
friend std::ostream& operator<<(std::ostream& os, const WeakReactionEntry& reaction) {
os << "WeakReactionEntry(type=" << weak_reaction_type_name(reaction.type)