diff --git a/src/composition/meson.build b/src/composition/meson.build index 456d718..8b297b4 100644 --- a/src/composition/meson.build +++ b/src/composition/meson.build @@ -11,6 +11,7 @@ dependencies = [ probe_dep, config_dep, quill_dep, + species_weight_dep ] # Define the libcomposition library so it can be linked against by other parts of the build system diff --git a/src/composition/private/composition.cpp b/src/composition/private/composition.cpp new file mode 100644 index 0000000..415bbb6 --- /dev/null +++ b/src/composition/private/composition.cpp @@ -0,0 +1,153 @@ +#include "composition.h" +#include "quill/LogMacros.h" + +#include + +#include "atomicSpecies.h" + +using namespace composition; + +Composition::Composition(const std::vector& symbols) { + for (const auto& symbol : symbols) { + registerSymbol(symbol); + } +} + +void Composition::validateComposition(const std::vector& 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& 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; + } + + return true; +} + +Composition::Composition(const std::vector& symbols, const std::vector& 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(); + + for (const auto &symbol : symbols) { + registerSymbol(symbol); + } + + for (size_t i = 0; i < symbols.size(); ++i) { + setComposition(symbols[i], mass_fractions[i]); + } +} + +void Composition::registerSymbol(const std::string& symbol) { + if (!isValidSymbol(symbol)) { + LOG_ERROR(m_logger, "Invalid symbol: {}", symbol); + throw std::runtime_error("Invalid symbol."); + } + + if (m_registeredSymbols.find(symbol) != m_registeredSymbols.end()) { + LOG_WARNING(m_logger, "Symbol {} is already registered.", symbol); + return; + } + + m_registeredSymbols.insert(symbol); + LOG_INFO(m_logger, "Registered symbol: {}", symbol); +} + +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; + 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 (mass_fraction < 0.0 || mass_fraction > 1.0) { + LOG_ERROR(m_logger, "Mass fraction must be between 0 and 1."); + 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}; + + + return old_mass_fraction; +} + +std::vector Composition::setComposition(const std::vector& symbols, const std::vector& mass_fractions) { + m_finalized = false; + 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."); + } + + std::vector 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])); + } + return old_mass_fractions; +} + +std::set Composition::getRegisteredSymbols() const { + return m_registeredSymbols; +} + +std::unordered_map 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())."); + } + return m_compositions; +} + +bool Composition::finalize() { + m_finalized = true; + std::vector mass_fractions; + mass_fractions.reserve(m_compositions.size()); + for (const auto& [_, entry] : m_compositions) { + mass_fractions.push_back(entry.mass_fraction); + } + try { + validateComposition(mass_fractions); + } catch (const std::runtime_error& e) { + double massSum = 0.0; + for (const auto& [_, entry] : m_compositions) { + massSum += entry.mass_fraction; + } + LOG_ERROR(m_logger, "Composition is invalid (Total mass {}).", massSum); + m_finalized = false; + return false; + } + return true; +} + +CompositionEntry 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); +} \ No newline at end of file diff --git a/src/composition/private/composition.h b/src/composition/private/composition.h deleted file mode 100644 index b8dc7f3..0000000 --- a/src/composition/private/composition.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef COMPOSITION_H -#define COMPOSITION_H - -#include -#include -#include -#include -#include - -#include "quill/LogMacros.h - -#include "probe.h" -#include "config.h" - -struct CompositionEntry { - std::string symbol; - std::string mass_fraction; - - friend std::ostream& operator<<(std::ostream& os, const CompositionEntry& entry) { - os << std::setw(5) << "<" << entry.symbol << " : " << entry.mass_fraction << ">"; - return os; - } -}; - -class Composition { -private: - Config& m_config = Config::getInstance(); - Probe::LogManager& m_logManager = Probe::LogManager::getInstance(); - quill::Logger* m_logger = logManager.getLogger('log'); - - std::vector m_registeredSymbols; - std::unordered_map m_compositions; - -} - -#endif // COMPOSITION_H \ No newline at end of file diff --git a/src/composition/public/composition.h b/src/composition/public/composition.h new file mode 100644 index 0000000..7546878 --- /dev/null +++ b/src/composition/public/composition.h @@ -0,0 +1,214 @@ +#ifndef COMPOSITION_H +#define COMPOSITION_H + +#include +#include +#include +#include +#include + +#include "quill/LogMacros.h" + +#include "probe.h" +#include "config.h" + +#include "atomicSpecies.h" + +namespace composition{ + /** + * @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. + + /** + * @brief Overloaded output stream operator for CompositionEntry. + * @param os The output stream. + * @param entry The CompositionEntry to output. + * @return The output stream. + */ + friend std::ostream& operator<<(std::ostream& os, const CompositionEntry& entry) { + os << "<" << entry.symbol << " : " << entry.mass_fraction << ">"; + return os; + } + }; + + /** + * @brief Manages the composition of elements. + * @details The composition is a collection of elements with their respective mass fractions. + * The general purpose of this class is to provide a standardized interface for managing the composition of + * any part of 4DSSE. There are a few rules when using this class. + * - Only species in the atomicSpecies.h database can be used. There are 1000s (All species from AME2020) in there so it should not be a problem. + * - Before a mass fraction can be set with a particular instance of Composition, the symbol must be registered. (i.e. register He-3 before setting its mass fraction) + * - Before any composition information can be retrived (e.g. getComposition), the composition must be finalized (call to .finalize()). This checks if the total mass fraction sums to approximatly 1 (within 1 part in 10^8) + * - Any changes made to the composition after finalization will "unfinalize" the composition. This means that the composition must be finalized again before any information can be retrived. + * - The mass fraction of any individual species must be no more than 1 and no less than 0. + * - The only exception to the finalize rule is if the compositon was constructed with symbols and mass fractions at instantiation time. In this case, the composition is automatically finalized. + * however, this means that the composition passed to the constructor must be valid. + * + * @example Constructing a finalized composition with symbols and mass fractions: + * @code + * std::vector symbols = {"H", "O"}; + * std::vector mass_fractions = {0.1, 0.9}; + * Composition comp(symbols, mass_fractions); + * @endcode + * @example Constructing a composition with symbols and finalizing it later: + * @code + * std::vector symbols = {"H", "O"}; + * Composition comp(symbols); + * comp.setComposition("H", 0.1); + * comp.setComposition("O", 0.9); + * comp.finalize(); + * @endcode + */ + class Composition { + private: + Config& m_config = Config::getInstance(); + Probe::LogManager& m_logManager = Probe::LogManager::getInstance(); + quill::Logger* m_logger = m_logManager.getLogger("log"); + + bool m_finalized = false; + + std::set m_registeredSymbols; + std::unordered_map m_compositions; + + /** + * @brief Checks if the given symbol is valid. + * @details A symbol is valid if it is in the atomic species database (species in atomicSpecies.h). These include all the isotopes from AME2020. + * @param symbol The symbol to check. + * @return True if the symbol is valid, false otherwise. + */ + bool isValidSymbol(const std::string& symbol) const; + + /** + * @brief Checks if the given mass fractions are valid. + * @param mass_fractions The mass fractions to check. + * @return True if the mass fractions are valid, false otherwise. + */ + bool isValidComposition(const std::vector& mass_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& mass_fractions) const; + + public: + /** + * @brief Default constructor. + */ + Composition() = default; + + /** + * @brief Default destructor. + */ + ~Composition() = default; + + /** + * @brief Finalizes the composition. + * @return True if the composition is successfully finalized, false otherwise. + */ + bool finalize(); + + /** + * @brief Constructs a Composition with the given symbols. + * @param symbols The symbols to initialize the composition with. + * @example + * @code + * std::vector symbols = {"H", "O"}; + * Composition comp(symbols); + * @endcode + */ + Composition(const std::vector& symbols); + + /** + * @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. + * @example + * @code + * std::vector symbols = {"H", "O"}; + * std::vector mass_fractions = {0.1, 0.9}; + * Composition comp(symbols, mass_fractions); + * @endcode + */ + Composition(const std::vector& symbols, const std::vector& mass_fractions); + + /** + * @brief Registers a new symbol. + * @param symbol The symbol to register. + * @example + * @code + * Composition comp; + * comp.registerSymbol("H"); + * @endcode + */ + void registerSymbol(const std::string& symbol); + + /** + * @brief Gets the registered symbols. + * @return A set of registered symbols. + */ + std::set getRegisteredSymbols() const; + + /** + * @brief Sets the composition for a given symbol. + * @param symbol The symbol to set the composition 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); + * @endcode + */ + double setComposition(const std::string& symbol, const double& mass_fraction); + + /** + * @brief Sets the composition for multiple symbols. + * @param symbols The symbols to set the composition for. + * @param mass_fractions The mass fractions corresponding to the symbols. + * @return A vector of mass fractions that were set. + * @example + * @code + * std::vector symbols = {"H", "O"}; + * std::vector mass_fractions = {0.1, 0.9}; + * Composition comp; + * comp.setComposition(symbols, mass_fractions); + * @endcode + */ + std::vector setComposition(const std::vector& symbols, const std::vector& mass_fractions); + + /** + * @brief Gets the compositions. + * @return An unordered map of compositions. + */ + std::unordered_map getCompositions() const; + + /** + * @brief Gets the composition for a given symbol. + * @param symbol The symbol to get the composition for. + * @return The CompositionEntry for the given symbol. + */ + CompositionEntry getComposition(const std::string& symbol) const; + + /** + * @brief Overloaded output stream operator for Composition. + * @param os The output stream. + * @param composition The Composition to output. + * @return The output stream. + */ + friend std::ostream& operator<<(std::ostream& os, const Composition& composition) { + os << "Composition: \n"; + for (const auto& [symbol, entry] : composition.m_compositions) { + os << entry << "\n"; + } + return os; + } + + }; +}; + +#endif // COMPOSITION_H \ No newline at end of file