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); }