2#include "atomicSpecies.h"
8#include "quill/LogMacros.h"
16#include <unordered_map>
19#include <boost/numeric/ublas/vector.hpp>
20#include <boost/numeric/odeint.hpp>
34 const double cullingThreshold,
55 std::set<std::string_view> uniqueSpeciesNames;
58 for (
const auto& reactant: reaction.reactants()) {
59 uniqueSpeciesNames.insert(reactant.name());
61 for (
const auto& product: reaction.products()) {
62 uniqueSpeciesNames.insert(product.name());
66 for (
const auto& name: uniqueSpeciesNames) {
67 auto it = serif::atomic::species.find(name);
68 if (it != serif::atomic::species.end()) {
72 LOG_ERROR(
m_logger,
"Species '{}' not found in global atomic species database.", name);
73 throw std::runtime_error(
"Species not found in global atomic species database: " + std::string(name));
80 LOG_INFO(
m_logger,
"Populating reaction ID map for REACLIB graph network (serif::network::GraphNetwork)...");
101 LOG_INFO(
m_logger,
"Jacobian matrix resized to {} rows and {} columns.",
114 LOG_DEBUG(
m_logger,
"Providing access to network reactions set. Size: {}.",
m_reactions.size());
121 LOG_DEBUG(
m_logger,
"Checking if species '{}' is involved in the network: {}.", species.name(), found ?
"Yes" :
"No");
127 std::unordered_map<serif::atomic::Species, int> stoichiometry;
130 for (
const auto& reactant : reaction.reactants()) {
133 stoichiometry[it->second]--;
135 LOG_WARNING(
m_logger,
"Reactant species '{}' in reaction '{}' not found in network species map during stoichiometry calculation.",
136 reactant.name(), reaction.id());
141 for (
const auto& product : reaction.products()) {
144 stoichiometry[it->second]++;
146 LOG_WARNING(
m_logger,
"Product species '{}' in reaction '{}' not found in network species map during stoichiometry calculation.",
147 product.name(), reaction.id());
150 LOG_DEBUG(
m_logger,
"Calculated net stoichiometry for reaction '{}'. Total unique species in stoichiometry: {}.", reaction.id(), stoichiometry.size());
151 return stoichiometry;
156 LOG_INFO(
m_logger,
"Validating mass (A) and charge (Z) conservation across all reactions in the network.");
159 uint64_t totalReactantA = 0;
160 uint64_t totalReactantZ = 0;
161 uint64_t totalProductA = 0;
162 uint64_t totalProductZ = 0;
165 for (
const auto& reactant : reaction.reactants()) {
168 totalReactantA += it->second.a();
169 totalReactantZ += it->second.z();
173 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Reactant species '{}' in reaction '{}' not found in network species map during conservation validation.",
174 reactant.name(), reaction.id());
180 for (
const auto& product : reaction.products()) {
183 totalProductA += it->second.a();
184 totalProductZ += it->second.z();
187 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Product species '{}' in reaction '{}' not found in network species map during conservation validation.",
188 product.name(), reaction.id());
194 if (totalReactantA != totalProductA) {
195 LOG_ERROR(
m_logger,
"Mass number (A) not conserved for reaction '{}': Reactants A={} vs Products A={}.",
196 reaction.id(), totalReactantA, totalProductA);
199 if (totalReactantZ != totalProductZ) {
200 LOG_ERROR(
m_logger,
"Atomic number (Z) not conserved for reaction '{}': Reactants Z={} vs Products Z={}.",
201 reaction.id(), totalReactantZ, totalProductZ);
206 LOG_INFO(
m_logger,
"Mass (A) and charge (Z) conservation validated successfully for all reactions.");
225 LOG_INFO(
m_logger,
"Reaction set not cached. Rebuilding the reaction set for T9={} and culling={}.", T9, culling);
233 LOG_INFO(
m_logger,
"Generating stoichiometry matrix...");
240 LOG_INFO(
m_logger,
"Stoichiometry matrix initialized with dimensions: {} rows (species) x {} columns (reactions).",
241 numSpecies, numReactions);
245 size_t reactionColumnIndex = 0;
251 for (
const auto& pair : netStoichiometry) {
252 const serif::atomic::Species& species = pair.first;
253 const int coefficient = pair.second;
258 const size_t speciesRowIndex = it->second;
263 LOG_ERROR(
m_logger,
"CRITICAL ERROR: Species '{}' from reaction '{}' stoichiometry not found in species to index map.",
264 species.name(), reaction.id());
265 throw std::runtime_error(
"Species not found in species to index map: " + std::string(species.name()));
268 reactionColumnIndex++;
271 LOG_INFO(
m_logger,
"Stoichiometry matrix population complete. Number of non-zero elements: {}.",
278 LOG_INFO(
m_logger,
"Generating jacobian matrix for T9={}, rho={}..", T9, rho);
282 std::vector<double> adInput(numSpecies + 2, 0.0);
283 for (
size_t i = 0; i < numSpecies; ++i) {
286 adInput[numSpecies] = T9;
287 adInput[numSpecies + 1] = rho;
290 const std::vector<double> dotY =
m_rhsADFun.Jacobian(adInput);
294 for (
size_t i = 0; i < numSpecies; ++i) {
295 for (
size_t j = 0; j < numSpecies; ++j) {
296 const double value = dotY[i * (numSpecies + 2) + j];
307 const std::vector<double> initial_y_stl(Y.begin(), Y.begin() + numSpecies);
309 const std::vector<double>& initial_dotY = derivatives.dydt;
311 double min_timescale = std::numeric_limits<double>::max();
312 double max_timescale = 0.0;
314 for (
size_t i = 0; i < numSpecies; ++i) {
315 if (std::abs(initial_dotY[i]) > 0) {
316 const double timescale = std::abs(Y(i) / initial_dotY[i]);
317 if (timescale > max_timescale) {max_timescale = timescale;}
318 if (timescale < min_timescale) {min_timescale = timescale;}
322 const double stiffnessRatio = max_timescale / min_timescale;
325 constexpr double stiffnessThreshold = 1.0e6;
327 LOG_INFO(
m_logger,
"Stiffness ratio is {} (max timescale: {}, min timescale: {}).", stiffnessRatio, max_timescale, min_timescale);
328 if (stiffnessRatio > stiffnessThreshold) {
329 LOG_INFO(
m_logger,
"Network is detected to be stiff. Using stiff ODE solver.");
332 LOG_INFO(
m_logger,
"Network is detected to be non-stiff. Using non-stiff ODE solver.");
338 namespace ublas = boost::numeric::ublas;
339 namespace odeint = boost::numeric::odeint;
341 const double T9 = netIn.temperature / 1e9;
345 constexpr double abs_tol = 1.0e-8;
346 constexpr double rel_tol = 1.0e-8;
352 ODETerm rhs_functor(*
this, T9, netIn.density);
355 ublas::vector<double> Y(numSpecies + 1);
356 for (
size_t i = 0; i < numSpecies; ++i) {
359 Y(i) = netIn.composition.getMassFraction(std::string(species.name()));
366 JacobianTerm jacobian_functor(*
this, T9, netIn.density);
367 LOG_INFO(
m_logger,
"Making use of stiff ODE solver for network evaluation.");
368 auto stepper = odeint::make_controlled<odeint::rosenbrock4<double>>(abs_tol, rel_tol);
369 stepCount = odeint::integrate_adaptive(
371 std::make_pair(rhs_functor, jacobian_functor),
379 LOG_INFO(
m_logger,
"Making use of ODE solver (non-stiff) for network evaluation.");
380 using state_type = ublas::vector<double>;
381 auto stepper = odeint::make_controlled<odeint::runge_kutta_dopri5<state_type>>(abs_tol, rel_tol);
382 stepCount = odeint::integrate_adaptive(
395 for (
int i = 0; i < numSpecies; ++i) { sumY += Y(i); }
396 for (
int i = 0; i < numSpecies; ++i) { Y(i) /= sumY; }
400 std::vector<std::string> speciesNames;
401 speciesNames.reserve(numSpecies);
403 speciesNames.push_back(std::string(species.name()));
406 std::vector<double> finalAbundances(Y.begin(), Y.begin() + numSpecies);
411 netOut.composition = outputComposition;
412 netOut.num_steps = stepCount;
413 netOut.energy = Y(numSpecies);
420 LOG_INFO(
m_logger,
"Recording AD tape for the RHS calculation...");
424 if (numSpecies == 0) {
425 LOG_ERROR(
m_logger,
"Cannot record AD tape: No species in the network.");
426 throw std::runtime_error(
"Cannot record AD tape: No species in the network.");
428 const size_t numADInputs = numSpecies + 2;
436 const auto uniformMassFraction =
static_cast<CppAD::AD<double>
>(1.0 / numSpecies);
437 std::vector<CppAD::AD<double>> adInput(numADInputs, uniformMassFraction);
438 adInput[numSpecies] = 1.0;
439 adInput[numSpecies + 1] = 1.0;
443 CppAD::Independent(adInput);
445 std::vector<CppAD::AD<double>> adY(numSpecies);
446 for(
size_t i = 0; i < numSpecies; ++i) {
449 const CppAD::AD<double> adT9 = adInput[numSpecies];
450 const CppAD::AD<double> adRho = adInput[numSpecies + 1];
457 m_rhsADFun.Dependent(adInput, derivatives.dydt);
459 LOG_INFO(
m_logger,
"AD tape recorded successfully for the RHS calculation. Number of independent variables: {}.",
Manages the composition of elements.
bool finalize(bool norm=false)
Finalizes the composition.
boost::numeric::ublas::compressed_matrix< double > m_jacobianMatrix
Jacobian matrix (species x species).
const std::vector< serif::atomic::Species > & getNetworkSpecies() const
Get the vector of unique species in the network.
std::unordered_map< std::string_view, serif::atomic::Species > m_networkSpeciesMap
Map from species name to Species object.
std::unordered_map< std::string_view, const reaclib::REACLIBReaction > m_reactionIDMap
Map from reaction ID to REACLIBReaction. //PERF: This makes copies of REACLIBReaction and could be a ...
CppAD::ADFun< double > m_rhsADFun
CppAD function for the right-hand side of the ODE.
reaclib::REACLIBReactionSet m_reactions
Set of REACLIB reactions in the network.
std::unordered_map< serif::atomic::Species, int > getNetReactionStoichiometry(const reaclib::REACLIBReaction &reaction) const
Get the net stoichiometric coefficients for all species in a reaction.
void populateReactionIDMap()
Populate the reaction ID map from the reaction set.
void detectStiff(const NetIn &netIn, double T9, double numSpecies, const boost::numeric::ublas::vector< double > &Y)
Detect if the network is stiff and select the appropriate ODE solver.
GraphNetwork(const serif::composition::Composition &composition)
Construct a GraphNetwork from a composition.
bool validateConservation() const
Validate mass and charge conservation for all reactions.
void populateSpeciesToIndexMap()
Populate the species-to-index map for matrix construction.
void reserveJacobianMatrix()
Reserve and resize the Jacobian matrix.
void validateComposition(const serif::composition::Composition &composition, double culling, double T9)
Validate and update the network composition if needed.
void collectNetworkSpecies()
Collect all unique species from the reaction set.
std::unordered_map< serif::atomic::Species, size_t > m_speciesToIndexMap
Map from species to their index in the stoichiometry matrix.
void generateStoichiometryMatrix()
Generate the stoichiometry matrix for the network.
void syncInternalMaps()
Synchronize all internal maps and matrices after network changes.
bool involvesSpecies(const serif::atomic::Species &species) const
Check if a species is present in the network.
void generateJacobianMatrix(const std::vector< double > &Y, const double T9, const double rho)
Generate the Jacobian matrix for the network.
const reaclib::REACLIBReactionSet & getNetworkReactions() const
Get the set of REACLIB reactions in the network.
boost::numeric::ublas::compressed_matrix< int > m_stoichiometryMatrix
Stoichiometry matrix (species x reactions).
NetOut evaluate(const NetIn &netIn) override
Evolve the network for the given input conditions.
std::vector< serif::atomic::Species > m_networkSpecies
Vector of unique species in the network.
void recordADTape()
Record the CppAD tape for automatic differentiation.
StepDerivatives< T > calculateAllDerivatives(const std::vector< T > &Y, T T9, T rho) const
Calculate all derivatives (dY/dt and energy rate) for the current state.
bool m_stiff
Flag indicating if the network is stiff.
Network(const NetworkFormat format=NetworkFormat::APPROX8)
quill::Logger * m_logger
Logger instance.
static constexpr double MIN_JACOBIAN_THRESHOLD
Minimum Jacobian entry threshold for sparsity.
@ REACLIB
General REACLIB nuclear reaction network format.
serif::network::reaclib::REACLIBReactionSet build_reaclib_nuclear_network(const serif::composition::Composition &composition)
Defines the GraphNetwork class for representing and evolving nuclear reaction networks as a heavily c...
Functor for Jacobian evaluation for stiff ODE solvers. (used in the NSE case)
Functor for ODE right-hand side evaluation.
Input structure for the network evaluation.
Output structure for the network evaluation.