feat(weak): major weak rate progress

Major weak rate progress which includes: A refactor of many of the public interfaces for GridFire Engines to use composition objects as opposed to raw abundance vectors. This helps prevent index mismatch errors. Further, the weak reaction class has been expanded with the majority of an implimentation, including an atomic_base derived class to allow for proper auto diff tracking of the interpolated table results. Some additional changes are that the version of fourdst and libcomposition have been bumped to versions with smarter caching of intermediate vectors and a few bug fixes.
This commit is contained in:
2025-10-07 15:16:03 -04:00
parent 4f1c260444
commit 8a0b5b2c36
53 changed files with 2310 additions and 1759 deletions

View File

@@ -11,6 +11,7 @@
#include "cppad/cppad.hpp"
#include "fourdst/composition/composition.h"
/**
* @file reaction.h
@@ -78,8 +79,18 @@ namespace gridfire::reaction {
public:
virtual ~Reaction() = default;
[[nodiscard]] virtual double calculate_rate(double T9, double rho, const std::vector<double>& Y) const = 0;
[[nodiscard]] virtual CppAD::AD<double> calculate_rate(CppAD::AD<double> T9, CppAD::AD<double> rho, const std::vector<CppAD::AD<double>>& Y) const = 0;
[[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
) const = 0;
[[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
) const = 0;
[[nodiscard]] virtual std::string_view id() const = 0;
[[nodiscard]] virtual const std::vector<fourdst::atomic::Species>& reactants() const = 0;
@@ -103,7 +114,26 @@ namespace gridfire::reaction {
[[nodiscard]] virtual uint64_t hash(uint64_t seed) const = 0;
[[nodiscard]] virtual double qValue() const = 0;
[[nodiscard]] virtual double calculate_forward_rate_log_derivative(double T9, double rho, const std::vector<double>& Y) const = 0;
[[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 {
return calculate_rate(T9, rho, 0, 0, Y, index_to_species_map) * qValue();
}
[[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 {
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;
[[nodiscard]] virtual ReactionType type() const = 0;
@@ -145,21 +175,37 @@ namespace gridfire::reaction {
* @brief Calculates the reaction rate for a given temperature.
* @param T9 The temperature in units of 10^9 K.
* @param rho Density [Not used in this implementation].
* @param Y Molar abundances of species [Not used in this implementation].
* @param Ye
* @param mue
* @param Y
* @param index_to_species_map
* @return The calculated reaction rate.
*/
[[nodiscard]] double calculate_rate(double T9, double rho, const std::vector<double>& Y) const override;
[[nodiscard]] 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
) const override;
/**
* @brief Calculates the reaction rate for a given temperature using CppAD types.
* @param T9 The temperature in units of 10^9 K, as a CppAD::AD<double>.
* @param rho Density, as a CppAD::AD<double> [Not used in this implementation].
* @param Ye
* @param mue
* @param Y Molar abundances of species, as a vector of CppAD::AD<double> [Not used in this implementation].
* @param index_to_species_map
* @return The calculated reaction rate, as a CppAD::AD<double>.
*/
[[nodiscard]] CppAD::AD<double> calculate_rate(CppAD::AD<double> T9, CppAD::AD<double> rho, const std::vector<CppAD::AD<double>>& Y) const override;
[[nodiscard]] 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
) const override;
[[nodiscard]] double calculate_forward_rate_log_derivative(double T9, double rho, const std::vector<double>& Y) const override;
[[nodiscard]] double calculate_forward_rate_log_derivative(double T9, double rho, double Ye, double mue, const fourdst::composition::Composition& comp) const override;
/**
* @brief Gets the reaction name in (projectile, ejectile) notation.
@@ -389,12 +435,24 @@ namespace gridfire::reaction {
* @brief Calculates the total reaction rate by summing all source rates.
* @param T9 The temperature in units of 10^9 K.
* @param rho
* @param Ye
* @param mue
* @param Y
* @param index_to_species_map
* @return The total calculated reaction rate.
*/
[[nodiscard]] double calculate_rate(double T9, double rho, const std::vector<double>& Y) const override;
[[nodiscard]] 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
) const override;
[[nodiscard]] double calculate_forward_rate_log_derivative(double T9, double rho, const std::vector<double>& Y) 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]] ReactionType type() const override { return ReactionType::LOGICAL_REACLIB; }
@@ -404,10 +462,18 @@ namespace gridfire::reaction {
* @brief Calculates the total reaction rate using CppAD types.
* @param T9 The temperature in units of 10^9 K, as a CppAD::AD<double>.
* @param rho
* @param Ye
* @param mue
* @param Y
* @param index_to_species_map
* @return The total calculated reaction rate, as a CppAD::AD<double>.
*/
[[nodiscard]] CppAD::AD<double> calculate_rate(CppAD::AD<double> T9, CppAD::AD<double> rho, const std::vector<CppAD::AD<double>>& Y) const override;
[[nodiscard]] 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
) const override;
/** @name Iterators
* Provides iterators to loop over the rate coefficient sets.

View File

@@ -1,57 +1,254 @@
#pragma once
#include "fourdst/composition/atomicSpecies.h"
#define GRIDFIRE_WEAK_REACTION_LIB_SENTINEL -60.0
#include "gridfire/reaction/reaction.h"
#include "gridfire/reaction/weak/weak_types.h"
#include "gridfire/reaction/weak/weak_interpolator.h"
#include "gridfire/engine/engine_abstract.h"
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/constants/const.h"
#include "cppad/cppad.hpp"
#include <memory>
#include <unordered_map>
#include <expected>
#include <vector>
#include <string>
#include <string_view>
#include <unordered_set>
namespace gridfire::rates::weak {
enum class WeakReactionType {
BETA_PLUS_DECAY,
BETA_MINUS_DECAY,
ELECTRON_CAPTURE,
POSITRON_CAPTURE,
};
inline std::unordered_map<WeakReactionType, std::string> WeakReactionTypeNames = {
{WeakReactionType::BETA_PLUS_DECAY, "β+ Decay"},
{WeakReactionType::BETA_MINUS_DECAY, "β- Decay"},
{WeakReactionType::ELECTRON_CAPTURE, "e- Capture"},
{WeakReactionType::POSITRON_CAPTURE, "e+ Capture"},
};
struct WeakReaction {
WeakReactionType type;
float T9;
float log_rhoYe;
float mu_e;
float log_rate;
float log_neutrino_loss;
friend std::ostream& operator<<(std::ostream& os, const WeakReaction& reaction) {
os << "WeakReaction(type=" << WeakReactionTypeNames[reaction.type]
<< ", T9=" << reaction.T9
<< ", log_rhoYe=" << reaction.log_rhoYe
<< ", mu_e=" << reaction.mu_e
<< ", log_rate=" << reaction.log_rate
<< ", log_neutrino_loss=" << reaction.log_neutrino_loss
<< ")";
return os;
}
};
class WeakReactionMap {
public:
WeakReactionMap();
~WeakReactionMap() = default;
std::vector<WeakReaction> get_all_reactions() const;
[[nodiscard]] std::vector<WeakReactionEntry> get_all_reactions() const;
std::expected<std::vector<WeakReaction>, bool> get_species_reactions(const fourdst::atomic::Species &species) const;
std::expected<std::vector<WeakReaction>, bool> get_species_reactions(const std::string& species_name) const;
[[nodiscard]] std::expected<std::vector<WeakReactionEntry>, WeakMapError> get_species_reactions(
const fourdst::atomic::Species &species) const;
[[nodiscard]] std::expected<std::vector<WeakReactionEntry>, WeakMapError> get_species_reactions(
const std::string &species_name) const;
private:
std::unordered_map<fourdst::atomic::Species, std::vector<WeakReaction>> m_weak_network;
std::unordered_map<fourdst::atomic::Species, std::vector<WeakReactionEntry>> m_weak_network;
};
class WeakReaction final : public reaction::Reaction {
public:
explicit WeakReaction(
const fourdst::atomic::Species &species,
WeakReactionType type,
const WeakRateInterpolator& interpolator
);
[[nodiscard]] 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
) const override;
[[nodiscard]] 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
) 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}; }
[[nodiscard]] bool contains(const fourdst::atomic::Species &species) const override;
[[nodiscard]] bool contains_reactant(const fourdst::atomic::Species &species) const override;
[[nodiscard]] bool contains_product(const fourdst::atomic::Species &species) const override;
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> all_species() const override;
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> reactant_species() const override;
[[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
[[nodiscard]] std::unordered_map<fourdst::atomic::Species, int> stoichiometry() const override;
[[nodiscard]] int stoichiometry(const fourdst::atomic::Species &species) const override;
[[nodiscard]] uint64_t hash(uint64_t seed) const override;
[[nodiscard]] double qValue() const override;
[[nodiscard]] 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 override;
[[nodiscard]] 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 override;
[[nodiscard]] double calculate_forward_rate_log_derivative(
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; }
[[nodiscard]] std::unique_ptr<Reaction> clone() const override;
[[nodiscard]] bool is_reverse() const override { return false; };
private:
template<IsArithmeticOrAD T>
T calculate_rate(
T T9,
T rho,
T Ye,
T mue,
const std::vector<T> &Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const;
double get_log_rate_from_payload(const WeakRatePayload& payload) const;
private:
class AtomicWeakRate final : public CppAD::atomic_base<double> {
public:
AtomicWeakRate(
const WeakRateInterpolator& interpolator,
const size_t a,
const size_t z,
const WeakReactionType type
) :
CppAD::atomic_base<double>(std::format("atomic-{}-{}-weak-rate", a, z)),
m_interpolator(interpolator),
m_a(a),
m_z(z) ,
m_type(type) {}
bool forward(
size_t p,
size_t q,
const CppAD::vector<bool>& vx,
CppAD::vector<bool>& vy,
const CppAD::vector<double>& tx,
CppAD::vector<double>& ty
) override;
bool reverse(
size_t q,
const CppAD::vector<double>& tx,
const CppAD::vector<double>& ty,
CppAD::vector<double>& px,
const CppAD::vector<double>& py
) override;
bool for_sparse_jac(
size_t q,
const CppAD::vector<std::set<size_t>>&r,
CppAD::vector<std::set<size_t>>& s
) override;
bool rev_sparse_jac(
size_t q,
const CppAD::vector<std::set<size_t>>&rt,
CppAD::vector<std::set<size_t>>& st
) override;
private:
const WeakRateInterpolator& m_interpolator;
const size_t m_a;
const size_t m_z;
const WeakReactionType m_type;
};
struct constants {
const fourdst::constant::Constants& c = fourdst::constant::Constants::getInstance();
fourdst::constant::Constant neutronMassG = c.get("mN");
fourdst::constant::Constant protonMassG = c.get("mP");
fourdst::constant::Constant electronMassG = c.get("mE");
fourdst::constant::Constant speedOfLight = c.get("c");
fourdst::constant::Constant eVgRelation = c.get("eV_kg"); // note that despite the symbol this is in g NOT kg
fourdst::constant::Constant MeV2Erg = c.get("MeV_to_erg");
fourdst::constant::Constant amu = c.get("u");
double MeVgRelation = eVgRelation.value * 1.0e6;
double MeVPerGraph = 1.0/MeVgRelation;
double neutronMassMeV = neutronMassG.value * MeVgRelation;
double protonMassMeV = protonMassG.value * MeVgRelation;
double electronMassMeV = electronMassG.value * MeVgRelation;
double u_to_MeV = (amu.value * speedOfLight.value * speedOfLight.value)/MeV2Erg.value;
};
private:
const constants m_constants;
fourdst::atomic::Species m_reactant;
fourdst::atomic::Species m_product;
size_t m_reactant_a;
size_t m_reactant_z;
size_t m_product_a;
size_t m_product_z;
std::string m_id;
WeakReactionType m_type;
const WeakRateInterpolator& m_interpolator;
AtomicWeakRate m_atomic;
};
template<IsArithmeticOrAD T>
T WeakReaction::calculate_rate(
T T9,
T rho,
T Ye,
T mue,
const std::vector<T> &Y,
const std::unordered_map<size_t, fourdst::atomic::Species>& index_to_species_map
) const {
T log_rhoYe = CppAD::log10(rho * Ye);
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);
m_atomic(ax, ay);
rateConstant = static_cast<T>(ay[0]);
} else { // The case where T is of type double
const std::expected<WeakRatePayload, InterpolationError> result = m_interpolator.get_rates(
static_cast<uint16_t>(m_reactant_a),
static_cast<uint8_t>(m_reactant_z),
T9,
log_rhoYe,
mue
);
if (!result.has_value()) {
const InterpolationErrorType type = result.error().type;
const std::string msg = std::format(
"Failed to interpolate weak rate for (A={}, Z={}) at T9={}, log10(rho*Ye)={}, mu_e={} with error: {}",
m_reactant.name(), m_reactant_a, m_reactant_z, T9, log_rhoYe, mue, InterpolationErrorTypeMap.at(type)
);
throw std::runtime_error(msg);
}
const WeakRatePayload payload = result.value();
const double logRate = get_log_rate_from_payload(payload);
if (logRate <= GRIDFIRE_WEAK_REACTION_LIB_SENTINEL) {
rateConstant = static_cast<T>(0.0);
} else {
rateConstant = CppAD::pow(10.0, logRate);
}
}
return rateConstant;
}
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include "gridfire/reaction/weak/weak_types.h"
#include "fourdst/composition/atomicSpecies.h"
#include <unordered_map>
#include <cstdint>
#include <vector>
#include <expected>
namespace gridfire::rates::weak {
class WeakRateInterpolator {
public:
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
explicit WeakRateInterpolator(const RowDataTable& raw_data);
[[nodiscard]] std::vector<fourdst::atomic::Species> available_isotopes() const;
[[nodiscard]] std::expected<WeakRatePayload, InterpolationError> get_rates(
uint16_t A,
uint8_t Z,
double t9,
double log_rhoYe,
double mu_e
) const;
[[nodiscard]] std::expected<WeakRateDerivatives, InterpolationError> get_rate_derivatives(
uint16_t A,
uint8_t Z,
double t9,
double log_rhoYe,
double mu_e
) const;
private:
static uint32_t pack_isotope_id(uint16_t A, uint8_t Z);
std::unordered_map<uint32_t, IsotopeGrid> m_rate_table;
};
}

View File

@@ -8,25 +8,10 @@
#include <array>
#include <cstdint>
#include "gridfire/reaction/weak/weak.h"
namespace gridfire::rates::weak {
// Represents a single row from the unified weak rate table
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;
};
// The complete, pre-processed weak rate table data
static constexpr std::array<RateDataRow, 77400> UNIFIED_WEAK_DATA = {
RateDataRow(6, 3, 0.01, 5.0, 0.485, -60.0, -60.0, -60.0, -60.0, -60.0, -60.0),

View File

@@ -0,0 +1,133 @@
#pragma once
#include <cstdint>
#include <array>
#include <vector>
#include <optional>
#include <unordered_map>
#include <ostream>
namespace gridfire::rates::weak {
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;
};
enum class WeakReactionType {
BETA_PLUS_DECAY,
BETA_MINUS_DECAY,
ELECTRON_CAPTURE,
POSITRON_CAPTURE,
};
enum class NeutrinoTypes {
ELECTRON_NEUTRINO,
ELECTRON_ANTINEUTRINO,
MUON_NEUTRINO,
MUON_ANTINEUTRINO,
TAU_NEUTRINO,
TAU_ANTINEUTRINO
};
enum class WeakMapError {
SPECIES_NOT_FOUND,
UNKNOWN_ERROR
};
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;
};
struct WeakRateDerivatives {
// Each array holds [d/dT9, d/dlogRhoYe, d/dMuE]
std::array<double, 3> d_log_beta_plus;
std::array<double, 3> d_log_electron_capture;
std::array<double, 3> d_log_neutrino_loss_ec;
std::array<double, 3> d_log_beta_minus;
std::array<double, 3> d_log_positron_capture;
std::array<double, 3> d_log_antineutrino_loss_bd;
};
enum class InterpolationErrorType {
BOUNDS_ERROR,
UNKNOWN_SPECIES_ERROR,
UNKNOWN_ERROR
};
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"}
};
enum class TableAxes {
T9,
LOG_RHOYE,
MUE
};
struct BoundsErrorInfo {
TableAxes axis;
double axisMinValue;
double axisMaxValue;
double queryValue;
};
struct InterpolationError {
InterpolationErrorType type;
std::optional<std::unordered_map<TableAxes, BoundsErrorInfo>> boundsErrorInfo = std::nullopt;
};
struct IsotopeGrid {
std::vector<double> t9_axis;
std::vector<double> rhoYe_axis;
std::vector<double> mue_axis;
// index = (i_t9 * logRhoYe_axis.size() + j_rhoYe) + mue_axis.size() + k_mue
std::vector<WeakRatePayload> data;
};
constexpr std::string_view weak_reaction_type_name(const WeakReactionType t) noexcept {
switch (t) {
case WeakReactionType::BETA_PLUS_DECAY: return "bp";
case WeakReactionType::BETA_MINUS_DECAY: return "bm";
case WeakReactionType::ELECTRON_CAPTURE: return "ec";
case WeakReactionType::POSITRON_CAPTURE: return "pc";
}
return "Unknown";
}
struct WeakReactionEntry {
WeakReactionType type;
float T9;
float log_rhoYe;
float mu_e;
float log_rate;
float log_neutrino_loss;
friend std::ostream& operator<<(std::ostream& os, const WeakReactionEntry& reaction) {
os << "WeakReactionEntry(type=" << weak_reaction_type_name(reaction.type)
<< ", T9=" << reaction.T9
<< ", log_rhoYe=" << reaction.log_rhoYe
<< ", mu_e=" << reaction.mu_e
<< ", log_rate=" << reaction.log_rate
<< ", log_neutrino_loss=" << reaction.log_neutrino_loss
<< ")";
return os;
}
};
}