2025-07-01 07:24:18 -04:00
# include "../../../include/gridfire/engine/views/engine_adaptive.h"
2025-06-29 14:52:16 -04:00
# include <ranges>
2025-06-29 14:53:39 -04:00
# include <queue>
2025-06-29 14:52:16 -04:00
# include "gridfire/network.h"
# include "quill/LogMacros.h"
2025-06-29 14:53:39 -04:00
# include "quill/Logger.h"
2025-06-29 14:52:16 -04:00
namespace gridfire {
using fourdst : : atomic : : Species ;
AdaptiveEngineView : : AdaptiveEngineView (
DynamicEngine & baseEngine
) :
m_baseEngine ( baseEngine ) ,
m_activeSpecies ( baseEngine . getNetworkSpecies ( ) ) ,
m_activeReactions ( baseEngine . getNetworkReactions ( ) ) ,
2025-06-29 14:53:39 -04:00
m_speciesIndexMap ( constructSpeciesIndexMap ( ) ) ,
m_reactionIndexMap ( constructReactionIndexMap ( ) )
2025-06-29 14:52:16 -04:00
{
}
std : : vector < size_t > AdaptiveEngineView : : constructSpeciesIndexMap ( ) const {
LOG_TRACE_L1 ( m_logger , " Constructing species index map for adaptive engine view... " ) ;
std : : unordered_map < Species , size_t > fullSpeciesReverseMap ;
const auto & fullSpeciesList = m_baseEngine . getNetworkSpecies ( ) ;
fullSpeciesReverseMap . reserve ( fullSpeciesList . size ( ) ) ;
for ( size_t i = 0 ; i < fullSpeciesList . size ( ) ; + + i ) {
fullSpeciesReverseMap [ fullSpeciesList [ i ] ] = i ;
}
std : : vector < size_t > speciesIndexMap ;
speciesIndexMap . reserve ( m_activeSpecies . size ( ) ) ;
for ( const auto & active_species : m_activeSpecies ) {
auto it = fullSpeciesReverseMap . find ( active_species ) ;
if ( it ! = fullSpeciesReverseMap . end ( ) ) {
speciesIndexMap . push_back ( it - > second ) ;
} else {
LOG_ERROR ( m_logger , " Species '{}' not found in full species map. " , active_species . name ( ) ) ;
2025-06-29 14:53:39 -04:00
m_logger - > flush_log ( ) ;
2025-06-29 14:52:16 -04:00
throw std : : runtime_error ( " Species not found in full species map: " + std : : string ( active_species . name ( ) ) ) ;
}
}
2025-06-29 14:53:39 -04:00
LOG_TRACE_L1 ( m_logger , " Species index map constructed with {} entries. " , speciesIndexMap . size ( ) ) ;
2025-06-29 14:52:16 -04:00
return speciesIndexMap ;
}
2025-06-29 14:53:39 -04:00
std : : vector < size_t > AdaptiveEngineView : : constructReactionIndexMap ( ) const {
LOG_TRACE_L1 ( m_logger , " Constructing reaction index map for adaptive engine view... " ) ;
// --- Step 1: Create a reverse map using the reaction's unique ID as the key. ---
std : : unordered_map < std : : string_view , size_t > fullReactionReverseMap ;
const auto & fullReactionSet = m_baseEngine . getNetworkReactions ( ) ;
fullReactionReverseMap . reserve ( fullReactionSet . size ( ) ) ;
for ( size_t i_full = 0 ; i_full < fullReactionSet . size ( ) ; + + i_full ) {
fullReactionReverseMap [ fullReactionSet [ i_full ] . id ( ) ] = i_full ;
}
// --- Step 2: Build the final index map using the active reaction set. ---
std : : vector < size_t > reactionIndexMap ;
reactionIndexMap . reserve ( m_activeReactions . size ( ) ) ;
for ( const auto & active_reaction_ptr : m_activeReactions ) {
auto it = fullReactionReverseMap . find ( active_reaction_ptr . id ( ) ) ;
if ( it ! = fullReactionReverseMap . end ( ) ) {
reactionIndexMap . push_back ( it - > second ) ;
} else {
LOG_ERROR ( m_logger , " Active reaction '{}' not found in base engine during reaction index map construction. " , active_reaction_ptr . id ( ) ) ;
m_logger - > flush_log ( ) ;
throw std : : runtime_error ( " Mismatch between active reactions and base engine. " ) ;
}
}
LOG_TRACE_L1 ( m_logger , " Reaction index map constructed with {} entries. " , reactionIndexMap . size ( ) ) ;
return reactionIndexMap ;
}
2025-06-29 14:52:16 -04:00
void AdaptiveEngineView : : update ( const NetIn & netIn ) {
2025-07-01 07:24:18 -04:00
LOG_TRACE_L1 ( m_logger , " Updating AdaptiveEngineView with new network input... " ) ;
2025-06-29 14:52:16 -04:00
2025-07-01 07:24:18 -04:00
std : : vector < double > Y_Full ;
std : : vector < ReactionFlow > allFlows = calculateAllReactionFlows ( netIn , Y_Full ) ;
2025-06-29 14:53:39 -04:00
2025-07-01 07:24:18 -04:00
double maxFlow = 0.0 ;
2025-06-29 14:53:39 -04:00
2025-07-01 07:24:18 -04:00
for ( const auto & [ reactionPtr , flowRate ] : allFlows ) {
if ( flowRate > maxFlow ) {
maxFlow = flowRate ;
2025-06-29 14:53:39 -04:00
}
}
2025-07-01 07:24:18 -04:00
LOG_DEBUG ( m_logger , " Maximum reaction flow rate in adaptive engine view: {:0.3E} [mol/s] " , maxFlow ) ;
2025-06-29 14:53:39 -04:00
2025-07-01 07:24:18 -04:00
const std : : unordered_set < Species > reachableSpecies = findReachableSpecies ( netIn ) ;
LOG_DEBUG ( m_logger , " Found {} reachable species in adaptive engine view. " , reachableSpecies . size ( ) ) ;
2025-06-29 14:53:39 -04:00
2025-07-01 07:24:18 -04:00
const std : : vector < const reaction : : LogicalReaction * > finalReactions = cullReactionsByFlow ( allFlows , reachableSpecies , Y_Full , maxFlow ) ;
2025-06-29 14:53:39 -04:00
2025-07-01 07:24:18 -04:00
finalizeActiveSet ( finalReactions ) ;
2025-06-29 14:53:39 -04:00
m_speciesIndexMap = constructSpeciesIndexMap ( ) ;
m_reactionIndexMap = constructReactionIndexMap ( ) ;
m_isStale = false ;
2025-07-01 07:24:18 -04:00
LOG_INFO ( m_logger , " AdaptiveEngineView updated successfully with {} active species and {} active reactions. " , m_activeSpecies . size ( ) , m_activeReactions . size ( ) ) ;
2025-06-29 14:52:16 -04:00
}
const std : : vector < Species > & AdaptiveEngineView : : getNetworkSpecies ( ) const {
return m_activeSpecies ;
}
StepDerivatives < double > AdaptiveEngineView : : calculateRHSAndEnergy (
2025-06-29 14:53:39 -04:00
const std : : vector < double > & Y_culled ,
2025-06-29 14:52:16 -04:00
const double T9 ,
const double rho
) const {
2025-06-29 14:53:39 -04:00
validateState ( ) ;
const auto Y_full = mapCulledToFull ( Y_culled ) ;
const auto [ dydt , nuclearEnergyGenerationRate ] = m_baseEngine . calculateRHSAndEnergy ( Y_full , T9 , rho ) ;
StepDerivatives < double > culledResults ;
culledResults . nuclearEnergyGenerationRate = nuclearEnergyGenerationRate ;
culledResults . dydt = mapFullToCulled ( dydt ) ;
return culledResults ;
2025-06-29 14:52:16 -04:00
}
void AdaptiveEngineView : : generateJacobianMatrix (
2025-06-29 14:53:39 -04:00
const std : : vector < double > & Y_culled ,
2025-06-29 14:52:16 -04:00
const double T9 ,
const double rho
) {
2025-06-29 14:53:39 -04:00
validateState ( ) ;
const auto Y_full = mapCulledToFull ( Y_culled ) ;
m_baseEngine . generateJacobianMatrix ( Y_full , T9 , rho ) ;
2025-06-29 14:52:16 -04:00
}
double AdaptiveEngineView : : getJacobianMatrixEntry (
2025-06-29 14:53:39 -04:00
const int i_culled ,
const int j_culled
2025-06-29 14:52:16 -04:00
) const {
2025-06-29 14:53:39 -04:00
validateState ( ) ;
const size_t i_full = mapCulledToFullSpeciesIndex ( i_culled ) ;
const size_t j_full = mapCulledToFullSpeciesIndex ( j_culled ) ;
return m_baseEngine . getJacobianMatrixEntry ( i_full , j_full ) ;
2025-06-29 14:52:16 -04:00
}
void AdaptiveEngineView : : generateStoichiometryMatrix ( ) {
2025-06-29 14:53:39 -04:00
validateState ( ) ;
2025-06-29 14:52:16 -04:00
m_baseEngine . generateStoichiometryMatrix ( ) ;
}
int AdaptiveEngineView : : getStoichiometryMatrixEntry (
2025-06-29 14:53:39 -04:00
const int speciesIndex_culled ,
const int reactionIndex_culled
2025-06-29 14:52:16 -04:00
) const {
2025-06-29 14:53:39 -04:00
validateState ( ) ;
const size_t speciesIndex_full = mapCulledToFullSpeciesIndex ( speciesIndex_culled ) ;
const size_t reactionIndex_full = mapCulledToFullReactionIndex ( reactionIndex_culled ) ;
return m_baseEngine . getStoichiometryMatrixEntry ( speciesIndex_full , reactionIndex_full ) ;
2025-06-29 14:52:16 -04:00
}
double AdaptiveEngineView : : calculateMolarReactionFlow (
const reaction : : Reaction & reaction ,
2025-06-29 14:53:39 -04:00
const std : : vector < double > & Y_culled ,
2025-06-29 14:52:16 -04:00
const double T9 ,
const double rho
) const {
2025-06-29 14:53:39 -04:00
validateState ( ) ;
if ( ! m_activeReactions . contains ( reaction ) ) {
LOG_ERROR ( m_logger , " Reaction '{}' is not part of the active reactions in the adaptive engine view. " , reaction . id ( ) ) ;
m_logger - > flush_log ( ) ;
throw std : : runtime_error ( " Reaction not found in active reactions: " + std : : string ( reaction . id ( ) ) ) ;
}
const auto Y = mapCulledToFull ( Y_culled ) ;
2025-06-29 14:52:16 -04:00
return m_baseEngine . calculateMolarReactionFlow ( reaction , Y , T9 , rho ) ;
}
2025-06-29 14:53:39 -04:00
const reaction : : LogicalReactionSet & AdaptiveEngineView : : getNetworkReactions ( ) const {
2025-06-29 14:52:16 -04:00
return m_activeReactions ;
}
2025-06-29 14:53:39 -04:00
std : : unordered_map < Species , double > AdaptiveEngineView : : getSpeciesTimescales (
const std : : vector < double > & Y_culled ,
2025-06-29 14:52:16 -04:00
const double T9 ,
const double rho
) const {
2025-06-29 14:53:39 -04:00
validateState ( ) ;
const auto Y_full = mapCulledToFull ( Y_culled ) ;
const auto fullTimescales = m_baseEngine . getSpeciesTimescales ( Y_full , T9 , rho ) ;
std : : unordered_map < Species , double > culledTimescales ;
culledTimescales . reserve ( m_activeSpecies . size ( ) ) ;
for ( const auto & active_species : m_activeSpecies ) {
if ( fullTimescales . contains ( active_species ) ) {
culledTimescales [ active_species ] = fullTimescales . at ( active_species ) ;
2025-06-29 14:52:16 -04:00
}
}
2025-06-29 14:53:39 -04:00
return culledTimescales ;
}
2025-07-01 11:40:03 -04:00
void AdaptiveEngineView : : setScreeningModel ( const screening : : ScreeningType model ) {
m_baseEngine . setScreeningModel ( model ) ;
}
screening : : ScreeningType AdaptiveEngineView : : getScreeningModel ( ) const {
return m_baseEngine . getScreeningModel ( ) ;
}
2025-06-29 14:53:39 -04:00
std : : vector < double > AdaptiveEngineView : : mapCulledToFull ( const std : : vector < double > & culled ) const {
std : : vector < double > full ( m_baseEngine . getNetworkSpecies ( ) . size ( ) , 0.0 ) ;
for ( size_t i_culled = 0 ; i_culled < culled . size ( ) ; + + i_culled ) {
const size_t i_full = m_speciesIndexMap [ i_culled ] ;
full [ i_full ] + = culled [ i_culled ] ;
}
return full ;
}
std : : vector < double > AdaptiveEngineView : : mapFullToCulled ( const std : : vector < double > & full ) const {
std : : vector < double > culled ( m_activeSpecies . size ( ) , 0.0 ) ;
for ( size_t i_culled = 0 ; i_culled < m_activeSpecies . size ( ) ; + + i_culled ) {
const size_t i_full = m_speciesIndexMap [ i_culled ] ;
culled [ i_culled ] = full [ i_full ] ;
}
return culled ;
}
size_t AdaptiveEngineView : : mapCulledToFullSpeciesIndex ( size_t culledSpeciesIndex ) const {
if ( culledSpeciesIndex < 0 | | culledSpeciesIndex > = static_cast < int > ( m_speciesIndexMap . size ( ) ) ) {
LOG_ERROR ( m_logger , " Culled index {} is out of bounds for species index map of size {}. " , culledSpeciesIndex , m_speciesIndexMap . size ( ) ) ;
m_logger - > flush_log ( ) ;
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 ( ) ) + " . " ) ;
}
return m_speciesIndexMap [ culledSpeciesIndex ] ;
}
size_t AdaptiveEngineView : : mapCulledToFullReactionIndex ( size_t culledReactionIndex ) const {
if ( culledReactionIndex < 0 | | culledReactionIndex > = static_cast < int > ( m_reactionIndexMap . size ( ) ) ) {
LOG_ERROR ( m_logger , " Culled index {} is out of bounds for reaction index map of size {}. " , culledReactionIndex , m_reactionIndexMap . size ( ) ) ;
m_logger - > flush_log ( ) ;
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 ( ) ) + " . " ) ;
}
return m_reactionIndexMap [ culledReactionIndex ] ;
}
void AdaptiveEngineView : : validateState ( ) const {
if ( m_isStale ) {
LOG_ERROR ( m_logger , " AdaptiveEngineView is stale. Please call update() before calculating RHS and energy. " ) ;
m_logger - > flush_log ( ) ;
throw std : : runtime_error ( " AdaptiveEngineView is stale. Please call update() before calculating RHS and energy. " ) ;
}
2025-06-29 14:52:16 -04:00
}
2025-07-01 07:24:18 -04:00
std : : vector < AdaptiveEngineView : : ReactionFlow > AdaptiveEngineView : : calculateAllReactionFlows (
const NetIn & netIn ,
std : : vector < double > & out_Y_Full
) const {
const auto & fullSpeciesList = m_baseEngine . getNetworkSpecies ( ) ;
out_Y_Full . clear ( ) ;
out_Y_Full . reserve ( fullSpeciesList . size ( ) ) ;
for ( const auto & species : fullSpeciesList ) {
if ( netIn . composition . contains ( species ) ) {
out_Y_Full . push_back ( netIn . composition . getMolarAbundance ( std : : string ( species . name ( ) ) ) ) ;
} else {
LOG_TRACE_L2 ( m_logger , " Species '{}' not found in composition. Setting abundance to 0.0. " , species . name ( ) ) ;
out_Y_Full . push_back ( 0.0 ) ;
}
}
const double T9 = netIn . temperature / 1e9 ; // Convert temperature from Kelvin to T9 (T9 = T / 1e9)
const double rho = netIn . density ; // Density in g/cm^3
std : : vector < ReactionFlow > reactionFlows ;
const auto & fullReactionSet = m_baseEngine . getNetworkReactions ( ) ;
reactionFlows . reserve ( fullReactionSet . size ( ) ) ;
for ( const auto & reaction : fullReactionSet ) {
const double flow = m_baseEngine . calculateMolarReactionFlow ( reaction , out_Y_Full , T9 , rho ) ;
reactionFlows . push_back ( { & reaction , flow } ) ;
LOG_TRACE_L2 ( m_logger , " Reaction '{}' has flow rate: {:0.3E} [mol/s] " , reaction . id ( ) , flow ) ;
}
return reactionFlows ;
}
std : : unordered_set < Species > AdaptiveEngineView : : findReachableSpecies (
const NetIn & netIn
) const {
std : : unordered_set < Species > reachable ;
std : : queue < Species > to_vist ;
constexpr double ABUNDANCE_FLOOR = 1e-12 ; // Abundance floor for a species to be considered part of the initial fuel
for ( const auto & species : m_baseEngine . getNetworkSpecies ( ) ) {
if ( netIn . composition . contains ( species ) & & netIn . composition . getMassFraction ( std : : string ( species . name ( ) ) ) > ABUNDANCE_FLOOR ) {
if ( ! reachable . contains ( species ) ) {
to_vist . push ( species ) ;
reachable . insert ( species ) ;
LOG_TRACE_L2 ( m_logger , " Network Connectivity Analysis: Species '{}' is part of the initial fuel. " , species . name ( ) ) ;
}
}
}
bool new_species_found_in_pass = true ;
while ( new_species_found_in_pass ) {
new_species_found_in_pass = false ;
for ( const auto & reaction : m_baseEngine . getNetworkReactions ( ) ) {
bool all_reactants_reachable = true ;
for ( const auto & reactant : reaction . reactants ( ) ) {
if ( ! reachable . contains ( reactant ) ) {
all_reactants_reachable = false ;
break ;
}
}
if ( all_reactants_reachable ) {
for ( const auto & product : reaction . products ( ) ) {
if ( ! reachable . contains ( product ) ) {
reachable . insert ( product ) ;
new_species_found_in_pass = true ;
LOG_TRACE_L2 ( m_logger , " Network Connectivity Analysis: Species '{}' is reachable via reaction '{}'. " , product . name ( ) , reaction . id ( ) ) ;
}
}
}
}
}
return reachable ;
}
std : : vector < const reaction : : LogicalReaction * > AdaptiveEngineView : : cullReactionsByFlow (
const std : : vector < ReactionFlow > & allFlows ,
const std : : unordered_set < fourdst : : atomic : : Species > & reachableSpecies ,
const std : : vector < double > & Y_full ,
const double maxFlow
) const {
LOG_TRACE_L1 ( m_logger , " Culling reactions based on flow rates... " ) ;
2025-07-01 11:40:03 -04:00
const auto relative_culling_threshold = m_config . get < double > ( " gridfire:AdaptiveEngineView:RelativeCullingThreshold " , 1e-75 ) ;
2025-07-01 07:24:18 -04:00
double absoluteCullingThreshold = relative_culling_threshold * maxFlow ;
LOG_DEBUG ( m_logger , " Relative culling threshold: {:0.3E} ({}) " , relative_culling_threshold , absoluteCullingThreshold ) ;
std : : vector < const reaction : : LogicalReaction * > culledReactions ;
for ( const auto & [ reactionPtr , flowRate ] : allFlows ) {
bool keepReaction = false ;
if ( flowRate > absoluteCullingThreshold ) {
LOG_TRACE_L2 ( m_logger , " Maintaining reaction '{}' with relative (abs) flow rate: {:0.3E} ({:0.3E} [mol/s]) " , reactionPtr - > id ( ) , flowRate / maxFlow , flowRate ) ;
keepReaction = true ;
} else {
bool zero_flow_due_to_reachable_reactants = false ;
if ( flowRate < 1e-99 ) {
for ( const auto & reactant : reactionPtr - > reactants ( ) ) {
const auto it = std : : ranges : : find ( m_baseEngine . getNetworkSpecies ( ) , reactant ) ;
const size_t index = std : : distance ( m_baseEngine . getNetworkSpecies ( ) . begin ( ) , it ) ;
if ( Y_full [ index ] < 1e-99 & & reachableSpecies . contains ( reactant ) ) {
LOG_TRACE_L2 ( m_logger , " Maintaining reaction '{}' with zero flow due to reachable reactant '{}'. " , reactionPtr - > id ( ) , reactant . name ( ) ) ;
zero_flow_due_to_reachable_reactants = true ;
break ;
}
}
}
if ( zero_flow_due_to_reachable_reactants ) {
keepReaction = true ;
}
}
if ( keepReaction ) {
culledReactions . push_back ( reactionPtr ) ;
} else {
LOG_TRACE_L1 ( m_logger , " Culling reaction '{}' due to low flow rate or lack of connectivity. " , reactionPtr - > id ( ) ) ;
}
}
LOG_DEBUG ( m_logger , " Selected {} (total: {}, culled: {}) reactions based on flow rates. " , culledReactions . size ( ) , allFlows . size ( ) , allFlows . size ( ) - culledReactions . size ( ) ) ;
return culledReactions ;
}
void AdaptiveEngineView : : finalizeActiveSet (
const std : : vector < const reaction : : LogicalReaction * > & finalReactions
) {
std : : unordered_set < Species > finalSpeciesSet ;
m_activeReactions . clear ( ) ;
for ( const auto * reactionPtr : finalReactions ) {
m_activeReactions . add_reaction ( * reactionPtr ) ;
for ( const auto & reactant : reactionPtr - > reactants ( ) ) {
finalSpeciesSet . insert ( reactant ) ;
}
for ( const auto & product : reactionPtr - > products ( ) ) {
finalSpeciesSet . insert ( product ) ;
}
}
m_activeSpecies . assign ( finalSpeciesSet . begin ( ) , finalSpeciesSet . end ( ) ) ;
std : : ranges : : sort (
m_activeSpecies ,
[ ] ( const Species & a , const Species & b ) { return a . mass ( ) < b . mass ( ) ; }
) ;
}
2025-06-29 14:52:16 -04:00
}