perf(thread saftey): All Engines are now thread safe

Previously engines were not thread safe, a seperate engine would be
needed for every thread. This is no longer the case. This allows for
much more efficient parallel execution
This commit is contained in:
2025-12-12 12:08:47 -05:00
parent c7574a2f3d
commit e114c0e240
46 changed files with 3685 additions and 1604 deletions

View File

@@ -3,12 +3,15 @@
#include "gridfire/utils/table_format.h"
#include "fourdst/atomic/species.h"
#include "gridfire/engine/scratchpads/blob.h"
#include <vector>
#include <string>
#include <algorithm>
namespace gridfire::engine::diagnostics {
std::optional<nlohmann::json> report_limiting_species(
scratch::StateBlob& ctx,
const DynamicEngine &engine,
const std::vector<double> &Y_full,
const std::vector<double> &E_full,
@@ -24,7 +27,7 @@ namespace gridfire::engine::diagnostics {
double abundance;
};
const auto& species_list = engine.getNetworkSpecies();
const auto& species_list = engine.getNetworkSpecies(ctx);
std::vector<SpeciesError> errors;
for (size_t i = 0; i < species_list.size(); ++i) {
@@ -75,6 +78,7 @@ namespace gridfire::engine::diagnostics {
}
std::optional<nlohmann::json> inspect_species_balance(
scratch::StateBlob& ctx,
const DynamicEngine& engine,
const std::string& species_name,
const fourdst::composition::Composition &comp,
@@ -90,11 +94,11 @@ namespace gridfire::engine::diagnostics {
double total_creation_flow = 0.0;
double total_destruction_flow = 0.0;
for (const auto& reaction : engine.getNetworkReactions()) {
for (const auto& reaction : engine.getNetworkReactions(ctx)) {
const int stoichiometry = reaction->stoichiometry(species_obj);
if (stoichiometry == 0) continue;
const double flow = engine.calculateMolarReactionFlow(*reaction, comp, T9, rho);
const double flow = engine.calculateMolarReactionFlow(ctx, *reaction, comp, T9, rho);
if (stoichiometry > 0) {
creation_ids.emplace_back(reaction->id());
@@ -157,17 +161,18 @@ namespace gridfire::engine::diagnostics {
}
std::optional<nlohmann::json> inspect_jacobian_stiffness(
scratch::StateBlob& ctx,
const DynamicEngine &engine,
const fourdst::composition::Composition &comp,
const double T9,
const double rho,
const bool json
) {
NetworkJacobian jac = engine.generateJacobianMatrix(comp, T9, rho);
NetworkJacobian jac = engine.generateJacobianMatrix(ctx, comp, T9, rho);
jac = regularize_jacobian(jac, comp);
const auto& species_list = engine.getNetworkSpecies();
const auto& species_list = engine.getNetworkSpecies(ctx);
double max_diag = 0.0;
double max_off_diag = 0.0;

View File

@@ -8,11 +8,16 @@
#include "gridfire/utils/hashing.h"
#include "gridfire/utils/table_format.h"
#include "gridfire/engine/scratchpads/engine_graph_scratchpad.h"
#include "gridfire/engine/scratchpads/blob.h"
#include "gridfire/engine/scratchpads/utils.h"
#include "fourdst/atomic/species.h"
#include "fourdst/atomic/atomicSpecies.h"
#include "quill/LogMacros.h"
// ReSharper disable once CppUnusedIncludeDirective
#include <cstdint>
#include <set>
#include <stdexcept>
@@ -28,9 +33,6 @@
#include "cppad/utility/sparse_rc.hpp"
#include "cppad/utility/sparse_rcv.hpp"
#ifdef GRIDFIRE_USE_OPENMP
#include <omp.h>
#endif
namespace {
@@ -115,8 +117,9 @@ namespace gridfire::engine {
const NetworkConstructionFlags reactionTypes ) :
m_weakRateInterpolator(rates::weak::UNIFIED_WEAK_DATA),
m_reactions(build_nuclear_network(composition, m_weakRateInterpolator, buildDepth, reactionTypes)),
m_partitionFunction(partitionFunction.clone()),
m_depth(buildDepth),
m_partitionFunction(partitionFunction.clone())
m_state_blob_offset(0) // For a base engine the offset is always 0
{
syncInternalMaps();
}
@@ -125,33 +128,37 @@ namespace gridfire::engine {
const reaction::ReactionSet &reactions
) :
m_weakRateInterpolator(rates::weak::UNIFIED_WEAK_DATA),
m_reactions(reactions)
m_reactions(reactions),
m_state_blob_offset(0)
{
syncInternalMaps();
}
std::expected<StepDerivatives<double>, EngineStatus> GraphEngine::calculateRHSAndEnergy(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
bool trust
) const {
return calculateRHSAndEnergy(comp, T9, rho, m_reactions);
return calculateRHSAndEnergy(ctx, comp, T9, rho, m_reactions);
}
std::expected<StepDerivatives<double>, EngineStatus> GraphEngine::calculateRHSAndEnergy(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const reaction::ReactionSet &activeReactions
) const {
auto* state = scratch::get_state<scratch::GraphEngineScratchPad, true>(ctx);
LOG_TRACE_L3(m_logger, "Calculating RHS and Energy in GraphEngine at T9 = {}, rho = {}.", T9, rho);
const double Ye = comp.getElectronAbundance();
const std::vector<double> molarAbundances = comp.getMolarAbundanceVector();
if (m_usePrecomputation) {
const std::size_t state_hash = utils::hash_state(comp, T9, rho, activeReactions);
if (m_stepDerivativesCache.contains(state_hash)) {
return m_stepDerivativesCache.at(state_hash);
if (state->stepDerivativesCache.contains(state_hash)) {
return state->stepDerivativesCache.at(state_hash);
}
LOG_TRACE_L3(m_logger, "Using precomputation for reaction rates in GraphEngine calculateRHSAndEnergy.");
std::vector<double> bare_rates;
@@ -171,9 +178,9 @@ namespace gridfire::engine {
LOG_TRACE_L3(m_logger, "Precomputed {} forward and {} reverse reaction rates for active reactions.", bare_rates.size(), bare_reverse_rates.size());
// --- The public facing interface can always use the precomputed version since taping is done internally ---
StepDerivatives<double> result = calculateAllDerivativesUsingPrecomputation(comp, bare_rates, bare_reverse_rates, T9, rho, activeReactions);
m_stepDerivativesCache.insert(std::make_pair(state_hash, result));
m_most_recent_rhs_calculation = result;
StepDerivatives<double> result = calculateAllDerivativesUsingPrecomputation(ctx, comp, bare_rates, bare_reverse_rates, T9, rho, activeReactions);
state->stepDerivativesCache.insert(std::make_pair(state_hash, result));
state->most_recent_rhs_calculation = result;
return result;
} else {
LOG_TRACE_L2(m_logger, "Not using precomputation for reaction rates in GraphEngine calculateRHSAndEnergy.");
@@ -194,25 +201,28 @@ namespace gridfire::engine {
return false;
}
);
m_most_recent_rhs_calculation = result;
state->most_recent_rhs_calculation = result;
return result;
}
}
EnergyDerivatives GraphEngine::calculateEpsDerivatives(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
return calculateEpsDerivatives(comp, T9, rho, m_reactions);
return calculateEpsDerivatives(ctx, comp, T9, rho, m_reactions);
}
EnergyDerivatives GraphEngine::calculateEpsDerivatives(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const reaction::ReactionSet &activeReactions
) const {
auto* state = scratch::get_state<scratch::GraphEngineScratchPad, true>(ctx);
const size_t numSpecies = m_networkSpecies.size();
const size_t numADInputs = numSpecies + 2; // +2 for T9 and rho
@@ -236,10 +246,11 @@ namespace gridfire::engine {
w[numSpecies] = 1.0; // We want the derivative of the energy generation rate
// Sweep the tape forward to record the function value at x
m_rhsADFun.Forward(0, x);
assert(state->rhsADFun.has_value() && "AD tape for energy derivatives has not been recorded.");
state->rhsADFun.value().Forward(0, x);
// Extract the gradient at the previously evaluated point x using reverse mode
const std::vector<double> eps_derivatives = m_rhsADFun.Reverse(1, w);
const std::vector<double> eps_derivatives = state->rhsADFun.value().Reverse(1, w);
const double dEps_dT9 = eps_derivatives[numSpecies];
const double dEps_dRho = eps_derivatives[numSpecies + 1];
@@ -252,19 +263,8 @@ namespace gridfire::engine {
return {dEps_dT, dEps_dRho};
}
void GraphEngine::syncInternalMaps() {
LOG_INFO(m_logger, "Synchronizing internal maps for REACLIB graph network (serif::network::GraphNetwork)...");
collectNetworkSpecies();
populateReactionIDMap();
populateSpeciesToIndexMap();
collectAtomicReverseRateAtomicBases();
generateStoichiometryMatrix();
recordADTape(); // Record the AD tape for the RHS of the ODE (dY/di and dEps/di) for all independent variables i
[[maybe_unused]] const size_t inputSize = m_rhsADFun.Domain();
const size_t outputSize = m_rhsADFun.Range();
void GraphEngine::generate_jacobian_sparsity_pattern() {
const size_t outputSize = m_authoritativeADFun.Range();
// Create a range x range identity pattern
CppAD::sparse_rc<std::vector<size_t>> patternIn(outputSize, outputSize, outputSize);
@@ -272,9 +272,8 @@ namespace gridfire::engine {
patternIn.set(i, i, i);
}
m_rhsADFun.rev_jac_sparsity(patternIn, false, false, false, m_full_jacobian_sparsity_pattern);
m_authoritativeADFun.rev_jac_sparsity(patternIn, false, false, false, m_full_jacobian_sparsity_pattern);
m_jac_work.clear();
m_full_sparsity_set.clear();
const auto& rows = m_full_jacobian_sparsity_pattern.row();
const auto& cols = m_full_jacobian_sparsity_pattern.col();
@@ -285,6 +284,17 @@ namespace gridfire::engine {
m_full_sparsity_set.insert(std::make_pair(rows[k], cols[k]));
}
}
}
void GraphEngine::syncInternalMaps() {
LOG_INFO(m_logger, "Synchronizing internal maps for REACLIB graph network (serif::network::GraphNetwork)...");
collectNetworkSpecies();
populateReactionIDMap();
populateSpeciesToIndexMap();
collectAtomicReverseRateAtomicBases();
recordADTape(); // Record the AD tape for the RHS of the ODE (dY/di and dEps/di) for all independent variables i
generate_jacobian_sparsity_pattern();
precomputeNetwork();
LOG_INFO(m_logger, "Internal maps synchronized. Network contains {} species and {} reactions.",
@@ -344,81 +354,26 @@ namespace gridfire::engine {
}
// --- Basic Accessors and Queries ---
const std::vector<fourdst::atomic::Species>& GraphEngine::getNetworkSpecies() const {
const std::vector<fourdst::atomic::Species>& GraphEngine::getNetworkSpecies(scratch::StateBlob &ctx) const {
return m_networkSpecies;
}
const reaction::ReactionSet& GraphEngine::getNetworkReactions() const {
const reaction::ReactionSet& GraphEngine::getNetworkReactions(
scratch::StateBlob& ctx
) const {
return m_reactions;
}
void GraphEngine::setNetworkReactions(const reaction::ReactionSet &reactions) {
m_reactions = reactions;
syncInternalMaps();
}
bool GraphEngine::involvesSpecies(const fourdst::atomic::Species& species) const {
bool GraphEngine::involvesSpecies(
scratch::StateBlob& ctx,
const fourdst::atomic::Species& species
) const {
const bool found = m_networkSpeciesMap.contains(species.name());
return found;
}
// --- Validation Methods ---
bool GraphEngine::validateConservation() const {
LOG_TRACE_L1(m_logger, "Validating mass (A) and charge (Z) conservation across all reactions in the network.");
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
for (const auto& reactant : reaction->reactants()) {
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.",
reactant.name(), reaction->id());
return false;
}
}
// Calculate total A and Z for products
for (const auto& product : reaction->products()) {
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.",
product.name(), reaction->id());
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={}.",
reaction->id(), totalReactantA, totalProductA);
return false;
}
if (totalReactantZ != totalProductZ) {
LOG_ERROR(m_logger, "Atomic number (Z) not conserved for reaction '{}': Reactants Z={} vs Products Z={}.",
reaction->id(), totalReactantZ, totalProductZ);
return false;
}
}
LOG_TRACE_L1(m_logger, "Mass (A) and charge (Z) conservation validated successfully for all reactions.");
return true; // All reactions passed the conservation check
}
double GraphEngine::compute_reaction_flow(
scratch::StateBlob& ctx,
const std::vector<double> &local_abundances,
const std::vector<double> &screening_factors,
const std::vector<double> &bare_rates,
@@ -488,6 +443,7 @@ namespace gridfire::engine {
}
std::pair<double, double> GraphEngine::compute_neutrino_fluxes(
scratch::StateBlob& ctx,
const double netFlow,
const reaction::Reaction &reaction
) const {
@@ -518,6 +474,7 @@ namespace gridfire::engine {
}
GraphEngine::PrecomputationKernelResults GraphEngine::accumulate_flows_serial(
scratch::StateBlob& ctx,
const std::vector<double> &local_abundances,
const std::vector<double> &screening_factors,
const std::vector<double> &bare_rates,
@@ -529,19 +486,20 @@ namespace gridfire::engine {
results.dydt_vector.resize(m_networkSpecies.size(), 0.0);
std::vector<double> molarReactionFlows;
molarReactionFlows.reserve(m_precomputedReactions.size());
molarReactionFlows.reserve(m_precomputed_reactions.size());
size_t reactionCounter = 0;
std::vector<size_t> reactionIndices;
reactionIndices.reserve(m_precomputedReactions.size());
reactionIndices.reserve(m_precomputed_reactions.size());
for (const auto& reaction : activeReactions) {
uint64_t reactionHash = reaction->hash(0);
const size_t reactionIndex = m_precomputedReactionIndexMap.at(reactionHash);
const size_t reactionIndex = m_precomputed_reaction_index_map.at(reactionHash);
reactionIndices.push_back(reactionIndex);
const PrecomputedReaction& precomputedReaction = m_precomputedReactions[reactionIndex];
const PrecomputedReaction& precomputedReaction = m_precomputed_reactions[reactionIndex];
double netFlow = compute_reaction_flow(
ctx,
local_abundances,
screening_factors,
bare_rates,
@@ -554,7 +512,7 @@ namespace gridfire::engine {
molarReactionFlows.push_back(netFlow);
auto [local_neutrino_loss, local_neutrino_flux] = compute_neutrino_fluxes(netFlow, *reaction);
auto [local_neutrino_loss, local_neutrino_flux] = compute_neutrino_fluxes(ctx, netFlow, *reaction);
results.total_neutrino_energy_loss_rate += local_neutrino_loss;
results.total_neutrino_flux += local_neutrino_flux;
@@ -565,7 +523,7 @@ namespace gridfire::engine {
reactionCounter = 0;
for (const auto& [reaction, j]: std::views::zip(activeReactions, reactionIndices)) {
const auto& precomp = m_precomputedReactions[j];
const auto& precomp = m_precomputed_reactions[j];
const double R_j = molarReactionFlows[reactionCounter];
for (size_t i = 0; i < precomp.affected_species_indices.size(); ++i) {
@@ -746,28 +704,24 @@ namespace gridfire::engine {
}
bool GraphEngine::isUsingReverseReactions() const {
bool GraphEngine::isUsingReverseReactions(
scratch::StateBlob& ctx
) const {
return m_useReverseReactions;
}
void GraphEngine::setUseReverseReactions(const bool useReverse) {
m_useReverseReactions = useReverse;
syncInternalMaps();
}
size_t GraphEngine::getSpeciesIndex(const fourdst::atomic::Species &species) const {
size_t GraphEngine::getSpeciesIndex(
scratch::StateBlob& ctx,
const fourdst::atomic::Species &species
) const {
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& [sp, y] : netIn.composition) {
Y[getSpeciesIndex(sp)] = y; // Map species to their molar abundance
}
return Y; // Return the vector of molar abundances
}
PrimingReport GraphEngine::primeEngine(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
PrimingReport GraphEngine::primeEngine(const NetIn &netIn) {
NetIn fullNetIn;
fourdst::composition::Composition composition;
@@ -787,27 +741,13 @@ namespace gridfire::engine {
reactionTypesToIgnore = {reaction::ReactionType::WEAK};
}
auto primingReport = primeNetwork(fullNetIn, *this, reactionTypesToIgnore);
auto primingReport = primeNetwork(ctx, fullNetIn, *this, reactionTypesToIgnore);
m_has_been_primed = true;
return primingReport;
}
BuildDepthType GraphEngine::getDepth() const {
return m_depth;
}
void GraphEngine::rebuild(const fourdst::composition::CompositionAbstract &comp, const BuildDepthType depth) {
if (depth != m_depth) {
m_depth = depth;
m_reactions = build_nuclear_network(comp, m_weakRateInterpolator, m_depth);
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.");
}
}
fourdst::composition::Composition GraphEngine::collectComposition(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
double T9,
double rho
@@ -827,7 +767,10 @@ namespace gridfire::engine {
return result;
}
SpeciesStatus GraphEngine::getSpeciesStatus(const fourdst::atomic::Species &species) const {
SpeciesStatus GraphEngine::getSpeciesStatus(
scratch::StateBlob& ctx,
const fourdst::atomic::Species &species
) const {
if (m_networkSpeciesMap.contains(species.name())) {
return SpeciesStatus::ACTIVE;
}
@@ -835,15 +778,18 @@ namespace gridfire::engine {
}
std::optional<StepDerivatives<double>> GraphEngine::getMostRecentRHSCalculation() const {
if (!m_most_recent_rhs_calculation.has_value()) {
std::optional<StepDerivatives<double>> GraphEngine::getMostRecentRHSCalculation(
scratch::StateBlob& ctx
) const {
const auto *state = scratch::get_state<scratch::GraphEngineScratchPad, true>(ctx);
if (!state->most_recent_rhs_calculation.has_value()) {
return std::nullopt;
}
return m_most_recent_rhs_calculation.value();
return state->most_recent_rhs_calculation.value();
}
StepDerivatives<double> GraphEngine::calculateAllDerivativesUsingPrecomputation(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const std::vector<double> &bare_rates,
const std::vector<double> &bare_reverse_rates,
@@ -851,6 +797,7 @@ namespace gridfire::engine {
const double rho,
const reaction::ReactionSet &activeReactions
) const {
auto *state = scratch::get_state<scratch::GraphEngineScratchPad, true>(ctx);
LOG_TRACE_L3(m_logger, "Computing screening factors for {} active reactions.", activeReactions.size());
// --- Calculate screening factors ---
const std::vector<double> screeningFactors = m_screeningModel->calculateScreeningFactors(
@@ -860,17 +807,17 @@ namespace gridfire::engine {
T9,
rho
);
m_local_abundance_cache.clear();
state->local_abundance_cache.clear();
for (const auto& species: m_networkSpecies) {
m_local_abundance_cache.push_back(comp.contains(species) ? comp.getMolarAbundance(species) : 0.0);
state->local_abundance_cache.push_back(comp.contains(species) ? comp.getMolarAbundance(species) : 0.0);
}
StepDerivatives<double> result;
std::vector<double> dydt_scratch(m_networkSpecies.size(), 0.0);
#ifndef GRIDFIRE_USE_OPENMP
const auto [dydt_vector, total_neutrino_energy_loss_rate, total_neutrino_flux] = accumulate_flows_serial(
m_local_abundance_cache,
ctx,
state->local_abundance_cache,
screeningFactors,
bare_rates,
bare_reverse_rates,
@@ -880,19 +827,6 @@ namespace gridfire::engine {
dydt_scratch = dydt_vector;
result.neutrinoEnergyLossRate = total_neutrino_energy_loss_rate;
result.totalNeutrinoFlux = total_neutrino_flux;
#else
const auto [dydt_vector, total_neutrino_energy_loss_rate, total_neutrino_flux] = accumulate_flows_parallel(
m_local_abundance_cache,
screeningFactors,
bare_rates,
bare_reverse_rates,
rho,
activeReactions
);
dydt_scratch = dydt_vector;
result.neutrinoEnergyLossRate = total_neutrino_energy_loss_rate;
result.totalNeutrinoFlux = total_neutrino_flux;
#endif
// load scratch into result.dydt
for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
@@ -910,33 +844,26 @@ namespace gridfire::engine {
}
// --- Generate Stoichiometry Matrix ---
void GraphEngine::generateStoichiometryMatrix() {
return; // Deprecated
}
void GraphEngine::setScreeningModel(const screening::ScreeningType model) {
m_screeningModel = screening::selectScreeningModel(model);
m_screeningType = model;
}
screening::ScreeningType GraphEngine::getScreeningModel() const {
screening::ScreeningType GraphEngine::getScreeningModel(
scratch::StateBlob& ctx
) const {
return m_screeningType;
}
void GraphEngine::setPrecomputation(const bool precompute) {
m_usePrecomputation = precompute;
}
bool GraphEngine::isPrecomputationEnabled() const {
bool GraphEngine::isPrecomputationEnabled(
scratch::StateBlob& ctx
) const {
return m_usePrecomputation;
}
const partition::PartitionFunction & GraphEngine::getPartitionFunction() const {
const partition::PartitionFunction & GraphEngine::getPartitionFunction(
scratch::StateBlob& ctx
) const {
return *m_partitionFunction;
}
double GraphEngine::calculateMolarReactionFlow(
scratch::StateBlob& ctx,
const reaction::Reaction &reaction,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
@@ -962,10 +889,12 @@ namespace gridfire::engine {
}
NetworkJacobian GraphEngine::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
auto *state = scratch::get_state<scratch::GraphEngineScratchPad, true>(ctx);
fourdst::composition::Composition mutableComp;
for (const auto& species : m_networkSpecies) {
mutableComp.registerSpecies(species);
@@ -986,10 +915,11 @@ namespace gridfire::engine {
adInput[numSpecies + 1] = rho; // rho
// 2. Calculate the full jacobian
const std::vector<double> dotY = m_rhsADFun.Jacobian(adInput);
assert(state->rhsADFun.has_value() && "RHS ADFun not recorded before Jacobian generation.");
const std::vector<double> dotY = state->rhsADFun.value().Jacobian(adInput);
// 3. Pack jacobian vector into sparse matrix
Eigen::SparseMatrix<double> jacobianMatrix(numSpecies, numSpecies);
Eigen::SparseMatrix<double> jacobianMatrix(static_cast<long>(numSpecies), static_cast<long>(numSpecies));
std::vector<Eigen::Triplet<double> > triplets;
for (size_t i = 0; i < numSpecies; ++i) {
for (size_t j = 0; j < numSpecies; ++j) {
@@ -1013,12 +943,15 @@ namespace gridfire::engine {
}
NetworkJacobian GraphEngine::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const std::vector<fourdst::atomic::Species> &activeSpecies
) const {
// PERF: For small k it may make sense to implement a purley forward mode AD computation, some heuristic could be used to switch between the two methods based on k and total network species
// PERF: For small k it may make sense to implement a purley forward mode AD computation,
// some heuristic could be used to switch between the two methods based on k and
// total network species
const size_t k_active = activeSpecies.size();
// --- 1. Get the list of global indices ---
@@ -1026,8 +959,8 @@ namespace gridfire::engine {
active_indices.reserve(k_active);
for (const auto& species : activeSpecies) {
assert(involvesSpecies(species));
active_indices.push_back(getSpeciesIndex(species));
assert(involvesSpecies(ctx, species));
active_indices.push_back(getSpeciesIndex(ctx, species));
}
// --- 2. Build the k x k sparsity pattern ---
@@ -1041,15 +974,17 @@ namespace gridfire::engine {
}
// --- 3. Call the sparse reverse-mode implementation ---
return generateJacobianMatrix(comp, T9, rho, sparsityPattern);
return generateJacobianMatrix(ctx, comp, T9, rho, sparsityPattern);
}
NetworkJacobian GraphEngine::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const SparsityPattern &sparsityPattern
) const {
auto *state = scratch::get_state<scratch::GraphEngineScratchPad, true>(ctx);
// --- Compute the intersection of the requested sparsity pattern with the full sparsity pattern ---
SparsityPattern intersectionSparsityPattern;
for (const auto& entry : sparsityPattern) {
@@ -1097,30 +1032,32 @@ namespace gridfire::engine {
}
// --- Check cache for existing subset ---
if (!m_jacobianSubsetCache.contains(sparsity_hash)) {
m_jacobianSubsetCache.emplace(sparsity_hash, CppAD_sparsity_pattern);
m_jac_work.clear();
if (!state->jacobianSubsetCache.contains(sparsity_hash)) {
state->jacobianSubsetCache.emplace(sparsity_hash, CppAD_sparsity_pattern);
state->jac_work.clear();
} else {
if (m_jacWorkCache.contains(sparsity_hash)) {
m_jac_work.clear();
m_jac_work = m_jacWorkCache.at(sparsity_hash);
if (state->jacWorkCache.contains(sparsity_hash)) {
state->jac_work.clear();
state->jac_work = state->jacWorkCache.at(sparsity_hash);
}
}
auto& jac_subset = m_jacobianSubsetCache.at(sparsity_hash);
m_rhsADFun.sparse_jac_rev(
auto& jac_subset = state->jacobianSubsetCache.at(sparsity_hash);
assert(state->rhsADFun.has_value() && "RHS ADFun not recorded before Jacobian generation.");
state->rhsADFun.value().sparse_jac_rev(
x,
jac_subset, // Sparse Jacobian output
m_full_jacobian_sparsity_pattern,
"cppad",
m_jac_work // Work vector for CppAD
state->jac_work // Work vector for CppAD
);
// --- Stash the now populated work vector in the cache if not already present ---
if (!m_jacWorkCache.contains(sparsity_hash)) {
m_jacWorkCache.emplace(sparsity_hash, m_jac_work);
if (!state->jacWorkCache.contains(sparsity_hash)) {
state->jacWorkCache.emplace(sparsity_hash, state->jac_work);
}
Eigen::SparseMatrix<double> jacobianMatrix(numSpecies, numSpecies);
Eigen::SparseMatrix<double> jacobianMatrix(static_cast<long>(numSpecies), static_cast<long>(numSpecies));
std::vector<Eigen::Triplet<double> > triplets;
for (size_t k = 0; k < nnz; ++k) {
const size_t row = jac_subset.row()[k];
@@ -1142,20 +1079,10 @@ namespace gridfire::engine {
return jac;
}
std::unordered_map<fourdst::atomic::Species, int> GraphEngine::getNetReactionStoichiometry(
const reaction::Reaction &reaction
) {
return reaction.stoichiometry();
}
int GraphEngine::getStoichiometryMatrixEntry(
const fourdst::atomic::Species& species,
const reaction::Reaction &reaction
void GraphEngine::exportToDot(
scratch::StateBlob& ctx,
const std::string &filename
) const {
return reaction.stoichiometry(species);
}
void GraphEngine::exportToDot(const std::string &filename) const {
LOG_TRACE_L1(m_logger, "Exporting network graph to DOT file: {}", filename);
std::ofstream dotFile(filename);
@@ -1203,7 +1130,10 @@ namespace gridfire::engine {
LOG_TRACE_L1(m_logger, "Successfully exported network to {}", filename);
}
void GraphEngine::exportToCSV(const std::string &filename) const {
void GraphEngine::exportToCSV(
scratch::StateBlob& ctx,
const std::string &filename
) const {
LOG_TRACE_L1(m_logger, "Exporting network graph to CSV file: {}", filename);
std::ofstream csvFile(filename, std::ios::out | std::ios::trunc);
@@ -1241,14 +1171,16 @@ namespace gridfire::engine {
}
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
return getSpeciesTimescales(comp, T9, rho, m_reactions);
return getSpeciesTimescales(ctx, comp, T9, rho, m_reactions);
}
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
@@ -1287,14 +1219,16 @@ namespace gridfire::engine {
}
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesDestructionTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
return getSpeciesDestructionTimescales(comp, T9, rho, m_reactions);
return getSpeciesDestructionTimescales(ctx, comp, T9, rho, m_reactions);
}
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesDestructionTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
@@ -1337,7 +1271,10 @@ namespace gridfire::engine {
return speciesDestructionTimescales;
}
fourdst::composition::Composition GraphEngine::update(const NetIn &netIn) {
fourdst::composition::Composition GraphEngine::project(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
fourdst::composition::Composition baseUpdatedComposition = netIn.composition;
for (const auto& species : m_networkSpecies) {
if (!netIn.composition.contains(species)) {
@@ -1347,11 +1284,8 @@ namespace gridfire::engine {
return baseUpdatedComposition;
}
bool GraphEngine::isStale(const NetIn &netIn) {
return false;
}
void GraphEngine::recordADTape() {
void GraphEngine::recordADTape() const {
LOG_TRACE_L1(m_logger, "Recording AD tape for the RHS calculation...");
// Task 1: Set dimensions and initialize the matrix
@@ -1415,13 +1349,14 @@ namespace gridfire::engine {
);
dependentVector.push_back(result.nuclearEnergyGenerationRate);
m_rhsADFun.Dependent(adInput, dependentVector);
m_rhsADFun.optimize();
m_authoritativeADFun.Dependent(adInput, dependentVector);
m_authoritativeADFun.optimize();
LOG_TRACE_L1(m_logger, "AD tape recorded successfully for the RHS and Eps calculation. Number of independent variables: {}.", adInput.size());
}
void GraphEngine::collectAtomicReverseRateAtomicBases() {
void GraphEngine::collectAtomicReverseRateAtomicBases(
) {
m_atomicReverseRates.clear();
m_atomicReverseRates.reserve(m_reactions.size());
@@ -1434,7 +1369,7 @@ namespace gridfire::engine {
}
}
void GraphEngine::precomputeNetwork() {
void GraphEngine::precomputeNetwork() {
LOG_TRACE_L1(m_logger, "Pre-computing constant components of GraphNetwork state...");
// --- Reverse map for fast species lookups ---
@@ -1443,10 +1378,10 @@ namespace gridfire::engine {
speciesIndexMap[m_networkSpecies[i]] = i;
}
m_precomputedReactions.clear();
m_precomputedReactions.reserve(m_reactions.size());
m_precomputedReactionIndexMap.clear();
m_precomputedReactionIndexMap.reserve(m_reactions.size());
m_precomputed_reactions.clear();
m_precomputed_reactions.reserve(m_reactions.size());
m_precomputed_reaction_index_map.clear();
m_precomputed_reaction_index_map.reserve(m_reactions.size());
for (size_t i = 0; i < m_reactions.size(); ++i) {
const auto& reaction = m_reactions[i];
@@ -1456,7 +1391,7 @@ namespace gridfire::engine {
uint64_t reactionHash = reaction.hash(0);
precomp.reaction_hash = reactionHash;
m_precomputedReactionIndexMap[reactionHash] = i;
m_precomputed_reaction_index_map[reactionHash] = i;
// --- Precompute forward reaction information ---
// Count occurrences for each reactant to determine powers and symmetry
@@ -1506,7 +1441,7 @@ namespace gridfire::engine {
precomp.stoichiometric_coefficients.push_back(coeff);
}
m_precomputedReactions.push_back(std::move(precomp));
m_precomputed_reactions.push_back(std::move(precomp));
}
LOG_TRACE_L1(m_logger, "Pre-computation complete. Precomputed data for {} reactions.", m_precomputedReactions.size());
}
@@ -1523,6 +1458,7 @@ namespace gridfire::engine {
if ( p != 0) { return false; }
const double T9 = tx[0];
// 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.
const fourdst::composition::Composition dummyComp;
@@ -1612,68 +1548,4 @@ namespace gridfire::engine {
return true;
}
#ifdef GRIDFIRE_USE_OPENMP
GraphEngine::PrecomputationKernelResults GraphEngine::accumulate_flows_parallel(
const std::vector<double> &local_abundances,
const std::vector<double> &screening_factors,
const std::vector<double> &bare_rates,
const std::vector<double> &bare_reverse_rates,
const double rho,
const reaction::ReactionSet &activeReactions
) const {
int n_threads = omp_get_max_threads();
std::vector<std::vector<double>> thread_local_dydt(n_threads, std::vector<double>(m_networkSpecies.size(), 0.0));
double total_neutrino_energy_loss_rate = 0.0;
double total_neutrino_flux = 0.0;
#pragma omp parallel for schedule(static) reduction(+:total_neutrino_energy_loss_rate, total_neutrino_flux)
for (size_t k = 0; k < activeReactions.size(); ++k) {
int t_id = omp_get_thread_num();
const auto& reaction = activeReactions[k];
const size_t reactionIndex = m_precomputedReactionIndexMap.at(reaction.hash(0));
const PrecomputedReaction& precomputedReaction = m_precomputedReactions[reactionIndex];
double netFlow = compute_reaction_flow(
local_abundances,
screening_factors,
bare_rates,
bare_reverse_rates,
rho,
reactionIndex,
reaction,
reactionIndex,
precomputedReaction
);
auto [neutrinoEnergyLossRate, neutrinoFlux] = compute_neutrino_fluxes(
netFlow,
reaction
);
total_neutrino_energy_loss_rate += neutrinoEnergyLossRate;
total_neutrino_flux += neutrinoFlux;
for (size_t i = 0; i < precomputedReaction.affected_species_indices.size(); ++i) {
thread_local_dydt[t_id][precomputedReaction.affected_species_indices[i]] +=
netFlow * precomputedReaction.stoichiometric_coefficients[i];
}
}
PrecomputationKernelResults results;
results.total_neutrino_energy_loss_rate = total_neutrino_energy_loss_rate;
results.total_neutrino_flux = total_neutrino_flux;
results.dydt_vector.resize(m_networkSpecies.size(), 0.0);
#pragma omp parallel for schedule(static)
for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
double sum = 0.0;
for (int t = 0; t < n_threads; ++t) sum += thread_local_dydt[t][i];
results.dydt_vector[i] = sum;
}
return results;
}
#endif
}

View File

@@ -9,6 +9,9 @@
#include "gridfire/types/types.h"
#include "gridfire/exceptions/error_solver.h"
#include "gridfire/engine/scratchpads/blob.h"
#include "gridfire/engine/scratchpads/engine_graph_scratchpad.h"
#include "fourdst/logging/logging.h"
#include "gridfire/solver/strategies/CVODE_solver_strategy.h"
#include "quill/Logger.h"
@@ -20,12 +23,12 @@ namespace gridfire::engine {
using fourdst::atomic::Species;
PrimingReport primeNetwork(
const NetIn& netIn,
GraphEngine& engine,
const std::optional<std::vector<reaction::ReactionType>>& ignoredReactionTypes
scratch::StateBlob &ctx,
const NetIn& netIn,
const GraphEngine& engine, const std::optional<std::vector<reaction::ReactionType>>& ignoredReactionTypes
) {
const auto logger = LogManager::getInstance().getLogger("log");
solver::CVODESolverStrategy integrator(engine);
solver::CVODESolverStrategy integrator(engine, ctx);
// Do not need high precision for priming
integrator.set_absTol(1e-3);
@@ -70,7 +73,7 @@ namespace gridfire::engine {
minAbundance = y;
}
}
double abundanceForUnprimedSpecies = minAbundance / 1e10;
const double abundanceForUnprimedSpecies = minAbundance / 1e10;
for (const auto& sp : unprimedSpecies) {
LOG_TRACE_L1(logger, "Clamping Species {}: initial abundance {}, primed abundance {} to {}", sp.name(), netIn.composition.getMolarAbundance(sp), report.primedComposition.getMolarAbundance(sp), abundanceForUnprimedSpecies);
report.primedComposition.setMolarAbundance(sp, abundanceForUnprimedSpecies);

View File

@@ -9,6 +9,10 @@
#include "gridfire/exceptions/error_engine.h"
#include "gridfire/utils/hashing.h"
#include "gridfire/engine/scratchpads/blob.h"
#include "gridfire/engine/scratchpads/utils.h"
#include "gridfire/engine/scratchpads/engine_adaptive_scratchpad.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
@@ -17,23 +21,24 @@ namespace gridfire::engine {
AdaptiveEngineView::AdaptiveEngineView(
DynamicEngine &baseEngine
) :
m_baseEngine(baseEngine),
m_activeSpecies(baseEngine.getNetworkSpecies()),
m_activeReactions(baseEngine.getNetworkReactions())
{}
m_baseEngine(baseEngine) {}
fourdst::composition::Composition AdaptiveEngineView::update(const NetIn &netIn) {
m_activeReactions.clear();
m_activeSpecies.clear();
fourdst::composition::Composition AdaptiveEngineView::project(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
auto *state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
state->active_reactions.clear();
state->active_species.clear();
fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.update(netIn);
fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.project(ctx, netIn);
NetIn updatedNetIn = netIn;
updatedNetIn.composition = baseUpdatedComposition;
LOG_TRACE_L1(m_logger, "Updating AdaptiveEngineView with new network input...");
auto [allFlows, composition] = calculateAllReactionFlows(updatedNetIn);
auto [allFlows, composition] = calculateAllReactionFlows(ctx, updatedNetIn);
double maxFlow = 0.0;
@@ -44,51 +49,50 @@ namespace gridfire::engine {
}
LOG_DEBUG(m_logger, "Maximum reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", maxFlow);
const std::unordered_set<Species> reachableSpecies = findReachableSpecies(updatedNetIn);
const std::unordered_set<Species> reachableSpecies = findReachableSpecies(ctx, updatedNetIn);
LOG_DEBUG(m_logger, "Found {} reachable species in adaptive engine view.", reachableSpecies.size());
const std::vector<const reaction::Reaction*> finalReactions = cullReactionsByFlow(allFlows, reachableSpecies, composition, maxFlow);
const std::vector<const reaction::Reaction*> finalReactions = cullReactionsByFlow(ctx, allFlows, reachableSpecies, composition, maxFlow);
finalizeActiveSet(finalReactions);
finalizeActiveSet(ctx, finalReactions);
auto [rescuedReactions, rescuedSpecies] = rescueEdgeSpeciesDestructionChannel(composition, netIn.temperature/1e9, netIn.density, m_activeSpecies, m_activeReactions);
auto [rescuedReactions, rescuedSpecies] = rescueEdgeSpeciesDestructionChannel(
ctx,
composition,
netIn.temperature/1e9,
netIn.density
);
for (const auto& reactionPtr : rescuedReactions) {
m_activeReactions.add_reaction(*reactionPtr);
state->active_reactions.add_reaction(*reactionPtr);
}
for (const auto& species : rescuedSpecies) {
if (!std::ranges::contains(m_activeSpecies, species) && m_baseEngine.getSpeciesStatus(species) == SpeciesStatus::ACTIVE) {
m_activeSpecies.push_back(species);
if (!std::ranges::contains(state->active_species, species) && m_baseEngine.getSpeciesStatus(ctx, species) == SpeciesStatus::ACTIVE) {
state->active_species.push_back(species);
}
}
m_isStale = false;
LOG_INFO(m_logger, "AdaptiveEngineView updated successfully with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
LOG_INFO(m_logger, "AdaptiveEngineView updated successfully with {} active species and {} active reactions.", state->active_species.size(), state->active_reactions.size());
return updatedNetIn.composition;
}
bool AdaptiveEngineView::isStale(const NetIn &netIn) {
return m_isStale || m_baseEngine.isStale(netIn);
}
const std::vector<Species> & AdaptiveEngineView::getNetworkSpecies() const {
return m_activeSpecies;
const std::vector<Species> & AdaptiveEngineView::getNetworkSpecies(scratch::StateBlob& ctx) const {
return scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx)->active_species;
}
std::expected<StepDerivatives<double>, EngineStatus> AdaptiveEngineView::calculateRHSAndEnergy(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho, bool trust
) const {
LOG_TRACE_L2(m_logger, "Calculating RHS and Energy in AdaptiveEngineView at T9 = {}, rho = {}.", T9, rho);
validateState();
const fourdst::composition::Composition collectedComp = collectComposition(comp, T9, rho);
const fourdst::composition::Composition collectedComp = collectComposition(ctx, comp, T9, rho);
auto result = m_baseEngine.calculateRHSAndEnergy(collectedComp, T9, rho, true);
auto result = m_baseEngine.calculateRHSAndEnergy(ctx, collectedComp, T9, rho, true);
LOG_TRACE_L2(m_logger, "Base engine calculation of RHS and Energy complete.");
if (!result) {
@@ -100,99 +104,89 @@ namespace gridfire::engine {
}
EnergyDerivatives AdaptiveEngineView::calculateEpsDerivatives(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateState();
return m_baseEngine.calculateEpsDerivatives(comp, T9, rho);
return m_baseEngine.calculateEpsDerivatives(ctx, comp, T9, rho);
}
NetworkJacobian AdaptiveEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
return generateJacobianMatrix(comp, T9, rho, m_activeSpecies);
const auto *state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
return generateJacobianMatrix(ctx, comp, T9, rho, state->active_species);
}
NetworkJacobian AdaptiveEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const std::vector<Species> &activeSpecies
) const {
validateState();
return m_baseEngine.generateJacobianMatrix(comp, T9, rho, activeSpecies);
const auto *state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
return m_baseEngine.generateJacobianMatrix(ctx, comp, T9, rho, state->active_species);
}
NetworkJacobian AdaptiveEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const SparsityPattern &sparsityPattern
) const {
validateState();
return m_baseEngine.generateJacobianMatrix(comp, T9, rho, sparsityPattern);
}
void AdaptiveEngineView::generateStoichiometryMatrix() {
validateState();
m_baseEngine.generateStoichiometryMatrix();
}
int AdaptiveEngineView::getStoichiometryMatrixEntry(
const Species &species,
const reaction::Reaction& reaction
) const {
validateState();
return m_baseEngine.getStoichiometryMatrixEntry(species, reaction);
return m_baseEngine.generateJacobianMatrix(ctx, comp, T9, rho, sparsityPattern);
}
double AdaptiveEngineView::calculateMolarReactionFlow(
scratch::StateBlob& ctx,
const reaction::Reaction &reaction,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateState();
if (!m_activeReactions.contains(reaction)) {
const auto *state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
if (!state->active_reactions.contains(reaction)) {
LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the adaptive engine view.", reaction.id());
m_logger -> flush_log();
throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
}
return m_baseEngine.calculateMolarReactionFlow(reaction, comp, T9, rho);
return m_baseEngine.calculateMolarReactionFlow(ctx, reaction, comp, T9, rho);
}
const reaction::ReactionSet & AdaptiveEngineView::getNetworkReactions() const {
return m_activeReactions;
}
void AdaptiveEngineView::setNetworkReactions(const reaction::ReactionSet &reactions) {
LOG_CRITICAL(m_logger, "AdaptiveEngineView does not support setting network reactions directly. Use update() with NetIn instead. Perhaps you meant to call this on the base engine?");
throw exceptions::UnableToSetNetworkReactionsError("AdaptiveEngineView does not support setting network reactions directly. Use update() with NetIn instead. Perhaps you meant to call this on the base engine?");
const reaction::ReactionSet & AdaptiveEngineView::getNetworkReactions(
scratch::StateBlob& ctx
) const {
return scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx) -> active_reactions;
}
std::expected<std::unordered_map<Species, double>, EngineStatus> AdaptiveEngineView::getSpeciesTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateState();
const auto result = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
const auto result = m_baseEngine.getSpeciesTimescales(ctx, comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
const auto* state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
const std::unordered_map<Species, double>& fullTimescales = result.value();
std::unordered_map<Species, double> culledTimescales;
culledTimescales.reserve(m_activeSpecies.size());
for (const auto& active_species : m_activeSpecies) {
culledTimescales.reserve(state->active_species.size());
for (const auto& active_species : state->active_species) {
if (fullTimescales.contains(active_species)) {
culledTimescales[active_species] = fullTimescales.at(active_species);
}
@@ -202,21 +196,23 @@ namespace gridfire::engine {
}
std::expected<std::unordered_map<Species, double>, EngineStatus> AdaptiveEngineView::getSpeciesDestructionTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateState();
const auto result = m_baseEngine.getSpeciesDestructionTimescales(comp, T9, rho);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(ctx, comp, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
const auto* state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
const std::unordered_map<Species, double>& destructionTimescales = result.value();
std::unordered_map<Species, double> culledTimescales;
culledTimescales.reserve(m_activeSpecies.size());
for (const auto& active_species : m_activeSpecies) {
culledTimescales.reserve(state->active_species.size());
for (const auto& active_species : state->active_species) {
if (destructionTimescales.contains(active_species)) {
culledTimescales[active_species] = destructionTimescales.at(active_species);
}
@@ -224,34 +220,29 @@ namespace gridfire::engine {
return culledTimescales;
}
void AdaptiveEngineView::setScreeningModel(const screening::ScreeningType model) {
m_baseEngine.setScreeningModel(model);
screening::ScreeningType AdaptiveEngineView::getScreeningModel(
scratch::StateBlob& ctx
) const {
return m_baseEngine.getScreeningModel(ctx);
}
screening::ScreeningType AdaptiveEngineView::getScreeningModel() const {
return m_baseEngine.getScreeningModel();
}
std::vector<double> AdaptiveEngineView::mapNetInToMolarAbundanceVector(const NetIn &netIn) const {
std::vector<double> Y(m_activeSpecies.size(), 0.0); // Initialize with zeros
for (const auto& [species, y] : netIn.composition) {
Y[getSpeciesIndex(species)] = y; // Map species to their molar abundance
}
return Y; // Return the vector of molar abundances
}
PrimingReport AdaptiveEngineView::primeEngine(const NetIn &netIn) {
return m_baseEngine.primeEngine(netIn);
PrimingReport AdaptiveEngineView::primeEngine(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
return m_baseEngine.primeEngine(ctx, netIn);
}
fourdst::composition::Composition AdaptiveEngineView::collectComposition(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
fourdst::composition::Composition result = m_baseEngine.collectComposition(comp, T9, rho);
const auto* state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
fourdst::composition::Composition result = m_baseEngine.collectComposition(ctx, comp, T9, rho);
for (const auto& species : m_activeSpecies) {
for (const auto& species : state->active_species) {
if (!result.contains(species)) {
result.registerSpecies(species);
}
@@ -260,18 +251,32 @@ namespace gridfire::engine {
return result;
}
SpeciesStatus AdaptiveEngineView::getSpeciesStatus(const fourdst::atomic::Species &species) const {
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(species);
if (status == SpeciesStatus::ACTIVE && std::ranges::find(m_activeSpecies, species) == m_activeSpecies.end()) {
SpeciesStatus AdaptiveEngineView::getSpeciesStatus(
scratch::StateBlob& ctx,
const Species &species
) const {
const auto* state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(ctx, species);
if (status == SpeciesStatus::ACTIVE && std::ranges::find(state->active_species, species) == state->active_species.end()) {
return SpeciesStatus::INACTIVE_FLOW;
}
return status;
}
size_t AdaptiveEngineView::getSpeciesIndex(const fourdst::atomic::Species &species) const {
const auto it = std::ranges::find(m_activeSpecies, species);
if (it != m_activeSpecies.end()) {
return static_cast<int>(std::distance(m_activeSpecies.begin(), it));
std::optional<StepDerivatives<double>> AdaptiveEngineView::getMostRecentRHSCalculation(
scratch::StateBlob &ctx
) const {
return m_baseEngine.getMostRecentRHSCalculation(ctx);
}
size_t AdaptiveEngineView::getSpeciesIndex(
scratch::StateBlob& ctx,
const Species &species
) const {
const auto *state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
const auto it = std::ranges::find(state->active_species, species);
if (it != state->active_species.end()) {
return static_cast<int>(std::distance(state->active_species.begin(), it));
} else {
LOG_ERROR(m_logger, "Species '{}' not found in active species list.", species.name());
m_logger->flush_log();
@@ -279,18 +284,11 @@ namespace gridfire::engine {
}
}
void AdaptiveEngineView::validateState() const {
if (m_isStale) {
LOG_ERROR(m_logger, "AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
m_logger->flush_log();
throw std::runtime_error("AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
}
}
std::pair<std::vector<AdaptiveEngineView::ReactionFlow>, fourdst::composition::Composition> AdaptiveEngineView::calculateAllReactionFlows(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies(ctx);
fourdst::composition::Composition composition = netIn.composition;
for (const auto& species: fullSpeciesList) {
@@ -304,10 +302,10 @@ namespace gridfire::engine {
const double rho = netIn.density; // Density in g/cm^3
std::vector<ReactionFlow> reactionFlows;
const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
const auto& fullReactionSet = m_baseEngine.getNetworkReactions(ctx);
reactionFlows.reserve(fullReactionSet.size());
for (const auto& reaction : fullReactionSet) {
const double flow = m_baseEngine.calculateMolarReactionFlow(*reaction, composition, T9, rho);
const double flow = m_baseEngine.calculateMolarReactionFlow(ctx, *reaction, composition, T9, rho);
reactionFlows.push_back({reaction.get(), flow});
LOG_TRACE_L3(m_logger, "Reaction '{}' has flow rate: {:0.3E} [mol/s/g]", reaction->id(), flow);
}
@@ -315,13 +313,14 @@ namespace gridfire::engine {
}
std::unordered_set<Species> AdaptiveEngineView::findReachableSpecies(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
std::unordered_set<Species> reachable;
std::queue<Species> to_vist;
constexpr double ABUNDANCE_FLOOR = 1e-12; // Abundance floor for a species to be considered part of the initial fuel
for (const auto& species: m_baseEngine.getNetworkSpecies()) {
for (const auto& species: m_baseEngine.getNetworkSpecies(ctx)) {
if (netIn.composition.contains(species) && netIn.composition.getMassFraction(std::string(species.name())) > ABUNDANCE_FLOOR) {
if (!reachable.contains(species)) {
to_vist.push(species);
@@ -334,7 +333,7 @@ namespace gridfire::engine {
bool new_species_found_in_pass = true;
while (new_species_found_in_pass) {
new_species_found_in_pass = false;
for (const auto& reaction: m_baseEngine.getNetworkReactions()) {
for (const auto& reaction: m_baseEngine.getNetworkReactions(ctx)) {
bool all_reactants_reachable = true;
for (const auto& reactant: reaction->reactants()) {
if (!reachable.contains(reactant)) {
@@ -358,6 +357,7 @@ namespace gridfire::engine {
}
std::vector<const reaction::Reaction *> AdaptiveEngineView::cullReactionsByFlow(
scratch::StateBlob& ctx,
const std::vector<ReactionFlow> &allFlows,
const std::unordered_set<fourdst::atomic::Species> &reachableSpecies,
const fourdst::composition::Composition &comp,
@@ -401,21 +401,22 @@ namespace gridfire::engine {
}
AdaptiveEngineView::RescueSet AdaptiveEngineView::rescueEdgeSpeciesDestructionChannel(
scratch::StateBlob& ctx,
const fourdst::composition::Composition &comp,
const double T9,
const double rho,
const std::vector<Species> &activeSpecies,
const reaction::ReactionSet &activeReactions
const double rho
) const {
const auto result = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
const auto result = m_baseEngine.getSpeciesTimescales(ctx, comp, T9, rho);
if (!result) {
LOG_CRITICAL(m_logger, "Failed to get species timescales due to base engine failure");
m_logger->flush_log();
throw exceptions::EngineError("Failed to get species timescales due base engine failure");
}
const auto* state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
std::unordered_map<Species, double> timescales = result.value();
std::set<Species> onlyProducedSpecies;
for (const auto& reaction : activeReactions) {
for (const auto& reaction : state->active_reactions) {
const std::vector<Species>& products = reaction->products();
onlyProducedSpecies.insert(products.begin(), products.end());
}
@@ -424,7 +425,7 @@ namespace gridfire::engine {
std::erase_if(
onlyProducedSpecies,
[&](const Species &species) {
for (const auto& reaction : activeReactions) {
for (const auto& reaction : state->active_reactions) {
if (reaction->contains_reactant(species)) {
return true; // If any active reaction consumes the species then erase it from the set.
}
@@ -444,14 +445,14 @@ namespace gridfire::engine {
std::unordered_map<Species, const reaction::Reaction*> reactionsToRescue;
for (const auto& species : onlyProducedSpecies) {
double maxSpeciesConsumptionRate = 0.0;
for (const auto& reaction : m_baseEngine.getNetworkReactions()) {
for (const auto& reaction : m_baseEngine.getNetworkReactions(ctx)) {
const bool speciesToCheckIsConsumed = reaction->contains_reactant(species);
if (!speciesToCheckIsConsumed) {
continue; // If the species is not consumed by this reaction, skip it.
}
bool allOtherReactantsAreAvailable = true;
for (const auto& reactant : reaction->reactants()) {
const bool reactantIsAvailable = std::ranges::contains(activeSpecies, reactant);
const bool reactantIsAvailable = std::ranges::contains(state->active_species, reactant);
if (!reactantIsAvailable && reactant != species) {
allOtherReactantsAreAvailable = false;
}
@@ -547,31 +548,33 @@ namespace gridfire::engine {
}
void AdaptiveEngineView::finalizeActiveSet(
scratch::StateBlob& ctx,
const std::vector<const reaction::Reaction *> &finalReactions
) {
) const {
auto* state = scratch::get_state<scratch::AdaptiveEngineViewScratchPad, true>(ctx);
std::unordered_set<Species>finalSpeciesSet;
m_activeReactions.clear();
state->active_reactions.clear();
for (const auto* reactionPtr: finalReactions) {
m_activeReactions.add_reaction(*reactionPtr);
state->active_reactions.add_reaction(*reactionPtr);
for (const auto& reactant : reactionPtr->reactants()) {
const SpeciesStatus reactantStatus = m_baseEngine.getSpeciesStatus(reactant);
const SpeciesStatus reactantStatus = m_baseEngine.getSpeciesStatus(ctx, reactant);
if (!finalSpeciesSet.contains(reactant) && (reactantStatus == SpeciesStatus::ACTIVE || reactantStatus == SpeciesStatus::EQUILIBRIUM)) {
LOG_TRACE_L3(m_logger, "Adding reactant '{}' to active species set through reaction {}.", reactant.name(), reactionPtr->id());
finalSpeciesSet.insert(reactant);
}
}
for (const auto& product : reactionPtr->products()) {
const SpeciesStatus productStatus = m_baseEngine.getSpeciesStatus(product);
const SpeciesStatus productStatus = m_baseEngine.getSpeciesStatus(ctx, product);
if (!finalSpeciesSet.contains(product) && (productStatus == SpeciesStatus::ACTIVE || productStatus == SpeciesStatus::EQUILIBRIUM)) {
LOG_TRACE_L3(m_logger, "Adding product '{}' to active species set through reaction {}.", product.name(), reactionPtr->id());
finalSpeciesSet.insert(product);
}
}
}
m_activeSpecies.clear();
m_activeSpecies = std::vector<Species>(finalSpeciesSet.begin(), finalSpeciesSet.end());
state->active_species.clear();
state->active_species = std::vector<Species>(finalSpeciesSet.begin(), finalSpeciesSet.end());
std::ranges::sort(
m_activeSpecies,
state->active_species,
[](const Species &a, const Species &b) { return a.mass() < b.mass(); }
);
}

View File

@@ -5,6 +5,10 @@
#include "fourdst/atomic/atomicSpecies.h"
#include "fourdst/composition/decorators/composition_masked.h"
#include "gridfire/engine/scratchpads/blob.h"
#include "gridfire/engine/scratchpads/engine_defined_scratchpad.h"
#include "gridfire/engine/scratchpads/utils.h"
#include "quill/LogMacros.h"
#include <string>
@@ -23,30 +27,34 @@ namespace gridfire::engine {
GraphEngine& baseEngine
) :
m_baseEngine(baseEngine) {
collect(peNames);
// collect(peNames);
}
const DynamicEngine & DefinedEngineView::getBaseEngine() const {
return m_baseEngine;
}
const std::vector<Species> & DefinedEngineView::getNetworkSpecies() const {
if (m_activeSpeciesVectorCache.has_value()) {
return m_activeSpeciesVectorCache.value();
const std::vector<Species> & DefinedEngineView::getNetworkSpecies(
scratch::StateBlob& ctx
) const {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
if (state->active_species_vector_cache.has_value()) {
return state->active_species_vector_cache.value();
}
m_activeSpeciesVectorCache = std::vector<Species>(m_activeSpecies.begin(), m_activeSpecies.end());
return m_activeSpeciesVectorCache.value();
state->active_species_vector_cache = std::vector<Species>(state->active_species.begin(), state->active_species.end());
return state->active_species_vector_cache.value();
}
std::expected<StepDerivatives<double>, EngineStatus> DefinedEngineView::calculateRHSAndEnergy(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho, bool trust
) const {
validateNetworkState();
auto *state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies | std::ranges::to<std::vector>());
const auto result = m_baseEngine.calculateRHSAndEnergy(masked, T9, rho, m_activeReactions);
const fourdst::composition::MaskedComposition masked(comp, state->active_species | std::ranges::to<std::vector>());
const auto result = m_baseEngine.calculateRHSAndEnergy(ctx, masked, T9, rho, state->active_reactions);
if (!result) {
return std::unexpected{result.error()};
@@ -56,37 +64,39 @@ namespace gridfire::engine {
}
EnergyDerivatives DefinedEngineView::calculateEpsDerivatives(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateNetworkState();
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
const fourdst::composition::MaskedComposition masked(comp, state->active_species | std::ranges::to<std::vector>());
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies | std::ranges::to<std::vector>());
return m_baseEngine.calculateEpsDerivatives(masked, T9, rho, m_activeReactions);
return m_baseEngine.calculateEpsDerivatives(ctx, masked, T9, rho, state->active_reactions);
}
NetworkJacobian DefinedEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateNetworkState();
if (!m_activeSpeciesVectorCache.has_value()) {
m_activeSpeciesVectorCache = std::vector<Species>(m_activeSpecies.begin(), m_activeSpecies.end());
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
if (!state->active_species_vector_cache.has_value()) {
state->active_species_vector_cache = std::vector<Species>(state->active_species.begin(), state->active_species.end());
}
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies | std::ranges::to<std::vector>());
return m_baseEngine.generateJacobianMatrix(masked, T9, rho, m_activeSpeciesVectorCache.value());
const fourdst::composition::MaskedComposition masked(comp, state->active_species | std::ranges::to<std::vector>());
return m_baseEngine.generateJacobianMatrix(ctx, masked, T9, rho, state->active_species_vector_cache.value());
}
NetworkJacobian DefinedEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const std::vector<fourdst::atomic::Species> &activeSpecies
const std::vector<Species> &activeSpecies
) const {
validateNetworkState();
const std::set<fourdst::atomic::Species> activeSpeciesSet(
activeSpecies.begin(),
@@ -94,96 +104,65 @@ namespace gridfire::engine {
);
const fourdst::composition::MaskedComposition masked(comp, activeSpeciesSet | std::ranges::to<std::vector>());
return m_baseEngine.generateJacobianMatrix(masked, T9, rho, activeSpecies);
return m_baseEngine.generateJacobianMatrix(ctx, masked, T9, rho, activeSpecies);
}
NetworkJacobian DefinedEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const SparsityPattern &sparsityPattern
) const {
validateNetworkState();
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies | std::ranges::to<std::vector>());
return m_baseEngine.generateJacobianMatrix(masked, T9, rho, sparsityPattern);
}
void DefinedEngineView::generateStoichiometryMatrix() {
validateNetworkState();
m_baseEngine.generateStoichiometryMatrix();
}
int DefinedEngineView::getStoichiometryMatrixEntry(
const Species& species,
const reaction::Reaction& reaction
) const {
validateNetworkState();
if (!m_activeSpecies.contains(species)) {
LOG_ERROR(m_logger, "Species '{}' is not part of the active species in the DefinedEngineView.", species.name());
m_logger -> flush_log();
throw std::runtime_error("Species not found in active species: " + std::string(species.name()));
}
if (!m_activeReactions.contains(reaction)) {
LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the DefinedEngineView.", reaction.id());
m_logger -> flush_log();
throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
}
return m_baseEngine.getStoichiometryMatrixEntry(species, reaction);
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
const fourdst::composition::MaskedComposition masked(comp, state->active_species | std::ranges::to<std::vector>());
return m_baseEngine.generateJacobianMatrix(ctx, masked, T9, rho, sparsityPattern);
}
double DefinedEngineView::calculateMolarReactionFlow(
scratch::StateBlob& ctx,
const reaction::Reaction &reaction,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateNetworkState();
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
if (!m_activeReactions.contains(reaction)) {
if (!state->active_reactions.contains(reaction)) {
LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the DefinedEngineView.", reaction.id());
m_logger -> flush_log();
throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
}
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies | std::ranges::to<std::vector>());
return m_baseEngine.calculateMolarReactionFlow(reaction, masked, T9, rho);
const fourdst::composition::MaskedComposition masked(comp, state->active_species | std::ranges::to<std::vector>());
return m_baseEngine.calculateMolarReactionFlow(ctx, reaction, masked, T9, rho);
}
const reaction::ReactionSet & DefinedEngineView::getNetworkReactions() const {
validateNetworkState();
const reaction::ReactionSet & DefinedEngineView::getNetworkReactions(
scratch::StateBlob& ctx
) const {
return m_activeReactions;
}
void DefinedEngineView::setNetworkReactions(const reaction::ReactionSet &reactions) {
std::vector<std::string> peNames;
for (const auto& reaction : reactions) {
peNames.emplace_back(reaction->id());
}
collect(peNames);
m_activeSpeciesVectorCache = std::nullopt; // Invalidate species vector cache
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
return state->active_reactions;
}
std::expected<std::unordered_map<Species, double>, EngineStatus> DefinedEngineView::getSpeciesTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateNetworkState();
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies | std::ranges::to<std::vector>());
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
const fourdst::composition::MaskedComposition masked(comp, state->active_species | std::ranges::to<std::vector>());
const auto result = m_baseEngine.getSpeciesTimescales(masked, T9, rho, m_activeReactions);
const auto result = m_baseEngine.getSpeciesTimescales(ctx, masked, T9, rho, state->active_reactions);
if (!result) {
return std::unexpected{result.error()};
}
const auto& fullTimescales = result.value();
std::unordered_map<Species, double> definedTimescales;
for (const auto& active_species : m_activeSpecies) {
for (const auto& active_species : state->active_species) {
if (fullTimescales.contains(active_species)) {
definedTimescales[active_species] = fullTimescales.at(active_species);
}
@@ -192,14 +171,15 @@ namespace gridfire::engine {
}
std::expected<std::unordered_map<Species, double>, EngineStatus> DefinedEngineView::getSpeciesDestructionTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
validateNetworkState();
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies | std::ranges::to<std::vector>());
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
const fourdst::composition::MaskedComposition masked(comp, state->active_species| std::ranges::to<std::vector>());
const auto result = m_baseEngine.getSpeciesDestructionTimescales(masked, T9, rho, m_activeReactions);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(ctx, masked, T9, rho, state->active_reactions);
if (!result) {
return std::unexpected{result.error()};
@@ -208,7 +188,7 @@ namespace gridfire::engine {
const auto& destructionTimescales = result.value();
std::unordered_map<Species, double> definedTimescales;
for (const auto& active_species : m_activeSpecies) {
for (const auto& active_species : state->active_species){
if (destructionTimescales.contains(active_species)) {
definedTimescales[active_species] = destructionTimescales.at(active_species);
}
@@ -216,29 +196,28 @@ namespace gridfire::engine {
return definedTimescales;
}
fourdst::composition::Composition DefinedEngineView::update(const NetIn &netIn) {
return m_baseEngine.update(netIn);
fourdst::composition::Composition DefinedEngineView::project(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
return m_baseEngine.project(ctx, netIn);
}
bool DefinedEngineView::isStale(const NetIn &netIn) {
return m_baseEngine.isStale(netIn);
screening::ScreeningType DefinedEngineView::getScreeningModel(
scratch::StateBlob& ctx
) const {
return m_baseEngine.getScreeningModel(ctx);
}
void DefinedEngineView::setScreeningModel(const screening::ScreeningType model) {
m_baseEngine.setScreeningModel(model);
}
size_t DefinedEngineView::getSpeciesIndex(
scratch::StateBlob& ctx,
const Species &species
) const {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
screening::ScreeningType DefinedEngineView::getScreeningModel() const {
return m_baseEngine.getScreeningModel();
}
size_t DefinedEngineView::getSpeciesIndex(const Species &species) const {
// TODO: We are working to phase out all of these methods, its probably broken but it also should no longer be used and will be removed soon
validateNetworkState();
const auto it = std::ranges::find(m_activeSpecies, species);
if (it != m_activeSpecies.end()) {
return static_cast<int>(std::distance(m_activeSpecies.begin(), it));
const auto it = std::ranges::find(state->active_species, species);
if (it != state->active_species.end()) {
return static_cast<int>(std::distance(state->active_species.begin(), it));
} else {
LOG_ERROR(m_logger, "Species '{}' not found in active species list.", species.name());
m_logger->flush_log();
@@ -246,29 +225,23 @@ namespace gridfire::engine {
}
}
std::vector<double> DefinedEngineView::mapNetInToMolarAbundanceVector(const NetIn &netIn) const {
std::vector<double> Y(m_activeSpecies.size(), 0.0); // Initialize with zeros
for (const auto& [sp, y] : netIn.composition) {
auto it = std::ranges::find(m_activeSpecies, sp);
if (it != m_activeSpecies.end()) {
Y[getSpeciesIndex(sp)] = y; // Map species to their molar abundance
}
}
return Y; // Return the vector of molar abundances
}
PrimingReport DefinedEngineView::primeEngine(const NetIn &netIn) {
return m_baseEngine.primeEngine(netIn);
PrimingReport DefinedEngineView::primeEngine(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
return m_baseEngine.primeEngine(ctx, netIn);
}
fourdst::composition::Composition DefinedEngineView::collectComposition(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
fourdst::composition::Composition result = m_baseEngine.collectComposition(comp, T9, rho);
fourdst::composition::Composition result = m_baseEngine.collectComposition(ctx, comp, T9, rho);
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
for (const auto& species : m_activeSpecies) {
for (const auto& species : state->active_species) {
if (!result.contains(species)) {
result.registerSpecies(species);
}
@@ -276,18 +249,30 @@ namespace gridfire::engine {
return result;
}
SpeciesStatus DefinedEngineView::getSpeciesStatus(const Species &species) const {
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(species);
if (status == SpeciesStatus::ACTIVE && !m_activeSpecies.contains(species)) {
SpeciesStatus DefinedEngineView::getSpeciesStatus(
scratch::StateBlob& ctx,
const Species &species
) const {
const auto *state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(ctx, species);
if (status == SpeciesStatus::ACTIVE && !state->active_species.contains(species)) {
return SpeciesStatus::INACTIVE_FLOW;
}
return status;
}
std::vector<size_t> DefinedEngineView::constructSpeciesIndexMap() const {
std::optional<StepDerivatives<double>> DefinedEngineView::getMostRecentRHSCalculation(
scratch::StateBlob &ctx
) const {
return m_baseEngine.getMostRecentRHSCalculation(ctx);
}
std::vector<size_t> DefinedEngineView::constructSpeciesIndexMap(
scratch::StateBlob& ctx
) const {
LOG_TRACE_L3(m_logger, "Constructing species index map for DefinedEngineView...");
std::unordered_map<Species, size_t> fullSpeciesReverseMap;
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies(ctx);
fullSpeciesReverseMap.reserve(fullSpeciesList.size());
@@ -296,9 +281,10 @@ namespace gridfire::engine {
}
std::vector<size_t> speciesIndexMap;
speciesIndexMap.reserve(m_activeSpecies.size());
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
speciesIndexMap.reserve(state->active_species.size());
for (const auto& active_species : m_activeSpecies) {
for (const auto& active_species : state->active_species) {
auto it = fullSpeciesReverseMap.find(active_species);
if (it != fullSpeciesReverseMap.end()) {
speciesIndexMap.push_back(it->second);
@@ -313,12 +299,15 @@ namespace gridfire::engine {
}
std::vector<size_t> DefinedEngineView::constructReactionIndexMap() const {
std::vector<size_t> DefinedEngineView::constructReactionIndexMap(
scratch::StateBlob& ctx
) const {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
LOG_TRACE_L3(m_logger, "Constructing reaction index map for DefinedEngineView...");
// --- Step 1: Create a reverse map using the reaction's unique ID as the key. ---
std::unordered_map<std::string_view, size_t> fullReactionReverseMap;
const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
const auto& fullReactionSet = m_baseEngine.getNetworkReactions(ctx);
fullReactionReverseMap.reserve(fullReactionSet.size());
for (size_t i_full = 0; i_full < fullReactionSet.size(); ++i_full) {
@@ -327,9 +316,9 @@ namespace gridfire::engine {
// --- Step 2: Build the final index map using the active reaction set. ---
std::vector<size_t> reactionIndexMap;
reactionIndexMap.reserve(m_activeReactions.size());
reactionIndexMap.reserve(state->active_reactions.size());
for (const auto& active_reaction_ptr : m_activeReactions) {
for (const auto& active_reaction_ptr : state->active_reactions) {
auto it = fullReactionReverseMap.find(active_reaction_ptr->id());
if (it != fullReactionReverseMap.end()) {
@@ -345,54 +334,66 @@ namespace gridfire::engine {
return reactionIndexMap;
}
std::vector<double> DefinedEngineView::mapViewToFull(const std::vector<double>& culled) const {
std::vector<double> full(m_baseEngine.getNetworkSpecies().size(), 0.0);
std::vector<double> DefinedEngineView::mapViewToFull(
scratch::StateBlob& ctx,
const std::vector<double>& culled
) const {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
std::vector<double> full(m_baseEngine.getNetworkSpecies(ctx).size(), 0.0);
for (size_t i_culled = 0; i_culled < culled.size(); ++i_culled) {
const size_t i_full = m_speciesIndexMap[i_culled];
const size_t i_full = state->species_index_map[i_culled];
full[i_full] += culled[i_culled];
}
return full;
}
std::vector<double> DefinedEngineView::mapFullToView(const std::vector<double>& full) const {
std::vector<double> culled(m_activeSpecies.size(), 0.0);
for (size_t i_culled = 0; i_culled < m_activeSpecies.size(); ++i_culled) {
const size_t i_full = m_speciesIndexMap[i_culled];
std::vector<double> DefinedEngineView::mapFullToView(
scratch::StateBlob& ctx,
const std::vector<double>& full
) {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
std::vector<double> culled(state->active_species.size(), 0.0);
for (size_t i_culled = 0; i_culled < state->active_species.size(); ++i_culled) {
const size_t i_full = state->species_index_map[i_culled];
culled[i_culled] = full[i_full];
}
return culled;
}
size_t DefinedEngineView::mapViewToFullSpeciesIndex(size_t culledSpeciesIndex) const {
if (culledSpeciesIndex >= m_speciesIndexMap.size()) {
LOG_ERROR(m_logger, "Defined index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
size_t DefinedEngineView::mapViewToFullSpeciesIndex(
scratch::StateBlob& ctx,
size_t culledSpeciesIndex
) const {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
if (culledSpeciesIndex >= state->species_index_map.size()) {
LOG_ERROR(m_logger, "Defined index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, state->species_index_map.size());
m_logger->flush_log();
throw std::out_of_range("Defined index " + std::to_string(culledSpeciesIndex) + " is out of bounds for species index map of size " + std::to_string(m_speciesIndexMap.size()) + ".");
throw std::out_of_range("Defined index " + std::to_string(culledSpeciesIndex) + " is out of bounds for species index map of size " + std::to_string(state->species_index_map.size()) + ".");
}
return m_speciesIndexMap[culledSpeciesIndex];
return state->species_index_map[culledSpeciesIndex];
}
size_t DefinedEngineView::mapViewToFullReactionIndex(size_t culledReactionIndex) const {
if (culledReactionIndex >= m_reactionIndexMap.size()) {
LOG_ERROR(m_logger, "Defined index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
size_t DefinedEngineView::mapViewToFullReactionIndex(
scratch::StateBlob& ctx,
size_t culledReactionIndex
) const {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
if (culledReactionIndex >= state->reaction_index_map.size()) {
LOG_ERROR(m_logger, "Defined index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, state->reaction_index_map.size());
m_logger->flush_log();
throw std::out_of_range("Defined index " + std::to_string(culledReactionIndex) + " is out of bounds for reaction index map of size " + std::to_string(m_reactionIndexMap.size()) + ".");
throw std::out_of_range("Defined index " + std::to_string(culledReactionIndex) + " is out of bounds for reaction index map of size " + std::to_string(state->reaction_index_map.size()) + ".");
}
return m_reactionIndexMap[culledReactionIndex];
return state->reaction_index_map[culledReactionIndex];
}
void DefinedEngineView::validateNetworkState() const {
if (m_isStale) {
LOG_ERROR(m_logger, "DefinedEngineView is stale. Please call update() with a valid NetIn object.");
m_logger->flush_log();
throw std::runtime_error("DefinedEngineView is stale. Please call update() with a valid NetIn object.");
}
}
void DefinedEngineView::collect(const std::vector<std::string> &peNames) {
void DefinedEngineView::collect(
scratch::StateBlob& ctx,
const std::vector<std::string> &peNames
) const {
auto* state = scratch::get_state<scratch::DefinedEngineViewScratchPad, true>(ctx);
std::unordered_set<Species> seenSpecies;
const auto& fullNetworkReactionSet = m_baseEngine.getNetworkReactions();
const auto& fullNetworkReactionSet = m_baseEngine.getNetworkReactions(ctx);
for (const auto& peName : peNames) {
if (!fullNetworkReactionSet.contains(peName)) {
LOG_ERROR(m_logger, "Reaction with name '{}' not found in the base engine's network reactions. Aborting...", peName);
@@ -403,16 +404,16 @@ namespace gridfire::engine {
for (const auto& reactant : reaction->reactants()) {
if (!seenSpecies.contains(reactant)) {
seenSpecies.insert(reactant);
m_activeSpecies.emplace(reactant);
state->active_species.emplace(reactant);
}
}
for (const auto& product : reaction->products()) {
if (!seenSpecies.contains(product)) {
seenSpecies.insert(product);
m_activeSpecies.emplace(product);
state->active_species.emplace(product);
}
}
m_activeReactions.add_reaction(*reaction);
state->active_reactions.add_reaction(*reaction);
}
LOG_TRACE_L3(m_logger, "DefinedEngineView built with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
LOG_TRACE_L3(m_logger, "Active species: {}", [this]() -> std::string {
@@ -437,9 +438,8 @@ namespace gridfire::engine {
}
return result;
}());
m_speciesIndexMap = constructSpeciesIndexMap();
m_reactionIndexMap = constructReactionIndexMap();
m_isStale = false;
state->species_index_map = constructSpeciesIndexMap(ctx);
state->reaction_index_map = constructReactionIndexMap(ctx);
}

View File

@@ -4,6 +4,10 @@
#include "gridfire/utils/sundials.h"
#include "gridfire/utils/logging.h"
#include "gridfire/engine/scratchpads/blob.h"
#include "gridfire/engine/scratchpads/utils.h"
#include "gridfire/engine/scratchpads/engine_multiscale_scratchpad.h"
#include <stdexcept>
#include <vector>
#include <ranges>
@@ -151,18 +155,6 @@ namespace {
return reactantSample != productSample;
}
void QuietErrorRouter(int line, const char *func, const char *file, const char *msg,
SUNErrCode err_code, void *err_user_data, SUNContext sunctx) {
// LIST OF ERRORS TO IGNORE
if (err_code == KIN_LINESEARCH_NONCONV) {
return;
}
// For everything else, use the default SUNDIALS logger (or your own)
SUNLogErrHandlerFn(line, func, file, msg, err_code, err_user_data, sunctx);
}
struct DisjointSet {
std::vector<size_t> parent;
explicit DisjointSet(const size_t n) {
@@ -170,7 +162,7 @@ namespace {
std::iota(parent.begin(), parent.end(), 0);
}
size_t find(const size_t i) {
size_t find(const size_t i) { // NOLINT(*-no-recursion)
if (parent.at(i) == i) return i;
return parent.at(i) = find(parent.at(i)); // Path compression
}
@@ -192,28 +184,16 @@ namespace gridfire::engine {
MultiscalePartitioningEngineView::MultiscalePartitioningEngineView(
DynamicEngine& baseEngine
) : m_baseEngine(baseEngine) {
const int flag = SUNContext_Create(SUN_COMM_NULL, &m_sun_ctx);
if (flag != 0) {
LOG_CRITICAL(m_logger, "Error while creating SUNContext in MultiscalePartitioningEngineView");
throw std::runtime_error("Error creating SUNContext in MultiscalePartitioningEngineView");
}
SUNContext_PushErrHandler(m_sun_ctx, QuietErrorRouter, nullptr);
}
MultiscalePartitioningEngineView::~MultiscalePartitioningEngineView() {
LOG_TRACE_L1(m_logger, "Cleaning up MultiscalePartitioningEngineView...");
m_qse_solvers.clear();
if (m_sun_ctx) {
SUNContext_Free(&m_sun_ctx);
m_sun_ctx = nullptr;
}
}
const std::vector<Species> & MultiscalePartitioningEngineView::getNetworkSpecies() const {
return m_baseEngine.getNetworkSpecies();
const std::vector<Species> & MultiscalePartitioningEngineView::getNetworkSpecies(
scratch::StateBlob& ctx
) const {
return m_baseEngine.getNetworkSpecies(ctx);
}
std::expected<StepDerivatives<double>, EngineStatus> MultiscalePartitioningEngineView::calculateRHSAndEnergy(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
@@ -232,7 +212,7 @@ namespace gridfire::engine {
}
return ss.str();
}());
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, trust);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, trust);
LOG_TRACE_L2(m_logger, "Equilibrated composition prior to calling base engine is {}", [&qseComposition, &comp]() -> std::string {
std::stringstream ss;
size_t i = 0;
@@ -249,7 +229,7 @@ namespace gridfire::engine {
return ss.str();
}());
const auto result = m_baseEngine.calculateRHSAndEnergy(qseComposition, T9, rho, false);
const auto result = m_baseEngine.calculateRHSAndEnergy(ctx, qseComposition, T9, rho, false);
LOG_TRACE_L2(m_logger, "Base engine calculation of RHS and Energy complete.");
if (!result) {
@@ -258,9 +238,10 @@ namespace gridfire::engine {
}
auto deriv = result.value();
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
LOG_TRACE_L2(m_logger, "Zeroing out algebraic species derivatives.");
for (const auto& species : m_algebraic_species) {
for (const auto& species : state->algebraic_species) {
deriv.dydt[species] = 0.0; // Fix the algebraic species to the equilibrium abundances we calculate.
}
LOG_TRACE_L2(m_logger, "Done Zeroing out algebraic species derivatives.");
@@ -268,24 +249,28 @@ namespace gridfire::engine {
}
EnergyDerivatives MultiscalePartitioningEngineView::calculateEpsDerivatives(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
return m_baseEngine.calculateEpsDerivatives(qseComposition, T9, rho);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
return m_baseEngine.calculateEpsDerivatives(ctx, qseComposition, T9, rho);
}
NetworkJacobian MultiscalePartitioningEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
return m_baseEngine.generateJacobianMatrix(qseComposition, T9, rho, m_dynamic_species);
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
return m_baseEngine.generateJacobianMatrix(ctx, qseComposition, T9, rho, state->dynamic_species);
}
NetworkJacobian MultiscalePartitioningEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
@@ -293,7 +278,7 @@ namespace gridfire::engine {
) const {
bool activeSpeciesIsSubset = true;
for (const auto& species : activeSpecies) {
if (!involvesSpecies(species)) activeSpeciesIsSubset = false;
if (!involvesSpecies(ctx, species)) activeSpeciesIsSubset = false;
}
if (!activeSpeciesIsSubset) {
std::string msg = std::format(
@@ -301,7 +286,7 @@ namespace gridfire::engine {
[&]() -> std::string {
std::stringstream ss;
for (const auto& species : activeSpecies) {
if (!this->involvesSpecies(species)) {
if (!involvesSpecies(ctx, species)) {
ss << species << " ";
}
}
@@ -314,114 +299,104 @@ namespace gridfire::engine {
std::vector<Species> dynamicActiveSpeciesIntersection;
for (const auto& species : activeSpecies) {
if (involvesSpeciesInDynamic(species)) {
if (involvesSpeciesInDynamic(ctx, species)) {
dynamicActiveSpeciesIntersection.push_back(species);
}
}
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
return m_baseEngine.generateJacobianMatrix(qseComposition, T9, rho, dynamicActiveSpeciesIntersection);
return m_baseEngine.generateJacobianMatrix(ctx, qseComposition, T9, rho, dynamicActiveSpeciesIntersection);
}
NetworkJacobian MultiscalePartitioningEngineView::generateJacobianMatrix(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho,
const SparsityPattern &sparsityPattern
) const {
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
return m_baseEngine.generateJacobianMatrix(qseComposition, T9, rho, sparsityPattern);
}
void MultiscalePartitioningEngineView::generateStoichiometryMatrix() {
m_baseEngine.generateStoichiometryMatrix();
}
int MultiscalePartitioningEngineView::getStoichiometryMatrixEntry(
const Species& species,
const reaction::Reaction& reaction
) const {
return m_baseEngine.getStoichiometryMatrixEntry(species, reaction);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
return m_baseEngine.generateJacobianMatrix(ctx, qseComposition, T9, rho, sparsityPattern);
}
double MultiscalePartitioningEngineView::calculateMolarReactionFlow(
scratch::StateBlob& ctx,
const reaction::Reaction &reaction,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
return m_baseEngine.calculateMolarReactionFlow(reaction, qseComposition, T9, rho);
return m_baseEngine.calculateMolarReactionFlow(ctx, reaction, qseComposition, T9, rho);
}
const reaction::ReactionSet & MultiscalePartitioningEngineView::getNetworkReactions() const {
return m_baseEngine.getNetworkReactions();
}
void MultiscalePartitioningEngineView::setNetworkReactions(const reaction::ReactionSet &reactions) {
LOG_CRITICAL(m_logger, "setNetworkReactions is not supported in MultiscalePartitioningEngineView. Did you mean to call this on the base engine?");
throw exceptions::UnableToSetNetworkReactionsError("setNetworkReactions is not supported in MultiscalePartitioningEngineView. Did you mean to call this on the base engine?");
const reaction::ReactionSet & MultiscalePartitioningEngineView::getNetworkReactions(
scratch::StateBlob& ctx
) const {
return m_baseEngine.getNetworkReactions(ctx);
}
std::expected<std::unordered_map<Species, double>, EngineStatus> MultiscalePartitioningEngineView::getSpeciesTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
const auto result = m_baseEngine.getSpeciesTimescales(qseComposition, T9, rho);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
const auto result = m_baseEngine.getSpeciesTimescales(ctx, qseComposition, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
std::unordered_map<Species, double> speciesTimescales = result.value();
for (const auto& algebraicSpecies : m_algebraic_species) {
for (const auto& algebraicSpecies : state->algebraic_species) {
speciesTimescales[algebraicSpecies] = std::numeric_limits<double>::infinity(); // Algebraic species have infinite timescales.
}
return speciesTimescales;
}
std::expected<std::unordered_map<Species, double>, EngineStatus> MultiscalePartitioningEngineView::getSpeciesDestructionTimescales(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(qseComposition, T9, rho);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
const auto result = m_baseEngine.getSpeciesDestructionTimescales(ctx, qseComposition, T9, rho);
if (!result) {
return std::unexpected{result.error()};
}
std::unordered_map<Species, double> speciesDestructionTimescales = result.value();
for (const auto& algebraicSpecies : m_algebraic_species) {
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
for (const auto& algebraicSpecies : state->algebraic_species) {
speciesDestructionTimescales[algebraicSpecies] = std::numeric_limits<double>::infinity(); // Algebraic species have infinite destruction timescales.
}
return speciesDestructionTimescales;
}
fourdst::composition::Composition MultiscalePartitioningEngineView::update(const NetIn &netIn) {
const fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.update(netIn);
fourdst::composition::Composition MultiscalePartitioningEngineView::project(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
const fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.project(ctx, netIn);
NetIn baseUpdatedNetIn = netIn;
baseUpdatedNetIn.composition = baseUpdatedComposition;
fourdst::composition::Composition equilibratedComposition = partitionNetwork(baseUpdatedNetIn);
m_composition_cache.clear();
fourdst::composition::Composition equilibratedComposition = partitionNetwork(ctx, baseUpdatedNetIn);
auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
state->composition_cache.clear();
return equilibratedComposition;
}
bool MultiscalePartitioningEngineView::isStale(const NetIn &netIn) {
return m_baseEngine.isStale(netIn);
}
void MultiscalePartitioningEngineView::setScreeningModel(
const screening::ScreeningType model
) {
m_baseEngine.setScreeningModel(model);
}
screening::ScreeningType MultiscalePartitioningEngineView::getScreeningModel() const {
return m_baseEngine.getScreeningModel();
screening::ScreeningType MultiscalePartitioningEngineView::getScreeningModel(
scratch::StateBlob& ctx
) const {
return m_baseEngine.getScreeningModel(ctx);
}
const DynamicEngine & MultiscalePartitioningEngineView::getBaseEngine() const {
@@ -429,8 +404,11 @@ namespace gridfire::engine {
}
std::vector<std::vector<Species>> MultiscalePartitioningEngineView::analyzeTimescalePoolConnectivity(
const std::vector<std::vector<Species>> &timescale_pools, const fourdst::composition::Composition &comp, double T9, double
rho
scratch::StateBlob& ctx,
const std::vector<std::vector<Species>> &timescale_pools,
const fourdst::composition::Composition &comp,
double T9,
double rho
) const {
std::vector<std::vector<Species>> final_connected_pools;
@@ -440,7 +418,7 @@ namespace gridfire::engine {
}
// For each timescale pool, we need to analyze connectivity.
auto connectivity_graph = buildConnectivityGraph(pool, comp, T9, rho);
auto connectivity_graph = buildConnectivityGraph(ctx, pool, comp, T9, rho);
auto components = findConnectedComponentsBFS(connectivity_graph, pool);
final_connected_pools.insert(final_connected_pools.end(), components.begin(), components.end());
}
@@ -449,20 +427,21 @@ namespace gridfire::engine {
}
std::vector<MultiscalePartitioningEngineView::QSEGroup> MultiscalePartitioningEngineView::pruneValidatedGroups(
scratch::StateBlob& ctx,
const std::vector<QSEGroup> &groups,
const std::vector<reaction::ReactionSet> &groupReactions,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
const auto result = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
const auto result = m_baseEngine.getSpeciesTimescales(ctx, comp, T9, rho);
if (!result) {
throw std::runtime_error("Base engine returned stale error during pruneValidatedGroups timescale retrieval.");
}
std::unordered_map<Species, double> speciesTimescales = result.value();
const std::unordered_map<Species, double>& speciesTimescales = result.value();
std::vector<QSEGroup> newGroups;
for (const auto &[group, reactions] : std::views::zip(groups, groupReactions)) {
if (reactions.size() == 0) { // If a QSE group has gotten here it should have reactions associated with it. If it doesn't that is a serious error.
if (reactions.empty()) { // If a QSE group has gotten here it should have reactions associated with it. If it doesn't that is a serious error.
LOG_CRITICAL(m_logger, "No reactions specified for QSE group {} during pruning analysis.", group.toString(false));
throw std::runtime_error("No reactions specified for QSE group " + group.toString(false) + " during pruneValidatedGroups flux analysis.");
}
@@ -475,7 +454,7 @@ namespace gridfire::engine {
for (const auto& species : group.algebraic_species) {
mean_molar_abundance += comp.getMolarAbundance(species);
}
mean_molar_abundance /= group.algebraic_species.size();
mean_molar_abundance /= static_cast<double>(group.algebraic_species.size());
{ // Safety Valve to ensure valid log scaling
if (mean_molar_abundance <= 0) {
LOG_CRITICAL(m_logger, "Non-positive mean molar abundance {} calculated for QSE group during pruning analysis.", mean_molar_abundance);
@@ -484,7 +463,7 @@ namespace gridfire::engine {
}
for (const auto& reaction : reactions) {
const double flux = m_baseEngine.calculateMolarReactionFlow(*reaction, comp, T9, rho);
const double flux = m_baseEngine.calculateMolarReactionFlow(ctx, *reaction, comp, T9, rho);
size_t hash = reaction->hash(0);
if (reactionFluxes.contains(hash)) {
throw std::runtime_error("Duplicate reaction hash found during pruneValidatedGroups flux analysis.");
@@ -624,7 +603,7 @@ namespace gridfire::engine {
for (const auto &species : g.algebraic_species) {
meanTimescale += speciesTimescales.at(species);
}
meanTimescale /= g.algebraic_species.size();
meanTimescale /= static_cast<double>(g.algebraic_species.size());
g.mean_timescale = meanTimescale;
newGroups.push_back(g);
}
@@ -634,6 +613,7 @@ namespace gridfire::engine {
}
std::vector<MultiscalePartitioningEngineView::QSEGroup> MultiscalePartitioningEngineView::merge_coupled_groups(
scratch::StateBlob& ctx,
const std::vector<QSEGroup> &groups,
const std::vector<reaction::ReactionSet> &groupReactions
) {
@@ -688,10 +668,12 @@ namespace gridfire::engine {
}
fourdst::composition::Composition MultiscalePartitioningEngineView::partitionNetwork(
scratch::StateBlob& ctx,
const NetIn &netIn
) {
) const {
auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
// --- Step 0. Prime the network ---
const PrimingReport primingReport = m_baseEngine.primeEngine(netIn);
const PrimingReport primingReport = m_baseEngine.primeEngine(ctx, netIn);
const fourdst::composition::Composition& comp = primingReport.primedComposition;
const double T9 = netIn.temperature / 1e9;
const double rho = netIn.density;
@@ -699,25 +681,25 @@ namespace gridfire::engine {
// --- Step 0.5 Clear previous state ---
LOG_TRACE_L1(m_logger, "Partitioning network...");
LOG_TRACE_L1(m_logger, "Clearing previous state...");
m_qse_groups.clear();
m_qse_solvers.clear();
m_dynamic_species.clear();
m_algebraic_species.clear();
m_composition_cache.clear(); // We need to clear the cache now cause the same comp, temp, and density may result in a different value
state->qse_groups.clear();
state->qse_solvers.clear();
state->dynamic_species.clear();
state->algebraic_species.clear();
state->composition_cache.clear(); // We need to clear the cache now cause the same comp, temp, and density may result in a different value
// --- Step 1. Identify distinct timescale regions ---
LOG_TRACE_L1(m_logger, "Identifying fast reactions...");
const std::vector<std::vector<Species>> timescale_pools = partitionByTimescale(comp, T9, rho);
const std::vector<std::vector<Species>> timescale_pools = partitionByTimescale(ctx, comp, T9, rho);
LOG_TRACE_L1(m_logger, "Found {} timescale pools.", timescale_pools.size());
// --- Step 2. Select the mean slowest pool as the base dynamical group ---
LOG_TRACE_L1(m_logger, "Identifying mean slowest pool...");
const size_t mean_slowest_pool_index = identifyMeanSlowestPool(timescale_pools, comp, T9, rho);
const size_t mean_slowest_pool_index = identifyMeanSlowestPool(ctx, timescale_pools, comp, T9, rho);
LOG_TRACE_L1(m_logger, "Mean slowest pool index: {}", mean_slowest_pool_index);
// --- Step 3. Push the slowest pool into the dynamic species list ---
for (const auto& slowSpecies : timescale_pools[mean_slowest_pool_index]) {
m_dynamic_species.push_back(slowSpecies);
state->dynamic_species.push_back(slowSpecies);
}
// --- Step 4. Pack Candidate QSE Groups ---
@@ -729,40 +711,40 @@ namespace gridfire::engine {
}
LOG_TRACE_L1(m_logger, "Preforming connectivity analysis on timescale pools...");
const std::vector<std::vector<Species>> connected_pools = analyzeTimescalePoolConnectivity(candidate_pools, comp, T9, rho);
const std::vector<std::vector<Species>> connected_pools = analyzeTimescalePoolConnectivity(ctx, candidate_pools, comp, T9, rho);
LOG_TRACE_L1(m_logger, "Found {} connected pools (compared to {} timescale pools) for QSE analysis.", connected_pools.size(), timescale_pools.size());
// --- Step 5. Identify potential seed species for each candidate pool ---
LOG_TRACE_L1(m_logger, "Identifying potential seed species for candidate pools...");
const std::vector<QSEGroup> candidate_groups = constructCandidateGroups(connected_pools, comp, T9, rho);
const std::vector<QSEGroup> candidate_groups = constructCandidateGroups(ctx, connected_pools, comp, T9, rho);
LOG_TRACE_L1(m_logger, "Found {} candidate QSE groups for further analysis ({})", candidate_groups.size(), utils::iterable_to_delimited_string(candidate_groups));
LOG_TRACE_L1(m_logger, "Validating candidate groups with flux analysis...");
const auto [validated_groups, invalidate_groups, validated_group_reactions] = validateGroupsWithFluxAnalysis(candidate_groups, comp, T9, rho);
const auto [validated_groups, invalidate_groups, validated_group_reactions] = validateGroupsWithFluxAnalysis(ctx, candidate_groups, comp, T9, rho);
LOG_TRACE_L1(m_logger, "Validated {} group(s) QSE groups. {}", validated_groups.size(), utils::iterable_to_delimited_string(validated_groups));
LOG_TRACE_L1(m_logger, "Pruning groups based on log abundance-normalized flux analysis...");
const std::vector<QSEGroup> prunedGroups = pruneValidatedGroups(validated_groups, validated_group_reactions, comp, T9, rho);
const std::vector<QSEGroup> prunedGroups = pruneValidatedGroups(ctx, validated_groups, validated_group_reactions, comp, T9, rho);
LOG_TRACE_L1(m_logger, "After Pruning remaining groups are: {}", utils::iterable_to_delimited_string(prunedGroups));
LOG_TRACE_L1(m_logger, "Re-validating pruned groups with flux analysis...");
auto [pruned_validated_groups, _, pruned_validated_reactions] = validateGroupsWithFluxAnalysis(prunedGroups, comp, T9, rho);
auto [pruned_validated_groups, _, pruned_validated_reactions] = validateGroupsWithFluxAnalysis(ctx, prunedGroups, comp, T9, rho);
LOG_TRACE_L1(m_logger, "After re-validation, {} QSE groups remain. ({})",pruned_validated_groups.size(), utils::iterable_to_delimited_string(pruned_validated_groups));
LOG_TRACE_L1(m_logger, "Merging coupled QSE groups...");
const std::vector<QSEGroup> merged_groups = merge_coupled_groups(pruned_validated_groups, pruned_validated_reactions);
const std::vector<QSEGroup> merged_groups = merge_coupled_groups(ctx, pruned_validated_groups, pruned_validated_reactions);
LOG_TRACE_L1(m_logger, "After merging, {} QSE groups remain. ({})", merged_groups.size(), utils::iterable_to_delimited_string(merged_groups));
m_qse_groups = pruned_validated_groups;
state->qse_groups = pruned_validated_groups;
LOG_TRACE_L1(m_logger, "Pushing all identified algebraic species into algebraic set...");
for (const auto& group : m_qse_groups) {
for (const auto& group : state->qse_groups) {
// Add algebraic species to the algebraic set
for (const auto& species : group.algebraic_species) {
if (std::ranges::find(m_algebraic_species, species) == m_algebraic_species.end()) {
m_algebraic_species.push_back(species);
if (std::ranges::find(state->algebraic_species, species) == state->algebraic_species.end()) {
state->algebraic_species.push_back(species);
}
}
}
@@ -771,46 +753,47 @@ namespace gridfire::engine {
LOG_INFO(
m_logger,
"Partitioning complete. Found {} dynamic species, {} algebraic (QSE) species ({}) spread over {} QSE group{}.",
m_dynamic_species.size(),
m_algebraic_species.size(),
utils::iterable_to_delimited_string(m_algebraic_species),
m_qse_groups.size(),
m_qse_groups.size() == 1 ? "" : "s"
state->dynamic_species.size(),
state->algebraic_species.size(),
utils::iterable_to_delimited_string(state->algebraic_species),
state->qse_groups.size(),
state->qse_groups.size() == 1 ? "" : "s"
);
// Sort the QSE groups by mean timescale so that fastest groups get equilibrated first (as these may feed slower groups)
LOG_TRACE_L1(m_logger, "Sorting algebraic set by mean timescale...");
std::ranges::sort(m_qse_groups, [](const QSEGroup& a, const QSEGroup& b) {
std::ranges::sort(state->qse_groups, [](const QSEGroup& a, const QSEGroup& b) {
return a.mean_timescale < b.mean_timescale;
});
LOG_TRACE_L1(m_logger, "Finalizing dynamic species list...");
for (const auto& species : m_baseEngine.getNetworkSpecies()) {
const bool involvesAlgebraic = involvesSpeciesInQSE(species);
if (std::ranges::find(m_dynamic_species, species) == m_dynamic_species.end() && !involvesAlgebraic) {
m_dynamic_species.push_back(species);
for (const auto& species : m_baseEngine.getNetworkSpecies(ctx)) {
const bool involvesAlgebraic = involvesSpeciesInQSE(ctx, species);
if (std::ranges::find(state->dynamic_species, species) == state->dynamic_species.end() && !involvesAlgebraic) {
state->dynamic_species.push_back(species);
}
}
LOG_TRACE_L1(m_logger, "Final dynamic species set: {}", utils::iterable_to_delimited_string(m_dynamic_species));
LOG_TRACE_L1(m_logger, "Creating QSE solvers for each identified QSE group...");
for (const auto& group : m_qse_groups) {
for (const auto& group : state->qse_groups) {
std::vector<Species> groupAlgebraicSpecies;
for (const auto& species : group.algebraic_species) {
groupAlgebraicSpecies.push_back(species);
}
m_qse_solvers.push_back(std::make_unique<QSESolver>(groupAlgebraicSpecies, m_baseEngine, m_sun_ctx));
state->qse_solvers.push_back(std::make_unique<QSESolver>(groupAlgebraicSpecies, m_baseEngine, state->sun_ctx));
}
LOG_TRACE_L1(m_logger, "{} QSE solvers created.", m_qse_solvers.size());
LOG_TRACE_L1(m_logger, "Calculating final equilibrated composition...");
fourdst::composition::Composition result = getNormalizedEquilibratedComposition(comp, T9, rho, false);
fourdst::composition::Composition result = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
LOG_TRACE_L1(m_logger, "Final equilibrated composition calculated...");
return result;
}
void MultiscalePartitioningEngineView::exportToDot(
scratch::StateBlob &ctx,
const std::string &filename,
const fourdst::composition::Composition &comp,
const double T9,
@@ -822,22 +805,24 @@ namespace gridfire::engine {
throw std::runtime_error("Failed to open file for writing: " + filename);
}
const auto& all_species = m_baseEngine.getNetworkSpecies();
const auto& all_reactions = m_baseEngine.getNetworkReactions();
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
const auto& all_species = m_baseEngine.getNetworkSpecies(ctx);
const auto& all_reactions = m_baseEngine.getNetworkReactions(ctx);
// --- 1. Pre-computation and Categorization ---
// Categorize species into algebraic, seed, and core dynamic
std::unordered_set<Species> algebraic_species;
std::unordered_set<Species> seed_species;
for (const auto& group : m_qse_groups) {
for (const auto& group : state->qse_groups) {
if (group.is_in_equilibrium) {
algebraic_species.insert(group.algebraic_species.begin(), group.algebraic_species.end());
seed_species.insert(group.seed_species.begin(), group.seed_species.end());
}
}
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho, false);
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, comp, T9, rho, false);
// Calculate reaction flows and find min/max for logarithmic scaling of transparency
std::vector<double> reaction_flows;
reaction_flows.reserve(all_reactions.size());
@@ -845,7 +830,7 @@ namespace gridfire::engine {
double max_log_flow = std::numeric_limits<double>::lowest();
for (const auto& reaction : all_reactions) {
double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(*reaction, qseComposition, T9, rho));
double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(ctx, *reaction, qseComposition, T9, rho));
reaction_flows.push_back(flow);
if (flow > 1e-99) { // Avoid log(0)
double log_flow = std::log10(flow);
@@ -875,7 +860,7 @@ namespace gridfire::engine {
fillcolor = "#e0f2fe"; // Light Blue: Algebraic (in QSE)
} else if (seed_species.contains(species)) {
fillcolor = "#a7f3d0"; // Light Green: Seed (Dynamic, feeds a QSE group)
} else if (std::ranges::contains(m_dynamic_species, species)) {
} else if (std::ranges::contains(state->dynamic_species, species)) {
fillcolor = "#dcfce7"; // Pale Green: Core Dynamic
}
dotFile << " \"" << species.name() << "\" [label=\"" << species.name() << "\", fillcolor=\"" << fillcolor << "\"];\n";
@@ -918,7 +903,7 @@ namespace gridfire::engine {
// Draw a prominent box around the algebraic species of each valid QSE group.
dotFile << " // --- QSE Group Clusters ---\n";
int group_counter = 0;
for (const auto& group : m_qse_groups) {
for (const auto& group : state->qse_groups) {
if (!group.is_in_equilibrium || group.algebraic_species.empty()) {
continue;
}
@@ -1019,58 +1004,64 @@ namespace gridfire::engine {
dotFile.close();
}
std::vector<double> MultiscalePartitioningEngineView::mapNetInToMolarAbundanceVector(const NetIn &netIn) const {
std::vector<double> Y(netIn.composition.size(), 0.0); // Initialize with zeros
for (const auto& [sp, y] : netIn.composition) {
Y[getSpeciesIndex(sp)] = y; // Map species to their molar abundance
}
return Y; // Return the vector of molar abundances
}
std::vector<Species> MultiscalePartitioningEngineView::getFastSpecies() const {
const auto& all_species = m_baseEngine.getNetworkSpecies();
std::vector<Species> MultiscalePartitioningEngineView::getFastSpecies(
scratch::StateBlob& ctx
) const {
const auto& all_species = m_baseEngine.getNetworkSpecies(ctx);
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
std::vector<Species> fast_species;
fast_species.reserve(all_species.size() - m_dynamic_species.size());
fast_species.reserve(all_species.size() - state->dynamic_species.size());
for (const auto& species : all_species) {
auto it = std::ranges::find(m_dynamic_species, species);
if (it == m_dynamic_species.end()) {
auto it = std::ranges::find(state->dynamic_species, species);
if (it == state->dynamic_species.end()) {
fast_species.push_back(species);
}
}
return fast_species;
}
const std::vector<Species> & MultiscalePartitioningEngineView::getDynamicSpecies() const {
return m_dynamic_species;
const std::vector<Species> & MultiscalePartitioningEngineView::getDynamicSpecies(
scratch::StateBlob& ctx
) {
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
return state->dynamic_species;
}
PrimingReport MultiscalePartitioningEngineView::primeEngine(const NetIn &netIn) {
return m_baseEngine.primeEngine(netIn);
PrimingReport MultiscalePartitioningEngineView::primeEngine(
scratch::StateBlob& ctx,
const NetIn &netIn
) const {
return m_baseEngine.primeEngine(ctx, netIn);
}
bool MultiscalePartitioningEngineView::involvesSpecies(
scratch::StateBlob& ctx,
const Species &species
) const {
if (involvesSpeciesInQSE(species)) return true; // check this first since the vector is likely to be smaller so short circuit cost is less on average
if (involvesSpeciesInDynamic(species)) return true;
) {
if (involvesSpeciesInQSE(ctx, species)) return true; // check this first since the vector is likely to be smaller so short circuit cost is less on average
if (involvesSpeciesInDynamic(ctx, species)) return true;
return false;
}
bool MultiscalePartitioningEngineView::involvesSpeciesInQSE(
scratch::StateBlob& ctx,
const Species &species
) const {
return std::ranges::find(m_algebraic_species, species) != m_algebraic_species.end();
) {
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
return std::ranges::find(state->algebraic_species, species) != state->algebraic_species.end();
}
bool MultiscalePartitioningEngineView::involvesSpeciesInDynamic(
scratch::StateBlob& ctx,
const Species &species
) const {
return std::ranges::find(m_dynamic_species, species) != m_dynamic_species.end();
) {
const auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
return std::ranges::find(state->dynamic_species, species) != state->dynamic_species.end();
}
fourdst::composition::Composition MultiscalePartitioningEngineView::getNormalizedEquilibratedComposition(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract& comp,
const double T9,
const double rho,
@@ -1086,54 +1077,69 @@ namespace gridfire::engine {
std::hash<double>()(rho)
};
auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
const uint64_t composite_hash = XXHash64::hash(hashes.begin(), sizeof(uint64_t) * 3, 0);
if (m_composition_cache.contains(composite_hash)) {
if (state->composition_cache.contains(composite_hash)) {
LOG_TRACE_L3(m_logger, "Cache Hit in Multiscale Partitioning Engine View for composition at T9 = {}, rho = {}.", T9, rho);
return m_composition_cache.at(composite_hash);
return state->composition_cache.at(composite_hash);
}
LOG_TRACE_L3(m_logger, "Cache Miss in Multiscale Partitioning Engine View for composition at T9 = {}, rho = {}. Solving QSE abundances...", T9, rho);
// Only solve if the composition and thermodynamic conditions have not been cached yet
fourdst::composition::Composition qseComposition(solveQSEAbundances(comp, T9, rho));
fourdst::composition::Composition qseComposition(solveQSEAbundances(ctx, comp, T9, rho));
m_composition_cache[composite_hash] = qseComposition;
state->composition_cache[composite_hash] = qseComposition;
return qseComposition;
}
fourdst::composition::Composition MultiscalePartitioningEngineView::collectComposition(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
const fourdst::composition::Composition result = m_baseEngine.collectComposition(comp, T9, rho);
const fourdst::composition::Composition result = m_baseEngine.collectComposition(ctx, comp, T9, rho);
fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(result, T9, rho, false);
fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(ctx, result, T9, rho, false);
return qseComposition;
}
SpeciesStatus MultiscalePartitioningEngineView::getSpeciesStatus(const Species &species) const {
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(species);
if (status == SpeciesStatus::ACTIVE && involvesSpeciesInQSE(species)) {
SpeciesStatus MultiscalePartitioningEngineView::getSpeciesStatus(
scratch::StateBlob& ctx,
const Species &species
) const {
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(ctx, species);
if (status == SpeciesStatus::ACTIVE && involvesSpeciesInQSE(ctx, species)) {
return SpeciesStatus::EQUILIBRIUM;
}
return status;
}
size_t MultiscalePartitioningEngineView::getSpeciesIndex(const Species &species) const {
return m_baseEngine.getSpeciesIndex(species);
std::optional<StepDerivatives<double>> MultiscalePartitioningEngineView::getMostRecentRHSCalculation(
scratch::StateBlob& ctx
) const {
return m_baseEngine.getMostRecentRHSCalculation(ctx);
}
size_t MultiscalePartitioningEngineView::getSpeciesIndex(
scratch::StateBlob& ctx,
const Species &species
) const {
return m_baseEngine.getSpeciesIndex(ctx, species);
}
std::vector<std::vector<Species>> MultiscalePartitioningEngineView::partitionByTimescale(
scratch::StateBlob& ctx,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
LOG_TRACE_L1(m_logger, "Partitioning by timescale...");
const auto destructionTimescale= m_baseEngine.getSpeciesDestructionTimescales(comp, T9, rho);
const auto netTimescale = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
const auto destructionTimescale= m_baseEngine.getSpeciesDestructionTimescales(ctx, comp, T9, rho);
const auto netTimescale = m_baseEngine.getSpeciesTimescales(ctx, comp, T9, rho);
if (!destructionTimescale || !netTimescale) {
LOG_CRITICAL(m_logger, "Failed to compute species timescales for partitioning due to base engine error.");
@@ -1155,7 +1161,7 @@ namespace gridfire::engine {
}()
);
const auto& all_species = m_baseEngine.getNetworkSpecies();
const auto& all_species = m_baseEngine.getNetworkSpecies(ctx);
std::vector<std::pair<double, Species>> sorted_destruction_timescales;
for (const auto & species : all_species) {
@@ -1311,6 +1317,7 @@ namespace gridfire::engine {
}
std::pair<bool, reaction::ReactionSet> MultiscalePartitioningEngineView::group_is_a_qse_cluster(
scratch::StateBlob& ctx,
const fourdst::composition::Composition &comp,
const double T9,
const double rho,
@@ -1332,8 +1339,8 @@ namespace gridfire::engine {
double coupling_flux = 0.0;
double leakage_flux = 0.0;
for (const auto& reaction: m_baseEngine.getNetworkReactions()) {
const double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(*reaction, comp, T9, rho));
for (const auto& reaction: m_baseEngine.getNetworkReactions(ctx)) {
const double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(ctx, *reaction, comp, T9, rho));
if (flow == 0.0) {
continue; // Skip reactions with zero flow
}
@@ -1422,10 +1429,11 @@ namespace gridfire::engine {
}
bool MultiscalePartitioningEngineView::group_is_a_qse_pipeline(
const fourdst::composition::Composition &comp,
const double T9,
const double rho,
const QSEGroup &group
scratch::StateBlob& ctx,
const fourdst::composition::Composition &comp,
const double T9,
const double rho,
const QSEGroup &group
) const {
// Total fluxes (Standard check)
double total_prod = 0.0;
@@ -1435,8 +1443,8 @@ namespace gridfire::engine {
double charged_prod = 0.0;
double charged_dest = 0.0;
for (const auto& reaction : m_baseEngine.getNetworkReactions()) {
const double flow = m_baseEngine.calculateMolarReactionFlow(*reaction, comp, T9, rho);
for (const auto& reaction : m_baseEngine.getNetworkReactions(ctx)) {
const double flow = m_baseEngine.calculateMolarReactionFlow(ctx, *reaction, comp, T9, rho);
if (std::abs(flow) < 1.0e-99) continue;
int groupNetStoichiometry = 0;
@@ -1476,6 +1484,7 @@ namespace gridfire::engine {
MultiscalePartitioningEngineView::FluxValidationResult MultiscalePartitioningEngineView::validateGroupsWithFluxAnalysis(
scratch::StateBlob& ctx,
const std::vector<QSEGroup> &candidate_groups,
const fourdst::composition::Composition &comp,
const double T9, const double rho
@@ -1487,10 +1496,10 @@ namespace gridfire::engine {
group_reactions.reserve(candidate_groups.size());
for (auto& group : candidate_groups) {
// Values for measuring the flux coupling vs leakage
auto [leakage_coupled, group_reaction_set] = group_is_a_qse_cluster(comp, T9, rho, group);
auto [leakage_coupled, group_reaction_set] = group_is_a_qse_cluster(ctx, comp, T9, rho, group);
bool is_flow_balanced = group_is_a_qse_pipeline(comp, T9, rho, group);
bool is_flow_balanced = group_is_a_qse_pipeline(ctx, comp, T9, rho, group);
if (leakage_coupled) {
LOG_TRACE_L1(m_logger, "{} is in equilibrium due to high coupling flux", group.toString(false));
@@ -1516,21 +1525,23 @@ namespace gridfire::engine {
}
fourdst::composition::Composition MultiscalePartitioningEngineView::solveQSEAbundances(
scratch::StateBlob& ctx,
const fourdst::composition::CompositionAbstract &comp,
const double T9,
const double rho
) const {
LOG_TRACE_L2(m_logger, "Solving for QSE abundances...");
auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
fourdst::composition::Composition outputComposition(comp);
std::vector<Species> species;
std::vector<double> abundances;
species.reserve(m_algebraic_species.size());
abundances.reserve(m_algebraic_species.size());
species.reserve(state->algebraic_species.size());
abundances.reserve(state->algebraic_species.size());
for (const auto& [group, solver]: std::views::zip(m_qse_groups, m_qse_solvers)) {
const fourdst::composition::Composition& groupResult = solver->solve(outputComposition, T9, rho);
for (const auto& [group, solver]: std::views::zip(state->qse_groups, state->qse_solvers)) {
const fourdst::composition::Composition& groupResult = solver->solve(ctx, outputComposition, T9, rho);
for (const auto& [sp, y] : groupResult) {
if (!std::isfinite(y)) {
LOG_CRITICAL(m_logger, "Non-finite abundance {} computed for species {} in QSE group solve at T9 = {}, rho = {}.",
@@ -1553,12 +1564,13 @@ namespace gridfire::engine {
}
size_t MultiscalePartitioningEngineView::identifyMeanSlowestPool(
scratch::StateBlob& ctx,
const std::vector<std::vector<Species>> &pools,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
const auto& result = m_baseEngine.getSpeciesDestructionTimescales(comp, T9, rho);
const auto& result = m_baseEngine.getSpeciesDestructionTimescales(ctx, comp, T9, rho);
if (!result) {
LOG_CRITICAL(m_logger, "Failed to get species destruction timescales due base engine failure");
m_logger->flush_log();
@@ -1603,6 +1615,7 @@ namespace gridfire::engine {
}
std::unordered_map<Species, std::vector<Species>> MultiscalePartitioningEngineView::buildConnectivityGraph(
scratch::StateBlob& ctx,
const std::vector<Species> &species_pool,
const fourdst::composition::Composition &comp,
double T9,
@@ -1622,7 +1635,7 @@ namespace gridfire::engine {
std::map<size_t, std::vector<reaction::LogicalReaclibReaction*>> speciesReactionMap;
std::vector<const reaction::LogicalReaclibReaction*> candidate_reactions;
for (const auto& reaction : m_baseEngine.getNetworkReactions()) {
for (const auto& reaction : m_baseEngine.getNetworkReactions(ctx)) {
const std::vector<Species> &reactants = reaction->reactants();
const std::vector<Species> &products = reaction->products();
@@ -1660,13 +1673,14 @@ namespace gridfire::engine {
}
std::vector<MultiscalePartitioningEngineView::QSEGroup> MultiscalePartitioningEngineView::constructCandidateGroups(
scratch::StateBlob& ctx,
const std::vector<std::vector<Species>> &candidate_pools,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
) const {
const auto& all_reactions = m_baseEngine.getNetworkReactions();
const auto& result = m_baseEngine.getSpeciesDestructionTimescales(comp, T9, rho);
const auto& all_reactions = m_baseEngine.getNetworkReactions(ctx);
const auto& result = m_baseEngine.getSpeciesDestructionTimescales(ctx, comp, T9, rho);
if (!result) {
LOG_ERROR(m_logger, "Failed to get species destruction timescales due base engine failure");
m_logger->flush_log();
@@ -1694,7 +1708,7 @@ namespace gridfire::engine {
}
}
if (has_external_reactant) {
double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(*reaction, comp, T9, rho));
double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(ctx, *reaction, comp, T9, rho));
LOG_TRACE_L3(m_logger, "Found bridge reaction {} with flow {} for species {}.", reaction->id(), flow, ash.name());
bridge_reactions.emplace_back(reaction.get(), flow);
}
@@ -1872,6 +1886,7 @@ namespace gridfire::engine {
}
fourdst::composition::Composition MultiscalePartitioningEngineView::QSESolver::solve(
scratch::StateBlob& ctx,
const fourdst::composition::Composition &comp,
const double T9,
const double rho
@@ -1885,7 +1900,8 @@ namespace gridfire::engine {
result,
m_speciesMap,
m_species,
*this
*this,
ctx
};
utils::check_sundials_flag(KINSetUserData(m_kinsol_mem, &data), "KINSetUserData", utils::SUNDIALS_RET_CODE_TYPES::KINSOL);
@@ -1905,9 +1921,9 @@ namespace gridfire::engine {
}
StepDerivatives<double> rhsGuess;
auto cached_rhs = m_engine.getMostRecentRHSCalculation();
auto cached_rhs = m_engine.getMostRecentRHSCalculation(ctx);
if (!cached_rhs) {
const auto initial_rhs = m_engine.calculateRHSAndEnergy(result, T9, rho, false);
const auto initial_rhs = m_engine.calculateRHSAndEnergy(ctx, result, T9, rho, false);
if (!initial_rhs) {
throw std::runtime_error("In QSE solver failed to calculate initial RHS for caching");
}
@@ -2063,6 +2079,16 @@ namespace gridfire::engine {
getLogger()->flush_log(true);
}
std::unique_ptr<MultiscalePartitioningEngineView::QSESolver> MultiscalePartitioningEngineView::QSESolver::clone() const {
auto new_solver = std::make_unique<QSESolver>(m_species, m_engine, m_sun_ctx);
return new_solver;
}
std::unique_ptr<MultiscalePartitioningEngineView::QSESolver> MultiscalePartitioningEngineView::QSESolver::clone(SUNContext sun_ctx) const {
auto new_solver = std::make_unique<QSESolver>(m_species, m_engine, sun_ctx);
return new_solver;
}
int MultiscalePartitioningEngineView::QSESolver::sys_func(
const N_Vector y,
@@ -2086,7 +2112,7 @@ namespace gridfire::engine {
data->comp.setMolarAbundance(species, y_data[index]);
}
const auto result = data->engine.calculateRHSAndEnergy(data->comp, data->T9, data->rho, false);
const auto result = data->engine.calculateRHSAndEnergy(data->ctx, data->comp, data->T9, data->rho, false);
if (!result) {
return 1; // Potentially recoverable error
@@ -2102,7 +2128,7 @@ namespace gridfire::engine {
for (const auto &s: map | std::views::keys) {
const double v = dydt.at(s);
if (!std::isfinite(v)) {
invalid_species.push_back(std::make_pair(s, v));
invalid_species.emplace_back(s, v);
}
}
std::string msg = std::format("Non-finite dydt values encountered for species: {}",
@@ -2150,6 +2176,7 @@ namespace gridfire::engine {
}
const NetworkJacobian jac = data->engine.generateJacobianMatrix(
data->ctx,
data->comp,
data->T9,
data->rho,
@@ -2159,9 +2186,6 @@ namespace gridfire::engine {
sunrealtype* J_data = SUNDenseMatrix_Data(J);
const sunindextype N = SUNDenseMatrix_Columns(J);
if (data->row_scaling_factors.size() != static_cast<size_t>(N)) {
data->row_scaling_factors.resize(N, 0.0);
}
for (const auto& [row_species, row_idx]: map) {
double max_value = std::numeric_limits<double>::lowest();

View File

@@ -1,5 +1,5 @@
#include "gridfire/engine/views/engine_priming.h"
#include "gridfire/solver/solver.h"
#include "gridfire/engine/scratchpads/blob.h"
#include "fourdst/atomic/species.h"
@@ -11,16 +11,17 @@
#include <unordered_set>
#include <unordered_map>
namespace gridfire::engine {
using fourdst::atomic::species;
NetworkPrimingEngineView::NetworkPrimingEngineView(
scratch::StateBlob& ctx,
const std::string &primingSymbol,
GraphEngine &baseEngine
) :
DefinedEngineView(
constructPrimingReactionSet(
ctx,
species.at(primingSymbol),
baseEngine
),
@@ -29,26 +30,27 @@ namespace gridfire::engine {
m_primingSpecies(species.at(primingSymbol)) {}
NetworkPrimingEngineView::NetworkPrimingEngineView(
scratch::StateBlob& ctx,
const fourdst::atomic::Species &primingSpecies,
GraphEngine &baseEngine
) :
DefinedEngineView(
constructPrimingReactionSet(
ctx,
primingSpecies,
baseEngine
),
baseEngine
),
m_primingSpecies(primingSpecies) {
}
m_primingSpecies(primingSpecies) {}
std::vector<std::string> NetworkPrimingEngineView::constructPrimingReactionSet(
scratch::StateBlob& ctx,
const fourdst::atomic::Species &primingSpecies,
const GraphEngine &baseEngine
) const {
std::unordered_set<std::string> primeReactions;
for (const auto &reaction : baseEngine.getNetworkReactions()) {
for (const auto &reaction : baseEngine.getNetworkReactions(ctx)) {
if (reaction->contains(primingSpecies)) {
primeReactions.insert(std::string(reaction->id()));
}

View File

@@ -9,6 +9,7 @@
#include <optional>
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/engine/scratchpads/blob.h"
namespace {
template <typename T>
@@ -137,8 +138,11 @@ namespace gridfire::io::gen {
}
std::string exportEngineToPy(const engine::DynamicEngine& engine) {
auto reactions = engine.getNetworkReactions();
std::string exportEngineToPy(
engine::scratch::StateBlob& ctx,
const engine::DynamicEngine& engine
) {
auto reactions = engine.getNetworkReactions(ctx);
std::vector<std::string> functions;
functions.emplace_back(R"(import numpy as np
from typing import Dict, List, Tuple, Callable)");
@@ -150,8 +154,8 @@ from typing import Dict, List, Tuple, Callable)");
return join<std::string>(functions, "\n\n");
}
void exportEngineToPy(const engine::DynamicEngine &engine, const std::string &fileName) {
const std::string funcCode = exportEngineToPy(engine);
void exportEngineToPy(engine::scratch::StateBlob &ctx, const engine::DynamicEngine &engine, const std::string &fileName) {
const std::string funcCode = exportEngineToPy(ctx, engine);
std::ofstream outFile(fileName);
outFile << funcCode;
outFile.close();

View File

@@ -5,6 +5,13 @@
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/engine/engine_graph.h"
#include "gridfire/engine/views/engine_views.h"
#include "gridfire/utils/logging.h"
#include "gridfire/engine/scratchpads/blob.h"
#include "gridfire/engine/scratchpads/utils.h"
#include "gridfire/engine/scratchpads/engine_graph_scratchpad.h"
#include "gridfire/engine/scratchpads/engine_adaptive_scratchpad.h"
#include "gridfire/engine/scratchpads/engine_multiscale_scratchpad.h"
#include "fourdst/atomic/species.h"
#include "fourdst/composition/utils.h"
@@ -47,17 +54,13 @@ namespace gridfire::policy {
m_partition_function = build_partition_function();
}
engine::DynamicEngine& MainSequencePolicy::construct() {
ConstructionResults MainSequencePolicy::construct() {
m_network_stack.clear();
m_network_stack.emplace_back(
std::make_unique<engine::GraphEngine>(m_initializing_composition, *m_partition_function, engine::NetworkBuildDepth::ThirdOrder, engine::NetworkConstructionFlags::DEFAULT)
);
auto& graphRepr = dynamic_cast<engine::GraphEngine&>(*m_network_stack.back().get());
graphRepr.setUseReverseReactions(false);
m_network_stack.emplace_back(
std::make_unique<engine::MultiscalePartitioningEngineView>(*m_network_stack.back().get())
);
@@ -65,8 +68,9 @@ namespace gridfire::policy {
std::make_unique<engine::AdaptiveEngineView>(*m_network_stack.back().get())
);
std::unique_ptr<engine::scratch::StateBlob> scratch_blob = get_stack_scratch_blob();
m_status = NetworkPolicyStatus::INITIALIZED_UNVERIFIED;
m_status = check_status();
m_status = check_status(*scratch_blob);
switch (m_status) {
case NetworkPolicyStatus::MISSING_KEY_REACTION:
@@ -80,7 +84,7 @@ namespace gridfire::policy {
case NetworkPolicyStatus::INITIALIZED_VERIFIED:
break;
}
return *m_network_stack.back();
return {.engine = *m_network_stack.back(), .scratch_blob = std::move(scratch_blob)};
}
inline std::unique_ptr<partition::PartitionFunction> MainSequencePolicy::build_partition_function() {
@@ -115,13 +119,49 @@ namespace gridfire::policy {
return m_partition_function;
}
inline NetworkPolicyStatus MainSequencePolicy::check_status() const {
std::unique_ptr<engine::scratch::StateBlob> MainSequencePolicy::get_stack_scratch_blob() const {
if (m_network_stack.empty()) {
throw exceptions::PolicyError("Cannot get stack scratch blob from MainSequencePolicy: Engine stack is empty. Call construct() first.");
}
auto blob = std::make_unique<engine::scratch::StateBlob>();
blob->enroll<engine::scratch::GraphEngineScratchPad>();
blob->enroll<engine::scratch::AdaptiveEngineViewScratchPad>();
blob->enroll<engine::scratch::MultiscalePartitioningEngineViewScratchPad>();
const engine::GraphEngine* graph_engine = dynamic_cast<engine::GraphEngine*>(m_network_stack.front().get());
if (!graph_engine) {
throw exceptions::PolicyError("Cannot get stack scratch blob from MainSequencePolicy: The base engine is not a GraphEngine. This indicates a serious internal inconsistency and should be reported to the GridFire developers, thank you.");
}
const engine::MultiscalePartitioningEngineView* multiscale_engine = dynamic_cast<engine::MultiscalePartitioningEngineView*>(m_network_stack[1].get());
if (!multiscale_engine) {
throw exceptions::PolicyError("Cannot get stack scratch blob from MainSequencePolicy: The middle engine is not a MultiscalePartitioningEngineView. This indicates a serious internal inconsistency and should be reported to the GridFire developers, thank you.");
}
const engine::AdaptiveEngineView* adaptive_engine = dynamic_cast<engine::AdaptiveEngineView*>(m_network_stack.back().get());
if (!adaptive_engine) {
throw exceptions::PolicyError("Cannot get stack scratch blob from MainSequencePolicy: The top engine is not an AdaptiveEngineView. This indicates a serious internal inconsistency and should be reported to the GridFire developers, thank you.");
}
auto* graph_engine_state = engine::scratch::get_state<engine::scratch::GraphEngineScratchPad, false>(*blob);
graph_engine_state->initialize(*graph_engine);
auto* multiscale_engine_state = engine::scratch::get_state<engine::scratch::MultiscalePartitioningEngineViewScratchPad, false>(*blob);
multiscale_engine_state->initialize();
auto* adaptive_engine_state = engine::scratch::get_state<engine::scratch::AdaptiveEngineViewScratchPad, false>(*blob);
adaptive_engine_state->initialize(*adaptive_engine);
return blob;
}
inline NetworkPolicyStatus MainSequencePolicy::check_status(engine::scratch::StateBlob& ctx) const {
for (const auto& species : m_seed_species) {
if (!m_initializing_composition.contains(species)) {
return NetworkPolicyStatus::MISSING_KEY_SPECIES;
}
}
const reaction::ReactionSet& baseReactions = m_network_stack.front()->getNetworkReactions();
const reaction::ReactionSet& baseReactions = m_network_stack.front()->getNetworkReactions(ctx);
for (const auto& reaction : m_reaction_policy->get_reactions()) {
const bool result = baseReactions.contains(*reaction);
if (!result) {
@@ -130,4 +170,4 @@ namespace gridfire::policy {
}
return NetworkPolicyStatus::INITIALIZED_VERIFIED;
}
}
}

View File

@@ -19,7 +19,6 @@
#include "fourdst/atomic/species.h"
#include "fourdst/composition/exceptions/exceptions_composition.h"
#include "gridfire/engine/engine_graph.h"
#include "gridfire/engine/types/engine_types.h"
#include "gridfire/solver/strategies/triggers/engine_partitioning_trigger.h"
#include "gridfire/trigger/procedures/trigger_pprint.h"
#include "gridfire/exceptions/error_solver.h"
@@ -41,7 +40,8 @@ namespace gridfire::solver {
const std::vector<fourdst::atomic::Species> &networkSpecies,
const size_t currentConvergenceFailure,
const size_t currentNonlinearIterations,
const std::map<fourdst::atomic::Species, std::unordered_map<std::string, double>> &reactionContributionMap
const std::map<fourdst::atomic::Species, std::unordered_map<std::string, double>> &reactionContributionMap,
scratch::StateBlob& ctx
) :
t(t),
state(state),
@@ -54,7 +54,8 @@ namespace gridfire::solver {
networkSpecies(networkSpecies),
currentConvergenceFailures(currentConvergenceFailure),
currentNonlinearIterations(currentNonlinearIterations),
reactionContributionMap(reactionContributionMap)
reactionContributionMap(reactionContributionMap),
state_ctx(ctx)
{}
std::vector<std::tuple<std::string, std::string>> CVODESolverStrategy::TimestepContext::describe() const {
@@ -74,8 +75,11 @@ namespace gridfire::solver {
}
CVODESolverStrategy::CVODESolverStrategy(DynamicEngine &engine): SingleZoneNetworkSolverStrategy<DynamicEngine>(engine) {
// TODO: In order to support MPI this function must be changed
CVODESolverStrategy::CVODESolverStrategy(
const DynamicEngine &engine,
const scratch::StateBlob& ctx
): SingleZoneNetworkSolver<DynamicEngine>(engine, ctx) {
// PERF: In order to support MPI this function must be changed
const int flag = SUNContext_Create(SUN_COMM_NULL, &m_sun_ctx);
if (flag < 0) {
throw std::runtime_error("Failed to create SUNDIALS context (SUNDIALS Errno: " + std::to_string(flag) + ")");
@@ -137,10 +141,10 @@ namespace gridfire::solver {
(!resourcesExist ? "CVODE resources do not exist" :
"Input composition inconsistent with previous state"));
LOG_TRACE_L1(m_logger, "Starting engine update chain...");
equilibratedComposition = m_engine.update(netIn);
equilibratedComposition = m_engine.project(*m_scratch_blob, netIn);
LOG_TRACE_L1(m_logger, "Engine updated and equilibrated composition found!");
size_t numSpecies = m_engine.getNetworkSpecies().size();
size_t numSpecies = m_engine.getNetworkSpecies(*m_scratch_blob).size();
uint64_t N = numSpecies + 1;
LOG_TRACE_L1(m_logger, "Number of species: {} ({} independent variables)", numSpecies, N);
@@ -153,10 +157,10 @@ namespace gridfire::solver {
} else {
LOG_INFO(m_logger, "Reusing existing CVODE resources (size: {})", m_last_size);
const size_t numSpecies = m_engine.getNetworkSpecies().size();
const size_t numSpecies = m_engine.getNetworkSpecies(*m_scratch_blob).size();
sunrealtype *y_data = N_VGetArrayPointer(m_Y);
for (size_t i = 0; i < numSpecies; i++) {
const auto& species = m_engine.getNetworkSpecies()[i];
const auto& species = m_engine.getNetworkSpecies(*m_scratch_blob)[i];
if (netIn.composition.contains(species)) {
y_data[i] = netIn.composition.getMolarAbundance(species);
} else {
@@ -170,10 +174,12 @@ namespace gridfire::solver {
equilibratedComposition = netIn.composition; // Use the provided composition as-is if we already have validated CVODE resources and that the composition is consistent with the previous state
}
size_t numSpecies = m_engine.getNetworkSpecies().size();
CVODEUserData user_data;
user_data.solver_instance = this;
user_data.engine = &m_engine;
size_t numSpecies = m_engine.getNetworkSpecies(*m_scratch_blob).size();
CVODEUserData user_data {
.solver_instance = this,
.ctx = *m_scratch_blob,
.engine = &m_engine,
};
LOG_TRACE_L1(m_logger, "CVODE resources successfully initialized!");
double current_time = 0;
@@ -199,7 +205,7 @@ namespace gridfire::solver {
while (current_time < netIn.tMax) {
user_data.T9 = T9;
user_data.rho = netIn.density;
user_data.networkSpecies = &m_engine.getNetworkSpecies();
user_data.networkSpecies = &m_engine.getNetworkSpecies(*m_scratch_blob);
user_data.captured_exception.reset();
utils::check_cvode_flag(CVodeSetUserData(m_cvode_mem, &user_data), "CVodeSetUserData");
@@ -247,7 +253,7 @@ namespace gridfire::solver {
);
}
for (size_t i = 0; i < numSpecies; ++i) {
const auto& species = m_engine.getNetworkSpecies()[i];
const auto& species = m_engine.getNetworkSpecies(*m_scratch_blob)[i];
if (y_data[i] > 0.0) {
postStep.setMolarAbundance(species, y_data[i]);
}
@@ -260,7 +266,7 @@ namespace gridfire::solver {
LOG_DEBUG(m_logger, "Current composition (molar abundance): {}", [&]() -> std::string {
std::stringstream ss;
for (size_t i = 0; i < numSpecies; ++i) {
const auto& species = m_engine.getNetworkSpecies()[i];
const auto& species = m_engine.getNetworkSpecies(*m_scratch_blob)[i];
ss << species.name() << ": (y_data = " << y_data[i] << ", collected = " << postStep.getMolarAbundance(species) << ")";
if (i < numSpecies - 1) {
ss << ", ";
@@ -285,10 +291,11 @@ namespace gridfire::solver {
netIn.density,
n_steps,
m_engine,
m_engine.getNetworkSpecies(),
m_engine.getNetworkSpecies(*m_scratch_blob),
convFail_diff,
iter_diff,
rcMap
rcMap,
*m_scratch_blob
);
prev_nonlinear_iterations = nliters + total_nonlinear_iterations;
@@ -300,7 +307,7 @@ namespace gridfire::solver {
trigger->step(ctx);
if (m_detailed_step_logging) {
log_step_diagnostics(user_data, true, true, true, "step_" + std::to_string(total_steps + n_steps) + ".json");
log_step_diagnostics(*m_scratch_blob, user_data, true, true, true, "step_" + std::to_string(total_steps + n_steps) + ".json");
}
if (trigger->check(ctx)) {
@@ -326,7 +333,7 @@ namespace gridfire::solver {
fourdst::composition::Composition temp_comp;
std::vector<double> mass_fractions;
auto num_species_at_stop = static_cast<long int>(m_engine.getNetworkSpecies().size());
auto num_species_at_stop = static_cast<long int>(m_engine.getNetworkSpecies(*m_scratch_blob).size());
if (num_species_at_stop > m_Y->ops->nvgetlength(m_Y) - 1) {
LOG_ERROR(
@@ -338,8 +345,8 @@ namespace gridfire::solver {
throw std::runtime_error("Number of species at engine update exceeds the number of species in the CVODE solver. This should never happen.");
}
for (const auto& species: m_engine.getNetworkSpecies()) {
const size_t sid = m_engine.getSpeciesIndex(species);
for (const auto& species: m_engine.getNetworkSpecies(*m_scratch_blob)) {
const size_t sid = m_engine.getSpeciesIndex(*m_scratch_blob, species);
temp_comp.registerSpecies(species);
double y = end_of_step_abundances[sid];
if (y > 0.0) {
@@ -349,7 +356,7 @@ namespace gridfire::solver {
#ifndef NDEBUG
for (long int i = 0; i < num_species_at_stop; ++i) {
const auto& species = m_engine.getNetworkSpecies()[i];
const auto& species = m_engine.getNetworkSpecies(*m_scratch_blob)[i];
if (std::abs(temp_comp.getMolarAbundance(species) - y_data[i]) > 1e-12) {
throw exceptions::UtilityError("Conversion from solver state to composition molar abundance failed verification.");
}
@@ -384,7 +391,7 @@ namespace gridfire::solver {
"Prior to Engine Update active reactions are: {}",
[&]() -> std::string {
std::stringstream ss;
const gridfire::reaction::ReactionSet& reactions = m_engine.getNetworkReactions();
const gridfire::reaction::ReactionSet& reactions = m_engine.getNetworkReactions(*m_scratch_blob);
size_t count = 0;
for (const auto& reaction : reactions) {
ss << reaction -> id();
@@ -396,7 +403,7 @@ namespace gridfire::solver {
return ss.str();
}()
);
fourdst::composition::Composition currentComposition = m_engine.update(netInTemp);
fourdst::composition::Composition currentComposition = m_engine.project(*m_scratch_blob, netInTemp);
LOG_DEBUG(
m_logger,
"After to Engine update composition is (molar abundance) {}",
@@ -443,7 +450,7 @@ namespace gridfire::solver {
"After Engine Update active reactions are: {}",
[&]() -> std::string {
std::stringstream ss;
const gridfire::reaction::ReactionSet& reactions = m_engine.getNetworkReactions();
const gridfire::reaction::ReactionSet& reactions = m_engine.getNetworkReactions(*m_scratch_blob);
size_t count = 0;
for (const auto& reaction : reactions) {
ss << reaction -> id();
@@ -459,10 +466,10 @@ namespace gridfire::solver {
m_logger,
"Due to a triggered engine update the composition was updated from size {} to {} species.",
num_species_at_stop,
m_engine.getNetworkSpecies().size()
m_engine.getNetworkSpecies(*m_scratch_blob).size()
);
numSpecies = m_engine.getNetworkSpecies().size();
numSpecies = m_engine.getNetworkSpecies(*m_scratch_blob).size();
size_t N = numSpecies + 1;
LOG_INFO(m_logger, "Starting CVODE reinitialization after engine update...");
@@ -490,15 +497,15 @@ namespace gridfire::solver {
accumulated_energy += y_data[numSpecies];
std::vector<double> y_vec(y_data, y_data + numSpecies);
for (size_t i = 0; i < y_vec.size(); ++i) {
if (y_vec[i] < 0 && std::abs(y_vec[i]) < 1e-16) {
y_vec[i] = 0.0; // Regularize tiny negative abundances to zero
for (double & i : y_vec) {
if (i < 0 && std::abs(i) < 1e-16) {
i = 0.0; // Regularize tiny negative abundances to zero
}
}
LOG_INFO(m_logger, "Constructing final composition= with {} species", numSpecies);
fourdst::composition::Composition topLevelComposition(m_engine.getNetworkSpecies(), y_vec);
fourdst::composition::Composition topLevelComposition(m_engine.getNetworkSpecies(*m_scratch_blob), y_vec);
LOG_INFO(m_logger, "Final composition constructed from solver state successfully! ({})", [&topLevelComposition]() -> std::string {
std::ostringstream ss;
size_t i = 0;
@@ -513,7 +520,7 @@ namespace gridfire::solver {
}());
LOG_INFO(m_logger, "Collecting final composition...");
fourdst::composition::Composition outputComposition = m_engine.collectComposition(topLevelComposition, netIn.temperature/1e9, netIn.density);
fourdst::composition::Composition outputComposition = m_engine.collectComposition(*m_scratch_blob, topLevelComposition, netIn.temperature/1e9, netIn.density);
assert(outputComposition.getRegisteredSymbols().size() == equilibratedComposition.getRegisteredSymbols().size());
@@ -538,6 +545,7 @@ namespace gridfire::solver {
LOG_TRACE_L2(m_logger, "generating final nuclear energy generation rate derivatives...");
auto [dEps_dT, dEps_dRho] = m_engine.calculateEpsDerivatives(
*m_scratch_blob,
outputComposition,
T9,
netIn.density
@@ -640,7 +648,7 @@ namespace gridfire::solver {
const auto* solver_instance = data->solver_instance;
LOG_TRACE_L2(solver_instance->m_logger, "CVODE Jacobian wrapper starting");
const size_t numSpecies = engine->getNetworkSpecies().size();
const size_t numSpecies = engine->getNetworkSpecies(data->ctx).size();
sunrealtype* y_data = N_VGetArrayPointer(y);
@@ -653,7 +661,7 @@ namespace gridfire::solver {
}
}
std::vector<double> y_vec(y_data, y_data + numSpecies);
fourdst::composition::Composition composition(engine->getNetworkSpecies(), y_vec);
fourdst::composition::Composition composition(engine->getNetworkSpecies(data->ctx), y_vec);
LOG_TRACE_L2(solver_instance->m_logger, "Generating Jacobian matrix at time {} with {} species in composition (mean molecular mass: {})", t, composition.size(), composition.getMeanParticleMass());
LOG_TRACE_L2(solver_instance->m_logger, "Composition is {}", [&composition]() -> std::string {
std::stringstream ss;
@@ -669,11 +677,11 @@ namespace gridfire::solver {
}());
LOG_TRACE_L2(solver_instance->m_logger, "Generating Jacobian matrix at time {}", t);
NetworkJacobian jac = engine->generateJacobianMatrix(composition, data->T9, data->rho);
NetworkJacobian jac = engine->generateJacobianMatrix(data->ctx, composition, data->T9, data->rho);
LOG_TRACE_L2(solver_instance->m_logger, "Regularizing Jacobian matrix at time {}", t);
jac = regularize_jacobian(jac, composition, solver_instance->m_logger);
LOG_TRACE_L2(solver_instance->m_logger, "Done regularizing Jacobian matrix at time {}", t);
if (jac.infs().size() != 0 || jac.nans().size() != 0) {
if (!jac.infs().empty() || !jac.nans().empty()) {
auto infString = [&jac]() -> std::string {
std::stringstream ss;
size_t i = 0;
@@ -685,7 +693,7 @@ namespace gridfire::solver {
}
i++;
}
if (entries.size() == 0) {
if (entries.empty()) {
ss << "None";
}
return ss.str();
@@ -701,7 +709,7 @@ namespace gridfire::solver {
}
i++;
}
if (entries.size() == 0) {
if (entries.empty()) {
ss << "None";
}
return ss.str();
@@ -724,9 +732,9 @@ namespace gridfire::solver {
LOG_TRACE_L2(solver_instance->m_logger, "Transferring Jacobian matrix data to SUNDenseMatrix format at time {}", t);
for (size_t j = 0; j < numSpecies; ++j) {
const fourdst::atomic::Species& species_j = engine->getNetworkSpecies()[j];
const fourdst::atomic::Species& species_j = engine->getNetworkSpecies(data->ctx)[j];
for (size_t i = 0; i < numSpecies; ++i) {
const fourdst::atomic::Species& species_i = engine->getNetworkSpecies()[i];
const fourdst::atomic::Species& species_i = engine->getNetworkSpecies(data->ctx)[i];
// J(i,j) = d(f_i)/d(y_j)
// Column-major order format for SUNDenseMatrix: J_data[j*N + i] indexes J(i,j)
const double dYi_dt = jac(species_i, species_j);
@@ -752,7 +760,7 @@ namespace gridfire::solver {
N_Vector ydot,
const CVODEUserData *data
) const {
const size_t numSpecies = m_engine.getNetworkSpecies().size();
const size_t numSpecies = m_engine.getNetworkSpecies(data->ctx).size();
sunrealtype* y_data = N_VGetArrayPointer(y);
// Solver constraints should keep these values very close to 0 but floating point noise can still result in very
@@ -764,10 +772,10 @@ namespace gridfire::solver {
}
}
std::vector<double> y_vec(y_data, y_data + numSpecies);
fourdst::composition::Composition composition(m_engine.getNetworkSpecies(), y_vec);
fourdst::composition::Composition composition(m_engine.getNetworkSpecies(*m_scratch_blob), y_vec);
LOG_TRACE_L2(m_logger, "Calculating RHS at time {} with {} species in composition", t, composition.size());
const auto result = m_engine.calculateRHSAndEnergy(composition, data->T9, data->rho, false);
const auto result = m_engine.calculateRHSAndEnergy(*m_scratch_blob, composition, data->T9, data->rho, false);
if (!result) {
LOG_CRITICAL(m_logger, "Failed to calculate RHS at time {}: {}", t, EngineStatus_to_string(result.error()));
throw exceptions::BadRHSEngineError(std::format("Failed to calculate RHS at time {}: {}", t, EngineStatus_to_string(result.error())));
@@ -797,7 +805,7 @@ namespace gridfire::solver {
}());
for (size_t i = 0; i < numSpecies; ++i) {
fourdst::atomic::Species species = m_engine.getNetworkSpecies()[i];
fourdst::atomic::Species species = m_engine.getNetworkSpecies(*m_scratch_blob)[i];
ydot_data[i] = dydt.at(species);
}
ydot_data[numSpecies] = nuclearEnergyGenerationRate; // Set the last element to the specific energy rate
@@ -822,7 +830,7 @@ namespace gridfire::solver {
sunrealtype *y_data = N_VGetArrayPointer(m_Y);
for (size_t i = 0; i < numSpecies; i++) {
const auto& species = m_engine.getNetworkSpecies()[i];
const auto& species = m_engine.getNetworkSpecies(*m_scratch_blob)[i];
if (composition.contains(species)) {
y_data[i] = composition.getMolarAbundance(species);
} else {
@@ -893,11 +901,11 @@ namespace gridfire::solver {
}
void CVODESolverStrategy::log_step_diagnostics(
scratch::StateBlob &ctx,
const CVODEUserData &user_data,
bool displayJacobianStiffness,
bool displaySpeciesBalance,
bool to_file,
std::optional<std::string> filename
bool to_file, std::optional<std::string> filename
) const {
if (to_file && !filename.has_value()) {
LOG_ERROR(m_logger, "Filename must be provided when logging diagnostics to file.");
@@ -982,7 +990,7 @@ namespace gridfire::solver {
std::vector<double> Y_full(y_data, y_data + num_components - 1);
std::vector<double> E_full(y_err_data, y_err_data + num_components - 1);
auto result = diagnostics::report_limiting_species(*user_data.engine, Y_full, E_full, relTol, absTol, 10, to_file);
auto result = diagnostics::report_limiting_species(ctx, *user_data.engine, Y_full, E_full, relTol, absTol, 10, to_file);
if (to_file && result.has_value()) {
j["Limiting_Species"] = result.value();
}
@@ -1005,11 +1013,11 @@ namespace gridfire::solver {
err_ratios[i] = err_ratio;
}
fourdst::composition::Composition composition(user_data.engine->getNetworkSpecies(), Y_full);
fourdst::composition::Composition collectedComposition = user_data.engine->collectComposition(composition, user_data.T9, user_data.rho);
fourdst::composition::Composition composition(user_data.engine->getNetworkSpecies(*m_scratch_blob), Y_full);
fourdst::composition::Composition collectedComposition = user_data.engine->collectComposition(*m_scratch_blob, composition, user_data.T9, user_data.rho);
auto destructionTimescales = user_data.engine->getSpeciesDestructionTimescales(collectedComposition, user_data.T9, user_data.rho);
auto netTimescales = user_data.engine->getSpeciesTimescales(collectedComposition, user_data.T9, user_data.rho);
auto destructionTimescales = user_data.engine->getSpeciesDestructionTimescales(*m_scratch_blob, collectedComposition, user_data.T9, user_data.rho);
auto netTimescales = user_data.engine->getSpeciesTimescales(*m_scratch_blob, collectedComposition, user_data.T9, user_data.rho);
bool timescaleOkay = false;
if (destructionTimescales && netTimescales) timescaleOkay = true;
@@ -1029,7 +1037,7 @@ namespace gridfire::solver {
if (destructionTimescales.value().contains(sp)) destructionTimescales_list.emplace_back(destructionTimescales.value().at(sp));
else destructionTimescales_list.emplace_back(std::numeric_limits<double>::infinity());
speciesStatus_list.push_back(SpeciesStatus_to_string(user_data.engine->getSpeciesStatus(sp)));
speciesStatus_list.push_back(SpeciesStatus_to_string(user_data.engine->getSpeciesStatus(*m_scratch_blob, sp)));
}
utils::Column<fourdst::atomic::Species> speciesColumn("Species", species_list);
@@ -1093,7 +1101,7 @@ namespace gridfire::solver {
// --- 4. Call Your Jacobian and Balance Diagnostics ---
if (displayJacobianStiffness) {
auto jStiff = diagnostics::inspect_jacobian_stiffness(*user_data.engine, composition, user_data.T9, user_data.rho, to_file);
auto jStiff = diagnostics::inspect_jacobian_stiffness(ctx, *user_data.engine, composition, user_data.T9, user_data.rho, to_file);
if (to_file && jStiff.has_value()) {
j["Jacobian_Stiffness_Diagnostics"] = jStiff.value();
}
@@ -1103,7 +1111,7 @@ namespace gridfire::solver {
const size_t num_species_to_inspect = std::min(sorted_species.size(), static_cast<size_t>(5));
for (size_t i = 0; i < num_species_to_inspect; ++i) {
const auto& species = sorted_species[i];
auto sbr = diagnostics::inspect_species_balance(*user_data.engine, std::string(species.name()), composition, user_data.T9, user_data.rho, to_file);
auto sbr = diagnostics::inspect_species_balance(ctx, *user_data.engine, std::string(species.name()), composition, user_data.T9, user_data.rho, to_file);
if (to_file && sbr.has_value()) {
j[std::string("Species_Balance_Diagnostics_") + species.name().data()] = sbr.value();
}

View File

@@ -1,5 +1,6 @@
#include "gridfire/utils/logging.h"
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/engine/scratchpads/blob.h"
#include <sstream>
#include <iomanip>
@@ -9,12 +10,12 @@
#include <string>
std::string gridfire::utils::formatNuclearTimescaleLogString(
engine::scratch::StateBlob &ctx,
const engine::DynamicEngine& engine,
const fourdst::composition::Composition& composition,
const double T9,
const double rho
const double T9, const double rho
) {
auto const& result = engine.getSpeciesTimescales(composition, T9, rho);
auto const& result = engine.getSpeciesTimescales(ctx, composition, T9, rho);
if (!result) {
std::ostringstream ss;
ss << "Failed to get species timescales: " << engine::EngineStatus_to_string(result.error());