21#include "quill/LogMacros.h"
24#include <unordered_map>
46 void throw_unknown_symbol(quill::Logger* logger,
const std::string& symbol) {
47 LOG_ERROR(logger,
"Symbol {} is not a valid species symbol (not in the species database)", symbol);
51 void throw_unregistered_symbol(quill::Logger* logger,
const std::string& symbol) {
52 LOG_ERROR(logger,
"Symbol {} is not registered in the composition.", symbol);
66 const std::set<std::string>& symbols
67 ) :
Composition(symbols | std::ranges::to<std::vector>()) {}
70 const std::set<atomic::Species> &species
71 ) :
Composition(species | std::ranges::to<std::vector>()) {}
74 const std::unordered_set<std::string> &symbols
75 ) :
Composition(symbols | std::ranges::to<std::vector>()) {}
78 const std::unordered_set<atomic::Species> &species
79 ) :
Composition(species | std::ranges::to<std::vector>()) {}
82 const std::vector<std::string>& symbols
86 const std::vector<atomic::Species> &species
93 const auto last = std::ranges::unique(
m_species).begin();
106 const std::vector<std::string>& symbols,
107 const std::vector<double>& molarAbundances
111 const std::set<std::string> &symbols,
112 const std::vector<double> &molarAbundances
117 const std::unordered_map<std::string, double> &symbolMolarAbundances
119 symbolMolarAbundances | std::views::keys | std::ranges::to<std::vector>(),
120 symbolMolarAbundances | std::views::values | std::ranges::to<std::vector>()
124 const std::map<std::string, double> &symbolMolarAbundances
126 symbolMolarAbundances | std::views::keys | std::ranges::to<std::vector>(),
127 symbolMolarAbundances | std::views::values | std::ranges::to<std::vector>()
131 const std::unordered_map<atomic::Species, double> &speciesMolarAbundances
133 speciesMolarAbundances | std::views::keys | std::ranges::to<std::vector>(),
134 speciesMolarAbundances | std::views::values | std::ranges::to<std::vector>()
138 const std::map<atomic::Species, double> &speciesMolarAbundances
140 speciesMolarAbundances | std::views::keys | std::ranges::to<std::vector>(),
141 speciesMolarAbundances | std::views::values | std::ranges::to<std::vector>()
145 const std::vector<atomic::Species> &species,
146 const std::vector<double> &molarAbundances
148 if (__builtin_expect(species.size() != molarAbundances.size(), 0)) {
149 LOG_CRITICAL(
getLogger(),
"The number of species and molarAbundances must be equal (got {} species and {} molarAbundances).", species.size(), molarAbundances.size());
150 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.");
153 const size_t numSpecies = species.size();
157 for (
size_t i = 0; i < numSpecies; ++i) {
159 if (__builtin_expect(molarAbundances[i] < 0.0, 0)) {
160 LOG_CRITICAL(
getLogger(),
"Molar abundance for species {} is negative (y = {}). Molar abundances must be non-negative.", species[i].name(), molarAbundances[i]);
161 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.");
168 std::ranges::sort(combined, [](
const auto& a,
const auto& b) ->
bool {
169 const auto& spA = std::get<0>(a);
170 const auto& spB = std::get<0>(b);
176 return std::get<1>(a) > std::get<1>(b);
179 auto [first, last] = std::ranges::unique(combined, [](
const auto& a,
const auto& b) {
180 return std::get<0>(a) == std::get<0>(b);
183 const auto newEndIndex = std::distance(combined.begin(), first);
198 for (
const auto& species :
composition.getRegisteredSpecies()) {
207 if (
this != &other) {
227 return std::make_unique<Composition>(*
this);
235 const std::string& symbol
239 throw_unknown_symbol(
getLogger(), symbol);
246 const std::vector<std::string>& symbols
254 if (
const auto it = std::ranges::lower_bound(
m_species, species); it ==
m_species.end() || *it != species) {
255 const auto index = std::distance(
m_species.begin(), it);
263 const std::vector<atomic::Species> &species
268 if (species.empty())
return;
270 const size_t total_size =
m_species.size() + species.size();
274 for (
const auto& sp : species) {
281 std::ranges::sort(combined, [](
const auto& a,
const auto& b) {
282 const auto& speciesA = std::get<0>(a);
283 const auto& speciesB = std::get<0>(b);
285 if (speciesA != speciesB) {
286 return speciesA < speciesB;
289 return std::get<1>(a) > std::get<1>(b);
292 auto [first, last] = std::ranges::unique(combined, [](
const auto& a,
const auto& b) {
293 return std::get<0>(a) == std::get<0>(b);
296 const auto newEndIndex = std::distance(combined.begin(), first);
305 std::set<std::string> symbols;
307 symbols.insert(std::string(species.name()));
322 const std::string &symbol,
323 const double &molar_abundance
326 if (__builtin_expect(!species, 0)) {
327 throw_unknown_symbol(
getLogger(), symbol);
335 const double &molar_abundance
337 if (__builtin_expect(molar_abundance < 0.0, 0)) {
338 LOG_ERROR(
getLogger(),
"Molar abundance must be non-negative for symbol {}. Currently it is {}.", species.
name(), molar_abundance);
342 const std::expected<std::ptrdiff_t, SpeciesIndexLookupError> speciesIndexResult =
findSpeciesIndex(species);
343 if (__builtin_expect(!speciesIndexResult, 0)) {
344 throw_unregistered_symbol(
getLogger(), std::string(species.
name()));
347 assert(
static_cast<size_t>(speciesIndexResult.value()) <
m_molarAbundances.size());
360 const std::vector<std::string> &symbols,
361 const std::vector<double> &molar_abundances
367 const std::set<std::string> &symbols,
368 const std::vector<double> &molar_abundances
374 const std::set<atomic::Species> &species,
375 const std::vector<double> &molar_abundances
381 const std::vector<atomic::Species> &species,
382 const std::vector<double> &molar_abundances
384 if (__builtin_expect(species.size() != molar_abundances.size(), 0)) {
385 LOG_CRITICAL(
getLogger(),
"The number of species and molar_abundances must be equal (got {} species and {} molar_abundances).", species.size(), molar_abundances.size());
386 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.");
389 if (species.empty())
return;
391 if (species.size() ==
m_species.size()) {
393 for (
const auto& [sp, y] : std::views::zip(species, molar_abundances)) {
394 if (__builtin_expect(y < 0.0, 0)) {
395 LOG_ERROR(
getLogger(),
"Molar abundance must be non-negative. Instead got {} for species {}.", y, sp.name());
406 for (
size_t i = 0; i < species.size(); ++i) {
407 const double y = molar_abundances[i];
408 const auto& sp = species[i];
409 if (__builtin_expect(y < 0.0, 0)) {
410 LOG_CRITICAL(
getLogger(),
"Molar abundance must be non-negative. Instead got {} for species {}.", y, sp.name());
414 const std::expected<std::ptrdiff_t, SpeciesIndexLookupError> speciesIndexResult =
findSpeciesIndex(sp);
415 if (__builtin_expect(!speciesIndexResult, 0)) {
416 throw_unregistered_symbol(
getLogger(), std::string(sp.name()));
419 const std::ptrdiff_t speciesIndex = speciesIndexResult.value();
435 throw_unknown_symbol(
getLogger(), symbol);
443 const std::expected<std::ptrdiff_t, SpeciesIndexLookupError> speciesIndexResult =
findSpeciesIndex(species);
444 if (!speciesIndexResult) {
445 throw_unregistered_symbol(
getLogger(), std::string(species.
name()));
448 double totalMass = 0;
449 double speciesMass = 0;
450 for (
const auto& [sp, y] : *
this) {
451 const double contrib = y * sp.mass();
452 totalMass += contrib;
454 speciesMass = contrib;
457 return speciesMass / totalMass;
461 std::unordered_map<atomic::Species, double> mass_fractions;
462 for (
const auto &species: *
this | std::views::keys) {
465 return mass_fractions;
470 const std::string& symbol
474 throw_unknown_symbol(
getLogger(), symbol);
482 const std::expected<std::ptrdiff_t, SpeciesIndexLookupError> speciesIndexResult =
findSpeciesIndex(species);
483 if (!speciesIndexResult) {
484 throw_unregistered_symbol(
getLogger(), std::string(species.
name()));
486 const std::ptrdiff_t speciesIndex = speciesIndexResult.value();
488 const double total_moles_per_gram = std::accumulate(
497 std::unordered_map<atomic::Species, double> number_fractions;
501 return number_fractions;
505 const std::string &symbol
509 throw_unknown_symbol(
getLogger(), symbol);
518 const std::expected<std::ptrdiff_t, SpeciesIndexLookupError> speciesIndexResult =
findSpeciesIndex(species);
519 if (!speciesIndexResult) {
520 throw_unregistered_symbol(
getLogger(), std::string(species.
name()));
522 const std::ptrdiff_t speciesIndex = speciesIndexResult.value();
531 double totalMass = 0.0;
532 double totalMoles = 0.0;
534 for (
size_t i = 0; i <
m_species.size(); ++i) {
539 return totalMass / totalMoles;
545 for (
const auto& [species, y] : *
this) {
546 Ye += species.z() * y;
556 if (
m_cache.canonicalComp.has_value()) {
557 return m_cache.canonicalComp.value();
563 for (
const auto& symbol : canonicalH) {
568 for (
const auto& symbol : canonicalHe) {
575 if (canonicalH.contains(
species) || canonicalHe.contains(
species)) {
583 const double Z = 1.0 - (canonicalComposition.
X + canonicalComposition.
Y);
584 if (std::abs(Z - canonicalComposition.
Z) > 1e-16) {
585 LOG_ERROR(
getLogger(),
"Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He).", Z, canonicalComposition.
Z);
586 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).");
588 m_cache.canonicalComp = canonicalComposition;
589 return canonicalComposition;
597 if (
m_cache.massFractions.has_value()) {
598 return m_cache.massFractions.value();
601 std::vector<double> massFractionVector;
609 m_cache.massFractions = massFractionVector;
610 return massFractionVector;
615 if (
m_cache.numberFractions.has_value()) {
616 return m_cache.numberFractions.value();
619 std::vector<double> numberFractionVector;
627 m_cache.numberFractions = numberFractionVector;
628 return numberFractionVector;
640 const std::string &symbol
644 throw_unknown_symbol(
getLogger(), symbol);
654 if (!speciesIndexResult) {
655 switch (speciesIndexResult.error()) {
661 throw std::logic_error(
"Unhandled SpeciesIndexLookupError in Composition::getSpeciesIndex");
665 return static_cast<size_t>(speciesIndexResult.value());
672 LOG_ERROR(
getLogger(),
"Index {} is out of bounds for registered species (size {}).", index,
m_species.size());
673 throw std::out_of_range(
"Index " + std::to_string(index) +
" is out of bounds for registered species (size " + std::to_string(
m_species.size()) +
").");
685 if (
m_cache.hash.has_value()) {
700 const std::string &symbol
704 throw_unknown_symbol(
getLogger(), symbol);
722 return std::distance(
m_species.begin(), it);
726 std::vector<atomic::Species>
species;
727 species.reserve(symbols.size());
730 for (
const auto& symbol : symbols) {
731 const auto speciesResult =
getSpecies(symbol);
732 if (!speciesResult) {
733 throw_unknown_symbol(
getLogger(), symbol);
735 species.push_back(speciesResult.value());
750 os <<
"Composition(Mass Fractions => [";
Abstract base class for chemical composition representations.
virtual double getMolarAbundance(const std::string &symbol) const =0
Get the molar abundance for a given symbol.
virtual const std::vector< atomic::Species > & getRegisteredSpecies() const noexcept=0
Get all registered atomic species in the composition.
CompositionCache m_cache
Cache for computed properties to avoid redundant calculations.
size_t getSpeciesIndex(const std::string &symbol) const override
get the index in the sorted vector representation for a given symbol
bool contains(const atomic::Species &species) const noexcept override
Checks if a given species is present in the composition.
std::unordered_map< atomic::Species, double > getNumberFraction() const noexcept override
Gets the number fractions of all species in the composition.
Composition()=default
Default constructor.
void setMolarAbundance(const std::string &symbol, const double &molar_abundance)
Sets the molar abundance for a given symbol.
void registerSpecies(const atomic::Species &species) noexcept
Registers a new species by extracting its symbol.
void registerSymbol(const std::string &symbol)
Registers a new symbol for inclusion in the composition.
std::set< std::string > getRegisteredSymbols() const noexcept override
Gets the registered symbols.
static std::vector< atomic::Species > symbolVectorToSpeciesVector(const std::vector< std::string > &symbols)
static quill::Logger * getLogger()
Gets the logger instance for the Composition class. This is static to ensure that all composition obj...
Composition & operator=(Composition const &other)
Assignment operator.
std::unique_ptr< CompositionAbstract > clone() const override
std::size_t hash() const override
double getElectronAbundance() const noexcept override
Compute the electron abundance of the composition.
size_t size() const noexcept override
Gets the number of registered species in the composition.
std::unordered_map< atomic::Species, double > getMassFraction() const noexcept override
Gets the mass fractions of all species in the composition.
std::vector< atomic::Species > m_species
CanonicalComposition getCanonicalComposition() const
Compute the canonical composition (X, Y, Z) of the composition.
std::vector< double > m_molarAbundances
std::vector< double > getMolarAbundanceVector() const noexcept override
Get a uniform vector representation of the molar abundances stored in the composition object sorted s...
double getMolarAbundance(const std::string &symbol) const override
Gets the molar abundances of all species in the composition.
std::expected< std::ptrdiff_t, SpeciesIndexLookupError > findSpeciesIndex(const atomic::Species &species) const noexcept
std::vector< double > getNumberFractionVector() const noexcept override
Get a uniform vector representation of the number fractions stored in the composition object sorted s...
atomic::Species getSpeciesAtIndex(size_t index) const override
Get the species at a given index in the sorted vector representation.
const std::vector< atomic::Species > & getRegisteredSpecies() const noexcept override
Get a set of all species that are registered in the composition.
std::vector< double > getMassFractionVector() const noexcept override
Get a uniform vector representation of the mass fraction stored in the composition object sorted such...
double getMeanParticleMass() const noexcept override
Compute the mean particle mass of the composition.
Exception thrown when a composition is in an invalid or inconsistent state.
Exception thrown when an unknown symbol is encountered.
Exception thrown when a symbol is used that has not been registered.
Contains canonical information about atomic species and elements used by 4D-STAR.
static const Species H_7("H-7", "H", 5, 6, 1, 7, 940.0, "B-", 23062.0, 652.0, "/2+#", "n ?", 7.052749, 1078.0)
static const Species H_2("H-2", "H", 0, 1, 1, 2, 1112.2831, "B-", std::numeric_limits< double >::quiet_NaN(), std::numeric_limits< double >::infinity(), "+*", "S=0.0145 78", 2.014101777844, 1.5e-05)
static const Species H_6("H-6", "H", 4, 5, 1, 6, 961.6395, "B-", 24283.6294, 294.0, "-#", "?;3n ?", 6.044955437, 272.816)
static const Species He_10("He-10", "He", 6, 8, 2, 10, 2995.134, "B-", 16144.5191, 260.0, "+", "n=100", 10.052815306, 99.676)
static const Species He_9("He-9", "He", 5, 7, 2, 9, 3349.038, "B-", 15980.9213, 2.5, "/2(+)", "=100", 9.043946414, 50.259)
static const std::unordered_map< std::string, const Species & > species
Map of species names to their corresponding Species objects.
static const Species He_8("He-8", "He", 4, 6, 2, 8, 3924.521, "B-", 10663.8784, 119.5, "+", "-=100;B-n=16 1;B-t=0.9 1", 8.033934388, 0.095)
static const Species He_3("He-3", "He", -1, 1, 2, 3, 2572.68044, "B-", -13736.0, std::numeric_limits< double >::infinity(), "/2+*", "S=0.0002 2", 3.01602932197, 6e-05)
static const Species H_1("H-1", "H", -1, 0, 1, 1, 0.0, "B-", std::numeric_limits< double >::quiet_NaN(), std::numeric_limits< double >::infinity(), "/2+*", "S=99.9855 78", 1.007825031898, 1.4e-05)
static const Species H_5("H-5", "H", 3, 4, 1, 5, 1336.3592, "B-", 21661.2131, 86.0, "1/2+)", "n=100", 5.035311492, 96.02)
static const Species He_7("He-7", "He", 3, 5, 2, 7, 4123.0578, "B-", 11166.0229, 2.51, "3/2)-", "=100", 7.027990652, 8.115)
static const Species He_4("He-4", "He", 0, 2, 2, 4, 7073.9156, "B-", -22898.274, std::numeric_limits< double >::infinity(), "+", "S=99.9998 2", 4.00260325413, 0.00016)
static const Species He_5("He-5", "He", 1, 3, 2, 5, 5512.1325, "B-", -447.6529, 602.0, "/2-", "=100", 5.012057224, 21.47)
static const Species H_3("H-3", "H", 1, 2, 1, 3, 2827.2654, "B-", 18.59202, 388789632.0, "/2+*", "-=100", 3.01604928132, 8e-05)
static const Species He_6("He-6", "He", 2, 4, 2, 6, 4878.5199, "B-", 3505.2147, 806.92, "+", "-=100;B-d=0.000278 18", 6.018885889, 0.057)
static const Species H_4("H-4", "H", 2, 3, 1, 4, 1720.4491, "B-", 22196.2131, 139.0, "-", "=100", 4.026431867, 107.354)
Utilities and types for representing and manipulating chemical compositions.
std::ostream & operator<<(std::ostream &os, const Composition &composition)
std::optional< fourdst::atomic::Species > getSpecies(const std::string &symbol)
Represents an atomic species (isotope) with its fundamental physical properties.
std::string_view name() const
Gets the name of the species.
Represents the canonical (X, Y, Z) composition of stellar material.
double Y
Mass fraction of Helium.
double X
Mass fraction of Hydrogen.
double Z
Mass fraction of Metals.
static uint64_t hash_exact(const CompositionT &comp)