11#include "quill/LogMacros.h"
12#include "quill/Logger.h"
15 using fourdst::atomic::Species;
28 LOG_TRACE_L1(
m_logger,
"Constructing species index map for adaptive engine view...");
29 std::unordered_map<Species, size_t> fullSpeciesReverseMap;
30 const auto& fullSpeciesList =
m_baseEngine.getNetworkSpecies();
32 fullSpeciesReverseMap.reserve(fullSpeciesList.size());
34 for (
size_t i = 0; i < fullSpeciesList.size(); ++i) {
35 fullSpeciesReverseMap[fullSpeciesList[i]] = i;
38 std::vector<size_t> speciesIndexMap;
42 auto it = fullSpeciesReverseMap.find(active_species);
43 if (it != fullSpeciesReverseMap.end()) {
44 speciesIndexMap.push_back(it->second);
46 LOG_ERROR(
m_logger,
"Species '{}' not found in full species map.", active_species.name());
48 throw std::runtime_error(
"Species not found in full species map: " + std::string(active_species.name()));
51 LOG_TRACE_L1(
m_logger,
"Species index map constructed with {} entries.", speciesIndexMap.size());
52 return speciesIndexMap;
57 LOG_TRACE_L1(
m_logger,
"Constructing reaction index map for adaptive engine view...");
60 std::unordered_map<std::string_view, size_t> fullReactionReverseMap;
61 const auto& fullReactionSet =
m_baseEngine.getNetworkReactions();
62 fullReactionReverseMap.reserve(fullReactionSet.size());
64 for (
size_t i_full = 0; i_full < fullReactionSet.size(); ++i_full) {
65 fullReactionReverseMap[fullReactionSet[i_full].id()] = i_full;
69 std::vector<size_t> reactionIndexMap;
73 auto it = fullReactionReverseMap.find(active_reaction_ptr.id());
75 if (it != fullReactionReverseMap.end()) {
76 reactionIndexMap.push_back(it->second);
78 LOG_ERROR(
m_logger,
"Active reaction '{}' not found in base engine during reaction index map construction.", active_reaction_ptr.id());
80 throw std::runtime_error(
"Mismatch between active reactions and base engine.");
84 LOG_TRACE_L1(
m_logger,
"Reaction index map constructed with {} entries.", reactionIndexMap.size());
85 return reactionIndexMap;
89 fourdst::composition::Composition baseUpdatedComposition =
m_baseEngine.update(netIn);
90 NetIn updatedNetIn = netIn;
101 LOG_TRACE_L1(
m_logger,
"Updating AdaptiveEngineView with new network input...");
103 std::vector<double> Y_Full;
106 double maxFlow = 0.0;
108 for (
const auto&[reactionPtr, flowRate]: allFlows) {
109 if (flowRate > maxFlow) {
113 LOG_DEBUG(
m_logger,
"Maximum reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", maxFlow);
116 LOG_DEBUG(
m_logger,
"Found {} reachable species in adaptive engine view.", reachableSpecies.size());
118 const std::vector<const reaction::LogicalReaction*> finalReactions =
cullReactionsByFlow(allFlows, reachableSpecies, Y_Full, maxFlow);
124 for (
const auto& reactionPtr : rescuedReactions) {
128 for (
const auto& species : rescuedSpecies) {
153 const std::vector<double> &Y_culled,
161 auto result =
m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
164 return std::unexpected{result.error()};
167 const auto [dydt, nuclearEnergyGenerationRate] = result.value();
172 return culledResults;
176 const std::vector<double> &Y_dynamic,
194 return m_baseEngine.getJacobianMatrixEntry(i_full, j_full);
203 const int speciesIndex_culled,
204 const int reactionIndex_culled
209 return m_baseEngine.getStoichiometryMatrixEntry(speciesIndex_full, reactionIndex_full);
214 const std::vector<double> &Y_culled,
220 LOG_ERROR(
m_logger,
"Reaction '{}' is not part of the active reactions in the adaptive engine view.",
reaction.id());
222 throw std::runtime_error(
"Reaction not found in active reactions: " + std::string(
reaction.id()));
234 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?");
239 const std::vector<double> &Y_culled,
245 const auto result =
m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
248 return std::unexpected{result.error()};
251 const std::unordered_map<Species, double> fullTimescales = result.value();
254 std::unordered_map<Species, double> culledTimescales;
257 if (fullTimescales.contains(active_species)) {
258 culledTimescales[active_species] = fullTimescales.at(active_species);
261 return culledTimescales;
267 const std::vector<double> &Y,
274 const auto result =
m_baseEngine.getSpeciesDestructionTimescales(Y_full, T9, rho);
277 return std::unexpected{result.error()};
280 const std::unordered_map<Species, double> destructionTimescales = result.value();
282 std::unordered_map<Species, double> culledTimescales;
285 if (destructionTimescales.contains(active_species)) {
286 culledTimescales[active_species] = destructionTimescales.at(active_species);
289 return culledTimescales;
302 for (
const auto& [symbol, entry] : netIn.
composition) {
317 LOG_ERROR(
m_logger,
"Species '{}' not found in active species list.", species.name());
319 throw std::runtime_error(
"Species not found in active species list: " + std::string(species.name()));
324 std::vector<double> full(
m_baseEngine.getNetworkSpecies().size(), 0.0);
325 for (
size_t i_culled = 0; i_culled < culled.size(); ++i_culled) {
327 full[i_full] += culled[i_culled];
334 for (
size_t i_culled = 0; i_culled <
m_activeSpecies.size(); ++i_culled) {
336 culled[i_culled] = full[i_full];
343 LOG_ERROR(
m_logger,
"Culled index {} is out of bounds for species index map of size {}.", culledSpeciesIndex,
m_speciesIndexMap.size());
345 throw std::out_of_range(
"Culled index " + std::to_string(culledSpeciesIndex) +
" is out of bounds for species index map of size " + std::to_string(
m_speciesIndexMap.size()) +
".");
352 LOG_ERROR(
m_logger,
"Culled index {} is out of bounds for reaction index map of size {}.", culledReactionIndex,
m_reactionIndexMap.size());
354 throw std::out_of_range(
"Culled index " + std::to_string(culledReactionIndex) +
" is out of bounds for reaction index map of size " + std::to_string(
m_reactionIndexMap.size()) +
".");
361 LOG_ERROR(
m_logger,
"AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
363 throw std::runtime_error(
"AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
370 std::vector<double> &out_Y_Full
372 const auto& fullSpeciesList =
m_baseEngine.getNetworkSpecies();
374 out_Y_Full.reserve(fullSpeciesList.size());
376 for (
const auto& species: fullSpeciesList) {
378 out_Y_Full.push_back(netIn.
composition.getMolarAbundance(std::string(species.name())));
380 LOG_TRACE_L2(
m_logger,
"Species '{}' not found in composition. Setting abundance to 0.0.", species.name());
381 out_Y_Full.push_back(0.0);
386 const double rho = netIn.
density;
388 std::vector<ReactionFlow> reactionFlows;
389 const auto& fullReactionSet =
m_baseEngine.getNetworkReactions();
390 reactionFlows.reserve(fullReactionSet.size());
391 for (
const auto&
reaction : fullReactionSet) {
393 reactionFlows.push_back({&
reaction, flow});
394 LOG_TRACE_L1(
m_logger,
"Reaction '{}' has flow rate: {:0.3E} [mol/s/g]",
reaction.id(), flow);
396 return reactionFlows;
402 std::unordered_set<Species> reachable;
403 std::queue<Species> to_vist;
405 constexpr double ABUNDANCE_FLOOR = 1e-12;
406 for (
const auto& species:
m_baseEngine.getNetworkSpecies()) {
407 if (netIn.
composition.contains(species) && netIn.
composition.getMassFraction(std::string(species.name())) > ABUNDANCE_FLOOR) {
408 if (!reachable.contains(species)) {
409 to_vist.push(species);
410 reachable.insert(species);
411 LOG_TRACE_L2(
m_logger,
"Network Connectivity Analysis: Species '{}' is part of the initial fuel.", species.name());
416 bool new_species_found_in_pass =
true;
417 while (new_species_found_in_pass) {
418 new_species_found_in_pass =
false;
420 bool all_reactants_reachable =
true;
421 for (
const auto& reactant:
reaction.reactants()) {
422 if (!reachable.contains(reactant)) {
423 all_reactants_reachable =
false;
427 if (all_reactants_reachable) {
428 for (
const auto& product:
reaction.products()) {
429 if (!reachable.contains(product)) {
430 reachable.insert(product);
431 new_species_found_in_pass =
true;
432 LOG_TRACE_L2(
m_logger,
"Network Connectivity Analysis: Species '{}' is reachable via reaction '{}'.", product.name(),
reaction.id());
443 const std::vector<ReactionFlow> &allFlows,
444 const std::unordered_set<fourdst::atomic::Species> &reachableSpecies,
445 const std::vector<double> &Y_full,
448 LOG_TRACE_L1(
m_logger,
"Culling reactions based on flow rates...");
449 const auto relative_culling_threshold =
m_config.get<
double>(
"gridfire:AdaptiveEngineView:RelativeCullingThreshold", 1e-75);
450 double absoluteCullingThreshold = relative_culling_threshold * maxFlow;
451 LOG_DEBUG(
m_logger,
"Relative culling threshold: {:0.3E} ({})", relative_culling_threshold, absoluteCullingThreshold);
452 std::vector<const reaction::LogicalReaction*> culledReactions;
453 for (
const auto& [reactionPtr, flowRate]: allFlows) {
454 bool keepReaction =
false;
455 if (flowRate > absoluteCullingThreshold) {
456 LOG_TRACE_L2(
m_logger,
"Maintaining reaction '{}' with relative (abs) flow rate: {:0.3E} ({:0.3E} [mol/s])", reactionPtr->id(), flowRate/maxFlow, flowRate);
459 bool zero_flow_due_to_reachable_reactants =
false;
460 if (flowRate < 1e-99 && flowRate > 0.0) {
461 for (
const auto& reactant: reactionPtr->reactants()) {
462 const auto it = std::ranges::find(
m_baseEngine.getNetworkSpecies(), reactant);
463 const size_t index = std::distance(
m_baseEngine.getNetworkSpecies().begin(), it);
464 if (Y_full[index] < 1e-99 && reachableSpecies.contains(reactant)) {
465 LOG_TRACE_L1(
m_logger,
"Maintaining reaction '{}' with low flow ({:0.3E} [mol/s/g]) due to reachable reactant '{}'.", reactionPtr->id(), flowRate, reactant.name());
466 zero_flow_due_to_reachable_reactants =
true;
471 if (zero_flow_due_to_reachable_reactants) {
476 culledReactions.push_back(reactionPtr);
478 LOG_TRACE_L1(
m_logger,
"Culling reaction '{}' due to low flow rate or lack of connectivity.", reactionPtr->id());
481 LOG_DEBUG(
m_logger,
"Selected {} (total: {}, culled: {}) reactions based on flow rates.", culledReactions.size(), allFlows.size(), allFlows.size() - culledReactions.size());
482 return culledReactions;
486 const std::vector<double> &Y_full,
489 const std::vector<Species> &activeSpecies,
492 const auto result =
m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
494 LOG_ERROR(
m_logger,
"Failed to get species timescales due to stale engine state.");
497 std::unordered_map<Species, double> timescales = result.value();
498 std::set<Species> onlyProducedSpecies;
499 for (
const auto&
reaction : activeReactions) {
500 const std::vector<Species> products =
reaction.products();
501 onlyProducedSpecies.insert(products.begin(), products.end());
507 [&](
const Species &species) {
508 for (
const auto&
reaction : activeReactions) {
509 if (
reaction.contains_reactant(species)) {
520 [&](
const Species &species) {
521 return std::isinf(timescales.at(species));
527 "Found {} species {}that are produced but not consumed in any reaction and do not have an inf timescale (those are expected to be equilibrium species and do not contribute to the stiffness of the network).",
528 onlyProducedSpecies.size(),
529 [&]() -> std::string {
530 std::ostringstream ss;
531 if (onlyProducedSpecies.empty()) {
536 for (
const auto& species : onlyProducedSpecies) {
537 ss << species.name();
538 if (count < onlyProducedSpecies.size() - 1) {
548 std::unordered_map<Species, const reaction::LogicalReaction*> reactionsToRescue;
549 for (
const auto& species : onlyProducedSpecies) {
550 double maxSpeciesConsumptionRate = 0.0;
551 for (
const auto&
reaction : m_baseEngine.getNetworkReactions()) {
552 const bool speciesToCheckIsConsumed =
reaction.contains_reactant(species);
553 if (!speciesToCheckIsConsumed) {
556 bool allOtherReactantsAreAvailable =
true;
557 for (
const auto& reactant : reaction.reactants()) {
558 const bool reactantIsAvailable = std::ranges::contains(activeSpecies, reactant);
559 if (!reactantIsAvailable && reactant != species) {
560 allOtherReactantsAreAvailable =
false;
563 if (allOtherReactantsAreAvailable && speciesToCheckIsConsumed) {
564 double rate =
reaction.calculate_rate(T9);
565 if (rate > maxSpeciesConsumptionRate) {
566 maxSpeciesConsumptionRate = rate;
567 reactionsToRescue[species] = &
reaction;
574 "Rescuing {} {}reactions",
575 reactionsToRescue.size(),
576 [&]() -> std::string {
577 std::ostringstream ss;
578 if (reactionsToRescue.empty()) {
583 for (
const auto &
reaction : reactionsToRescue | std::views::values) {
585 if (count < reactionsToRescue.size() - 1) {
597 "Timescale adjustments due to reaction rescue: {}",
598 [&]() -> std::string {
599 std::stringstream ss;
600 if (reactionsToRescue.empty()) {
601 return "No reactions rescued...";
604 for (
const auto& [species, reaction] : reactionsToRescue) {
605 ss <<
"(Species: " << species.name() <<
" started with a timescale of " << timescales.at(species);
606 ss <<
", rescued by reaction: " << reaction->id();
607 ss <<
" whose product timescales are --- [";
609 for (
const auto& product : reaction->products()) {
610 ss << product.name() <<
": " << timescales.at(product);
611 if (icount < reaction->products().size() - 1) {
618 if (count < reactionsToRescue.size() - 1) {
629 std::unordered_set<const reaction::LogicalReaction*> newReactions;
630 std::unordered_set<Species> newSpecies;
632 for (
const auto &reactionPtr: reactionsToRescue | std::views::values) {
633 newReactions.insert(reactionPtr);
634 for (
const auto& product : reactionPtr->products()) {
635 newSpecies.insert(product);
638 return {std::move(newReactions), std::move(newSpecies)};
642 const std::vector<const reaction::LogicalReaction *> &finalReactions
644 std::unordered_set<Species>finalSpeciesSet;
646 for (
const auto* reactionPtr: finalReactions) {
648 for (
const auto& reactant : reactionPtr->reactants()) {
649 if (!finalSpeciesSet.contains(reactant)) {
650 LOG_TRACE_L1(
m_logger,
"Adding reactant '{}' to active species set through reaction {}.", reactant.name(), reactionPtr->id());
652 LOG_TRACE_L1(
m_logger,
"Reactant '{}' already in active species set through another reaction.", reactant.name());
654 finalSpeciesSet.insert(reactant);
656 for (
const auto& product : reactionPtr->products()) {
657 if (!finalSpeciesSet.contains(product)) {
658 LOG_TRACE_L1(
m_logger,
"Adding product '{}' to active species set through reaction {}.", product.name(), reactionPtr->id());
660 LOG_TRACE_L1(
m_logger,
"Product '{}' already in active species set through another reaction.", product.name());
662 finalSpeciesSet.insert(product);
666 m_activeSpecies = std::vector<Species>(finalSpeciesSet.begin(), finalSpeciesSet.end());
669 [](
const Species &a,
const Species &b) {
return a.mass() < b.mass(); }
void generateJacobianMatrix(const std::vector< double > &Y_dynamic, const double T9, const double rho) const override
Generates the Jacobian matrix for the active species.
double calculateMolarReactionFlow(const reaction::Reaction &reaction, const std::vector< double > &Y_culled, double T9, double rho) const override
Calculates the molar reaction flow for a given reaction in the active network.
screening::ScreeningType getScreeningModel() const override
Gets the screening model from the base engine.
std::unordered_set< fourdst::atomic::Species > findReachableSpecies(const NetIn &netIn) const
Finds all species that are reachable from the initial fuel through the reaction network.
const reaction::LogicalReactionSet & getNetworkReactions() const override
Gets the set of active logical reactions in the network.
Config & m_config
A reference to the singleton Config instance, used for retrieving configuration parameters.
reaction::LogicalReactionSet m_activeReactions
The set of reactions that are currently active in the network.
std::vector< size_t > m_reactionIndexMap
A map from the indices of the active reactions to the indices of the corresponding reactions in the f...
void generateStoichiometryMatrix() override
Generates the stoichiometry matrix for the active reactions and species.
size_t mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const
Maps a culled species index to a full species index.
fourdst::composition::Composition update(const NetIn &netIn) override
Updates the active species and reactions based on the current conditions.
std::vector< double > mapFullToCulled(const std::vector< double > &full) const
Maps a vector of full abundances to a vector of culled abundances.
std::vector< const reaction::LogicalReaction * > cullReactionsByFlow(const std::vector< ReactionFlow > &allFlows, const std::unordered_set< fourdst::atomic::Species > &reachableSpecies, const std::vector< double > &Y_full, double maxFlow) const
Culls reactions from the network based on their flow rates.
double getJacobianMatrixEntry(const int i_culled, const int j_culled) const override
Gets an entry from the Jacobian matrix for the active species.
DynamicEngine & m_baseEngine
The underlying engine to which this view delegates calculations.
std::expected< std::unordered_map< fourdst::atomic::Species, double >, expectations::StaleEngineError > getSpeciesTimescales(const std::vector< double > &Y_culled, double T9, double rho) const override
Computes timescales for all active species in the network.
std::pair< std::unordered_set< const reaction::LogicalReaction * >, std::unordered_set< fourdst::atomic::Species > > RescueSet
std::expected< std::unordered_map< fourdst::atomic::Species, double >, expectations::StaleEngineError > getSpeciesDestructionTimescales(const std::vector< double > &Y, double T9, double rho) const override
std::vector< size_t > m_speciesIndexMap
A map from the indices of the active species to the indices of the corresponding species in the full ...
bool m_isStale
A flag indicating whether the view is stale and needs to be updated.
int getStoichiometryMatrixEntry(const int speciesIndex_culled, const int reactionIndex_culled) const override
Gets an entry from the stoichiometry matrix for the active species and reactions.
std::vector< double > mapCulledToFull(const std::vector< double > &culled) const
Maps a vector of culled abundances to a vector of full abundances.
PrimingReport primeEngine(const NetIn &netIn) override
void setNetworkReactions(const reaction::LogicalReactionSet &reactions) override
std::vector< double > mapNetInToMolarAbundanceVector(const NetIn &netIn) const override
RescueSet rescueEdgeSpeciesDestructionChannel(const std::vector< double > &Y_full, const double T9, const double rho, const std::vector< fourdst::atomic::Species > &activeSpecies, const reaction::LogicalReactionSet &activeReactions) const
std::vector< size_t > constructReactionIndexMap() const
Constructs the reaction index map.
std::vector< size_t > constructSpeciesIndexMap() const
Constructs the species index map.
int getSpeciesIndex(const fourdst::atomic::Species &species) const override
size_t mapCulledToFullReactionIndex(size_t culledReactionIndex) const
Maps a culled reaction index to a full reaction index.
void finalizeActiveSet(const std::vector< const reaction::LogicalReaction * > &finalReactions)
Finalizes the set of active species and reactions.
void setScreeningModel(screening::ScreeningType model) override
Sets the screening model for the base engine.
std::vector< ReactionFlow > calculateAllReactionFlows(const NetIn &netIn, std::vector< double > &out_Y_Full) const
Calculates the molar reaction flow rate for all reactions in the full network.
quill::Logger * m_logger
A pointer to the logger instance, used for logging messages.
const std::vector< fourdst::atomic::Species > & getNetworkSpecies() const override
Gets the list of active species in the network.
bool isStale(const NetIn &netIn) override
AdaptiveEngineView(DynamicEngine &baseEngine)
Constructs an AdaptiveEngineView.
void validateState() const
Validates that the AdaptiveEngineView is not stale.
std::vector< fourdst::atomic::Species > m_activeSpecies
The set of species that are currently active in the network.
std::expected< StepDerivatives< double >, expectations::StaleEngineError > calculateRHSAndEnergy(const std::vector< double > &Y_culled, const double T9, const double rho) const override
Calculates the right-hand side (dY/dt) and energy generation for the active species.
Abstract class for engines supporting Jacobian and stoichiometry operations.
Represents a single nuclear reaction from a specific data source.
TemplatedReactionSet< LogicalReaction > LogicalReactionSet
A set of logical reactions.
ScreeningType
Enumerates the available plasma screening models.
double density
Density in g/cm^3.
fourdst::composition::Composition composition
Composition of the network.
double temperature
Temperature in Kelvin.
Captures the result of a network priming operation.
Structure holding derivatives and energy generation for a network step.
T nuclearEnergyGenerationRate
Specific energy generation rate (e.g., erg/g/s).
std::vector< T > dydt
Derivatives of abundances (dY/dt for each species).