21#include "quill/LogMacros.h"
24#include <unordered_map>
39 template<
typename A,
typename B>
40 std::vector<A> sortVectorBy(std::vector<A> toSort,
const std::vector<B>& by) {
41 std::vector<std::size_t> indices(by.size());
42 for (
size_t i = 0; i < indices.size(); i++) {
46 std::ranges::sort(indices, [&](
size_t a,
size_t b) {
50 std::vector<A> sorted;
51 sorted.reserve(indices.size());
53 for (
const auto idx: indices) {
54 sorted.push_back(toSort[idx]);
177 for (
const auto& symbol : symbols) {
183 for (
const auto& symbol : symbols) {
189 if (symbols.size() != fractions.size()) {
190 LOG_CRITICAL(m_logger,
"The number of symbols and fractions must be equal (got {} symbols and {} fractions).", symbols.size(), fractions.size());
191 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.");
196 for (
const auto &symbol : symbols) {
200 for (
size_t i = 0; i < symbols.size(); ++i) {
220 if (
this != &other) {
235 LOG_ERROR(
m_logger,
"Invalid symbol: {}", symbol);
244 LOG_ERROR(
m_logger,
"Composition is in mass fraction mode. Cannot register symbol ({}) in number fraction mode.", symbol);
250 LOG_WARNING(
m_logger,
"Symbol {} is already registered.", symbol);
257 LOG_TRACE_L3(
m_logger,
"Registered symbol: {}", symbol);
261 for (
const auto& symbol : symbols) {
271 for (
const auto& s : species) {
281 std::set<fourdst::atomic::Species> result;
283 result.insert(entry.isotope());
290 LOG_ERROR(
m_logger,
"Invalid composition.");
297 for (
const auto& fraction : fractions) {
300 if (sum < 0.999999 || sum > 1.000001) {
301 LOG_ERROR(
m_logger,
"The sum of fractions must be equal to 1 (expected 1, got {}).", sum);
314 LOG_ERROR(
m_logger,
"Symbol {} is not registered.", symbol);
319 LOG_ERROR(
m_logger,
"Composition is in number fraction mode.");
323 if (mass_fraction < 0.0 || mass_fraction > 1.0) {
324 LOG_ERROR(
m_logger,
"Mass fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, mass_fraction);
329 const double old_mass_fraction =
m_compositions.at(symbol).mass_fraction();
332 return old_mass_fraction;
336 if (symbols.size() != mass_fractions.size()) {
337 LOG_ERROR(
m_logger,
"The number of symbols and mass fractions must be equal (currently {} symbols and {} mass fractions).", symbols.size(), mass_fractions.size());
338 throw exceptions::InvalidCompositionError(
"The number of symbols and mass fractions must be equal (currently " + std::to_string(symbols.size()) +
" symbols and " + std::to_string(mass_fractions.size()) +
" mass fractions).");
341 std::vector<double> old_mass_fractions;
342 old_mass_fractions.reserve(symbols.size());
343 for (
size_t i = 0; i < symbols.size(); ++i) {
344 old_mass_fractions.push_back(
setMassFraction(symbols[i], mass_fractions[i]));
346 return old_mass_fractions;
354 const std::vector<double> &mass_fractions) {
355 std::vector<double> old_mass_fractions;
356 old_mass_fractions.reserve(species.size());
357 for (
const auto& spec : species) {
358 old_mass_fractions.push_back(
setMassFraction(spec, mass_fractions[&spec - &species[0]]));
360 return old_mass_fractions;
365 LOG_ERROR(
m_logger,
"Symbol {} is not registered.", symbol);
370 LOG_ERROR(
m_logger,
"Composition is in mass fraction mode, should be in number fraction mode to call setNumberFraction. Hint: The mode can be switched by first finalizing and then calling setCompositionMode(false).");
371 throw exceptions::CompositionModeError(
"Composition is in mass fraction mode, should be in number fraction mode to call setNumberFraction. Hint: The mode can be switched by first finalizing and then calling setCompositionMode(false).");
374 if (number_fraction < 0.0 || number_fraction > 1.0) {
375 LOG_ERROR(
m_logger,
"Number fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, number_fraction);
380 const double old_number_fraction =
m_compositions.at(symbol).number_fraction();
383 return old_number_fraction;
387 if (symbols.size() != number_fractions.size()) {
388 LOG_ERROR(
m_logger,
"The number of symbols and number fractions must be equal. (Currently {} symbols and {} number fractions).", symbols.size(), number_fractions.size());
389 throw exceptions::InvalidCompositionError(
"The number of symbols and number fractions must be equal. (Currently " + std::to_string(symbols.size()) +
" symbols and " + std::to_string(number_fractions.size()) +
" number fractions).");
392 std::vector<double> old_number_fractions;
393 old_number_fractions.reserve(symbols.size());
394 for (
size_t i = 0; i < symbols.size(); ++i) {
395 old_number_fractions.push_back(
setNumberFraction(symbols[i], number_fractions[i]));
397 return old_number_fractions;
405 const std::vector<double> &number_fractions) {
406 std::vector<double> old_number_fractions;
407 old_number_fractions.reserve(species.size());
408 for (
const auto& spec : species) {
409 old_number_fractions.push_back(
setNumberFraction(spec, number_fractions[&spec - &species[0]]));
411 return old_number_fractions;
415 bool finalized =
false;
428 std::vector<double> mass_fractions;
431 mass_fractions.push_back(entry.mass_fraction());
435 for (
const auto& mass_fraction : mass_fractions) {
436 sum += mass_fraction;
438 for (
double & mass_fraction : mass_fractions) {
439 mass_fraction /= sum;
448 double massSum = 0.0;
450 massSum += entry.mass_fraction();
452 LOG_ERROR(
m_logger,
"Composition is invalid (Total mass {}).", massSum);
465 std::vector<double> number_fractions;
468 number_fractions.push_back(entry.number_fraction());
472 for (
const auto& number_fraction : number_fractions) {
473 sum += number_fraction;
481 }
catch ([[maybe_unused]]
const std::runtime_error& e) {
482 double numberSum = 0.0;
484 numberSum += entry.number_fraction();
486 LOG_ERROR(
m_logger,
"Composition is invalid (Total number {}).", numberSum);
500 LOG_ERROR(
m_logger,
"Compositions have not both been finalized. Hint: Consider running .finalize() on both compositions before mixing.");
504 if (fraction < 0.0 || fraction > 1.0) {
505 LOG_ERROR(
m_logger,
"Mixing fraction must be between 0 and 1. Currently it is {}.", fraction);
514 for (
const auto& symbol : mixedSymbols) {
515 double otherMassFrac = 0.0;
521 double massFraction = fraction * thisMassFrac + otherMassFrac * (1-fraction);
525 return mixedComposition;
530 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
534 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
535 std::string currentSymbols;
538 currentSymbols += sym;
540 currentSymbols +=
", ";
542 currentSymbols +=
", and ";
560 std::unordered_map<std::string, double> mass_fractions;
564 return mass_fractions;
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);
589 std::unordered_map<std::string, double> number_fractions;
593 return number_fractions;
598 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
602 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
615 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
619 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
632 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
640 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
648 LOG_ERROR(
m_logger,
"Composition must be finalized before getting the mean atomic mass number. Hint: Consider running .finalize().");
657 zSum += (val.mass_fraction() * val.m_isotope.z())/val.m_isotope.a();
666 const std::array<std::string, 2> methods = {
"norm",
"none"};
668 if (std::ranges::find(methods, method) == methods.end()) {
669 const std::string errorMessage =
"Invalid method: " + method +
". Valid methods are 'norm' and 'none'.";
670 LOG_ERROR(
m_logger,
"Invalid method: {}. Valid methods are norm and none.", method);
675 for (
const auto& symbol : symbols) {
677 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
684 if (method ==
"norm") {
685 const bool isNorm = subsetComposition.
finalize(
true);
687 LOG_ERROR(
m_logger,
"Subset composition is invalid. (Unable to finalize with normalization).");
691 return subsetComposition;
696 LOG_ERROR(
m_logger,
"Composition has not been finalized. Mode cannot be set unless composition is finalized. Hint: Consider running .finalize().");
708 LOG_ERROR(
m_logger,
"Composition mode could not be set due to some unknown error.");
709 throw std::runtime_error(
"Composition mode could not be set due to an unknown error.");
717 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
721 const std::array<std::string, 7> canonicalH = {
722 "H-1",
"H-2",
"H-3",
"H-4",
"H-5",
"H-6",
"H-7"
724 const std::array<std::string, 8> canonicalHe = {
725 "He-3",
"He-4",
"He-5",
"He-6",
"He-7",
"He-8",
"He-9",
"He-10"
727 for (
const auto& symbol : canonicalH) {
732 for (
const auto& symbol : canonicalHe) {
739 const bool isHSymbol = std::ranges::find(canonicalH, symbol) != std::end(canonicalH);
740 const bool isHeSymbol = std::ranges::find(canonicalHe, symbol) != std::end(canonicalHe);
742 if (isHSymbol || isHeSymbol) {
749 const double Z = 1.0 - (canonicalComposition.
X + canonicalComposition.
Y);
750 if (std::abs(Z - canonicalComposition.
Z) > 1e-6) {
752 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);
755 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);
756 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).");
759 return canonicalComposition;
764 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
768 std::vector<double> massFractionVector;
769 std::vector<double> speciesMass;
775 massFractionVector.push_back(entry.mass_fraction());
776 speciesMass.push_back(entry.isotope().mass());
779 return sortVectorBy(massFractionVector, speciesMass);
785 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
789 std::vector<double> numberFractionVector;
790 std::vector<double> speciesMass;
796 numberFractionVector.push_back(entry.number_fraction());
797 speciesMass.push_back(entry.isotope().mass());
800 return sortVectorBy(numberFractionVector, speciesMass);
805 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
809 std::vector<double> molarAbundanceVector;
810 std::vector<double> speciesMass;
817 speciesMass.push_back(entry.isotope().mass());
820 return sortVectorBy(molarAbundanceVector, speciesMass);
825 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
829 LOG_ERROR(
m_logger,
"Symbol {} is not in the composition.", symbol);
833 std::vector<std::string> symbols;
834 std::vector<double> speciesMass;
840 symbols.emplace_back(entry.isotope().name());
841 speciesMass.push_back(entry.isotope().mass());
844 std::vector<std::string> sortedSymbols = sortVectorBy(symbols, speciesMass);
845 return std::distance(sortedSymbols.begin(), std::ranges::find(sortedSymbols, symbol));
850 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
854 LOG_ERROR(
m_logger,
"Species {} is not in the composition.", species.
name());
858 std::vector<atomic::Species> speciesVector;
859 std::vector<double> speciesMass;
865 speciesVector.emplace_back(entry.isotope());
866 speciesMass.push_back(entry.isotope().mass());
869 std::vector<atomic::Species> sortedSpecies = sortVectorBy(speciesVector, speciesMass);
870 return std::distance(sortedSpecies.begin(), std::ranges::find(sortedSpecies, species));
875 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
879 LOG_ERROR(
m_logger,
"Index {} is out of bounds for composition of size {}.", index,
m_compositions.size());
880 throw std::out_of_range(
"Index " + std::to_string(index) +
" is out of bounds for composition of size " + std::to_string(
m_compositions.size()) +
".");
883 LOG_ERROR(
m_logger,
"Index {} is negative. Cannot get species at negative index.", index);
884 throw std::out_of_range(
"Index " + std::to_string(index) +
" is negative. Cannot get species at negative index.");
887 std::vector<atomic::Species> speciesVector;
888 std::vector<double> speciesMass;
894 speciesVector.emplace_back(entry.isotope());
895 speciesMass.push_back(entry.isotope().mass());
898 std::vector<atomic::Species> sortedSymbols = sortVectorBy(speciesVector, speciesMass);
899 return sortedSymbols.at(index);
909 LOG_ERROR(
m_logger,
"Composition has not been finalized. Hint: Consider running .finalize().");
912 const auto symbol =
static_cast<std::string
>(isotope.
name());
922 return mix(other, 0.5);
926 os <<
"Global Composition: \n";
938 os <<
"Composition(finalized: " << (
composition.m_finalized ?
"true" :
"false") <<
", " ;
940 for (
const auto &entry:
composition.m_compositions | std::views::values) {
942 if (count <
composition.m_compositions.size() - 1) {
void setCompositionMode(bool massFracMode)
Sets the composition mode (mass fraction vs. number fraction).
std::pair< std::unordered_map< std::string, CompositionEntry >, GlobalComposition > getComposition() const
Gets all composition entries and the global composition data.
size_t getSpeciesIndex(const std::string &symbol) const
get the index in the sorted vector representation for a given symbol
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::vector< double > getNumberFractionVector() const
Get a uniform vector representation of the number fractions stored in the composition object sorted s...
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< fourdst::atomic::Species > getRegisteredSpecies() const
Get a set of all species that are registered in the composition.
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.
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.
double getMeanParticleMass() const
Compute the mean particle mass of the composition.
bool m_massFracMode
True if mass fraction mode, false if number fraction mode.
double getMolarAbundance(const std::string &symbol) const
Gets the molar abundance (X_i / A_i) for a given symbol.
bool hasSymbol(const std::string &symbol) const
Checks if a symbol is registered in the composition.
bool finalize(bool norm=false)
Finalizes the composition, making it ready for querying.
std::unordered_map< std::string, double > getNumberFraction() const
Gets the number fractions of all species in the composition.
double setNumberFraction(const std::string &symbol, const double &number_fraction)
Sets the number fraction for a given symbol.
std::set< std::string > getRegisteredSymbols() const
Gets the registered symbols.
std::vector< double > getMolarAbundanceVector() const
Get a uniform vector representation of the molar abundances stored in the composition object sorted s...
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.
double getMeanAtomicNumber() const
Compute the mean atomic number of the composition.
bool m_finalized
True if the composition is finalized.
atomic::Species getSpeciesAtIndex(size_t index) const
Get the species at a given index in the sorted vector representation.
std::unordered_map< std::string, CompositionEntry > m_compositions
The compositions.
CanonicalComposition getCanonicalComposition(bool harsh=false) const
Gets the current canonical composition (X, Y, Z).
bool contains(const fourdst::atomic::Species &isotope) const
Checks if a given isotope is present in the composition.
std::vector< double > getMassFractionVector() const
Get a uniform vector representation of the mass fraction stored in the composition object sorted such...
std::unordered_map< std::string, double > getMassFraction() const
Gets the mass fractions of all species in the composition.
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).
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
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.
double m_relAbundance
The relative abundance, used internally for conversions. For mass fraction mode, this is X_i / A_i; f...
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 m_numberFraction
The number fraction (mole fraction) of the species. Valid only if m_massFracMode is false.
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.
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.
bool setNumberFracMode(double totalMoles)
Switches the mode to number fraction mode.
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").
double m_massFraction
The mass fraction of the species. Valid only if m_massFracMode is true.
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.