21#include "quill/LogMacros.h"
24#include <unordered_map>
42 template<
typename A,
typename B>
43 std::vector<A> sortVectorBy(
44 std::vector<A> toSort,
45 const std::vector<B>& by
47 std::vector<std::size_t> indices(by.size());
48 for (
size_t i = 0; i < indices.size(); i++) {
52 std::ranges::sort(indices, [&](
size_t a,
size_t b) {
56 std::vector<A> sorted;
57 sorted.reserve(indices.size());
59 for (
const auto idx: indices) {
60 sorted.push_back(toSort[idx]);
76 const bool massFracMode
119 const double totalMolesPerMass
122 if (totalMolesPerMass == 0.0)
return 0.0;
160 [[maybe_unused]]
const double meanMolarMass
172 const double totalMolesPerMass
188 const std::vector<std::string>& symbols
190 for (
const auto& symbol : symbols) {
196 const std::set<std::string>& symbols
198 for (
const auto& symbol : symbols) {
204 const std::vector<std::string>& symbols,
205 const std::vector<double>& fractions,
206 const bool massFracMode
208 if (symbols.size() != fractions.size()) {
209 LOG_CRITICAL(m_logger,
"The number of symbols and fractions must be equal (got {} symbols and {} fractions).", symbols.size(), fractions.size());
210 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.");
215 for (
const auto &symbol : symbols) {
219 for (
size_t i = 0; i < symbols.size(); ++i) {
226 if (
const bool didFinalize =
finalize(); !didFinalize) {
227 std::string msg =
"Failed to finalize composition on construction. ";
228 msg +=
"Construction of a composition object requires that the sum of the fractions vector be 1.\n";
244 if (
this != &other) {
256 const std::string& symbol,
257 const bool massFracMode
260 LOG_ERROR(
m_logger,
"Invalid symbol: {}", symbol);
268 LOG_ERROR(
m_logger,
"Composition is in {} fraction mode. Cannot register symbol ({}) in {} fraction mode.",
m_massFracMode ?
"mass" :
"number", symbol, massFracMode ?
"mass" :
"number");
274 LOG_WARNING(
m_logger,
"Symbol {} is already registered.", symbol);
281 LOG_TRACE_L3(
m_logger,
"Registered symbol: {}", symbol);
285 const std::vector<std::string>& symbols,
286 const bool massFracMode
288 for (
const auto& symbol : symbols) {
295 const bool massFracMode
301 const std::vector<atomic::Species> &species,
302 const bool massFracMode
304 for (
const auto& s : species) {
314 std::set<atomic::Species> result;
316 result.insert(entry.isotope());
322 const std::string& symbol
329 LOG_ERROR(
m_logger,
"Invalid composition.");
335 const double sum = std::accumulate(fractions.begin(), fractions.end(), 0.0);
336 if (sum < 0.999999 || sum > 1.000001) {
337 LOG_ERROR(
m_logger,
"The sum of fractions must be equal to 1 (expected 1, got {}).", sum);
345 LOG_ERROR(
m_logger,
"Symbol {} is not registered.", symbol);
349 LOG_ERROR(
m_logger,
"Composition is in number fraction mode.");
352 if (mass_fraction < 0.0 || mass_fraction > 1.0) {
353 LOG_ERROR(
m_logger,
"Mass fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, mass_fraction);
357 const double old_mass_fraction =
m_compositions.at(symbol).mass_fraction();
359 return old_mass_fraction;
363 if (symbols.size() != mass_fractions.size()) {
366 std::vector<double> old_mass_fractions;
367 old_mass_fractions.reserve(symbols.size());
368 for (
size_t i = 0; i < symbols.size(); ++i) {
369 old_mass_fractions.push_back(
setMassFraction(symbols[i], mass_fractions[i]));
371 return old_mass_fractions;
375 const std::string& symbol,
376 const double& number_fraction
379 LOG_ERROR(
m_logger,
"Symbol {} is not registered.", symbol);
383 LOG_ERROR(
m_logger,
"Composition is in mass fraction mode.");
386 if (number_fraction < 0.0 || number_fraction > 1.0) {
387 LOG_ERROR(
m_logger,
"Number fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, number_fraction);
391 const double old_number_fraction =
m_compositions.at(symbol).number_fraction();
393 return old_number_fraction;
397 const std::vector<std::string>& symbols,
398 const std::vector<double>& number_fractions
400 if (symbols.size() != number_fractions.size()) {
403 std::vector<double> old_number_fractions;
404 old_number_fractions.reserve(symbols.size());
405 for (
size_t i = 0; i < symbols.size(); ++i) {
406 old_number_fractions.push_back(
setNumberFraction(symbols[i], number_fractions[i]));
408 return old_number_fractions;
413 const double &mass_fraction
419 const std::vector<atomic::Species> &species,
420 const std::vector<double> &mass_fractions
422 std::vector<std::string> symbols;
423 symbols.reserve(species.size());
424 for(
const auto& s : species) symbols.emplace_back(s.name());
430 const double &number_fraction
436 const std::vector<atomic::Species> &species,
437 const std::vector<double> &number_fractions
439 std::vector<std::string> symbols;
440 symbols.reserve(species.size());
441 for(
const auto& s : species) symbols.push_back(std::string(s.name()));
454 std::vector<double> mass_fractions;
457 mass_fractions.push_back(entry.mass_fraction());
460 double sum = std::accumulate(mass_fractions.begin(), mass_fractions.end(), 0.0);
461 if (norm && sum > 0) {
466 mass_fractions.clear();
468 mass_fractions.push_back(entry.mass_fraction());
475 LOG_ERROR(
m_logger,
"Composition is invalid after mass frac finalization (Total mass {}).", sum);
490 std::vector<double> number_fractions;
493 number_fractions.push_back(entry.number_fraction());
496 double sum = std::accumulate(number_fractions.begin(), number_fractions.end(), 0.0);
497 if (norm && sum > 0) {
502 number_fractions.clear();
504 number_fractions.push_back(entry.number_fraction());
511 LOG_ERROR(
m_logger,
"Composition is invalid after number frac finalization (Total number frac {}).", sum);
522 entry.m_massFracMode =
true;
523 entry.setMassFraction(X_i);
524 entry.m_massFracMode =
false;
535 LOG_ERROR(
m_logger,
"Compositions have not both been finalized. Hint: Consider running .finalize() on both compositions before mixing.");
539 if (fraction < 0.0 || fraction > 1.0) {
540 LOG_ERROR(
m_logger,
"Mixing fraction must be between 0 and 1. Currently it is {}.", fraction);
549 for (
const auto& symbol : mixedSymbols) {
550 double otherMassFrac = 0.0;
556 double massFraction = fraction * thisMassFrac + otherMassFrac * (1-fraction);
559 if (
const bool didFinalize = mixedComposition.
finalize(); !didFinalize) {
560 std::string msg =
"Failed to finalize mixed composition. ";
561 msg +=
"This likely indicates an issue with the input compositions not summing to 1.\n";
565 return mixedComposition;
570 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
574 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
575 std::string currentSymbols;
578 currentSymbols += sym;
580 currentSymbols +=
", ";
582 currentSymbols +=
", and ";
602 std::unordered_map<std::string, double> mass_fractions;
606 return mass_fractions;
611 const std::string& symbol
614 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
618 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
634 std::unordered_map<std::string, double> number_fractions;
638 return number_fractions;
642 const std::string &symbol
645 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
649 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
663 const std::string& symbol
666 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
670 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
684 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
692 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
700 LOG_ERROR(
m_logger,
"Composition must be finalized before getting the mean atomic mass number. Hint: Consider running .finalize().");
708 zSum += (val.mass_fraction() * val.m_isotope.z())/val.m_isotope.a();
718 LOG_ERROR(
m_logger,
"Composition must be finalized before getting the electron abundance. Hint: Consider running .finalize().");
728 Ye += (val.mass_fraction() * val.m_isotope.z())/val.m_isotope.a();
735 const std::vector<std::string>& symbols,
736 const std::string& method
738 if (
const std::array<std::string, 2> methods = {
"norm",
"none"}; std::ranges::find(methods, method) == methods.end()) {
739 const std::string errorMessage =
"Invalid method: " + method +
". Valid methods are 'norm' and 'none'.";
740 LOG_ERROR(
m_logger,
"Invalid method: {}. Valid methods are norm and none.", method);
745 for (
const auto& symbol : symbols) {
747 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
753 if (method ==
"norm") {
754 if (
const bool isNorm = subsetComposition.
finalize(
true); !isNorm) {
755 LOG_ERROR(
m_logger,
"Subset composition is invalid. (Unable to finalize with normalization).");
759 return subsetComposition;
763 const bool massFracMode
766 LOG_ERROR(
m_logger,
"Composition has not been finalized. Mode cannot be set unless composition is finalized. Hint: Consider running .finalize().");
778 LOG_ERROR(
m_logger,
"Composition mode could not be set due to some unknown error.");
779 throw std::runtime_error(
"Composition mode could not be set due to an unknown error.");
789 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
792 if (
m_cache.canonicalComp.has_value()) {
793 return m_cache.canonicalComp.value();
796 const std::array<std::string, 7> canonicalH = {
797 "H-1",
"H-2",
"H-3",
"H-4",
"H-5",
"H-6",
"H-7"
799 const std::array<std::string, 8> canonicalHe = {
800 "He-3",
"He-4",
"He-5",
"He-6",
"He-7",
"He-8",
"He-9",
"He-10"
802 for (
const auto& symbol : canonicalH) {
807 for (
const auto& symbol : canonicalHe) {
814 const bool isHSymbol = std::ranges::find(canonicalH, symbol) != std::end(canonicalH);
816 const bool isHeSymbol = std::ranges::find(canonicalHe, symbol) != std::end(canonicalHe);
818 if (isHSymbol || isHeSymbol) {
826 const double Z = 1.0 - (canonicalComposition.
X + canonicalComposition.
Y);
827 if (std::abs(Z - canonicalComposition.
Z) > 1e-6) {
829 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);
832 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);
833 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).");
836 m_cache.canonicalComp = canonicalComposition;
837 return canonicalComposition;
842 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
845 if (
m_cache.massFractions.has_value()) {
846 return m_cache.massFractions.value();
849 std::vector<double> massFractionVector;
850 std::vector<double> speciesMass;
856 massFractionVector.push_back(entry.mass_fraction());
857 speciesMass.push_back(entry.isotope().mass());
860 std::vector<double> massFractions = sortVectorBy(massFractionVector, speciesMass);
861 m_cache.massFractions = massFractions;
862 return massFractions;
868 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
871 if (
m_cache.numberFractions.has_value()) {
872 return m_cache.numberFractions.value();
875 std::vector<double> numberFractionVector;
876 std::vector<double> speciesMass;
882 numberFractionVector.push_back(entry.number_fraction());
883 speciesMass.push_back(entry.isotope().mass());
886 std::vector<double> numberFractions = sortVectorBy(numberFractionVector, speciesMass);
887 m_cache.numberFractions = numberFractions;
888 return numberFractions;
893 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
896 if (
m_cache.molarAbundances.has_value()) {
897 return m_cache.molarAbundances.value();
900 std::vector<double> molarAbundanceVector;
901 std::vector<double> speciesMass;
908 speciesMass.push_back(entry.isotope().mass());
911 std::vector<double> molarAbundances = sortVectorBy(molarAbundanceVector, speciesMass);
912 m_cache.molarAbundances = molarAbundances;
913 return molarAbundances;
918 const std::string &symbol
921 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
925 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
928 if (
m_cache.sortedSymbols.has_value()) {
929 return std::distance(
930 m_cache.sortedSymbols->begin(),
932 m_cache.sortedSymbols.value().begin(),
933 m_cache.sortedSymbols.value().end(),
939 std::vector<std::string> symbols;
940 std::vector<double> speciesMass;
946 symbols.emplace_back(entry.isotope().name());
947 speciesMass.push_back(entry.isotope().mass());
950 std::vector<std::string> sortedSymbols = sortVectorBy(symbols, speciesMass);
951 m_cache.sortedSymbols = sortedSymbols;
952 return std::distance(sortedSymbols.begin(), std::ranges::find(sortedSymbols, symbol));
959 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
963 LOG_ERROR(
m_logger,
"Species {} is not in the composition.", species.
name());
966 if (
m_cache.sortedSpecies.has_value()) {
967 return std::distance(
968 m_cache.sortedSpecies->begin(),
970 m_cache.sortedSpecies.value().begin(),
971 m_cache.sortedSpecies.value().end(),
977 std::vector<atomic::Species> speciesVector;
978 std::vector<double> speciesMass;
984 speciesVector.emplace_back(entry.isotope());
985 speciesMass.push_back(entry.isotope().mass());
988 std::vector<atomic::Species> sortedSpecies = sortVectorBy(speciesVector, speciesMass);
989 m_cache.sortedSpecies = sortedSpecies;
990 return std::distance(sortedSpecies.begin(), std::ranges::find(sortedSpecies, species));
997 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
1001 LOG_ERROR(
m_logger,
"Index {} is out of bounds for composition of size {}.", index,
m_compositions.size());
1002 throw std::out_of_range(
"Index " + std::to_string(index) +
" is out of bounds for composition of size " + std::to_string(
m_compositions.size()) +
".");
1004 if (
m_cache.sortedSpecies.has_value()) {
1005 return m_cache.sortedSpecies.value().at(index);
1008 std::vector<atomic::Species> speciesVector;
1009 std::vector<double> speciesMass;
1015 speciesVector.emplace_back(entry.isotope());
1016 speciesMass.push_back(entry.isotope().mass());
1019 std::vector<atomic::Species> sortedSymbols = sortVectorBy(speciesVector, speciesMass);
1020 return sortedSymbols.at(index);
1024 const std::string& symbol
1031 if (entry.isotope() == species) {
1043 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
1046 if (
const auto symbol =
static_cast<std::string
>(isotope.
name());
m_compositions.contains(symbol)) {
1057 return mix(other, 0.5);
1064 os <<
"Global Composition: \n";
1082 os <<
"Composition(finalized: " << (
composition.m_finalized ?
"true" :
"false") <<
", " ;
1084 for (
const auto &entry:
composition.m_compositions | std::views::values) {
1086 if (count <
composition.m_compositions.size() - 1) {
CompositionCache m_cache
Cache for computed properties to avoid redundant calculations.
void setCompositionMode(bool massFracMode)
Sets the composition mode (mass fraction vs. number fraction).
size_t getSpeciesIndex(const std::string &symbol) const override
get the index in the sorted vector representation for a given symbol
std::pair< std::unordered_map< std::string, CompositionEntry >, GlobalComposition > getComposition() const
Gets all composition entries and the global composition data.
Composition subset(const std::vector< std::string > &symbols, const std::string &method="norm") const
Creates a new Composition object containing a subset of species from this one.
void registerSymbol(const std::string &symbol, bool massFracMode=true)
Registers a new symbol for inclusion in the composition.
Composition()=default
Default constructor.
Composition operator+(const Composition &other) const
Overloads the + operator to mix two compositions with a 50/50 fraction.
std::set< std::string > m_registeredSymbols
The registered symbols.
Composition mix(const Composition &other, double fraction) const
Mixes this composition with another to produce a new composition.
std::set< std::string > getRegisteredSymbols() const override
Gets the registered symbols.
bool finalizeNumberFracMode(bool norm)
Finalizes the composition in number fraction mode.
double setMassFraction(const std::string &symbol, const double &mass_fraction)
Sets the mass fraction for a given symbol.
std::vector< double > getNumberFractionVector() const override
Get a uniform vector representation of the number fractions stored in the composition object sorted s...
double m_meanParticleMass
The mean particle mass of the composition (\sum_{i} \frac{n_i}{m_i}. where n_i is the number fraction...
void registerSpecies(const fourdst::atomic::Species &species, bool massFracMode=true)
Registers a new species by extracting its symbol.
Composition & operator=(Composition const &other)
Assignment operator.
bool hasSpecies(const fourdst::atomic::Species &species) const override
Checks if a species is registered in the composition.
double getElectronAbundance() const override
Compute the electron abundance of the composition.
bool m_massFracMode
True if mass fraction mode, false if number fraction mode.
bool finalize(bool norm=false)
Finalizes the composition, making it ready for querying.
double getMeanParticleMass() const override
Compute the mean particle mass of the composition.
double setNumberFraction(const std::string &symbol, const double &number_fraction)
Sets the number fraction for a given symbol.
bool contains(const atomic::Species &isotope) const override
Checks if a given isotope is present in the composition.
std::vector< double > getMassFractionVector() const override
Get a uniform vector representation of the mass fraction stored in the composition object sorted such...
void validateComposition(const std::vector< double > &fractions) const
Validates the given fractions, throwing an exception on failure.
bool finalizeMassFracMode(bool norm)
Finalizes the composition in mass fraction mode.
static bool isValidSymbol(const std::string &symbol)
Checks if the given symbol is valid by checking against the global species database.
bool m_finalized
True if the composition is finalized.
std::unordered_map< std::string, CompositionEntry > m_compositions
The compositions.
std::unordered_map< std::string, double > getMassFraction() const override
Gets the mass fractions of all species in the composition.
std::vector< double > getMolarAbundanceVector() const override
Get a uniform vector representation of the molar abundances stored in the composition object sorted s...
bool hasSymbol(const std::string &symbol) const override
Checks if a symbol is registered in the composition.
CanonicalComposition getCanonicalComposition(bool harsh=false) const
Gets the current canonical composition (X, Y, Z).
double getMolarAbundance(const std::string &symbol) const override
Gets the molar abundance (X_i / A_i) for a given symbol.
double m_specificNumberDensity
The specific number density of the composition (\sum_{i} X_i m_i. Where X_i is the number fraction of...
bool isValidComposition(const std::vector< double > &fractions) const
Checks if the given fractions are valid (sum to ~1.0).
std::unordered_map< std::string, double > getNumberFraction() const override
Gets the number fractions of all species in the composition.
atomic::Species getSpeciesAtIndex(size_t index) const override
Get the species at a given index in the sorted vector representation.
std::set< fourdst::atomic::Species > getRegisteredSpecies() const override
Get a set of all species that are registered in the composition.
double getMeanAtomicNumber() const override
Compute the mean atomic number of the composition.
Exception thrown due to a conflict in composition modes at the entry level.
Exception thrown when an operation is attempted on a composition that has not been finalized.
Exception thrown when attempting to initialize a composition entry that has already been initialized.
Exception thrown when the finalization process of a composition fails.
Exception thrown when a composition is in an invalid or inconsistent state.
Exception thrown for an invalid or unsupported mixing mode.
Exception thrown for an invalid chemical species symbol in a composition entry.
Exception thrown when a symbol used in a composition is invalid.
Exception thrown when a symbol is used that has not been registered.
Contains classes and functions related to atomic data, such as properties of atomic species.
static const std::unordered_map< std::string, const Species & > species
Map of species names to their corresponding Species objects.
std::ostream & operator<<(std::ostream &os, const GlobalComposition &comp)
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.
Represents a single entry (an isotope) within a composition.
bool setNumberFracMode(double totalMolesPerMass)
Switches the mode to number fraction mode.
bool getMassFracMode() const
Gets the mode of the composition entry.
CompositionEntry()
Default constructor. Initializes a default entry (H-1), but in an uninitialized state.
bool m_massFracMode
The mode of the composition entry. True if mass fraction, false if number fraction.
double number_fraction() const
Gets the number fraction of the species.
bool m_initialized
True if the composition entry has been initialized with a valid species.
double m_cachedNumberFraction
Cached number fraction for conversions when in mass fraction mode.
bool setMassFracMode(double meanMolarMass)
Switches the mode to mass fraction mode.
void setMassFraction(double mass_fraction)
Sets the mass fraction of the species.
std::string symbol() const
Gets the chemical symbol of the species.
void setSpecies(const std::string &symbol)
Sets the species for the composition entry. This can only be done once.
double mass_fraction() const
Gets the mass fraction of the species.
atomic::Species m_isotope
The atomic::Species object containing detailed isotope data.
void setNumberFraction(double number_fraction)
Sets the number fraction of the species.
double rel_abundance() const
Gets the relative abundance of the species.
std::string m_symbol
The chemical symbol of the species (e.g., "H-1", "Fe-56").
atomic::Species isotope() const
Gets the isotope data for the species.
Represents global properties of a finalized composition.
double specificNumberDensity
The specific number density (moles per unit mass, sum of X_i/M_i), where X_i is mass fraction and M_i...
double meanParticleMass
The mean mass per particle (inverse of specific number density). Units: g/mol.