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-07-14 14:50:49 -04:00
# include "gridfire/engine/procedures/priming.h"
# include "gridfire/partition/partition_ground.h"
# include "gridfire/engine/procedures/construction.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>
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-07-03 09:55:10 -04:00
# include <ranges>
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-07-14 14:50:49 -04:00
# include "cppad/cppad.hpp"
# include "cppad/utility/sparse_rc.hpp"
# include "cppad/utility/sparse_rcv.hpp"
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-07-14 14:50:49 -04:00
const fourdst : : composition : : Composition & composition ,
const BuildDepthType buildDepth
) : GraphEngine ( composition , partition : : GroundStatePartitionFunction ( ) , buildDepth ) { }
2025-07-03 09:55:10 -04:00
GraphEngine : : GraphEngine (
const fourdst : : composition : : Composition & composition ,
2025-07-14 14:50:49 -04:00
const partition : : PartitionFunction & partitionFunction ,
const BuildDepthType buildDepth ) :
2025-10-07 15:16:03 -04:00
m_weakRateInterpolator ( rates : : weak : : UNIFIED_WEAK_DATA ) ,
2025-10-08 11:17:35 -04:00
m_reactions ( build_nuclear_network ( composition , m_weakRateInterpolator , buildDepth , false ) ) ,
2025-07-24 08:37:52 -04:00
m_depth ( buildDepth ) ,
m_partitionFunction ( partitionFunction . clone ( ) )
2025-07-03 09:55:10 -04:00
{
2025-06-19 09:42:20 -04:00
syncInternalMaps ( ) ;
}
2025-07-01 14:30:45 -04:00
GraphEngine : : GraphEngine (
2025-08-14 13:33:46 -04:00
const reaction : : ReactionSet & reactions
2025-07-01 14:30:45 -04:00
) :
2025-10-07 15:16:03 -04:00
m_weakRateInterpolator ( rates : : weak : : UNIFIED_WEAK_DATA ) ,
m_reactions ( reactions )
{
2025-07-01 14:30:45 -04:00
syncInternalMaps ( ) ;
}
2025-06-23 15:18:56 -04:00
2025-07-22 12:48:24 -04:00
std : : expected < StepDerivatives < double > , expectations : : StaleEngineError > GraphEngine : : calculateRHSAndEnergy (
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-06-26 15:13:46 -04:00
const double T9 ,
const double rho
2025-10-22 09:54:10 -04:00
) const {
return calculateRHSAndEnergy ( comp , T9 , rho , m_reactions ) ;
}
std : : expected < StepDerivatives < double > , expectations : : StaleEngineError > GraphEngine : : calculateRHSAndEnergy (
const fourdst : : composition : : Composition & comp ,
const double T9 ,
const double rho ,
const reaction : : ReactionSet & activeReactions
2025-06-26 15:13:46 -04:00
) const {
2025-10-07 15:16:03 -04:00
const double Ye = comp . getElectronAbundance ( ) ;
const double mue = 5.0e-3 * std : : pow ( rho * Ye , 1.0 / 3.0 ) + 0.5 * T9 ;
2025-07-01 14:30:45 -04:00
if ( m_usePrecomputation ) {
std : : vector < double > bare_rates ;
2025-07-10 09:36:05 -04:00
std : : vector < double > bare_reverse_rates ;
2025-07-01 14:30:45 -04:00
bare_rates . reserve ( m_reactions . size ( ) ) ;
2025-07-10 09:36:05 -04:00
bare_reverse_rates . reserve ( m_reactions . size ( ) ) ;
2025-07-18 15:23:43 -04:00
// TODO: Add cache to this
2025-10-07 15:16:03 -04:00
2025-07-01 14:30:45 -04:00
for ( const auto & reaction : m_reactions ) {
2025-10-07 15:16:03 -04:00
bare_rates . push_back ( reaction - > calculate_rate ( T9 , rho , Ye , mue , comp . getMolarAbundanceVector ( ) , m_indexToSpeciesMap ) ) ;
2025-10-22 09:54:10 -04:00
if ( reaction - > type ( ) ! = reaction : : ReactionType : : WEAK ) {
bare_reverse_rates . push_back ( calculateReverseRate ( * reaction , T9 , rho , comp ) ) ;
}
2025-07-01 14:30:45 -04:00
}
// --- The public facing interface can always use the precomputed version since taping is done internally ---
2025-10-07 15:16:03 -04:00
return calculateAllDerivativesUsingPrecomputation ( comp , bare_rates , bare_reverse_rates , T9 , rho ) ;
2025-07-01 14:30:45 -04:00
} else {
2025-10-10 09:12:40 -04:00
return calculateAllDerivatives < double > (
comp . getMolarAbundanceVector ( ) ,
T9 ,
rho ,
Ye ,
mue ,
[ & comp ] ( const fourdst : : atomic : : Species & species ) - > std : : optional < size_t > {
if ( comp . contains ( species ) ) {
return comp . getSpeciesIndex ( species ) ; // Return the index of the species in the composition
}
return std : : nullopt ; // Species not found in the composition
2025-10-22 09:54:10 -04:00
} ,
[ & activeReactions ] ( const reaction : : Reaction & reaction ) - > bool {
if ( activeReactions . contains ( reaction ) ) { return true ; }
return false ;
2025-10-10 09:12:40 -04:00
}
) ;
2025-07-01 14:30:45 -04:00
}
2025-06-26 15:13:46 -04:00
}
2025-09-19 15:14:46 -04:00
EnergyDerivatives GraphEngine : : calculateEpsDerivatives (
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-09-19 15:14:46 -04:00
const double T9 ,
const double rho
2025-10-22 09:54:10 -04:00
) const {
return calculateEpsDerivatives ( comp , T9 , rho , m_reactions ) ;
}
EnergyDerivatives GraphEngine : : calculateEpsDerivatives (
const fourdst : : composition : : Composition & comp ,
const double T9 ,
const double rho ,
const reaction : : ReactionSet & activeReactions
2025-09-19 15:14:46 -04:00
) const {
const size_t numSpecies = m_networkSpecies . size ( ) ;
const size_t numADInputs = numSpecies + 2 ; // +2 for T9 and rho
2025-10-07 15:16:03 -04:00
if ( comp . getRegisteredSpecies ( ) . size ( ) ! = numSpecies ) {
2025-09-19 15:14:46 -04:00
LOG_ERROR ( m_logger , " Input abundance vector size ({}) does not match number of species in the network ({}). " ,
2025-10-07 15:16:03 -04:00
comp . getRegisteredSpecies ( ) . size ( ) , numSpecies ) ;
2025-09-19 15:14:46 -04:00
throw std : : invalid_argument ( " Input abundance vector size does not match number of species in the network. " ) ;
}
std : : vector < double > x ( numADInputs ) ;
2025-10-07 15:16:03 -04:00
const std : : vector < double > Y = comp . getMolarAbundanceVector ( ) ;
2025-09-19 15:14:46 -04:00
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
x [ i ] = Y [ i ] ;
}
x [ numSpecies ] = T9 ;
x [ numSpecies + 1 ] = rho ;
// Use reverse mode to get the gradient. W selects which dependent variable we care about, the Eps AD tape only has eps as a dependent variable so we just select set the 0th element to 1.
std : : vector < double > w ( 1 ) ;
w [ 0 ] = 1.0 ; // We want the derivative of the energy generation rate
// Sweep the tape forward to record the function value at x
m_epsADFun . Forward ( 0 , x ) ;
// Extract the gradient at the previously evaluated point x using reverse mode
const std : : vector < double > eps_derivatives = m_epsADFun . Reverse ( 1 , w ) ;
const double dEps_dT9 = eps_derivatives [ numSpecies ] ;
const double dEps_dRho = eps_derivatives [ numSpecies + 1 ] ;
// Chain rule to scale from deps/dT9 to deps/dT
// dT9/dT = 1e-9
const double dEps_dT = dEps_dT9 * 1e-9 ;
return { dEps_dT , dEps_dRho } ;
}
2025-06-26 15:13:46 -04:00
void GraphEngine : : syncInternalMaps ( ) {
2025-07-10 09:36:05 -04:00
LOG_INFO ( m_logger , " Synchronizing internal maps for REACLIB graph network (serif::network::GraphNetwork)... " ) ;
2025-06-19 09:42:20 -04:00
collectNetworkSpecies ( ) ;
populateReactionIDMap ( ) ;
populateSpeciesToIndexMap ( ) ;
2025-07-10 09:36:05 -04:00
collectAtomicReverseRateAtomicBases ( ) ;
2025-06-23 15:18:56 -04:00
generateStoichiometryMatrix ( ) ;
2025-06-20 13:52:09 -04:00
reserveJacobianMatrix ( ) ;
2025-09-19 15:14:46 -04:00
recordADTape ( ) ; // Record the AD tape for the RHS function
recordEpsADTape ( ) ; // Record the AD tape for the energy generation rate function
2025-07-14 14:50:49 -04:00
const size_t n = m_rhsADFun . Domain ( ) ;
const size_t m = m_rhsADFun . Range ( ) ;
2025-07-22 12:48:24 -04:00
const std : : vector < bool > select_domain ( n , true ) ;
const std : : vector < bool > select_range ( m , true ) ;
2025-07-14 14:50:49 -04:00
m_rhsADFun . subgraph_sparsity ( select_domain , select_range , false , m_full_jacobian_sparsity_pattern ) ;
m_jac_work . clear ( ) ;
2025-07-10 09:36:05 -04:00
precomputeNetwork ( ) ;
LOG_INFO ( m_logger , " Internal maps synchronized. Network contains {} species and {} reactions. " ,
m_networkSpecies . size ( ) , m_reactions . size ( ) ) ;
2025-06-19 09:42:20 -04:00
}
// --- Network Graph Construction Methods ---
2025-06-26 15:13:46 -04:00
void GraphEngine : : collectNetworkSpecies ( ) {
2025-10-07 15:16:03 -04:00
//TODO: Ensure consistent ordering in the m_networkSpecies vector so that it is ordered by species mass.
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-08-14 13:33:46 -04:00
for ( const auto & reactant : reaction - > reactants ( ) ) {
2025-06-19 09:42:20 -04:00
uniqueSpeciesNames . insert ( reactant . name ( ) ) ;
}
2025-08-14 13:33:46 -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-10-22 09:54:10 -04:00
// TODO: Currently this works. We sort the vector based on mass so that for the same set of species we always get the same ordering and we get the same ordering as a composition with the same set of species
// However, we need some checks so that when we get a composition we confirm that it is the same ordering / contains teh same species. This is important for the ODE integrator to work properly.
2025-10-14 13:37:48 -04:00
std : : ranges : : sort ( m_networkSpecies , [ ] ( const fourdst : : atomic : : Species & a , const fourdst : : atomic : : Species & b ) - > bool {
return a . mass ( ) < b . mass ( ) ; // Otherwise, sort by mass
} ) ;
2025-06-19 09:42:20 -04:00
}
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 ) {
2025-08-14 13:33:46 -04:00
m_reactionIDMap . emplace ( reaction - > id ( ) , reaction . get ( ) ) ;
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-10-07 15:16:03 -04:00
m_indexToSpeciesMap . clear ( ) ;
for ( size_t i = 0 ; i < m_networkSpecies . size ( ) ; + + i ) {
m_indexToSpeciesMap . insert ( { i , m_networkSpecies [ i ] } ) ;
}
2025-06-19 09:42:20 -04:00
}
2025-07-22 12:48:24 -04:00
void GraphEngine : : reserveJacobianMatrix ( ) const {
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.
2025-07-22 12:48:24 -04:00
const size_t numSpecies = m_networkSpecies . size ( ) ;
2025-06-20 13:52:09 -04:00
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-09-19 15:14:46 -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-08-14 13:33:46 -04:00
const reaction : : ReactionSet & GraphEngine : : getNetworkReactions ( ) const {
2025-06-19 09:42:20 -04:00
// Returns a constant reference to the set of reactions in the network.
2025-09-19 15:14:46 -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-08-14 13:33:46 -04:00
void GraphEngine : : setNetworkReactions ( const reaction : : ReactionSet & reactions ) {
2025-07-22 12:48:24 -04:00
m_reactions = reactions ;
syncInternalMaps ( ) ;
}
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-08-14 13:33:46 -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-08-14 13:33:46 -04:00
reactant . name ( ) , reaction - > id ( ) ) ;
2025-06-19 09:42:20 -04:00
return false ;
}
}
// Calculate total A and Z for products
2025-08-14 13:33:46 -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-08-14 13:33:46 -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-08-14 13:33:46 -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-08-14 13:33:46 -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-07-03 09:55:10 -04:00
double GraphEngine : : calculateReverseRate (
const reaction : : Reaction & reaction ,
2025-08-14 13:33:46 -04:00
const double T9 ,
const double rho ,
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp
2025-07-03 09:55:10 -04:00
) const {
2025-07-10 09:36:05 -04:00
if ( ! m_useReverseReactions ) {
2025-07-22 12:48:24 -04:00
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 ( ) ) ;
2025-07-10 09:36:05 -04:00
return 0.0 ; // If reverse reactions are not used, return 0.0
}
2025-07-29 07:49:01 -04:00
const double temp = T9 * 1e9 ; // Convert T9 to Kelvin
2025-10-07 15:16:03 -04:00
2025-10-10 09:12:40 -04:00
// Reverse reactions are only relevant for strong reactions (at least during the vast majority of stellar evolution)
// So here we just let these be dummy values since we know
// 1. The reaction should always be strong
// 2. The strong reaction rate is independent of Ye and mue
//
// In development builds the assert below will confirm this
constexpr double Ye = 0.0 ;
constexpr double mue = 0.0 ;
2025-10-07 15:16:03 -04:00
2025-10-10 09:12:40 -04:00
// It is a logic error to call this function on a weak reaction
assert ( reaction . type ( ) ! = gridfire : : reaction : : ReactionType : : WEAK ) ;
2025-07-29 07:49:01 -04:00
// 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 " ) ;
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 ) ) ;
2025-07-03 09:55:10 -04:00
double reverseRate = 0.0 ;
2025-10-10 09:12:40 -04:00
// We also let Y be an empy vector since the strong reaction rate is independent of Y
const double forwardRate = reaction . calculate_rate ( T9 , rho , Ye , mue , { } , m_indexToSpeciesMap ) ;
2025-07-03 09:55:10 -04:00
if ( reaction . reactants ( ) . size ( ) = = 2 & & reaction . products ( ) . size ( ) = = 2 ) {
reverseRate = calculateReverseRateTwoBody ( reaction , T9 , forwardRate , expFactor ) ;
} else {
2025-08-14 13:33:46 -04:00
LOG_WARNING_LIMIT_EVERY_N ( 1000000 , m_logger , " Reverse rate calculation for reactions with more than two reactants or products is not implemented (reaction id {}). " , reaction . id ( ) ) ;
2025-07-03 09:55:10 -04:00
}
2025-07-22 12:48:24 -04:00
LOG_TRACE_L2_LIMIT_EVERY_N ( 1000 , m_logger , " Calculated reverse rate for reaction '{}': {:.3E} at T9={:.3E}. " , reaction . id ( ) , reverseRate , T9 ) ;
2025-07-03 09:55:10 -04:00
return reverseRate ;
}
double GraphEngine : : calculateReverseRateTwoBody (
const reaction : : Reaction & reaction ,
const double T9 ,
const double forwardRate ,
const double expFactor
) const {
std : : vector < double > reactantPartitionFunctions ;
std : : vector < double > productPartitionFunctions ;
reactantPartitionFunctions . reserve ( reaction . reactants ( ) . size ( ) ) ;
productPartitionFunctions . reserve ( reaction . products ( ) . size ( ) ) ;
std : : unordered_map < fourdst : : atomic : : Species , int > reactantMultiplicity ;
std : : unordered_map < fourdst : : atomic : : Species , int > productMultiplicity ;
reactantMultiplicity . reserve ( reaction . reactants ( ) . size ( ) ) ;
productMultiplicity . reserve ( reaction . products ( ) . size ( ) ) ;
for ( const auto & reactant : reaction . reactants ( ) ) {
reactantMultiplicity [ reactant ] + = 1 ;
}
for ( const auto & product : reaction . products ( ) ) {
productMultiplicity [ product ] + = 1 ;
}
double reactantSymmetryFactor = 1.0 ;
double productSymmetryFactor = 1.0 ;
for ( const auto & count : reactantMultiplicity | std : : views : : values ) {
reactantSymmetryFactor * = std : : tgamma ( count + 1 ) ;
}
for ( const auto & count : productMultiplicity | std : : views : : values ) {
productSymmetryFactor * = std : : tgamma ( count + 1 ) ;
}
const double symmetryFactor = reactantSymmetryFactor / productSymmetryFactor ;
// Accumulate mass terms
auto mass_op = [ ] ( double acc , const auto & species ) { return acc * species . a ( ) ; } ;
const double massNumerator = std : : accumulate (
reaction . reactants ( ) . begin ( ) ,
reaction . reactants ( ) . end ( ) ,
1.0 ,
mass_op
) ;
const double massDenominator = std : : accumulate (
reaction . products ( ) . begin ( ) ,
reaction . products ( ) . end ( ) ,
1.0 ,
mass_op
) ;
// Accumulate partition functions
2025-07-10 09:36:05 -04:00
auto pf_op = [ & ] ( double acc , const auto & species ) {
return acc * m_partitionFunction - > evaluate ( species . z ( ) , species . a ( ) , T9 ) ;
} ;
2025-07-03 09:55:10 -04:00
const double partitionFunctionNumerator = std : : accumulate (
reaction . reactants ( ) . begin ( ) ,
reaction . reactants ( ) . end ( ) ,
1.0 ,
pf_op
) ;
const double partitionFunctionDenominator = std : : accumulate (
reaction . products ( ) . begin ( ) ,
reaction . products ( ) . end ( ) ,
1.0 ,
pf_op
) ;
2025-07-10 09:36:05 -04:00
const double CT = std : : pow ( massNumerator / massDenominator , 1.5 ) *
( partitionFunctionNumerator / partitionFunctionDenominator ) ;
2025-07-03 09:55:10 -04:00
2025-07-22 12:48:24 -04:00
const double reverseRate = forwardRate * symmetryFactor * CT * expFactor ;
2025-07-10 09:36:05 -04:00
if ( ! std : : isfinite ( reverseRate ) ) {
return 0.0 ; // If the reverse rate is not finite, return 0.0
}
return reverseRate ; // Return the calculated reverse rate
2025-07-03 09:55:10 -04:00
}
double GraphEngine : : calculateReverseRateTwoBodyDerivative (
const reaction : : Reaction & reaction ,
const double T9 ,
2025-08-14 13:33:46 -04:00
const double rho ,
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-07-03 09:55:10 -04:00
const double reverseRate
) const {
2025-10-22 09:54:10 -04:00
assert ( reaction . type ( ) = = reaction : : ReactionType : : LOGICAL_REACLIB | | reaction . type ( ) = = reaction : : ReactionType : : REACLIB ) ;
2025-07-10 09:36:05 -04:00
if ( ! m_useReverseReactions ) {
2025-07-22 12:48:24 -04:00
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 ( ) ) ;
2025-07-10 09:36:05 -04:00
return 0.0 ; // If reverse reactions are not used, return 0.0
}
2025-10-22 09:54:10 -04:00
const double d_log_kFwd = reaction . calculate_log_rate_partial_deriv_wrt_T9 ( T9 , rho , { } , { } , { } ) ;
2025-07-03 09:55:10 -04:00
auto log_deriv_pf_op = [ & ] ( double acc , const auto & species ) {
const double g = m_partitionFunction - > evaluate ( species . z ( ) , species . a ( ) , T9 ) ;
const double dg_dT = m_partitionFunction - > evaluateDerivative ( species . z ( ) , species . a ( ) , T9 ) ;
return ( g = = 0.0 ) ? acc : acc + ( dg_dT / g ) ;
} ;
const double reactant_log_derivative_sum = std : : accumulate (
reaction . reactants ( ) . begin ( ) ,
reaction . reactants ( ) . end ( ) ,
0.0 ,
log_deriv_pf_op
) ;
const double product_log_derivative_sum = std : : accumulate (
reaction . products ( ) . begin ( ) ,
reaction . products ( ) . end ( ) ,
0.0 ,
log_deriv_pf_op
) ;
const double d_log_C = reactant_log_derivative_sum - product_log_derivative_sum ;
const double d_log_exp = reaction . qValue ( ) / ( m_constants . kB * T9 * T9 ) ;
const double log_total_derivative = d_log_kFwd + d_log_C + d_log_exp ;
return reverseRate * log_total_derivative ; // Return the derivative of the reverse rate with respect to T9
}
2025-07-10 09:36:05 -04:00
bool GraphEngine : : isUsingReverseReactions ( ) const {
return m_useReverseReactions ;
}
void GraphEngine : : setUseReverseReactions ( const bool useReverse ) {
m_useReverseReactions = useReverse ;
}
2025-08-14 13:33:46 -04:00
size_t GraphEngine : : getSpeciesIndex ( const fourdst : : atomic : : Species & species ) const {
2025-07-10 09:36:05 -04:00
return m_speciesToIndexMap . at ( species ) ; // Returns the index of the species in the stoichiometry matrix
}
std : : vector < double > GraphEngine : : mapNetInToMolarAbundanceVector ( const NetIn & netIn ) const {
std : : vector < double > Y ( m_networkSpecies . size ( ) , 0.0 ) ; // Initialize with zeros
for ( const auto & [ symbol , entry ] : netIn . composition ) {
Y [ getSpeciesIndex ( entry . isotope ( ) ) ] = netIn . composition . getMolarAbundance ( symbol ) ; // Map species to their molar abundance
}
return Y ; // Return the vector of molar abundances
}
2025-07-14 14:50:49 -04:00
PrimingReport GraphEngine : : primeEngine ( const NetIn & netIn ) {
NetIn fullNetIn ;
fourdst : : composition : : Composition composition ;
std : : vector < std : : string > symbols ;
symbols . reserve ( m_networkSpecies . size ( ) ) ;
for ( const auto & symbol : m_networkSpecies ) {
symbols . emplace_back ( symbol . name ( ) ) ;
}
composition . registerSymbol ( symbols ) ;
for ( const auto & [ symbol , entry ] : netIn . composition ) {
if ( m_networkSpeciesMap . contains ( symbol ) ) {
composition . setMassFraction ( symbol , entry . mass_fraction ( ) ) ;
} else {
composition . setMassFraction ( symbol , 0.0 ) ;
}
}
2025-10-14 13:37:48 -04:00
const bool didFinalize = composition . finalize ( true ) ;
if ( ! didFinalize ) {
LOG_ERROR ( m_logger , " Failed to finalize composition during priming. Check input mass fractions for validity. " ) ;
throw std : : runtime_error ( " Failed to finalize composition during network priming. " ) ;
}
2025-07-14 14:50:49 -04:00
fullNetIn . composition = composition ;
fullNetIn . temperature = netIn . temperature ;
fullNetIn . density = netIn . density ;
2025-10-14 13:37:48 -04:00
std : : optional < std : : vector < reaction : : ReactionType > > reactionTypesToIgnore = std : : nullopt ;
if ( ! m_useReverseReactions ) {
reactionTypesToIgnore = { reaction : : ReactionType : : WEAK } ;
}
auto primingReport = primeNetwork ( fullNetIn , * this , reactionTypesToIgnore ) ;
2025-07-14 14:50:49 -04:00
return primingReport ;
}
BuildDepthType GraphEngine : : getDepth ( ) const {
return m_depth ;
}
void GraphEngine : : rebuild ( const fourdst : : composition : : Composition & comp , const BuildDepthType depth ) {
if ( depth ! = m_depth ) {
m_depth = depth ;
2025-10-08 11:17:35 -04:00
m_reactions = build_nuclear_network ( comp , m_weakRateInterpolator , m_depth , false ) ;
2025-07-14 14:50:49 -04:00
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. " ) ;
}
}
2025-07-01 14:30:45 -04:00
StepDerivatives < double > GraphEngine : : calculateAllDerivativesUsingPrecomputation (
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-07-01 14:30:45 -04:00
const std : : vector < double > & bare_rates ,
2025-07-10 09:36:05 -04:00
const std : : vector < double > & bare_reverse_rates ,
2025-07-01 14:30:45 -04:00
const double T9 ,
const double rho
) const {
// --- Calculate screening factors ---
const std : : vector < double > screeningFactors = m_screeningModel - > calculateScreeningFactors (
m_reactions ,
m_networkSpecies ,
2025-10-07 15:16:03 -04:00
comp . getMolarAbundanceVector ( ) ,
2025-07-01 14:30:45 -04:00
T9 ,
rho
) ;
2025-10-07 15:16:03 -04:00
// 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.
2025-07-01 14:30:45 -04:00
// --- Optimized loop ---
std : : vector < double > molarReactionFlows ;
molarReactionFlows . reserve ( m_precomputedReactions . size ( ) ) ;
for ( const auto & precomp : m_precomputedReactions ) {
2025-07-14 14:50:49 -04:00
double forwardAbundanceProduct = 1.0 ;
2025-07-01 14:30:45 -04:00
for ( size_t i = 0 ; i < precomp . unique_reactant_indices . size ( ) ; + + i ) {
const size_t reactantIndex = precomp . unique_reactant_indices [ i ] ;
2025-10-07 15:16:03 -04:00
const fourdst : : atomic : : Species & reactant = m_networkSpecies [ reactantIndex ] ;
2025-07-01 14:30:45 -04:00
const int power = precomp . reactant_powers [ i ] ;
2025-10-07 15:16:03 -04:00
forwardAbundanceProduct * = std : : pow ( comp . getMolarAbundance ( reactant ) , power ) ;
2025-07-01 14:30:45 -04:00
}
const double bare_rate = bare_rates [ precomp . reaction_index ] ;
const double screeningFactor = screeningFactors [ precomp . reaction_index ] ;
const size_t numReactants = m_reactions [ precomp . reaction_index ] . reactants ( ) . size ( ) ;
2025-07-10 09:36:05 -04:00
const size_t numProducts = m_reactions [ precomp . reaction_index ] . products ( ) . size ( ) ;
2025-07-01 14:30:45 -04:00
2025-07-10 09:36:05 -04:00
const double forwardMolarReactionFlow =
2025-07-01 14:30:45 -04:00
screeningFactor *
bare_rate *
precomp . symmetry_factor *
2025-07-14 14:50:49 -04:00
forwardAbundanceProduct *
2025-08-14 13:33:46 -04:00
std : : pow ( rho , numReactants > 1 ? static_cast < double > ( numReactants ) - 1 : 0.0 ) ;
2025-07-10 09:36:05 -04:00
double reverseMolarReactionFlow = 0.0 ;
if ( precomp . reverse_symmetry_factor ! = 0.0 and m_useReverseReactions ) {
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 ) {
2025-10-07 15:16:03 -04:00
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 ] ) ;
2025-07-10 09:36:05 -04:00
}
reverseMolarReactionFlow = screeningFactor *
bare_reverse_rate *
precomp . reverse_symmetry_factor *
reverseAbundanceProduct *
2025-08-14 13:33:46 -04:00
std : : pow ( rho , numProducts > 1 ? static_cast < double > ( numProducts ) - 1 : 0.0 ) ;
2025-07-10 09:36:05 -04:00
}
molarReactionFlows . push_back ( forwardMolarReactionFlow - reverseMolarReactionFlow ) ;
2025-07-01 14:30:45 -04:00
}
// --- Assemble molar abundance derivatives ---
StepDerivatives < double > result ;
2025-10-07 15:16:03 -04:00
for ( const auto & species : m_networkSpecies ) {
result . dydt [ species ] = 0.0 ; // Initialize the change in abundance for each network species to 0
}
2025-07-01 14:30:45 -04:00
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 ] ;
2025-10-07 15:16:03 -04:00
const fourdst : : atomic : : Species & species = m_networkSpecies [ speciesIndex ] ;
2025-07-01 14:30:45 -04:00
const int stoichiometricCoefficient = precomp . stoichiometric_coefficients [ i ] ;
// Update the derivative for this species
2025-10-07 15:16:03 -04:00
result . dydt . at ( species ) + = static_cast < double > ( stoichiometricCoefficient ) * R_j ;
2025-07-01 14:30:45 -04:00
}
}
// --- Calculate the nuclear energy generation rate ---
double massProductionRate = 0.0 ; // [mol][s^-1]
2025-10-07 15:16:03 -04:00
for ( const auto & species : m_networkSpecies ) {
massProductionRate + = result . dydt [ species ] * species . mass ( ) * m_constants . u ;
2025-07-01 14:30:45 -04:00
}
result . nuclearEnergyGenerationRate = - massProductionRate * m_constants . Na * m_constants . c * m_constants . c ; // [erg][s^-1][g^-1]
return result ;
}
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-08-14 13:33:46 -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-08-14 13:33:46 -04:00
species . name ( ) , reaction - > id ( ) ) ;
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 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-10-07 15:16:03 -04:00
// 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);
// }
2025-06-26 15:13:46 -04:00
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-07-01 14:30:45 -04:00
void GraphEngine : : setPrecomputation ( const bool precompute ) {
m_usePrecomputation = precompute ;
}
bool GraphEngine : : isPrecomputationEnabled ( ) const {
return m_usePrecomputation ;
}
2025-07-03 09:55:10 -04:00
const partition : : PartitionFunction & GraphEngine : : getPartitionFunction ( ) const {
return * m_partitionFunction ;
}
2025-06-26 15:13:46 -04:00
double GraphEngine : : calculateMolarReactionFlow (
const reaction : : Reaction & reaction ,
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-06-26 15:13:46 -04:00
const double T9 ,
const double rho
) const {
2025-10-07 15:16:03 -04:00
const double Ye = comp . getElectronAbundance ( ) ;
2025-10-10 09:12:40 -04:00
return calculateMolarReactionFlow < double > (
reaction ,
comp . getMolarAbundanceVector ( ) ,
T9 ,
rho ,
Ye ,
0.0 ,
[ & comp ] ( const fourdst : : atomic : : Species & species ) - > std : : optional < size_t > {
if ( comp . contains ( species ) ) { // Species present in the composition
return comp . getSpeciesIndex ( species ) ;
}
return std : : nullopt ; // Species not present
}
) ;
2025-06-26 15:13:46 -04:00
}
void GraphEngine : : generateJacobianMatrix (
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-06-26 15:13:46 -04:00
const double T9 ,
const double rho
2025-07-18 15:23:43 -04:00
) const {
2025-07-22 12:48:24 -04:00
LOG_TRACE_L1_LIMIT_EVERY_N ( 1000 , 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
2025-10-07 15:16:03 -04:00
const std : : vector < double > & Y_dynamic = comp . getMolarAbundanceVector ( ) ;
2025-06-20 13:52:09 -04:00
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
2025-07-18 15:23:43 -04:00
adInput [ i ] = std : : max ( Y_dynamic [ i ] , 1e-99 ) ; // regularize the jacobian...
2025-06-20 13:52:09 -04:00
}
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 ] ;
2025-07-18 15:23:43 -04:00
if ( std : : abs ( value ) > MIN_JACOBIAN_THRESHOLD | | i = = j ) { // Always keep diagonal elements to avoid pathological stiffness
2025-06-20 13:52:09 -04:00
m_jacobianMatrix ( i , j ) = value ;
2025-10-14 13:37:48 -04:00
2025-06-20 13:52:09 -04:00
}
}
}
2025-07-22 12:48:24 -04:00
LOG_TRACE_L1_LIMIT_EVERY_N ( 1000 , 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-07-14 14:50:49 -04:00
void GraphEngine : : generateJacobianMatrix (
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-07-14 14:50:49 -04:00
const double T9 ,
const double rho ,
const SparsityPattern & sparsityPattern
2025-07-18 15:23:43 -04:00
) const {
2025-07-14 14:50:49 -04:00
// --- Pack the input variables into a vector for CppAD ---
const size_t numSpecies = m_networkSpecies . size ( ) ;
std : : vector < double > x ( numSpecies + 2 , 0.0 ) ;
2025-10-07 15:16:03 -04:00
const std : : vector < double > & Y_dynamic = comp . getMolarAbundanceVector ( ) ;
2025-07-14 14:50:49 -04:00
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
x [ i ] = Y_dynamic [ i ] ;
}
x [ numSpecies ] = T9 ;
x [ numSpecies + 1 ] = rho ;
// --- Convert into CppAD Sparsity pattern ---
const size_t nnz = sparsityPattern . size ( ) ; // Number of non-zero entries in the sparsity pattern
std : : vector < size_t > row_indices ( nnz ) ;
std : : vector < size_t > col_indices ( nnz ) ;
for ( size_t k = 0 ; k < nnz ; + + k ) {
row_indices [ k ] = sparsityPattern [ k ] . first ;
col_indices [ k ] = sparsityPattern [ k ] . second ;
}
std : : vector < double > values ( nnz ) ;
const size_t num_rows_jac = numSpecies ;
const size_t num_cols_jac = numSpecies + 2 ; // +2 for T9 and rho
CppAD : : sparse_rc < std : : vector < size_t > > CppAD_sparsity_pattern ( num_rows_jac , num_cols_jac , nnz ) ;
for ( size_t k = 0 ; k < nnz ; + + k ) {
CppAD_sparsity_pattern . set ( k , sparsityPattern [ k ] . first , sparsityPattern [ k ] . second ) ;
}
CppAD : : sparse_rcv < std : : vector < size_t > , std : : vector < double > > jac_subset ( CppAD_sparsity_pattern ) ;
m_rhsADFun . sparse_jac_rev (
x ,
jac_subset , // Sparse Jacobian output
m_full_jacobian_sparsity_pattern ,
" cppad " ,
m_jac_work // Work vector for CppAD
) ;
// --- Convert the sparse Jacobian back to the Boost uBLAS format ---
m_jacobianMatrix . clear ( ) ;
for ( size_t k = 0 ; k < nnz ; + + k ) {
const size_t row = jac_subset . row ( ) [ k ] ;
const size_t col = jac_subset . col ( ) [ k ] ;
const double value = jac_subset . val ( ) [ k ] ;
if ( std : : abs ( value ) > MIN_JACOBIAN_THRESHOLD ) {
m_jacobianMatrix ( row , col ) = value ; // Insert into the sparse matrix
}
}
}
2025-10-07 15:16:03 -04:00
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 ) ;
2025-06-26 15:13:46 -04:00
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 (
2025-10-07 15:16:03 -04:00
const fourdst : : atomic : : Species & species ,
const reaction : : Reaction & reaction
2025-06-26 15:13:46 -04:00
) const {
2025-10-07 15:16:03 -04:00
return reaction . stoichiometry ( species ) ;
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-08-14 13:33:46 -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-08-14 13:33:46 -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-08-14 13:33:46 -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-08-14 13:33:46 -04:00
csvFile < < reaction - > id ( ) < < " ; " ;
2025-06-26 15:13:46 -04:00
// Reactants
2025-07-24 10:20:44 -04:00
size_t count = 0 ;
2025-08-14 13:33:46 -04:00
for ( const auto & reactant : reaction - > reactants ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < reactant . name ( ) ;
2025-08-14 13:33:46 -04:00
if ( + + count < reaction - > reactants ( ) . size ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < " , " ;
}
}
csvFile < < " ; " ;
count = 0 ;
2025-08-14 13:33:46 -04:00
for ( const auto & product : reaction - > products ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < product . name ( ) ;
2025-08-14 13:33:46 -04:00
if ( + + count < reaction - > products ( ) . size ( ) ) {
2025-06-26 15:13:46 -04:00
csvFile < < " , " ;
}
}
2025-08-14 13:33:46 -04:00
csvFile < < " ; " < < reaction - > qValue ( ) < < " ; " ;
2025-06-26 15:13:46 -04:00
// Reaction coefficients
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-07-22 12:48:24 -04:00
std : : expected < std : : unordered_map < fourdst : : atomic : : Species , double > , expectations : : StaleEngineError > GraphEngine : : getSpeciesTimescales (
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-07-10 09:36:05 -04:00
const double T9 ,
const double rho
2025-10-22 09:54:10 -04:00
) const {
return getSpeciesTimescales ( comp , T9 , rho , m_reactions ) ;
}
std : : expected < std : : unordered_map < fourdst : : atomic : : Species , double > , expectations : : StaleEngineError > GraphEngine : : getSpeciesTimescales (
const fourdst : : composition : : Composition & comp ,
const double T9 ,
const double rho ,
const reaction : : ReactionSet & activeReactions
2025-07-10 09:36:05 -04:00
) const {
2025-10-07 15:16:03 -04:00
const double Ye = comp . getElectronAbundance ( ) ;
2025-10-10 09:12:40 -04:00
auto [ dydt , _ ] = calculateAllDerivatives < double > (
comp . getMolarAbundanceVector ( ) ,
T9 ,
rho ,
Ye ,
0.0 ,
[ & comp ] ( const fourdst : : atomic : : Species & species ) - > std : : optional < size_t > {
if ( comp . contains ( species ) ) { // Species present in the composition
return comp . getSpeciesIndex ( species ) ;
}
return std : : nullopt ; // Species not present
2025-10-22 09:54:10 -04:00
} ,
[ & activeReactions ] ( const reaction : : Reaction & reaction ) - > bool {
return activeReactions . contains ( reaction ) ;
2025-10-10 09:12:40 -04:00
}
) ;
2025-06-26 15:13:46 -04:00
std : : unordered_map < fourdst : : atomic : : Species , double > speciesTimescales ;
speciesTimescales . reserve ( m_networkSpecies . size ( ) ) ;
2025-10-07 15:16:03 -04:00
for ( const auto & species : m_networkSpecies ) {
2025-06-26 15:13:46 -04:00
double timescale = std : : numeric_limits < double > : : infinity ( ) ;
2025-10-07 15:16:03 -04:00
if ( std : : abs ( dydt . at ( species ) ) > 0.0 ) {
timescale = std : : abs ( comp . getMolarAbundance ( species ) / dydt . at ( species ) ) ;
2025-06-26 15:13:46 -04:00
}
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-22 12:48:24 -04:00
std : : expected < std : : unordered_map < fourdst : : atomic : : Species , double > , expectations : : StaleEngineError > GraphEngine : : getSpeciesDestructionTimescales (
2025-10-07 15:16:03 -04:00
const fourdst : : composition : : Composition & comp ,
2025-07-22 12:48:24 -04:00
const double T9 ,
const double rho
2025-10-22 09:54:10 -04:00
) const {
return getSpeciesDestructionTimescales ( comp , T9 , rho , m_reactions ) ;
}
std : : expected < std : : unordered_map < fourdst : : atomic : : Species , double > , expectations : : StaleEngineError > GraphEngine : : getSpeciesDestructionTimescales (
const fourdst : : composition : : Composition & comp ,
const double T9 ,
const double rho ,
const reaction : : ReactionSet & activeReactions
2025-07-22 12:48:24 -04:00
) const {
2025-10-07 15:16:03 -04:00
const double Ye = comp . getElectronAbundance ( ) ;
2025-10-10 09:12:40 -04:00
const std : : vector < double > & Y = comp . getMolarAbundanceVector ( ) ;
auto speciesLookup = [ & comp ] ( const fourdst : : atomic : : Species & species ) - > std : : optional < size_t > {
if ( comp . contains ( species ) ) { // Species present in the composition
return comp . getSpeciesIndex ( species ) ;
}
return std : : nullopt ; // Species not present
} ;
auto [ dydt , _ ] = calculateAllDerivatives < double > (
Y ,
T9 ,
rho ,
Ye ,
0.0 ,
2025-10-22 09:54:10 -04:00
speciesLookup ,
[ & activeReactions ] ( const reaction : : Reaction & reaction ) - > bool {
return activeReactions . contains ( reaction ) ;
}
2025-10-10 09:12:40 -04:00
) ;
2025-10-07 15:16:03 -04:00
2025-07-22 12:48:24 -04:00
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 ) {
2025-08-14 13:33:46 -04:00
if ( reaction - > stoichiometry ( species ) < 0 ) {
2025-10-10 09:12:40 -04:00
const auto flow = calculateMolarReactionFlow < double > (
* reaction ,
Y ,
T9 ,
rho ,
Ye ,
0.0 ,
speciesLookup
) ;
2025-07-22 12:48:24 -04:00
netDestructionFlow + = flow ;
}
}
double timescale = std : : numeric_limits < double > : : infinity ( ) ;
if ( netDestructionFlow ! = 0.0 ) {
2025-10-07 15:16:03 -04:00
timescale = comp . getMolarAbundance ( species ) / netDestructionFlow ;
2025-07-22 12:48:24 -04:00
}
speciesDestructionTimescales . emplace ( species , timescale ) ;
}
return speciesDestructionTimescales ;
}
2025-07-18 15:23:43 -04:00
fourdst : : composition : : Composition GraphEngine : : update ( const NetIn & netIn ) {
2025-07-22 12:48:24 -04:00
fourdst : : composition : : Composition baseUpdatedComposition = netIn . composition ;
for ( const auto & species : m_networkSpecies ) {
if ( ! netIn . composition . contains ( species ) ) {
baseUpdatedComposition . registerSpecies ( species ) ;
baseUpdatedComposition . setMassFraction ( species , 0.0 ) ;
}
}
2025-10-14 13:37:48 -04:00
const bool didFinalize = baseUpdatedComposition . finalize ( false ) ;
if ( ! didFinalize ) {
LOG_ERROR ( m_logger , " Failed to finalize composition during update. Check input mass fractions for validity. " ) ;
throw std : : runtime_error ( " Failed to finalize composition during network update. " ) ;
}
2025-07-22 12:48:24 -04:00
return baseUpdatedComposition ;
2025-07-18 15:23:43 -04:00
}
bool GraphEngine : : isStale ( const NetIn & netIn ) {
return false ;
2025-07-01 11:40:03 -04:00
}
2025-08-14 13:33:46 -04:00
void GraphEngine : : recordADTape ( ) const {
2025-06-26 15:13:46 -04:00
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. " ) ;
}
2025-10-10 09:12:40 -04:00
const size_t numADInputs = numSpecies + 2 ; // Y + T9 + rho
2025-06-20 13:52:09 -04:00
// --- 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
2025-07-01 14:30:45 -04:00
const auto uniformMassFraction = static_cast < CppAD : : AD < double > > ( 1.0 / static_cast < double > ( numSpecies ) ) ;
2025-06-20 13:52:09 -04:00
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 ] ;
2025-10-07 15:16:03 -04:00
// Dummy values for Ye and mue to let taping happen
2025-10-10 09:12:40 -04:00
const CppAD : : AD < double > adYe = 1e6 ;
const CppAD : : AD < double > adMue = 10.0 ;
2025-10-07 15:16:03 -04:00
2025-06-20 13:52:09 -04:00
// 5. Call the actual templated function
// We let T9 and rho be constant, so we pass them as fixed values.
2025-10-10 09:12:40 -04:00
auto [ dydt , nuclearEnergyGenerationRate ] = calculateAllDerivatives < CppAD : : AD < double > > (
adY ,
adT9 ,
adRho ,
adYe ,
adMue ,
[ & ] ( const fourdst : : atomic : : Species & querySpecies ) - > size_t {
2025-10-14 13:37:48 -04:00
return m_speciesToIndexMap . at ( querySpecies ) ;
2025-10-22 09:54:10 -04:00
} ,
[ ] ( const reaction : : Reaction & reaction ) - > bool {
return true ; // Use all reactions
2025-10-10 09:12:40 -04:00
}
) ;
2025-10-07 15:16:03 -04:00
2025-10-14 13:37:48 -04:00
2025-10-07 15:16:03 -04:00
// Extract the raw vector from the associative map
std : : vector < CppAD : : AD < double > > dydt_vec ;
dydt_vec . reserve ( dydt . size ( ) ) ;
2025-10-22 09:54:10 -04:00
// TODO: There is a possibility for a bug here if the map ordering is not consistent with the ordering of the species in m_networkSpecies.
// right now this works but that's because I am careful to build the map in the right order. This should be made less fragile
// so that if map construction order changes this still works.
2025-10-07 15:16:03 -04:00
std : : ranges : : transform ( dydt , std : : back_inserter ( dydt_vec ) , [ ] ( const auto & kv ) { return kv . second ; } ) ;
2025-06-20 13:52:09 -04:00
2025-10-07 15:16:03 -04:00
m_rhsADFun . Dependent ( adInput , dydt_vec ) ;
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
}
2025-07-01 14:30:45 -04:00
2025-09-19 15:14:46 -04:00
void GraphEngine : : recordEpsADTape ( ) const {
LOG_TRACE_L1 ( m_logger , " Recording AD tape for the EPS calculation... " ) ;
const size_t numSpecies = m_networkSpecies . size ( ) ;
if ( numSpecies = = 0 ) {
LOG_ERROR ( m_logger , " Cannot record EPS tape: No species in the network. " ) ;
throw std : : runtime_error ( " Cannot record EPS AD tape: No species in the network. " ) ;
}
const size_t numADInputs = numSpecies + 2 ; // Y + T9 + rho
std : : vector < CppAD : : AD < double > > adInput ( numADInputs , 0.0 ) ;
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
adInput [ i ] = static_cast < CppAD : : AD < double > > ( 1.0 / static_cast < double > ( numSpecies ) ) ; // Uniform distribution
}
adInput [ numSpecies ] = 1.0 ; // Dummy T9
adInput [ numSpecies + 1 ] = 1.0 ; // Dummy rho
CppAD : : Independent ( adInput ) ;
2025-10-07 15:16:03 -04:00
const std : : vector < CppAD : : AD < double > > adY ( adInput . begin ( ) , adInput . begin ( ) + static_cast < long > ( numSpecies ) ) ;
2025-09-19 15:14:46 -04:00
const CppAD : : AD < double > adT9 = adInput [ numSpecies ] ;
const CppAD : : AD < double > adRho = adInput [ numSpecies + 1 ] ;
2025-10-07 15:16:03 -04:00
// Dummy values for Ye and mue to let taping happen
2025-10-10 09:12:40 -04:00
const CppAD : : AD < double > adYe = 1e6 ;
2025-10-07 15:16:03 -04:00
const CppAD : : AD < double > adMue = 1.0 ;
2025-10-10 09:12:40 -04:00
auto [ dydt , nuclearEnergyGenerationRate ] = calculateAllDerivatives < CppAD : : AD < double > > (
adY ,
adT9 ,
adRho ,
adYe ,
adMue ,
[ & ] ( const fourdst : : atomic : : Species & querySpecies ) - > size_t {
return m_speciesToIndexMap . at ( querySpecies ) ; // TODO: This is bad, needs to be fixed
2025-10-22 09:54:10 -04:00
} ,
[ ] ( const reaction : : Reaction & reaction ) - > bool {
return true ; // Use all reactions
2025-10-10 09:12:40 -04:00
}
) ;
2025-09-19 15:14:46 -04:00
std : : vector < CppAD : : AD < double > > adOutput ( 1 ) ;
2025-10-07 15:16:03 -04:00
adOutput [ 0 ] = nuclearEnergyGenerationRate ;
2025-09-19 15:14:46 -04:00
m_epsADFun . Dependent ( adInput , adOutput ) ;
LOG_TRACE_L1 ( m_logger , " AD tape recorded successfully for the EPS calculation. Number of independent variables: {}. " , adInput . size ( ) ) ;
}
2025-07-10 09:36:05 -04:00
void GraphEngine : : collectAtomicReverseRateAtomicBases ( ) {
m_atomicReverseRates . clear ( ) ;
m_atomicReverseRates . reserve ( m_reactions . size ( ) ) ;
for ( const auto & reaction : m_reactions ) {
2025-08-14 13:33:46 -04:00
if ( reaction - > qValue ( ) ! = 0.0 ) {
m_atomicReverseRates . push_back ( std : : make_unique < AtomicReverseRate > ( * reaction , * this ) ) ;
2025-07-10 09:36:05 -04:00
} else {
m_atomicReverseRates . push_back ( nullptr ) ;
}
}
}
2025-07-01 14:30:45 -04:00
void GraphEngine : : precomputeNetwork ( ) {
LOG_TRACE_L1 ( m_logger , " Pre-computing constant components of GraphNetwork state... " ) ;
// --- Reverse map for fast species lookups ---
std : : unordered_map < fourdst : : atomic : : Species , size_t > speciesIndexMap ;
for ( size_t i = 0 ; i < m_networkSpecies . size ( ) ; + + i ) {
speciesIndexMap [ m_networkSpecies [ i ] ] = i ;
}
m_precomputedReactions . clear ( ) ;
m_precomputedReactions . reserve ( m_reactions . size ( ) ) ;
for ( size_t i = 0 ; i < m_reactions . size ( ) ; + + i ) {
const auto & reaction = m_reactions [ i ] ;
PrecomputedReaction precomp ;
precomp . reaction_index = i ;
2025-10-22 09:54:10 -04:00
precomp . reaction_type = reaction . type ( ) ;
2025-07-01 14:30:45 -04:00
2025-07-10 09:36:05 -04:00
// --- Precompute forward reaction information ---
2025-07-01 14:30:45 -04:00
// Count occurrences for each reactant to determine powers and symmetry
std : : unordered_map < size_t , int > reactantCounts ;
for ( const auto & reactant : reaction . reactants ( ) ) {
size_t reactantIndex = speciesIndexMap . at ( reactant ) ;
reactantCounts [ reactantIndex ] + + ;
}
double symmetryDenominator = 1.0 ;
for ( const auto & [ index , count ] : reactantCounts ) {
precomp . unique_reactant_indices . push_back ( index ) ;
precomp . reactant_powers . push_back ( count ) ;
2025-07-10 09:36:05 -04:00
symmetryDenominator * = std : : tgamma ( count + 1 ) ;
2025-07-01 14:30:45 -04:00
}
2025-07-10 09:36:05 -04:00
precomp . symmetry_factor = 1.0 / symmetryDenominator ;
// --- Precompute reverse reaction information ---
2025-10-22 09:54:10 -04:00
if ( reaction . qValue ( ) ! = 0.0 & & reaction . type ( ) ! = reaction : : ReactionType : : WEAK ) {
2025-07-10 09:36:05 -04:00
std : : unordered_map < size_t , int > productCounts ;
for ( const auto & product : reaction . products ( ) ) {
productCounts [ speciesIndexMap . at ( product ) ] + + ;
}
double reverseSymmetryDenominator = 1.0 ;
for ( const auto & [ index , count ] : productCounts ) {
precomp . unique_product_indices . push_back ( index ) ;
precomp . product_powers . push_back ( count ) ;
reverseSymmetryDenominator * = std : : tgamma ( count + 1 ) ;
}
precomp . reverse_symmetry_factor = 1.0 / reverseSymmetryDenominator ;
} else {
precomp . unique_product_indices . clear ( ) ;
precomp . product_powers . clear ( ) ;
2025-10-22 09:54:10 -04:00
precomp . reverse_symmetry_factor = 0.0 ; // No reverse reaction for weak reactions
2025-07-10 09:36:05 -04:00
}
2025-07-01 14:30:45 -04:00
// --- Precompute stoichiometry information ---
const auto stoichiometryMap = reaction . stoichiometry ( ) ;
precomp . affected_species_indices . reserve ( stoichiometryMap . size ( ) ) ;
precomp . stoichiometric_coefficients . reserve ( stoichiometryMap . size ( ) ) ;
for ( const auto & [ species , coeff ] : stoichiometryMap ) {
precomp . affected_species_indices . push_back ( speciesIndexMap . at ( species ) ) ;
precomp . stoichiometric_coefficients . push_back ( coeff ) ;
}
m_precomputedReactions . push_back ( std : : move ( precomp ) ) ;
}
}
2025-07-03 09:55:10 -04:00
2025-07-10 09:36:05 -04:00
bool GraphEngine : : AtomicReverseRate : : forward (
2025-07-03 09:55:10 -04:00
const size_t p ,
const size_t q ,
const CppAD : : vector < bool > & vx ,
CppAD : : vector < bool > & vy ,
const CppAD : : vector < double > & tx ,
CppAD : : vector < double > & ty
) {
2025-07-10 09:36:05 -04:00
if ( p ! = 0 ) { return false ; }
2025-07-03 09:55:10 -04:00
const double T9 = tx [ 0 ] ;
2025-07-10 09:36:05 -04:00
2025-10-22 15:08:49 -04:00
// We can pass a dummy comp and rho because reverse rates should only be calculated for strong reactions whose
// rates of progression do not depend on composition or density.
2025-10-10 09:12:40 -04:00
const double reverseRate = m_engine . calculateReverseRate ( m_reaction , T9 , 0.0 , { } ) ;
2025-07-10 09:36:05 -04:00
ty [ 0 ] = reverseRate ; // Store the reverse rate in the output vector
if ( vx . size ( ) > 0 ) {
vy [ 0 ] = vx [ 0 ] ;
2025-07-03 09:55:10 -04:00
}
2025-07-10 09:36:05 -04:00
return true ;
2025-07-03 09:55:10 -04:00
}
2025-07-10 09:36:05 -04:00
bool GraphEngine : : AtomicReverseRate : : reverse (
size_t q ,
const CppAD : : vector < double > & tx ,
const CppAD : : vector < double > & ty ,
CppAD : : vector < double > & px ,
const CppAD : : vector < double > & py
) {
const double T9 = tx [ 0 ] ;
const double reverseRate = ty [ 0 ] ;
2025-08-14 13:33:46 -04:00
const double derivative = m_engine . calculateReverseRateTwoBodyDerivative ( m_reaction , T9 , 0 , { } , reverseRate ) ;
2025-07-10 09:36:05 -04:00
px [ 0 ] = py [ 0 ] * derivative ; // Return the derivative of the reverse rate with respect to T9
return true ;
}
bool GraphEngine : : AtomicReverseRate : : for_sparse_jac (
size_t q ,
const CppAD : : vector < std : : set < size_t > > & r ,
CppAD : : vector < std : : set < size_t > > & s
) {
s [ 0 ] = r [ 0 ] ;
return true ;
}
bool GraphEngine : : AtomicReverseRate : : rev_sparse_jac (
size_t q ,
const CppAD : : vector < std : : set < size_t > > & rt ,
CppAD : : vector < std : : set < size_t > > & st
) {
st [ 0 ] = rt [ 0 ] ;
return true ;
}
2025-06-19 09:42:20 -04:00
}