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 AdaptiveEngineView with new network input...");
87
88 std::vector<double> Y_Full;
89 std::vector<ReactionFlow> allFlows = calculateAllReactionFlows(netIn, Y_Full);
90
91 double maxFlow = 0.0;
92
93 for (const auto&[reactionPtr, flowRate]: allFlows) {
94 if (flowRate > maxFlow) {
95 maxFlow = flowRate;
96 }
97 }
98 LOG_DEBUG(m_logger, "Maximum reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", maxFlow);
99
100 const std::unordered_set<Species> reachableSpecies = findReachableSpecies(netIn);
101 LOG_DEBUG(m_logger, "Found {} reachable species in adaptive engine view.", reachableSpecies.size());
102
103 const std::vector<const reaction::LogicalReaction*> finalReactions = cullReactionsByFlow(allFlows, reachableSpecies, Y_Full, maxFlow);
104
105 finalizeActiveSet(finalReactions);
106
107
110
111 m_isStale = false;
112
113 LOG_INFO(m_logger, "AdaptiveEngineView updated successfully with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
114 }
115
116 const std::vector<Species> & AdaptiveEngineView::getNetworkSpecies() const {
117 return m_activeSpecies;
118 }
119
121 const std::vector<double> &Y_culled,
122 const double T9,
123 const double rho
124 ) const {
126
127 const auto Y_full = mapCulledToFull(Y_culled);
128
129 const auto [dydt, nuclearEnergyGenerationRate] = m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
130
131 StepDerivatives<double> culledResults;
132 culledResults.nuclearEnergyGenerationRate = nuclearEnergyGenerationRate;
133 culledResults.dydt = mapFullToCulled(dydt);
134
135 return culledResults;
136 }
137
139 const std::vector<double> &Y_culled,
140 const double T9,
141 const double rho
142 ) {
144 const auto Y_full = mapCulledToFull(Y_culled);
145
146 m_baseEngine.generateJacobianMatrix(Y_full, T9, rho);
147 }
148
150 const int i_culled,
151 const int j_culled
152 ) const {
154 const size_t i_full = mapCulledToFullSpeciesIndex(i_culled);
155 const size_t j_full = mapCulledToFullSpeciesIndex(j_culled);
156
157 return m_baseEngine.getJacobianMatrixEntry(i_full, j_full);
158 }
159
162 m_baseEngine.generateStoichiometryMatrix();
163 }
164
166 const int speciesIndex_culled,
167 const int reactionIndex_culled
168 ) const {
170 const size_t speciesIndex_full = mapCulledToFullSpeciesIndex(speciesIndex_culled);
171 const size_t reactionIndex_full = mapCulledToFullReactionIndex(reactionIndex_culled);
172 return m_baseEngine.getStoichiometryMatrixEntry(speciesIndex_full, reactionIndex_full);
173 }
174
177 const std::vector<double> &Y_culled,
178 const double T9,
179 const double rho
180 ) const {
182 if (!m_activeReactions.contains(reaction)) {
183 LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the adaptive engine view.", reaction.id());
184 m_logger -> flush_log();
185 throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
186 }
187 const auto Y = mapCulledToFull(Y_culled);
188
189 return m_baseEngine.calculateMolarReactionFlow(reaction, Y, T9, rho);
190 }
191
195
196 std::unordered_map<Species, double> AdaptiveEngineView::getSpeciesTimescales(
197 const std::vector<double> &Y_culled,
198 const double T9,
199 const double rho
200 ) const {
202 const auto Y_full = mapCulledToFull(Y_culled);
203 const auto fullTimescales = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
204
205 std::unordered_map<Species, double> culledTimescales;
206 culledTimescales.reserve(m_activeSpecies.size());
207 for (const auto& active_species : m_activeSpecies) {
208 if (fullTimescales.contains(active_species)) {
209 culledTimescales[active_species] = fullTimescales.at(active_species);
210 }
211 }
212 return culledTimescales;
213
214 }
215
217 m_baseEngine.setScreeningModel(model);
218 }
219
221 return m_baseEngine.getScreeningModel();
222 }
223
224 std::vector<double> AdaptiveEngineView::mapCulledToFull(const std::vector<double>& culled) const {
225 std::vector<double> full(m_baseEngine.getNetworkSpecies().size(), 0.0);
226 for (size_t i_culled = 0; i_culled < culled.size(); ++i_culled) {
227 const size_t i_full = m_speciesIndexMap[i_culled];
228 full[i_full] += culled[i_culled];
229 }
230 return full;
231 }
232
233 std::vector<double> AdaptiveEngineView::mapFullToCulled(const std::vector<double>& full) const {
234 std::vector<double> culled(m_activeSpecies.size(), 0.0);
235 for (size_t i_culled = 0; i_culled < m_activeSpecies.size(); ++i_culled) {
236 const size_t i_full = m_speciesIndexMap[i_culled];
237 culled[i_culled] = full[i_full];
238 }
239 return culled;
240 }
241
242 size_t AdaptiveEngineView::mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const {
243 if (culledSpeciesIndex < 0 || culledSpeciesIndex >= static_cast<int>(m_speciesIndexMap.size())) {
244 LOG_ERROR(m_logger, "Culled index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
245 m_logger->flush_log();
246 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()) + ".");
247 }
248 return m_speciesIndexMap[culledSpeciesIndex];
249 }
250
251 size_t AdaptiveEngineView::mapCulledToFullReactionIndex(size_t culledReactionIndex) const {
252 if (culledReactionIndex < 0 || culledReactionIndex >= static_cast<int>(m_reactionIndexMap.size())) {
253 LOG_ERROR(m_logger, "Culled index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
254 m_logger->flush_log();
255 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()) + ".");
256 }
257 return m_reactionIndexMap[culledReactionIndex];
258 }
259
261 if (m_isStale) {
262 LOG_ERROR(m_logger, "AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
263 m_logger->flush_log();
264 throw std::runtime_error("AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
265 }
266 }
267
268 std::vector<AdaptiveEngineView::ReactionFlow> AdaptiveEngineView::calculateAllReactionFlows(
269 const NetIn &netIn,
270 std::vector<double> &out_Y_Full
271 ) const {
272 const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
273 out_Y_Full.clear();
274 out_Y_Full.reserve(fullSpeciesList.size());
275
276 for (const auto& species: fullSpeciesList) {
277 if (netIn.composition.contains(species)) {
278 out_Y_Full.push_back(netIn.composition.getMolarAbundance(std::string(species.name())));
279 } else {
280 LOG_TRACE_L2(m_logger, "Species '{}' not found in composition. Setting abundance to 0.0.", species.name());
281 out_Y_Full.push_back(0.0);
282 }
283 }
284
285 const double T9 = netIn.temperature / 1e9; // Convert temperature from Kelvin to T9 (T9 = T / 1e9)
286 const double rho = netIn.density; // Density in g/cm^3
287
288 std::vector<ReactionFlow> reactionFlows;
289 const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
290 reactionFlows.reserve(fullReactionSet.size());
291 for (const auto& reaction : fullReactionSet) {
292 const double flow = m_baseEngine.calculateMolarReactionFlow(reaction, out_Y_Full, T9, rho);
293 reactionFlows.push_back({&reaction, flow});
294 LOG_TRACE_L2(m_logger, "Reaction '{}' has flow rate: {:0.3E} [mol/s]", reaction.id(), flow);
295 }
296 return reactionFlows;
297 }
298
299 std::unordered_set<Species> AdaptiveEngineView::findReachableSpecies(
300 const NetIn &netIn
301 ) const {
302 std::unordered_set<Species> reachable;
303 std::queue<Species> to_vist;
304
305 constexpr double ABUNDANCE_FLOOR = 1e-12; // Abundance floor for a species to be considered part of the initial fuel
306 for (const auto& species: m_baseEngine.getNetworkSpecies()) {
307 if (netIn.composition.contains(species) && netIn.composition.getMassFraction(std::string(species.name())) > ABUNDANCE_FLOOR) {
308 if (!reachable.contains(species)) {
309 to_vist.push(species);
310 reachable.insert(species);
311 LOG_TRACE_L2(m_logger, "Network Connectivity Analysis: Species '{}' is part of the initial fuel.", species.name());
312 }
313 }
314 }
315
316 bool new_species_found_in_pass = true;
317 while (new_species_found_in_pass) {
318 new_species_found_in_pass = false;
319 for (const auto& reaction: m_baseEngine.getNetworkReactions()) {
320 bool all_reactants_reachable = true;
321 for (const auto& reactant: reaction.reactants()) {
322 if (!reachable.contains(reactant)) {
323 all_reactants_reachable = false;
324 break;
325 }
326 }
327 if (all_reactants_reachable) {
328 for (const auto& product: reaction.products()) {
329 if (!reachable.contains(product)) {
330 reachable.insert(product);
331 new_species_found_in_pass = true;
332 LOG_TRACE_L2(m_logger, "Network Connectivity Analysis: Species '{}' is reachable via reaction '{}'.", product.name(), reaction.id());
333 }
334 }
335 }
336 }
337 }
338
339 return reachable;
340 }
341
342 std::vector<const reaction::LogicalReaction *> AdaptiveEngineView::cullReactionsByFlow(
343 const std::vector<ReactionFlow> &allFlows,
344 const std::unordered_set<fourdst::atomic::Species> &reachableSpecies,
345 const std::vector<double> &Y_full,
346 const double maxFlow
347 ) const {
348 LOG_TRACE_L1(m_logger, "Culling reactions based on flow rates...");
349 const auto relative_culling_threshold = m_config.get<double>("gridfire:AdaptiveEngineView:RelativeCullingThreshold", 1e-75);
350 double absoluteCullingThreshold = relative_culling_threshold * maxFlow;
351 LOG_DEBUG(m_logger, "Relative culling threshold: {:0.3E} ({})", relative_culling_threshold, absoluteCullingThreshold);
352 std::vector<const reaction::LogicalReaction*> culledReactions;
353 for (const auto& [reactionPtr, flowRate]: allFlows) {
354 bool keepReaction = false;
355 if (flowRate > absoluteCullingThreshold) {
356 LOG_TRACE_L2(m_logger, "Maintaining reaction '{}' with relative (abs) flow rate: {:0.3E} ({:0.3E} [mol/s])", reactionPtr->id(), flowRate/maxFlow, flowRate);
357 keepReaction = true;
358 } else {
359 bool zero_flow_due_to_reachable_reactants = false;
360 if (flowRate < 1e-99) {
361 for (const auto& reactant: reactionPtr->reactants()) {
362 const auto it = std::ranges::find(m_baseEngine.getNetworkSpecies(), reactant);
363 const size_t index = std::distance(m_baseEngine.getNetworkSpecies().begin(), it);
364 if (Y_full[index] < 1e-99 && reachableSpecies.contains(reactant)) {
365 LOG_TRACE_L2(m_logger, "Maintaining reaction '{}' with zero flow due to reachable reactant '{}'.", reactionPtr->id(), reactant.name());
366 zero_flow_due_to_reachable_reactants = true;
367 break;
368 }
369 }
370 }
371 if (zero_flow_due_to_reachable_reactants) {
372 keepReaction = true;
373 }
374 }
375 if (keepReaction) {
376 culledReactions.push_back(reactionPtr);
377 } else {
378 LOG_TRACE_L1(m_logger, "Culling reaction '{}' due to low flow rate or lack of connectivity.", reactionPtr->id());
379 }
380 }
381 LOG_DEBUG(m_logger, "Selected {} (total: {}, culled: {}) reactions based on flow rates.", culledReactions.size(), allFlows.size(), allFlows.size() - culledReactions.size());
382 return culledReactions;
383 }
384
386 const std::vector<const reaction::LogicalReaction *> &finalReactions
387 ) {
388 std::unordered_set<Species>finalSpeciesSet;
389 m_activeReactions.clear();
390 for (const auto* reactionPtr: finalReactions) {
391 m_activeReactions.add_reaction(*reactionPtr);
392 for (const auto& reactant : reactionPtr->reactants()) {
393 finalSpeciesSet.insert(reactant);
394 }
395 for (const auto& product : reactionPtr->products()) {
396 finalSpeciesSet.insert(product);
397 }
398 }
399 m_activeSpecies.assign(finalSpeciesSet.begin(), finalSpeciesSet.end());
400 std::ranges::sort(
402 [](const Species &a, const Species &b) { return a.mass() < b.mass(); }
403 );
404 }
405}
406
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.
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::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.
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.
void update(const NetIn &netIn) override
Updates the active species and reactions based on the current conditions.
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.
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.
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
The set of species that are currently active in the network.
Abstract class for engines supporting Jacobian and stoichiometry operations.
Represents a single nuclear reaction from a specific data source.
Definition reaction.h:72
TemplatedReactionSet< LogicalReaction > LogicalReactionSet
A set of logical reactions.
Definition reaction.h:557
ScreeningType
Enumerates the available plasma screening models.
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).