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

@@ -5,7 +5,6 @@
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
#include <algorithm>
namespace gridfire::diagnostics {
@@ -73,7 +72,7 @@ namespace gridfire::diagnostics {
void inspect_species_balance(
const DynamicEngine& engine,
const std::string& species_name,
const std::vector<double>& Y_full,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) {
@@ -89,15 +88,15 @@ namespace gridfire::diagnostics {
const int stoichiometry = reaction->stoichiometry(species_obj);
if (stoichiometry == 0) continue;
const double flow = engine.calculateMolarReactionFlow(*reaction, Y_full, T9, rho);
const double flow = engine.calculateMolarReactionFlow(*reaction, comp, T9, rho);
if (stoichiometry > 0) {
creation_ids.push_back(std::string(reaction->id()));
creation_ids.emplace_back(reaction->id());
creation_stoichiometry.push_back(stoichiometry);
creation_flows.push_back(flow);
total_creation_flow += stoichiometry * flow;
} else {
destruction_ids.push_back(std::string(reaction->id()));
destruction_ids.emplace_back(reaction->id());
destruction_stoichiometry.push_back(stoichiometry);
destruction_flows.push_back(flow);
total_destruction_flow += std::abs(stoichiometry) * flow;
@@ -129,38 +128,38 @@ namespace gridfire::diagnostics {
void inspect_jacobian_stiffness(
const DynamicEngine& engine,
const std::vector<double>& Y_full,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) {
engine.generateJacobianMatrix(Y_full, T9, rho);
engine.generateJacobianMatrix(comp, T9, rho);
const auto& species_list = engine.getNetworkSpecies();
double max_diag = 0.0;
double max_off_diag = 0.0;
int max_diag_idx = -1;
int max_off_diag_i = -1, max_off_diag_j = -1;
std::optional<fourdst::atomic::Species> max_diag_species = std::nullopt;
std::optional<std::pair<fourdst::atomic::Species, fourdst::atomic::Species>> max_off_diag_species = std::nullopt;
for (size_t i = 0; i < species_list.size(); ++i) {
for (size_t j = 0; j < species_list.size(); ++j) {
const double val = std::abs(engine.getJacobianMatrixEntry(i, j));
if (i == j) {
if (val > max_diag) { max_diag = val; max_diag_idx = i; }
for (const auto& rowSpecies : species_list) {
for (const auto& colSpecies : species_list) {
const double val = std::abs(engine.getJacobianMatrixEntry(rowSpecies, colSpecies));
if (rowSpecies == colSpecies) {
if (val > max_diag) { max_diag = val; max_diag_species = colSpecies; }
} else {
if (val > max_off_diag) { max_off_diag = val; max_off_diag_i = i; max_off_diag_j = j; }
if (val > max_off_diag) { max_off_diag = val; max_off_diag_species = {rowSpecies, colSpecies};}
}
}
}
std::cout << "\n--- Jacobian Stiffness Report ---" << std::endl;
if (max_diag_idx != -1) {
if (max_diag_species.has_value()) {
std::cout << " Largest Diagonal Element (d(dYi/dt)/dYi): " << std::scientific << max_diag
<< " for species " << species_list[max_diag_idx].name() << std::endl;
<< " for species " << max_diag_species->name() << std::endl;
}
if (max_off_diag_i != -1) {
if (max_off_diag_species.has_value()) {
std::cout << " Largest Off-Diagonal Element (d(dYi/dt)/dYj): " << std::scientific << max_off_diag
<< " for d(" << species_list[max_off_diag_i].name()
<< ")/d(" << species_list[max_off_diag_j].name() << ")" << std::endl;
<< " for d(" << max_off_diag_species->first.name()
<< ")/d(" << max_off_diag_species->second.name() << ")" << std::endl;
}
std::cout << "---------------------------------" << std::endl;
}

View File

@@ -12,7 +12,6 @@
#include "quill/LogMacros.h"
#include <cstdint>
#include <iostream>
#include <set>
#include <stdexcept>
#include <string>
@@ -40,7 +39,8 @@ namespace gridfire {
const fourdst::composition::Composition &composition,
const partition::PartitionFunction& partitionFunction,
const BuildDepthType buildDepth) :
m_reactions(build_reaclib_nuclear_network(composition, buildDepth, false)),
m_weakRateInterpolator(rates::weak::UNIFIED_WEAK_DATA),
m_reactions(build_reaclib_nuclear_network(composition, m_weakRateInterpolator, buildDepth, false)),
m_depth(buildDepth),
m_partitionFunction(partitionFunction.clone())
{
@@ -50,15 +50,19 @@ namespace gridfire {
GraphEngine::GraphEngine(
const reaction::ReactionSet &reactions
) :
m_reactions(reactions) {
m_weakRateInterpolator(rates::weak::UNIFIED_WEAK_DATA),
m_reactions(reactions)
{
syncInternalMaps();
}
std::expected<StepDerivatives<double>, expectations::StaleEngineError> GraphEngine::calculateRHSAndEnergy(
const std::vector<double> &Y,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
const double Ye = comp.getElectronAbundance();
const double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
if (m_usePrecomputation) {
std::vector<double> bare_rates;
std::vector<double> bare_reverse_rates;
@@ -66,33 +70,35 @@ namespace gridfire {
bare_reverse_rates.reserve(m_reactions.size());
// TODO: Add cache to this
for (const auto& reaction: m_reactions) {
bare_rates.push_back(reaction->calculate_rate(T9, rho, Y));
bare_reverse_rates.push_back(calculateReverseRate(*reaction, T9, rho, Y));
bare_rates.push_back(reaction->calculate_rate(T9, rho, Ye, mue, comp.getMolarAbundanceVector(), m_indexToSpeciesMap));
bare_reverse_rates.push_back(calculateReverseRate(*reaction, T9, rho, comp));
}
// --- The public facing interface can always use the precomputed version since taping is done internally ---
return calculateAllDerivativesUsingPrecomputation(Y, bare_rates, bare_reverse_rates, T9, rho);
return calculateAllDerivativesUsingPrecomputation(comp, bare_rates, bare_reverse_rates, T9, rho);
} else {
return calculateAllDerivatives<double>(Y, T9, rho);
return calculateAllDerivatives<double>(comp.getMolarAbundanceVector(), T9, rho, Ye, mue);
}
}
EnergyDerivatives GraphEngine::calculateEpsDerivatives(
const std::vector<double> &Y,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
const size_t numSpecies = m_networkSpecies.size();
const size_t numADInputs = numSpecies + 2; // +2 for T9 and rho
if (Y.size() != numSpecies) {
if (comp.getRegisteredSpecies().size() != numSpecies) {
LOG_ERROR(m_logger, "Input abundance vector size ({}) does not match number of species in the network ({}).",
Y.size(), numSpecies);
comp.getRegisteredSpecies().size(), numSpecies);
throw std::invalid_argument("Input abundance vector size does not match number of species in the network.");
}
std::vector<double> x(numADInputs);
const std::vector<double> Y = comp.getMolarAbundanceVector();
for (size_t i = 0; i < numSpecies; ++i) {
x[i] = Y[i];
}
@@ -148,6 +154,7 @@ namespace gridfire {
// --- Network Graph Construction Methods ---
void GraphEngine::collectNetworkSpecies() {
//TODO: Ensure consistent ordering in the m_networkSpecies vector so that it is ordered by species mass.
m_networkSpecies.clear();
m_networkSpeciesMap.clear();
@@ -190,6 +197,10 @@ namespace gridfire {
for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
m_speciesToIndexMap.insert({m_networkSpecies[i], i});
}
m_indexToSpeciesMap.clear();
for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
m_indexToSpeciesMap.insert({i, m_networkSpecies[i]});
}
}
void GraphEngine::reserveJacobianMatrix() const {
@@ -287,13 +298,18 @@ namespace gridfire {
const reaction::Reaction &reaction,
const double T9,
const double rho,
const std::vector<double> &Y
const fourdst::composition::Composition &comp
) const {
if (!m_useReverseReactions) {
LOG_TRACE_L3_LIMIT_EVERY_N(std::numeric_limits<int>::max(), m_logger, "Reverse reactions are disabled. Returning 0.0 for reverse rate of reaction '{}'.", reaction.id());
return 0.0; // If reverse reactions are not used, return 0.0
}
const double temp = T9 * 1e9; // Convert T9 to Kelvin
const double Ye = comp.getElectronAbundance();
// TODO: This is a dummy value for the electron chemical potential. We eventually need to replace this with an EOS call.
const double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
// In debug builds we check the units on kB to ensure it is in erg/K. This is removed in release builds to avoid overhead. (Note assert is a no-op in release builds)
assert(Constants::getInstance().get("kB").unit == "erg / K");
@@ -301,7 +317,7 @@ namespace gridfire {
const double kBMeV = m_constants.kB * 624151; // Convert kB to MeV/K NOTE: This relies on the fact that m_constants.kB is in erg/K!
const double expFactor = std::exp(-reaction.qValue() / (kBMeV * temp));
double reverseRate = 0.0;
const double forwardRate = reaction.calculate_rate(T9, rho, Y);
const double forwardRate = reaction.calculate_rate(T9, rho, Ye, mue, comp.getMolarAbundanceVector(), m_indexToSpeciesMap);
if (reaction.reactants().size() == 2 && reaction.products().size() == 2) {
reverseRate = calculateReverseRateTwoBody(reaction, T9, forwardRate, expFactor);
@@ -393,14 +409,17 @@ namespace gridfire {
const reaction::Reaction &reaction,
const double T9,
const double rho,
const std::vector<double> &Y,
const fourdst::composition::Composition& comp,
const double reverseRate
) const {
if (!m_useReverseReactions) {
LOG_TRACE_L3_LIMIT_EVERY_N(std::numeric_limits<int>::max(), m_logger, "Reverse reactions are disabled. Returning 0.0 for reverse rate of reaction '{}'.", reaction.id());
return 0.0; // If reverse reactions are not used, return 0.0
}
const double d_log_kFwd = reaction.calculate_forward_rate_log_derivative(T9, rho, Y);
double Ye = comp.getElectronAbundance();
// TODO: This is a dummy value for the electron chemical potential. We eventually need to replace this with an EOS call.
double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
const double d_log_kFwd = reaction.calculate_forward_rate_log_derivative(T9, rho, Ye, mue, comp);
auto log_deriv_pf_op = [&](double acc, const auto& species) {
const double g = m_partitionFunction->evaluate(species.z(), species.a(), T9);
@@ -486,7 +505,7 @@ namespace gridfire {
void GraphEngine::rebuild(const fourdst::composition::Composition& comp, const BuildDepthType depth) {
if (depth != m_depth) {
m_depth = depth;
m_reactions = build_reaclib_nuclear_network(comp, m_depth, false);
m_reactions = build_reaclib_nuclear_network(comp, m_weakRateInterpolator, m_depth, false);
syncInternalMaps(); // Resync internal maps after changing the depth
} else {
LOG_DEBUG(m_logger, "Rebuild requested with the same depth. No changes made to the network.");
@@ -494,7 +513,7 @@ namespace gridfire {
}
StepDerivatives<double> GraphEngine::calculateAllDerivativesUsingPrecomputation(
const std::vector<double> &Y_in,
const fourdst::composition::Composition& comp,
const std::vector<double> &bare_rates,
const std::vector<double> &bare_reverse_rates,
const double T9,
@@ -504,33 +523,27 @@ namespace gridfire {
const std::vector<double> screeningFactors = m_screeningModel->calculateScreeningFactors(
m_reactions,
m_networkSpecies,
Y_in,
comp.getMolarAbundanceVector(),
T9,
rho
);
// TODO: Fix up the precomputation to use the new comp in interface as opposed to a raw vector of molar abundances
// This will require carefully checking the way the precomputation is stashed.
// --- Optimized loop ---
std::vector<double> molarReactionFlows;
molarReactionFlows.reserve(m_precomputedReactions.size());
for (const auto& precomp : m_precomputedReactions) {
double forwardAbundanceProduct = 1.0;
// bool below_threshold = false;
for (size_t i = 0; i < precomp.unique_reactant_indices.size(); ++i) {
const size_t reactantIndex = precomp.unique_reactant_indices[i];
const fourdst::atomic::Species& reactant = m_networkSpecies[reactantIndex];
const int power = precomp.reactant_powers[i];
// const double abundance = Y_in[reactantIndex];
// if (abundance < MIN_ABUNDANCE_THRESHOLD) {
// below_threshold = true;
// break;
// }
forwardAbundanceProduct *= std::pow(Y_in[reactantIndex], power);
forwardAbundanceProduct *= std::pow(comp.getMolarAbundance(reactant), power);
}
// if (below_threshold) {
// molarReactionFlows.push_back(0.0);
// continue; // Skip this reaction if any reactant is below the abundance threshold
// }
const double bare_rate = bare_rates[precomp.reaction_index];
const double screeningFactor = screeningFactors[precomp.reaction_index];
@@ -549,7 +562,9 @@ namespace gridfire {
const double bare_reverse_rate = bare_reverse_rates[precomp.reaction_index];
double reverseAbundanceProduct = 1.0;
for (size_t i = 0; i < precomp.unique_product_indices.size(); ++i) {
reverseAbundanceProduct *= std::pow(Y_in[precomp.unique_product_indices[i]], precomp.product_powers[i]);
const size_t productIndex = precomp.unique_product_indices[i];
const fourdst::atomic::Species& product = m_networkSpecies[productIndex];
reverseAbundanceProduct *= std::pow(comp.getMolarAbundance(product), precomp.product_powers[i]);
}
reverseMolarReactionFlow = screeningFactor *
bare_reverse_rate *
@@ -564,25 +579,28 @@ namespace gridfire {
// --- Assemble molar abundance derivatives ---
StepDerivatives<double> result;
result.dydt.assign(m_networkSpecies.size(), 0.0); // Initialize derivatives to zero
for (const auto& species: m_networkSpecies) {
result.dydt[species] = 0.0; // Initialize the change in abundance for each network species to 0
}
for (size_t j = 0; j < m_precomputedReactions.size(); ++j) {
const auto& precomp = m_precomputedReactions[j];
const double R_j = molarReactionFlows[j];
for (size_t i = 0; i < precomp.affected_species_indices.size(); ++i) {
const size_t speciesIndex = precomp.affected_species_indices[i];
const fourdst::atomic::Species& species = m_networkSpecies[speciesIndex];
const int stoichiometricCoefficient = precomp.stoichiometric_coefficients[i];
// Update the derivative for this species
result.dydt[speciesIndex] += static_cast<double>(stoichiometricCoefficient) * R_j;
result.dydt.at(species) += static_cast<double>(stoichiometricCoefficient) * R_j;
}
}
// --- Calculate the nuclear energy generation rate ---
double massProductionRate = 0.0; // [mol][s^-1]
for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
const auto& species = m_networkSpecies[i];
massProductionRate += result.dydt[i] * species.mass() * m_constants.u;
for (const auto & species : m_networkSpecies) {
massProductionRate += result.dydt[species] * species.mass() * m_constants.u;
}
result.nuclearEnergyGenerationRate = -massProductionRate * m_constants.Na * m_constants.c * m_constants.c; // [erg][s^-1][g^-1]
return result;
@@ -631,21 +649,22 @@ namespace gridfire {
m_stoichiometryMatrix.nnz()); // Assuming nnz() exists for compressed_matrix
}
StepDerivatives<double> GraphEngine::calculateAllDerivatives(
const std::vector<double> &Y_in,
const double T9,
const double rho
) const {
return calculateAllDerivatives<double>(Y_in, T9, rho);
}
StepDerivatives<ADDouble> GraphEngine::calculateAllDerivatives(
const std::vector<ADDouble> &Y_in,
const ADDouble &T9,
const ADDouble &rho
) const {
return calculateAllDerivatives<ADDouble>(Y_in, T9, rho);
}
// StepDerivatives<double> GraphEngine::calculateAllDerivatives(
// const std::vector<double> &Y,
// const double T9,
// const double rho
// ) const {
// return calculateAllDerivatives<double>(Y, T9, rho);
// }
//
// StepDerivatives<ADDouble> GraphEngine::calculateAllDerivatives(
// const std::vector<ADDouble> &Y,
// ADDouble T9,
// ADDouble rho
// ) const {
// //TODO: Sort out why this template is not being found
// return calculateAllDerivatives<ADDouble>(Y, T9, rho);
// }
void GraphEngine::setScreeningModel(const screening::ScreeningType model) {
m_screeningModel = screening::selectScreeningModel(model);
@@ -670,15 +689,21 @@ namespace gridfire {
double GraphEngine::calculateMolarReactionFlow(
const reaction::Reaction &reaction,
const std::vector<double> &Y,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
return calculateMolarReactionFlow<double>(reaction, Y, T9, rho);
const double Ye = comp.getElectronAbundance();
// TODO: This is a dummy placeholder which must be replaced with an EOS call
const double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
return calculateMolarReactionFlow<double>(reaction, comp.getMolarAbundanceVector(), T9, rho, Ye, mue);
}
void GraphEngine::generateJacobianMatrix(
const std::vector<double> &Y_dynamic,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
@@ -688,6 +713,7 @@ namespace gridfire {
// 1. Pack the input variables into a vector for CppAD
std::vector<double> adInput(numSpecies + 2, 0.0); // +2 for T9 and rho
const std::vector<double>& Y_dynamic = comp.getMolarAbundanceVector();
for (size_t i = 0; i < numSpecies; ++i) {
adInput[i] = std::max(Y_dynamic[i], 1e-99); // regularize the jacobian...
}
@@ -711,7 +737,7 @@ namespace gridfire {
}
void GraphEngine::generateJacobianMatrix(
const std::vector<double> &Y_dynamic,
const fourdst::composition::Composition &comp,
const double T9,
const double rho,
const SparsityPattern &sparsityPattern
@@ -719,6 +745,7 @@ namespace gridfire {
// --- Pack the input variables into a vector for CppAD ---
const size_t numSpecies = m_networkSpecies.size();
std::vector<double> x(numSpecies + 2, 0.0);
const std::vector<double>& Y_dynamic = comp.getMolarAbundanceVector();
for (size_t i = 0; i < numSpecies; ++i) {
x[i] = Y_dynamic[i];
}
@@ -767,8 +794,13 @@ namespace gridfire {
}
}
double GraphEngine::getJacobianMatrixEntry(const int i, const int j) const {
// LOG_TRACE_L3(m_logger, "Getting jacobian matrix entry for {},{} = {}", i, j, m_jacobianMatrix(i, j));
double GraphEngine::getJacobianMatrixEntry(
const fourdst::atomic::Species& rowSpecies,
const fourdst::atomic::Species& colSpecies
) const {
//PERF: There may be some way to make this more efficient
const size_t i = getSpeciesIndex(rowSpecies);
const size_t j = getSpeciesIndex(colSpecies);
return m_jacobianMatrix(i, j);
}
@@ -779,10 +811,10 @@ namespace gridfire {
}
int GraphEngine::getStoichiometryMatrixEntry(
const int speciesIndex,
const int reactionIndex
const fourdst::atomic::Species& species,
const reaction::Reaction &reaction
) const {
return m_stoichiometryMatrix(speciesIndex, reactionIndex);
return reaction.stoichiometry(species);
}
void GraphEngine::exportToDot(const std::string &filename) const {
@@ -871,18 +903,21 @@ namespace gridfire {
}
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesTimescales(
const std::vector<double> &Y,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
auto [dydt, _] = calculateAllDerivatives<double>(Y, T9, rho);
const double Ye = comp.getElectronAbundance();
// TODO: This is a dummy placeholder which must be replaced with an EOS call
const double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
auto [dydt, _] = calculateAllDerivatives<double>(comp.getMolarAbundanceVector(), T9, rho, Ye, mue);
std::unordered_map<fourdst::atomic::Species, double> speciesTimescales;
speciesTimescales.reserve(m_networkSpecies.size());
for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
for (const auto& species : m_networkSpecies) {
double timescale = std::numeric_limits<double>::infinity();
const auto species = m_networkSpecies[i];
if (std::abs(dydt[i]) > 0.0) {
timescale = std::abs(Y[i] / dydt[i]);
if (std::abs(dydt.at(species)) > 0.0) {
timescale = std::abs(comp.getMolarAbundance(species) / dydt.at(species));
}
speciesTimescales.emplace(species, timescale);
}
@@ -890,24 +925,28 @@ namespace gridfire {
}
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesDestructionTimescales(
const std::vector<double> &Y,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
auto [dydt, _] = calculateAllDerivatives<double>(Y, T9, rho);
const double Ye = comp.getElectronAbundance();
// TODO: This is a dummy placeholder which must be replaced with an EOS call
const double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
auto [dydt, _] = calculateAllDerivatives<double>(comp.getMolarAbundanceVector(), T9, rho, Ye, mue);
std::unordered_map<fourdst::atomic::Species, double> speciesDestructionTimescales;
speciesDestructionTimescales.reserve(m_networkSpecies.size());
for (const auto& species : m_networkSpecies) {
double netDestructionFlow = 0.0;
for (const auto& reaction : m_reactions) {
if (reaction->stoichiometry(species) < 0) {
const auto flow = calculateMolarReactionFlow<double>(*reaction, Y, T9, rho);
const auto flow = calculateMolarReactionFlow<double>(*reaction, comp.getMolarAbundanceVector(), T9, rho, Ye, mue);
netDestructionFlow += flow;
}
}
double timescale = std::numeric_limits<double>::infinity();
if (netDestructionFlow != 0.0) {
timescale = Y[getSpeciesIndex(species)] / netDestructionFlow;
timescale = comp.getMolarAbundance(species) / netDestructionFlow;
}
speciesDestructionTimescales.emplace(species, timescale);
}
@@ -964,12 +1003,21 @@ namespace gridfire {
const CppAD::AD<double> adT9 = adInput[numSpecies];
const CppAD::AD<double> adRho = adInput[numSpecies + 1];
// Dummy values for Ye and mue to let taping happen
const CppAD::AD<double> adYe = 1.0;
const CppAD::AD<double> adMue = 1.0;
// 5. Call the actual templated function
// We let T9 and rho be constant, so we pass them as fixed values.
auto [dydt, nuclearEnergyGenerationRate] = calculateAllDerivatives<CppAD::AD<double>>(adY, adT9, adRho);
auto [dydt, nuclearEnergyGenerationRate] = calculateAllDerivatives<CppAD::AD<double>>(adY, adT9, adRho, adYe, adMue);
m_rhsADFun.Dependent(adInput, dydt);
// Extract the raw vector from the associative map
std::vector<CppAD::AD<double>> dydt_vec;
dydt_vec.reserve(dydt.size());
std::ranges::transform(dydt, std::back_inserter(dydt_vec),[](const auto& kv) { return kv.second; });
m_rhsADFun.Dependent(adInput, dydt_vec);
LOG_TRACE_L1(m_logger, "AD tape recorded successfully for the RHS calculation. Number of independent variables: {}.",
adInput.size());
@@ -996,16 +1044,19 @@ namespace gridfire {
CppAD::Independent(adInput);
const std::vector<CppAD::AD<double>> adY(adInput.begin(), adInput.begin() + numSpecies);
const std::vector<CppAD::AD<double>> adY(adInput.begin(), adInput.begin() + static_cast<long>(numSpecies));
const CppAD::AD<double> adT9 = adInput[numSpecies];
const CppAD::AD<double> adRho = adInput[numSpecies + 1];
StepDerivatives<CppAD::AD<double>> derivatives = calculateAllDerivatives<CppAD::AD<double>>(adY, adT9, adRho);
// Dummy values for Ye and mue to let taping happen
const CppAD::AD<double> adYe = 1.0;
const CppAD::AD<double> adMue = 1.0;
auto [dydt, nuclearEnergyGenerationRate] = calculateAllDerivatives<CppAD::AD<double>>(adY, adT9, adRho, adYe, adMue);
std::vector<CppAD::AD<double>> adOutput(1);
adOutput[0] = derivatives.nuclearEnergyGenerationRate;
adOutput[0] = nuclearEnergyGenerationRate;
m_epsADFun.Dependent(adInput, adOutput);
// m_epsADFun.optimize();
LOG_TRACE_L1(m_logger, "AD tape recorded successfully for the EPS calculation. Number of independent variables: {}.", adInput.size());

View File

@@ -1,7 +1,11 @@
#include "gridfire/engine/procedures/construction.h"
#include "gridfire/reaction/weak/weak_interpolator.h"
#include "gridfire/reaction/weak/weak.h"
#include "gridfire/reaction/weak/weak_types.h"
#include <ranges>
#include <stdexcept>
#include <memory>
#include "gridfire/reaction/reaction.h"
#include "gridfire/reaction/reaclib.h"
@@ -20,11 +24,11 @@ namespace gridfire {
using fourdst::atomic::Species;
ReactionSet build_reaclib_nuclear_network(
const Composition &composition,
BuildDepthType maxLayers,
bool reverse
) {
ReactionSet build_nuclear_network(
const Composition& composition,
const rates::weak::WeakRateInterpolator& weak_interpolator,
BuildDepthType maxLayers,
bool reverse_reaclib) {
int depth;
if (std::holds_alternative<NetworkBuildDepth>(maxLayers)) {
depth = static_cast<int>(std::get<NetworkBuildDepth>(maxLayers));
@@ -37,31 +41,71 @@ namespace gridfire {
throw std::logic_error("Network build depth is set to 0. No reactions will be collected.");
}
const auto& allReactions = reaclib::get_all_reaclib_reactions();
std::vector<Reaction*> remainingReactions;
for (const auto& reaction : allReactions) {
if (reaction->is_reverse() == reverse) {
remainingReactions.push_back(reaction.get());
// --- Step 1: Create a single master pool that owns all possible reactions ---
ReactionSet master_reaction_pool;
// Clone all relevant REACLIB reactions into the master pool
const auto& allReaclibReactions = reaclib::get_all_reaclib_reactions();
for (const auto& reaction : allReaclibReactions) {
if (reaction->is_reverse() == reverse_reaclib) {
master_reaction_pool.add_reaction(reaction->clone());
}
}
if (depth == static_cast<int>(NetworkBuildDepth::Full)) {
LOG_INFO(logger, "Building full nuclear network with a total of {} reactions.", allReactions.size());
const ReactionSet reactionSet(remainingReactions);
return reactionSet;
for (const auto& parent_species: weak_interpolator.available_isotopes()) {
master_reaction_pool.add_reaction(
std::make_unique<rates::weak::WeakReaction>(
parent_species,
rates::weak::WeakReactionType::BETA_PLUS_DECAY,
weak_interpolator
)
);
master_reaction_pool.add_reaction(
std::make_unique<rates::weak::WeakReaction>(
parent_species,
rates::weak::WeakReactionType::BETA_MINUS_DECAY,
weak_interpolator
)
);
master_reaction_pool.add_reaction(
std::make_unique<rates::weak::WeakReaction>(
parent_species,
rates::weak::WeakReactionType::ELECTRON_CAPTURE,
weak_interpolator
)
);
master_reaction_pool.add_reaction(
std::make_unique<rates::weak::WeakReaction>(
parent_species,
rates::weak::WeakReactionType::POSITRON_CAPTURE,
weak_interpolator
)
);
}
// --- Step 2: Use non-owning raw pointers for the fast build algorithm ---
std::vector<Reaction*> remainingReactions;
remainingReactions.reserve(master_reaction_pool.size());
for(const auto& reaction : master_reaction_pool) {
remainingReactions.push_back(reaction.get());
}
if (depth == static_cast<int>(NetworkBuildDepth::Full)) {
LOG_INFO(logger, "Building full nuclear network with a total of {} reactions.", remainingReactions.size());
return master_reaction_pool; // No need to build layer by layer if we want the full network
}
// --- Step 3: Execute the layered network build using observing pointers ---
std::unordered_set<Species> availableSpecies;
for (const auto &entry: composition | std::views::values) {
for (const auto& entry : composition | std::views::values) {
if (entry.mass_fraction() > 0.0) {
availableSpecies.insert(entry.isotope());
}
}
std::vector<Reaction*> collectedReactions;
std::vector<Reaction*> collectedReactionPtrs;
LOG_INFO(logger, "Starting network construction with {} available species.", availableSpecies.size());
for (int layer = 0; layer < depth && !remainingReactions.empty(); ++layer) {
LOG_TRACE_L1(logger, "Collecting reactions for layer {} with {} remaining reactions. Currently there are {} available species", layer, remainingReactions.size(), availableSpecies.size());
std::vector<Reaction*> reactionsForNextPass;
@@ -70,7 +114,7 @@ namespace gridfire {
reactionsForNextPass.reserve(remainingReactions.size());
for (const auto &reaction : remainingReactions) {
for (Reaction* reaction : remainingReactions) {
bool allReactantsAvailable = true;
for (const auto& reactant : reaction->reactants()) {
if (!availableSpecies.contains(reactant)) {
@@ -80,7 +124,7 @@ namespace gridfire {
}
if (allReactantsAvailable) {
collectedReactions.push_back(reaction);
collectedReactionPtrs.push_back(reaction);
newReactionsAdded = true;
for (const auto& product : reaction->products()) {
@@ -92,19 +136,29 @@ namespace gridfire {
}
if (!newReactionsAdded) {
LOG_INFO(logger, "No new reactions added in layer {}. Stopping network construction with {} reactions collected.", layer, collectedReactions.size());
LOG_INFO(logger, "No new reactions added in layer {}. Stopping network construction.", layer);
break;
}
LOG_TRACE_L1(logger, "Layer {}: Collected {} reactions. New products this layer: {}", layer, collectedReactions.size(), newProductsThisLayer.size());
LOG_TRACE_L1(logger, "Layer {}: Collected {} new reactions. New products this layer: {}", collectedReactionPtrs.size() - collectedReactionPtrs.size(), newProductsThisLayer.size());
availableSpecies.insert(newProductsThisLayer.begin(), newProductsThisLayer.end());
remainingReactions = std::move(reactionsForNextPass);
}
LOG_INFO(logger, "Network construction completed with {} reactions collected.", collectedReactions.size());
const ReactionSet reactionSet(collectedReactions);
return reactionSet;
// --- Step 4: Construct the final ReactionSet by moving ownership ---
LOG_INFO(logger, "Network construction completed. Assembling final set of {} reactions.", collectedReactionPtrs.size());
ReactionSet finalReactionSet;
std::unordered_set<Reaction*> collectedPtrSet(collectedReactionPtrs.begin(), collectedReactionPtrs.end());
for (auto& reaction_ptr : master_reaction_pool) {
if (reaction_ptr && collectedPtrSet.contains(reaction_ptr.get())) {
finalReactionSet.add_reaction(std::move(reaction_ptr));
}
}
return finalReactionSet;
}
}

View File

@@ -17,7 +17,7 @@ namespace gridfire {
const reaction::Reaction* findDominantCreationChannel (
const DynamicEngine& engine,
const Species& species,
const std::vector<double>& Y,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) {
@@ -25,7 +25,7 @@ namespace gridfire {
double maxFlow = -1.0;
for (const auto& reaction : engine.getNetworkReactions()) {
if (reaction->contains(species) && reaction->stoichiometry(species) > 0) {
const double flow = engine.calculateMolarReactionFlow(*reaction, Y, T9, rho);
const double flow = engine.calculateMolarReactionFlow(*reaction, comp, T9, rho);
if (flow > maxFlow) {
maxFlow = flow;
dominateReaction = reaction.get();
@@ -67,7 +67,7 @@ namespace gridfire {
* robustly primed composition.
*/
PrimingReport primeNetwork(const NetIn& netIn, DynamicEngine& engine) {
auto logger = LogManager::getInstance().getLogger("log");
auto logger = fourdst::logging::LogManager::getInstance().getLogger("log");
// --- Initial Setup ---
// Identify all species with zero initial mass fraction that need to be primed.
@@ -127,9 +127,6 @@ namespace gridfire {
}
tempComp.finalize(true);
NetIn tempNetIn = netIn;
tempNetIn.composition = tempComp;
NetworkPrimingEngineView primer(primingSpecies, engine);
if (primer.getNetworkReactions().size() == 0) {
LOG_ERROR(logger, "No priming reactions found for species {}.", primingSpecies.name());
@@ -138,15 +135,14 @@ namespace gridfire {
continue;
}
const auto Y = primer.mapNetInToMolarAbundanceVector(tempNetIn);
const double destructionRateConstant = calculateDestructionRateConstant(primer, primingSpecies, Y, T9, rho);
const double destructionRateConstant = calculateDestructionRateConstant(primer, primingSpecies, tempComp, T9, rho);
if (destructionRateConstant > 1e-99) {
const double creationRate = calculateCreationRate(primer, primingSpecies, Y, T9, rho);
const double creationRate = calculateCreationRate(primer, primingSpecies, tempComp, T9, rho);
double equilibriumMassFraction = (creationRate / destructionRateConstant) * primingSpecies.mass();
if (std::isnan(equilibriumMassFraction)) equilibriumMassFraction = 0.0;
if (const reaction::Reaction* dominantChannel = findDominantCreationChannel(primer, primingSpecies, Y, T9, rho)) {
if (const reaction::Reaction* dominantChannel = findDominantCreationChannel(primer, primingSpecies, tempComp, T9, rho)) {
// Store the request instead of applying it immediately.
requests.push_back({primingSpecies, equilibriumMassFraction, dominantChannel->reactants()});
} else {
@@ -403,19 +399,17 @@ namespace gridfire {
double calculateDestructionRateConstant(
const DynamicEngine& engine,
const fourdst::atomic::Species& species,
const std::vector<double>& Y,
const Species& species,
const Composition& comp,
const double T9,
const double rho
) {
const size_t speciesIndex = engine.getSpeciesIndex(species);
std::vector<double> Y_scaled(Y.begin(), Y.end());
Y_scaled[speciesIndex] = 1.0; // Set the abundance of the species to 1.0 for rate constant calculation
//TODO: previously (when using raw vectors) I set y[speciesIndex] = 1.0 to let there be enough so that just the destruction rate could be found (without bottlenecks from abundance) we will need to do a similar thing here.
double destructionRateConstant = 0.0;
for (const auto& reaction: engine.getNetworkReactions()) {
if (reaction->contains_reactant(species)) {
const int stoichiometry = reaction->stoichiometry(species);
destructionRateConstant += std::abs(stoichiometry) * engine.calculateMolarReactionFlow(*reaction, Y_scaled, T9, rho);
destructionRateConstant += std::abs(stoichiometry) * engine.calculateMolarReactionFlow(*reaction, comp, T9, rho);
}
}
return destructionRateConstant;
@@ -424,7 +418,7 @@ namespace gridfire {
double calculateCreationRate(
const DynamicEngine& engine,
const Species& species,
const std::vector<double>& Y,
const Composition& comp,
const double T9,
const double rho
) {
@@ -432,9 +426,9 @@ namespace gridfire {
for (const auto& reaction: engine.getNetworkReactions()) {
const int stoichiometry = reaction->stoichiometry(species);
if (stoichiometry > 0) {
if (engine.calculateMolarReactionFlow(*reaction, Y, T9, rho) > 0.0) {
if (engine.calculateMolarReactionFlow(*reaction, comp, T9, rho) > 0.0) {
}
creationRate += stoichiometry * engine.calculateMolarReactionFlow(*reaction, Y, T9, rho);
creationRate += stoichiometry * engine.calculateMolarReactionFlow(*reaction, comp, T9, rho);
}
}
return creationRate;

View File

@@ -95,8 +95,7 @@ namespace gridfire {
LOG_TRACE_L1(m_logger, "Updating AdaptiveEngineView with new network input...");
std::vector<double> Y_Full;
std::vector<ReactionFlow> allFlows = calculateAllReactionFlows(updatedNetIn, Y_Full);
auto [allFlows, composition] = calculateAllReactionFlows(updatedNetIn);
double maxFlow = 0.0;
@@ -110,11 +109,11 @@ namespace gridfire {
const std::unordered_set<Species> reachableSpecies = findReachableSpecies(updatedNetIn);
LOG_DEBUG(m_logger, "Found {} reachable species in adaptive engine view.", reachableSpecies.size());
const std::vector<const reaction::Reaction*> finalReactions = cullReactionsByFlow(allFlows, reachableSpecies, Y_Full, maxFlow);
const std::vector<const reaction::Reaction*> finalReactions = cullReactionsByFlow(allFlows, reachableSpecies, composition, maxFlow);
finalizeActiveSet(finalReactions);
auto [rescuedReactions, rescuedSpecies] = rescueEdgeSpeciesDestructionChannel(Y_Full, netIn.temperature/1e9, netIn.density, m_activeSpecies, m_activeReactions);
auto [rescuedReactions, rescuedSpecies] = rescueEdgeSpeciesDestructionChannel(composition, netIn.temperature/1e9, netIn.density, m_activeSpecies, m_activeReactions);
for (const auto& reactionPtr : rescuedReactions) {
m_activeReactions.add_reaction(*reactionPtr);
@@ -145,59 +144,46 @@ namespace gridfire {
}
std::expected<StepDerivatives<double>, expectations::StaleEngineError> AdaptiveEngineView::calculateRHSAndEnergy(
const std::vector<double> &Y_culled,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
validateState();
const auto Y_full = mapCulledToFull(Y_culled);
auto result = m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
// TODO: Think about if I need to reach in and adjust the composition to zero out inactive species.
auto result = m_baseEngine.calculateRHSAndEnergy(comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
const auto [dydt, nuclearEnergyGenerationRate] = result.value();
StepDerivatives<double> culledResults;
culledResults.nuclearEnergyGenerationRate = nuclearEnergyGenerationRate;
culledResults.dydt = mapFullToCulled(dydt);
return culledResults;
return result.value();
}
EnergyDerivatives AdaptiveEngineView::calculateEpsDerivatives(
const std::vector<double> &Y_culled,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
validateState();
const auto Y_full = mapCulledToFull(Y_culled);
return m_baseEngine.calculateEpsDerivatives(Y_full, T9, rho);
return m_baseEngine.calculateEpsDerivatives(comp, T9, rho);
}
void AdaptiveEngineView::generateJacobianMatrix(
const std::vector<double> &Y_dynamic,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
validateState();
const auto Y_full = mapCulledToFull(Y_dynamic);
m_baseEngine.generateJacobianMatrix(Y_full, T9, rho);
m_baseEngine.generateJacobianMatrix(comp, T9, rho);
}
double AdaptiveEngineView::getJacobianMatrixEntry(
const int i_culled,
const int j_culled
const Species &rowSpecies,
const Species &colSpecies
) const {
validateState();
const size_t i_full = mapCulledToFullSpeciesIndex(i_culled);
const size_t j_full = mapCulledToFullSpeciesIndex(j_culled);
return m_baseEngine.getJacobianMatrixEntry(static_cast<int>(i_full), static_cast<int>(j_full));
return m_baseEngine.getJacobianMatrixEntry(rowSpecies, colSpecies);
}
void AdaptiveEngineView::generateStoichiometryMatrix() {
@@ -206,18 +192,16 @@ namespace gridfire {
}
int AdaptiveEngineView::getStoichiometryMatrixEntry(
const int speciesIndex_culled,
const int reactionIndex_culled
const Species &species,
const reaction::Reaction& reaction
) const {
validateState();
const size_t speciesIndex_full = mapCulledToFullSpeciesIndex(speciesIndex_culled);
const size_t reactionIndex_full = mapCulledToFullReactionIndex(reactionIndex_culled);
return m_baseEngine.getStoichiometryMatrixEntry(static_cast<int>(speciesIndex_full), static_cast<int>(reactionIndex_full));
return m_baseEngine.getStoichiometryMatrixEntry(species, reaction);
}
double AdaptiveEngineView::calculateMolarReactionFlow(
const reaction::Reaction &reaction,
const std::vector<double> &Y_culled,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
@@ -227,9 +211,8 @@ namespace gridfire {
m_logger -> flush_log();
throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
}
const auto Y = mapCulledToFull(Y_culled);
return m_baseEngine.calculateMolarReactionFlow(reaction, Y, T9, rho);
return m_baseEngine.calculateMolarReactionFlow(reaction, comp, T9, rho);
}
const reaction::ReactionSet & AdaptiveEngineView::getNetworkReactions() const {
@@ -242,13 +225,12 @@ namespace gridfire {
}
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError> AdaptiveEngineView::getSpeciesTimescales(
const std::vector<double> &Y_culled,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
validateState();
const auto Y_full = mapCulledToFull(Y_culled);
const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
const auto result = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
@@ -270,15 +252,13 @@ namespace gridfire {
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError>
AdaptiveEngineView::getSpeciesDestructionTimescales(
const std::vector<double> &Y,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
validateState();
const std::vector<double> Y_full = mapCulledToFull(Y);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(Y_full, T9, rho);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
@@ -344,7 +324,7 @@ namespace gridfire {
}
size_t AdaptiveEngineView::mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const {
if (culledSpeciesIndex < 0 || culledSpeciesIndex >= m_speciesIndexMap.size()) {
if (culledSpeciesIndex >= m_speciesIndexMap.size()) {
LOG_ERROR(m_logger, "Culled index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
m_logger->flush_log();
throw std::out_of_range("Culled index " + std::to_string(culledSpeciesIndex) + " is out of bounds for species index map of size " + std::to_string(m_speciesIndexMap.size()) + ".");
@@ -353,7 +333,7 @@ namespace gridfire {
}
size_t AdaptiveEngineView::mapCulledToFullReactionIndex(size_t culledReactionIndex) const {
if (culledReactionIndex < 0 || culledReactionIndex >= m_reactionIndexMap.size()) {
if (culledReactionIndex >= m_reactionIndexMap.size()) {
LOG_ERROR(m_logger, "Culled index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
m_logger->flush_log();
throw std::out_of_range("Culled index " + std::to_string(culledReactionIndex) + " is out of bounds for reaction index map of size " + std::to_string(m_reactionIndexMap.size()) + ".");
@@ -369,21 +349,17 @@ namespace gridfire {
}
}
// TODO: Change this to use a return value instead of an output parameter.
std::vector<AdaptiveEngineView::ReactionFlow> AdaptiveEngineView::calculateAllReactionFlows(
const NetIn &netIn,
std::vector<double> &out_Y_Full
std::pair<std::vector<AdaptiveEngineView::ReactionFlow>, fourdst::composition::Composition> AdaptiveEngineView::calculateAllReactionFlows(
const NetIn &netIn
) const {
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
out_Y_Full.clear();
out_Y_Full.reserve(fullSpeciesList.size());
fourdst::composition::Composition composition = netIn.composition;
for (const auto& species: fullSpeciesList) {
if (netIn.composition.contains(species)) {
out_Y_Full.push_back(netIn.composition.getMolarAbundance(std::string(species.name())));
} else {
if (!netIn.composition.contains(species)) {
LOG_TRACE_L2(m_logger, "Species '{}' not found in composition. Setting abundance to 0.0.", species.name());
out_Y_Full.push_back(0.0);
composition.registerSpecies(species);
composition.setMassFraction(species, 0.0);
}
}
@@ -394,11 +370,11 @@ namespace gridfire {
const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
reactionFlows.reserve(fullReactionSet.size());
for (const auto& reaction : fullReactionSet) {
const double flow = m_baseEngine.calculateMolarReactionFlow(*reaction, out_Y_Full, T9, rho);
const double flow = m_baseEngine.calculateMolarReactionFlow(*reaction, composition, T9, rho);
reactionFlows.push_back({reaction.get(), flow});
LOG_TRACE_L1(m_logger, "Reaction '{}' has flow rate: {:0.3E} [mol/s/g]", reaction->id(), flow);
}
return reactionFlows;
return {reactionFlows, composition};
}
std::unordered_set<Species> AdaptiveEngineView::findReachableSpecies(
@@ -447,7 +423,7 @@ namespace gridfire {
std::vector<const reaction::Reaction *> AdaptiveEngineView::cullReactionsByFlow(
const std::vector<ReactionFlow> &allFlows,
const std::unordered_set<fourdst::atomic::Species> &reachableSpecies,
const std::vector<double> &Y_full,
const fourdst::composition::Composition &comp,
const double maxFlow
) const {
LOG_TRACE_L1(m_logger, "Culling reactions based on flow rates...");
@@ -464,9 +440,7 @@ namespace gridfire {
bool zero_flow_due_to_reachable_reactants = false;
if (flowRate < 1e-99 && flowRate > 0.0) {
for (const auto& reactant: reactionPtr->reactants()) {
const auto it = std::ranges::find(m_baseEngine.getNetworkSpecies(), reactant);
const size_t index = std::distance(m_baseEngine.getNetworkSpecies().begin(), it);
if (Y_full[index] < 1e-99 && reachableSpecies.contains(reactant)) {
if (comp.getMolarAbundance(reactant) < 1e-99 && reachableSpecies.contains(reactant)) {
LOG_TRACE_L1(m_logger, "Maintaining reaction '{}' with low flow ({:0.3E} [mol/s/g]) due to reachable reactant '{}'.", reactionPtr->id(), flowRate, reactant.name());
zero_flow_due_to_reachable_reactants = true;
break;
@@ -488,13 +462,13 @@ namespace gridfire {
}
AdaptiveEngineView::RescueSet AdaptiveEngineView::rescueEdgeSpeciesDestructionChannel(
const std::vector<double> &Y_full,
const fourdst::composition::Composition &comp,
const double T9,
const double rho,
const std::vector<Species> &activeSpecies,
const reaction::ReactionSet &activeReactions
) const {
const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
const auto result = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
if (!result) {
LOG_ERROR(m_logger, "Failed to get species timescales due to stale engine state.");
throw exceptions::StaleEngineError("Failed to get species timescales");
@@ -565,8 +539,23 @@ namespace gridfire {
allOtherReactantsAreAvailable = false;
}
}
if (allOtherReactantsAreAvailable && speciesToCheckIsConsumed) {
double rate = reaction->calculate_rate(T9, rho, Y_full);
if (allOtherReactantsAreAvailable) {
std::vector<double> Y = comp.getMolarAbundanceVector();
const double Ye = comp.getElectronAbundance();
// TODO: This is a dummy placeholder which must be replaced with an EOS call
const double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
std::unordered_map<Species, double> speciesMassMap;
for (const auto &entry: comp | std::views::values) {
speciesMassMap[entry.isotope()] = entry.isotope().mass();
}
std::unordered_map<size_t, Species> speciesIndexMap;
for (const auto& entry: comp | std::views::values) {
size_t distance = std::distance(speciesMassMap.begin(), speciesMassMap.find(entry.isotope()));
speciesIndexMap.emplace(distance, entry.isotope());
}
double rate = reaction->calculate_rate(T9, rho, Ye, mue, Y, speciesIndexMap);
if (rate > maxSpeciesConsumptionRate) {
maxSpeciesConsumptionRate = rate;
reactionsToRescue[species] = reaction.get();

View File

@@ -28,58 +28,48 @@ namespace gridfire {
}
std::expected<StepDerivatives<double>, expectations::StaleEngineError> DefinedEngineView::calculateRHSAndEnergy(
const std::vector<double> &Y_defined,
const fourdst::composition::Composition& comp,
const double T9,
const double rho
) const {
validateNetworkState();
const auto Y_full = mapViewToFull(Y_defined);
const auto result = m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
const auto result = m_baseEngine.calculateRHSAndEnergy(comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
const auto [dydt, nuclearEnergyGenerationRate] = result.value();
StepDerivatives<double> definedResults;
definedResults.nuclearEnergyGenerationRate = nuclearEnergyGenerationRate;
definedResults.dydt = mapFullToView(dydt);
return definedResults;
return result.value();
}
EnergyDerivatives DefinedEngineView::calculateEpsDerivatives(
const std::vector<double> &Y_dynamic,
const fourdst::composition::Composition& comp,
const double T9,
const double rho
) const {
validateNetworkState();
const auto Y_full = mapViewToFull(Y_dynamic);
return m_baseEngine.calculateEpsDerivatives(Y_full, T9, rho);
return m_baseEngine.calculateEpsDerivatives(comp, T9, rho);
}
void DefinedEngineView::generateJacobianMatrix(
const std::vector<double> &Y_dynamic,
const fourdst::composition::Composition& comp,
const double T9,
const double rho
) const {
validateNetworkState();
const auto Y_full = mapViewToFull(Y_dynamic);
m_baseEngine.generateJacobianMatrix(Y_full, T9, rho);
m_baseEngine.generateJacobianMatrix(comp, T9, rho);
}
double DefinedEngineView::getJacobianMatrixEntry(
const int i_defined,
const int j_defined
const Species& rowSpecies,
const Species& colSpecies
) const {
validateNetworkState();
const size_t i_full = mapViewToFullSpeciesIndex(i_defined);
const size_t j_full = mapViewToFullSpeciesIndex(j_defined);
return m_baseEngine.getJacobianMatrixEntry(static_cast<int>(i_full), static_cast<int>(j_full));
return m_baseEngine.getJacobianMatrixEntry(rowSpecies, colSpecies);
}
void DefinedEngineView::generateStoichiometryMatrix() {
@@ -89,19 +79,17 @@ namespace gridfire {
}
int DefinedEngineView::getStoichiometryMatrixEntry(
const int speciesIndex_defined,
const int reactionIndex_defined
const Species& species,
const reaction::Reaction& reaction
) const {
validateNetworkState();
const size_t i_full = mapViewToFullSpeciesIndex(speciesIndex_defined);
const size_t j_full = mapViewToFullReactionIndex(reactionIndex_defined);
return m_baseEngine.getStoichiometryMatrixEntry(static_cast<int>(i_full), static_cast<int>(j_full));
return m_baseEngine.getStoichiometryMatrixEntry(species, reaction);
}
double DefinedEngineView::calculateMolarReactionFlow(
const reaction::Reaction &reaction,
const std::vector<double> &Y_defined,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
@@ -112,8 +100,7 @@ namespace gridfire {
m_logger -> flush_log();
throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
}
const auto Y_full = mapViewToFull(Y_defined);
return m_baseEngine.calculateMolarReactionFlow(reaction, Y_full, T9, rho);
return m_baseEngine.calculateMolarReactionFlow(reaction, comp, T9, rho);
}
const reaction::ReactionSet & DefinedEngineView::getNetworkReactions() const {
@@ -131,14 +118,13 @@ namespace gridfire {
}
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError> DefinedEngineView::getSpeciesTimescales(
const std::vector<double> &Y_defined,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
validateNetworkState();
const auto Y_full = mapViewToFull(Y_defined);
const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
const auto result = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
@@ -155,14 +141,13 @@ namespace gridfire {
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError>
DefinedEngineView::getSpeciesDestructionTimescales(
const std::vector<double> &Y_defined,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
validateNetworkState();
const auto Y_full = mapViewToFull(Y_defined);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(Y_full, T9, rho);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
@@ -304,7 +289,7 @@ namespace gridfire {
}
size_t DefinedEngineView::mapViewToFullSpeciesIndex(size_t culledSpeciesIndex) const {
if (culledSpeciesIndex < 0 || culledSpeciesIndex >= m_speciesIndexMap.size()) {
if (culledSpeciesIndex >= m_speciesIndexMap.size()) {
LOG_ERROR(m_logger, "Defined index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
m_logger->flush_log();
throw std::out_of_range("Defined index " + std::to_string(culledSpeciesIndex) + " is out of bounds for species index map of size " + std::to_string(m_speciesIndexMap.size()) + ".");
@@ -313,7 +298,7 @@ namespace gridfire {
}
size_t DefinedEngineView::mapViewToFullReactionIndex(size_t culledReactionIndex) const {
if (culledReactionIndex < 0 || culledReactionIndex >= m_reactionIndexMap.size()) {
if (culledReactionIndex >= m_reactionIndexMap.size()) {
LOG_ERROR(m_logger, "Defined index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
m_logger->flush_log();
throw std::out_of_range("Defined index " + std::to_string(culledReactionIndex) + " is out of bounds for reaction index map of size " + std::to_string(m_reactionIndexMap.size()) + ".");

File diff suppressed because it is too large Load Diff