GridFire 0.0.1a
General Purpose Nuclear Network
Loading...
Searching...
No Matches
engine_adaptive.cpp
Go to the documentation of this file.
2
3#include <ranges>
4#include <queue>
5
6#include "gridfire/network.h"
7
8#include "quill/LogMacros.h"
9#include "quill/Logger.h"
10
11namespace gridfire {
12 using fourdst::atomic::Species;
23
25 LOG_TRACE_L1(m_logger, "Constructing species index map for adaptive engine view...");
26 std::unordered_map<Species, size_t> fullSpeciesReverseMap;
27 const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
28
29 fullSpeciesReverseMap.reserve(fullSpeciesList.size());
30
31 for (size_t i = 0; i < fullSpeciesList.size(); ++i) {
32 fullSpeciesReverseMap[fullSpeciesList[i]] = i;
33 }
34
35 std::vector<size_t> speciesIndexMap;
36 speciesIndexMap.reserve(m_activeSpecies.size());
37
38 for (const auto& active_species : m_activeSpecies) {
39 auto it = fullSpeciesReverseMap.find(active_species);
40 if (it != fullSpeciesReverseMap.end()) {
41 speciesIndexMap.push_back(it->second);
42 } else {
43 LOG_ERROR(m_logger, "Species '{}' not found in full species map.", active_species.name());
44 m_logger -> flush_log();
45 throw std::runtime_error("Species not found in full species map: " + std::string(active_species.name()));
46 }
47 }
48 LOG_TRACE_L1(m_logger, "Species index map constructed with {} entries.", speciesIndexMap.size());
49 return speciesIndexMap;
50
51 }
52
54 LOG_TRACE_L1(m_logger, "Constructing reaction index map for adaptive engine view...");
55
56 // --- Step 1: Create a reverse map using the reaction's unique ID as the key. ---
57 std::unordered_map<std::string_view, size_t> fullReactionReverseMap;
58 const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
59 fullReactionReverseMap.reserve(fullReactionSet.size());
60
61 for (size_t i_full = 0; i_full < fullReactionSet.size(); ++i_full) {
62 fullReactionReverseMap[fullReactionSet[i_full].id()] = i_full;
63 }
64
65 // --- Step 2: Build the final index map using the active reaction set. ---
66 std::vector<size_t> reactionIndexMap;
67 reactionIndexMap.reserve(m_activeReactions.size());
68
69 for (const auto& active_reaction_ptr : m_activeReactions) {
70 auto it = fullReactionReverseMap.find(active_reaction_ptr.id());
71
72 if (it != fullReactionReverseMap.end()) {
73 reactionIndexMap.push_back(it->second);
74 } else {
75 LOG_ERROR(m_logger, "Active reaction '{}' not found in base engine during reaction index map construction.", active_reaction_ptr.id());
76 m_logger->flush_log();
77 throw std::runtime_error("Mismatch between active reactions and base engine.");
78 }
79 }
80
81 LOG_TRACE_L1(m_logger, "Reaction index map constructed with {} entries.", reactionIndexMap.size());
82 return reactionIndexMap;
83 }
84
85 void AdaptiveEngineView::update(const NetIn& netIn) {
86 LOG_TRACE_L1(m_logger, "Updating adaptive engine view...");
87
88 const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
89 std::vector<double>Y_full;
90 Y_full.reserve(fullSpeciesList.size());
91
92 for (const auto& species : fullSpeciesList) {
93 if (netIn.composition.contains(species)) {
94 Y_full.push_back(netIn.composition.getMolarAbundance(std::string(species.name())));
95 } else {
96 LOG_TRACE_L2(m_logger, "Species '{}' not found in composition. Setting abundance to 0.0.", species.name());
97 Y_full.push_back(0.0);
98 }
99 }
100
101 const double T9 = netIn.temperature / 1e9; // Convert temperature from Kelvin to T9 (T9 = T / 1e9)
102 const double rho = netIn.density; // Density in g/cm^3
103
104
105 std::vector<ReactionFlow> reactionFlows;
106 const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
107 reactionFlows.reserve(fullReactionSet.size());
108
109 for (const auto& reactionPtr : fullReactionSet) {
110 const double flow = m_baseEngine.calculateMolarReactionFlow(reactionPtr, Y_full, T9, rho);
111 reactionFlows.push_back({&reactionPtr, flow});
112 }
113
114 double max_flow = 0.0;
115 double min_flow = std::numeric_limits<double>::max();
116 double flowSum = 0.0;
117 for (const auto&[reactionPtr, flowRate] : reactionFlows) {
118 if (flowRate > max_flow) {
119 max_flow = flowRate;
120 } else if (flowRate < min_flow) {
121 min_flow = flowRate;
122 }
123 flowSum += flowRate;
124 LOG_TRACE_L2(m_logger, "Reaction '{}' has flow rate: {:0.3E} [mol/s]", reactionPtr->id(), flowRate);
125 }
126 flowSum /= reactionFlows.size();
127
128 LOG_DEBUG(m_logger, "Maximum reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", max_flow);
129 LOG_DEBUG(m_logger, "Minimum reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", min_flow);
130 LOG_DEBUG(m_logger, "Average reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", flowSum);
131
132 const double relative_culling_threshold = m_config.get<double>("gridfire:AdaptiveEngineView:RelativeCullingThreshold", 1e-75);
133
134 double absoluteCullingThreshold = relative_culling_threshold * max_flow;
135 LOG_DEBUG(m_logger, "Relative culling threshold: {:0.3E} ({})", relative_culling_threshold, absoluteCullingThreshold);
136
137 // --- Reaction Culling ---
138 LOG_TRACE_L1(m_logger, "Culling reactions based on reaction flow rates...");
139 std::vector<const reaction::Reaction*> flowCulledReactions;
140 for (const auto&[reactionPtr, flowRate] : reactionFlows) {
141 if (flowRate > absoluteCullingThreshold) {
142 LOG_TRACE_L2(m_logger, "Maintaining reaction '{}' with relative (abs) flow rate: {:0.3E} ({:0.3E} [mol/s])", reactionPtr->id(), flowRate/max_flow, flowRate);
143 flowCulledReactions.push_back(reactionPtr);
144 }
145 }
146 LOG_DEBUG(m_logger, "Selected {} (total: {}, culled: {}) reactions based on flow rates.", flowCulledReactions.size(), fullReactionSet.size(), fullReactionSet.size() - flowCulledReactions.size());
147
148 // --- Connectivity Analysis ---
149 std::queue<Species> species_to_visit;
150 std::unordered_set<Species> reachable_species;
151
152 constexpr double ABUNDANCE_FLOOR = 1e-12; // Abundance floor for a species to be considered part of the initial fuel
153 for (const auto& species : fullSpeciesList) {
154 if (netIn.composition.contains(species) && netIn.composition.getMassFraction(std::string(species.name())) > ABUNDANCE_FLOOR) {
155 species_to_visit.push(species);
156 reachable_species.insert(species);
157 LOG_TRACE_L2(m_logger, "Species '{}' is part of the initial fuel.", species.name());
158 }
159 }
160 std::unordered_map<Species, std::vector<const reaction::Reaction*>> reactant_to_reactions_map;
161 for (const auto* reaction_ptr : flowCulledReactions) {
162 for (const auto& reactant : reaction_ptr->reactants()) {
163 reactant_to_reactions_map[reactant].push_back(reaction_ptr);
164 }
165 }
166
167 while (!species_to_visit.empty()) {
168 Species currentSpecies = species_to_visit.front();
169 species_to_visit.pop();
170
171 auto it = reactant_to_reactions_map.find(currentSpecies);
172 if (it == reactant_to_reactions_map.end()) {
173 continue; // The species does not initiate any further reactions
174 }
175
176 const auto& reactions = it->second;
177 for (const auto* reaction_ptr : reactions) {
178 for (const auto& product : reaction_ptr->products()) {
179 if (!reachable_species.contains(product)) {
180 reachable_species.insert(product);
181 species_to_visit.push(product);
182 LOG_TRACE_L2(m_logger, "Species '{}' is reachable via reaction '{}'.", product.name(), reaction_ptr->id());
183 }
184 }
185 }
186 }
187 LOG_DEBUG(m_logger, "Reachable species count: {}", reachable_species.size());
188
189 m_activeSpecies.assign(reachable_species.begin(), reachable_species.end());
190 std::ranges::sort(m_activeSpecies,
191 [](const Species &a, const Species &b) { return a.mass() < b.mass(); });
192
193 m_activeReactions.clear();
194 for (const auto* reaction_ptr : flowCulledReactions) {
195 bool all_reactants_present = true;
196 for (const auto& reactant : reaction_ptr->reactants()) {
197 if (!reachable_species.contains(reactant)) {
198 all_reactants_present = false;
199 break;
200 }
201 }
202
203 if (all_reactants_present) {
204 m_activeReactions.add_reaction(*reaction_ptr);
205 LOG_TRACE_L2(m_logger, "Maintaining reaction '{}' with all reactants present.", reaction_ptr->id());
206 } else {
207 LOG_TRACE_L1(m_logger, "Culling reaction '{}' due to missing reactants.", reaction_ptr->id());
208 }
209 }
210 LOG_DEBUG(m_logger, "Active reactions count: {} (total: {}, culled: {}, culled due to connectivity: {})", m_activeReactions.size(),
211 fullReactionSet.size(), fullReactionSet.size() - m_activeReactions.size(), flowCulledReactions.size() - m_activeReactions.size());
212
215
216 m_isStale = false;
217 }
218
219 const std::vector<Species> & AdaptiveEngineView::getNetworkSpecies() const {
220 return m_activeSpecies;
221 }
222
224 const std::vector<double> &Y_culled,
225 const double T9,
226 const double rho
227 ) const {
229
230 const auto Y_full = mapCulledToFull(Y_culled);
231
232 const auto [dydt, nuclearEnergyGenerationRate] = m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
233
234 StepDerivatives<double> culledResults;
235 culledResults.nuclearEnergyGenerationRate = nuclearEnergyGenerationRate;
236 culledResults.dydt = mapFullToCulled(dydt);
237
238 return culledResults;
239 }
240
242 const std::vector<double> &Y_culled,
243 const double T9,
244 const double rho
245 ) {
247 const auto Y_full = mapCulledToFull(Y_culled);
248
249 m_baseEngine.generateJacobianMatrix(Y_full, T9, rho);
250 }
251
253 const int i_culled,
254 const int j_culled
255 ) const {
257 const size_t i_full = mapCulledToFullSpeciesIndex(i_culled);
258 const size_t j_full = mapCulledToFullSpeciesIndex(j_culled);
259
260 return m_baseEngine.getJacobianMatrixEntry(i_full, j_full);
261 }
262
265 m_baseEngine.generateStoichiometryMatrix();
266 }
267
269 const int speciesIndex_culled,
270 const int reactionIndex_culled
271 ) const {
273 const size_t speciesIndex_full = mapCulledToFullSpeciesIndex(speciesIndex_culled);
274 const size_t reactionIndex_full = mapCulledToFullReactionIndex(reactionIndex_culled);
275 return m_baseEngine.getStoichiometryMatrixEntry(speciesIndex_full, reactionIndex_full);
276 }
277
280 const std::vector<double> &Y_culled,
281 const double T9,
282 const double rho
283 ) const {
285 if (!m_activeReactions.contains(reaction)) {
286 LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the adaptive engine view.", reaction.id());
287 m_logger -> flush_log();
288 throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
289 }
290 const auto Y = mapCulledToFull(Y_culled);
291
292 return m_baseEngine.calculateMolarReactionFlow(reaction, Y, T9, rho);
293 }
294
298
299 std::unordered_map<Species, double> AdaptiveEngineView::getSpeciesTimescales(
300 const std::vector<double> &Y_culled,
301 const double T9,
302 const double rho
303 ) const {
305 const auto Y_full = mapCulledToFull(Y_culled);
306 const auto fullTimescales = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
307
308 std::unordered_map<Species, double> culledTimescales;
309 culledTimescales.reserve(m_activeSpecies.size());
310 for (const auto& active_species : m_activeSpecies) {
311 if (fullTimescales.contains(active_species)) {
312 culledTimescales[active_species] = fullTimescales.at(active_species);
313 }
314 }
315 return culledTimescales;
316
317 }
318
319 std::vector<double> AdaptiveEngineView::mapCulledToFull(const std::vector<double>& culled) const {
320 std::vector<double> full(m_baseEngine.getNetworkSpecies().size(), 0.0);
321 for (size_t i_culled = 0; i_culled < culled.size(); ++i_culled) {
322 const size_t i_full = m_speciesIndexMap[i_culled];
323 full[i_full] += culled[i_culled];
324 }
325 return full;
326 }
327
328 std::vector<double> AdaptiveEngineView::mapFullToCulled(const std::vector<double>& full) const {
329 std::vector<double> culled(m_activeSpecies.size(), 0.0);
330 for (size_t i_culled = 0; i_culled < m_activeSpecies.size(); ++i_culled) {
331 const size_t i_full = m_speciesIndexMap[i_culled];
332 culled[i_culled] = full[i_full];
333 }
334 return culled;
335 }
336
337 size_t AdaptiveEngineView::mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const {
338 if (culledSpeciesIndex < 0 || culledSpeciesIndex >= static_cast<int>(m_speciesIndexMap.size())) {
339 LOG_ERROR(m_logger, "Culled index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
340 m_logger->flush_log();
341 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()) + ".");
342 }
343 return m_speciesIndexMap[culledSpeciesIndex];
344 }
345
346 size_t AdaptiveEngineView::mapCulledToFullReactionIndex(size_t culledReactionIndex) const {
347 if (culledReactionIndex < 0 || culledReactionIndex >= static_cast<int>(m_reactionIndexMap.size())) {
348 LOG_ERROR(m_logger, "Culled index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
349 m_logger->flush_log();
350 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()) + ".");
351 }
352 return m_reactionIndexMap[culledReactionIndex];
353 }
354
356 if (m_isStale) {
357 LOG_ERROR(m_logger, "AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
358 m_logger->flush_log();
359 throw std::runtime_error("AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
360 }
361 }
362}
363
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.
const reaction::LogicalReactionSet & getNetworkReactions() const override
Gets the set of active logical reactions in the network.
reaction::LogicalReactionSet m_activeReactions
std::vector< size_t > m_reactionIndexMap
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.
std::vector< double > mapFullToCulled(const std::vector< double > &full) const
Maps a vector of full abundances to a vector of culled abundances.
void update(const NetIn &netIn)
Updates the active species and reactions based on the current conditions.
double getJacobianMatrixEntry(const int i_culled, const int j_culled) const override
Gets an entry from the Jacobian matrix for the active species.
std::vector< size_t > m_speciesIndexMap
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.
StepDerivatives< double > 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.
std::vector< size_t > constructReactionIndexMap() const
Constructs the reaction index map.
std::vector< size_t > constructSpeciesIndexMap() const
Constructs the species index map.
size_t mapCulledToFullReactionIndex(size_t culledReactionIndex) const
Maps a culled reaction index to a full reaction index.
std::unordered_map< fourdst::atomic::Species, double > getSpeciesTimescales(const std::vector< double > &Y_culled, double T9, double rho) const override
Computes timescales for all active species in the network.
const std::vector< fourdst::atomic::Species > & getNetworkSpecies() const override
Gets the list of active species in the network.
void generateJacobianMatrix(const std::vector< double > &Y_culled, const double T9, const double rho) override
Generates the Jacobian matrix for the active species.
AdaptiveEngineView(DynamicEngine &baseEngine)
Constructs an AdaptiveEngineView.
void validateState() const
Validates that the AdaptiveEngineView is not stale.
std::vector< fourdst::atomic::Species > m_activeSpecies
Abstract class for engines supporting Jacobian and stoichiometry operations.
A collection of LogicalReaction objects.
Definition reaction.h:554
Represents a single nuclear reaction from a specific data source.
Definition reaction.h:71
double density
Density in g/cm^3.
Definition network.h:58
fourdst::composition::Composition composition
Composition of the network.
Definition network.h:54
double temperature
Temperature in Kelvin.
Definition network.h:57
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).