feat(reverse-reactions): major work towrds detailed balance calculations
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <ranges>
|
||||
|
||||
#include <boost/numeric/odeint.hpp>
|
||||
|
||||
@@ -26,7 +27,18 @@ namespace gridfire {
|
||||
GraphEngine::GraphEngine(
|
||||
const fourdst::composition::Composition &composition
|
||||
):
|
||||
m_reactions(build_reaclib_nuclear_network(composition, false)) {
|
||||
m_reactions(build_reaclib_nuclear_network(composition, false)),
|
||||
m_partitionFunction(std::make_unique<partition::GroundStatePartitionFunction>()){
|
||||
syncInternalMaps();
|
||||
precomputeNetwork();
|
||||
}
|
||||
|
||||
GraphEngine::GraphEngine(
|
||||
const fourdst::composition::Composition &composition,
|
||||
const partition::PartitionFunction& partitionFunction) :
|
||||
m_reactions(build_reaclib_nuclear_network(composition, false)),
|
||||
m_partitionFunction(partitionFunction.clone()) // Clone the partition function to ensure ownership
|
||||
{
|
||||
syncInternalMaps();
|
||||
precomputeNetwork();
|
||||
}
|
||||
@@ -220,6 +232,129 @@ namespace gridfire {
|
||||
}
|
||||
}
|
||||
|
||||
double GraphEngine::calculateReverseRate(
|
||||
const reaction::Reaction &reaction,
|
||||
const double T9,
|
||||
const double expFactor
|
||||
) const {
|
||||
double reverseRate = 0.0;
|
||||
const double forwardRate = reaction.calculate_rate(T9);
|
||||
|
||||
if (reaction.reactants().size() == 2 && reaction.products().size() == 2) {
|
||||
reverseRate = calculateReverseRateTwoBody(reaction, T9, forwardRate, expFactor);
|
||||
} else {
|
||||
LOG_WARNING(m_logger, "Reverse rate calculation for reactions with more than two reactants or products is not implemented.");
|
||||
}
|
||||
return reverseRate;
|
||||
}
|
||||
|
||||
double GraphEngine::calculateReverseRateTwoBody(
|
||||
const reaction::Reaction &reaction,
|
||||
const double T9,
|
||||
const double forwardRate,
|
||||
const double expFactor
|
||||
) const {
|
||||
std::vector<double> reactantPartitionFunctions;
|
||||
std::vector<double> productPartitionFunctions;
|
||||
|
||||
reactantPartitionFunctions.reserve(reaction.reactants().size());
|
||||
productPartitionFunctions.reserve(reaction.products().size());
|
||||
|
||||
std::unordered_map<fourdst::atomic::Species, int> reactantMultiplicity;
|
||||
std::unordered_map<fourdst::atomic::Species, int> productMultiplicity;
|
||||
|
||||
reactantMultiplicity.reserve(reaction.reactants().size());
|
||||
productMultiplicity.reserve(reaction.products().size());
|
||||
|
||||
for (const auto& reactant : reaction.reactants()) {
|
||||
reactantMultiplicity[reactant] += 1;
|
||||
}
|
||||
for (const auto& product : reaction.products()) {
|
||||
productMultiplicity[product] += 1;
|
||||
}
|
||||
double reactantSymmetryFactor = 1.0;
|
||||
double productSymmetryFactor = 1.0;
|
||||
for (const auto& count : reactantMultiplicity | std::views::values) {
|
||||
reactantSymmetryFactor *= std::tgamma(count + 1);
|
||||
}
|
||||
for (const auto& count : productMultiplicity | std::views::values) {
|
||||
productSymmetryFactor *= std::tgamma(count + 1);
|
||||
}
|
||||
const double symmetryFactor = reactantSymmetryFactor / productSymmetryFactor;
|
||||
|
||||
// Accumulate mass terms
|
||||
auto mass_op = [](double acc, const auto& species) { return acc * species.a(); };
|
||||
const double massNumerator = std::accumulate(
|
||||
reaction.reactants().begin(),
|
||||
reaction.reactants().end(),
|
||||
1.0,
|
||||
mass_op
|
||||
);
|
||||
const double massDenominator = std::accumulate(
|
||||
reaction.products().begin(),
|
||||
reaction.products().end(),
|
||||
1.0,
|
||||
mass_op
|
||||
);
|
||||
|
||||
// Accumulate partition functions
|
||||
auto pf_op = [&](double acc, const auto& species) { return acc * m_partitionFunction->evaluate(species.z(), species.a(), T9); };
|
||||
const double partitionFunctionNumerator = std::accumulate(
|
||||
reaction.reactants().begin(),
|
||||
reaction.reactants().end(),
|
||||
1.0,
|
||||
pf_op
|
||||
);
|
||||
const double partitionFunctionDenominator = std::accumulate(
|
||||
reaction.products().begin(),
|
||||
reaction.products().end(),
|
||||
1.0,
|
||||
pf_op
|
||||
);
|
||||
|
||||
const double CT = std::pow(massNumerator/massDenominator, 1.5) * (partitionFunctionNumerator/partitionFunctionDenominator);
|
||||
|
||||
return forwardRate * symmetryFactor * CT * expFactor;
|
||||
|
||||
}
|
||||
|
||||
double GraphEngine::calculateReverseRateTwoBodyDerivative(
|
||||
const reaction::Reaction &reaction,
|
||||
const double T9,
|
||||
const double reverseRate
|
||||
) const {
|
||||
const double d_log_kFwd = reaction.calculate_forward_rate_log_derivative(T9);
|
||||
|
||||
auto log_deriv_pf_op = [&](double acc, const auto& species) {
|
||||
const double g = m_partitionFunction->evaluate(species.z(), species.a(), T9);
|
||||
const double dg_dT = m_partitionFunction->evaluateDerivative(species.z(), species.a(), T9);
|
||||
return (g == 0.0) ? acc : acc + (dg_dT / g);
|
||||
};
|
||||
|
||||
const double reactant_log_derivative_sum = std::accumulate(
|
||||
reaction.reactants().begin(),
|
||||
reaction.reactants().end(),
|
||||
0.0,
|
||||
log_deriv_pf_op
|
||||
);
|
||||
|
||||
const double product_log_derivative_sum = std::accumulate(
|
||||
reaction.products().begin(),
|
||||
reaction.products().end(),
|
||||
0.0,
|
||||
log_deriv_pf_op
|
||||
);
|
||||
|
||||
const double d_log_C = reactant_log_derivative_sum - product_log_derivative_sum;
|
||||
|
||||
const double d_log_exp = reaction.qValue() / (m_constants.kB * T9 * T9);
|
||||
|
||||
const double log_total_derivative = d_log_kFwd + d_log_C + d_log_exp;
|
||||
|
||||
return reverseRate * log_total_derivative; // Return the derivative of the reverse rate with respect to T9
|
||||
|
||||
}
|
||||
|
||||
StepDerivatives<double> GraphEngine::calculateAllDerivativesUsingPrecomputation(
|
||||
const std::vector<double> &Y_in,
|
||||
const std::vector<double> &bare_rates,
|
||||
@@ -374,6 +509,10 @@ namespace gridfire {
|
||||
return m_usePrecomputation;
|
||||
}
|
||||
|
||||
const partition::PartitionFunction & GraphEngine::getPartitionFunction() const {
|
||||
return *m_partitionFunction;
|
||||
}
|
||||
|
||||
double GraphEngine::calculateMolarReactionFlow(
|
||||
const reaction::Reaction &reaction,
|
||||
const std::vector<double> &Y,
|
||||
@@ -648,4 +787,21 @@ namespace gridfire {
|
||||
m_precomputedReactions.push_back(std::move(precomp));
|
||||
}
|
||||
}
|
||||
|
||||
bool AtomicReverseRate::forward(
|
||||
const size_t p,
|
||||
const size_t q,
|
||||
const CppAD::vector<bool> &vx,
|
||||
CppAD::vector<bool> &vy,
|
||||
const CppAD::vector<double> &tx,
|
||||
CppAD::vector<double> &ty
|
||||
) {
|
||||
const double T9 = tx[0];
|
||||
const double expFactor = std::exp(-m_reaction.qValue() / (m_kB * T9 * 1e9)); // Convert MeV to erg
|
||||
if (p == 0) {
|
||||
// --- Zeroth order forward sweep ---
|
||||
const auto k_rev = m_engine.calculateReverseRate(m_reaction, T9, expFactor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,13 @@ namespace gridfire::partition {
|
||||
}
|
||||
}
|
||||
|
||||
CompositePartitionFunction::CompositePartitionFunction(const CompositePartitionFunction &other) {
|
||||
m_partitionFunctions.reserve(other.m_partitionFunctions.size());
|
||||
for (const auto& pf : other.m_partitionFunctions) {
|
||||
m_partitionFunctions.push_back(pf->clone());
|
||||
}
|
||||
}
|
||||
|
||||
double CompositePartitionFunction::evaluate(int z, int a, double T9) const {
|
||||
LOG_TRACE_L1(m_logger, "Evaluating partition function for Z={} A={} T9={}", z, a, T9);
|
||||
for (const auto& partitionFunction : m_partitionFunctions) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
||||
namespace gridfire::partition {
|
||||
static constexpr std::array<double, 24> RT_TEMPERATURE_GRID_T9 = {
|
||||
@@ -25,8 +26,17 @@ namespace gridfire::partition {
|
||||
IsotopeData data;
|
||||
data.ground_state_spin = record.ground_state_spin;
|
||||
std::ranges::copy(record.normalized_g_values, data.normalized_g_values.begin());
|
||||
const int key = make_key(record.z, record.a);
|
||||
LOG_TRACE_L3_LIMIT_EVERY_N(
|
||||
100,
|
||||
m_logger,
|
||||
"(EVERY 100) Adding Rauscher-Thielemann partition data for Z={} A={} (key={})",
|
||||
record.z,
|
||||
record.a,
|
||||
key
|
||||
);
|
||||
|
||||
m_partitionData[make_key(record.z, record.a)] = std::move(data);
|
||||
m_partitionData[key] = std::move(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,27 @@ namespace gridfire::reaction {
|
||||
return calculate_rate<CppAD::AD<double>>(T9);
|
||||
}
|
||||
|
||||
double Reaction::calculate_forward_rate_log_derivative(const double T9) const {
|
||||
constexpr double r_p13 = 1.0 / 3.0;
|
||||
constexpr double r_p53 = 5.0 / 3.0;
|
||||
constexpr double r_p23 = 2.0 / 3.0;
|
||||
constexpr double r_p43 = 4.0 / 3.0;
|
||||
|
||||
const double T9_m1 = 1.0 / T9;
|
||||
const double T9_m23 = std::pow(T9, -r_p23);
|
||||
const double T9_m43 = std::pow(T9, -r_p43);
|
||||
|
||||
const double d_log_k_fwd_dT9 =
|
||||
-m_rateCoefficients.a1 * T9_m1 * T9_m1
|
||||
- r_p13 * m_rateCoefficients.a2 * T9_m43
|
||||
+ r_p13 * m_rateCoefficients.a3 * T9_m23
|
||||
+ m_rateCoefficients.a4
|
||||
+ r_p53 * m_rateCoefficients.a5 * std::pow(T9, r_p23)
|
||||
+ m_rateCoefficients.a6 * T9_m1;
|
||||
|
||||
return d_log_k_fwd_dT9; // Return the derivative of the log rate with respect to T9
|
||||
}
|
||||
|
||||
bool Reaction::contains(const Species &species) const {
|
||||
return contains_reactant(species) || contains_product(species);
|
||||
}
|
||||
@@ -194,6 +215,57 @@ namespace gridfire::reaction {
|
||||
return calculate_rate<double>(T9);
|
||||
}
|
||||
|
||||
double LogicalReaction::calculate_forward_rate_log_derivative(const double T9) const {
|
||||
constexpr double r_p13 = 1.0 / 3.0;
|
||||
constexpr double r_p53 = 5.0 / 3.0;
|
||||
constexpr double r_p23 = 2.0 / 3.0;
|
||||
constexpr double r_p43 = 4.0 / 3.0;
|
||||
|
||||
double totalRate = 0.0;
|
||||
double totalRateDerivative = 0.0;
|
||||
|
||||
|
||||
const double T9_m1 = 1.0 / T9;
|
||||
const double T913 = std::pow(T9, r_p13);
|
||||
const double T953 = std::pow(T9, r_p53);
|
||||
const double logT9 = std::log(T9);
|
||||
|
||||
const double T9_m1_sq = T9_m1 * T9_m1;
|
||||
const double T9_m23 = std::pow(T9, -r_p23);
|
||||
const double T9_m43 = std::pow(T9, -r_p43);
|
||||
const double T9_p23 = std::pow(T9, r_p23);
|
||||
|
||||
|
||||
for (const auto& coeffs : m_rates) {
|
||||
const double exponent = coeffs.a0 +
|
||||
coeffs.a1 * T9_m1 +
|
||||
coeffs.a2 / T913 +
|
||||
coeffs.a3 * T913 +
|
||||
coeffs.a4 * T9 +
|
||||
coeffs.a5 * T953 +
|
||||
coeffs.a6 * logT9;
|
||||
const double individualRate = std::exp(exponent);
|
||||
|
||||
const double d_exponent_T9 =
|
||||
-coeffs.a1 * T9_m1_sq
|
||||
- r_p13 * coeffs.a2 * T9_m43
|
||||
+ r_p13 * coeffs.a3 * T9_m23
|
||||
+ coeffs.a4
|
||||
+ r_p53 * coeffs.a5 * T9_p23
|
||||
+ coeffs.a6 * T9_m1;
|
||||
|
||||
const double individualRateDerivative = individualRate * d_exponent_T9;
|
||||
totalRate += individualRate;
|
||||
totalRateDerivative += individualRateDerivative;
|
||||
}
|
||||
|
||||
if (totalRate == 0.0) {
|
||||
return 0.0; // Avoid division by zero
|
||||
}
|
||||
|
||||
return totalRateDerivative / totalRate;
|
||||
}
|
||||
|
||||
CppAD::AD<double> LogicalReaction::calculate_rate(const CppAD::AD<double> T9) const {
|
||||
return calculate_rate<CppAD::AD<double>>(T9);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user