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#include <algorithm>
6
7
8#include "gridfire/network.h"
10
11#include "quill/LogMacros.h"
12#include "quill/Logger.h"
13
14namespace gridfire {
15 using fourdst::atomic::Species;
26
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();
31
32 fullSpeciesReverseMap.reserve(fullSpeciesList.size());
33
34 for (size_t i = 0; i < fullSpeciesList.size(); ++i) {
35 fullSpeciesReverseMap[fullSpeciesList[i]] = i;
36 }
37
38 std::vector<size_t> speciesIndexMap;
39 speciesIndexMap.reserve(m_activeSpecies.size());
40
41 for (const auto& active_species : m_activeSpecies) {
42 auto it = fullSpeciesReverseMap.find(active_species);
43 if (it != fullSpeciesReverseMap.end()) {
44 speciesIndexMap.push_back(it->second);
45 } else {
46 LOG_ERROR(m_logger, "Species '{}' not found in full species map.", active_species.name());
47 m_logger -> flush_log();
48 throw std::runtime_error("Species not found in full species map: " + std::string(active_species.name()));
49 }
50 }
51 LOG_TRACE_L1(m_logger, "Species index map constructed with {} entries.", speciesIndexMap.size());
52 return speciesIndexMap;
53
54 }
55
57 LOG_TRACE_L1(m_logger, "Constructing reaction index map for adaptive engine view...");
58
59 // --- Step 1: Create a reverse map using the reaction's unique ID as the key. ---
60 std::unordered_map<std::string_view, size_t> fullReactionReverseMap;
61 const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
62 fullReactionReverseMap.reserve(fullReactionSet.size());
63
64 for (size_t i_full = 0; i_full < fullReactionSet.size(); ++i_full) {
65 fullReactionReverseMap[fullReactionSet[i_full].id()] = i_full;
66 }
67
68 // --- Step 2: Build the final index map using the active reaction set. ---
69 std::vector<size_t> reactionIndexMap;
70 reactionIndexMap.reserve(m_activeReactions.size());
71
72 for (const auto& active_reaction_ptr : m_activeReactions) {
73 auto it = fullReactionReverseMap.find(active_reaction_ptr.id());
74
75 if (it != fullReactionReverseMap.end()) {
76 reactionIndexMap.push_back(it->second);
77 } else {
78 LOG_ERROR(m_logger, "Active reaction '{}' not found in base engine during reaction index map construction.", active_reaction_ptr.id());
79 m_logger->flush_log();
80 throw std::runtime_error("Mismatch between active reactions and base engine.");
81 }
82 }
83
84 LOG_TRACE_L1(m_logger, "Reaction index map constructed with {} entries.", reactionIndexMap.size());
85 return reactionIndexMap;
86 }
87
88 fourdst::composition::Composition AdaptiveEngineView::update(const NetIn &netIn) {
89 fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.update(netIn);
90 NetIn updatedNetIn = netIn;
91
92 // for (const auto &entry: netIn.composition | std::views::values) {
93 // if (baseUpdatedComposition.contains(entry.isotope())) {
94 // updatedNetIn.composition.setMassFraction(entry.isotope(), baseUpdatedComposition.getMassFraction(entry.isotope()));
95 // }
96 // }
97 updatedNetIn.composition = baseUpdatedComposition;
98
99 updatedNetIn.composition.finalize(false);
100
101 LOG_TRACE_L1(m_logger, "Updating AdaptiveEngineView with new network input...");
102
103 std::vector<double> Y_Full;
104 std::vector<ReactionFlow> allFlows = calculateAllReactionFlows(updatedNetIn, Y_Full);
105
106 double maxFlow = 0.0;
107
108 for (const auto&[reactionPtr, flowRate]: allFlows) {
109 if (flowRate > maxFlow) {
110 maxFlow = flowRate;
111 }
112 }
113 LOG_DEBUG(m_logger, "Maximum reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", maxFlow);
114
115 const std::unordered_set<Species> reachableSpecies = findReachableSpecies(updatedNetIn);
116 LOG_DEBUG(m_logger, "Found {} reachable species in adaptive engine view.", reachableSpecies.size());
117
118 const std::vector<const reaction::LogicalReaction*> finalReactions = cullReactionsByFlow(allFlows, reachableSpecies, Y_Full, maxFlow);
119
120 finalizeActiveSet(finalReactions);
121
122 auto [rescuedReactions, rescuedSpecies] = rescueEdgeSpeciesDestructionChannel(Y_Full, netIn.temperature/1e9, netIn.density, m_activeSpecies, m_activeReactions);
123
124 for (const auto& reactionPtr : rescuedReactions) {
125 m_activeReactions.add_reaction(*reactionPtr);
126 }
127
128 for (const auto& species : rescuedSpecies) {
129 if (!std::ranges::contains(m_activeSpecies, species)) {
130 m_activeSpecies.push_back(species);
131 }
132 }
133
136
137 m_isStale = false;
138
139 LOG_INFO(m_logger, "AdaptiveEngineView updated successfully with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
140
141 return updatedNetIn.composition;
142 }
143
145 return m_isStale || m_baseEngine.isStale(netIn);
146 }
147
148 const std::vector<Species> & AdaptiveEngineView::getNetworkSpecies() const {
149 return m_activeSpecies;
150 }
151
153 const std::vector<double> &Y_culled,
154 const double T9,
155 const double rho
156 ) const {
158
159 const auto Y_full = mapCulledToFull(Y_culled);
160
161 auto result = m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
162
163 if (!result) {
164 return std::unexpected{result.error()};
165 }
166
167 const auto [dydt, nuclearEnergyGenerationRate] = result.value();
168 StepDerivatives<double> culledResults;
169 culledResults.nuclearEnergyGenerationRate = nuclearEnergyGenerationRate;
170 culledResults.dydt = mapFullToCulled(dydt);
171
172 return culledResults;
173 }
174
176 const std::vector<double> &Y_dynamic,
177 const double T9,
178 const double rho
179 ) const {
181 const auto Y_full = mapCulledToFull(Y_dynamic);
182
183 m_baseEngine.generateJacobianMatrix(Y_full, T9, rho);
184 }
185
187 const int i_culled,
188 const int j_culled
189 ) const {
191 const size_t i_full = mapCulledToFullSpeciesIndex(i_culled);
192 const size_t j_full = mapCulledToFullSpeciesIndex(j_culled);
193
194 return m_baseEngine.getJacobianMatrixEntry(i_full, j_full);
195 }
196
199 m_baseEngine.generateStoichiometryMatrix();
200 }
201
203 const int speciesIndex_culled,
204 const int reactionIndex_culled
205 ) const {
207 const size_t speciesIndex_full = mapCulledToFullSpeciesIndex(speciesIndex_culled);
208 const size_t reactionIndex_full = mapCulledToFullReactionIndex(reactionIndex_culled);
209 return m_baseEngine.getStoichiometryMatrixEntry(speciesIndex_full, reactionIndex_full);
210 }
211
214 const std::vector<double> &Y_culled,
215 const double T9,
216 const double rho
217 ) const {
219 if (!m_activeReactions.contains(reaction)) {
220 LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the adaptive engine view.", reaction.id());
221 m_logger -> flush_log();
222 throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
223 }
224 const auto Y = mapCulledToFull(Y_culled);
225
226 return m_baseEngine.calculateMolarReactionFlow(reaction, Y, T9, rho);
227 }
228
232
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?");
235 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?");
236 }
237
238 std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError> AdaptiveEngineView::getSpeciesTimescales(
239 const std::vector<double> &Y_culled,
240 const double T9,
241 const double rho
242 ) const {
244 const auto Y_full = mapCulledToFull(Y_culled);
245 const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
246
247 if (!result) {
248 return std::unexpected{result.error()};
249 }
250
251 const std::unordered_map<Species, double> fullTimescales = result.value();
252
253
254 std::unordered_map<Species, double> culledTimescales;
255 culledTimescales.reserve(m_activeSpecies.size());
256 for (const auto& active_species : m_activeSpecies) {
257 if (fullTimescales.contains(active_species)) {
258 culledTimescales[active_species] = fullTimescales.at(active_species);
259 }
260 }
261 return culledTimescales;
262
263 }
264
265 std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError>
267 const std::vector<double> &Y,
268 double T9,
269 double rho
270 ) const {
272
273 const auto Y_full = mapCulledToFull(Y);
274 const auto result = m_baseEngine.getSpeciesDestructionTimescales(Y_full, T9, rho);
275
276 if (!result) {
277 return std::unexpected{result.error()};
278 }
279
280 const std::unordered_map<Species, double> destructionTimescales = result.value();
281
282 std::unordered_map<Species, double> culledTimescales;
283 culledTimescales.reserve(m_activeSpecies.size());
284 for (const auto& active_species : m_activeSpecies) {
285 if (destructionTimescales.contains(active_species)) {
286 culledTimescales[active_species] = destructionTimescales.at(active_species);
287 }
288 }
289 return culledTimescales;
290 }
291
293 m_baseEngine.setScreeningModel(model);
294 }
295
297 return m_baseEngine.getScreeningModel();
298 }
299
300 std::vector<double> AdaptiveEngineView::mapNetInToMolarAbundanceVector(const NetIn &netIn) const {
301 std::vector<double> Y(m_activeSpecies.size(), 0.0); // Initialize with zeros
302 for (const auto& [symbol, entry] : netIn.composition) {
303 Y[getSpeciesIndex(entry.isotope())] = netIn.composition.getMolarAbundance(symbol); // Map species to their molar abundance
304 }
305 return Y; // Return the vector of molar abundances
306 }
307
309 return m_baseEngine.primeEngine(netIn);
310 }
311
312 int AdaptiveEngineView::getSpeciesIndex(const fourdst::atomic::Species &species) const {
313 const auto it = std::ranges::find(m_activeSpecies, species);
314 if (it != m_activeSpecies.end()) {
315 return static_cast<int>(std::distance(m_activeSpecies.begin(), it));
316 } else {
317 LOG_ERROR(m_logger, "Species '{}' not found in active species list.", species.name());
318 m_logger->flush_log();
319 throw std::runtime_error("Species not found in active species list: " + std::string(species.name()));
320 }
321 }
322
323 std::vector<double> AdaptiveEngineView::mapCulledToFull(const std::vector<double>& culled) const {
324 std::vector<double> full(m_baseEngine.getNetworkSpecies().size(), 0.0);
325 for (size_t i_culled = 0; i_culled < culled.size(); ++i_culled) {
326 const size_t i_full = m_speciesIndexMap[i_culled];
327 full[i_full] += culled[i_culled];
328 }
329 return full;
330 }
331
332 std::vector<double> AdaptiveEngineView::mapFullToCulled(const std::vector<double>& full) const {
333 std::vector<double> culled(m_activeSpecies.size(), 0.0);
334 for (size_t i_culled = 0; i_culled < m_activeSpecies.size(); ++i_culled) {
335 const size_t i_full = m_speciesIndexMap[i_culled];
336 culled[i_culled] = full[i_full];
337 }
338 return culled;
339 }
340
341 size_t AdaptiveEngineView::mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const {
342 if (culledSpeciesIndex < 0 || culledSpeciesIndex >= m_speciesIndexMap.size()) {
343 LOG_ERROR(m_logger, "Culled index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
344 m_logger->flush_log();
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()) + ".");
346 }
347 return m_speciesIndexMap[culledSpeciesIndex];
348 }
349
350 size_t AdaptiveEngineView::mapCulledToFullReactionIndex(size_t culledReactionIndex) const {
351 if (culledReactionIndex < 0 || culledReactionIndex >= m_reactionIndexMap.size()) {
352 LOG_ERROR(m_logger, "Culled index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
353 m_logger->flush_log();
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()) + ".");
355 }
356 return m_reactionIndexMap[culledReactionIndex];
357 }
358
360 if (m_isStale) {
361 LOG_ERROR(m_logger, "AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
362 m_logger->flush_log();
363 throw std::runtime_error("AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
364 }
365 }
366
367 // TODO: Change this to use a return value instead of an output parameter.
368 std::vector<AdaptiveEngineView::ReactionFlow> AdaptiveEngineView::calculateAllReactionFlows(
369 const NetIn &netIn,
370 std::vector<double> &out_Y_Full
371 ) const {
372 const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
373 out_Y_Full.clear();
374 out_Y_Full.reserve(fullSpeciesList.size());
375
376 for (const auto& species: fullSpeciesList) {
377 if (netIn.composition.contains(species)) {
378 out_Y_Full.push_back(netIn.composition.getMolarAbundance(std::string(species.name())));
379 } else {
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);
382 }
383 }
384
385 const double T9 = netIn.temperature / 1e9; // Convert temperature from Kelvin to T9 (T9 = T / 1e9)
386 const double rho = netIn.density; // Density in g/cm^3
387
388 std::vector<ReactionFlow> reactionFlows;
389 const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
390 reactionFlows.reserve(fullReactionSet.size());
391 for (const auto& reaction : fullReactionSet) {
392 const double flow = m_baseEngine.calculateMolarReactionFlow(reaction, out_Y_Full, T9, rho);
393 reactionFlows.push_back({&reaction, flow});
394 LOG_TRACE_L1(m_logger, "Reaction '{}' has flow rate: {:0.3E} [mol/s/g]", reaction.id(), flow);
395 }
396 return reactionFlows;
397 }
398
399 std::unordered_set<Species> AdaptiveEngineView::findReachableSpecies(
400 const NetIn &netIn
401 ) const {
402 std::unordered_set<Species> reachable;
403 std::queue<Species> to_vist;
404
405 constexpr double ABUNDANCE_FLOOR = 1e-12; // Abundance floor for a species to be considered part of the initial fuel
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());
412 }
413 }
414 }
415
416 bool new_species_found_in_pass = true;
417 while (new_species_found_in_pass) {
418 new_species_found_in_pass = false;
419 for (const auto& reaction: m_baseEngine.getNetworkReactions()) {
420 bool all_reactants_reachable = true;
421 for (const auto& reactant: reaction.reactants()) {
422 if (!reachable.contains(reactant)) {
423 all_reactants_reachable = false;
424 break;
425 }
426 }
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());
433 }
434 }
435 }
436 }
437 }
438
439 return reachable;
440 }
441
442 std::vector<const reaction::LogicalReaction *> AdaptiveEngineView::cullReactionsByFlow(
443 const std::vector<ReactionFlow> &allFlows,
444 const std::unordered_set<fourdst::atomic::Species> &reachableSpecies,
445 const std::vector<double> &Y_full,
446 const double maxFlow
447 ) const {
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);
457 keepReaction = true;
458 } else {
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;
467 break;
468 }
469 }
470 }
471 if (zero_flow_due_to_reachable_reactants) {
472 keepReaction = true;
473 }
474 }
475 if (keepReaction) {
476 culledReactions.push_back(reactionPtr);
477 } else {
478 LOG_TRACE_L1(m_logger, "Culling reaction '{}' due to low flow rate or lack of connectivity.", reactionPtr->id());
479 }
480 }
481 LOG_DEBUG(m_logger, "Selected {} (total: {}, culled: {}) reactions based on flow rates.", culledReactions.size(), allFlows.size(), allFlows.size() - culledReactions.size());
482 return culledReactions;
483 }
484
486 const std::vector<double> &Y_full,
487 const double T9,
488 const double rho,
489 const std::vector<Species> &activeSpecies,
490 const reaction::LogicalReactionSet &activeReactions
491 ) const {
492 const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
493 if (!result) {
494 LOG_ERROR(m_logger, "Failed to get species timescales due to stale engine state.");
495 throw exceptions::StaleEngineError("Failed to get species timescales");
496 }
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());
502 }
503
504 // Remove species that are consumed by any one of the active reactions.
505 std::erase_if(
506 onlyProducedSpecies,
507 [&](const Species &species) {
508 for (const auto& reaction : activeReactions) {
509 if (reaction.contains_reactant(species)) {
510 return true; // If any active reaction consumes the species then erase it from the set.
511 }
512 }
513 return false;
514 }
515 );
516
517 // Remove species that have a non-zero timescale (these are expected to be +inf as they should be the equilibrium species if using with a MultiscalePartitioningEngineView)
518 std::erase_if(
519 onlyProducedSpecies,
520 [&](const Species &species) {
521 return std::isinf(timescales.at(species));
522 }
523 );
524
525 LOG_TRACE_L1(
526 m_logger,
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()) {
532 return "";
533 }
534 int count = 0;
535 ss << "(";
536 for (const auto& species : onlyProducedSpecies) {
537 ss << species.name();
538 if (count < onlyProducedSpecies.size() - 1) {
539 ss << ", ";
540 }
541 count++;
542 }
543 ss << ") ";
544 return ss.str();
545 }()
546 );
547
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) {
554 continue; // If the species is not consumed by this reaction, skip it.
555 }
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;
561 }
562 }
563 if (allOtherReactantsAreAvailable && speciesToCheckIsConsumed) {
564 double rate = reaction.calculate_rate(T9);
565 if (rate > maxSpeciesConsumptionRate) {
566 maxSpeciesConsumptionRate = rate;
567 reactionsToRescue[species] = &reaction;
568 }
569 }
570 }
571 }
572 LOG_TRACE_L1(
573 m_logger,
574 "Rescuing {} {}reactions",
575 reactionsToRescue.size(),
576 [&]() -> std::string {
577 std::ostringstream ss;
578 if (reactionsToRescue.empty()) {
579 return "";
580 }
581 int count = 0;
582 ss << "(";
583 for (const auto &reaction : reactionsToRescue | std::views::values) {
584 ss << reaction->id();
585 if (count < reactionsToRescue.size() - 1) {
586 ss << ", ";
587 }
588 count++;
589 }
590 ss << ") ";
591 return ss.str();
592 }()
593 );
594
595 LOG_TRACE_L1(
596 m_logger,
597 "Timescale adjustments due to reaction rescue: {}",
598 [&]() -> std::string {
599 std::stringstream ss;
600 if (reactionsToRescue.empty()) {
601 return "No reactions rescued...";
602 }
603 int count = 0;
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 --- [";
608 int icount = 0;
609 for (const auto& product : reaction->products()) {
610 ss << product.name() << ": " << timescales.at(product);
611 if (icount < reaction->products().size() - 1) {
612 ss << ", ";
613 }
614 icount++;
615 }
616 ss << "])";
617
618 if (count < reactionsToRescue.size() - 1) {
619 ss << ", ";
620 }
621 count++;
622 }
623
624 return ss.str();
625 }()
626 );
627
628 RescueSet rescueSet;
629 std::unordered_set<const reaction::LogicalReaction*> newReactions;
630 std::unordered_set<Species> newSpecies;
631
632 for (const auto &reactionPtr: reactionsToRescue | std::views::values) {
633 newReactions.insert(reactionPtr);
634 for (const auto& product : reactionPtr->products()) {
635 newSpecies.insert(product);
636 }
637 }
638 return {std::move(newReactions), std::move(newSpecies)};
639 }
640
642 const std::vector<const reaction::LogicalReaction *> &finalReactions
643 ) {
644 std::unordered_set<Species>finalSpeciesSet;
645 m_activeReactions.clear();
646 for (const auto* reactionPtr: finalReactions) {
647 m_activeReactions.add_reaction(*reactionPtr);
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());
651 } else {
652 LOG_TRACE_L1(m_logger, "Reactant '{}' already in active species set through another reaction.", reactant.name());
653 }
654 finalSpeciesSet.insert(reactant);
655 }
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());
659 } else {
660 LOG_TRACE_L1(m_logger, "Product '{}' already in active species set through another reaction.", product.name());
661 }
662 finalSpeciesSet.insert(product);
663 }
664 }
665 m_activeSpecies.clear();
666 m_activeSpecies = std::vector<Species>(finalSpeciesSet.begin(), finalSpeciesSet.end());
667 std::ranges::sort(
669 [](const Species &a, const Species &b) { return a.mass() < b.mass(); }
670 );
671 }
672}
673
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.
Definition reaction.h:72
TemplatedReactionSet< LogicalReaction > LogicalReactionSet
A set of logical reactions.
Definition reaction.h:563
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
Captures the result of a network priming operation.
Definition reporting.h:67
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).