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
std::unordered_set< fourdst::atomic::Species > findReachableSpecies(const NetIn &netIn) const
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.
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
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.
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)
void setScreeningModel(screening::ScreeningType model) override
std::vector< ReactionFlow > calculateAllReactionFlows(const NetIn &netIn, std::vector< double > &out_Y_Full) const
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.
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
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).