From 539c3b567e5bd83ad3fc443ee246a09b65f240d2 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Tue, 16 Sep 2025 11:23:01 -0400 Subject: [PATCH] feat(composition): added uniform tools to get vector representation of mass fraction, number fraction, and molar abundance this is useful for external tools that need to ensure uniformity in which species are at what index in a vector representation --- .../include/fourdst/composition/composition.h | 66 ++++++- src/composition/lib/composition.cpp | 165 ++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) diff --git a/src/composition/include/fourdst/composition/composition.h b/src/composition/include/fourdst/composition/composition.h index acb55fc..b85437d 100644 --- a/src/composition/include/fourdst/composition/composition.h +++ b/src/composition/include/fourdst/composition/composition.h @@ -734,6 +734,68 @@ namespace fourdst::composition { */ [[nodiscard]] CanonicalComposition getCanonicalComposition(bool harsh=false) const; + /** + * @brief Get a uniform vector representation of the mass fraction stored in the composition object sorted such that the lightest species is at index 0 and the heaviest is at the last index. + * @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained + * @return the vector of mass fractions sorted by species mass (lightest to heaviest). + * @pre The composition must be finalized. + * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. + */ + [[nodiscard]] std::vector getMassFractionVector() const; + + /** + * @brief Get a uniform vector representation of the number fractions stored in the composition object sorted such that the lightest species is at index 0 and the heaviest is at the last index. + * @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained + * @return the vector of number fractions sorted by species mass (lightest to heaviest). + * @pre The composition must be finalized. + * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. + */ + [[nodiscard]] std::vector getNumberFractionVector() const; + + /** + * @brief Get a uniform vector representation of the molar abundances stored in the composition object sorted such that the lightest species is at index 0 and the heaviest is at the last index. + * @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained + * @return the vector of molar abundances sorted by species mass (lightest to heaviest). + * @pre The composition must be finalized. + * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. + */ + [[nodiscard]] std::vector getMolarAbundanceVector() const; + + /** + * @brief get the index in the sorted vector representation for a given symbol + * @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained + * @pre The composition must be finalized. + * @pre symbol must be registered in the composition + * @param symbol the symbol to look up the index for. Note that this is the index species data will be at if you were to call getMolarAbundanceVector(), getMassFractionVector(), or getNumberFractionVector() + * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. + * @throws exceptions::UnregisteredSymbolError if the symbol is not registered in the composition + * @return The index of the symbol in the sorted vector representation. + */ + [[nodiscard]] size_t getSpeciesIndex(const std::string& symbol) const; + + /** + * @brief get the index in the sorted vector representation for a given symbol + * @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained + * @pre The composition must be finalized. + * @pre symbol must be registered in the composition + * @param species the species to look up the index for. Note that this is the index species data will be at if you were to call getMolarAbundanceVector(), getMassFractionVector(), or getNumberFractionVector() + * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. + * @throws exceptions::UnregisteredSymbolError if the symbol is not registered in the composition + * @return The index of the symbol in the sorted vector representation. + */ + [[nodiscard]] size_t getSpeciesIndex(const atomic::Species& species) const; + + /** + * @brief Get the species at a given index in the sorted vector representation. + * @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained + * @pre The composition must be finalized. + * @param index The index in the sorted vector representation for which to return the species. Must be in [0, N-1] where N is the number of registered species. + * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. + * @throws std::out_of_range if the index is out of range. + * @return The species at the given index in the sorted vector representation. + */ + [[nodiscard]] atomic::Species getSpeciesAtIndex(size_t index) const; + /** * @brief Overloaded output stream operator for Composition. * @param os The output stream. @@ -764,7 +826,7 @@ namespace fourdst::composition { * @brief Returns a const iterator to the beginning of the composition map. * @return A const iterator to the beginning. */ - auto begin() const { + [[nodiscard]] auto begin() const { return m_compositions.cbegin(); } @@ -780,7 +842,7 @@ namespace fourdst::composition { * @brief Returns a const iterator to the end of the composition map. * @return A const iterator to the end. */ - auto end() const { + [[nodiscard]] auto end() const { return m_compositions.cend(); } diff --git a/src/composition/lib/composition.cpp b/src/composition/lib/composition.cpp index d39c739..4e68031 100644 --- a/src/composition/lib/composition.cpp +++ b/src/composition/lib/composition.cpp @@ -25,6 +25,8 @@ #include #include #include +#include + #include @@ -33,6 +35,29 @@ #include "fourdst/composition/composition.h" #include "fourdst/composition/exceptions/exceptions_composition.h" +namespace { + template + std::vector sortVectorBy(std::vector toSort, const std::vector& by) { + std::vector indices(by.size()); + for (size_t i = 0; i < indices.size(); i++) { + indices[i] = i; + } + + std::ranges::sort(indices, [&](size_t a, size_t b) { + return by[a] < by[b]; + }); + + std::vector sorted; + sorted.reserve(indices.size()); + + for (const auto idx: indices) { + sorted.push_back(toSort[idx]); + } + + return sorted; + } +} + namespace fourdst::composition { CompositionEntry::CompositionEntry() : @@ -734,6 +759,146 @@ namespace fourdst::composition { return canonicalComposition; } + std::vector Composition::getMassFractionVector() const { + if (!m_finalized) { + LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize()."); + throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize()."); + } + + std::vector massFractionVector; + std::vector speciesMass; + + massFractionVector.reserve(m_compositions.size()); + speciesMass.reserve(m_compositions.size()); + + for (const auto &entry: m_compositions | std::views::values) { + massFractionVector.push_back(entry.mass_fraction()); + speciesMass.push_back(entry.isotope().mass()); + } + + return sortVectorBy(massFractionVector, speciesMass); + + } + + std::vector Composition::getNumberFractionVector() const { + if (!m_finalized) { + LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize()."); + throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize()."); + } + + std::vector numberFractionVector; + std::vector speciesMass; + + numberFractionVector.reserve(m_compositions.size()); + speciesMass.reserve(m_compositions.size()); + + for (const auto &entry: m_compositions | std::views::values) { + numberFractionVector.push_back(entry.number_fraction()); + speciesMass.push_back(entry.isotope().mass()); + } + + return sortVectorBy(numberFractionVector, speciesMass); + } + + std::vector Composition::getMolarAbundanceVector() const { + if (!m_finalized) { + LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize()."); + throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize()."); + } + + std::vector molarAbundanceVector; + std::vector speciesMass; + + molarAbundanceVector.reserve(m_compositions.size()); + speciesMass.reserve(m_compositions.size()); + + for (const auto &entry: m_compositions | std::views::values) { + molarAbundanceVector.push_back(getMolarAbundance(entry.isotope())); + speciesMass.push_back(entry.isotope().mass()); + } + + return sortVectorBy(molarAbundanceVector, speciesMass); + } + + size_t Composition::getSpeciesIndex(const std::string &symbol) const { + if (!m_finalized) { + LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize()."); + throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize()."); + } + if (!m_compositions.contains(symbol)) { + LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol); + throw exceptions::UnregisteredSymbolError("Symbol " + symbol + " is not in the composition."); + } + + std::vector symbols; + std::vector speciesMass; + + symbols.reserve(m_compositions.size()); + speciesMass.reserve(m_compositions.size()); + + for (const auto &entry: m_compositions | std::views::values) { + symbols.emplace_back(entry.isotope().name()); + speciesMass.push_back(entry.isotope().mass()); + } + + std::vector sortedSymbols = sortVectorBy(symbols, speciesMass); + return std::distance(sortedSymbols.begin(), std::ranges::find(sortedSymbols, symbol)); + } + + size_t Composition::getSpeciesIndex(const atomic::Species &species) const { + if (!m_finalized) { + LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize()."); + throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize()."); + } + if (!m_compositions.contains(static_cast(species.name()))) { + LOG_ERROR(m_logger, "Species {} is not in the composition.", species.name()); + throw exceptions::UnregisteredSymbolError("Species " + std::string(species.name()) + " is not in the composition."); + } + + std::vector speciesVector; + std::vector speciesMass; + + speciesVector.reserve(m_compositions.size()); + speciesMass.reserve(m_compositions.size()); + + for (const auto &entry: m_compositions | std::views::values) { + speciesVector.emplace_back(entry.isotope()); + speciesMass.push_back(entry.isotope().mass()); + } + + std::vector sortedSpecies = sortVectorBy(speciesVector, speciesMass); + return std::distance(sortedSpecies.begin(), std::ranges::find(sortedSpecies, species)); + } + + atomic::Species Composition::getSpeciesAtIndex(size_t index) const { + if (!m_finalized) { + LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize()."); + throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize()."); + } + if (index >= m_compositions.size()) { + LOG_ERROR(m_logger, "Index {} is out of bounds for composition of size {}.", index, m_compositions.size()); + throw std::out_of_range("Index " + std::to_string(index) + " is out of bounds for composition of size " + std::to_string(m_compositions.size()) + "."); + } + if (index < 0) { + LOG_ERROR(m_logger, "Index {} is negative. Cannot get species at negative index.", index); + throw std::out_of_range("Index " + std::to_string(index) + " is negative. Cannot get species at negative index."); + } + + std::vector speciesVector; + std::vector speciesMass; + + speciesVector.reserve(m_compositions.size()); + speciesMass.reserve(m_compositions.size()); + + for (const auto &entry: m_compositions | std::views::values) { + speciesVector.emplace_back(entry.isotope()); + speciesMass.push_back(entry.isotope().mass()); + } + + std::vector sortedSymbols = sortVectorBy(speciesVector, speciesMass); + return sortedSymbols.at(index); + } + bool Composition::hasSymbol(const std::string& symbol) const { return m_compositions.contains(symbol); }