5#include "fourdst/composition/species.h"
6#include "fourdst/composition/atomicSpecies.h"
8#include "quill/LogMacros.h"
16#include <unordered_map>
21#include <boost/numeric/odeint.hpp>
26 const fourdst::composition::Composition &composition
38 const std::vector<double> &Y,
60 std::set<std::string_view> uniqueSpeciesNames;
63 for (
const auto& reactant:
reaction.reactants()) {
64 uniqueSpeciesNames.insert(reactant.name());
66 for (
const auto& product:
reaction.products()) {
67 uniqueSpeciesNames.insert(product.name());
71 for (
const auto& name: uniqueSpeciesNames) {
72 auto it = fourdst::atomic::species.find(std::string(name));
73 if (it != fourdst::atomic::species.end()) {
77 LOG_ERROR(
m_logger,
"Species '{}' not found in global atomic species database.", name);
79 throw std::runtime_error(
"Species not found in global atomic species database: " + std::string(name));
86 LOG_TRACE_L1(
m_logger,
"Populating reaction ID map for REACLIB graph network (serif::network::GraphNetwork)...");
107 LOG_TRACE_L2(
m_logger,
"Jacobian matrix resized to {} rows and {} columns.",
120 LOG_TRACE_L3(
m_logger,
"Providing access to network reactions set. Size: {}.",
m_reactions.size());
127 LOG_DEBUG(
m_logger,
"Checking if species '{}' is involved in the network: {}.", species.name(), found ?
"Yes" :
"No");
133 LOG_TRACE_L1(
m_logger,
"Validating mass (A) and charge (Z) conservation across all reactions in the network.");
136 uint64_t totalReactantA = 0;
137 uint64_t totalReactantZ = 0;
138 uint64_t totalProductA = 0;
139 uint64_t totalProductZ = 0;
142 for (
const auto& reactant :
reaction.reactants()) {
145 totalReactantA += it->second.a();
146 totalReactantZ += it->second.z();
150 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Reactant species '{}' in reaction '{}' not found in network species map during conservation validation.",
157 for (
const auto& product :
reaction.products()) {
160 totalProductA += it->second.a();
161 totalProductZ += it->second.z();
164 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Product species '{}' in reaction '{}' not found in network species map during conservation validation.",
171 if (totalReactantA != totalProductA) {
172 LOG_ERROR(
m_logger,
"Mass number (A) not conserved for reaction '{}': Reactants A={} vs Products A={}.",
173 reaction.id(), totalReactantA, totalProductA);
176 if (totalReactantZ != totalProductZ) {
177 LOG_ERROR(
m_logger,
"Atomic number (Z) not conserved for reaction '{}': Reactants Z={} vs Products Z={}.",
178 reaction.id(), totalReactantZ, totalProductZ);
183 LOG_TRACE_L1(
m_logger,
"Mass (A) and charge (Z) conservation validated successfully for all reactions.");
201 LOG_DEBUG(
m_logger,
"Reaction set not cached. Rebuilding the reaction set for T9={} and culling={}.", T9, culling);
209 LOG_TRACE_L1(
m_logger,
"Generating stoichiometry matrix...");
216 LOG_TRACE_L1(
m_logger,
"Stoichiometry matrix initialized with dimensions: {} rows (species) x {} columns (reactions).",
217 numSpecies, numReactions);
221 size_t reactionColumnIndex = 0;
224 std::unordered_map<fourdst::atomic::Species, int> netStoichiometry =
reaction.stoichiometry();
227 for (
const auto& [species, coefficient] : netStoichiometry) {
231 const size_t speciesRowIndex = it->second;
236 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Species '{}' from reaction '{}' stoichiometry not found in species to index map.",
239 throw std::runtime_error(
"Species not found in species to index map: " + std::string(species.name()));
242 reactionColumnIndex++;
245 LOG_TRACE_L1(
m_logger,
"Stoichiometry matrix population complete. Number of non-zero elements: {}.",
250 const std::vector<double> &Y_in,
258 const std::vector<ADDouble> &Y_in,
267 const std::vector<double> &Y,
275 const std::vector<double> &Y,
280 LOG_TRACE_L1(
m_logger,
"Generating jacobian matrix for T9={}, rho={}..", T9, rho);
284 std::vector<double> adInput(numSpecies + 2, 0.0);
285 for (
size_t i = 0; i < numSpecies; ++i) {
288 adInput[numSpecies] = T9;
289 adInput[numSpecies + 1] = rho;
292 const std::vector<double> dotY =
m_rhsADFun.Jacobian(adInput);
296 for (
size_t i = 0; i < numSpecies; ++i) {
297 for (
size_t j = 0; j < numSpecies; ++j) {
298 const double value = dotY[i * (numSpecies + 2) + j];
318 const int speciesIndex,
319 const int reactionIndex
325 LOG_TRACE_L1(
m_logger,
"Exporting network graph to DOT file: {}", filename);
327 std::ofstream dotFile(filename);
328 if (!dotFile.is_open()) {
329 LOG_ERROR(
m_logger,
"Failed to open file for writing: {}", filename);
331 throw std::runtime_error(
"Failed to open file for writing: " + filename);
334 dotFile <<
"digraph NuclearReactionNetwork {\n";
335 dotFile <<
" graph [rankdir=LR, splines=true, overlap=false, bgcolor=\"#f0f0f0\"];\n";
336 dotFile <<
" node [shape=circle, style=filled, fillcolor=\"#a7c7e7\", fontname=\"Helvetica\"];\n";
337 dotFile <<
" edge [fontname=\"Helvetica\", fontsize=10];\n\n";
340 dotFile <<
" // --- Species Nodes ---\n";
342 dotFile <<
" \"" << species.name() <<
"\" [label=\"" << species.name() <<
"\"];\n";
347 dotFile <<
" // --- Reaction Edges ---\n";
350 std::string reactionNodeId =
"reaction_" + std::string(
reaction.id());
353 dotFile <<
" \"" << reactionNodeId <<
"\" [shape=point, fillcolor=black, width=0.1, height=0.1, label=\"\"];\n";
356 for (
const auto& reactant :
reaction.reactants()) {
357 dotFile <<
" \"" << reactant.name() <<
"\" -> \"" << reactionNodeId <<
"\";\n";
361 for (
const auto& product :
reaction.products()) {
362 dotFile <<
" \"" << reactionNodeId <<
"\" -> \"" << product.name() <<
"\" [label=\"" <<
reaction.qValue() <<
" MeV\"];\n";
369 LOG_TRACE_L1(
m_logger,
"Successfully exported network to {}", filename);
373 LOG_TRACE_L1(
m_logger,
"Exporting network graph to CSV file: {}", filename);
375 std::ofstream csvFile(filename, std::ios::out | std::ios::trunc);
376 if (!csvFile.is_open()) {
377 LOG_ERROR(
m_logger,
"Failed to open file for writing: {}", filename);
379 throw std::runtime_error(
"Failed to open file for writing: " + filename);
381 csvFile <<
"Reaction;Reactants;Products;Q-value;sources;rates\n";
387 for (
const auto& reactant :
reaction.reactants()) {
388 csvFile << reactant.name();
389 if (++count <
reaction.reactants().size()) {
395 for (
const auto& product :
reaction.products()) {
396 csvFile << product.name();
397 if (++count <
reaction.products().size()) {
401 csvFile <<
";" <<
reaction.qValue() <<
";";
405 for (
const auto& source : sources) {
407 if (++count < sources.size()) {
414 for (
const auto& rates :
reaction) {
423 LOG_TRACE_L1(
m_logger,
"Successfully exported network graph to {}", filename);
427 const double rho)
const {
429 std::unordered_map<fourdst::atomic::Species, double> speciesTimescales;
432 double timescale = std::numeric_limits<double>::infinity();
434 if (std::abs(dydt[i]) > 0.0) {
435 timescale = std::abs(Y[i] / dydt[i]);
437 speciesTimescales.emplace(species, timescale);
439 return speciesTimescales;
443 LOG_TRACE_L1(
m_logger,
"Recording AD tape for the RHS calculation...");
447 if (numSpecies == 0) {
448 LOG_ERROR(
m_logger,
"Cannot record AD tape: No species in the network.");
450 throw std::runtime_error(
"Cannot record AD tape: No species in the network.");
452 const size_t numADInputs = numSpecies + 2;
460 const auto uniformMassFraction =
static_cast<CppAD::AD<double>
>(1.0 / numSpecies);
461 std::vector<CppAD::AD<double>> adInput(numADInputs, uniformMassFraction);
462 adInput[numSpecies] = 1.0;
463 adInput[numSpecies + 1] = 1.0;
467 CppAD::Independent(adInput);
469 std::vector<CppAD::AD<double>> adY(numSpecies);
470 for(
size_t i = 0; i < numSpecies; ++i) {
473 const CppAD::AD<double> adT9 = adInput[numSpecies];
474 const CppAD::AD<double> adRho = adInput[numSpecies + 1];
483 LOG_TRACE_L1(
m_logger,
"AD tape recorded successfully for the RHS calculation. Number of independent variables: {}.",
const std::vector< fourdst::atomic::Species > & getNetworkSpecies() const override
Gets the list of species in the network.
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 reserveJacobianMatrix()
Reserves space for the Jacobian matrix.
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 ...
int getStoichiometryMatrixEntry(const int speciesIndex, const int reactionIndex) const override
Gets an entry from the stoichiometry matrix.
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.
StepDerivatives< T > calculateAllDerivatives(const std::vector< T > &Y_in, T T9, T rho) const
Calculates all derivatives (dY/dt) and the energy generation rate.
A collection of LogicalReaction objects.
Represents a single nuclear reaction from a specific data source.
CppAD::AD< double > ADDouble
Alias for CppAD AD type for double precision.
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.
Structure holding derivatives and energy generation for a network step.