2025-07-10 09:36:05 -04:00
# include "gridfire/engine/procedures/priming.h"
# include "gridfire/engine/views/engine_priming.h"
2025-07-14 14:50:49 -04:00
# include "gridfire/engine/procedures/construction.h"
2025-07-10 09:36:05 -04:00
# include "gridfire/solver/solver.h"
# include "gridfire/engine/engine_abstract.h"
# include "gridfire/network.h"
2025-07-14 14:50:49 -04:00
# include "fourdst/logging/logging.h"
# include "quill/Logger.h"
# include "quill/LogMacros.h"
2025-10-14 13:37:48 -04:00
namespace {
// Create a dummy wrapper Composition to measure the unrestricted flow rate of species
class UnrestrictedComposition final : public fourdst : : composition : : Composition {
private :
const fourdst : : atomic : : Species & m_unrestrictedSpecies ;
public :
explicit UnrestrictedComposition ( const Composition & baseComposition , const fourdst : : atomic : : Species & unrestrictedSpecies ) :
Composition ( baseComposition ) ,
m_unrestrictedSpecies ( unrestrictedSpecies ) { }
double getMolarAbundance ( const fourdst : : atomic : : Species & species ) const override {
if ( species = = m_unrestrictedSpecies ) {
return 1.0 ; // Set to a high value to simulate unrestricted abundance
}
return Composition : : getMolarAbundance ( species ) ;
}
double getMolarAbundance ( const std : : string & symbol ) const override {
if ( symbol = = m_unrestrictedSpecies . name ( ) ) {
return 1.0 ; // Set to a high value to simulate unrestricted abundance
}
return Composition : : getMolarAbundance ( symbol ) ;
}
} ;
bool isReactionIgnorable (
const gridfire : : reaction : : Reaction & reaction ,
const std : : optional < std : : vector < gridfire : : reaction : : ReactionType > > & reactionsTypesToIgnore
) {
if ( reactionsTypesToIgnore . has_value ( ) ) {
for ( const auto & type : reactionsTypesToIgnore . value ( ) ) {
if ( reaction . type ( ) = = type ) {
return true ;
}
}
}
return false ;
}
}
2025-07-10 09:36:05 -04:00
namespace gridfire {
2025-07-14 14:50:49 -04:00
using fourdst : : composition : : Composition ;
using fourdst : : atomic : : Species ;
const reaction : : Reaction * findDominantCreationChannel (
const DynamicEngine & engine ,
const Species & species ,
2025-10-10 09:12:40 -04:00
const Composition & comp ,
2025-07-14 14:50:49 -04:00
const double T9 ,
2025-10-14 13:37:48 -04:00
const double rho ,
const std : : optional < std : : vector < reaction : : ReactionType > > & reactionsTypesToIgnore
2025-07-14 14:50:49 -04:00
) {
const reaction : : Reaction * dominateReaction = nullptr ;
double maxFlow = - 1.0 ;
for ( const auto & reaction : engine . getNetworkReactions ( ) ) {
2025-10-14 13:37:48 -04:00
if ( isReactionIgnorable ( * reaction , reactionsTypesToIgnore ) ) continue ;
2025-08-14 13:33:46 -04:00
if ( reaction - > contains ( species ) & & reaction - > stoichiometry ( species ) > 0 ) {
2025-10-07 15:16:03 -04:00
const double flow = engine . calculateMolarReactionFlow ( * reaction , comp , T9 , rho ) ;
2025-07-14 14:50:49 -04:00
if ( flow > maxFlow ) {
maxFlow = flow ;
2025-08-14 13:33:46 -04:00
dominateReaction = reaction . get ( ) ;
2025-07-14 14:50:49 -04:00
}
}
}
return dominateReaction ;
}
2025-07-10 09:36:05 -04:00
2025-10-22 09:54:10 -04:00
2025-09-19 15:14:46 -04:00
/**
* @ brief Primes absent species in the network to their equilibrium abundances using a robust , two - stage approach .
*
* @ details This function implements a robust network priming algorithm that avoids the pitfalls of
* sequential , one - by - one priming . The previous , brittle method could allow an early priming
* reaction to consume all of a shared reactant , starving later reactions . This new , two - stage
* method ensures that all priming reactions are considered collectively , competing for the
* same limited pool of initial reactants in a physically consistent manner .
*
* The algorithm proceeds in three main stages :
* 1. * * Calculation Stage : * * It first loops through all species that need priming . For each one ,
* it calculates its theoretical equilibrium mass fraction and identifies the dominant
* creation channel . Crucially , it * does not * modify any abundances at this stage . Instead ,
* it stores these calculations as a list of " mass transfer requests " .
*
* 2. * * Collective Scaling Stage : * * It then processes the full list of requests to determine the
* total " debit " required from each reactant . By comparing these total debits to the
* initially available mass of each reactant , it calculates a single , global ` scalingFactor ` .
* If any reactant is overdrawn , this factor will be less than 1.0 , ensuring that no
* reactant ' s abundance can go negative .
*
* 3. * * Application Stage : * * Finally , it loops through the requests again , applying the mass
* transfers . Each calculated equilibrium mass fraction and corresponding reactant debit is
* multiplied by the global ` scalingFactor ` before being applied to the final composition .
* This ensures that if resources are limited , all primed species are scaled down proportionally .
*
* @ param netIn Input network data containing initial composition , temperature , and density .
* @ param engine DynamicEngine used to build and evaluate the reaction network .
2025-10-14 13:37:48 -04:00
* @ param ignoredReactionTypes Types of reactions to ignore during priming ( e . g . , weak reactions ) .
2025-09-19 15:14:46 -04:00
* @ return PrimingReport encapsulating the results of the priming operation , including the new
* robustly primed composition .
*/
2025-10-14 13:37:48 -04:00
PrimingReport primeNetwork (
const NetIn & netIn ,
2025-10-22 09:54:10 -04:00
GraphEngine & engine ,
2025-10-14 13:37:48 -04:00
const std : : optional < std : : vector < reaction : : ReactionType > > & ignoredReactionTypes
) {
2025-10-07 15:16:03 -04:00
auto logger = fourdst : : logging : : LogManager : : getInstance ( ) . getLogger ( " log " ) ;
2025-07-14 14:50:49 -04:00
2025-09-19 15:14:46 -04:00
// --- Initial Setup ---
// Identify all species with zero initial mass fraction that need to be primed.
2025-07-10 09:36:05 -04:00
std : : vector < Species > speciesToPrime ;
for ( const auto & entry : netIn . composition | std : : views : : values ) {
if ( entry . mass_fraction ( ) = = 0.0 ) {
speciesToPrime . push_back ( entry . isotope ( ) ) ;
}
}
2025-10-14 13:37:48 -04:00
// sort primingSpecies by mass number, lightest to heaviest. This ensures we prime in a physically sensible order.
std : : ranges : : sort ( speciesToPrime , [ ] ( const Species & a , const Species & b ) {
return a . mass ( ) < b . mass ( ) ;
} ) ;
2025-07-22 12:48:24 -04:00
LOG_DEBUG ( logger , " Priming {} species in the network. " , speciesToPrime . size ( ) ) ;
2025-07-10 09:36:05 -04:00
2025-09-19 15:14:46 -04:00
// If no species need priming, return immediately.
2025-07-14 14:50:49 -04:00
PrimingReport report ;
2025-07-10 09:36:05 -04:00
if ( speciesToPrime . empty ( ) ) {
2025-07-14 14:50:49 -04:00
report . primedComposition = netIn . composition ;
report . success = true ;
report . status = PrimingReportStatus : : NO_SPECIES_TO_PRIME ;
return report ;
}
const double T9 = netIn . temperature / 1e9 ;
const double rho = netIn . density ;
2025-07-22 12:48:24 -04:00
const auto initialReactionSet = engine . getNetworkReactions ( ) ;
2025-07-14 14:50:49 -04:00
report . status = PrimingReportStatus : : FULL_SUCCESS ;
report . success = true ;
2025-09-19 15:14:46 -04:00
// Create a mutable map of the mass fractions that we will modify.
2025-07-14 14:50:49 -04:00
std : : unordered_map < Species , double > currentMassFractions ;
for ( const auto & entry : netIn . composition | std : : views : : values ) {
currentMassFractions [ entry . isotope ( ) ] = entry . mass_fraction ( ) ;
}
2025-09-19 15:14:46 -04:00
// Ensure all species to be primed exist in the map, initialized to zero.
2025-07-14 14:50:49 -04:00
for ( const auto & entry : speciesToPrime ) {
2025-09-19 15:14:46 -04:00
currentMassFractions [ entry ] = 0.0 ;
2025-07-10 09:36:05 -04:00
}
2025-09-19 15:14:46 -04:00
// Rebuild the engine with the full network to ensure all possible creation channels are available.
2025-07-14 14:50:49 -04:00
engine . rebuild ( netIn . composition , NetworkBuildDepth : : Full ) ;
2025-09-19 15:14:46 -04:00
// --- STAGE 1: Calculation and Bookkeeping (No Modifications) ---
// In this stage, we calculate all required mass transfers but do not apply them yet.
// A struct to hold the result of each individual priming calculation.
struct MassTransferRequest {
Species species_to_prime ;
double equilibrium_mass_fraction ;
std : : vector < Species > reactants ;
} ;
std : : vector < MassTransferRequest > requests ;
2025-07-14 14:50:49 -04:00
2025-09-19 15:14:46 -04:00
for ( const auto & primingSpecies : speciesToPrime ) {
// Create a temporary composition reflecting the current state for rate calculations.
2025-07-14 14:50:49 -04:00
Composition tempComp ;
for ( const auto & [ sp , mf ] : currentMassFractions ) {
tempComp . registerSymbol ( std : : string ( sp . name ( ) ) ) ;
2025-09-19 15:14:46 -04:00
tempComp . setMassFraction ( sp , std : : max ( 0.0 , mf ) ) ;
2025-07-14 14:50:49 -04:00
}
2025-10-14 13:37:48 -04:00
bool didFinalize = tempComp . finalize ( true ) ;
if ( ! didFinalize ) {
LOG_ERROR ( logger , " Failed to finalize temporary composition during priming. " ) ;
report . success = false ;
report . status = PrimingReportStatus : : FAILED_TO_FINALIZE_COMPOSITION ;
continue ;
}
2025-07-14 14:50:49 -04:00
2025-07-10 09:36:05 -04:00
NetworkPrimingEngineView primer ( primingSpecies , engine ) ;
2025-07-14 14:50:49 -04:00
if ( primer . getNetworkReactions ( ) . size ( ) = = 0 ) {
LOG_ERROR ( logger , " No priming reactions found for species {}. " , primingSpecies . name ( ) ) ;
report . success = false ;
report . status = PrimingReportStatus : : FAILED_TO_FIND_PRIMING_REACTIONS ;
continue ;
}
2025-10-14 13:37:48 -04:00
const double destructionRateConstant = calculateDestructionRateConstant (
primer ,
primingSpecies ,
tempComp ,
T9 ,
rho ,
ignoredReactionTypes
) ;
2025-07-10 09:36:05 -04:00
2025-07-14 14:50:49 -04:00
if ( destructionRateConstant > 1e-99 ) {
2025-10-14 13:37:48 -04:00
const double creationRate = calculateCreationRate (
primer ,
primingSpecies ,
tempComp ,
T9 ,
rho ,
ignoredReactionTypes
) ;
2025-09-19 15:14:46 -04:00
double equilibriumMassFraction = ( creationRate / destructionRateConstant ) * primingSpecies . mass ( ) ;
2025-10-14 13:37:48 -04:00
// ReSharper disable once CppDFAUnusedValue
2025-09-19 15:14:46 -04:00
if ( std : : isnan ( equilibriumMassFraction ) ) equilibriumMassFraction = 0.0 ;
2025-07-14 14:50:49 -04:00
2025-10-14 13:37:48 -04:00
if ( const reaction : : Reaction * dominantChannel = findDominantCreationChannel (
primer ,
primingSpecies ,
tempComp ,
T9 ,
rho ,
ignoredReactionTypes )
) {
2025-09-19 15:14:46 -04:00
// Store the request instead of applying it immediately.
requests . push_back ( { primingSpecies , equilibriumMassFraction , dominantChannel - > reactants ( ) } ) ;
} else {
LOG_ERROR ( logger , " Failed to find dominant creation channel for {}. " , primingSpecies . name ( ) ) ;
report . status = PrimingReportStatus : : FAILED_TO_FIND_CREATION_CHANNEL ;
}
} else {
LOG_WARNING ( logger , " No destruction channel found for {}. Using fallback abundance. " , primingSpecies . name ( ) ) ;
// For species with no destruction, we can't calculate an equilibrium.
// We add a request with a tiny fallback abundance to ensure it exists in the network.
requests . push_back ( { primingSpecies , 1e-40 , { } } ) ;
}
}
2025-07-14 14:50:49 -04:00
2025-09-19 15:14:46 -04:00
// --- STAGE 2: Collective Scaling Based on Reactant Availability ---
// Now, we determine the total demand for each reactant and find a global scaling factor.
2025-07-14 14:50:49 -04:00
2025-09-19 15:14:46 -04:00
std : : unordered_map < Species , double > total_mass_debits ;
for ( const auto & req : requests ) {
if ( req . reactants . empty ( ) ) continue ; // Skip fallbacks which don't consume reactants.
2025-07-14 14:50:49 -04:00
2025-09-19 15:14:46 -04:00
double totalReactantMass = 0.0 ;
for ( const auto & reactant : req . reactants ) {
totalReactantMass + = reactant . mass ( ) ;
}
if ( totalReactantMass = = 0.0 ) continue ;
for ( const auto & reactant : req . reactants ) {
const double massToSubtract = req . equilibrium_mass_fraction * ( reactant . mass ( ) / totalReactantMass ) ;
total_mass_debits [ reactant ] + = massToSubtract ;
}
}
double globalScalingFactor = 1.0 ;
for ( const auto & [ reactant , total_debit ] : total_mass_debits ) {
double availableMass ;
if ( currentMassFractions . contains ( reactant ) ) {
availableMass = currentMassFractions . at ( reactant ) ;
} else {
availableMass = 0.0 ;
}
if ( total_debit > availableMass & & availableMass > 0 ) {
globalScalingFactor = std : : min ( globalScalingFactor , availableMass / total_debit ) ;
}
}
if ( globalScalingFactor < 1.0 ) {
LOG_WARNING ( logger , " Priming was limited by reactant availability. All transfers will be scaled by {:.4e} " , globalScalingFactor ) ;
}
// --- STAGE 3: Application of Scaled Mass Transfers ---
// Finally, apply all the transfers, scaled by our global factor.
std : : unordered_map < Species , double > totalMassFractionChanges ;
for ( const auto & [ species_to_prime , equilibrium_mass_fraction , reactants ] : requests ) {
const double scaled_equilibrium_mf = equilibrium_mass_fraction * globalScalingFactor ;
// Add the scaled mass to the primed species.
currentMassFractions . at ( species_to_prime ) + = scaled_equilibrium_mf ;
totalMassFractionChanges [ species_to_prime ] + = scaled_equilibrium_mf ;
2025-07-14 14:50:49 -04:00
2025-09-19 15:14:46 -04:00
// Subtract the scaled mass from the reactants.
if ( ! reactants . empty ( ) ) {
double totalReactantMass = 0.0 ;
for ( const auto & reactant : reactants ) {
totalReactantMass + = reactant . mass ( ) ;
}
if ( totalReactantMass = = 0.0 ) continue ;
2025-07-14 14:50:49 -04:00
2025-09-19 15:14:46 -04:00
for ( const auto & reactant : reactants ) {
const double massToSubtract = scaled_equilibrium_mf * ( reactant . mass ( ) / totalReactantMass ) ;
if ( massToSubtract ! = 0 ) {
currentMassFractions . at ( reactant ) - = massToSubtract ;
2025-07-14 14:50:49 -04:00
totalMassFractionChanges [ reactant ] - = massToSubtract ;
}
}
}
}
// --- Final Composition Construction ---
std : : vector < std : : string > final_symbols ;
std : : vector < double > final_mass_fractions ;
for ( const auto & [ species , mass_fraction ] : currentMassFractions ) {
2025-08-14 13:33:46 -04:00
final_symbols . emplace_back ( species . name ( ) ) ;
2025-09-19 15:14:46 -04:00
final_mass_fractions . push_back ( std : : max ( 0.0 , mass_fraction ) ) ; // Ensure no negative mass fractions.
2025-07-14 14:50:49 -04:00
}
Composition primedComposition ( final_symbols , final_mass_fractions , true ) ;
report . primedComposition = primedComposition ;
for ( const auto & [ species , change ] : totalMassFractionChanges ) {
report . massFractionChanges . emplace_back ( species , change ) ;
2025-07-10 09:36:05 -04:00
}
2025-09-19 15:14:46 -04:00
// Restore the engine to its original, smaller network state.
2025-07-22 12:48:24 -04:00
engine . setNetworkReactions ( initialReactionSet ) ;
2025-07-14 14:50:49 -04:00
return report ;
2025-07-10 09:36:05 -04:00
}
2025-07-14 14:50:49 -04:00
double calculateDestructionRateConstant (
2025-10-14 13:37:48 -04:00
const DynamicEngine & engine ,
const Species & species ,
const Composition & composition ,
const double T9 ,
const double rho ,
const std : : optional < std : : vector < reaction : : ReactionType > > & reactionTypesToIgnore
) {
const UnrestrictedComposition unrestrictedComp ( composition , species ) ; // Create a composition that simulates an enormous source abundance of the target species (getMolarAbundance(species) always returns 1.0)
2025-07-10 09:36:05 -04:00
double destructionRateConstant = 0.0 ;
for ( const auto & reaction : engine . getNetworkReactions ( ) ) {
2025-10-14 13:37:48 -04:00
if ( isReactionIgnorable ( * reaction , reactionTypesToIgnore ) ) continue ;
const int stoichiometry = reaction - > stoichiometry ( species ) ;
if ( stoichiometry < 0 ) {
destructionRateConstant + = std : : abs ( stoichiometry ) * engine . calculateMolarReactionFlow ( * reaction , unrestrictedComp , T9 , rho ) ;
2025-07-10 09:36:05 -04:00
}
}
return destructionRateConstant ;
}
double calculateCreationRate (
const DynamicEngine & engine ,
2025-08-15 12:11:32 -04:00
const Species & species ,
2025-10-14 13:37:48 -04:00
const Composition & composition ,
2025-07-10 09:36:05 -04:00
const double T9 ,
2025-10-14 13:37:48 -04:00
const double rho ,
const std : : optional < std : : vector < reaction : : ReactionType > > & reactionTypesToIgnore
2025-07-10 09:36:05 -04:00
) {
double creationRate = 0.0 ;
for ( const auto & reaction : engine . getNetworkReactions ( ) ) {
2025-10-14 13:37:48 -04:00
if ( isReactionIgnorable ( * reaction , reactionTypesToIgnore ) ) continue ;
2025-08-14 13:33:46 -04:00
const int stoichiometry = reaction - > stoichiometry ( species ) ;
2025-07-10 09:36:05 -04:00
if ( stoichiometry > 0 ) {
2025-10-14 13:37:48 -04:00
creationRate + = stoichiometry * engine . calculateMolarReactionFlow ( * reaction , composition , T9 , rho ) ;
2025-07-10 09:36:05 -04:00
}
}
return creationRate ;
}
}