2025-06-26 15:13:46 -04:00
# include "gridfire/engine/engine_graph.h"
# include "gridfire/reaction/reaction.h"
2025-06-23 15:18:56 -04:00
# include "gridfire/network.h"
2025-07-01 11:40:03 -04:00
# include "gridfire/screening/screening_types.h"
2025-06-26 15:13:46 -04:00
2025-06-23 15:18:56 -04:00
# include "fourdst/composition/species.h"
2025-06-26 15:13:46 -04:00
# include "fourdst/composition/atomicSpecies.h"
2025-06-19 09:42:20 -04:00
# include "quill/LogMacros.h"
2025-06-20 13:52:09 -04:00
# include <cstdint>
# include <iostream>
2025-06-19 09:42:20 -04:00
# include <set>
# include <stdexcept>
2025-06-20 13:52:09 -04:00
# include <string>
2025-06-19 09:42:20 -04:00
# include <string_view>
2025-06-20 13:52:09 -04:00
# include <unordered_map>
2025-06-26 15:13:46 -04:00
# include <utility>
2025-06-20 13:52:09 -04:00
# include <vector>
2025-06-21 05:04:14 -04:00
# include <fstream>
2025-06-19 09:42:20 -04:00
2025-06-20 13:52:09 -04:00
# include <boost/numeric/odeint.hpp>
2025-06-19 13:23:31 -04:00
2025-06-19 09:42:20 -04:00
2025-06-21 13:18:38 -04:00
namespace gridfire {
2025-06-26 15:13:46 -04:00
GraphEngine : : GraphEngine (
2025-06-21 13:18:38 -04:00
const fourdst : : composition : : Composition & composition
2025-06-19 09:42:20 -04:00
) :
2025-06-26 15:13:46 -04:00
m_reactions ( build_reaclib_nuclear_network ( composition , false ) ) {
2025-06-19 09:42:20 -04:00
syncInternalMaps ( ) ;
}
2025-06-29 14:53:39 -04:00
GraphEngine : : GraphEngine ( reaction : : LogicalReactionSet reactions ) :
2025-06-26 15:13:46 -04:00
m_reactions ( std : : move ( reactions ) ) {
2025-06-23 15:18:56 -04:00
syncInternalMaps ( ) ;
}
2025-06-26 15:13:46 -04:00
StepDerivatives < double > GraphEngine : : calculateRHSAndEnergy (
const std : : vector < double > & Y ,
const double T9 ,
const double rho
) const {
return calculateAllDerivatives < double > ( Y , T9 , rho ) ;
}
void GraphEngine : : syncInternalMaps ( ) {
2025-06-19 09:42:20 -04:00
collectNetworkSpecies ( ) ;
populateReactionIDMap ( ) ;
populateSpeciesToIndexMap ( ) ;
2025-06-23 15:18:56 -04:00
generateStoichiometryMatrix ( ) ;
2025-06-20 13:52:09 -04:00
reserveJacobianMatrix ( ) ;
recordADTape ( ) ;
2025-06-19 09:42:20 -04:00
}
// --- Network Graph Construction Methods ---
2025-06-26 15:13:46 -04:00
void GraphEngine : : collectNetworkSpecies ( ) {
2025-06-19 09:42:20 -04:00
m_networkSpecies . clear ( ) ;
m_networkSpeciesMap . clear ( ) ;
std : : set < std : : string_view > uniqueSpeciesNames ;
for ( const auto & reaction : m_reactions ) {
2025-06-29 14:53:39 -04:00
for ( const auto & reactant : reaction . reactants ( ) ) {
2025-06-19 09:42:20 -04:00
uniqueSpeciesNames . insert ( reactant . name ( ) ) ;
}
2025-06-29 14:53:39 -04:00
for ( const auto & product : reaction . products ( ) ) {
2025-06-19 09:42:20 -04:00
uniqueSpeciesNames . insert ( product . name ( ) ) ;
}
}
for ( const auto & name : uniqueSpeciesNames ) {
2025-06-29 14:53:39 -04:00
auto it = fourdst : : atomic : : species . find ( std : : string ( name ) ) ;
2025-06-21 13:18:38 -04:00
if ( it ! = fourdst : : atomic : : species . end ( ) ) {
2025-06-19 09:42:20 -04:00
m_networkSpecies . push_back ( it - > second ) ;
m_networkSpeciesMap . insert ( { name , it - > second } ) ;
} else {
LOG_ERROR ( m_logger , " Species '{}' not found in global atomic species database. " , name ) ;
2025-06-29 14:53:39 -04:00
m_logger - > flush_log ( ) ;
2025-06-19 09:42:20 -04:00
throw std : : runtime_error ( " Species not found in global atomic species database: " + std : : string ( name ) ) ;
}
}
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : populateReactionIDMap ( ) {
LOG_TRACE_L1 ( m_logger , " Populating reaction ID map for REACLIB graph network (serif::network::GraphNetwork)... " ) ;
2025-06-19 09:42:20 -04:00
m_reactionIDMap . clear ( ) ;
2025-06-29 14:53:39 -04:00
for ( auto & reaction : m_reactions ) {
m_reactionIDMap . emplace ( reaction . id ( ) , & reaction ) ;
2025-06-19 09:42:20 -04:00
}
2025-06-26 15:13:46 -04:00
LOG_TRACE_L1 ( m_logger , " Populated {} reactions in the reaction ID map. " , m_reactionIDMap . size ( ) ) ;
2025-06-19 09:42:20 -04:00
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : populateSpeciesToIndexMap ( ) {
2025-06-19 09:42:20 -04:00
m_speciesToIndexMap . clear ( ) ;
for ( size_t i = 0 ; i < m_networkSpecies . size ( ) ; + + i ) {
m_speciesToIndexMap . insert ( { m_networkSpecies [ i ] , i } ) ;
}
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : reserveJacobianMatrix ( ) {
2025-06-20 13:52:09 -04:00
// The implementation of this function (and others) constrains this nuclear network to a constant temperature and density during
// each evaluation.
size_t numSpecies = m_networkSpecies . size ( ) ;
m_jacobianMatrix . clear ( ) ;
m_jacobianMatrix . resize ( numSpecies , numSpecies , false ) ; // Sparse matrix, no initial values
2025-06-26 15:13:46 -04:00
LOG_TRACE_L2 ( m_logger , " Jacobian matrix resized to {} rows and {} columns. " ,
2025-06-20 13:52:09 -04:00
m_jacobianMatrix . size1 ( ) , m_jacobianMatrix . size2 ( ) ) ;
2025-06-19 09:42:20 -04:00
}
2025-06-20 13:52:09 -04:00
// --- Basic Accessors and Queries ---
2025-06-26 15:13:46 -04:00
const std : : vector < fourdst : : atomic : : Species > & GraphEngine : : getNetworkSpecies ( ) const {
2025-06-19 09:42:20 -04:00
// Returns a constant reference to the vector of unique species in the network.
2025-06-29 14:53:39 -04:00
LOG_TRACE_L3 ( m_logger , " Providing access to network species vector. Size: {}. " , m_networkSpecies . size ( ) ) ;
2025-06-19 09:42:20 -04:00
return m_networkSpecies ;
}
2025-06-29 14:53:39 -04:00
const reaction : : LogicalReactionSet & GraphEngine : : getNetworkReactions ( ) const {
2025-06-19 09:42:20 -04:00
// Returns a constant reference to the set of reactions in the network.
2025-06-29 14:53:39 -04:00
LOG_TRACE_L3 ( m_logger , " Providing access to network reactions set. Size: {}. " , m_reactions . size ( ) ) ;
2025-06-19 09:42:20 -04:00
return m_reactions ;
}
2025-06-26 15:13:46 -04:00
bool GraphEngine : : involvesSpecies ( const fourdst : : atomic : : Species & species ) const {
2025-06-19 09:42:20 -04:00
// Checks if a given species is present in the network's species map for efficient lookup.
const bool found = m_networkSpeciesMap . contains ( species . name ( ) ) ;
LOG_DEBUG ( m_logger , " Checking if species '{}' is involved in the network: {}. " , species . name ( ) , found ? " Yes " : " No " ) ;
return found ;
}
// --- Validation Methods ---
2025-06-26 15:13:46 -04:00
bool GraphEngine : : validateConservation ( ) const {
LOG_TRACE_L1 ( m_logger , " Validating mass (A) and charge (Z) conservation across all reactions in the network. " ) ;
2025-06-19 09:42:20 -04:00
for ( const auto & reaction : m_reactions ) {
uint64_t totalReactantA = 0 ;
uint64_t totalReactantZ = 0 ;
uint64_t totalProductA = 0 ;
uint64_t totalProductZ = 0 ;
// Calculate total A and Z for reactants
2025-06-29 14:53:39 -04:00
for ( const auto & reactant : reaction . reactants ( ) ) {
2025-06-19 09:42:20 -04:00
auto it = m_networkSpeciesMap . find ( reactant . name ( ) ) ;
if ( it ! = m_networkSpeciesMap . end ( ) ) {
totalReactantA + = it - > second . a ( ) ;
totalReactantZ + = it - > second . z ( ) ;
} else {
// This scenario indicates a severe data integrity issue:
// a reactant is part of a reaction but not in the network's species map.
LOG_ERROR ( m_logger , " CRITICAL ERROR: Reactant species '{}' in reaction '{}' not found in network species map during conservation validation. " ,
2025-06-29 14:53:39 -04:00
reactant . name ( ) , reaction . id ( ) ) ;
2025-06-19 09:42:20 -04:00
return false ;
}
}
// Calculate total A and Z for products
2025-06-29 14:53:39 -04:00
for ( const auto & product : reaction . products ( ) ) {
2025-06-19 09:42:20 -04:00
auto it = m_networkSpeciesMap . find ( product . name ( ) ) ;
if ( it ! = m_networkSpeciesMap . end ( ) ) {
totalProductA + = it - > second . a ( ) ;
totalProductZ + = it - > second . z ( ) ;
} else {
// Similar critical error for product species
LOG_ERROR ( m_logger , " CRITICAL ERROR: Product species '{}' in reaction '{}' not found in network species map during conservation validation. " ,
2025-06-29 14:53:39 -04:00
product . name ( ) , reaction . id ( ) ) ;
2025-06-19 09:42:20 -04:00
return false ;
}
}
// Compare totals for conservation
if ( totalReactantA ! = totalProductA ) {
LOG_ERROR ( m_logger , " Mass number (A) not conserved for reaction '{}': Reactants A={} vs Products A={}. " ,
2025-06-29 14:53:39 -04:00
reaction . id ( ) , totalReactantA , totalProductA ) ;
2025-06-19 09:42:20 -04:00
return false ;
}
if ( totalReactantZ ! = totalProductZ ) {
LOG_ERROR ( m_logger , " Atomic number (Z) not conserved for reaction '{}': Reactants Z={} vs Products Z={}. " ,
2025-06-29 14:53:39 -04:00
reaction . id ( ) , totalReactantZ , totalProductZ ) ;
2025-06-19 09:42:20 -04:00
return false ;
}
}
2025-06-26 15:13:46 -04:00
LOG_TRACE_L1 ( m_logger , " Mass (A) and charge (Z) conservation validated successfully for all reactions. " ) ;
2025-06-19 09:42:20 -04:00
return true ; // All reactions passed the conservation check
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : validateComposition ( const fourdst : : composition : : Composition & composition , double culling , double T9 ) {
2025-06-20 13:52:09 -04:00
// Check if the requested network has already been cached.
// PERF: Rebuilding this should be pretty fast but it may be a good point of optimization in the future.
2025-06-29 14:53:39 -04:00
const reaction : : LogicalReactionSet validationReactionSet = build_reaclib_nuclear_network ( composition , false ) ;
2025-06-21 05:04:14 -04:00
// TODO: need some more robust method here to
// A. Build the basic network from the composition's species with non zero mass fractions.
// B. rebuild a new composition from the reaction set's reactants + products (with the mass fractions from the things that are only products set to 0)
// C. Rebuild the reaction set from the new composition
// D. Cull reactions where all reactants have mass fractions below the culling threshold.
// E. Be careful about maintaining caching through all of this
2025-06-20 13:52:09 -04:00
// This allows for dynamic network modification while retaining caching for networks which are very similar.
if ( validationReactionSet ! = m_reactions ) {
2025-06-26 15:13:46 -04:00
LOG_DEBUG ( m_logger , " Reaction set not cached. Rebuilding the reaction set for T9={} and culling={}. " , T9 , culling ) ;
2025-06-29 14:53:39 -04:00
m_reactions = std : : move ( validationReactionSet ) ;
2025-06-20 13:52:09 -04:00
syncInternalMaps ( ) ; // Re-sync internal maps after updating reactions. Note this will also retrace the AD tape.
}
}
2025-06-19 09:42:20 -04:00
// --- Generate Stoichiometry Matrix ---
2025-06-26 15:13:46 -04:00
void GraphEngine : : generateStoichiometryMatrix ( ) {
LOG_TRACE_L1 ( m_logger , " Generating stoichiometry matrix... " ) ;
2025-06-19 09:42:20 -04:00
// Task 1: Set dimensions and initialize the matrix
size_t numSpecies = m_networkSpecies . size ( ) ;
size_t numReactions = m_reactions . size ( ) ;
m_stoichiometryMatrix . resize ( numSpecies , numReactions , false ) ;
2025-06-26 15:13:46 -04:00
LOG_TRACE_L1 ( m_logger , " Stoichiometry matrix initialized with dimensions: {} rows (species) x {} columns (reactions). " ,
2025-06-19 09:42:20 -04:00
numSpecies , numReactions ) ;
// Task 2: Populate the stoichiometry matrix
// Iterate through all reactions, assign them a column index, and fill in their stoichiometric coefficients.
size_t reactionColumnIndex = 0 ;
for ( const auto & reaction : m_reactions ) {
// Get the net stoichiometry for the current reaction
2025-06-29 14:53:39 -04:00
std : : unordered_map < fourdst : : atomic : : Species , int > netStoichiometry = reaction . stoichiometry ( ) ;
2025-06-19 09:42:20 -04:00
// Iterate through the species and their coefficients in the stoichiometry map
2025-06-26 15:13:46 -04:00
for ( const auto & [ species , coefficient ] : netStoichiometry ) {
2025-06-19 09:42:20 -04:00
// Find the row index for this species
auto it = m_speciesToIndexMap . find ( species ) ;
if ( it ! = m_speciesToIndexMap . end ( ) ) {
const size_t speciesRowIndex = it - > second ;
// Set the matrix element. Boost.uBLAS handles sparse insertion.
m_stoichiometryMatrix ( speciesRowIndex , reactionColumnIndex ) = coefficient ;
} else {
// This scenario should ideally not happen if m_networkSpeciesMap and m_speciesToIndexMap are correctly synced
LOG_ERROR ( m_logger , " CRITICAL ERROR: Species '{}' from reaction '{}' stoichiometry not found in species to index map. " ,
2025-06-29 14:53:39 -04:00
species . name ( ) , reaction . id ( ) ) ;
m_logger - > flush_log ( ) ;
2025-06-19 09:42:20 -04:00
throw std : : runtime_error ( " Species not found in species to index map: " + std : : string ( species . name ( ) ) ) ;
}
}
reactionColumnIndex + + ; // Move to the next column for the next reaction
}
2025-06-26 15:13:46 -04:00
LOG_TRACE_L1 ( m_logger , " Stoichiometry matrix population complete. Number of non-zero elements: {}. " ,
2025-06-19 09:42:20 -04:00
m_stoichiometryMatrix . nnz ( ) ) ; // Assuming nnz() exists for compressed_matrix
}
2025-06-26 15:13:46 -04:00
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 ,
2025-06-29 14:53:39 -04:00
const ADDouble & T9 ,
const ADDouble & rho
2025-06-26 15:13:46 -04:00
) const {
return calculateAllDerivatives < ADDouble > ( Y_in , T9 , rho ) ;
}
2025-07-01 11:40:03 -04:00
void GraphEngine : : setScreeningModel ( const screening : : ScreeningType model ) {
m_screeningModel = screening : : selectScreeningModel ( model ) ;
m_screeningType = model ;
}
screening : : ScreeningType GraphEngine : : getScreeningModel ( ) const {
return m_screeningType ;
}
2025-06-26 15:13:46 -04:00
double GraphEngine : : calculateMolarReactionFlow (
const reaction : : Reaction & reaction ,
const std : : vector < double > & Y ,
const double T9 ,
const double rho
) const {
return calculateMolarReactionFlow < double > ( reaction , Y , T9 , rho ) ;
}
void GraphEngine : : generateJacobianMatrix (
const std : : vector < double > & Y ,
const double T9 ,
const double rho
) {
2025-06-19 13:23:31 -04:00
2025-06-26 15:13:46 -04:00
LOG_TRACE_L1 ( m_logger , " Generating jacobian matrix for T9={}, rho={}.. " , T9 , rho ) ;
2025-06-20 13:52:09 -04:00
const size_t numSpecies = m_networkSpecies . size ( ) ;
2025-06-19 13:23:31 -04:00
2025-06-20 13:52:09 -04:00
// 1. Pack the input variables into a vector for CppAD
std : : vector < double > adInput ( numSpecies + 2 , 0.0 ) ; // +2 for T9 and rho
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
adInput [ i ] = Y [ i ] ;
}
adInput [ numSpecies ] = T9 ; // T9
adInput [ numSpecies + 1 ] = rho ; // rho
// 2. Calculate the full jacobian
const std : : vector < double > dotY = m_rhsADFun . Jacobian ( adInput ) ;
// 3. Pack jacobian vector into sparse matrix
m_jacobianMatrix . clear ( ) ;
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
for ( size_t j = 0 ; j < numSpecies ; + + j ) {
const double value = dotY [ i * ( numSpecies + 2 ) + j ] ;
if ( std : : abs ( value ) > MIN_JACOBIAN_THRESHOLD ) {
m_jacobianMatrix ( i , j ) = value ;
}
}
}
2025-06-29 14:53:39 -04:00
LOG_TRACE_L1 ( m_logger , " Jacobian matrix generated with dimensions: {} rows x {} columns. " , m_jacobianMatrix . size1 ( ) , m_jacobianMatrix . size2 ( ) ) ;
2025-06-19 13:23:31 -04:00
}
2025-06-26 15:13:46 -04:00
double GraphEngine : : getJacobianMatrixEntry ( const int i , const int j ) const {
return m_jacobianMatrix ( i , j ) ;
}
2025-06-19 13:23:31 -04:00
2025-06-26 15:13:46 -04:00
std : : unordered_map < fourdst : : atomic : : Species , int > GraphEngine : : getNetReactionStoichiometry (
const reaction : : Reaction & reaction
2025-06-29 14:53:39 -04:00
) {
2025-06-26 15:13:46 -04:00
return reaction . stoichiometry ( ) ;
}
2025-06-23 15:18:56 -04:00
2025-06-26 15:13:46 -04:00
int GraphEngine : : getStoichiometryMatrixEntry (
const int speciesIndex ,
const int reactionIndex
) const {
return m_stoichiometryMatrix ( speciesIndex , reactionIndex ) ;
2025-06-19 13:23:31 -04:00
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : exportToDot ( const std : : string & filename ) const {
LOG_TRACE_L1 ( m_logger , " Exporting network graph to DOT file: {} " , filename ) ;
2025-06-21 05:04:14 -04:00
std : : ofstream dotFile ( filename ) ;
if ( ! dotFile . is_open ( ) ) {
LOG_ERROR ( m_logger , " Failed to open file for writing: {} " , filename ) ;
2025-06-29 14:53:39 -04:00
m_logger - > flush_log ( ) ;
2025-06-21 05:04:14 -04:00
throw std : : runtime_error ( " Failed to open file for writing: " + filename ) ;
}
dotFile < < " digraph NuclearReactionNetwork { \n " ;
dotFile < < " graph [rankdir=LR, splines=true, overlap=false, bgcolor= \" #f0f0f0 \" ]; \n " ;
dotFile < < " node [shape=circle, style=filled, fillcolor= \" #a7c7e7 \" , fontname= \" Helvetica \" ]; \n " ;
dotFile < < " edge [fontname= \" Helvetica \" , fontsize=10]; \n \n " ;
// 1. Define all species as nodes
dotFile < < " // --- Species Nodes --- \n " ;
for ( const auto & species : m_networkSpecies ) {
dotFile < < " \" " < < species . name ( ) < < " \" [label= \" " < < species . name ( ) < < " \" ]; \n " ;
}
dotFile < < " \n " ;
// 2. Define all reactions as intermediate nodes and connect them
dotFile < < " // --- Reaction Edges --- \n " ;
for ( const auto & reaction : m_reactions ) {
// Create a unique ID for the reaction node
2025-06-29 14:53:39 -04:00
std : : string reactionNodeId = " reaction_ " + std : : string ( reaction . id ( ) ) ;
2025-06-21 05:04:14 -04:00
// Define the reaction node (small, black dot)
dotFile < < " \" " < < reactionNodeId < < " \" [shape=point, fillcolor=black, width=0.1, height=0.1, label= \" \" ]; \n " ;
// Draw edges from reactants to the reaction node
2025-06-29 14:53:39 -04:00
for ( const auto & reactant : reaction . reactants ( ) ) {
2025-06-21 05:04:14 -04:00
dotFile < < " \" " < < reactant . name ( ) < < " \" -> \" " < < reactionNodeId < < " \" ; \n " ;
}
// Draw edges from the reaction node to products
2025-06-29 14:53:39 -04:00
for ( const auto & product : reaction . products ( ) ) {
dotFile < < " \" " < < reactionNodeId < < " \" -> \" " < < product . name ( ) < < " \" [label= \" " < < reaction . qValue ( ) < < " MeV \" ]; \n " ;
2025-06-21 05:04:14 -04:00
}
dotFile < < " \n " ;
}
dotFile < < " } \n " ;
dotFile . close ( ) ;
2025-06-26 15:13:46 -04:00
LOG_TRACE_L1 ( m_logger , " Successfully exported network to {} " , filename ) ;
2025-06-21 05:04:14 -04:00
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : exportToCSV ( const std : : string & filename ) const {
LOG_TRACE_L1 ( m_logger , " Exporting network graph to CSV file: {} " , filename ) ;
2025-06-19 13:23:31 -04:00
2025-06-26 15:13:46 -04:00
std : : ofstream csvFile ( filename , std : : ios : : out | std : : ios : : trunc ) ;
if ( ! csvFile . is_open ( ) ) {
LOG_ERROR ( m_logger , " Failed to open file for writing: {} " , filename ) ;
2025-06-29 14:53:39 -04:00
m_logger - > flush_log ( ) ;
2025-06-26 15:13:46 -04:00
throw std : : runtime_error ( " Failed to open file for writing: " + filename ) ;
2025-06-20 13:52:09 -04:00
}
2025-06-26 15:13:46 -04:00
csvFile < < " Reaction;Reactants;Products;Q-value;sources;rates \n " ;
for ( const auto & reaction : m_reactions ) {
// Dynamic cast to REACLIBReaction to access specific properties
2025-06-29 14:53:39 -04:00
csvFile < < reaction . id ( ) < < " ; " ;
2025-06-26 15:13:46 -04:00
// Reactants
int count = 0 ;
2025-06-29 14:53:39 -04:00
for ( const auto & reactant : reaction . reactants ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < reactant . name ( ) ;
2025-06-29 14:53:39 -04:00
if ( + + count < reaction . reactants ( ) . size ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < " , " ;
}
}
csvFile < < " ; " ;
count = 0 ;
2025-06-29 14:53:39 -04:00
for ( const auto & product : reaction . products ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < product . name ( ) ;
2025-06-29 14:53:39 -04:00
if ( + + count < reaction . products ( ) . size ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < " , " ;
}
}
2025-06-29 14:53:39 -04:00
csvFile < < " ; " < < reaction . qValue ( ) < < " ; " ;
2025-06-26 15:13:46 -04:00
// Reaction coefficients
2025-06-29 14:53:39 -04:00
auto sources = reaction . sources ( ) ;
2025-06-26 15:13:46 -04:00
count = 0 ;
for ( const auto & source : sources ) {
csvFile < < source ;
if ( + + count < sources . size ( ) ) {
csvFile < < " , " ;
}
}
csvFile < < " ; " ;
// Reaction coefficients
count = 0 ;
2025-06-29 14:53:39 -04:00
for ( const auto & rates : reaction ) {
2025-06-26 15:13:46 -04:00
csvFile < < rates ;
2025-06-29 14:53:39 -04:00
if ( + + count < reaction . size ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < " , " ;
}
}
csvFile < < " \n " ;
2025-06-19 13:23:31 -04:00
}
2025-06-26 15:13:46 -04:00
csvFile . close ( ) ;
LOG_TRACE_L1 ( m_logger , " Successfully exported network graph to {} " , filename ) ;
}
2025-06-20 13:52:09 -04:00
2025-06-26 15:13:46 -04:00
std : : unordered_map < fourdst : : atomic : : Species , double > GraphEngine : : getSpeciesTimescales ( const std : : vector < double > & Y , const double T9 ,
const double rho ) const {
auto [ dydt , _ ] = calculateAllDerivatives < double > ( Y , T9 , rho ) ;
std : : unordered_map < fourdst : : atomic : : Species , double > speciesTimescales ;
speciesTimescales . reserve ( m_networkSpecies . size ( ) ) ;
for ( size_t i = 0 ; i < m_networkSpecies . size ( ) ; + + i ) {
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 ] ) ;
}
speciesTimescales . emplace ( species , timescale ) ;
2025-06-20 13:52:09 -04:00
}
2025-06-26 15:13:46 -04:00
return speciesTimescales ;
2025-06-19 13:23:31 -04:00
}
2025-07-01 11:40:03 -04:00
void GraphEngine : : update ( const NetIn & netIn ) {
return ; // No-op for GraphEngine, as it does not support manually triggering updates.
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : recordADTape ( ) {
LOG_TRACE_L1 ( m_logger , " Recording AD tape for the RHS calculation... " ) ;
2025-06-20 13:52:09 -04:00
// Task 1: Set dimensions and initialize the matrix
const size_t numSpecies = m_networkSpecies . size ( ) ;
if ( numSpecies = = 0 ) {
LOG_ERROR ( m_logger , " Cannot record AD tape: No species in the network. " ) ;
2025-06-29 14:53:39 -04:00
m_logger - > flush_log ( ) ;
2025-06-20 13:52:09 -04:00
throw std : : runtime_error ( " Cannot record AD tape: No species in the network. " ) ;
}
const size_t numADInputs = numSpecies + 2 ; // Note here that by not letting T9 and rho be independent variables, we are constraining the network to a constant temperature and density during each evaluation.
// --- CppAD Tape Recording ---
// 1. Declare independent variable (adY)
// We also initialize the dummy variable for tape recording (these tell CppAD what the derivative chain looks like).
// Their numeric values are irrelevant except for in so far as they avoid numerical instabilities.
// Distribute total mass fraction uniformly between species in the dummy variable space
const auto uniformMassFraction = static_cast < CppAD : : AD < double > > ( 1.0 / numSpecies ) ;
std : : vector < CppAD : : AD < double > > adInput ( numADInputs , uniformMassFraction ) ;
adInput [ numSpecies ] = 1.0 ; // Dummy T9
adInput [ numSpecies + 1 ] = 1.0 ; // Dummy rho
// 3. Declare independent variables (what CppAD will differentiate wrt.)
// This also beings the tape recording process.
CppAD : : Independent ( adInput ) ;
std : : vector < CppAD : : AD < double > > adY ( numSpecies ) ;
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
adY [ i ] = adInput [ i ] ;
}
const CppAD : : AD < double > adT9 = adInput [ numSpecies ] ;
const CppAD : : AD < double > adRho = adInput [ numSpecies + 1 ] ;
// 5. Call the actual templated function
// We let T9 and rho be constant, so we pass them as fixed values.
2025-06-26 15:13:46 -04:00
auto [ dydt , nuclearEnergyGenerationRate ] = calculateAllDerivatives < CppAD : : AD < double > > ( adY , adT9 , adRho ) ;
2025-06-20 13:52:09 -04:00
2025-06-26 15:13:46 -04:00
m_rhsADFun . Dependent ( adInput , dydt ) ;
2025-06-20 13:52:09 -04:00
2025-06-26 15:13:46 -04:00
LOG_TRACE_L1 ( m_logger , " AD tape recorded successfully for the RHS calculation. Number of independent variables: {}. " ,
2025-06-20 13:52:09 -04:00
adInput . size ( ) ) ;
2025-06-19 09:42:20 -04:00
}
}