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>
2025-06-16 15:00:33 -04:00
# include <ranges>
2025-09-16 11:23:01 -04:00
# include <algorithm>
2025-11-07 15:49:25 -05:00
# include <set>
# include <string>
2025-09-16 11:23:01 -04:00
2025-03-25 12:49:37 -04:00
# include <utility>
2025-03-24 12:58:30 -04:00
2025-11-07 15:49:25 -05:00
# include "fourdst/atomic/atomicSpecies.h"
# include "fourdst/atomic/species.h"
2025-06-22 04:56:04 -04:00
# include "fourdst/composition/composition.h"
2025-12-08 11:31:46 -05:00
# include <numeric>
2025-12-07 07:47:32 -05:00
# include "fourdst/composition/utils/composition_hash.h"
2025-11-28 11:42:13 -05:00
# include "fourdst/composition/utils.h"
2025-10-06 14:29:33 -04:00
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 {
2025-11-07 15:49:25 -05:00
void throw_unknown_symbol ( quill : : Logger * logger , const std : : string & symbol ) {
LOG_ERROR ( logger , " Symbol {} is not a valid species symbol (not in the species database) " , symbol ) ;
throw fourdst : : composition : : exceptions : : UnknownSymbolError ( " Symbol " + symbol + " is not a valid species symbol (not in the species database) " ) ;
2025-03-25 12:49:37 -04:00
}
2025-11-07 15:49:25 -05:00
void throw_unregistered_symbol ( quill : : Logger * logger , const std : : string & symbol ) {
LOG_ERROR ( logger , " Symbol {} is not registered in the composition. " , symbol ) ;
throw fourdst : : composition : : exceptions : : UnregisteredSymbolError ( " Symbol " + symbol + " is not registered in the composition. " ) ;
2025-03-24 12:58:30 -04:00
}
2025-11-07 15:49:25 -05:00
}
2025-03-25 12:49:37 -04:00
2025-11-07 15:49:25 -05:00
namespace fourdst : : composition {
2025-12-08 11:31:46 -05:00
/////////////////////////////////////////////
/// Constructors without molar abundances ///
/// These all delegate to the ctor ///
/// vector<Species> ///
/////////////////////////////////////////////
2025-03-25 12:49:37 -04:00
2025-11-07 15:49:25 -05:00
Composition : : Composition (
const std : : set < std : : string > & symbols
2025-12-08 11:31:46 -05:00
) : Composition ( symbols | std : : ranges : : to < std : : vector > ( ) ) { }
2025-03-25 12:49:37 -04:00
2025-11-07 15:49:25 -05:00
Composition : : Composition (
2025-12-08 11:31:46 -05:00
const std : : set < atomic : : Species > & species
) : Composition ( species | std : : ranges : : to < std : : vector > ( ) ) { }
2025-03-26 08:07:11 -04:00
2025-11-07 15:49:25 -05:00
Composition : : Composition (
2025-12-08 11:31:46 -05:00
const std : : unordered_set < std : : string > & symbols
) : Composition ( symbols | std : : ranges : : to < std : : vector > ( ) ) { }
Composition : : Composition (
const std : : unordered_set < atomic : : Species > & species
) : Composition ( species | std : : ranges : : to < std : : vector > ( ) ) { }
Composition : : Composition (
const std : : vector < std : : string > & symbols
) : Composition ( symbolVectorToSpeciesVector ( symbols ) ) { }
Composition : : Composition (
const std : : vector < atomic : : Species > & species
2025-10-06 14:29:33 -04:00
) {
2025-12-08 11:31:46 -05:00
m_species = species ;
std : : ranges : : sort ( m_species , [ & ] ( const atomic : : Species & a , const atomic : : Species & b ) {
return a < b ;
} ) ;
2025-03-24 12:58:30 -04:00
2025-12-08 11:31:46 -05:00
const auto last = std : : ranges : : unique ( m_species ) . begin ( ) ;
m_species . erase ( last , m_species . end ( ) ) ;
2025-11-25 11:27:45 -05:00
2025-12-08 11:31:46 -05:00
m_molarAbundances . resize ( m_species . size ( ) , 0.0 ) ;
2025-11-25 11:27:45 -05:00
}
2025-12-08 11:31:46 -05:00
//////////////////////////////////////////
/// Constructors with molar abundances ///
/// These all delegate to the ctor ///
/// vector<Species, vector<double>> ///
//////////////////////////////////////////
2025-11-07 15:49:25 -05:00
Composition : : Composition (
const std : : vector < std : : string > & symbols ,
const std : : vector < double > & molarAbundances
2025-12-08 11:31:46 -05:00
) : Composition ( symbolVectorToSpeciesVector ( symbols ) , molarAbundances ) { }
2025-03-24 12:58:30 -04:00
2025-12-08 11:31:46 -05:00
Composition : : Composition (
const std : : set < std : : string > & symbols ,
const std : : vector < double > & molarAbundances
) : Composition ( symbolVectorToSpeciesVector ( symbols | std : : ranges : : to < std : : vector > ( ) ) , molarAbundances ) { }
Composition : : Composition (
const std : : unordered_map < std : : string , double > & symbolMolarAbundances
) : Composition (
symbolMolarAbundances | std : : views : : keys | std : : ranges : : to < std : : vector > ( ) ,
symbolMolarAbundances | std : : views : : values | std : : ranges : : to < std : : vector > ( )
) { }
Composition : : Composition (
const std : : map < std : : string , double > & symbolMolarAbundances
) : Composition (
symbolMolarAbundances | std : : views : : keys | std : : ranges : : to < std : : vector > ( ) ,
symbolMolarAbundances | std : : views : : values | std : : ranges : : to < std : : vector > ( )
) { }
Composition : : Composition (
const std : : unordered_map < atomic : : Species , double > & speciesMolarAbundances
) : Composition (
speciesMolarAbundances | std : : views : : keys | std : : ranges : : to < std : : vector > ( ) ,
speciesMolarAbundances | std : : views : : values | std : : ranges : : to < std : : vector > ( )
) { }
Composition : : Composition (
const std : : map < atomic : : Species , double > & speciesMolarAbundances
) : Composition (
speciesMolarAbundances | std : : views : : keys | std : : ranges : : to < std : : vector > ( ) ,
speciesMolarAbundances | std : : views : : values | std : : ranges : : to < std : : vector > ( )
) { }
2025-03-24 12:58:30 -04:00
2025-10-06 14:29:33 -04:00
Composition : : Composition (
2025-11-07 15:49:25 -05:00
const std : : vector < atomic : : Species > & species ,
const std : : vector < double > & molarAbundances
2025-10-06 14:29:33 -04:00
) {
2025-12-08 11:31:46 -05:00
if ( __builtin_expect ( species . size ( ) ! = molarAbundances . size ( ) , 0 ) ) {
2025-11-07 15:49:25 -05:00
LOG_CRITICAL ( getLogger ( ) , " The number of species and molarAbundances must be equal (got {} species and {} molarAbundances). " , species . size ( ) , molarAbundances . size ( ) ) ;
throw exceptions : : InvalidCompositionError ( " The number of species and fractions must be equal. Got " + std : : to_string ( species . size ( ) ) + " species and " + std : : to_string ( molarAbundances . size ( ) ) + " fractions. " ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-24 12:58:30 -04:00
2025-12-08 11:31:46 -05:00
const size_t numSpecies = species . size ( ) ;
m_species . reserve ( numSpecies ) ;
m_molarAbundances . reserve ( numSpecies ) ;
2025-03-25 12:49:37 -04:00
2025-12-08 11:31:46 -05:00
for ( size_t i = 0 ; i < numSpecies ; + + i ) {
m_species . push_back ( species [ i ] ) ;
if ( __builtin_expect ( molarAbundances [ i ] < 0.0 , 0 ) ) {
LOG_CRITICAL ( getLogger ( ) , " Molar abundance for species {} is negative (y = {}). Molar abundances must be non-negative. " , species [ i ] . name ( ) , molarAbundances [ i ] ) ;
throw exceptions : : InvalidCompositionError ( " Molar abundance for species " + std : : string ( species [ i ] . name ( ) ) + " is negative (y = " + std : : to_string ( molarAbundances [ i ] ) + " ). Molar abundances must be non-negative. " ) ;
}
m_molarAbundances . push_back ( molarAbundances [ i ] ) ;
2025-06-16 15:00:33 -04:00
}
2025-12-08 11:31:46 -05:00
auto combined = std : : views : : zip ( m_species , m_molarAbundances ) ;
2025-06-16 15:00:33 -04:00
2025-12-08 11:31:46 -05:00
std : : ranges : : sort ( combined , [ ] ( const auto & a , const auto & b ) - > bool {
const auto & spA = std : : get < 0 > ( a ) ;
const auto & spB = std : : get < 0 > ( b ) ;
2025-11-25 11:27:45 -05:00
2025-12-08 11:31:46 -05:00
if ( spA ! = spB ) {
return spA < spB ;
}
2025-11-25 11:27:45 -05:00
2025-12-08 11:31:46 -05:00
return std : : get < 1 > ( a ) > std : : get < 1 > ( b ) ;
} ) ;
2025-11-25 11:27:45 -05:00
2025-12-08 11:31:46 -05:00
auto [ first , last ] = std : : ranges : : unique ( combined , [ ] ( const auto & a , const auto & b ) {
return std : : get < 0 > ( a ) = = std : : get < 0 > ( b ) ;
} ) ;
const auto newEndIndex = std : : distance ( combined . begin ( ) , first ) ;
m_species . erase ( m_species . begin ( ) + newEndIndex , m_species . end ( ) ) ;
m_molarAbundances . erase ( m_molarAbundances . begin ( ) + newEndIndex , m_molarAbundances . end ( ) ) ;
2025-11-25 11:27:45 -05:00
}
2025-12-08 11:31:46 -05:00
////////////////////////////////////////////
/// Copy and conversion constructors ///
////////////////////////////////////////////
Composition : : Composition ( const Composition & composition ) {
m_species = composition . m_species ;
2025-11-07 15:49:25 -05:00
m_molarAbundances = composition . m_molarAbundances ;
2025-06-16 15:00:33 -04:00
}
2025-11-12 15:21:33 -05:00
Composition : : Composition ( const CompositionAbstract & composition ) {
for ( const auto & species : composition . getRegisteredSpecies ( ) ) {
registerSpecies ( species ) ;
setMolarAbundance ( species , composition . getMolarAbundance ( species ) ) ;
}
}
2025-11-07 15:49:25 -05:00
Composition & Composition : : operator = (
const Composition & other
) {
2025-06-16 15:00:33 -04:00
if ( this ! = & other ) {
2025-12-08 11:31:46 -05:00
m_species = other . m_species ;
2025-11-07 15:49:25 -05:00
m_molarAbundances = other . m_molarAbundances ;
2025-06-16 15:00:33 -04:00
}
2025-12-08 11:31:46 -05:00
m_cache . clear ( ) ;
return * this ;
}
Composition & Composition : : operator = ( const CompositionAbstract & other ) {
m_species . clear ( ) ;
m_molarAbundances . clear ( ) ;
m_cache . clear ( ) ;
for ( const auto & species : other . getRegisteredSpecies ( ) ) {
registerSpecies ( species ) ;
setMolarAbundance ( species , other . getMolarAbundance ( species ) ) ;
}
2025-06-16 15:00:33 -04:00
return * this ;
2025-03-25 12:49:37 -04:00
}
2025-12-08 11:31:46 -05:00
std : : unique_ptr < CompositionAbstract > Composition : : clone ( ) const {
return std : : make_unique < Composition > ( * this ) ;
}
//------------------------------------------
// Registration methods
//------------------------------------------
2025-10-06 14:29:33 -04:00
void Composition : : registerSymbol (
2025-11-07 15:49:25 -05:00
const std : : string & symbol
2025-10-06 14:29:33 -04:00
) {
2025-11-07 15:49:25 -05:00
const auto result = getSpecies ( symbol ) ;
if ( ! result ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-25 12:49:37 -04:00
2025-11-07 15:49:25 -05:00
registerSpecies ( result . value ( ) ) ;
2025-03-25 12:49:37 -04:00
}
2025-10-06 14:29:33 -04:00
void Composition : : registerSymbol (
2025-11-07 15:49:25 -05:00
const std : : vector < std : : string > & symbols
2025-10-06 14:29:33 -04:00
) {
2025-12-08 11:31:46 -05:00
registerSpecies ( symbolVectorToSpeciesVector ( symbols ) ) ;
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 (
2025-11-07 15:49:25 -05:00
const atomic : : Species & species
2025-11-08 06:42:41 -05:00
) noexcept {
2025-12-08 11:31:46 -05:00
if ( const auto it = std : : ranges : : lower_bound ( m_species , species ) ; it = = m_species . end ( ) | | * it ! = species ) {
const auto index = std : : distance ( m_species . begin ( ) , it ) ;
m_species . insert ( it , species ) ;
m_molarAbundances . insert ( m_molarAbundances . begin ( ) + index , 0.0 ) ;
m_cache . clear ( ) ;
2025-11-07 15:49:25 -05:00
}
2025-07-14 11:29:04 -04:00
}
2025-10-06 14:29:33 -04:00
void Composition : : registerSpecies (
2025-11-07 15:49:25 -05:00
const std : : vector < atomic : : Species > & species
2025-11-08 06:42:41 -05:00
) noexcept {
2025-12-08 11:31:46 -05:00
// We do not simply call registerSpecies(species) here as that would have a complexity of O(n^2) due to constantly
// reinserting into the vector. Rather we build the vector once and then sort it
if ( species . empty ( ) ) return ;
const size_t total_size = m_species . size ( ) + species . size ( ) ;
m_species . reserve ( total_size ) ;
m_molarAbundances . reserve ( total_size ) ;
for ( const auto & sp : species ) {
m_species . push_back ( sp ) ;
m_molarAbundances . push_back ( 0.0 ) ;
2025-07-14 11:29:04 -04:00
}
2025-12-08 11:31:46 -05:00
auto combined = std : : views : : zip ( m_species , m_molarAbundances ) ;
std : : ranges : : sort ( combined , [ ] ( const auto & a , const auto & b ) {
const auto & speciesA = std : : get < 0 > ( a ) ;
const auto & speciesB = std : : get < 0 > ( b ) ;
if ( speciesA ! = speciesB ) {
return speciesA < speciesB ;
}
return std : : get < 1 > ( a ) > std : : get < 1 > ( b ) ;
} ) ;
auto [ first , last ] = std : : ranges : : unique ( combined , [ ] ( const auto & a , const auto & b ) {
return std : : get < 0 > ( a ) = = std : : get < 0 > ( b ) ;
} ) ;
const auto newEndIndex = std : : distance ( combined . begin ( ) , first ) ;
m_species . erase ( m_species . begin ( ) + newEndIndex , m_species . end ( ) ) ;
m_molarAbundances . erase ( m_molarAbundances . begin ( ) + newEndIndex , m_molarAbundances . end ( ) ) ;
m_cache . clear ( ) ;
2025-07-14 11:29:04 -04:00
}
2025-11-08 06:42:41 -05:00
std : : set < std : : string > Composition : : getRegisteredSymbols ( ) const noexcept {
2025-11-07 15:49:25 -05:00
std : : set < std : : string > symbols ;
2025-12-08 11:31:46 -05:00
for ( const auto & species : m_species ) {
2025-11-07 15:49:25 -05:00
symbols . insert ( std : : string ( species . name ( ) ) ) ;
2025-10-06 14:29:33 -04:00
}
2025-11-07 15:49:25 -05:00
return symbols ;
2025-03-25 12:49:37 -04:00
}
2025-12-08 11:31:46 -05:00
const std : : vector < atomic : : Species > & Composition : : getRegisteredSpecies ( ) const noexcept {
return m_species ;
}
//------------------------------------------
// Molar abundance setters
//------------------------------------------
void Composition : : setMolarAbundance (
const std : : string & symbol ,
const double & molar_abundance
) {
const auto species = getSpecies ( symbol ) ;
if ( __builtin_expect ( ! species , 0 ) ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
}
setMolarAbundance ( species . value ( ) , molar_abundance ) ;
}
void Composition : : setMolarAbundance (
const atomic : : Species & species ,
const double & molar_abundance
) {
if ( __builtin_expect ( molar_abundance < 0.0 , 0 ) ) {
LOG_ERROR ( getLogger ( ) , " Molar abundance must be non-negative for symbol {}. Currently it is {}. " , species . name ( ) , molar_abundance ) ;
throw exceptions : : InvalidCompositionError ( " Molar abundance must be non-negative, got " + std : : to_string ( molar_abundance ) + " for symbol " + std : : string ( species . name ( ) ) + " . " ) ;
}
const std : : expected < std : : ptrdiff_t , SpeciesIndexLookupError > speciesIndexResult = findSpeciesIndex ( species ) ;
if ( __builtin_expect ( ! speciesIndexResult , 0 ) ) {
throw_unregistered_symbol ( getLogger ( ) , std : : string ( species . name ( ) ) ) ;
}
2025-12-22 07:39:59 -05:00
assert ( static_cast < size_t > ( speciesIndexResult . value ( ) ) < m_molarAbundances . size ( ) ) ;
2025-12-08 11:31:46 -05:00
m_molarAbundances [ speciesIndexResult . value ( ) ] = molar_abundance ;
m_cache . clear ( ) ;
}
////----------------------------------------------
/// Methods which set multiple molar abundances
/// delegate to vector<Species>, vector<double>
///-----------------------------------------------
void Composition : : setMolarAbundance (
const std : : vector < std : : string > & symbols ,
const std : : vector < double > & molar_abundances
) {
setMolarAbundance ( symbolVectorToSpeciesVector ( symbols ) , molar_abundances ) ;
}
void Composition : : setMolarAbundance (
const std : : set < std : : string > & symbols ,
const std : : vector < double > & molar_abundances
) {
setMolarAbundance ( symbolVectorToSpeciesVector ( symbols | std : : ranges : : to < std : : vector > ( ) ) , molar_abundances ) ;
2025-03-25 12:49:37 -04:00
}
2025-03-24 12:58:30 -04:00
2025-12-08 11:31:46 -05:00
void Composition : : setMolarAbundance (
const std : : set < atomic : : Species > & species ,
const std : : vector < double > & molar_abundances
) {
setMolarAbundance ( species | std : : ranges : : to < std : : vector > ( ) , molar_abundances ) ;
}
void Composition : : setMolarAbundance (
const std : : vector < atomic : : Species > & species ,
const std : : vector < double > & molar_abundances
) {
if ( __builtin_expect ( species . size ( ) ! = molar_abundances . size ( ) , 0 ) ) {
LOG_CRITICAL ( getLogger ( ) , " The number of species and molar_abundances must be equal (got {} species and {} molar_abundances). " , species . size ( ) , molar_abundances . size ( ) ) ;
throw exceptions : : InvalidCompositionError ( " The number of species and fractions must be equal. Got " + std : : to_string ( species . size ( ) ) + " species and " + std : : to_string ( molar_abundances . size ( ) ) + " fractions. " ) ;
}
if ( species . empty ( ) ) return ;
if ( species . size ( ) = = m_species . size ( ) ) {
if ( species = = m_species ) {
for ( const auto & [ sp , y ] : std : : views : : zip ( species , molar_abundances ) ) {
if ( __builtin_expect ( y < 0.0 , 0 ) ) {
LOG_ERROR ( getLogger ( ) , " Molar abundance must be non-negative. Instead got {} for species {}. " , y , sp . name ( ) ) ;
throw exceptions : : InvalidCompositionError ( " Molar abundance must be non-negative. Instead got " + std : : to_string ( y ) + " for species " + std : : string ( sp . name ( ) ) + " . " ) ;
}
}
m_molarAbundances = molar_abundances ;
m_cache . clear ( ) ;
return ;
}
}
for ( size_t i = 0 ; i < species . size ( ) ; + + i ) {
const double y = molar_abundances [ i ] ;
const auto & sp = species [ i ] ;
if ( __builtin_expect ( y < 0.0 , 0 ) ) {
LOG_CRITICAL ( getLogger ( ) , " Molar abundance must be non-negative. Instead got {} for species {}. " , y , sp . name ( ) ) ;
throw exceptions : : InvalidCompositionError ( " Molar abundance must be non-negative. Instead got " + std : : to_string ( y ) + " for species " + std : : string ( sp . name ( ) ) + " . " ) ;
}
const std : : expected < std : : ptrdiff_t , SpeciesIndexLookupError > speciesIndexResult = findSpeciesIndex ( sp ) ;
if ( __builtin_expect ( ! speciesIndexResult , 0 ) ) {
throw_unregistered_symbol ( getLogger ( ) , std : : string ( sp . name ( ) ) ) ;
}
const std : : ptrdiff_t speciesIndex = speciesIndexResult . value ( ) ;
m_molarAbundances [ speciesIndex ] = y ;
}
m_cache . clear ( ) ;
}
//------------------------------------------
// Fraction and abundance getters
//------------------------------------------
2025-03-26 08:07:11 -04:00
2025-06-16 15:00:33 -04:00
double Composition : : getMassFraction ( const std : : string & symbol ) const {
2025-11-07 15:49:25 -05:00
const auto species = getSpecies ( symbol ) ;
if ( ! species ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
2025-06-16 15:00:33 -04:00
}
2025-11-07 15:49:25 -05:00
return getMassFraction ( species . value ( ) ) ;
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-12-08 11:31:46 -05:00
const std : : expected < std : : ptrdiff_t , SpeciesIndexLookupError > speciesIndexResult = findSpeciesIndex ( species ) ;
if ( ! speciesIndexResult ) {
2025-11-08 06:42:41 -05:00
throw_unregistered_symbol ( getLogger ( ) , std : : string ( species . name ( ) ) ) ;
}
2025-12-08 11:31:46 -05:00
2025-11-07 15:49:25 -05:00
double totalMass = 0 ;
2025-12-08 11:31:46 -05:00
double speciesMass = 0 ;
for ( const auto & [ sp , y ] : * this ) {
2025-11-07 15:49:25 -05:00
const double contrib = y * sp . mass ( ) ;
totalMass + = contrib ;
2025-12-08 11:31:46 -05:00
if ( sp = = species ) {
speciesMass = contrib ;
}
2025-11-07 15:49:25 -05:00
}
2025-12-08 11:31:46 -05:00
return speciesMass / totalMass ;
2025-07-14 11:29:04 -04:00
}
2025-11-08 06:42:41 -05:00
std : : unordered_map < atomic : : Species , double > Composition : : getMassFraction ( ) const noexcept {
2025-11-07 15:49:25 -05:00
std : : unordered_map < atomic : : Species , double > mass_fractions ;
2025-12-08 11:31:46 -05:00
for ( const auto & species : * this | std : : views : : keys ) {
2025-11-07 15:49:25 -05:00
mass_fractions . emplace ( species , getMassFraction ( species ) ) ;
2025-06-16 15:00:33 -04:00
}
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-11-08 06:42:41 -05:00
const auto species = getSpecies ( symbol ) ;
2025-11-07 15:49:25 -05:00
if ( ! species ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
2025-06-16 15:00:33 -04:00
}
2025-11-07 15:49:25 -05:00
return getNumberFraction ( species . value ( ) ) ;
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-12-08 11:31:46 -05:00
const std : : expected < std : : ptrdiff_t , SpeciesIndexLookupError > speciesIndexResult = findSpeciesIndex ( species ) ;
if ( ! speciesIndexResult ) {
2025-11-08 06:42:41 -05:00
throw_unregistered_symbol ( getLogger ( ) , std : : string ( species . name ( ) ) ) ;
}
2025-12-08 11:31:46 -05:00
const std : : ptrdiff_t speciesIndex = speciesIndexResult . value ( ) ;
const double total_moles_per_gram = std : : accumulate (
m_molarAbundances . begin ( ) ,
m_molarAbundances . end ( ) ,
0.0
) ;
return m_molarAbundances [ speciesIndex ] / total_moles_per_gram ;
2025-07-14 11:29:04 -04:00
}
2025-11-08 06:42:41 -05:00
std : : unordered_map < atomic : : Species , double > Composition : : getNumberFraction ( ) const noexcept {
2025-11-07 15:49:25 -05:00
std : : unordered_map < atomic : : Species , double > number_fractions ;
2025-12-08 11:31:46 -05:00
for ( const auto & species : m_species ) {
2025-11-07 15:49:25 -05:00
number_fractions . emplace ( species , getNumberFraction ( species ) ) ;
2025-06-16 15:00:33 -04:00
}
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-11-08 06:42:41 -05:00
const auto species = getSpecies ( symbol ) ;
2025-11-07 15:49:25 -05:00
if ( ! species ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
2025-06-25 08:26:50 -04:00
}
2025-11-07 15:49:25 -05:00
return getMolarAbundance ( species . value ( ) ) ;
2025-06-25 08:26:50 -04:00
}
2025-10-06 14:29:33 -04:00
double Composition : : getMolarAbundance (
const atomic : : Species & species
) const {
2025-12-08 11:31:46 -05:00
const std : : expected < std : : ptrdiff_t , SpeciesIndexLookupError > speciesIndexResult = findSpeciesIndex ( species ) ;
if ( ! speciesIndexResult ) {
2025-11-08 06:42:41 -05:00
throw_unregistered_symbol ( getLogger ( ) , std : : string ( species . name ( ) ) ) ;
2025-06-16 15:00:33 -04:00
}
2025-12-08 11:31:46 -05:00
const std : : ptrdiff_t speciesIndex = speciesIndexResult . value ( ) ;
return m_molarAbundances [ speciesIndex ] ;
2025-03-25 12:49:37 -04:00
}
2025-12-08 11:31:46 -05:00
//------------------------------------------
// Derived property getters
//------------------------------------------
2025-11-08 06:42:41 -05:00
double Composition : : getMeanParticleMass ( ) const noexcept {
2025-12-08 11:31:46 -05:00
double totalMass = 0.0 ;
double totalMoles = 0.0 ;
for ( size_t i = 0 ; i < m_species . size ( ) ; + + i ) {
totalMoles + = m_molarAbundances [ i ] ;
totalMass + = m_molarAbundances [ i ] * m_species [ i ] . mass ( ) ;
2025-06-16 15:00:33 -04:00
}
2025-03-25 12:49:37 -04:00
2025-12-08 11:31:46 -05:00
return totalMass / totalMoles ;
2025-03-25 12:49:37 -04:00
}
2025-06-16 15:00:33 -04:00
2025-12-08 11:31:46 -05:00
2025-11-08 06:42:41 -05:00
double Composition : : getElectronAbundance ( ) const noexcept {
2025-10-06 14:29:33 -04:00
double Ye = 0.0 ;
2025-12-08 11:31:46 -05:00
for ( const auto & [ species , y ] : * this ) {
2025-11-07 15:49:25 -05:00
Ye + = species . z ( ) * y ;
2025-10-06 14:29:33 -04:00
}
return Ye ;
}
2025-06-16 15:00:33 -04:00
2025-03-26 08:07:11 -04:00
2025-10-06 14:29:33 -04:00
CanonicalComposition Composition : : getCanonicalComposition (
) const {
2025-11-07 15:49:25 -05:00
using namespace fourdst : : atomic ;
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-12-08 11:31:46 -05:00
static const std : : unordered_set < Species > canonicalH = { H_1 , H_2 , H_3 , H_4 , H_5 , H_6 , H_7 } ;
static const std : : unordered_set < Species > canonicalHe = { He_3 , He_4 , He_5 , He_6 , He_7 , He_8 , He_9 , He_10 } ;
2025-11-07 15:49:25 -05:00
2025-06-17 08:12:41 -04:00
for ( const auto & symbol : canonicalH ) {
2025-11-07 15:49:25 -05:00
if ( contains ( symbol ) ) {
2025-06-17 08:12:41 -04:00
canonicalComposition . X + = getMassFraction ( symbol ) ;
}
}
for ( const auto & symbol : canonicalHe ) {
2025-11-07 15:49:25 -05:00
if ( contains ( symbol ) ) {
2025-06-17 08:12:41 -04:00
canonicalComposition . Y + = getMassFraction ( symbol ) ;
}
}
2025-12-08 11:31:46 -05:00
for ( const auto & species : m_species ) {
if ( canonicalH . contains ( species ) | | canonicalHe . contains ( species ) ) {
2025-06-17 08:12:41 -04:00
continue ; // Skip canonical H and He symbols
}
2025-11-07 15:49:25 -05:00
canonicalComposition . Z + = getMassFraction ( species ) ;
2025-06-17 08:12:41 -04:00
}
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 ) ;
2025-11-08 06:42:41 -05:00
if ( std : : abs ( Z - canonicalComposition . Z ) > 1e-16 ) {
LOG_ERROR ( getLogger ( ) , " Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He). " , Z , canonicalComposition . Z ) ;
throw exceptions : : InvalidCompositionError ( " 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-06-17 08:12:41 -04:00
}
2025-10-06 14:29:33 -04:00
m_cache . canonicalComp = canonicalComposition ;
2025-06-17 08:12:41 -04:00
return canonicalComposition ;
}
2025-12-08 11:31:46 -05:00
//------------------------------------------
// Vector getters
//------------------------------------------
2025-11-08 06:42:41 -05:00
std : : vector < double > Composition : : getMassFractionVector ( ) const noexcept {
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 ;
2025-11-07 15:49:25 -05:00
massFractionVector . reserve ( m_molarAbundances . size ( ) ) ;
2025-09-16 11:23:01 -04:00
2025-12-08 11:31:46 -05:00
for ( const auto & species : m_species ) {
2025-11-07 15:49:25 -05:00
massFractionVector . push_back ( getMassFraction ( species ) ) ;
2025-09-16 11:23:01 -04:00
}
2025-12-08 11:31:46 -05:00
m_cache . massFractions = massFractionVector ; // Cache the result
return massFractionVector ;
2025-09-16 11:23:01 -04:00
}
2025-11-08 06:42:41 -05:00
std : : vector < double > Composition : : getNumberFractionVector ( ) const noexcept {
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 ;
2025-11-07 15:49:25 -05:00
numberFractionVector . reserve ( m_molarAbundances . size ( ) ) ;
2025-09-16 11:23:01 -04:00
2025-12-08 11:31:46 -05:00
for ( const auto & species : m_species ) {
2025-11-07 15:49:25 -05:00
numberFractionVector . push_back ( getNumberFraction ( species ) ) ;
2025-09-16 11:23:01 -04:00
}
2025-12-08 11:31:46 -05:00
m_cache . numberFractions = numberFractionVector ; // Cache the result
return numberFractionVector ;
2025-09-16 11:23:01 -04:00
}
2025-11-08 06:42:41 -05:00
std : : vector < double > Composition : : getMolarAbundanceVector ( ) const noexcept {
2025-12-08 11:31:46 -05:00
return m_molarAbundances ;
2025-09-16 11:23:01 -04:00
}
2025-12-08 11:31:46 -05:00
//------------------------------------------
// Species index getters and lookups
//------------------------------------------
2025-10-06 14:29:33 -04:00
size_t Composition : : getSpeciesIndex (
const std : : string & symbol
) const {
2025-11-07 15:49:25 -05:00
const auto species = getSpecies ( symbol ) ;
if ( ! species ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
2025-09-16 11:23:01 -04:00
}
2025-11-07 15:49:25 -05:00
return getSpeciesIndex ( species . value ( ) ) ;
2025-09-16 11:23:01 -04:00
}
2025-10-06 14:29:33 -04:00
size_t Composition : : getSpeciesIndex (
const atomic : : Species & species
) const {
2025-12-08 11:31:46 -05:00
std : : expected < std : : ptrdiff_t , SpeciesIndexLookupError > speciesIndexResult = findSpeciesIndex ( species ) ;
if ( ! speciesIndexResult ) {
switch ( speciesIndexResult . error ( ) ) {
case SpeciesIndexLookupError : : NO_REGISTERED_SPECIES :
[[fallthrough]] ;
case SpeciesIndexLookupError : : SPECIES_NOT_FOUND :
throw_unregistered_symbol ( getLogger ( ) , std : : string ( species . name ( ) ) ) ;
default :
throw std : : logic_error ( " Unhandled SpeciesIndexLookupError in Composition::getSpeciesIndex " ) ;
}
2025-09-16 11:23:01 -04:00
}
2025-12-08 11:31:46 -05:00
return static_cast < size_t > ( speciesIndexResult . value ( ) ) ;
2025-09-16 11:23:01 -04:00
}
2025-10-06 14:29:33 -04:00
atomic : : Species Composition : : getSpeciesAtIndex (
2025-11-07 15:49:25 -05:00
const size_t index
2025-10-06 14:29:33 -04:00
) const {
2025-12-08 11:31:46 -05:00
if ( index > = m_species . size ( ) ) {
LOG_ERROR ( getLogger ( ) , " Index {} is out of bounds for registered species (size {}). " , index , m_species . size ( ) ) ;
throw std : : out_of_range ( " Index " + std : : to_string ( index ) + " is out of bounds for registered species (size " + std : : to_string ( m_species . size ( ) ) + " ). " ) ;
2025-09-16 11:23:01 -04:00
}
2025-12-08 11:31:46 -05:00
return m_species [ index ] ;
2025-09-16 11:23:01 -04:00
}
2025-12-08 11:31:46 -05:00
//------------------------------------------
// Utility methods
//------------------------------------------
2025-11-09 09:51:29 -05:00
2025-12-07 07:47:32 -05:00
std : : size_t Composition : : hash ( ) const {
if ( m_cache . hash . has_value ( ) ) {
return m_cache . hash . value ( ) ;
}
std : : size_t hash = utils : : CompositionHash : : hash_exact ( * this ) ;
m_cache . hash = hash ;
return hash ;
}
2025-11-07 15:49:25 -05:00
bool Composition : : contains (
const atomic : : Species & species
2025-11-08 06:42:41 -05:00
) const noexcept {
2025-12-08 11:31:46 -05:00
return std : : ranges : : binary_search ( m_species , species ) ;
2025-10-12 10:12:49 -04:00
}
2025-10-06 14:29:33 -04:00
bool Composition : : contains (
2025-11-07 15:49:25 -05:00
const std : : string & symbol
2025-10-06 14:29:33 -04:00
) const {
2025-11-07 15:49:25 -05:00
const auto species = getSpecies ( symbol ) ;
if ( ! species ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
2025-06-18 15:25:09 -04:00
}
2025-11-07 15:49:25 -05:00
return contains ( species . value ( ) ) ;
}
2025-11-08 06:42:41 -05:00
size_t Composition : : size ( ) const noexcept {
2025-12-08 11:31:46 -05:00
return m_species . size ( ) ;
2025-11-07 15:49:25 -05:00
}
2025-12-08 11:31:46 -05:00
std : : expected < std : : ptrdiff_t , Composition : : SpeciesIndexLookupError > Composition : : findSpeciesIndex ( const atomic : : Species & species ) const noexcept {
if ( m_species . empty ( ) ) return std : : unexpected ( SpeciesIndexLookupError : : NO_REGISTERED_SPECIES ) ;
2025-11-07 15:49:25 -05:00
2025-12-08 11:31:46 -05:00
const auto it = std : : ranges : : lower_bound ( m_species , species ) ;
2025-06-18 15:25:09 -04:00
2025-12-08 11:31:46 -05:00
if ( it = = m_species . end ( ) | | * it ! = species ) {
return std : : unexpected ( SpeciesIndexLookupError : : SPECIES_NOT_FOUND ) ;
2025-11-08 06:42:41 -05:00
}
2025-12-07 07:52:08 -05:00
2025-12-08 11:31:46 -05:00
return std : : distance ( m_species . begin ( ) , it ) ;
2025-11-07 15:49:25 -05:00
}
2025-03-26 08:07:11 -04:00
2025-12-08 11:31:46 -05:00
std : : vector < atomic : : Species > Composition : : symbolVectorToSpeciesVector ( const std : : vector < std : : string > & symbols ) {
std : : vector < atomic : : Species > species ;
species . reserve ( symbols . size ( ) ) ;
2025-03-26 08:07:11 -04:00
2025-06-11 14:49:11 -04:00
2025-12-08 11:31:46 -05:00
for ( const auto & symbol : symbols ) {
const auto speciesResult = getSpecies ( symbol ) ;
if ( ! speciesResult ) {
throw_unknown_symbol ( getLogger ( ) , symbol ) ;
}
species . push_back ( speciesResult . value ( ) ) ;
2025-11-07 15:49:25 -05:00
}
2025-12-08 11:31:46 -05:00
return species ;
2025-06-16 15:00:33 -04:00
}
2025-06-11 14:49:11 -04:00
2025-12-08 11:31:46 -05:00
//------------------------------------------
// Stream operator
//------------------------------------------
2025-11-07 15:49:25 -05:00
2025-10-06 14:29:33 -04:00
std : : ostream & operator < < (
std : : ostream & os ,
const Composition & composition
) {
2025-11-07 15:49:25 -05:00
os < < " Composition(Mass Fractions => [ " ;
2025-07-24 09:35:52 -04:00
size_t count = 0 ;
2025-12-08 11:31:46 -05:00
for ( const auto & species : composition . m_species ) {
2025-11-07 15:49:25 -05:00
os < < species < < " : " < < composition . getMassFraction ( species ) ;
if ( count < composition . size ( ) - 1 ) {
2025-06-20 13:51:27 -04:00
os < < " , " ;
}
2025-10-06 14:29:33 -04:00
count + + ;
2025-06-16 15:00:33 -04:00
}
2025-11-07 15:49:25 -05: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