feat(composition): added numberFrac methods and subset method

Composition can now be placed in either number fraction or mass fraction mode and can (after finalization) convert between them. Subsets of compositions can also be made. Normalization has also been added to finalize. Tests have been added
This commit is contained in:
2025-03-25 12:49:37 -04:00
parent 39461ba145
commit 95e4d10c59
3 changed files with 680 additions and 92 deletions

View File

@@ -2,98 +2,235 @@
#include "quill/LogMacros.h"
#include <stdexcept>
#include <unordered_map>
#include <vector>
#include <array>
#include <utility>
#include "atomicSpecies.h"
using namespace composition;
CompositionEntry::CompositionEntry() : m_symbol("H-1"), m_isotope(chemSpecies::species.at("H-1")), m_initialized(false) {}
CompositionEntry::CompositionEntry(const std::string& symbol, bool massFracMode) : m_symbol(symbol), m_isotope(chemSpecies::species.at(symbol)), m_massFracMode(massFracMode) {
setSpecies(symbol);
}
CompositionEntry::CompositionEntry(const CompositionEntry& entry) :
m_symbol(entry.m_symbol),
m_isotope(entry.m_isotope),
m_massFracMode(entry.m_massFracMode),
m_massFraction(entry.m_massFraction),
m_numberFraction(entry.m_numberFraction),
m_relAbundance(entry.m_relAbundance),
m_initialized(entry.m_initialized) {}
void CompositionEntry::setSpecies(const std::string& symbol) {
if (m_initialized) {
throw std::runtime_error("Composition entry is already initialized.");
}
if (chemSpecies::species.count(symbol) == 0) {
throw std::runtime_error("Invalid symbol.");
}
m_symbol = symbol;
m_isotope = chemSpecies::species.at(symbol);
m_initialized = true;
}
std::string CompositionEntry::symbol() const {
return m_symbol;
}
double CompositionEntry::mass_fraction() const {
if (!m_massFracMode) {
throw std::runtime_error("Composition entry is in number fraction mode.");
}
return m_massFraction;
}
double CompositionEntry::mass_fraction(double meanMolarMass) const {
if (m_massFracMode) {
return m_massFraction;
}
return m_relAbundance / meanMolarMass;
}
double CompositionEntry::number_fraction() const {
if (m_massFracMode) {
throw std::runtime_error("Composition entry is in mass fraction mode.");
}
return m_numberFraction;
}
double CompositionEntry::number_fraction(double totalMoles) const {
if (m_massFracMode) {
return m_relAbundance / totalMoles;
}
return m_numberFraction;
}
double CompositionEntry::rel_abundance() const {
return m_relAbundance;
}
chemSpecies::Species CompositionEntry::isotope() const {
return m_isotope;
}
void CompositionEntry::setMassFraction(double mass_fraction) {
if (!m_massFracMode) {
throw std::runtime_error("Composition entry is in number fraction mode.");
}
m_massFraction = mass_fraction;
m_relAbundance = m_massFraction / m_isotope.mass();
}
void CompositionEntry::setNumberFraction(double number_fraction) {
if (m_massFracMode) {
throw std::runtime_error("Composition entry is in mass fraction mode.");
}
m_numberFraction = number_fraction;
m_relAbundance = m_numberFraction * m_isotope.mass();
}
bool CompositionEntry::setMassFracMode(double meanParticleMass) {
if (m_massFracMode) {
return false;
}
m_massFracMode = true;
m_massFraction = m_relAbundance / meanParticleMass;
return true;
}
bool CompositionEntry::setNumberFracMode(double specificNumberDensity) {
if (!m_massFracMode) {
return false;
}
m_massFracMode = false;
m_numberFraction = m_relAbundance / specificNumberDensity;
return true;
}
bool CompositionEntry::getMassFracMode() const {
return m_massFracMode;
}
Composition::Composition(const std::vector<std::string>& symbols) {
for (const auto& symbol : symbols) {
registerSymbol(symbol);
}
}
void Composition::validateComposition(const std::vector<double>& mass_fractions) const {
if (!isValidComposition(mass_fractions)) {
LOG_ERROR(m_logger, "Invalid composition.");
throw std::runtime_error("Invalid composition.");
}
}
bool Composition::isValidComposition(const std::vector<double>& mass_fractions) const {
double sum = 0.0;
for (const auto& mass_fraction : mass_fractions) {
sum += mass_fraction;
}
if (sum < 0.99999999 || sum > 1.000000001) {
LOG_ERROR(m_logger, "The sum of mass fractions must be equal to 1.");
return false;
Composition::Composition(const std::vector<std::string>& symbols, const std::vector<double>& fractions, bool massFracMode) : m_massFracMode(massFracMode) {
if (symbols.size() != fractions.size()) {
LOG_ERROR(m_logger, "The number of symbols and fractions must be equal.");
throw std::runtime_error("The number of symbols and fractions must be equal.");
}
return true;
}
Composition::Composition(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions) {
if (symbols.size() != mass_fractions.size()) {
LOG_ERROR(m_logger, "The number of symbols and mass fractions must be equal.");
throw std::runtime_error("The number of symbols and mass fractions must be equal.");
}
validateComposition(mass_fractions);
finalize();
validateComposition(fractions);
for (const auto &symbol : symbols) {
registerSymbol(symbol);
}
for (size_t i = 0; i < symbols.size(); ++i) {
setComposition(symbols[i], mass_fractions[i]);
if (m_massFracMode) {
setMassFraction(symbols[i], fractions[i]);
} else {
setNumberFraction(symbols[i], fractions[i]);
}
}
finalize();
}
void Composition::registerSymbol(const std::string& symbol) {
void Composition::registerSymbol(const std::string& symbol, bool massFracMode) {
if (!isValidSymbol(symbol)) {
LOG_ERROR(m_logger, "Invalid symbol: {}", symbol);
throw std::runtime_error("Invalid symbol.");
}
// If no symbols have been registered allow mode to be set
if (m_registeredSymbols.size() == 0) {
m_massFracMode = massFracMode;
} else {
if (m_massFracMode != massFracMode) {
LOG_ERROR(m_logger, "Composition is in mass fraction mode. Cannot register symbol in number fraction mode.");
throw std::runtime_error("Composition is in mass fraction mode. Cannot register symbol in number fraction mode.");
}
}
if (m_registeredSymbols.find(symbol) != m_registeredSymbols.end()) {
LOG_WARNING(m_logger, "Symbol {} is already registered.", symbol);
return;
}
m_registeredSymbols.insert(symbol);
CompositionEntry entry(symbol, m_massFracMode);
m_compositions[symbol] = entry;
LOG_INFO(m_logger, "Registered symbol: {}", symbol);
}
void Composition::registerSymbol(const std::vector<std::string>& symbols, bool massFracMode) {
for (const auto& symbol : symbols) {
registerSymbol(symbol, massFracMode);
}
}
std::set<std::string> Composition::getRegisteredSymbols() const {
return m_registeredSymbols;
}
void Composition::validateComposition(const std::vector<double>& fractions) const {
if (!isValidComposition(fractions)) {
LOG_ERROR(m_logger, "Invalid composition.");
throw std::runtime_error("Invalid composition.");
}
}
bool Composition::isValidComposition(const std::vector<double>& fractions) const {
double sum = 0.0;
for (const auto& fraction : fractions) {
sum += fraction;
}
if (sum < 0.999999 || sum > 1.000001) {
LOG_ERROR(m_logger, "The sum of fractions must be equal to 1.");
return false;
}
return true;
}
bool Composition::isValidSymbol(const std::string& symbol) const {
return chemSpecies::species.count(symbol) > 0;
}
double Composition::setComposition(const std::string& symbol, const double& mass_fraction) {
m_finalized = false;
double Composition::setMassFraction(const std::string& symbol, const double& mass_fraction) {
if (m_registeredSymbols.find(symbol) == m_registeredSymbols.end()) {
LOG_ERROR(m_logger, "Symbol {} is not registered.", symbol);
throw std::runtime_error("Symbol is not registered.");
}
if (!m_massFracMode) {
LOG_ERROR(m_logger, "Composition is in number fraction mode.");
throw std::runtime_error("Composition is in number fraction mode.");
}
if (mass_fraction < 0.0 || mass_fraction > 1.0) {
LOG_ERROR(m_logger, "Mass fraction must be between 0 and 1.");
LOG_ERROR(m_logger, "Mass fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, mass_fraction);
throw std::runtime_error("Mass fraction must be between 0 and 1.");
}
double old_mass_fraction = 0.0;
if (m_compositions.find(symbol) != m_compositions.end()) {
old_mass_fraction = m_compositions[symbol].mass_fraction;
}
m_compositions[symbol] = {symbol, mass_fraction};
m_finalized = false;
double old_mass_fraction = m_compositions.at(symbol).mass_fraction();
m_compositions.at(symbol).setMassFraction(mass_fraction);
return old_mass_fraction;
}
std::vector<double> Composition::setComposition(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions) {
m_finalized = false;
std::vector<double> Composition::setMassFraction(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions) {
if (symbols.size() != mass_fractions.size()) {
LOG_ERROR(m_logger, "The number of symbols and mass fractions must be equal.");
throw std::runtime_error("The number of symbols and mass fractions must be equal.");
@@ -102,45 +239,131 @@ std::vector<double> Composition::setComposition(const std::vector<std::string>&
std::vector<double> old_mass_fractions;
old_mass_fractions.reserve(symbols.size());
for (size_t i = 0; i < symbols.size(); ++i) {
old_mass_fractions.push_back(setComposition(symbols[i], mass_fractions[i]));
old_mass_fractions.push_back(setMassFraction(symbols[i], mass_fractions[i]));
}
return old_mass_fractions;
}
std::set<std::string> Composition::getRegisteredSymbols() const {
return m_registeredSymbols;
}
std::unordered_map<std::string, CompositionEntry> Composition::getCompositions() const {
if (!m_finalized) {
LOG_ERROR(m_logger, "Composition has not been finalized.");
throw std::runtime_error("Composition has not been finalized (Consider running .finalize()).");
double Composition::setNumberFraction(const std::string& symbol, const double& number_fraction) {
if (m_registeredSymbols.find(symbol) == m_registeredSymbols.end()) {
LOG_ERROR(m_logger, "Symbol {} is not registered.", symbol);
throw std::runtime_error("Symbol is not registered.");
}
return m_compositions;
if (m_massFracMode) {
LOG_ERROR(m_logger, "Composition is in mass fraction mode.");
throw std::runtime_error("Composition is in mass fraction mode.");
}
if (number_fraction < 0.0 || number_fraction > 1.0) {
LOG_ERROR(m_logger, "Number fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, number_fraction);
throw std::runtime_error("Number fraction must be between 0 and 1.");
}
m_finalized = false;
double old_number_fraction = m_compositions.at(symbol).number_fraction();
m_compositions.at(symbol).setNumberFraction(number_fraction);
return old_number_fraction;
}
bool Composition::finalize() {
m_finalized = true;
std::vector<double> Composition::setNumberFraction(const std::vector<std::string>& symbols, const std::vector<double>& number_fractions) {
if (symbols.size() != number_fractions.size()) {
LOG_ERROR(m_logger, "The number of symbols and number fractions must be equal.");
throw std::runtime_error("The number of symbols and number fractions must be equal.");
}
std::vector<double> old_number_fractions;
old_number_fractions.reserve(symbols.size());
for (size_t i = 0; i < symbols.size(); ++i) {
old_number_fractions.push_back(setNumberFraction(symbols[i], number_fractions[i]));
}
return old_number_fractions;
}
bool Composition::finalize(bool norm) {
bool finalized = false;
if (m_massFracMode) {
finalized = finalizeMassFracMode(norm);
} else {
finalized = finalizeNumberFracMode(norm);
}
if (finalized) {
m_finalized = true;
}
return finalized;
}
bool Composition::finalizeMassFracMode(bool norm) {
std::vector<double> mass_fractions;
mass_fractions.reserve(m_compositions.size());
for (const auto& [_, entry] : m_compositions) {
mass_fractions.push_back(entry.mass_fraction);
mass_fractions.push_back(entry.mass_fraction());
}
if (norm) {
double sum = 0.0;
for (const auto& mass_fraction : mass_fractions) {
sum += mass_fraction;
}
for (int i = 0; i < mass_fractions.size(); ++i) {
mass_fractions[i] /= sum;
}
for (auto& [symbol, entry] : m_compositions) {
setMassFraction(symbol, entry.mass_fraction() / sum);
}
}
try {
validateComposition(mass_fractions);
} catch (const std::runtime_error& e) {
double massSum = 0.0;
for (const auto& [_, entry] : m_compositions) {
massSum += entry.mass_fraction;
massSum += entry.mass_fraction();
}
LOG_ERROR(m_logger, "Composition is invalid (Total mass {}).", massSum);
m_finalized = false;
return false;
}
for (const auto& [_, entry] : m_compositions) {
m_specificNumberDensity += entry.rel_abundance();
}
m_meanParticleMass = 1.0/m_specificNumberDensity;
return true;
}
CompositionEntry Composition::getComposition(const std::string& symbol) const {
bool Composition::finalizeNumberFracMode(bool norm) {
std::vector<double> number_fractions;
number_fractions.reserve(m_compositions.size());
for (const auto& [_, entry] : m_compositions) {
number_fractions.push_back(entry.number_fraction());
}
if (norm) {
double sum = 0.0;
for (const auto& number_fraction : number_fractions) {
sum += number_fraction;
}
for (auto& [symbol, entry] : m_compositions) {
setNumberFraction(symbol, entry.number_fraction() / sum);
}
}
try {
validateComposition(number_fractions);
} catch (const std::runtime_error& e) {
double numberSum = 0.0;
for (const auto& [_, entry] : m_compositions) {
numberSum += entry.number_fraction();
}
LOG_ERROR(m_logger, "Composition is invalid (Total number {}).", numberSum);
m_finalized = false;
return false;
}
for (const auto& [_, entry] : m_compositions) {
m_meanParticleMass += entry.rel_abundance();
}
m_specificNumberDensity = 1.0/m_meanParticleMass;
return true;
}
double Composition::getMassFraction(const std::string& symbol) const {
if (!m_finalized) {
LOG_ERROR(m_logger, "Composition has not been finalized.");
throw std::runtime_error("Composition has not been finalized (Consider running .finalize()).");
@@ -149,5 +372,91 @@ CompositionEntry Composition::getComposition(const std::string& symbol) const {
LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
throw std::runtime_error("Symbol is not in the composition.");
}
return m_compositions.at(symbol);
if (m_massFracMode) {
return m_compositions.at(symbol).mass_fraction();
} else {
return m_compositions.at(symbol).mass_fraction(m_meanParticleMass);
}
}
std::unordered_map<std::string, double> Composition::getMassFraction() const {
std::unordered_map<std::string, double> mass_fractions;
for (const auto& [symbol, entry] : m_compositions) {
mass_fractions[symbol] = getMassFraction(symbol);
}
return mass_fractions;
}
double Composition::getNumberFraction(const std::string& symbol) const {
if (!m_finalized) {
LOG_ERROR(m_logger, "Composition has not been finalized.");
throw std::runtime_error("Composition has not been finalized (Consider running .finalize()).");
}
if (m_compositions.count(symbol) == 0) {
LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
throw std::runtime_error("Symbol is not in the composition.");
}
if (!m_massFracMode) {
return m_compositions.at(symbol).number_fraction();
} else {
return m_compositions.at(symbol).number_fraction(m_specificNumberDensity);
}
}
std::unordered_map<std::string, double> Composition::getNumberFraction() const {
std::unordered_map<std::string, double> number_fractions;
for (const auto& [symbol, entry] : m_compositions) {
number_fractions[symbol] = getNumberFraction(symbol);
}
return number_fractions;
}
std::pair<CompositionEntry, GlobalComposition> Composition::getComposition(const std::string& symbol) const {
if (!m_finalized) {
LOG_ERROR(m_logger, "Composition has not been finalized.");
throw std::runtime_error("Composition has not been finalized (Consider running .finalize()).");
}
if (m_compositions.count(symbol) == 0) {
LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
throw std::runtime_error("Symbol is not in the composition.");
}
return {m_compositions.at(symbol), {m_specificNumberDensity, m_meanParticleMass}};
}
std::pair<std::unordered_map<std::string, CompositionEntry>, GlobalComposition> Composition::getComposition() const {
if (!m_finalized) {
LOG_ERROR(m_logger, "Composition has not been finalized.");
throw std::runtime_error("Composition has not been finalized (Consider running .finalize()).");
}
return {m_compositions, {m_specificNumberDensity, m_meanParticleMass}};
}
Composition Composition::subset(const std::vector<std::string>& symbols, std::string method) const {
std::array<std::string, 2> methods = {"norm", "none"};
if (std::find(methods.begin(), methods.end(), method) == methods.end()) {
std::string errorMessage = "Invalid method: " + method + ". Valid methods are 'norm' and 'none'.";
LOG_ERROR(m_logger, "Invalid method: {}. Valid methods are norm and none.", method);
throw std::runtime_error(errorMessage);
}
Composition subsetComposition;
for (const auto& symbol : symbols) {
if (m_compositions.count(symbol) == 0) {
LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
throw std::runtime_error("Symbol is not in the composition.");
} else {
subsetComposition.registerSymbol(symbol);
}
subsetComposition.setMassFraction(symbol, m_compositions.at(symbol).mass_fraction());
}
if (method == "norm") {
bool isNorm = subsetComposition.finalize(true);
if (!isNorm) {
LOG_ERROR(m_logger, "Subset composition is invalid.");
throw std::runtime_error("Subset composition is invalid.");
}
}
return subsetComposition;
}

View File

@@ -1,13 +1,12 @@
#ifndef COMPOSITION_H
#define COMPOSITION_H
#include <iomanip>
#include <iostream>
#include <string>
#include <unordered_map>
#include <set>
#include "quill/LogMacros.h"
#include <utility>
#include "probe.h"
#include "config.h"
@@ -15,12 +14,139 @@
#include "atomicSpecies.h"
namespace composition{
/**
* @brief Represents the global composition of a system. This tends to be used after finalize and is primarily for internal use.
*/
struct GlobalComposition {
double specificNumberDensity; ///< The specific number density of the composition (\sum_{i} X_i m_i. Where X_i is the number fraction of the ith species and m_i is the mass of the ith species).
double meanParticleMass; ///< The mean particle mass of the composition (\sum_{i} \frac{n_i}{m_i}. where n_i is the number fraction of the ith species and m_i is the mass of the ith species).
// Overload the output stream operator for GlobalComposition
friend std::ostream& operator<<(std::ostream& os, const GlobalComposition& comp) {
os << "Global Composition: \n";
os << "\tSpecific Number Density: " << comp.specificNumberDensity << "\n";
os << "\tMean Particle Mass: " << comp.meanParticleMass << "\n";
return os;
}
};
/**
* @brief Represents an entry in the composition with a symbol and mass fraction.
*/
struct CompositionEntry {
std::string symbol; ///< The chemical symbol of the element.
double mass_fraction; ///< The mass fraction of the element.
std::string m_symbol; ///< The chemical symbol of the species.
chemSpecies::Species m_isotope; ///< The isotope of the species.
bool m_massFracMode = true; ///< The mode of the composition entry. True if mass fraction, false if number fraction.
double m_massFraction = 0.0; ///< The mass fraction of the species.
double m_numberFraction = 0.0; ///< The number fraction of the species.
double m_relAbundance = 0.0; ///< The relative abundance of the species for converting between mass and number fractions.
bool m_initialized = false; ///< True if the composition entry has been initialized.
/**
* @brief Default constructor.
*/
CompositionEntry();
/**
* @brief Constructs a CompositionEntry with the given symbol and mode.
* @param symbol The chemical symbol of the species.
* @param massFracMode True if mass fraction mode, false if number fraction mode.
* @example
* @code
* CompositionEntry entry("H", true);
* @endcode
*/
CompositionEntry(const std::string& symbol, bool massFracMode=true);
/**
* @brief Copy constructor.
* @param entry The CompositionEntry to copy.
*/
CompositionEntry(const CompositionEntry& entry);
/**
* @brief Sets the species for the composition entry.
* @param symbol The chemical symbol of the species.
*/
void setSpecies(const std::string& symbol);
/**
* @brief Gets the chemical symbol of the species.
* @return The chemical symbol of the species.
*/
std::string symbol() const;
/**
* @brief Gets the mass fraction of the species.
* @return The mass fraction of the species.
*/
double mass_fraction() const;
/**
* @brief Gets the mass fraction of the species given the mean molar mass.
* @param meanMolarMass The mean molar mass.
* @return The mass fraction of the species.
*/
double mass_fraction(double meanMolarMass) const;
/**
* @brief Gets the number fraction of the species.
* @return The number fraction of the species.
*/
double number_fraction() const;
/**
* @brief Gets the number fraction of the species given the total moles.
* @param totalMoles The total moles.
* @return The number fraction of the species.
*/
double number_fraction(double totalMoles) const;
/**
* @brief Gets the relative abundance of the species.
* @return The relative abundance of the species.
*/
double rel_abundance() const;
/**
* @brief Gets the isotope of the species.
* @return The isotope of the species.
*/
chemSpecies::Species isotope() const;
/**
* @brief Gets the mode of the composition entry.
* @return True if mass fraction mode, false if number fraction mode.
*/
bool getMassFracMode() const;
/**
* @brief Sets the mass fraction of the species.
* @param mass_fraction The mass fraction to set.
*/
void setMassFraction(double mass_fraction);
/**
* @brief Sets the number fraction of the species.
* @param number_fraction The number fraction to set.
*/
void setNumberFraction(double number_fraction);
/**
* @brief Sets the mode to mass fraction mode.
* @param meanMolarMass The mean molar mass.
* @return True if the mode was successfully set, false otherwise.
*/
bool setMassFracMode(double meanMolarMass);
/**
* @brief Sets the mode to number fraction mode.
* @param totalMoles The total moles.
* @return True if the mode was successfully set, false otherwise.
*/
bool setNumberFracMode(double totalMoles);
/**
* @brief Overloaded output stream operator for CompositionEntry.
@@ -29,7 +155,7 @@ namespace composition{
* @return The output stream.
*/
friend std::ostream& operator<<(std::ostream& os, const CompositionEntry& entry) {
os << "<" << entry.symbol << " : " << entry.mass_fraction << ">";
os << "<" << entry.m_symbol << " : m_frac = " << entry.mass_fraction() << ">";
return os;
}
};
@@ -68,10 +194,13 @@ namespace composition{
Probe::LogManager& m_logManager = Probe::LogManager::getInstance();
quill::Logger* m_logger = m_logManager.getLogger("log");
bool m_finalized = false;
bool m_finalized = false; ///< True if the composition is finalized.
double m_specificNumberDensity = 0.0; ///< The specific number density of the composition (\sum_{i} X_i m_i. Where X_i is the number fraction of the ith species and m_i is the mass of the ith species).
double m_meanParticleMass = 0.0; ///< The mean particle mass of the composition (\sum_{i} \frac{n_i}{m_i}. where n_i is the number fraction of the ith species and m_i is the mass of the ith species).
bool m_massFracMode = true; ///< True if mass fraction mode, false if number fraction mode.
std::set<std::string> m_registeredSymbols;
std::unordered_map<std::string, CompositionEntry> m_compositions;
std::set<std::string> m_registeredSymbols; ///< The registered symbols.
std::unordered_map<std::string, CompositionEntry> m_compositions; ///< The compositions.
/**
* @brief Checks if the given symbol is valid.
@@ -86,14 +215,28 @@ namespace composition{
* @param mass_fractions The mass fractions to check.
* @return True if the mass fractions are valid, false otherwise.
*/
bool isValidComposition(const std::vector<double>& mass_fractions) const;
bool isValidComposition(const std::vector<double>& fractions) const;
/**
* @brief Validates the given mass fractions.
* @param mass_fractions The mass fractions to validate.
* @throws std::invalid_argument if the mass fractions are invalid.
*/
void validateComposition(const std::vector<double>& mass_fractions) const;
void validateComposition(const std::vector<double>& fractions) const;
/**
* @brief Finalizes the composition in mass fraction mode.
* @param norm If true, the composition will be normalized to sum to 1.
* @return True if the composition is successfully finalized, false otherwise.
*/
bool finalizeMassFracMode(bool norm);
/**
* @brief Finalizes the composition in number fraction mode.
* @param norm If true, the composition will be normalized to sum to 1.
* @return True if the composition is successfully finalized, false otherwise.
*/
bool finalizeNumberFracMode(bool norm);
public:
/**
@@ -108,9 +251,10 @@ namespace composition{
/**
* @brief Finalizes the composition.
* @param norm If true, the composition will be normalized to sum to 1 [Default False]
* @return True if the composition is successfully finalized, false otherwise.
*/
bool finalize();
bool finalize(bool norm=false);
/**
* @brief Constructs a Composition with the given symbols.
@@ -127,6 +271,7 @@ namespace composition{
* @brief Constructs a Composition with the given symbols and mass fractions.
* @param symbols The symbols to initialize the composition with.
* @param mass_fractions The mass fractions corresponding to the symbols.
* @param massFracMode True if mass fraction mode, false if number fraction mode.
* @example
* @code
* std::vector<std::string> symbols = {"H", "O"};
@@ -134,18 +279,32 @@ namespace composition{
* Composition comp(symbols, mass_fractions);
* @endcode
*/
Composition(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions);
Composition(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions, bool massFracMode=true);
/**
* @brief Registers a new symbol.
* @param symbol The symbol to register.
* @param massFracMode True if mass fraction mode, false if number fraction mode.
* @example
* @code
* Composition comp;
* comp.registerSymbol("H");
* @endcode
*/
void registerSymbol(const std::string& symbol);
void registerSymbol(const std::string& symbol, bool massFracMode=true);
/**
* @brief Registers multiple new symbols.
* @param symbols The symbols to register.
* @param massFracMode True if mass fraction mode, false if number fraction mode.
* @example
* @code
* std::vector<std::string> symbols = {"H", "O"};
* Composition comp;
* comp.registerSymbol(symbols);
* @endcode
*/
void registerSymbol(const std::vector<std::string>& symbols, bool massFracMode=true);
/**
* @brief Gets the registered symbols.
@@ -154,21 +313,21 @@ namespace composition{
std::set<std::string> getRegisteredSymbols() const;
/**
* @brief Sets the composition for a given symbol.
* @param symbol The symbol to set the composition for.
* @brief Sets the mass fraction for a given symbol.
* @param symbol The symbol to set the mass fraction for.
* @param mass_fraction The mass fraction to set.
* @return The mass fraction that was set.
* @example
* @code
* Composition comp;
* comp.setComposition("H", 0.1);
* comp.setMassFraction("H", 0.1);
* @endcode
*/
double setComposition(const std::string& symbol, const double& mass_fraction);
double setMassFraction(const std::string& symbol, const double& mass_fraction);
/**
* @brief Sets the composition for multiple symbols.
* @param symbols The symbols to set the composition for.
* @brief Sets the mass fraction for multiple symbols.
* @param symbols The symbols to set the mass fraction for.
* @param mass_fractions The mass fractions corresponding to the symbols.
* @return A vector of mass fractions that were set.
* @example
@@ -176,23 +335,73 @@ namespace composition{
* std::vector<std::string> symbols = {"H", "O"};
* std::vector<double> mass_fractions = {0.1, 0.9};
* Composition comp;
* comp.setComposition(symbols, mass_fractions);
* comp.setMassFraction(symbols, mass_fractions);
* @endcode
*/
std::vector<double> setComposition(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions);
std::vector<double> setMassFraction(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions);
/**
* @brief Gets the compositions.
* @return An unordered map of compositions.
* @brief Sets the number fraction for a given symbol.
* @param symbol The symbol to set the number fraction for.
* @param number_fraction The number fraction to set.
* @return The number fraction that was set.
*/
std::unordered_map<std::string, CompositionEntry> getCompositions() const;
double setNumberFraction(const std::string& symbol, const double& number_fraction);
/**
* @brief Gets the composition for a given symbol.
* @brief Sets the number fraction for multiple symbols.
* @param symbols The symbols to set the number fraction for.
* @param number_fractions The number fractions corresponding to the symbols.
* @return A vector of number fractions that were set.
*/
std::vector<double> setNumberFraction(const std::vector<std::string>& symbols, const std::vector<double>& number_fractions);
/**
* @brief Gets the mass fractions of all compositions.
* @return An unordered map of compositions with their mass fractions.
*/
std::unordered_map<std::string, double> getMassFraction() const;
/**
* @brief Gets the mass fraction for a given symbol.
* @param symbol The symbol to get the mass fraction for.
* @return The mass fraction for the given symbol.
*/
double getMassFraction(const std::string& symbol) const;
/**
* @brief Gets the number fraction for a given symbol.
* @param symbol The symbol to get the number fraction for.
* @return The number fraction for the given symbol.
*/
double getNumberFraction(const std::string& symbol) const;
/**
* @brief Gets the number fractions of all compositions.
* @return An unordered map of compositions with their number fractions.
*/
std::unordered_map<std::string, double> getNumberFraction() const;
/**
* @brief Gets the composition entry and global composition for a given symbol.
* @param symbol The symbol to get the composition for.
* @return The CompositionEntry for the given symbol.
* @return A pair containing the CompositionEntry and GlobalComposition for the given symbol.
*/
CompositionEntry getComposition(const std::string& symbol) const;
std::pair<CompositionEntry, GlobalComposition> getComposition(const std::string& symbol) const;
/**
* @brief Gets all composition entries and the global composition.
* @return A pair containing an unordered map of CompositionEntries and the GlobalComposition.
*/
std::pair<std::unordered_map<std::string, CompositionEntry>, GlobalComposition> getComposition() const;
/**
* @brief Gets a subset of the composition.
* @param symbols The symbols to include in the subset.
* @param method The method to use for the subset (default is "norm").
* @return A Composition object containing the subset.
*/
Composition subset(const std::vector<std::string>& symbols, std::string method="norm") const;
/**
* @brief Overloaded output stream operator for Composition.

View File

@@ -49,23 +49,93 @@ TEST_F(compositionTest, setGetComposition) {
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
EXPECT_DOUBLE_EQ(comp.setComposition("H-1", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setComposition("He-4", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setComposition("H-1", 0.6), 0.5);
EXPECT_DOUBLE_EQ(comp.setComposition("He-4", 0.4), 0.5);
EXPECT_DOUBLE_EQ(comp.setMassFraction("H-1", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setMassFraction("He-4", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setMassFraction("H-1", 0.6), 0.5);
EXPECT_DOUBLE_EQ(comp.setMassFraction("He-4", 0.4), 0.5);
EXPECT_NO_THROW(comp.finalize());
std::unordered_map<std::string, composition::CompositionEntry> compositions = comp.getCompositions();
EXPECT_DOUBLE_EQ(compositions["H-1"].mass_fraction, 0.6);
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.6);
EXPECT_THROW(comp.setComposition("He-3", 0.3), std::runtime_error);
EXPECT_THROW(comp.setMassFraction("He-3", 0.3), std::runtime_error);
EXPECT_NO_THROW(comp.setComposition({"H-1", "He-4"}, {0.5, 0.5}));
EXPECT_NO_THROW(comp.setMassFraction({"H-1", "He-4"}, {0.5, 0.5}));
EXPECT_THROW(comp.getComposition("H-1"), std::runtime_error);
EXPECT_TRUE(comp.finalize());
EXPECT_DOUBLE_EQ(comp.getComposition("H-1").mass_fraction, 0.5);
EXPECT_DOUBLE_EQ(comp.getComposition("H-1").first.mass_fraction(), 0.5);
EXPECT_NO_THROW(comp.setComposition({"H-1", "He-4"}, {0.6, 0.6}));
EXPECT_NO_THROW(comp.setMassFraction({"H-1", "He-4"}, {0.6, 0.6}));
EXPECT_FALSE(comp.finalize());
EXPECT_THROW(comp.getComposition("H-1"), std::runtime_error);
}
TEST_F(compositionTest, setGetNumberFraction) {
Config::getInstance().loadConfig(EXAMPLE_FILENAME);
composition::Composition comp;
comp.registerSymbol("H-1", false);
comp.registerSymbol("He-4", false);
EXPECT_DOUBLE_EQ(comp.setNumberFraction("H-1", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setNumberFraction("He-4", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setNumberFraction("H-1", 0.6), 0.5);
EXPECT_DOUBLE_EQ(comp.setNumberFraction("He-4", 0.4), 0.5);
EXPECT_NO_THROW(comp.finalize());
EXPECT_DOUBLE_EQ(comp.getNumberFraction("H-1"), 0.6);
EXPECT_THROW(comp.setNumberFraction("He-3", 0.3), std::runtime_error);
}
TEST_F(compositionTest, subset) {
Config::getInstance().loadConfig(EXAMPLE_FILENAME);
composition::Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6);
comp.setMassFraction("He-4", 0.4);
EXPECT_NO_THROW(comp.finalize());
std::vector<std::string> symbols = {"H-1"};
composition::Composition subsetComp = comp.subset(symbols, "norm");
EXPECT_TRUE(subsetComp.finalize());
EXPECT_DOUBLE_EQ(subsetComp.getMassFraction("H-1"), 1.0);
}
TEST_F(compositionTest, finalizeWithNormalization) {
Config::getInstance().loadConfig(EXAMPLE_FILENAME);
composition::Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.3);
comp.setMassFraction("He-4", 0.3);
EXPECT_TRUE(comp.finalize(true));
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.5);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.5);
}
TEST_F(compositionTest, finalizeWithoutNormalization) {
Config::getInstance().loadConfig(EXAMPLE_FILENAME);
composition::Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.5);
comp.setMassFraction("He-4", 0.5);
EXPECT_TRUE(comp.finalize(false));
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.5);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.5);
}
TEST_F(compositionTest, getComposition) {
Config::getInstance().loadConfig(EXAMPLE_FILENAME);
composition::Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6);
comp.setMassFraction("He-4", 0.4);
EXPECT_NO_THROW(comp.finalize());
auto compositionEntry = comp.getComposition("H-1");
EXPECT_DOUBLE_EQ(compositionEntry.first.mass_fraction(), 0.6);
EXPECT_DOUBLE_EQ(compositionEntry.second.meanParticleMass, 1.4382769310381101);
EXPECT_DOUBLE_EQ(compositionEntry.second.specificNumberDensity, 1.0/1.4382769310381101);
}