6#include "fourdst/composition/species.h"
7#include "fourdst/composition/atomicSpecies.h"
9#include "quill/LogMacros.h"
17#include <unordered_map>
22#include <boost/numeric/odeint.hpp>
27 const fourdst::composition::Composition &composition
43 const std::vector<double> &Y,
48 std::vector<double> bare_rates;
51 bare_rates.push_back(
reaction.calculate_rate(T9));
76 std::set<std::string_view> uniqueSpeciesNames;
79 for (
const auto& reactant:
reaction.reactants()) {
80 uniqueSpeciesNames.insert(reactant.name());
82 for (
const auto& product:
reaction.products()) {
83 uniqueSpeciesNames.insert(product.name());
87 for (
const auto& name: uniqueSpeciesNames) {
88 auto it = fourdst::atomic::species.find(std::string(name));
89 if (it != fourdst::atomic::species.end()) {
93 LOG_ERROR(
m_logger,
"Species '{}' not found in global atomic species database.", name);
95 throw std::runtime_error(
"Species not found in global atomic species database: " + std::string(name));
102 LOG_TRACE_L1(
m_logger,
"Populating reaction ID map for REACLIB graph network (serif::network::GraphNetwork)...");
123 LOG_TRACE_L2(
m_logger,
"Jacobian matrix resized to {} rows and {} columns.",
136 LOG_TRACE_L3(
m_logger,
"Providing access to network reactions set. Size: {}.",
m_reactions.size());
143 LOG_DEBUG(
m_logger,
"Checking if species '{}' is involved in the network: {}.", species.name(), found ?
"Yes" :
"No");
149 LOG_TRACE_L1(
m_logger,
"Validating mass (A) and charge (Z) conservation across all reactions in the network.");
152 uint64_t totalReactantA = 0;
153 uint64_t totalReactantZ = 0;
154 uint64_t totalProductA = 0;
155 uint64_t totalProductZ = 0;
158 for (
const auto& reactant :
reaction.reactants()) {
161 totalReactantA += it->second.a();
162 totalReactantZ += it->second.z();
166 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Reactant species '{}' in reaction '{}' not found in network species map during conservation validation.",
173 for (
const auto& product :
reaction.products()) {
176 totalProductA += it->second.a();
177 totalProductZ += it->second.z();
180 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Product species '{}' in reaction '{}' not found in network species map during conservation validation.",
187 if (totalReactantA != totalProductA) {
188 LOG_ERROR(
m_logger,
"Mass number (A) not conserved for reaction '{}': Reactants A={} vs Products A={}.",
189 reaction.id(), totalReactantA, totalProductA);
192 if (totalReactantZ != totalProductZ) {
193 LOG_ERROR(
m_logger,
"Atomic number (Z) not conserved for reaction '{}': Reactants Z={} vs Products Z={}.",
194 reaction.id(), totalReactantZ, totalProductZ);
199 LOG_TRACE_L1(
m_logger,
"Mass (A) and charge (Z) conservation validated successfully for all reactions.");
217 LOG_DEBUG(
m_logger,
"Reaction set not cached. Rebuilding the reaction set for T9={} and culling={}.", T9, culling);
224 const std::vector<double> &Y_in,
225 const std::vector<double> &bare_rates,
230 const std::vector<double> screeningFactors =
m_screeningModel->calculateScreeningFactors(
239 std::vector<double> molarReactionFlows;
243 double abundanceProduct = 1.0;
244 bool below_threshold =
false;
245 for (
size_t i = 0; i < precomp.unique_reactant_indices.size(); ++i) {
246 const size_t reactantIndex = precomp.unique_reactant_indices[i];
247 const int power = precomp.reactant_powers[i];
248 const double abundance = Y_in[reactantIndex];
250 below_threshold =
true;
254 abundanceProduct *= std::pow(Y_in[reactantIndex], power);
256 if (below_threshold) {
257 molarReactionFlows.push_back(0.0);
261 const double bare_rate = bare_rates[precomp.reaction_index];
262 const double screeningFactor = screeningFactors[precomp.reaction_index];
263 const size_t numReactants =
m_reactions[precomp.reaction_index].reactants().size();
265 const double molarReactionFlow =
268 precomp.symmetry_factor *
270 std::pow(rho, numReactants);
272 molarReactionFlows.push_back(molarReactionFlow);
280 const double R_j = molarReactionFlows[j];
282 for (
size_t i = 0; i < precomp.affected_species_indices.size(); ++i) {
283 const size_t speciesIndex = precomp.affected_species_indices[i];
284 const int stoichiometricCoefficient = precomp.stoichiometric_coefficients[i];
287 result.
dydt[speciesIndex] +=
static_cast<double>(stoichiometricCoefficient) * R_j / rho;
292 double massProductionRate = 0.0;
304 LOG_TRACE_L1(
m_logger,
"Generating stoichiometry matrix...");
311 LOG_TRACE_L1(
m_logger,
"Stoichiometry matrix initialized with dimensions: {} rows (species) x {} columns (reactions).",
312 numSpecies, numReactions);
316 size_t reactionColumnIndex = 0;
319 std::unordered_map<fourdst::atomic::Species, int> netStoichiometry =
reaction.stoichiometry();
322 for (
const auto& [species, coefficient] : netStoichiometry) {
326 const size_t speciesRowIndex = it->second;
331 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Species '{}' from reaction '{}' stoichiometry not found in species to index map.",
334 throw std::runtime_error(
"Species not found in species to index map: " + std::string(species.name()));
337 reactionColumnIndex++;
340 LOG_TRACE_L1(
m_logger,
"Stoichiometry matrix population complete. Number of non-zero elements: {}.",
345 const std::vector<double> &Y_in,
353 const std::vector<ADDouble> &Y_in,
379 const std::vector<double> &Y,
387 const std::vector<double> &Y,
392 LOG_TRACE_L1(
m_logger,
"Generating jacobian matrix for T9={}, rho={}..", T9, rho);
396 std::vector<double> adInput(numSpecies + 2, 0.0);
397 for (
size_t i = 0; i < numSpecies; ++i) {
400 adInput[numSpecies] = T9;
401 adInput[numSpecies + 1] = rho;
404 const std::vector<double> dotY =
m_rhsADFun.Jacobian(adInput);
408 for (
size_t i = 0; i < numSpecies; ++i) {
409 for (
size_t j = 0; j < numSpecies; ++j) {
410 const double value = dotY[i * (numSpecies + 2) + j];
430 const int speciesIndex,
431 const int reactionIndex
437 LOG_TRACE_L1(
m_logger,
"Exporting network graph to DOT file: {}", filename);
439 std::ofstream dotFile(filename);
440 if (!dotFile.is_open()) {
441 LOG_ERROR(
m_logger,
"Failed to open file for writing: {}", filename);
443 throw std::runtime_error(
"Failed to open file for writing: " + filename);
446 dotFile <<
"digraph NuclearReactionNetwork {\n";
447 dotFile <<
" graph [rankdir=LR, splines=true, overlap=false, bgcolor=\"#f0f0f0\"];\n";
448 dotFile <<
" node [shape=circle, style=filled, fillcolor=\"#a7c7e7\", fontname=\"Helvetica\"];\n";
449 dotFile <<
" edge [fontname=\"Helvetica\", fontsize=10];\n\n";
452 dotFile <<
" // --- Species Nodes ---\n";
454 dotFile <<
" \"" << species.name() <<
"\" [label=\"" << species.name() <<
"\"];\n";
459 dotFile <<
" // --- Reaction Edges ---\n";
462 std::string reactionNodeId =
"reaction_" + std::string(
reaction.id());
465 dotFile <<
" \"" << reactionNodeId <<
"\" [shape=point, fillcolor=black, width=0.1, height=0.1, label=\"\"];\n";
468 for (
const auto& reactant :
reaction.reactants()) {
469 dotFile <<
" \"" << reactant.name() <<
"\" -> \"" << reactionNodeId <<
"\";\n";
473 for (
const auto& product :
reaction.products()) {
474 dotFile <<
" \"" << reactionNodeId <<
"\" -> \"" << product.name() <<
"\" [label=\"" <<
reaction.qValue() <<
" MeV\"];\n";
481 LOG_TRACE_L1(
m_logger,
"Successfully exported network to {}", filename);
485 LOG_TRACE_L1(
m_logger,
"Exporting network graph to CSV file: {}", filename);
487 std::ofstream csvFile(filename, std::ios::out | std::ios::trunc);
488 if (!csvFile.is_open()) {
489 LOG_ERROR(
m_logger,
"Failed to open file for writing: {}", filename);
491 throw std::runtime_error(
"Failed to open file for writing: " + filename);
493 csvFile <<
"Reaction;Reactants;Products;Q-value;sources;rates\n";
499 for (
const auto& reactant :
reaction.reactants()) {
500 csvFile << reactant.name();
501 if (++count <
reaction.reactants().size()) {
507 for (
const auto& product :
reaction.products()) {
508 csvFile << product.name();
509 if (++count <
reaction.products().size()) {
513 csvFile <<
";" <<
reaction.qValue() <<
";";
517 for (
const auto& source : sources) {
519 if (++count < sources.size()) {
526 for (
const auto& rates :
reaction) {
535 LOG_TRACE_L1(
m_logger,
"Successfully exported network graph to {}", filename);
539 const double rho)
const {
541 std::unordered_map<fourdst::atomic::Species, double> speciesTimescales;
544 double timescale = std::numeric_limits<double>::infinity();
546 if (std::abs(dydt[i]) > 0.0) {
547 timescale = std::abs(Y[i] / dydt[i]);
549 speciesTimescales.emplace(species, timescale);
551 return speciesTimescales;
559 LOG_TRACE_L1(
m_logger,
"Recording AD tape for the RHS calculation...");
563 if (numSpecies == 0) {
564 LOG_ERROR(
m_logger,
"Cannot record AD tape: No species in the network.");
566 throw std::runtime_error(
"Cannot record AD tape: No species in the network.");
568 const size_t numADInputs = numSpecies + 2;
576 const auto uniformMassFraction =
static_cast<CppAD::AD<double>
>(1.0 /
static_cast<double>(numSpecies));
577 std::vector<CppAD::AD<double>> adInput(numADInputs, uniformMassFraction);
578 adInput[numSpecies] = 1.0;
579 adInput[numSpecies + 1] = 1.0;
583 CppAD::Independent(adInput);
585 std::vector<CppAD::AD<double>> adY(numSpecies);
586 for(
size_t i = 0; i < numSpecies; ++i) {
589 const CppAD::AD<double> adT9 = adInput[numSpecies];
590 const CppAD::AD<double> adRho = adInput[numSpecies + 1];
599 LOG_TRACE_L1(
m_logger,
"AD tape recorded successfully for the RHS calculation. Number of independent variables: {}.",
604 LOG_TRACE_L1(
m_logger,
"Pre-computing constant components of GraphNetwork state...");
607 std::unordered_map<fourdst::atomic::Species, size_t> speciesIndexMap;
622 std::unordered_map<size_t, int> reactantCounts;
623 for (
const auto& reactant:
reaction.reactants()) {
624 size_t reactantIndex = speciesIndexMap.at(reactant);
625 reactantCounts[reactantIndex]++;
628 double symmetryDenominator = 1.0;
629 for (
const auto& [index, count] : reactantCounts) {
633 symmetryDenominator *= 1.0/std::tgamma(count + 1);
639 const auto stoichiometryMap =
reaction.stoichiometry();
643 for (
const auto& [species, coeff] : stoichiometryMap) {
bool isPrecomputationEnabled() const
const std::vector< fourdst::atomic::Species > & getNetworkSpecies() const override
Gets the list of species in the network.
bool m_usePrecomputation
Flag to enable or disable using precomputed reactions for efficiency. Mathematically,...
std::unordered_map< fourdst::atomic::Species, double > getSpeciesTimescales(const std::vector< double > &Y, double T9, double rho) const override
Computes timescales for all species in the network.
void populateReactionIDMap()
Populates the reaction ID map.
CppAD::ADFun< double > m_rhsADFun
CppAD function for the right-hand side of the ODE.
boost::numeric::ublas::compressed_matrix< double > m_jacobianMatrix
Jacobian matrix (species x species).
double getJacobianMatrixEntry(const int i, const int j) const override
Gets an entry from the previously generated Jacobian matrix.
std::unordered_map< std::string_view, fourdst::atomic::Species > m_networkSpeciesMap
Map from species name to Species object.
void populateSpeciesToIndexMap()
Populates the species-to-index map.
void update(const NetIn &netIn) override
Update the internal state of the engine.
screening::ScreeningType m_screeningType
Screening type for the reaction network. Default to no screening.
void reserveJacobianMatrix()
Reserves space for the Jacobian matrix.
std::vector< PrecomputedReaction > m_precomputedReactions
Precomputed reactions for efficiency.
std::unordered_map< std::string_view, reaction::Reaction * > m_reactionIDMap
Map from reaction ID to REACLIBReaction. //PERF: This makes copies of REACLIBReaction and could be a ...
StepDerivatives< double > calculateAllDerivativesUsingPrecomputation(const std::vector< double > &Y_in, const std::vector< double > &bare_rates, double T9, double rho) const
screening::ScreeningType getScreeningModel() const override
Get the current electron screening model.
int getStoichiometryMatrixEntry(const int speciesIndex, const int reactionIndex) const override
Gets an entry from the stoichiometry matrix.
void setPrecomputation(bool precompute)
void setScreeningModel(screening::ScreeningType) override
Set the electron screening model.
void exportToCSV(const std::string &filename) const
Exports the network to a CSV file for analysis.
StepDerivatives< double > calculateRHSAndEnergy(const std::vector< double > &Y, const double T9, const double rho) const override
Calculates the right-hand side (dY/dt) and energy generation rate.
static std::unordered_map< fourdst::atomic::Species, int > getNetReactionStoichiometry(const reaction::Reaction &reaction)
Gets the net stoichiometry for a given reaction.
double calculateMolarReactionFlow(const reaction::Reaction &reaction, const std::vector< double > &Y, const double T9, const double rho) const override
Calculates the molar reaction flow for a given reaction.
std::vector< fourdst::atomic::Species > m_networkSpecies
Vector of unique species in the network.
void recordADTape()
Records the AD tape for the right-hand side of the ODE.
GraphEngine(const fourdst::composition::Composition &composition)
Constructs a GraphEngine from a composition.
bool involvesSpecies(const fourdst::atomic::Species &species) const
Checks if a given species is involved in the network.
reaction::LogicalReactionSet m_reactions
Set of REACLIB reactions in the network.
void syncInternalMaps()
Synchronizes the internal maps.
bool validateConservation() const
Validates mass and charge conservation across all reactions.
boost::numeric::ublas::compressed_matrix< int > m_stoichiometryMatrix
Stoichiometry matrix (species x reactions).
const reaction::LogicalReactionSet & getNetworkReactions() const override
Gets the set of logical reactions in the network.
std::unordered_map< fourdst::atomic::Species, size_t > m_speciesToIndexMap
Map from species to their index in the stoichiometry matrix.
void exportToDot(const std::string &filename) const
Exports the network to a DOT file for visualization.
void generateJacobianMatrix(const std::vector< double > &Y, const double T9, const double rho) override
Generates the Jacobian matrix for the current state.
void generateStoichiometryMatrix() override
Generates the stoichiometry matrix for the network.
void collectNetworkSpecies()
Collects the unique species in the network.
void validateComposition(const fourdst::composition::Composition &composition, double culling, double T9)
Validates the composition against the current reaction set.
std::unique_ptr< screening::ScreeningModel > m_screeningModel
StepDerivatives< T > calculateAllDerivatives(const std::vector< T > &Y_in, T T9, T rho) const
Calculates all derivatives (dY/dt) and the energy generation rate.
Represents a single nuclear reaction from a specific data source.
TemplatedReactionSet< LogicalReaction > LogicalReactionSet
A set of logical reactions.
std::unique_ptr< ScreeningModel > selectScreeningModel(ScreeningType type)
A factory function to select and create a screening model.
ScreeningType
Enumerates the available plasma screening models.
CppAD::AD< double > ADDouble
Alias for CppAD AD type for double precision.
static constexpr double MIN_ABUNDANCE_THRESHOLD
Minimum abundance threshold below which species are ignored.
reaction::LogicalReactionSet build_reaclib_nuclear_network(const fourdst::composition::Composition &composition, bool reverse)
static constexpr double MIN_JACOBIAN_THRESHOLD
Minimum value for Jacobian matrix entries.
Defines classes for representing and managing nuclear reactions.
std::vector< int > reactant_powers
std::vector< size_t > affected_species_indices
std::vector< size_t > unique_reactant_indices
std::vector< int > stoichiometric_coefficients
Structure holding derivatives and energy generation for a network step.
T nuclearEnergyGenerationRate
Specific energy generation rate (e.g., erg/g/s).
std::vector< T > dydt
Derivatives of abundances (dY/dt for each species).