GridFire 0.0.1a
General Purpose Nuclear Network
Loading...
Searching...
No Matches
engine_graph.cpp
Go to the documentation of this file.
3#include "gridfire/network.h"
8
9#include "fourdst/composition/species.h"
10#include "fourdst/composition/atomicSpecies.h"
11
12#include "quill/LogMacros.h"
13
14#include <cstdint>
15#include <iostream>
16#include <set>
17#include <stdexcept>
18#include <string>
19#include <string_view>
20#include <unordered_map>
21#include <utility>
22#include <vector>
23#include <fstream>
24#include <ranges>
25
26#include <boost/numeric/odeint.hpp>
27
28#include "cppad/cppad.hpp"
29#include "cppad/utility/sparse_rc.hpp"
30#include "cppad/utility/sparse_rcv.hpp"
31
32
33namespace gridfire {
35 const fourdst::composition::Composition &composition,
36 const BuildDepthType buildDepth
37 ): GraphEngine(composition, partition::GroundStatePartitionFunction(), buildDepth) {}
38
40 const fourdst::composition::Composition &composition,
41 const partition::PartitionFunction& partitionFunction,
42 const BuildDepthType buildDepth) :
43 m_reactions(build_reaclib_nuclear_network(composition, buildDepth, false)),
44 m_depth(buildDepth),
45 m_partitionFunction(partitionFunction.clone())
46 {
48 }
49
51 const reaction::LogicalReactionSet &reactions
52 ) :
53 m_reactions(reactions) {
55 }
56
58 const std::vector<double> &Y,
59 const double T9,
60 const double rho
61 ) const {
63 std::vector<double> bare_rates;
64 std::vector<double> bare_reverse_rates;
65 bare_rates.reserve(m_reactions.size());
66 bare_reverse_rates.reserve(m_reactions.size());
67
68 // TODO: Add cache to this
69 for (const auto& reaction: m_reactions) {
70 bare_rates.push_back(reaction.calculate_rate(T9));
71 bare_reverse_rates.push_back(calculateReverseRate(reaction, T9));
72 }
73
74 // --- The public facing interface can always use the precomputed version since taping is done internally ---
75 return calculateAllDerivativesUsingPrecomputation(Y, bare_rates, bare_reverse_rates, T9, rho);
76 } else {
77 return calculateAllDerivatives<double>(Y, T9, rho);
78 }
79 }
80
82 LOG_INFO(m_logger, "Synchronizing internal maps for REACLIB graph network (serif::network::GraphNetwork)...");
90
91 const size_t n = m_rhsADFun.Domain();
92 const size_t m = m_rhsADFun.Range();
93
94 const std::vector<bool> select_domain(n, true);
95 const std::vector<bool> select_range(m, true);
96
97 m_rhsADFun.subgraph_sparsity(select_domain, select_range, false, m_full_jacobian_sparsity_pattern);
98 m_jac_work.clear();
99
101 LOG_INFO(m_logger, "Internal maps synchronized. Network contains {} species and {} reactions.",
102 m_networkSpecies.size(), m_reactions.size());
103 }
104
105 // --- Network Graph Construction Methods ---
107 m_networkSpecies.clear();
108 m_networkSpeciesMap.clear();
109
110 std::set<std::string_view> uniqueSpeciesNames;
111
112 for (const auto& reaction: m_reactions) {
113 for (const auto& reactant: reaction.reactants()) {
114 uniqueSpeciesNames.insert(reactant.name());
115 }
116 for (const auto& product: reaction.products()) {
117 uniqueSpeciesNames.insert(product.name());
118 }
119 }
120
121 for (const auto& name: uniqueSpeciesNames) {
122 auto it = fourdst::atomic::species.find(std::string(name));
123 if (it != fourdst::atomic::species.end()) {
124 m_networkSpecies.push_back(it->second);
125 m_networkSpeciesMap.insert({name, it->second});
126 } else {
127 LOG_ERROR(m_logger, "Species '{}' not found in global atomic species database.", name);
128 m_logger->flush_log();
129 throw std::runtime_error("Species not found in global atomic species database: " + std::string(name));
130 }
131 }
132
133 }
134
136 LOG_TRACE_L1(m_logger, "Populating reaction ID map for REACLIB graph network (serif::network::GraphNetwork)...");
137 m_reactionIDMap.clear();
138 for (auto& reaction: m_reactions) {
139 m_reactionIDMap.emplace(reaction.id(), &reaction);
140 }
141 LOG_TRACE_L1(m_logger, "Populated {} reactions in the reaction ID map.", m_reactionIDMap.size());
142 }
143
145 m_speciesToIndexMap.clear();
146 for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
148 }
149 }
150
152 // The implementation of this function (and others) constrains this nuclear network to a constant temperature and density during
153 // each evaluation.
154 const size_t numSpecies = m_networkSpecies.size();
155 m_jacobianMatrix.clear();
156 m_jacobianMatrix.resize(numSpecies, numSpecies, false); // Sparse matrix, no initial values
157 LOG_TRACE_L2(m_logger, "Jacobian matrix resized to {} rows and {} columns.",
158 m_jacobianMatrix.size1(), m_jacobianMatrix.size2());
159 }
160
161 // --- Basic Accessors and Queries ---
162 const std::vector<fourdst::atomic::Species>& GraphEngine::getNetworkSpecies() const {
163 // Returns a constant reference to the vector of unique species in the network.
164 LOG_TRACE_L3(m_logger, "Providing access to network species vector. Size: {}.", m_networkSpecies.size());
165 return m_networkSpecies;
166 }
167
169 // Returns a constant reference to the set of reactions in the network.
170 LOG_TRACE_L3(m_logger, "Providing access to network reactions set. Size: {}.", m_reactions.size());
171 return m_reactions;
172 }
173
178
179 bool GraphEngine::involvesSpecies(const fourdst::atomic::Species& species) const {
180 // Checks if a given species is present in the network's species map for efficient lookup.
181 const bool found = m_networkSpeciesMap.contains(species.name());
182 LOG_DEBUG(m_logger, "Checking if species '{}' is involved in the network: {}.", species.name(), found ? "Yes" : "No");
183 return found;
184 }
185
186 // --- Validation Methods ---
188 LOG_TRACE_L1(m_logger, "Validating mass (A) and charge (Z) conservation across all reactions in the network.");
189
190 for (const auto& reaction : m_reactions) {
191 uint64_t totalReactantA = 0;
192 uint64_t totalReactantZ = 0;
193 uint64_t totalProductA = 0;
194 uint64_t totalProductZ = 0;
195
196 // Calculate total A and Z for reactants
197 for (const auto& reactant : reaction.reactants()) {
198 auto it = m_networkSpeciesMap.find(reactant.name());
199 if (it != m_networkSpeciesMap.end()) {
200 totalReactantA += it->second.a();
201 totalReactantZ += it->second.z();
202 } else {
203 // This scenario indicates a severe data integrity issue:
204 // a reactant is part of a reaction but not in the network's species map.
205 LOG_ERROR(m_logger, "CRITICAL ERROR: Reactant species '{}' in reaction '{}' not found in network species map during conservation validation.",
206 reactant.name(), reaction.id());
207 return false;
208 }
209 }
210
211 // Calculate total A and Z for products
212 for (const auto& product : reaction.products()) {
213 auto it = m_networkSpeciesMap.find(product.name());
214 if (it != m_networkSpeciesMap.end()) {
215 totalProductA += it->second.a();
216 totalProductZ += it->second.z();
217 } else {
218 // Similar critical error for product species
219 LOG_ERROR(m_logger, "CRITICAL ERROR: Product species '{}' in reaction '{}' not found in network species map during conservation validation.",
220 product.name(), reaction.id());
221 return false;
222 }
223 }
224
225 // Compare totals for conservation
226 if (totalReactantA != totalProductA) {
227 LOG_ERROR(m_logger, "Mass number (A) not conserved for reaction '{}': Reactants A={} vs Products A={}.",
228 reaction.id(), totalReactantA, totalProductA);
229 return false;
230 }
231 if (totalReactantZ != totalProductZ) {
232 LOG_ERROR(m_logger, "Atomic number (Z) not conserved for reaction '{}': Reactants Z={} vs Products Z={}.",
233 reaction.id(), totalReactantZ, totalProductZ);
234 return false;
235 }
236 }
237
238 LOG_TRACE_L1(m_logger, "Mass (A) and charge (Z) conservation validated successfully for all reactions.");
239 return true; // All reactions passed the conservation check
240 }
241
242 void GraphEngine::validateComposition(const fourdst::composition::Composition &composition, double culling, double T9) {
243 // Check if the requested network has already been cached.
244 // PERF: Rebuilding this should be pretty fast but it may be a good point of optimization in the future.
245 const reaction::LogicalReactionSet validationReactionSet = build_reaclib_nuclear_network(composition, false);
246 // TODO: need some more robust method here to
247 // A. Build the basic network from the composition's species with non zero mass fractions.
248 // 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)
249 // C. Rebuild the reaction set from the new composition
250 // D. Cull reactions where all reactants have mass fractions below the culling threshold.
251 // E. Be careful about maintaining caching through all of this
252
253
254 // This allows for dynamic network modification while retaining caching for networks which are very similar.
255 if (validationReactionSet != m_reactions) {
256 LOG_DEBUG(m_logger, "Reaction set not cached. Rebuilding the reaction set for T9={} and culling={}.", T9, culling);
257 m_reactions = validationReactionSet;
258 syncInternalMaps(); // Re-sync internal maps after updating reactions. Note this will also retrace the AD tape.
259 }
260 }
261
264 const double T9
265 ) const {
267 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());
268 return 0.0; // If reverse reactions are not used, return 0.0
269 }
270 const double expFactor = std::exp(-reaction.qValue() / (m_constants.kB * T9));
271 if (s_debug) {
272 std::cout << "\texpFactor = exp(-qValue/(kB * T9))\n";
273 std::cout << "\texpFactor: " << expFactor << " for reaction: " << reaction.peName() << std::endl;
274 std::cout << "\tQ-value: " << reaction.qValue() << " at T9: " << T9 << std::endl;
275 std::cout << "\tT9: " << T9 << std::endl;
276 std::cout << "\tkB * T9: " << m_constants.kB * T9 << std::endl;
277 std::cout << "\tqValue/(kB * T9): " << reaction.qValue() / (m_constants.kB * T9) << std::endl;
278 }
279 double reverseRate = 0.0;
280 const double forwardRate = reaction.calculate_rate(T9);
281
282 if (reaction.reactants().size() == 2 && reaction.products().size() == 2) {
283 reverseRate = calculateReverseRateTwoBody(reaction, T9, forwardRate, expFactor);
284 } else {
285 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.peName());
286 }
287 LOG_TRACE_L2_LIMIT_EVERY_N(1000, m_logger, "Calculated reverse rate for reaction '{}': {:.3E} at T9={:.3E}.", reaction.id(), reverseRate, T9);
288 return reverseRate;
289 }
290
293 const double T9,
294 const double forwardRate,
295 const double expFactor
296 ) const {
297 std::vector<double> reactantPartitionFunctions;
298 std::vector<double> productPartitionFunctions;
299
300 reactantPartitionFunctions.reserve(reaction.reactants().size());
301 productPartitionFunctions.reserve(reaction.products().size());
302
303 std::unordered_map<fourdst::atomic::Species, int> reactantMultiplicity;
304 std::unordered_map<fourdst::atomic::Species, int> productMultiplicity;
305
306 reactantMultiplicity.reserve(reaction.reactants().size());
307 productMultiplicity.reserve(reaction.products().size());
308
309 for (const auto& reactant : reaction.reactants()) {
310 reactantMultiplicity[reactant] += 1;
311 }
312 for (const auto& product : reaction.products()) {
313 productMultiplicity[product] += 1;
314 }
315 double reactantSymmetryFactor = 1.0;
316 double productSymmetryFactor = 1.0;
317 for (const auto& count : reactantMultiplicity | std::views::values) {
318 reactantSymmetryFactor *= std::tgamma(count + 1);
319 }
320 for (const auto& count : productMultiplicity | std::views::values) {
321 productSymmetryFactor *= std::tgamma(count + 1);
322 }
323 const double symmetryFactor = reactantSymmetryFactor / productSymmetryFactor;
324
325 // Accumulate mass terms
326 auto mass_op = [](double acc, const auto& species) { return acc * species.a(); };
327 const double massNumerator = std::accumulate(
328 reaction.reactants().begin(),
329 reaction.reactants().end(),
330 1.0,
331 mass_op
332 );
333 const double massDenominator = std::accumulate(
334 reaction.products().begin(),
335 reaction.products().end(),
336 1.0,
337 mass_op
338 );
339
340 // Accumulate partition functions
341 auto pf_op = [&](double acc, const auto& species) {
342 return acc * m_partitionFunction->evaluate(species.z(), species.a(), T9);
343 };
344 const double partitionFunctionNumerator = std::accumulate(
345 reaction.reactants().begin(),
346 reaction.reactants().end(),
347 1.0,
348 pf_op
349 );
350 const double partitionFunctionDenominator = std::accumulate(
351 reaction.products().begin(),
352 reaction.products().end(),
353 1.0,
354 pf_op
355 );
356
357 const double CT = std::pow(massNumerator/massDenominator, 1.5) *
358 (partitionFunctionNumerator/partitionFunctionDenominator);
359
360 const double reverseRate = forwardRate * symmetryFactor * CT * expFactor;
361 if (!std::isfinite(reverseRate)) {
362 return 0.0; // If the reverse rate is not finite, return 0.0
363 }
364 return reverseRate; // Return the calculated reverse rate
365
366 }
367
370 const double T9,
371 const double reverseRate
372 ) const {
374 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());
375 return 0.0; // If reverse reactions are not used, return 0.0
376 }
377 const double d_log_kFwd = reaction.calculate_forward_rate_log_derivative(T9);
378
379 auto log_deriv_pf_op = [&](double acc, const auto& species) {
380 const double g = m_partitionFunction->evaluate(species.z(), species.a(), T9);
381 const double dg_dT = m_partitionFunction->evaluateDerivative(species.z(), species.a(), T9);
382 return (g == 0.0) ? acc : acc + (dg_dT / g);
383 };
384
385 const double reactant_log_derivative_sum = std::accumulate(
386 reaction.reactants().begin(),
387 reaction.reactants().end(),
388 0.0,
389 log_deriv_pf_op
390 );
391
392 const double product_log_derivative_sum = std::accumulate(
393 reaction.products().begin(),
394 reaction.products().end(),
395 0.0,
396 log_deriv_pf_op
397 );
398
399 const double d_log_C = reactant_log_derivative_sum - product_log_derivative_sum;
400
401 const double d_log_exp = reaction.qValue() / (m_constants.kB * T9 * T9);
402
403 const double log_total_derivative = d_log_kFwd + d_log_C + d_log_exp;
404
405 return reverseRate * log_total_derivative; // Return the derivative of the reverse rate with respect to T9
406
407 }
408
412
413 void GraphEngine::setUseReverseReactions(const bool useReverse) {
414 m_useReverseReactions = useReverse;
415 }
416
417 int GraphEngine::getSpeciesIndex(const fourdst::atomic::Species &species) const {
418 return m_speciesToIndexMap.at(species); // Returns the index of the species in the stoichiometry matrix
419 }
420
421 std::vector<double> GraphEngine::mapNetInToMolarAbundanceVector(const NetIn &netIn) const {
422 std::vector<double> Y(m_networkSpecies.size(), 0.0); // Initialize with zeros
423 for (const auto& [symbol, entry] : netIn.composition) {
424 Y[getSpeciesIndex(entry.isotope())] = netIn.composition.getMolarAbundance(symbol); // Map species to their molar abundance
425 }
426 return Y; // Return the vector of molar abundances
427 }
428
430 NetIn fullNetIn;
431 fourdst::composition::Composition composition;
432
433 std::vector<std::string> symbols;
434 symbols.reserve(m_networkSpecies.size());
435 for (const auto &symbol: m_networkSpecies) {
436 symbols.emplace_back(symbol.name());
437 }
438 composition.registerSymbol(symbols);
439 for (const auto& [symbol, entry] : netIn.composition) {
440 if (m_networkSpeciesMap.contains(symbol)) {
441 composition.setMassFraction(symbol, entry.mass_fraction());
442 } else {
443 composition.setMassFraction(symbol, 0.0);
444 }
445 }
446 composition.finalize(true);
447 fullNetIn.composition = composition;
448 fullNetIn.temperature = netIn.temperature;
449 fullNetIn.density = netIn.density;
450
451 auto primingReport = primeNetwork(fullNetIn, *this);
452
453 return primingReport;
454 }
455
457 return m_depth;
458 }
459
460 void GraphEngine::rebuild(const fourdst::composition::Composition& comp, const BuildDepthType depth) {
461 if (depth != m_depth) {
462 m_depth = depth;
464 syncInternalMaps(); // Resync internal maps after changing the depth
465 } else {
466 LOG_DEBUG(m_logger, "Rebuild requested with the same depth. No changes made to the network.");
467 }
468 }
469
471 const std::vector<double> &Y_in,
472 const std::vector<double> &bare_rates,
473 const std::vector<double> &bare_reverse_rates,
474 const double T9,
475 const double rho
476 ) const {
477 // --- Calculate screening factors ---
478 const std::vector<double> screeningFactors = m_screeningModel->calculateScreeningFactors(
481 Y_in,
482 T9,
483 rho
484 );
485
486 // --- Optimized loop ---
487 std::vector<double> molarReactionFlows;
488 molarReactionFlows.reserve(m_precomputedReactions.size());
489
490 for (const auto& precomp : m_precomputedReactions) {
491 double forwardAbundanceProduct = 1.0;
492 // bool below_threshold = false;
493 for (size_t i = 0; i < precomp.unique_reactant_indices.size(); ++i) {
494 const size_t reactantIndex = precomp.unique_reactant_indices[i];
495 const int power = precomp.reactant_powers[i];
496 // const double abundance = Y_in[reactantIndex];
497 // if (abundance < MIN_ABUNDANCE_THRESHOLD) {
498 // below_threshold = true;
499 // break;
500 // }
501
502 forwardAbundanceProduct *= std::pow(Y_in[reactantIndex], power);
503 }
504 // if (below_threshold) {
505 // molarReactionFlows.push_back(0.0);
506 // continue; // Skip this reaction if any reactant is below the abundance threshold
507 // }
508
509 const double bare_rate = bare_rates[precomp.reaction_index];
510 const double screeningFactor = screeningFactors[precomp.reaction_index];
511 const size_t numReactants = m_reactions[precomp.reaction_index].reactants().size();
512 const size_t numProducts = m_reactions[precomp.reaction_index].products().size();
513
514 const double forwardMolarReactionFlow =
515 screeningFactor *
516 bare_rate *
517 precomp.symmetry_factor *
518 forwardAbundanceProduct *
519 std::pow(rho, numReactants > 1 ? numReactants - 1 : 0.0);
520
521 double reverseMolarReactionFlow = 0.0;
522 if (precomp.reverse_symmetry_factor != 0.0 and m_useReverseReactions) {
523 const double bare_reverse_rate = bare_reverse_rates[precomp.reaction_index];
524 double reverseAbundanceProduct = 1.0;
525 for (size_t i = 0; i < precomp.unique_product_indices.size(); ++i) {
526 reverseAbundanceProduct *= std::pow(Y_in[precomp.unique_product_indices[i]], precomp.product_powers[i]);
527 }
528 reverseMolarReactionFlow = screeningFactor *
529 bare_reverse_rate *
530 precomp.reverse_symmetry_factor *
531 reverseAbundanceProduct *
532 std::pow(rho, numProducts > 1 ? numProducts - 1 : 0.0);
533 }
534
535 molarReactionFlows.push_back(forwardMolarReactionFlow - reverseMolarReactionFlow);
536
537 }
538
539 // --- Assemble molar abundance derivatives ---
541 result.dydt.assign(m_networkSpecies.size(), 0.0); // Initialize derivatives to zero
542 for (size_t j = 0; j < m_precomputedReactions.size(); ++j) {
543 const auto& precomp = m_precomputedReactions[j];
544 const double R_j = molarReactionFlows[j];
545
546 for (size_t i = 0; i < precomp.affected_species_indices.size(); ++i) {
547 const size_t speciesIndex = precomp.affected_species_indices[i];
548 const int stoichiometricCoefficient = precomp.stoichiometric_coefficients[i];
549
550 // Update the derivative for this species
551 result.dydt[speciesIndex] += static_cast<double>(stoichiometricCoefficient) * R_j;
552 }
553 }
554
555 // --- Calculate the nuclear energy generation rate ---
556 double massProductionRate = 0.0; // [mol][s^-1]
557 for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
558 const auto& species = m_networkSpecies[i];
559 massProductionRate += result.dydt[i] * species.mass() * m_constants.u;
560 }
561 result.nuclearEnergyGenerationRate = -massProductionRate * m_constants.Na * m_constants.c * m_constants.c; // [erg][s^-1][g^-1]
562 return result;
563
564 }
565
566 // --- Generate Stoichiometry Matrix ---
568 LOG_TRACE_L1(m_logger, "Generating stoichiometry matrix...");
569
570 // Task 1: Set dimensions and initialize the matrix
571 size_t numSpecies = m_networkSpecies.size();
572 size_t numReactions = m_reactions.size();
573 m_stoichiometryMatrix.resize(numSpecies, numReactions, false);
574
575 LOG_TRACE_L1(m_logger, "Stoichiometry matrix initialized with dimensions: {} rows (species) x {} columns (reactions).",
576 numSpecies, numReactions);
577
578 // Task 2: Populate the stoichiometry matrix
579 // Iterate through all reactions, assign them a column index, and fill in their stoichiometric coefficients.
580 size_t reactionColumnIndex = 0;
581 for (const auto& reaction : m_reactions) {
582 // Get the net stoichiometry for the current reaction
583 std::unordered_map<fourdst::atomic::Species, int> netStoichiometry = reaction.stoichiometry();
584
585 // Iterate through the species and their coefficients in the stoichiometry map
586 for (const auto& [species, coefficient] : netStoichiometry) {
587 // Find the row index for this species
588 auto it = m_speciesToIndexMap.find(species);
589 if (it != m_speciesToIndexMap.end()) {
590 const size_t speciesRowIndex = it->second;
591 // Set the matrix element. Boost.uBLAS handles sparse insertion.
592 m_stoichiometryMatrix(speciesRowIndex, reactionColumnIndex) = coefficient;
593 } else {
594 // This scenario should ideally not happen if m_networkSpeciesMap and m_speciesToIndexMap are correctly synced
595 LOG_ERROR(m_logger, "CRITICAL ERROR: Species '{}' from reaction '{}' stoichiometry not found in species to index map.",
596 species.name(), reaction.id());
597 m_logger -> flush_log();
598 throw std::runtime_error("Species not found in species to index map: " + std::string(species.name()));
599 }
600 }
601 reactionColumnIndex++; // Move to the next column for the next reaction
602 }
603
604 LOG_TRACE_L1(m_logger, "Stoichiometry matrix population complete. Number of non-zero elements: {}.",
605 m_stoichiometryMatrix.nnz()); // Assuming nnz() exists for compressed_matrix
606 }
607
609 const std::vector<double> &Y_in,
610 const double T9,
611 const double rho
612 ) const {
613 return calculateAllDerivatives<double>(Y_in, T9, rho);
614 }
615
617 const std::vector<ADDouble> &Y_in,
618 const ADDouble &T9,
619 const ADDouble &rho
620 ) const {
621 return calculateAllDerivatives<ADDouble>(Y_in, T9, rho);
622 }
623
628
632
633 void GraphEngine::setPrecomputation(const bool precompute) {
634 m_usePrecomputation = precompute;
635 }
636
640
644
647 const std::vector<double> &Y,
648 const double T9,
649 const double rho
650 ) const {
652 }
653
655 const std::vector<double> &Y_dynamic,
656 const double T9,
657 const double rho
658 ) const {
659
660 LOG_TRACE_L1_LIMIT_EVERY_N(1000, m_logger, "Generating jacobian matrix for T9={}, rho={}..", T9, rho);
661 const size_t numSpecies = m_networkSpecies.size();
662
663 // 1. Pack the input variables into a vector for CppAD
664 std::vector<double> adInput(numSpecies + 2, 0.0); // +2 for T9 and rho
665 for (size_t i = 0; i < numSpecies; ++i) {
666 adInput[i] = std::max(Y_dynamic[i], 1e-99); // regularize the jacobian...
667 }
668 adInput[numSpecies] = T9; // T9
669 adInput[numSpecies + 1] = rho; // rho
670
671 // 2. Calculate the full jacobian
672 const std::vector<double> dotY = m_rhsADFun.Jacobian(adInput);
673
674 // 3. Pack jacobian vector into sparse matrix
675 m_jacobianMatrix.clear();
676 for (size_t i = 0; i < numSpecies; ++i) {
677 for (size_t j = 0; j < numSpecies; ++j) {
678 const double value = dotY[i * (numSpecies + 2) + j];
679 if (std::abs(value) > MIN_JACOBIAN_THRESHOLD || i == j) { // Always keep diagonal elements to avoid pathological stiffness
680 m_jacobianMatrix(i, j) = value;
681 }
682 }
683 }
684 LOG_TRACE_L1_LIMIT_EVERY_N(1000, m_logger, "Jacobian matrix generated with dimensions: {} rows x {} columns.", m_jacobianMatrix.size1(), m_jacobianMatrix.size2());
685 }
686
688 const std::vector<double> &Y_dynamic,
689 const double T9,
690 const double rho,
691 const SparsityPattern &sparsityPattern
692 ) const {
693 // --- Pack the input variables into a vector for CppAD ---
694 const size_t numSpecies = m_networkSpecies.size();
695 std::vector<double> x(numSpecies + 2, 0.0);
696 for (size_t i = 0; i < numSpecies; ++i) {
697 x[i] = Y_dynamic[i];
698 }
699 x[numSpecies] = T9;
700 x[numSpecies + 1] = rho;
701
702 // --- Convert into CppAD Sparsity pattern ---
703 const size_t nnz = sparsityPattern.size(); // Number of non-zero entries in the sparsity pattern
704 std::vector<size_t> row_indices(nnz);
705 std::vector<size_t> col_indices(nnz);
706
707 for (size_t k = 0; k < nnz; ++k) {
708 row_indices[k] = sparsityPattern[k].first;
709 col_indices[k] = sparsityPattern[k].second;
710 }
711
712 std::vector<double> values(nnz);
713 const size_t num_rows_jac = numSpecies;
714 const size_t num_cols_jac = numSpecies + 2; // +2 for T9 and rho
715
716 CppAD::sparse_rc<std::vector<size_t>> CppAD_sparsity_pattern(num_rows_jac, num_cols_jac, nnz);
717 for (size_t k = 0; k < nnz; ++k) {
718 CppAD_sparsity_pattern.set(k, sparsityPattern[k].first, sparsityPattern[k].second);
719 }
720
721 CppAD::sparse_rcv<std::vector<size_t>, std::vector<double>> jac_subset(CppAD_sparsity_pattern);
722
723 m_rhsADFun.sparse_jac_rev(
724 x,
725 jac_subset, // Sparse Jacobian output
727 "cppad",
728 m_jac_work // Work vector for CppAD
729 );
730
731 // --- Convert the sparse Jacobian back to the Boost uBLAS format ---
732 m_jacobianMatrix.clear();
733 for (size_t k = 0; k < nnz; ++k) {
734 const size_t row = jac_subset.row()[k];
735 const size_t col = jac_subset.col()[k];
736 const double value = jac_subset.val()[k];
737
738 if (std::abs(value) > MIN_JACOBIAN_THRESHOLD) {
739 m_jacobianMatrix(row, col) = value; // Insert into the sparse matrix
740 }
741 }
742 }
743
744 double GraphEngine::getJacobianMatrixEntry(const int i, const int j) const {
745 // LOG_TRACE_L3(m_logger, "Getting jacobian matrix entry for {},{} = {}", i, j, m_jacobianMatrix(i, j));
746 return m_jacobianMatrix(i, j);
747 }
748
749 std::unordered_map<fourdst::atomic::Species, int> GraphEngine::getNetReactionStoichiometry(
751 ) {
752 return reaction.stoichiometry();
753 }
754
756 const int speciesIndex,
757 const int reactionIndex
758 ) const {
759 return m_stoichiometryMatrix(speciesIndex, reactionIndex);
760 }
761
762 void GraphEngine::exportToDot(const std::string &filename) const {
763 LOG_TRACE_L1(m_logger, "Exporting network graph to DOT file: {}", filename);
764
765 std::ofstream dotFile(filename);
766 if (!dotFile.is_open()) {
767 LOG_ERROR(m_logger, "Failed to open file for writing: {}", filename);
768 m_logger->flush_log();
769 throw std::runtime_error("Failed to open file for writing: " + filename);
770 }
771
772 dotFile << "digraph NuclearReactionNetwork {\n";
773 dotFile << " graph [rankdir=LR, splines=true, overlap=false, bgcolor=\"#f0f0f0\"];\n";
774 dotFile << " node [shape=circle, style=filled, fillcolor=\"#a7c7e7\", fontname=\"Helvetica\"];\n";
775 dotFile << " edge [fontname=\"Helvetica\", fontsize=10];\n\n";
776
777 // 1. Define all species as nodes
778 dotFile << " // --- Species Nodes ---\n";
779 for (const auto& species : m_networkSpecies) {
780 dotFile << " \"" << species.name() << "\" [label=\"" << species.name() << "\"];\n";
781 }
782 dotFile << "\n";
783
784 // 2. Define all reactions as intermediate nodes and connect them
785 dotFile << " // --- Reaction Edges ---\n";
786 for (const auto& reaction : m_reactions) {
787 // Create a unique ID for the reaction node
788 std::string reactionNodeId = "reaction_" + std::string(reaction.id());
789
790 // Define the reaction node (small, black dot)
791 dotFile << " \"" << reactionNodeId << "\" [shape=point, fillcolor=black, width=0.1, height=0.1, label=\"\"];\n";
792
793 // Draw edges from reactants to the reaction node
794 for (const auto& reactant : reaction.reactants()) {
795 dotFile << " \"" << reactant.name() << "\" -> \"" << reactionNodeId << "\";\n";
796 }
797
798 // Draw edges from the reaction node to products
799 for (const auto& product : reaction.products()) {
800 dotFile << " \"" << reactionNodeId << "\" -> \"" << product.name() << "\" [label=\"" << reaction.qValue() << " MeV\"];\n";
801 }
802 dotFile << "\n";
803 }
804
805 dotFile << "}\n";
806 dotFile.close();
807 LOG_TRACE_L1(m_logger, "Successfully exported network to {}", filename);
808 }
809
810 void GraphEngine::exportToCSV(const std::string &filename) const {
811 LOG_TRACE_L1(m_logger, "Exporting network graph to CSV file: {}", filename);
812
813 std::ofstream csvFile(filename, std::ios::out | std::ios::trunc);
814 if (!csvFile.is_open()) {
815 LOG_ERROR(m_logger, "Failed to open file for writing: {}", filename);
816 m_logger->flush_log();
817 throw std::runtime_error("Failed to open file for writing: " + filename);
818 }
819 csvFile << "Reaction;Reactants;Products;Q-value;sources;rates\n";
820 for (const auto& reaction : m_reactions) {
821 // Dynamic cast to REACLIBReaction to access specific properties
822 csvFile << reaction.id() << ";";
823 // Reactants
824 size_t count = 0;
825 for (const auto& reactant : reaction.reactants()) {
826 csvFile << reactant.name();
827 if (++count < reaction.reactants().size()) {
828 csvFile << ",";
829 }
830 }
831 csvFile << ";";
832 count = 0;
833 for (const auto& product : reaction.products()) {
834 csvFile << product.name();
835 if (++count < reaction.products().size()) {
836 csvFile << ",";
837 }
838 }
839 csvFile << ";" << reaction.qValue() << ";";
840 // Reaction coefficients
841 auto sources = reaction.sources();
842 count = 0;
843 for (const auto& source : sources) {
844 csvFile << source;
845 if (++count < sources.size()) {
846 csvFile << ",";
847 }
848 }
849 csvFile << ";";
850 // Reaction coefficients
851 count = 0;
852 for (const auto& rates : reaction) {
853 csvFile << rates;
854 if (++count < reaction.size()) {
855 csvFile << ",";
856 }
857 }
858 csvFile << "\n";
859 }
860 csvFile.close();
861 LOG_TRACE_L1(m_logger, "Successfully exported network graph to {}", filename);
862 }
863
864 std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesTimescales(
865 const std::vector<double> &Y,
866 const double T9,
867 const double rho
868 ) const {
869 auto [dydt, _] = calculateAllDerivatives<double>(Y, T9, rho);
870 std::unordered_map<fourdst::atomic::Species, double> speciesTimescales;
871 speciesTimescales.reserve(m_networkSpecies.size());
872 for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
873 double timescale = std::numeric_limits<double>::infinity();
874 const auto species = m_networkSpecies[i];
875 if (std::abs(dydt[i]) > 0.0) {
876 timescale = std::abs(Y[i] / dydt[i]);
877 }
878 speciesTimescales.emplace(species, timescale);
879 }
880 return speciesTimescales;
881 }
882
883 std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesDestructionTimescales(
884 const std::vector<double> &Y,
885 const double T9,
886 const double rho
887 ) const {
888 auto [dydt, _] = calculateAllDerivatives<double>(Y, T9, rho);
889 std::unordered_map<fourdst::atomic::Species, double> speciesDestructionTimescales;
890 speciesDestructionTimescales.reserve(m_networkSpecies.size());
891 for (const auto& species : m_networkSpecies) {
892 double netDestructionFlow = 0.0;
893 for (const auto& reaction : m_reactions) {
894 if (reaction.stoichiometry(species) < 0) {
895 const double flow = calculateMolarReactionFlow<double>(reaction, Y, T9, rho);
896 netDestructionFlow += flow;
897 }
898 }
899 double timescale = std::numeric_limits<double>::infinity();
900 if (netDestructionFlow != 0.0) {
901 timescale = Y[getSpeciesIndex(species)] / netDestructionFlow;
902 }
903 speciesDestructionTimescales.emplace(species, timescale);
904 }
905 return speciesDestructionTimescales;
906 }
907
908 fourdst::composition::Composition GraphEngine::update(const NetIn &netIn) {
909 fourdst::composition::Composition baseUpdatedComposition = netIn.composition;
910 for (const auto& species : m_networkSpecies) {
911 if (!netIn.composition.contains(species)) {
912 baseUpdatedComposition.registerSpecies(species);
913 baseUpdatedComposition.setMassFraction(species, 0.0);
914 }
915 }
916 baseUpdatedComposition.finalize(false);
917 return baseUpdatedComposition;
918 }
919
920 bool GraphEngine::isStale(const NetIn &netIn) {
921 return false;
922 }
923
925 LOG_TRACE_L1(m_logger, "Recording AD tape for the RHS calculation...");
926
927 // Task 1: Set dimensions and initialize the matrix
928 const size_t numSpecies = m_networkSpecies.size();
929 if (numSpecies == 0) {
930 LOG_ERROR(m_logger, "Cannot record AD tape: No species in the network.");
931 m_logger->flush_log();
932 throw std::runtime_error("Cannot record AD tape: No species in the network.");
933 }
934 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.
935
936 // --- CppAD Tape Recording ---
937 // 1. Declare independent variable (adY)
938 // We also initialize the dummy variable for tape recording (these tell CppAD what the derivative chain looks like).
939 // Their numeric values are irrelevant except for in so far as they avoid numerical instabilities.
940
941 // Distribute total mass fraction uniformly between species in the dummy variable space
942 const auto uniformMassFraction = static_cast<CppAD::AD<double>>(1.0 / static_cast<double>(numSpecies));
943 std::vector<CppAD::AD<double>> adInput(numADInputs, uniformMassFraction);
944 adInput[numSpecies] = 1.0; // Dummy T9
945 adInput[numSpecies + 1] = 1.0; // Dummy rho
946
947 // 3. Declare independent variables (what CppAD will differentiate wrt.)
948 // This also beings the tape recording process.
949 CppAD::Independent(adInput);
950
951 std::vector<CppAD::AD<double>> adY(numSpecies);
952 for(size_t i = 0; i < numSpecies; ++i) {
953 adY[i] = adInput[i];
954 }
955 const CppAD::AD<double> adT9 = adInput[numSpecies];
956 const CppAD::AD<double> adRho = adInput[numSpecies + 1];
957
958
959 // 5. Call the actual templated function
960 // We let T9 and rho be constant, so we pass them as fixed values.
961 auto [dydt, nuclearEnergyGenerationRate] = calculateAllDerivatives<CppAD::AD<double>>(adY, adT9, adRho);
962
963 m_rhsADFun.Dependent(adInput, dydt);
964
965 LOG_TRACE_L1(m_logger, "AD tape recorded successfully for the RHS calculation. Number of independent variables: {}.",
966 adInput.size());
967 }
968
970 m_atomicReverseRates.clear();
971 m_atomicReverseRates.reserve(m_reactions.size());
972
973 for (const auto& reaction: m_reactions) {
974 if (reaction.qValue() != 0.0) {
975 m_atomicReverseRates.push_back(std::make_unique<AtomicReverseRate>(reaction, *this));
976 } else {
977 m_atomicReverseRates.push_back(nullptr);
978 }
979 }
980 }
981
983 LOG_TRACE_L1(m_logger, "Pre-computing constant components of GraphNetwork state...");
984
985 // --- Reverse map for fast species lookups ---
986 std::unordered_map<fourdst::atomic::Species, size_t> speciesIndexMap;
987 for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
988 speciesIndexMap[m_networkSpecies[i]] = i;
989 }
990
992 m_precomputedReactions.reserve(m_reactions.size());
993
994 for (size_t i = 0; i < m_reactions.size(); ++i) {
995 const auto& reaction = m_reactions[i];
996 PrecomputedReaction precomp;
997 precomp.reaction_index = i;
998
999 // --- Precompute forward reaction information ---
1000 // Count occurrences for each reactant to determine powers and symmetry
1001 std::unordered_map<size_t, int> reactantCounts;
1002 for (const auto& reactant: reaction.reactants()) {
1003 size_t reactantIndex = speciesIndexMap.at(reactant);
1004 reactantCounts[reactantIndex]++;
1005 }
1006
1007 double symmetryDenominator = 1.0;
1008 for (const auto& [index, count] : reactantCounts) {
1009 precomp.unique_reactant_indices.push_back(index);
1010 precomp.reactant_powers.push_back(count);
1011
1012 symmetryDenominator *= std::tgamma(count + 1);
1013 }
1014
1015 precomp.symmetry_factor = 1.0/symmetryDenominator;
1016
1017 // --- Precompute reverse reaction information ---
1018 if (reaction.qValue() != 0.0) {
1019 std::unordered_map<size_t, int> productCounts;
1020 for (const auto& product : reaction.products()) {
1021 productCounts[speciesIndexMap.at(product)]++;
1022 }
1023 double reverseSymmetryDenominator = 1.0;
1024 for (const auto& [index, count] : productCounts) {
1025 precomp.unique_product_indices.push_back(index);
1026 precomp.product_powers.push_back(count);
1027 reverseSymmetryDenominator *= std::tgamma(count + 1);
1028 }
1029
1030 precomp.reverse_symmetry_factor = 1.0/reverseSymmetryDenominator;
1031 } else {
1032 precomp.unique_product_indices.clear();
1033 precomp.product_powers.clear();
1034 precomp.reverse_symmetry_factor = 0.0; // No reverse reaction for Q = 0 reactions
1035 }
1036
1037 // --- Precompute stoichiometry information ---
1038 const auto stoichiometryMap = reaction.stoichiometry();
1039 precomp.affected_species_indices.reserve(stoichiometryMap.size());
1040 precomp.stoichiometric_coefficients.reserve(stoichiometryMap.size());
1041
1042 for (const auto& [species, coeff] : stoichiometryMap) {
1043 precomp.affected_species_indices.push_back(speciesIndexMap.at(species));
1044 precomp.stoichiometric_coefficients.push_back(coeff);
1045 }
1046
1047 m_precomputedReactions.push_back(std::move(precomp));
1048 }
1049 }
1050
1052 const size_t p,
1053 const size_t q,
1054 const CppAD::vector<bool> &vx,
1055 CppAD::vector<bool> &vy,
1056 const CppAD::vector<double> &tx,
1057 CppAD::vector<double> &ty
1058 ) {
1059
1060 if ( p != 0) { return false; }
1061 const double T9 = tx[0];
1062
1063 const double reverseRate = m_engine.calculateReverseRate(m_reaction, T9);
1064 // std::cout << m_reaction.peName() << " reverseRate: " << reverseRate << " at T9: " << T9 << "\n";
1065 ty[0] = reverseRate; // Store the reverse rate in the output vector
1066
1067 if (vx.size() > 0) {
1068 vy[0] = vx[0];
1069 }
1070 return true;
1071 }
1072
1074 size_t q,
1075 const CppAD::vector<double> &tx,
1076 const CppAD::vector<double> &ty,
1077 CppAD::vector<double> &px,
1078 const CppAD::vector<double> &py
1079 ) {
1080 const double T9 = tx[0];
1081 const double reverseRate = ty[0];
1082
1083 const double derivative = m_engine.calculateReverseRateTwoBodyDerivative(m_reaction, T9, reverseRate);
1084 // std::cout << m_reaction.peName() << " reverseRate Derivative: " << derivative << "\n";
1085
1086 px[0] = py[0] * derivative; // Return the derivative of the reverse rate with respect to T9
1087
1088 return true;
1089 }
1090
1092 size_t q,
1093 const CppAD::vector<std::set<size_t>> &r,
1094 CppAD::vector<std::set<size_t>> &s
1095 ) {
1096 s[0] = r[0];
1097 return true;
1098 }
1099
1101 size_t q,
1102 const CppAD::vector<std::set<size_t>> &rt,
1103 CppAD::vector<std::set<size_t>> &st
1104 ) {
1105 st[0] = rt[0];
1106 return true;
1107 }
1108}
bool reverse(size_t q, const CppAD::vector< double > &tx, const CppAD::vector< double > &ty, CppAD::vector< double > &px, const CppAD::vector< double > &py) override
bool rev_sparse_jac(size_t q, const CppAD::vector< std::set< size_t > > &rt, CppAD::vector< std::set< size_t > > &st) override
const reaction::Reaction & m_reaction
bool forward(size_t p, size_t q, const CppAD::vector< bool > &vx, CppAD::vector< bool > &vy, const CppAD::vector< double > &tx, CppAD::vector< double > &ty) override
bool for_sparse_jac(size_t q, const CppAD::vector< std::set< size_t > > &r, CppAD::vector< std::set< size_t > > &s) override
bool isPrecomputationEnabled() const
double calculateReverseRateTwoBody(const reaction::Reaction &reaction, const double T9, const double forwardRate, const double expFactor) const
double calculateReverseRate(const reaction::Reaction &reaction, double T9) const
const std::vector< fourdst::atomic::Species > & getNetworkSpecies() const override
Gets the list of species in the network.
BuildDepthType getDepth() const override
bool m_usePrecomputation
Flag to enable or disable using precomputed reactions for efficiency. Mathematically,...
CppAD::sparse_rc< std::vector< size_t > > m_full_jacobian_sparsity_pattern
Full sparsity pattern for the Jacobian matrix.
CppAD::sparse_jac_work m_jac_work
Work object for sparse Jacobian calculations.
void populateReactionIDMap()
Populates the reaction ID map.
std::vector< double > mapNetInToMolarAbundanceVector(const NetIn &netIn) const override
void collectAtomicReverseRateAtomicBases()
CppAD::ADFun< double > m_rhsADFun
CppAD function for the right-hand side of the ODE.
boost::numeric::ublas::compressed_matrix< double > m_jacobianMatrix
Jacobian matrix (species x species).
double getJacobianMatrixEntry(const int i, const int j) const override
Gets an entry from the previously generated Jacobian matrix.
std::unordered_map< std::string_view, fourdst::atomic::Species > m_networkSpeciesMap
Map from species name to Species object.
bool m_useReverseReactions
Flag to enable or disable reverse reactions. If false, only forward reactions are considered.
std::unique_ptr< partition::PartitionFunction > m_partitionFunction
Partition function for the network.
void setNetworkReactions(const reaction::LogicalReactionSet &reactions) override
void setUseReverseReactions(bool useReverse)
void populateSpeciesToIndexMap()
Populates the species-to-index map.
quill::Logger * m_logger
screening::ScreeningType m_screeningType
Screening type for the reaction network. Default to no screening.
fourdst::composition::Composition update(const NetIn &netIn) override
Update the internal state of the engine.
std::vector< PrecomputedReaction > m_precomputedReactions
Precomputed reactions for efficiency.
std::unordered_map< std::string_view, reaction::Reaction * > m_reactionIDMap
Map from reaction ID to REACLIBReaction. //PERF: This makes copies of REACLIBReaction and could be a ...
std::expected< std::unordered_map< fourdst::atomic::Species, double >, expectations::StaleEngineError > getSpeciesTimescales(const std::vector< double > &Y, double T9, double rho) const override
Computes timescales for all species in the network.
screening::ScreeningType getScreeningModel() const override
Get the current electron screening model.
int getStoichiometryMatrixEntry(const int speciesIndex, const int reactionIndex) const override
Gets an entry from the stoichiometry matrix.
void setPrecomputation(bool precompute)
BuildDepthType m_depth
void setScreeningModel(screening::ScreeningType) override
Set the electron screening model.
std::vector< std::unique_ptr< AtomicReverseRate > > m_atomicReverseRates
void exportToCSV(const std::string &filename) const
Exports the network to a CSV file for analysis.
static std::unordered_map< fourdst::atomic::Species, int > getNetReactionStoichiometry(const reaction::Reaction &reaction)
Gets the net stoichiometry for a given reaction.
void reserveJacobianMatrix() const
Reserves space for the Jacobian matrix.
int getSpeciesIndex(const fourdst::atomic::Species &species) const override
double calculateMolarReactionFlow(const reaction::Reaction &reaction, const std::vector< double > &Y, const double T9, const double rho) const override
Calculates the molar reaction flow for a given reaction.
std::vector< fourdst::atomic::Species > m_networkSpecies
Vector of unique species in the network.
void recordADTape()
Records the AD tape for the right-hand side of the ODE.
StepDerivatives< double > calculateAllDerivativesUsingPrecomputation(const std::vector< double > &Y_in, const std::vector< double > &bare_rates, const std::vector< double > &bare_reverse_rates, double T9, double rho) const
bool involvesSpecies(const fourdst::atomic::Species &species) const
Checks if a given species is involved in the network.
std::expected< StepDerivatives< double >, expectations::StaleEngineError > calculateRHSAndEnergy(const std::vector< double > &Y, const double T9, const double rho) const override
Calculates the right-hand side (dY/dt) and energy generation rate.
reaction::LogicalReactionSet m_reactions
Set of REACLIB reactions in the network.
void syncInternalMaps()
Synchronizes the internal maps.
bool validateConservation() const
Validates mass and charge conservation across all reactions.
void generateJacobianMatrix(const std::vector< double > &Y_dynamic, const double T9, const double rho) const override
Generates the Jacobian matrix for the current state.
boost::numeric::ublas::compressed_matrix< int > m_stoichiometryMatrix
Stoichiometry matrix (species x reactions).
const reaction::LogicalReactionSet & getNetworkReactions() const override
Gets the set of logical reactions in the network.
std::unordered_map< fourdst::atomic::Species, size_t > m_speciesToIndexMap
Map from species to their index in the stoichiometry matrix.
void rebuild(const fourdst::composition::Composition &comp, const BuildDepthType depth) override
void exportToDot(const std::string &filename) const
Exports the network to a DOT file for visualization.
const partition::PartitionFunction & getPartitionFunction() const
bool isUsingReverseReactions() const
PrimingReport primeEngine(const NetIn &netIn) override
void generateStoichiometryMatrix() override
Generates the stoichiometry matrix for the network.
void collectNetworkSpecies()
Collects the unique species in the network.
void validateComposition(const fourdst::composition::Composition &composition, double culling, double T9)
Validates the composition against the current reaction set.
std::expected< std::unordered_map< fourdst::atomic::Species, double >, expectations::StaleEngineError > getSpeciesDestructionTimescales(const std::vector< double > &Y, double T9, double rho) const override
bool isStale(const NetIn &netIn) override
std::unique_ptr< screening::ScreeningModel > m_screeningModel
double calculateReverseRateTwoBodyDerivative(const reaction::Reaction &reaction, const double T9, const double reverseRate) const
StepDerivatives< T > calculateAllDerivatives(const std::vector< T > &Y_in, T T9, T rho) const
Calculates all derivatives (dY/dt) and the energy generation rate.
GraphEngine(const fourdst::composition::Composition &composition, const BuildDepthType=NetworkBuildDepth::Full)
Constructs a GraphEngine from a composition.
Abstract interface for evaluating nuclear partition functions.
Represents a single nuclear reaction from a specific data source.
Definition reaction.h:72
TemplatedReactionSet< LogicalReaction > LogicalReactionSet
A set of logical reactions.
Definition reaction.h:563
std::unique_ptr< ScreeningModel > selectScreeningModel(ScreeningType type)
A factory function to select and create a screening model.
ScreeningType
Enumerates the available plasma screening models.
CppAD::AD< double > ADDouble
Alias for CppAD AD type for double precision.
std::variant< NetworkBuildDepth, int > BuildDepthType
Variant specifying either a predefined NetworkBuildDepth or a custom integer depth.
Definition building.h:37
std::vector< std::pair< size_t, size_t > > SparsityPattern
PrimingReport primeNetwork(const NetIn &, DynamicEngine &engine)
Primes absent species in the network to their equilibrium abundances.
Definition priming.cpp:39
static constexpr double MIN_JACOBIAN_THRESHOLD
Minimum value for Jacobian matrix entries.
reaction::LogicalReactionSet build_reaclib_nuclear_network(const fourdst::composition::Composition &composition, BuildDepthType maxLayers=NetworkBuildDepth::Full, bool reverse=false)
Builds a nuclear reaction network from the Reaclib library based on an initial composition.
static bool s_debug
Defines classes for representing and managing nuclear reactions.
std::vector< int > product_powers
Powers of each unique product in the reverse reaction.
double reverse_symmetry_factor
Symmetry factor for reverse reactions.
std::vector< size_t > unique_product_indices
Unique product indices for reverse reactions.
double density
Density in g/cm^3.
Definition network.h:58
fourdst::composition::Composition composition
Composition of the network.
Definition network.h:54
double temperature
Temperature in Kelvin.
Definition network.h:57
Captures the result of a network priming operation.
Definition reporting.h:67
Structure holding derivatives and energy generation for a network step.
T nuclearEnergyGenerationRate
Specific energy generation rate (e.g., erg/g/s).
std::vector< T > dydt
Derivatives of abundances (dY/dt for each species).