2025-04-21 08:56:45 -04:00
/* ***********************************************************************
//
// Copyright (C) 2025 -- The 4D-STAR Collaboration
// File Author: Emily Boudreaux
2025-10-06 14:29:33 -04:00
// Last Modified: October 6, 2025
2025-04-21 08:56:45 -04:00
//
// 4DSSE is free software; you can use it and/or modify
// it under the terms and restrictions the GNU General Library Public
// License version 3 (GPLv3) as published by the Free Software Foundation.
//
// 4DSSE is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with this software; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// *********************************************************************** */
2025-03-24 12:58:30 -04:00
# include "quill/LogMacros.h"
# include <stdexcept>
2025-03-25 12:49:37 -04:00
# include <unordered_map>
# include <vector>
# include <array>
2025-06-16 15:00:33 -04:00
# include <ranges>
2025-09-16 11:23:01 -04:00
# include <algorithm>
2025-03-25 12:49:37 -04:00
# include <utility>
2025-03-24 12:58:30 -04:00
2025-06-22 04:56:04 -04:00
# include "fourdst/composition/atomicSpecies.h"
# include "fourdst/composition/species.h"
# include "fourdst/composition/composition.h"
2025-10-06 14:29:33 -04:00
# include <numeric>
2025-07-21 07:48:00 -04:00
# include "fourdst/composition/exceptions/exceptions_composition.h"
2025-03-24 12:58:30 -04:00
2025-09-16 11:23:01 -04:00
namespace {
template < typename A , typename B >
2025-10-06 14:29:33 -04:00
std : : vector < A > sortVectorBy (
std : : vector < A > toSort ,
const std : : vector < B > & by
) {
2025-09-16 11:23:01 -04:00
std : : vector < std : : size_t > indices ( by . size ( ) ) ;
for ( size_t i = 0 ; i < indices . size ( ) ; i + + ) {
indices [ i ] = i ;
}
std : : ranges : : sort ( indices , [ & ] ( size_t a , size_t b ) {
return by [ a ] < by [ b ] ;
} ) ;
std : : vector < A > sorted ;
sorted . reserve ( indices . size ( ) ) ;
for ( const auto idx : indices ) {
sorted . push_back ( toSort [ idx ] ) ;
}
return sorted ;
}
}
2025-06-21 11:33:27 -04:00
namespace fourdst : : composition {
2025-03-24 12:58:30 -04:00
2025-06-28 06:31:41 -04:00
CompositionEntry : : CompositionEntry ( ) :
m_symbol ( " H-1 " ) ,
m_isotope ( fourdst : : atomic : : species . at ( " H-1 " ) ) ,
2025-10-06 14:29:33 -04:00
m_initialized ( false ) { }
CompositionEntry : : CompositionEntry (
const std : : string & symbol ,
const bool massFracMode
) :
m_symbol ( symbol ) ,
m_isotope ( atomic : : species . at ( symbol ) ) ,
m_massFracMode ( massFracMode ) {
2025-06-16 15:00:33 -04:00
setSpecies ( symbol ) ;
2025-03-25 12:49:37 -04:00
}
2025-03-24 12:58:30 -04:00
2025-10-06 14:29:33 -04:00
CompositionEntry : : CompositionEntry ( const CompositionEntry & entry ) = default ;
2025-03-25 12:49:37 -04:00
2025-06-16 15:00:33 -04:00
void CompositionEntry : : setSpecies ( const std : : string & symbol ) {
if ( m_initialized ) {
2025-07-21 07:48:00 -04:00
throw exceptions : : EntryAlreadyInitializedError ( " Composition entry is already initialized. " ) ;
2025-06-16 15:00:33 -04:00
}
2025-06-25 08:26:50 -04:00
if ( ! fourdst : : atomic : : species . contains ( symbol ) ) {
2025-07-21 07:48:00 -04:00
throw exceptions : : InvalidSpeciesSymbolError ( " Invalid symbol. " ) ;
2025-06-16 15:00:33 -04:00
}
m_symbol = symbol ;
2025-10-06 14:29:33 -04:00
m_isotope = atomic : : species . at ( symbol ) ;
2025-06-16 15:00:33 -04:00
m_initialized = true ;
2025-03-24 12:58:30 -04:00
}
2025-06-16 15:00:33 -04:00
std : : string CompositionEntry : : symbol ( ) const {
return m_symbol ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
double CompositionEntry : : mass_fraction ( ) const {
if ( ! m_massFracMode ) {
2025-07-21 07:48:00 -04:00
throw exceptions : : CompositionModeError ( " Composition entry is in number fraction mode. " ) ;
2025-06-16 15:00:33 -04:00
}
2025-10-06 14:29:33 -04:00
// X_i = (moles_i / mass_total) * (mass_i / moles_i) = m_molesPerMass * A_i
return m_molesPerMass * m_isotope . mass ( ) ;
2025-03-24 12:58:30 -04:00
}
2025-03-25 12:49:37 -04:00
2025-06-16 15:00:33 -04:00
double CompositionEntry : : number_fraction ( ) const {
if ( m_massFracMode ) {
2025-07-21 07:48:00 -04:00
throw exceptions : : CompositionModeError ( " Composition entry is in mass fraction mode. " ) ;
2025-06-16 15:00:33 -04:00
}
2025-10-06 14:29:33 -04:00
// In number fraction mode, the value is cached during the mode switch.
return m_cachedNumberFraction ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double CompositionEntry : : number_fraction (
const double totalMolesPerMass
) const {
// n_i = (moles_i / mass_total) / (moles_total / mass_total)
if ( totalMolesPerMass = = 0.0 ) return 0.0 ;
return m_molesPerMass / totalMolesPerMass ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
double CompositionEntry : : rel_abundance ( ) const {
2025-10-06 14:29:33 -04:00
return m_molesPerMass ;
2025-03-24 12:58:30 -04:00
}
2025-10-06 14:29:33 -04:00
atomic : : Species CompositionEntry : : isotope ( ) const {
2025-06-16 15:00:33 -04:00
return m_isotope ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
void CompositionEntry : : setMassFraction (
const double mass_fraction
) {
2025-06-16 15:00:33 -04:00
if ( ! m_massFracMode ) {
2025-07-21 07:48:00 -04:00
throw exceptions : : CompositionModeError ( " Composition entry is in number fraction mode. " ) ;
2025-06-16 15:00:33 -04:00
}
2025-10-06 14:29:33 -04:00
// Set the invariant from the given mass fraction
if ( m_isotope . mass ( ) = = 0.0 ) {
m_molesPerMass = 0.0 ;
} else {
m_molesPerMass = mass_fraction / m_isotope . mass ( ) ;
}
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
void CompositionEntry : : setNumberFraction (
const double number_fraction
) {
2025-06-16 15:00:33 -04:00
if ( m_massFracMode ) {
2025-07-21 07:48:00 -04:00
throw exceptions : : CompositionModeError ( " Composition entry is in mass fraction mode. " ) ;
2025-06-16 15:00:33 -04:00
}
2025-10-06 14:29:33 -04:00
// In number fraction mode, we only cache the value. The invariant
// m_molesPerMass cannot be calculated until finalize() provides global context.
m_cachedNumberFraction = number_fraction ;
2025-03-26 08:07:11 -04:00
}
2025-10-06 14:29:33 -04:00
bool CompositionEntry : : setMassFracMode (
[[maybe_unused]] const double meanMolarMass
) {
2025-06-16 15:00:33 -04:00
if ( m_massFracMode ) {
return false ;
}
m_massFracMode = true ;
2025-10-06 14:29:33 -04:00
// The invariant m_molesPerMass does not change when switching mode.
// The cached number fraction is now stale, but that's okay.
2025-06-16 15:00:33 -04:00
return true ;
2025-03-24 12:58:30 -04:00
}
2025-10-06 14:29:33 -04:00
bool CompositionEntry : : setNumberFracMode (
const double totalMolesPerMass
) {
2025-06-16 15:00:33 -04:00
if ( ! m_massFracMode ) {
return false ;
}
m_massFracMode = false ;
2025-10-06 14:29:33 -04:00
// Calculate and cache the number fraction for the new mode.
m_cachedNumberFraction = number_fraction ( totalMolesPerMass ) ;
2025-06-16 15:00:33 -04:00
return true ;
}
2025-03-24 12:58:30 -04:00
2025-06-16 15:00:33 -04:00
bool CompositionEntry : : getMassFracMode ( ) const {
return m_massFracMode ;
2025-03-24 12:58:30 -04:00
}
2025-10-06 14:29:33 -04:00
Composition : : Composition (
const std : : vector < std : : string > & symbols
) {
2025-06-16 15:00:33 -04:00
for ( const auto & symbol : symbols ) {
registerSymbol ( symbol ) ;
2025-03-25 12:49:37 -04:00
}
2025-03-24 12:58:30 -04:00
}
2025-10-06 14:29:33 -04:00
Composition : : Composition (
const std : : set < std : : string > & symbols
) {
2025-06-16 15:00:33 -04:00
for ( const auto & symbol : symbols ) {
registerSymbol ( symbol ) ;
}
2025-03-24 12:58:30 -04:00
}
2025-10-06 14:29:33 -04:00
Composition : : Composition (
const std : : vector < std : : string > & symbols ,
const std : : vector < double > & fractions ,
const bool massFracMode
) : m_massFracMode ( massFracMode ) {
2025-06-16 15:00:33 -04:00
if ( symbols . size ( ) ! = fractions . size ( ) ) {
2025-07-21 07:48:00 -04:00
LOG_CRITICAL ( m_logger , " The number of symbols and fractions must be equal (got {} symbols and {} fractions). " , symbols . size ( ) , fractions . size ( ) ) ;
throw exceptions : : InvalidCompositionError ( " The number of symbols and fractions must be equal. Got " + std : : to_string ( symbols . size ( ) ) + " symbols and " + std : : to_string ( fractions . size ( ) ) + " fractions. " ) ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
validateComposition ( fractions ) ;
2025-03-24 12:58:30 -04:00
2025-06-16 15:00:33 -04:00
for ( const auto & symbol : symbols ) {
2025-10-06 14:29:33 -04:00
registerSymbol ( symbol , m_massFracMode ) ;
2025-06-16 15:00:33 -04:00
}
for ( size_t i = 0 ; i < symbols . size ( ) ; + + i ) {
if ( m_massFracMode ) {
setMassFraction ( symbols [ i ] , fractions [ i ] ) ;
} else {
setNumberFraction ( symbols [ i ] , fractions [ i ] ) ;
}
}
2025-10-12 10:12:49 -04:00
if ( const bool didFinalize = finalize ( ) ; ! didFinalize ) {
std : : string msg = " Failed to finalize composition on construction. " ;
msg + = " Construction of a composition object requires that the sum of the fractions vector be 1. \n " ;
LOG_CRITICAL ( m_logger , " {} " , msg ) ;
throw exceptions : : InvalidCompositionError ( msg ) ;
}
2025-06-16 15:00:33 -04:00
}
Composition : : Composition ( const Composition & composition ) {
m_finalized = composition . m_finalized ;
m_specificNumberDensity = composition . m_specificNumberDensity ;
m_meanParticleMass = composition . m_meanParticleMass ;
m_massFracMode = composition . m_massFracMode ;
m_registeredSymbols = composition . m_registeredSymbols ;
m_compositions = composition . m_compositions ;
}
Composition & Composition : : operator = ( const Composition & other ) {
if ( this ! = & other ) {
m_finalized = other . m_finalized ;
m_specificNumberDensity = other . m_specificNumberDensity ;
m_meanParticleMass = other . m_meanParticleMass ;
m_massFracMode = other . m_massFracMode ;
m_registeredSymbols = other . m_registeredSymbols ;
m_compositions = other . m_compositions ;
}
return * this ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
void Composition : : registerSymbol (
const std : : string & symbol ,
const bool massFracMode
) {
2025-06-16 15:00:33 -04:00
if ( ! isValidSymbol ( symbol ) ) {
LOG_ERROR ( m_logger , " Invalid symbol: {} " , symbol ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : InvalidSymbolError ( " Invalid symbol: " + symbol ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-25 12:49:37 -04:00
2025-06-25 08:26:50 -04:00
if ( m_registeredSymbols . empty ( ) ) {
2025-06-16 15:00:33 -04:00
m_massFracMode = massFracMode ;
} else {
if ( m_massFracMode ! = massFracMode ) {
2025-10-06 14:29:33 -04:00
LOG_ERROR ( m_logger , " Composition is in {} fraction mode. Cannot register symbol ({}) in {} fraction mode. " , m_massFracMode ? " mass " : " number " , symbol , massFracMode ? " mass " : " number " ) ;
throw exceptions : : CompositionModeError ( " Composition mode mismatch. " ) ;
2025-06-16 15:00:33 -04:00
}
}
2025-06-25 08:26:50 -04:00
if ( m_registeredSymbols . contains ( symbol ) ) {
2025-06-16 15:00:33 -04:00
LOG_WARNING ( m_logger , " Symbol {} is already registered. " , symbol ) ;
return ;
}
m_registeredSymbols . insert ( symbol ) ;
2025-10-06 14:29:33 -04:00
m_compositions [ symbol ] = CompositionEntry ( symbol , m_massFracMode ) ;
m_finalized = false ;
2025-06-26 09:05:31 -04:00
LOG_TRACE_L3 ( m_logger , " Registered symbol: {} " , symbol ) ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
void Composition : : registerSymbol (
const std : : vector < std : : string > & symbols ,
const bool massFracMode
) {
2025-06-16 15:00:33 -04:00
for ( const auto & symbol : symbols ) {
registerSymbol ( symbol , massFracMode ) ;
}
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
2025-10-06 14:29:33 -04:00
void Composition : : registerSpecies (
const atomic : : Species & species ,
const bool massFracMode
) {
registerSymbol ( std : : string ( species . name ( ) ) , massFracMode ) ;
2025-07-14 11:29:04 -04:00
}
2025-10-06 14:29:33 -04:00
void Composition : : registerSpecies (
const std : : vector < atomic : : Species > & species ,
const bool massFracMode
) {
2025-07-14 11:29:04 -04:00
for ( const auto & s : species ) {
registerSpecies ( s , massFracMode ) ;
}
}
2025-06-16 15:00:33 -04:00
std : : set < std : : string > Composition : : getRegisteredSymbols ( ) const {
return m_registeredSymbols ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
std : : set < atomic : : Species > Composition : : getRegisteredSpecies ( ) const {
std : : set < atomic : : Species > result ;
2025-07-14 11:29:04 -04:00
for ( const auto & entry : m_compositions | std : : views : : values ) {
result . insert ( entry . isotope ( ) ) ;
}
return result ;
}
2025-10-06 14:29:33 -04:00
bool Composition : : isValidSymbol (
const std : : string & symbol
) {
return atomic : : species . contains ( symbol ) ;
}
2025-06-16 15:00:33 -04:00
void Composition : : validateComposition ( const std : : vector < double > & fractions ) const {
if ( ! isValidComposition ( fractions ) ) {
LOG_ERROR ( m_logger , " Invalid composition. " ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : InvalidCompositionError ( " Invalid composition. " ) ;
2025-06-16 15:00:33 -04:00
}
}
2025-03-25 12:49:37 -04:00
2025-06-16 15:00:33 -04:00
bool Composition : : isValidComposition ( const std : : vector < double > & fractions ) const {
2025-10-12 10:12:49 -04:00
const double sum = std : : accumulate ( fractions . begin ( ) , fractions . end ( ) , 0.0 ) ;
2025-06-16 15:00:33 -04:00
if ( sum < 0.999999 | | sum > 1.000001 ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " The sum of fractions must be equal to 1 (expected 1, got {}). " , sum ) ;
2025-06-16 15:00:33 -04:00
return false ;
}
return true ;
2025-03-24 12:58:30 -04:00
}
2025-06-16 15:00:33 -04:00
double Composition : : setMassFraction ( const std : : string & symbol , const double & mass_fraction ) {
2025-06-17 08:12:41 -04:00
if ( ! m_registeredSymbols . contains ( symbol ) ) {
2025-06-16 15:00:33 -04:00
LOG_ERROR ( m_logger , " Symbol {} is not registered. " , symbol ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : UnregisteredSymbolError ( " Symbol ( " + symbol + " ) is not registered. " ) ;
2025-06-16 15:00:33 -04:00
}
if ( ! m_massFracMode ) {
LOG_ERROR ( m_logger , " Composition is in number fraction mode. " ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : CompositionModeError ( " Composition is in number fraction mode. " ) ;
2025-06-16 15:00:33 -04:00
}
if ( mass_fraction < 0.0 | | mass_fraction > 1.0 ) {
LOG_ERROR ( m_logger , " Mass fraction must be between 0 and 1 for symbol {}. Currently it is {}. " , symbol , mass_fraction ) ;
2025-10-06 14:29:33 -04:00
throw exceptions : : InvalidCompositionError ( " Mass fraction must be between 0 and 1. " ) ;
2025-06-16 15:00:33 -04:00
}
m_finalized = false ;
2025-06-17 08:12:41 -04:00
const double old_mass_fraction = m_compositions . at ( symbol ) . mass_fraction ( ) ;
2025-06-16 15:00:33 -04:00
m_compositions . at ( symbol ) . setMassFraction ( mass_fraction ) ;
return old_mass_fraction ;
2025-03-24 12:58:30 -04:00
}
2025-06-16 15:00:33 -04:00
std : : vector < double > Composition : : setMassFraction ( const std : : vector < std : : string > & symbols , const std : : vector < double > & mass_fractions ) {
if ( symbols . size ( ) ! = mass_fractions . size ( ) ) {
2025-10-06 14:29:33 -04:00
throw exceptions : : InvalidCompositionError ( " The number of symbols and mass fractions must be equal. " ) ;
2025-06-16 15:00:33 -04:00
}
std : : vector < double > old_mass_fractions ;
old_mass_fractions . reserve ( symbols . size ( ) ) ;
for ( size_t i = 0 ; i < symbols . size ( ) ; + + i ) {
old_mass_fractions . push_back ( setMassFraction ( symbols [ i ] , mass_fractions [ i ] ) ) ;
}
return old_mass_fractions ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double Composition : : setNumberFraction (
const std : : string & symbol ,
const double & number_fraction
) {
2025-06-25 08:26:50 -04:00
if ( ! m_registeredSymbols . contains ( symbol ) ) {
2025-06-16 15:00:33 -04:00
LOG_ERROR ( m_logger , " Symbol {} is not registered. " , symbol ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : UnregisteredSymbolError ( " Symbol ( " + symbol + " ) is not registered. " ) ;
2025-06-16 15:00:33 -04:00
}
if ( m_massFracMode ) {
2025-10-06 14:29:33 -04:00
LOG_ERROR ( m_logger , " Composition is in mass fraction mode. " ) ;
throw exceptions : : CompositionModeError ( " Composition is in mass fraction mode. " ) ;
2025-06-16 15:00:33 -04:00
}
if ( number_fraction < 0.0 | | number_fraction > 1.0 ) {
LOG_ERROR ( m_logger , " Number fraction must be between 0 and 1 for symbol {}. Currently it is {}. " , symbol , number_fraction ) ;
2025-10-06 14:29:33 -04:00
throw exceptions : : InvalidCompositionError ( " Number fraction must be between 0 and 1. " ) ;
2025-06-16 15:00:33 -04:00
}
m_finalized = false ;
2025-07-21 07:48:00 -04:00
const double old_number_fraction = m_compositions . at ( symbol ) . number_fraction ( ) ;
2025-06-16 15:00:33 -04:00
m_compositions . at ( symbol ) . setNumberFraction ( number_fraction ) ;
return old_number_fraction ;
2025-03-24 12:58:30 -04:00
}
2025-10-06 14:29:33 -04:00
std : : vector < double > Composition : : setNumberFraction (
const std : : vector < std : : string > & symbols ,
const std : : vector < double > & number_fractions
) {
2025-06-16 15:00:33 -04:00
if ( symbols . size ( ) ! = number_fractions . size ( ) ) {
2025-10-06 14:29:33 -04:00
throw exceptions : : InvalidCompositionError ( " The number of symbols and number fractions must be equal. " ) ;
2025-06-16 15:00:33 -04:00
}
std : : vector < double > old_number_fractions ;
old_number_fractions . reserve ( symbols . size ( ) ) ;
for ( size_t i = 0 ; i < symbols . size ( ) ; + + i ) {
old_number_fractions . push_back ( setNumberFraction ( symbols [ i ] , number_fractions [ i ] ) ) ;
}
return old_number_fractions ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double Composition : : setMassFraction (
const atomic : : Species & species ,
const double & mass_fraction
) {
return setMassFraction ( std : : string ( species . name ( ) ) , mass_fraction ) ;
}
std : : vector < double > Composition : : setMassFraction (
const std : : vector < atomic : : Species > & species ,
const std : : vector < double > & mass_fractions
) {
std : : vector < std : : string > symbols ;
symbols . reserve ( species . size ( ) ) ;
2025-10-12 10:12:49 -04:00
for ( const auto & s : species ) symbols . emplace_back ( s . name ( ) ) ;
2025-10-06 14:29:33 -04:00
return setMassFraction ( symbols , mass_fractions ) ;
}
double Composition : : setNumberFraction (
const atomic : : Species & species ,
const double & number_fraction
) {
2025-07-14 11:29:04 -04:00
return setNumberFraction ( std : : string ( species . name ( ) ) , number_fraction ) ;
}
2025-10-06 14:29:33 -04:00
std : : vector < double > Composition : : setNumberFraction (
const std : : vector < atomic : : Species > & species ,
const std : : vector < double > & number_fractions
) {
std : : vector < std : : string > symbols ;
symbols . reserve ( species . size ( ) ) ;
for ( const auto & s : species ) symbols . push_back ( std : : string ( s . name ( ) ) ) ;
return setNumberFraction ( symbols , number_fractions ) ;
2025-07-14 11:29:04 -04:00
}
2025-06-17 09:43:43 -04:00
bool Composition : : finalize ( const bool norm ) {
2025-10-06 14:29:33 -04:00
m_specificNumberDensity = 0.0 ;
m_meanParticleMass = 0.0 ;
m_finalized = m_massFracMode ? finalizeMassFracMode ( norm ) : finalizeNumberFracMode ( norm ) ;
m_cache . clear ( ) ;
return m_finalized ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
2025-10-06 14:29:33 -04:00
bool Composition : : finalizeMassFracMode ( const bool norm ) {
2025-06-16 15:00:33 -04:00
std : : vector < double > mass_fractions ;
mass_fractions . reserve ( m_compositions . size ( ) ) ;
2025-06-25 08:26:50 -04:00
for ( const auto & entry : m_compositions | std : : views : : values ) {
2025-06-16 15:00:33 -04:00
mass_fractions . push_back ( entry . mass_fraction ( ) ) ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double sum = std : : accumulate ( mass_fractions . begin ( ) , mass_fractions . end ( ) , 0.0 ) ;
if ( norm & & sum > 0 ) {
2025-06-16 15:00:33 -04:00
for ( auto & [ symbol , entry ] : m_compositions ) {
setMassFraction ( symbol , entry . mass_fraction ( ) / sum ) ;
}
2025-10-06 14:29:33 -04:00
// Recalculate fractions vector after normalization for validation
mass_fractions . clear ( ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
mass_fractions . push_back ( entry . mass_fraction ( ) ) ;
}
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
2025-06-16 15:00:33 -04:00
try {
validateComposition ( mass_fractions ) ;
2025-07-21 07:48:00 -04:00
} catch ( [[maybe_unused]] const exceptions : : InvalidCompositionError & e ) {
2025-10-06 14:29:33 -04:00
LOG_ERROR ( m_logger , " Composition is invalid after mass frac finalization (Total mass {}). " , sum ) ;
2025-06-16 15:00:33 -04:00
return false ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
2025-06-25 08:26:50 -04:00
for ( const auto & entry : m_compositions | std : : views : : values ) {
2025-10-06 14:29:33 -04:00
m_specificNumberDensity + = entry . rel_abundance ( ) ; // rel_abundance is now consistently moles/mass
}
if ( m_specificNumberDensity > 0 ) {
m_meanParticleMass = 1.0 / m_specificNumberDensity ;
2025-03-24 12:58:30 -04:00
}
2025-06-16 15:00:33 -04:00
return true ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
bool Composition : : finalizeNumberFracMode ( const bool norm ) {
2025-06-16 15:00:33 -04:00
std : : vector < double > number_fractions ;
number_fractions . reserve ( m_compositions . size ( ) ) ;
2025-06-25 08:26:50 -04:00
for ( const auto & entry : m_compositions | std : : views : : values ) {
2025-06-16 15:00:33 -04:00
number_fractions . push_back ( entry . number_fraction ( ) ) ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double sum = std : : accumulate ( number_fractions . begin ( ) , number_fractions . end ( ) , 0.0 ) ;
if ( norm & & sum > 0 ) {
2025-06-16 15:00:33 -04:00
for ( auto & [ symbol , entry ] : m_compositions ) {
setNumberFraction ( symbol , entry . number_fraction ( ) / sum ) ;
}
2025-10-06 14:29:33 -04:00
// Recalculate fractions vector after normalization for validation
number_fractions . clear ( ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
number_fractions . push_back ( entry . number_fraction ( ) ) ;
}
2025-06-16 15:00:33 -04:00
}
2025-10-06 14:29:33 -04:00
2025-06-16 15:00:33 -04:00
try {
validateComposition ( number_fractions ) ;
2025-10-06 14:29:33 -04:00
} catch ( [[maybe_unused]] const exceptions : : InvalidCompositionError & e ) {
LOG_ERROR ( m_logger , " Composition is invalid after number frac finalization (Total number frac {}). " , sum ) ;
2025-06-16 15:00:33 -04:00
return false ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
// Calculate mean particle mass <A> = sum(n_i * A_i)
2025-06-25 08:26:50 -04:00
for ( const auto & entry : m_compositions | std : : views : : values ) {
2025-10-06 14:29:33 -04:00
m_meanParticleMass + = entry . number_fraction ( ) * entry . isotope ( ) . mass ( ) ;
}
for ( auto & entry : m_compositions | std : : views : : values ) {
const double X_i = ( m_meanParticleMass > 0 ) ? ( entry . number_fraction ( ) * entry . isotope ( ) . mass ( ) / m_meanParticleMass ) : 0.0 ;
entry . m_massFracMode = true ;
entry . setMassFraction ( X_i ) ;
entry . m_massFracMode = false ;
}
if ( m_meanParticleMass > 0 ) {
m_specificNumberDensity = 1.0 / m_meanParticleMass ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
return true ;
2025-03-25 12:49:37 -04:00
}
2025-03-24 12:58:30 -04:00
2025-07-21 07:48:00 -04:00
Composition Composition : : mix ( const Composition & other , const double fraction ) const {
2025-06-16 15:00:33 -04:00
if ( ! m_finalized | | ! other . m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Compositions have not both been finalized. Hint: Consider running .finalize() on both compositions before mixing. " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Compositions have not been finalized (Hint: Consider running .finalize() on both compositions before mixing). " ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-26 08:07:11 -04:00
2025-06-16 15:00:33 -04:00
if ( fraction < 0.0 | | fraction > 1.0 ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Mixing fraction must be between 0 and 1. Currently it is {}. " , fraction ) ;
throw exceptions : : InvalidCompositionError ( " Mixing fraction must be between 0 and 1. Currently it is " + std : : to_string ( fraction ) + " . " ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-26 08:07:11 -04:00
2025-06-16 15:00:33 -04:00
std : : set < std : : string > mixedSymbols = other . getRegisteredSymbols ( ) ;
2025-07-24 09:35:52 -04:00
// Get the union of the two sets of symbols to ensure all species are included in the new composition.
2025-06-16 15:00:33 -04:00
mixedSymbols . insert ( m_registeredSymbols . begin ( ) , m_registeredSymbols . end ( ) ) ;
2025-03-26 08:07:11 -04:00
2025-06-16 15:00:33 -04:00
Composition mixedComposition ( mixedSymbols ) ;
for ( const auto & symbol : mixedSymbols ) {
2025-06-17 08:12:41 -04:00
double otherMassFrac = 0.0 ;
2025-03-26 08:07:11 -04:00
2025-06-17 08:12:41 -04:00
const double thisMassFrac = hasSymbol ( symbol ) ? getMassFraction ( symbol ) : 0.0 ;
2025-06-16 15:00:33 -04:00
otherMassFrac = other . hasSymbol ( symbol ) ? other . getMassFraction ( symbol ) : 0.0 ;
2025-03-26 08:07:11 -04:00
2025-07-24 09:35:52 -04:00
// The mixing formula is a linear interpolation of mass fractions.
2025-06-16 15:00:33 -04:00
double massFraction = fraction * thisMassFrac + otherMassFrac * ( 1 - fraction ) ;
mixedComposition . setMassFraction ( symbol , massFraction ) ;
}
2025-10-12 10:12:49 -04:00
if ( const bool didFinalize = mixedComposition . finalize ( ) ; ! didFinalize ) {
std : : string msg = " Failed to finalize mixed composition. " ;
msg + = " This likely indicates an issue with the input compositions not summing to 1. \n " ;
LOG_CRITICAL ( m_logger , " {} " , msg ) ;
throw exceptions : : InvalidCompositionError ( msg ) ;
}
2025-06-16 15:00:33 -04:00
return mixedComposition ;
2025-03-26 08:07:11 -04:00
}
2025-06-16 15:00:33 -04:00
double Composition : : getMassFraction ( const std : : string & symbol ) const {
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-16 15:00:33 -04:00
}
2025-06-17 08:12:41 -04:00
if ( ! m_compositions . contains ( symbol ) ) {
2025-06-16 15:00:33 -04:00
LOG_ERROR ( m_logger , " Symbol {} is not in the composition. " , symbol ) ;
2025-06-25 08:26:50 -04:00
std : : string currentSymbols ;
2025-07-24 09:35:52 -04:00
size_t count = 0 ;
2025-06-21 05:04:14 -04:00
for ( const auto & sym : m_compositions | std : : views : : keys ) {
currentSymbols + = sym ;
if ( count < m_compositions . size ( ) - 2 ) {
currentSymbols + = " , " ;
} else if ( count = = m_compositions . size ( ) - 2 ) {
currentSymbols + = " , and " ;
}
count + + ;
}
2025-07-21 07:48:00 -04:00
throw exceptions : : UnregisteredSymbolError ( " Symbol( " + symbol + " ) is not in the current composition. Current composition has symbols: " + currentSymbols + " . " ) ;
2025-06-16 15:00:33 -04:00
}
if ( m_massFracMode ) {
return m_compositions . at ( symbol ) . mass_fraction ( ) ;
}
2025-10-06 14:29:33 -04:00
return m_compositions . at ( symbol ) . mass_fraction ( ) ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double Composition : : getMassFraction (
const atomic : : Species & species
) const {
2025-07-14 11:29:04 -04:00
return getMassFraction ( std : : string ( species . name ( ) ) ) ;
}
2025-06-16 15:00:33 -04:00
std : : unordered_map < std : : string , double > Composition : : getMassFraction ( ) const {
std : : unordered_map < std : : string , double > mass_fractions ;
2025-06-17 08:12:41 -04:00
for ( const auto & symbol : m_compositions | std : : views : : keys ) {
2025-06-16 15:00:33 -04:00
mass_fractions [ symbol ] = getMassFraction ( symbol ) ;
}
return mass_fractions ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double Composition : : getNumberFraction (
const std : : string & symbol
) const {
2025-06-16 15:00:33 -04:00
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-16 15:00:33 -04:00
}
2025-06-17 08:12:41 -04:00
if ( ! m_compositions . contains ( symbol ) ) {
2025-06-16 15:00:33 -04:00
LOG_ERROR ( m_logger , " Symbol {} is not in the composition. " , symbol ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : CompositionNotFinalizedError ( " Symbol " + symbol + " is not in the composition. " ) ;
2025-06-16 15:00:33 -04:00
}
if ( ! m_massFracMode ) {
return m_compositions . at ( symbol ) . number_fraction ( ) ;
}
2025-10-06 14:29:33 -04:00
return m_compositions . at ( symbol ) . number_fraction ( m_specificNumberDensity ) ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double Composition : : getNumberFraction (
const atomic : : Species & species
) const {
2025-07-14 11:29:04 -04:00
return getNumberFraction ( std : : string ( species . name ( ) ) ) ;
}
2025-06-16 15:00:33 -04:00
std : : unordered_map < std : : string , double > Composition : : getNumberFraction ( ) const {
std : : unordered_map < std : : string , double > number_fractions ;
2025-06-17 08:12:41 -04:00
for ( const auto & symbol : m_compositions | std : : views : : keys ) {
2025-06-16 15:00:33 -04:00
number_fractions [ symbol ] = getNumberFraction ( symbol ) ;
}
return number_fractions ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
double Composition : : getMolarAbundance (
const std : : string & symbol
) const {
2025-06-25 08:26:50 -04:00
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-25 08:26:50 -04:00
}
if ( ! m_compositions . contains ( symbol ) ) {
LOG_ERROR ( m_logger , " Symbol {} is not in the composition. " , symbol ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : UnregisteredSymbolError ( " Symbol " + symbol + " is not in the composition. " ) ;
2025-06-25 08:26:50 -04:00
}
return getMassFraction ( symbol ) / m_compositions . at ( symbol ) . isotope ( ) . mass ( ) ;
}
2025-10-06 14:29:33 -04:00
double Composition : : getMolarAbundance (
const atomic : : Species & species
) const {
2025-07-14 11:29:04 -04:00
return getMolarAbundance ( std : : string ( species . name ( ) ) ) ;
}
2025-10-06 14:29:33 -04:00
std : : pair < CompositionEntry , GlobalComposition > Composition : : getComposition (
const std : : string & symbol
) const {
2025-06-16 15:00:33 -04:00
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-16 15:00:33 -04:00
}
2025-06-17 08:12:41 -04:00
if ( ! m_compositions . contains ( symbol ) ) {
2025-06-16 15:00:33 -04:00
LOG_ERROR ( m_logger , " Symbol {} is not in the composition. " , symbol ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : UnregisteredSymbolError ( " Symbol " + symbol + " is not in the composition. " ) ;
2025-06-16 15:00:33 -04:00
}
return { m_compositions . at ( symbol ) , { m_specificNumberDensity , m_meanParticleMass } } ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
2025-07-14 11:29:04 -04:00
std : : pair < CompositionEntry , GlobalComposition > Composition : : getComposition (
2025-10-06 14:29:33 -04:00
const atomic : : Species & species
) const {
2025-07-14 11:29:04 -04:00
return getComposition ( std : : string ( species . name ( ) ) ) ;
}
2025-06-16 15:00:33 -04:00
std : : pair < std : : unordered_map < std : : string , CompositionEntry > , GlobalComposition > Composition : : getComposition ( ) const {
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-16 15:00:33 -04:00
}
return { m_compositions , { m_specificNumberDensity , m_meanParticleMass } } ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
double Composition : : getMeanParticleMass ( ) const {
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-16 15:00:33 -04:00
}
return m_meanParticleMass ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
double Composition : : getMeanAtomicNumber ( ) const {
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition must be finalized before getting the mean atomic mass number. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition not finalized. Cannot retrieve mean atomic mass number. Hint: Consider running .finalize(). " ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-25 12:49:37 -04:00
2025-06-17 08:12:41 -04:00
double zSum = 0.0 ;
2025-03-25 12:49:37 -04:00
2025-06-16 15:00:33 -04:00
for ( const auto & val : m_compositions | std : : views : : values ) {
2025-07-24 09:35:52 -04:00
// Sum of (X_i * Z_i / A_i)
2025-06-17 08:12:41 -04:00
zSum + = ( val . mass_fraction ( ) * val . m_isotope . z ( ) ) / val . m_isotope . a ( ) ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
2025-10-06 14:29:33 -04:00
// <Z> = <A> * sum(X_i * Z_i / A_i)
2025-06-17 08:12:41 -04:00
const double mean_A = m_meanParticleMass * zSum ;
2025-06-16 15:00:33 -04:00
return mean_A ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
2025-10-06 14:29:33 -04:00
double Composition : : getElectronAbundance ( ) const {
if ( ! m_finalized ) {
LOG_ERROR ( m_logger , " Composition must be finalized before getting the electron abundance. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition not finalized. Cannot retrieve electron abundance. Hint: Consider running .finalize(). " ) ;
}
if ( m_cache . Ye . has_value ( ) ) {
return m_cache . Ye . value ( ) ;
}
double Ye = 0.0 ;
for ( const auto & val : m_compositions | std : : views : : values ) {
Ye + = ( val . mass_fraction ( ) * val . m_isotope . z ( ) ) / val . m_isotope . a ( ) ;
}
m_cache . Ye = Ye ;
return Ye ;
}
2025-06-16 15:00:33 -04:00
2025-10-06 14:29:33 -04:00
Composition Composition : : subset (
const std : : vector < std : : string > & symbols ,
const std : : string & method
) const {
if ( const std : : array < std : : string , 2 > methods = { " norm " , " none " } ; std : : ranges : : find ( methods , method ) = = methods . end ( ) ) {
2025-06-17 08:12:41 -04:00
const std : : string errorMessage = " Invalid method: " + method + " . Valid methods are 'norm' and 'none'. " ;
2025-06-16 15:00:33 -04:00
LOG_ERROR ( m_logger , " Invalid method: {}. Valid methods are norm and none. " , method ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : InvalidMixingMode ( errorMessage ) ;
2025-03-25 12:49:37 -04:00
}
2025-03-25 13:01:22 -04:00
2025-06-16 15:00:33 -04:00
Composition subsetComposition ;
for ( const auto & symbol : symbols ) {
2025-06-17 08:12:41 -04:00
if ( ! m_compositions . contains ( symbol ) ) {
2025-06-16 15:00:33 -04:00
LOG_ERROR ( m_logger , " Symbol {} is not in the composition. " , symbol ) ;
2025-07-21 07:48:00 -04:00
throw exceptions : : UnregisteredSymbolError ( " Symbol " + symbol + " is not in the composition. " ) ;
2025-06-16 15:00:33 -04:00
}
2025-10-06 14:29:33 -04:00
subsetComposition . registerSymbol ( symbol ) ;
2025-06-16 15:00:33 -04:00
subsetComposition . setMassFraction ( symbol , m_compositions . at ( symbol ) . mass_fraction ( ) ) ;
}
if ( method = = " norm " ) {
2025-10-06 14:29:33 -04:00
if ( const bool isNorm = subsetComposition . finalize ( true ) ; ! isNorm ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Subset composition is invalid. (Unable to finalize with normalization). " ) ;
throw exceptions : : FailedToFinalizeCompositionError ( " Subset composition is invalid. (Unable to finalize with normalization). " ) ;
2025-06-16 15:00:33 -04:00
}
}
return subsetComposition ;
2025-03-25 13:01:22 -04:00
}
2025-10-06 14:29:33 -04:00
void Composition : : setCompositionMode (
const bool massFracMode
) {
2025-06-16 15:00:33 -04:00
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Mode cannot be set unless composition is finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Mode cannot be set unless composition is finalized. Hint: Consider running .finalize(). " ) ;
2025-03-25 13:01:22 -04:00
}
2025-06-16 15:00:33 -04:00
2025-10-06 14:29:33 -04:00
bool okay ;
2025-06-17 08:12:41 -04:00
for ( auto & entry : m_compositions | std : : views : : values ) {
2025-06-16 15:00:33 -04:00
if ( massFracMode ) {
okay = entry . setMassFracMode ( m_meanParticleMass ) ;
} else {
okay = entry . setNumberFracMode ( m_specificNumberDensity ) ;
}
if ( ! okay ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition mode could not be set due to some unknown error. " ) ;
2025-06-16 15:00:33 -04:00
throw std : : runtime_error ( " Composition mode could not be set due to an unknown error. " ) ;
}
2025-03-25 13:01:22 -04:00
}
2025-06-16 15:00:33 -04:00
m_massFracMode = massFracMode ;
2025-03-25 13:01:22 -04:00
}
2025-03-26 08:07:11 -04:00
2025-10-06 14:29:33 -04:00
CanonicalComposition Composition : : getCanonicalComposition (
const bool harsh
) const {
2025-06-17 08:12:41 -04:00
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-17 08:12:41 -04:00
}
2025-10-06 14:29:33 -04:00
if ( m_cache . canonicalComp . has_value ( ) ) {
return m_cache . canonicalComp . value ( ) ; // Short circuit if we have cached the canonical composition
}
2025-06-17 08:12:41 -04:00
CanonicalComposition canonicalComposition ;
2025-06-17 10:12:07 -04:00
const std : : array < std : : string , 7 > canonicalH = {
2025-06-17 08:12:41 -04:00
" H-1 " , " H-2 " , " H-3 " , " H-4 " , " H-5 " , " H-6 " , " H-7 "
} ;
2025-06-17 10:12:07 -04:00
const std : : array < std : : string , 8 > canonicalHe = {
2025-06-17 08:12:41 -04:00
" He-3 " , " He-4 " , " He-5 " , " He-6 " , " He-7 " , " He-8 " , " He-9 " , " He-10 "
} ;
for ( const auto & symbol : canonicalH ) {
if ( hasSymbol ( symbol ) ) {
canonicalComposition . X + = getMassFraction ( symbol ) ;
}
}
for ( const auto & symbol : canonicalHe ) {
if ( hasSymbol ( symbol ) ) {
canonicalComposition . Y + = getMassFraction ( symbol ) ;
}
}
for ( const auto & symbol : getRegisteredSymbols ( ) ) {
const bool isHSymbol = std : : ranges : : find ( canonicalH , symbol ) ! = std : : end ( canonicalH ) ;
2025-10-06 14:29:33 -04:00
// ReSharper disable once CppTooWideScopeInitStatement
2025-06-17 08:12:41 -04:00
const bool isHeSymbol = std : : ranges : : find ( canonicalHe , symbol ) ! = std : : end ( canonicalHe ) ;
if ( isHSymbol | | isHeSymbol ) {
continue ; // Skip canonical H and He symbols
}
canonicalComposition . Z + = getMassFraction ( symbol ) ;
}
2025-10-06 14:29:33 -04:00
// ReSharper disable once CppTooWideScopeInitStatement
2025-06-17 08:12:41 -04:00
const double Z = 1.0 - ( canonicalComposition . X + canonicalComposition . Y ) ;
if ( std : : abs ( Z - canonicalComposition . Z ) > 1e-6 ) {
if ( ! harsh ) {
LOG_WARNING ( m_logger , " Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He). " , Z , canonicalComposition . Z ) ;
}
else {
LOG_ERROR ( m_logger , " Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He). " , Z , canonicalComposition . Z ) ;
throw std : : runtime_error ( " Validation composition Z (X-Y = " + std : : to_string ( Z ) + " ) is different than canonical composition Z ( " + std : : to_string ( canonicalComposition . Z ) + " ) (∑a_i where a_i != H/He). " ) ;
}
}
2025-10-06 14:29:33 -04:00
m_cache . canonicalComp = canonicalComposition ;
2025-06-17 08:12:41 -04:00
return canonicalComposition ;
}
2025-09-16 11:23:01 -04:00
std : : vector < double > Composition : : getMassFractionVector ( ) const {
if ( ! m_finalized ) {
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
}
2025-10-06 14:29:33 -04:00
if ( m_cache . massFractions . has_value ( ) ) {
return m_cache . massFractions . value ( ) ; // Short circuit if we have cached the mass fractions
}
2025-09-16 11:23:01 -04:00
std : : vector < double > massFractionVector ;
std : : vector < double > speciesMass ;
massFractionVector . reserve ( m_compositions . size ( ) ) ;
speciesMass . reserve ( m_compositions . size ( ) ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
massFractionVector . push_back ( entry . mass_fraction ( ) ) ;
speciesMass . push_back ( entry . isotope ( ) . mass ( ) ) ;
}
2025-10-06 14:29:33 -04:00
std : : vector < double > massFractions = sortVectorBy ( massFractionVector , speciesMass ) ;
m_cache . massFractions = massFractions ; // Cache the result
return massFractions ;
2025-09-16 11:23:01 -04:00
}
std : : vector < double > Composition : : getNumberFractionVector ( ) const {
if ( ! m_finalized ) {
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
}
2025-10-06 14:29:33 -04:00
if ( m_cache . numberFractions . has_value ( ) ) {
return m_cache . numberFractions . value ( ) ; // Short circuit if we have cached the number fractions
}
2025-09-16 11:23:01 -04:00
std : : vector < double > numberFractionVector ;
std : : vector < double > speciesMass ;
numberFractionVector . reserve ( m_compositions . size ( ) ) ;
speciesMass . reserve ( m_compositions . size ( ) ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
numberFractionVector . push_back ( entry . number_fraction ( ) ) ;
speciesMass . push_back ( entry . isotope ( ) . mass ( ) ) ;
}
2025-10-06 14:29:33 -04:00
std : : vector < double > numberFractions = sortVectorBy ( numberFractionVector , speciesMass ) ;
m_cache . numberFractions = numberFractions ; // Cache the result
return numberFractions ;
2025-09-16 11:23:01 -04:00
}
std : : vector < double > Composition : : getMolarAbundanceVector ( ) const {
if ( ! m_finalized ) {
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
}
2025-10-06 14:29:33 -04:00
if ( m_cache . molarAbundances . has_value ( ) ) {
return m_cache . molarAbundances . value ( ) ; // Short circuit if we have cached the molar abundances
}
2025-09-16 11:23:01 -04:00
std : : vector < double > molarAbundanceVector ;
std : : vector < double > speciesMass ;
molarAbundanceVector . reserve ( m_compositions . size ( ) ) ;
speciesMass . reserve ( m_compositions . size ( ) ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
molarAbundanceVector . push_back ( getMolarAbundance ( entry . isotope ( ) ) ) ;
speciesMass . push_back ( entry . isotope ( ) . mass ( ) ) ;
}
2025-10-06 14:29:33 -04:00
std : : vector < double > molarAbundances = sortVectorBy ( molarAbundanceVector , speciesMass ) ;
m_cache . molarAbundances = molarAbundances ; // Cache the result
return molarAbundances ;
2025-09-16 11:23:01 -04:00
}
2025-10-06 14:29:33 -04:00
size_t Composition : : getSpeciesIndex (
const std : : string & symbol
) const {
2025-09-16 11:23:01 -04:00
if ( ! m_finalized ) {
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
}
if ( ! m_compositions . contains ( symbol ) ) {
LOG_ERROR ( m_logger , " Symbol {} is not in the composition. " , symbol ) ;
throw exceptions : : UnregisteredSymbolError ( " Symbol " + symbol + " is not in the composition. " ) ;
}
2025-10-06 14:29:33 -04:00
if ( m_cache . sortedSymbols . has_value ( ) ) {
return std : : distance (
m_cache . sortedSymbols - > begin ( ) ,
std : : ranges : : find (
m_cache . sortedSymbols . value ( ) . begin ( ) ,
m_cache . sortedSymbols . value ( ) . end ( ) ,
symbol
)
) ;
}
2025-09-16 11:23:01 -04:00
std : : vector < std : : string > symbols ;
std : : vector < double > speciesMass ;
symbols . reserve ( m_compositions . size ( ) ) ;
speciesMass . reserve ( m_compositions . size ( ) ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
symbols . emplace_back ( entry . isotope ( ) . name ( ) ) ;
speciesMass . push_back ( entry . isotope ( ) . mass ( ) ) ;
}
std : : vector < std : : string > sortedSymbols = sortVectorBy ( symbols , speciesMass ) ;
2025-10-06 14:29:33 -04:00
m_cache . sortedSymbols = sortedSymbols ;
2025-09-16 11:23:01 -04:00
return std : : distance ( sortedSymbols . begin ( ) , std : : ranges : : find ( sortedSymbols , symbol ) ) ;
}
2025-10-06 14:29:33 -04:00
size_t Composition : : getSpeciesIndex (
const atomic : : Species & species
) const {
2025-09-16 11:23:01 -04:00
if ( ! m_finalized ) {
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
}
if ( ! m_compositions . contains ( static_cast < std : : string > ( species . name ( ) ) ) ) {
LOG_ERROR ( m_logger , " Species {} is not in the composition. " , species . name ( ) ) ;
throw exceptions : : UnregisteredSymbolError ( " Species " + std : : string ( species . name ( ) ) + " is not in the composition. " ) ;
}
2025-10-06 14:29:33 -04:00
if ( m_cache . sortedSpecies . has_value ( ) ) {
return std : : distance (
m_cache . sortedSpecies - > begin ( ) ,
std : : ranges : : find (
m_cache . sortedSpecies . value ( ) . begin ( ) ,
m_cache . sortedSpecies . value ( ) . end ( ) ,
species
)
) ;
}
2025-09-16 11:23:01 -04:00
std : : vector < atomic : : Species > speciesVector ;
std : : vector < double > speciesMass ;
speciesVector . reserve ( m_compositions . size ( ) ) ;
speciesMass . reserve ( m_compositions . size ( ) ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
speciesVector . emplace_back ( entry . isotope ( ) ) ;
speciesMass . push_back ( entry . isotope ( ) . mass ( ) ) ;
}
std : : vector < atomic : : Species > sortedSpecies = sortVectorBy ( speciesVector , speciesMass ) ;
2025-10-06 14:29:33 -04:00
m_cache . sortedSpecies = sortedSpecies ;
2025-09-16 11:23:01 -04:00
return std : : distance ( sortedSpecies . begin ( ) , std : : ranges : : find ( sortedSpecies , species ) ) ;
}
2025-10-06 14:29:33 -04:00
atomic : : Species Composition : : getSpeciesAtIndex (
size_t index
) const {
2025-09-16 11:23:01 -04:00
if ( ! m_finalized ) {
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
}
if ( index > = m_compositions . size ( ) ) {
LOG_ERROR ( m_logger , " Index {} is out of bounds for composition of size {}. " , index , m_compositions . size ( ) ) ;
throw std : : out_of_range ( " Index " + std : : to_string ( index ) + " is out of bounds for composition of size " + std : : to_string ( m_compositions . size ( ) ) + " . " ) ;
}
2025-10-06 14:29:33 -04:00
if ( m_cache . sortedSpecies . has_value ( ) ) {
return m_cache . sortedSpecies . value ( ) . at ( index ) ;
2025-09-16 11:23:01 -04:00
}
std : : vector < atomic : : Species > speciesVector ;
std : : vector < double > speciesMass ;
speciesVector . reserve ( m_compositions . size ( ) ) ;
speciesMass . reserve ( m_compositions . size ( ) ) ;
for ( const auto & entry : m_compositions | std : : views : : values ) {
speciesVector . emplace_back ( entry . isotope ( ) ) ;
speciesMass . push_back ( entry . isotope ( ) . mass ( ) ) ;
}
std : : vector < atomic : : Species > sortedSymbols = sortVectorBy ( speciesVector , speciesMass ) ;
return sortedSymbols . at ( index ) ;
}
2025-10-06 14:29:33 -04:00
bool Composition : : hasSymbol (
const std : : string & symbol
) const {
2025-06-25 08:26:50 -04:00
return m_compositions . contains ( symbol ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-26 08:07:11 -04:00
2025-10-12 10:12:49 -04:00
bool Composition : : hasSpecies ( const fourdst : : atomic : : Species & species ) const {
for ( const auto & entry : m_compositions | std : : views : : values ) {
if ( entry . isotope ( ) = = species ) {
return true ;
}
}
return false ;
}
2025-10-06 14:29:33 -04:00
bool Composition : : contains (
const atomic : : Species & isotope
) const {
2025-06-18 15:25:09 -04:00
// Check if the isotope's symbol is in the composition
if ( ! m_finalized ) {
2025-07-21 07:48:00 -04:00
LOG_ERROR ( m_logger , " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
throw exceptions : : CompositionNotFinalizedError ( " Composition has not been finalized. Hint: Consider running .finalize(). " ) ;
2025-06-18 15:25:09 -04:00
}
2025-10-06 14:29:33 -04:00
if ( const auto symbol = static_cast < std : : string > ( isotope . name ( ) ) ; m_compositions . contains ( symbol ) ) {
2025-06-18 15:25:09 -04:00
return true ;
}
return false ;
}
2025-06-16 15:00:33 -04:00
/// OVERLOADS
2025-03-26 08:07:11 -04:00
2025-10-06 14:29:33 -04:00
Composition Composition : : operator + (
const Composition & other
) const {
2025-06-16 15:00:33 -04:00
return mix ( other , 0.5 ) ;
}
2025-03-26 08:07:11 -04:00
2025-10-06 14:29:33 -04:00
std : : ostream & operator < < (
std : : ostream & os ,
const GlobalComposition & comp
) {
2025-06-16 15:00:33 -04:00
os < < " Global Composition: \n " ;
os < < " \t Specific Number Density: " < < comp . specificNumberDensity < < " \n " ;
os < < " \t Mean Particle Mass: " < < comp . meanParticleMass < < " \n " ;
return os ;
}
2025-06-11 14:49:11 -04:00
2025-10-06 14:29:33 -04:00
std : : ostream & operator < < (
std : : ostream & os ,
const CompositionEntry & entry
) {
2025-06-16 15:00:33 -04:00
os < < " < " < < entry . m_symbol < < " : m_frac = " < < entry . mass_fraction ( ) < < " > " ;
return os ;
}
2025-06-11 14:49:11 -04:00
2025-10-06 14:29:33 -04:00
std : : ostream & operator < < (
std : : ostream & os ,
const Composition & composition
) {
2025-06-20 13:51:27 -04:00
os < < " Composition(finalized: " < < ( composition . m_finalized ? " true " : " false " ) < < " , " ;
2025-07-24 09:35:52 -04:00
size_t count = 0 ;
2025-06-25 08:26:50 -04:00
for ( const auto & entry : composition . m_compositions | std : : views : : values ) {
2025-06-20 13:51:27 -04:00
os < < entry ;
if ( count < composition . m_compositions . size ( ) - 1 ) {
os < < " , " ;
}
2025-10-06 14:29:33 -04:00
count + + ;
2025-06-16 15:00:33 -04:00
}
2025-06-20 13:51:27 -04:00
os < < " ) " ;
2025-06-16 15:00:33 -04:00
return os ;
2025-03-26 08:07:11 -04:00
}
2025-06-11 14:49:11 -04:00
2025-06-21 11:33:27 -04:00
} // namespace fourdst::composition