diff --git a/benchmarks/ConstructionAndIteration/benchmark_composition_construction_and_iteration.cpp b/benchmarks/ConstructionAndIteration/benchmark_composition_construction_and_iteration.cpp new file mode 100644 index 0000000..7cc559b --- /dev/null +++ b/benchmarks/ConstructionAndIteration/benchmark_composition_construction_and_iteration.cpp @@ -0,0 +1,123 @@ +#include "benchmark_utils.h" + +#include "fourdst/composition/composition.h" +#include "fourdst/atomic/species.h" + +#include +#include +#include + +std::chrono::duration benchmark_construction(const size_t iterations, const size_t nSpecies) { + using namespace fourdst::composition; + using namespace fourdst::atomic; + + // Setup random machine to get random double between 0 and 1 for molar abundances + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0.0, 1.0); + + std::vector species_to_register; + std::vector molarAbundances; + + size_t count = 0; + for (const auto& sp : species | std::views::values) { + if (count >= nSpecies) { + break; + } + species_to_register.push_back(sp); + molarAbundances.push_back(dis(gen)); + count++; + } + + const auto duration = fdst_benchmark_function([&]() { + for (size_t i = 0; i < iterations; ++i) { + fourdst::composition::Composition comp(species_to_register, molarAbundances); + } + }); + + return duration / static_cast(iterations); +} + +std::chrono::duration benchmark_access(const size_t iterations, const size_t nSpecies) { + using namespace fourdst::composition; + using namespace fourdst::atomic; + + // Setup random machine to get random double between 0 and 1 for molar abundances + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0.0, 1.0); + + std::vector species_to_register; + std::vector molarAbundances; + + size_t count = 0; + for (const auto& sp : species | std::views::values) { + if (count >= nSpecies) { + break; + } + species_to_register.push_back(sp); + molarAbundances.push_back(dis(gen)); + count++; + } + + const Composition comp(species_to_register, molarAbundances); + + std::uniform_int_distribution<>(0, nSpecies - 1); + std::vector random_lookup_species; + for (size_t i = 0; i < iterations; ++i) { + random_lookup_species.push_back(species_to_register[static_cast(dis(gen))]); + } + + const auto duration = fdst_benchmark_function([&]() { + for (size_t i = 0; i < iterations; ++i) { + volatile double y = comp.getMolarAbundance(random_lookup_species[i]); + do_not_optimize(y); + } + }); + + + return duration / static_cast(iterations); +} + +int main () { + constexpr size_t nIterations = 1000; + constexpr size_t nSpecies = 100; + + std::vector durations; + durations.resize(nIterations); + + for (size_t i = 0; i < nIterations; ++i) { + std::print("Iteration {}/{}\r", i + 1, nIterations); + auto duration = benchmark_construction(10, nSpecies); + durations[i] = duration.count(); + } + std::println(""); + + std::println("Average time to construct composition over {} iterations: {} ns", nIterations, + std::accumulate(durations.begin(), durations.end(), 0.0) / nIterations); + std::println("Max time to construct composition over {} iterations: {} ns", nIterations, + *std::ranges::max_element(durations)); + std::println("Min time to construct composition over {} iterations: {} ns", nIterations, + *std::ranges::min_element(durations)); + + + plot_ascii_histogram(durations, "Composition Construction Time Histogram"); + + + durations.clear(); + durations.resize(nIterations); + for (size_t i = 0; i < nIterations; ++i) { + std::print("Iteration {}/{}\r", i + 1, nIterations); + auto duration = benchmark_access(1000, nSpecies); + durations[i] = duration.count(); + } + std::println(""); + std::println("Average time to access composition over {} iterations: {} ns", nIterations, + std::accumulate(durations.begin(), durations.end(), 0.0) / nIterations); + std::println("Max time to access composition over {} iterations: {} ns", nIterations, + *std::ranges::max_element(durations)); + std::println("Min time to access composition over {} iterations: {} ns", nIterations, + *std::ranges::min_element(durations)); + + plot_ascii_histogram(durations, "Composition Access Time Histogram"); +} \ No newline at end of file diff --git a/benchmarks/ConstructionAndIteration/meson.build b/benchmarks/ConstructionAndIteration/meson.build new file mode 100644 index 0000000..538d791 --- /dev/null +++ b/benchmarks/ConstructionAndIteration/meson.build @@ -0,0 +1 @@ +executable('construction_and_iteration_bench', 'benchmark_composition_construction_and_iteration.cpp', dependencies: [composition_dep], include_directories: [benchmark_utils_includes]) \ No newline at end of file diff --git a/benchmarks/hashing/benchmark_composition_hash.cpp b/benchmarks/hashing/benchmark_composition_hash.cpp index 0eeb108..a7e0990 100644 --- a/benchmarks/hashing/benchmark_composition_hash.cpp +++ b/benchmarks/hashing/benchmark_composition_hash.cpp @@ -1,58 +1,16 @@ #include "fourdst/composition/composition.h" #include "fourdst/composition/utils/composition_hash.h" -#include "fourdst/composition/utils.h" #include "fourdst/atomic/atomicSpecies.h" #include "fourdst/atomic/species.h" -#include #include #include -#include #include -#include #include +#include -template -void do_not_optimize(T&& datum) { - asm volatile("" : "+r" (datum)); -} +#include "benchmark_utils.h" -uint32_t calc_num_bins(const std::vector& data) { - // Use Sturges' formula - const size_t n = data.size(); - return static_cast(std::ceil(std::log2(n) + 1)); -} - -std::string plot_ascii_histogram(std::vector data, std::string title) { - // Use std::format - const uint32_t nBins = calc_num_bins(data); - const double minVal = *std::ranges::min_element(data); - const double maxVal = *std::ranges::max_element(data); - - std::string histogram; - histogram += std::format("{:^60}\n", title); - histogram += std::string(60, '=') + "\n"; - std::vector bins(nBins, 0); - const double binWidth = (maxVal - minVal) / nBins; - for (const auto& value : data) { - const uint32_t binIndex = static_cast((value - minVal) / binWidth); - if (binIndex < nBins) { - bins[binIndex]++; - } else { - bins[nBins - 1]++; - } - } - const uint32_t maxBinCount = *std::ranges::max_element(bins); - for (uint32_t i = 0; i < nBins; ++i) { - const double binStart = minVal + i * binWidth; - const double binEnd = binStart + binWidth; - const uint32_t barLength = static_cast(std::round((static_cast(bins[i]) / maxBinCount) * 50.0)); - histogram += std::format("[{:.2e}, {:.2e}): {:>15} | {:}\n", - binStart, binEnd, bins[i], std::string(barLength, '*')); - } - return histogram; - -} std::chrono::duration build_and_hash_compositions(const size_t iter, const size_t nSpecies = 8) { using namespace fourdst::composition; @@ -69,15 +27,14 @@ std::chrono::duration build_and_hash_compositions(const size_ count++; } - const auto start = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < iter; ++i) { - uint64_t hashValue = utils::CompositionHash::hash_exact(comp); - do_not_optimize(hashValue); - } - const auto end = std::chrono::high_resolution_clock::now(); + const auto duration = fdst_benchmark_function([&]() { + for (size_t i = 0; i < iter; ++i) { + uint64_t hashValue = utils::CompositionHash::hash_exact(comp); + do_not_optimize(hashValue); + } + }); - const std::chrono::duration duration = (end - start)/iter; - return duration; + return duration / static_cast(iter); } int main() { diff --git a/benchmarks/hashing/meson.build b/benchmarks/hashing/meson.build index d92b6d5..195a088 100644 --- a/benchmarks/hashing/meson.build +++ b/benchmarks/hashing/meson.build @@ -1 +1 @@ -executable('hashing_bench', 'benchmark_composition_hash.cpp', dependencies: [composition_dep]) \ No newline at end of file +executable('hashing_bench', 'benchmark_composition_hash.cpp', dependencies: [composition_dep], include_directories: [benchmark_utils_includes]) \ No newline at end of file diff --git a/benchmarks/meson.build b/benchmarks/meson.build index 4b32e51..ad7f744 100644 --- a/benchmarks/meson.build +++ b/benchmarks/meson.build @@ -1 +1,4 @@ -subdir('hashing') \ No newline at end of file +benchmark_utils_includes = include_directories('utils') + +subdir('hashing') +subdir('ConstructionAndIteration') \ No newline at end of file diff --git a/benchmarks/utils/benchmark_utils.h b/benchmarks/utils/benchmark_utils.h new file mode 100644 index 0000000..ba72b16 --- /dev/null +++ b/benchmarks/utils/benchmark_utils.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +template +void do_not_optimize(T&& datum) { + asm volatile("" : "+r" (datum)); +} + +inline uint32_t calc_num_bins(const std::vector& data) { + const size_t n = data.size(); + return static_cast(std::ceil(std::log2(n) + 1)); +} + +inline std::string plot_ascii_histogram(std::vector data, std::string title) { + const uint32_t nBins = calc_num_bins(data); + const double minVal = *std::ranges::min_element(data); + const double maxVal = *std::ranges::max_element(data); + + std::string histogram; + histogram += std::format("{:^60}\n", title); + histogram += std::string(60, '=') + "\n"; + std::vector bins(nBins, 0); + const double binWidth = (maxVal - minVal) / nBins; + for (const auto& value : data) { + const uint32_t binIndex = static_cast((value - minVal) / binWidth); + if (binIndex < nBins) { + bins[binIndex]++; + } else { + bins[nBins - 1]++; + } + } + const uint32_t maxBinCount = *std::ranges::max_element(bins); + for (uint32_t i = 0; i < nBins; ++i) { + const double binStart = minVal + i * binWidth; + const double binEnd = binStart + binWidth; + const uint32_t barLength = static_cast(std::round((static_cast(bins[i]) / maxBinCount) * 50.0)); + histogram += std::format("[{:.2e}, {:.2e}): {:>15} | {:}\n", + binStart, binEnd, bins[i], std::string(barLength, '*')); + } + return histogram; + +} + +template +auto fdst_benchmark_function(Func&& func_call) { + auto start = std::chrono::high_resolution_clock::now(); + + // Forward the callable + std::forward(func_call)(); + + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + + do_not_optimize(duration.count()); + + return duration; +} \ No newline at end of file diff --git a/src/composition/include/fourdst/atomic/atomicSpecies.h b/src/composition/include/fourdst/atomic/atomicSpecies.h index b374d44..058db27 100644 --- a/src/composition/include/fourdst/atomic/atomicSpecies.h +++ b/src/composition/include/fourdst/atomic/atomicSpecies.h @@ -1,6 +1,7 @@ #pragma once +#include #include #include #include @@ -275,8 +276,7 @@ namespace fourdst::atomic { friend bool operator==(const Species& lhs, const Species& rhs); friend bool operator!=(const Species& lhs, const Species& rhs); - friend bool operator<(const Species& lhs, const Species& rhs); - friend bool operator>(const Species& lhs, const Species& rhs); + friend std::partial_ordering operator<=>(const Species &lhs, const Species &rhs); }; /** * @brief Equality operator for Species. Compares based on name. @@ -296,25 +296,18 @@ namespace fourdst::atomic { inline bool operator!=(const Species& lhs, const Species& rhs) { return (lhs.m_name != rhs.m_name); } - /** - * @brief Less-than operator for Species. Compares based on atomic mass. - * @param lhs The left-hand side Species. - * @param rhs The right-hand side Species. - * @return `true` if lhs atomic mass is less than rhs atomic mass, `false` otherwise. - */ - inline bool operator<(const Species& lhs, const Species& rhs) { - return (lhs.m_atomicMass < rhs.m_atomicMass); - } - /** - * @brief Greater-than operator for Species. Compares based on atomic mass. - * @param lhs The left-hand side Species. - * @param rhs The right-hand side Species. - * @return `true` if lhs atomic mass is greater than rhs atomic mass, `false` otherwise. - */ - inline bool operator>(const Species& lhs, const Species& rhs) { - return (lhs.m_atomicMass > rhs.m_atomicMass); + + inline std::partial_ordering operator<=>(const Species &lhs, const Species &rhs) { + if (const auto cmp = lhs.m_atomicMass <=> rhs.m_atomicMass; cmp != 0) { + return cmp; + } + + return lhs.m_name <=> rhs.m_name; } + + + /** * @brief Converts a spin-parity string (JPI string) to a double-precision floating-point number representing the spin. * @@ -424,14 +417,15 @@ namespace fourdst::atomic { * } * @endcode */ + template<> struct std::hash { /** - * @brief Computes the hash for a Species object. - * @param s The Species object to hash. - * @return The hash value of the species' name. - */ + * @brief Computes the hash for a Species object. + * @param s The Species object to hash. + * @return The hash value of the species' name. + */ size_t operator()(const fourdst::atomic::Species& s) const noexcept { return std::hash()(s.m_name); } -}; // namespace std +}; diff --git a/src/composition/include/fourdst/composition/composition.h b/src/composition/include/fourdst/composition/composition.h index 1968c2f..2a26aa8 100644 --- a/src/composition/include/fourdst/composition/composition.h +++ b/src/composition/include/fourdst/composition/composition.h @@ -26,6 +26,7 @@ #include #include +#include #include "fourdst/config/config.h" #include "fourdst/logging/logging.h" @@ -96,6 +97,9 @@ namespace fourdst::composition { */ // ReSharper disable once CppClassCanBeFinal class Composition final : public CompositionAbstract { + public: + using iterator = detail::CompositionIterator; + using const_iterator = detail::CompositionIterator; private: /** * @struct CompositionCache @@ -138,6 +142,11 @@ namespace fourdst::composition { !Ye.has_value() && !sortedSpecies.has_value() && !hash.has_value(); } }; + + enum class SpeciesIndexLookupError : uint8_t { + NO_REGISTERED_SPECIES, + SPECIES_NOT_FOUND + }; private: /** * @brief Gets the logger instance for the Composition class. This is static to ensure that all composition @@ -149,11 +158,18 @@ namespace fourdst::composition { return logger; } - std::set m_registeredSpecies; ///< Set of registered species in the composition. - std::map m_molarAbundances; ///< Map of species to their molar abundances. + // std::set m_registeredSpecies; ///< Set of registered species in the composition. + // std::map m_molarAbundances; ///< Map of species to their molar abundances. + + std::vector m_species; + std::vector m_molarAbundances; mutable CompositionCache m_cache; ///< Cache for computed properties to avoid redundant calculations. + private: + [[nodiscard]] std::expected findSpeciesIndex(const atomic::Species &species) const noexcept; + [[nodiscard]] static std::vector symbolVectorToSpeciesVector(const std::vector& symbols); + public: /** * @brief Default constructor. @@ -295,6 +311,7 @@ namespace fourdst::composition { * @return A reference to this Composition. */ Composition& operator=(Composition const& other); + Composition& operator=(const CompositionAbstract& other); /** * @brief Registers a new symbol for inclusion in the composition. @@ -546,7 +563,7 @@ namespace fourdst::composition { * value of this method will only be valid as long as the Composition object is valid (i.e. it cannot * outlive the Composition object it was called on). */ - [[nodiscard]] const std::set &getRegisteredSpecies() const noexcept override; + [[nodiscard]] const std::vector &getRegisteredSpecies() const noexcept override; /** * @brief Gets the mass fractions of all species in the composition. @@ -767,8 +784,8 @@ namespace fourdst::composition { * for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be * seen in order from lightest to heaviest. */ - [[nodiscard]] std::map::iterator begin() override { - return m_molarAbundances.begin(); + [[nodiscard]] iterator begin() override { + return {m_species.begin(), m_molarAbundances.begin()}; } /** @@ -788,8 +805,8 @@ namespace fourdst::composition { * for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be * seen in order from lightest to heaviest. */ - [[nodiscard]] std::map::const_iterator begin() const override { - return m_molarAbundances.cbegin(); + [[nodiscard]] const_iterator begin() const override { + return {m_species.cbegin(), m_molarAbundances.cbegin()}; } /** @@ -809,8 +826,8 @@ namespace fourdst::composition { * for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be * seen in order from lightest to heaviest. */ - [[nodiscard]] std::map::iterator end() override { - return m_molarAbundances.end(); + [[nodiscard]] detail::CompositionIterator end() override { + return {m_species.end(), m_molarAbundances.end()}; } /** @@ -830,8 +847,8 @@ namespace fourdst::composition { * for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be * seen in order from lightest to heaviest. */ - [[nodiscard]] std::map::const_iterator end() const override { - return m_molarAbundances.cend(); + [[nodiscard]] detail::CompositionIterator end() const override { + return {m_species.cend(), m_molarAbundances.cend()}; } [[nodiscard]] std::size_t hash() const override; @@ -841,18 +858,9 @@ namespace fourdst::composition { inline bool operator==(const Composition& a, const Composition& b) noexcept { if (a.size() != b.size()) return false; - // Compare species sets quickly if (a.getRegisteredSpecies() != b.getRegisteredSpecies()) return false; - // Compare all abundances - for (auto itA = a.begin(), itB = b.begin(); - itA != a.end() && itB != b.end(); ++itA, ++itB) { - if (itA->first != itB->first) - return false; - if (itA->second != itB->second) - return false; - } - return true; + return a.hash() == b.hash(); } }; // namespace fourdst::composition diff --git a/src/composition/include/fourdst/composition/composition_abstract.h b/src/composition/include/fourdst/composition/composition_abstract.h index 999561a..ab425d6 100644 --- a/src/composition/include/fourdst/composition/composition_abstract.h +++ b/src/composition/include/fourdst/composition/composition_abstract.h @@ -1,10 +1,10 @@ #pragma once #include "fourdst/atomic/atomicSpecies.h" +#include "fourdst/composition/iterators/composition_abstract_iterator.h" #include #include -#include #include #include #include @@ -35,6 +35,8 @@ namespace fourdst::composition { */ class CompositionAbstract { public: + using iterator = detail::CompositionIterator; + using const_iterator = detail::CompositionIterator; /** * @brief Virtual destructor. */ @@ -66,7 +68,7 @@ namespace fourdst::composition { * @brief Get all registered atomic species in the composition. * @return A set of registered atomic species. */ - [[nodiscard]] virtual const std::set &getRegisteredSpecies() const noexcept = 0; + [[nodiscard]] virtual const std::vector &getRegisteredSpecies() const noexcept = 0; /** * @brief Get the mass fraction for all registered symbols. @@ -175,10 +177,10 @@ namespace fourdst::composition { [[nodiscard]] virtual std::unique_ptr clone() const = 0; - [[nodiscard]] virtual std::map::iterator begin() = 0; - [[nodiscard]] virtual std::map::iterator end() = 0; - [[nodiscard]] virtual std::map::const_iterator begin() const = 0; - [[nodiscard]] virtual std::map::const_iterator end() const = 0; + [[nodiscard]] virtual iterator begin() = 0; + [[nodiscard]] virtual iterator end() = 0; + [[nodiscard]] virtual const_iterator begin() const = 0; + [[nodiscard]] virtual const_iterator end() const = 0; [[nodiscard]] virtual std::size_t hash() const = 0; }; diff --git a/src/composition/include/fourdst/composition/decorators/composition_decorator_abstract.h b/src/composition/include/fourdst/composition/decorators/composition_decorator_abstract.h index 4eca926..8898377 100644 --- a/src/composition/include/fourdst/composition/decorators/composition_decorator_abstract.h +++ b/src/composition/include/fourdst/composition/decorators/composition_decorator_abstract.h @@ -1,6 +1,7 @@ #pragma once #include "fourdst/atomic/atomicSpecies.h" +#include "fourdst/composition/iterators/composition_abstract_iterator.h" #include "fourdst/composition/composition_abstract.h" @@ -19,7 +20,7 @@ namespace fourdst::composition { [[nodiscard]] bool contains(const std::string& symbol) const override { return m_base_composition->contains(symbol); }; [[nodiscard]] size_t size() const noexcept override { return m_base_composition->size(); }; [[nodiscard]] std::set getRegisteredSymbols() const noexcept override { return m_base_composition->getRegisteredSymbols(); }; - [[nodiscard]] const std::set &getRegisteredSpecies() const noexcept override { return m_base_composition->getRegisteredSpecies(); }; + [[nodiscard]] const std::vector &getRegisteredSpecies() const noexcept override { return m_base_composition->getRegisteredSpecies(); }; [[nodiscard]] std::unordered_map getMassFraction() const noexcept override { return m_base_composition->getMassFraction(); }; [[nodiscard]] std::unordered_map getNumberFraction() const noexcept override { return m_base_composition->getNumberFraction(); }; [[nodiscard]] double getMassFraction(const std::string& symbol) const override { return m_base_composition->getMassFraction(symbol); }; @@ -36,13 +37,12 @@ namespace fourdst::composition { [[nodiscard]] size_t getSpeciesIndex(const std::string& symbol) const override { return m_base_composition->getSpeciesIndex(symbol); }; [[nodiscard]] size_t getSpeciesIndex(const atomic::Species& species) const override { return m_base_composition->getSpeciesIndex(species); }; [[nodiscard]] atomic::Species getSpeciesAtIndex(const size_t index) const override { return m_base_composition->getSpeciesAtIndex(index); } - [[nodiscard]] size_t hash() const override { return m_base_composition->hash(); }; - [[nodiscard]] std::map::iterator begin() override { return m_base_composition->begin(); }; - [[nodiscard]] std::map::iterator end() override { return m_base_composition->end(); }; + [[nodiscard]] detail::CompositionIterator begin() override { return m_base_composition->begin(); }; + [[nodiscard]] detail::CompositionIterator end() override { return m_base_composition->end(); }; - [[nodiscard]] std::map::const_iterator begin() const override { return std::as_const(*m_base_composition).begin(); }; - [[nodiscard]] std::map::const_iterator end() const override { return std::as_const(*m_base_composition).end(); }; + [[nodiscard]] detail::CompositionIterator begin() const override { return std::as_const(*m_base_composition).begin(); }; + [[nodiscard]] detail::CompositionIterator end() const override { return std::as_const(*m_base_composition).end(); }; protected: std::unique_ptr m_base_composition; }; diff --git a/src/composition/include/fourdst/composition/decorators/composition_masked.h b/src/composition/include/fourdst/composition/decorators/composition_masked.h index d9254ac..b7e275e 100644 --- a/src/composition/include/fourdst/composition/decorators/composition_masked.h +++ b/src/composition/include/fourdst/composition/decorators/composition_masked.h @@ -1,29 +1,36 @@ #pragma once +#include +#include +#include +#include +#include + +#include "fourdst/composition/composition_abstract.h" #include "fourdst/composition/decorators/composition_decorator_abstract.h" -#include "fourdst/composition/exceptions/exceptions_composition.h" +#include "fourdst/composition/iterators/composition_abstract_iterator.h" #include "fourdst/atomic/atomicSpecies.h" namespace fourdst::composition { class MaskedComposition final : public CompositionDecorator { public: + using iterator = detail::CompositionIterator; + using const_iterator = detail::CompositionIterator; + MaskedComposition( const CompositionAbstract& baseComposition, - const std::set& activeSpecies + const std::vector& activeSpecies ); [[nodiscard]] bool contains(const atomic::Species &species) const noexcept override; - [[nodiscard]] bool contains(const std::string &symbol) const override; - [[nodiscard]] const std::set& getRegisteredSpecies() const noexcept override; - + [[nodiscard]] const std::vector& getRegisteredSpecies() const noexcept override; [[nodiscard]] std::set getRegisteredSymbols() const noexcept override; [[nodiscard]] size_t size() const noexcept override; [[nodiscard]] std::unordered_map getMassFraction() const noexcept override; - [[nodiscard]] std::unordered_map getNumberFraction() const noexcept override; [[nodiscard]] double getMassFraction(const std::string &symbol) const override; @@ -37,29 +44,26 @@ namespace fourdst::composition { [[nodiscard]] double getElectronAbundance() const noexcept override; [[nodiscard]] std::vector getMassFractionVector() const noexcept override; - [[nodiscard]] std::vector getNumberFractionVector() const noexcept override; - [[nodiscard]] std::vector getMolarAbundanceVector() const noexcept override; [[nodiscard]] size_t getSpeciesIndex(const std::string &symbol) const override; - [[nodiscard]] size_t getSpeciesIndex(const atomic::Species &species) const override; - [[nodiscard]] atomic::Species getSpeciesAtIndex(size_t index) const override; [[nodiscard]] std::unique_ptr clone() const override; - [[nodiscard]] std::map::iterator begin() override; + [[nodiscard]] iterator begin() override; + [[nodiscard]] iterator end() override; - [[nodiscard]] std::map::iterator end() override; + [[nodiscard]] const_iterator begin() const override; + [[nodiscard]] const_iterator end() const override; - [[nodiscard]] std::map::const_iterator begin() const override; + [[nodiscard]] size_t hash() const override; - [[nodiscard]] std::map::const_iterator end() const override; private: - std::set m_activeSpecies; - std::map m_masked_composition; + std::vector m_activeSpecies; + std::vector m_molarAbundances; }; } \ No newline at end of file diff --git a/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h b/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h index 6e06015..5487265 100644 --- a/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h +++ b/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h @@ -2,6 +2,7 @@ #include #include +#include namespace fourdst::composition::exceptions { /** @@ -22,14 +23,14 @@ namespace fourdst::composition::exceptions { * @brief Constructs a CompositionError with an error message. * @param message The error message. */ - explicit CompositionError(const std::string& message) + explicit CompositionError(std::string message) : m_message(std::move(message)) {} /** * @brief Returns the error message. * @return A C-style string containing the error message. */ - const char* what() const noexcept override{ + [[nodiscard]] const char* what() const noexcept override{ return m_message.c_str(); } }; @@ -60,10 +61,10 @@ namespace fourdst::composition::exceptions { protected: std::string m_message; public: - explicit SpeciesError(const std::string& message) + explicit SpeciesError(std::string message) : m_message(std::move(message)) {} - const char* what() const noexcept override { + [[nodiscard]] const char* what() const noexcept override { return m_message.c_str(); } }; diff --git a/src/composition/include/fourdst/composition/iterators/composition_abstract_iterator.h b/src/composition/include/fourdst/composition/iterators/composition_abstract_iterator.h new file mode 100644 index 0000000..a356376 --- /dev/null +++ b/src/composition/include/fourdst/composition/iterators/composition_abstract_iterator.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +#include "fourdst/atomic/atomicSpecies.h" + +namespace fourdst::composition::detail { + + template + class CompositionIterator { + public: + using iterator_category = std::random_access_iterator_tag; + using difference_type = std::ptrdiff_t; + // Returns a pair of references. Natively supports structured binding [sp, y] + using value_type = std::pair; + + + // Define reference types based on const-ness + using SpeciesRef = const atomic::Species&; + using AbundRef = std::conditional_t; + using reference = std::pair; + + struct ArrowProxy { + reference m_payload; + const reference* operator->() const { return &m_payload; } + }; + + using pointer = ArrowProxy; + + private: + using SpecIt = std::vector::const_iterator; + using AbunIt = std::conditional_t::const_iterator, + std::vector::iterator>; + + SpecIt m_sIt; + AbunIt m_aIt; + + public: + CompositionIterator() = default; + CompositionIterator(SpecIt sIt, AbunIt aIt) : m_sIt(sIt), m_aIt(aIt) {} + + template > + CompositionIterator(const CompositionIterator& other) + : m_sIt(other.getSpeciesIt()), m_aIt(other.getAbundanceIt()) {} + + [[nodiscard]] SpecIt getSpeciesIt() const { return m_sIt; } + [[nodiscard]] AbunIt getAbundanceIt() const { return m_aIt; } + + reference operator*() const { + return { *m_sIt, *m_aIt }; + } + + ArrowProxy operator->() const { + return ArrowProxy{ **this }; + } + + reference operator[](difference_type n) const { + return { *(m_sIt + n), *(m_aIt + n) }; + } + + // --- Movement --- + CompositionIterator& operator++() { ++m_sIt; ++m_aIt; return *this; } + CompositionIterator operator++(int) { auto tmp = *this; ++(*this); return tmp; } + + CompositionIterator& operator--() { --m_sIt; --m_aIt; return *this; } // FIXED + CompositionIterator operator--(int) { auto tmp = *this; --(*this); return tmp; } + + CompositionIterator& operator+=(difference_type n) { m_sIt += n; m_aIt += n; return *this; } + CompositionIterator& operator-=(difference_type n) { m_sIt -= n; m_aIt -= n; return *this; } + + // --- Arithmetic --- + friend CompositionIterator operator+(CompositionIterator it, difference_type n) { return it += n; } + + // Commutative addition (n + it) + friend CompositionIterator operator+(difference_type n, CompositionIterator it) { return it += n; } + friend CompositionIterator operator-(CompositionIterator it, difference_type n) { return it -= n; } + + // Difference between iterators + friend difference_type operator-(const CompositionIterator& lhs, const CompositionIterator& rhs) { + return lhs.m_sIt - rhs.m_sIt; + } + + template + bool operator==(const CompositionIterator& other) const { return m_sIt == other.getSpeciesIt(); } + + template + bool operator!=(const CompositionIterator& other) const { return m_sIt != other.getSpeciesIt(); } + + template + bool operator<(const CompositionIterator& other) const { return m_sIt < other.getSpeciesIt(); } + + template + bool operator>(const CompositionIterator& other) const { return m_sIt > other.getSpeciesIt(); } + + template + bool operator<=(const CompositionIterator& other) const { return m_sIt <= other.getSpeciesIt(); } + + template + bool operator>=(const CompositionIterator& other) const { return m_sIt >= other.getSpeciesIt(); } + }; + +} \ No newline at end of file diff --git a/src/composition/include/fourdst/composition/utils/composition_hash.h b/src/composition/include/fourdst/composition/utils/composition_hash.h index a9b3542..86c7ce8 100644 --- a/src/composition/include/fourdst/composition/utils/composition_hash.h +++ b/src/composition/include/fourdst/composition/utils/composition_hash.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -10,8 +9,12 @@ #include "fourdst/composition/composition_abstract.h" namespace fourdst::composition::utils { + // Make a concept which checks if a type inherits from CompositionAbstract + template + concept CompositionType = std::is_base_of_v; + struct CompositionHash { - template + template static uint64_t hash_exact(const CompositionT& comp) { uint64_t h0 = kSeed; uint64_t h1 = kSeed ^ kPrime1; @@ -99,13 +102,19 @@ namespace fourdst::composition::utils { }; } -namespace std { - template<> - struct hash { - std::size_t operator()(const fourdst::composition::Composition& c) const noexcept { - return static_cast( - fourdst::composition::utils::CompositionHash::hash_exact(c) - ); - } - }; -} \ No newline at end of file +template<> +struct std::hash { + std::size_t operator()(const fourdst::composition::CompositionAbstract& c) const noexcept { + return static_cast( + fourdst::composition::utils::CompositionHash::hash_exact(c) + ); + } +}; +template<> +struct std::hash { + std::size_t operator()(const fourdst::composition::Composition& c) const noexcept { + return static_cast( + fourdst::composition::utils::CompositionHash::hash_exact(c) + ); + } +}; diff --git a/src/composition/lib/composition.cpp b/src/composition/lib/composition.cpp index d19a4fa..1887ff5 100644 --- a/src/composition/lib/composition.cpp +++ b/src/composition/lib/composition.cpp @@ -34,36 +34,15 @@ #include "fourdst/atomic/atomicSpecies.h" #include "fourdst/atomic/species.h" #include "fourdst/composition/composition.h" + +#include + #include "fourdst/composition/utils/composition_hash.h" #include "fourdst/composition/utils.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; - } - void throw_unknown_symbol(quill::Logger* logger, const std::string& symbol) { LOG_ERROR(logger, "Symbol {} is not a valid species symbol (not in the species database)", symbol); throw fourdst::composition::exceptions::UnknownSymbolError("Symbol " + symbol + " is not a valid species symbol (not in the species database)"); @@ -76,127 +55,142 @@ namespace { } namespace fourdst::composition { - Composition::Composition( - const std::vector& symbols - ) { - for (const auto& symbol : symbols) { - registerSymbol(symbol); - } - } + + ///////////////////////////////////////////// + /// Constructors without molar abundances /// + /// These all delegate to the ctor /// + /// vector /// + ///////////////////////////////////////////// Composition::Composition( const std::set& symbols - ) { - for (const auto& symbol : symbols) { - registerSymbol(symbol); - } - } + ) : Composition(symbols | std::ranges::to()) {} + + Composition::Composition( + const std::set &species + ) : Composition(species | std::ranges::to()) {} + + Composition::Composition( + const std::unordered_set &symbols + ) : Composition(symbols | std::ranges::to()) {} + + Composition::Composition( + const std::unordered_set &species + ) : Composition(species | std::ranges::to()) {} + + Composition::Composition( + const std::vector& symbols + ) : Composition(symbolVectorToSpeciesVector(symbols)) {} Composition::Composition( const std::vector &species ) { - for (const auto& s : species) { - registerSpecies(s); - } + m_species = species; + std::ranges::sort(m_species, [&](const atomic::Species& a, const atomic::Species& b) { + return a < b; + }); + + const auto last = std::ranges::unique(m_species).begin(); + m_species.erase(last, m_species.end()); + + m_molarAbundances.resize(m_species.size(), 0.0); } - Composition::Composition( - const std::set &species - ) { - for (const auto& s : species) { - registerSpecies(s); - } - } - - Composition::Composition(const std::unordered_set &symbols) { - for (const auto& symbol : symbols) { - registerSymbol(symbol); - } - } - - Composition::Composition(const std::unordered_set &species) { - for (const auto& s : species) { - registerSpecies(s); - } - } + ////////////////////////////////////////// + /// Constructors with molar abundances /// + /// These all delegate to the ctor /// + /// vector> /// + ////////////////////////////////////////// Composition::Composition( const std::vector& symbols, const std::vector& molarAbundances - ) { - if (symbols.size() != molarAbundances.size()) { - LOG_CRITICAL(getLogger(), "The number of symbols and molarAbundances must be equal (got {} symbols and {} molarAbundances).", symbols.size(), molarAbundances.size()); - throw exceptions::InvalidCompositionError("The number of symbols and fractions must be equal. Got " + std::to_string(symbols.size()) + " symbols and " + std::to_string(molarAbundances.size()) + " fractions."); - } + ) : Composition(symbolVectorToSpeciesVector(symbols), molarAbundances) {} - for (const auto &[symbol, y] : std::views::zip(symbols, molarAbundances)) { - registerSymbol(symbol); - setMolarAbundance(symbol, y); - } - } + Composition::Composition( + const std::set &symbols, + const std::vector &molarAbundances + ) : Composition(symbolVectorToSpeciesVector(symbols | std::ranges::to()), molarAbundances) {} + + + Composition::Composition( + const std::unordered_map &symbolMolarAbundances + ) : Composition( + symbolMolarAbundances | std::views::keys | std::ranges::to(), + symbolMolarAbundances | std::views::values | std::ranges::to() + ) {} + + Composition::Composition( + const std::map &symbolMolarAbundances + ) : Composition( + symbolMolarAbundances | std::views::keys | std::ranges::to(), + symbolMolarAbundances | std::views::values | std::ranges::to() + ) {} + + Composition::Composition( + const std::unordered_map &speciesMolarAbundances + ) : Composition( + speciesMolarAbundances | std::views::keys | std::ranges::to(), + speciesMolarAbundances | std::views::values | std::ranges::to() + ) {} + + Composition::Composition( + const std::map &speciesMolarAbundances + ) : Composition( + speciesMolarAbundances | std::views::keys | std::ranges::to(), + speciesMolarAbundances | std::views::values | std::ranges::to() + ) {} Composition::Composition( const std::vector &species, const std::vector &molarAbundances ) { - if (species.size() != molarAbundances.size()) { + if (__builtin_expect(species.size() != molarAbundances.size(), 0)) { LOG_CRITICAL(getLogger(), "The number of species and molarAbundances must be equal (got {} species and {} molarAbundances).", species.size(), molarAbundances.size()); throw exceptions::InvalidCompositionError("The number of species and fractions must be equal. Got " + std::to_string(species.size()) + " species and " + std::to_string(molarAbundances.size()) + " fractions."); } - for (const auto& [s, y] : std::views::zip(species, molarAbundances)) { - registerSpecies(s); - setMolarAbundance(s, y); + const size_t numSpecies = species.size(); + m_species.reserve(numSpecies); + m_molarAbundances.reserve(numSpecies); + + for (size_t i = 0; i < numSpecies; ++i) { + m_species.push_back(species[i]); + if (__builtin_expect(molarAbundances[i] < 0.0, 0)) { + LOG_CRITICAL(getLogger(), "Molar abundance for species {} is negative (y = {}). Molar abundances must be non-negative.", species[i].name(), molarAbundances[i]); + throw exceptions::InvalidCompositionError("Molar abundance for species " + std::string(species[i].name()) + " is negative (y = " + std::to_string(molarAbundances[i]) + "). Molar abundances must be non-negative."); + } + m_molarAbundances.push_back(molarAbundances[i]); } + + auto combined = std::views::zip(m_species, m_molarAbundances); + + std::ranges::sort(combined, [](const auto& a, const auto& b) -> bool { + const auto& spA = std::get<0>(a); + const auto& spB = std::get<0>(b); + + if (spA != spB) { + return spA < spB; + } + + return std::get<1>(a) > std::get<1>(b); + }); + + auto [first, last] = std::ranges::unique(combined, [](const auto& a, const auto& b) { + return std::get<0>(a) == std::get<0>(b); + }); + + const auto newEndIndex = std::distance(combined.begin(), first); + m_species.erase(m_species.begin() + newEndIndex, m_species.end()); + m_molarAbundances.erase(m_molarAbundances.begin() + newEndIndex, m_molarAbundances.end()); } - Composition::Composition( - const std::set &symbols, - const std::vector &molarAbundances - ) { - if (symbols.size() != molarAbundances.size()) { - LOG_CRITICAL(getLogger(), "The number of symbols and molarAbundances must be equal (got {} symbols and {} molarAbundances).", symbols.size(), molarAbundances.size()); - throw exceptions::InvalidCompositionError("The number of symbols and fractions must be equal. Got " + std::to_string(symbols.size()) + " symbols and " + std::to_string(molarAbundances.size()) + " fractions."); - } + //////////////////////////////////////////// + /// Copy and conversion constructors /// + //////////////////////////////////////////// - for (const auto& [symbol, y] : std::views::zip(sortVectorBy(std::vector(symbols.begin(), symbols.end()), molarAbundances), molarAbundances)) { - registerSymbol(symbol); - setMolarAbundance(symbol, y); - } - } - - Composition::Composition(const std::unordered_map &symbolMolarAbundances) { - for (const auto& [symbol, y] : symbolMolarAbundances) { - registerSymbol(symbol); - setMolarAbundance(symbol, y); - } - } - - Composition::Composition(const std::map &symbolMolarAbundances) { - for (const auto& [symbol, y] : symbolMolarAbundances) { - registerSymbol(symbol); - setMolarAbundance(symbol, y); - } - } - - Composition::Composition(const std::unordered_map &speciesMolarAbundances) { - for (const auto& [species, y] : speciesMolarAbundances) { - registerSpecies(species); - setMolarAbundance(species, y); - } - } - - Composition::Composition(const std::map &speciesMolarAbundances) { - for (const auto& [species, y] : speciesMolarAbundances) { - registerSpecies(species); - setMolarAbundance(species, y); - } - } - - Composition::Composition( - const Composition &composition - ) { - m_registeredSpecies = composition.m_registeredSpecies; + Composition::Composition(const Composition &composition) { + m_species = composition.m_species; m_molarAbundances = composition.m_molarAbundances; } @@ -211,12 +205,32 @@ namespace fourdst::composition { const Composition &other ) { if (this != &other) { - m_registeredSpecies = other.m_registeredSpecies; + m_species = other.m_species; m_molarAbundances = other.m_molarAbundances; } + m_cache.clear(); + return *this; + } + + Composition & Composition::operator=(const CompositionAbstract &other) { + m_species.clear(); + m_molarAbundances.clear(); + m_cache.clear(); + for (const auto& species : other.getRegisteredSpecies()) { + registerSpecies(species); + setMolarAbundance(species, other.getMolarAbundance(species)); + } return *this; } + std::unique_ptr Composition::clone() const { + return std::make_unique(*this); + } + + //------------------------------------------ + // Registration methods + //------------------------------------------ + void Composition::registerSymbol( const std::string& symbol ) { @@ -231,41 +245,190 @@ namespace fourdst::composition { void Composition::registerSymbol( const std::vector& symbols ) { - for (const auto& symbol : symbols) { - registerSymbol(symbol); - } + registerSpecies(symbolVectorToSpeciesVector(symbols)); } void Composition::registerSpecies( const atomic::Species &species ) noexcept { - m_registeredSpecies.insert(species); - if (!m_molarAbundances.contains(species)) { - m_molarAbundances.emplace(species, 0.0); + if (const auto it = std::ranges::lower_bound(m_species, species); it == m_species.end() || *it != species) { + const auto index = std::distance(m_species.begin(), it); + m_species.insert(it, species); + m_molarAbundances.insert(m_molarAbundances.begin() + index, 0.0); + m_cache.clear(); } } void Composition::registerSpecies( const std::vector &species ) noexcept { - for (const auto& s : species) { - registerSpecies(s); + // We do not simply call registerSpecies(species) here as that would have a complexity of O(n^2) due to constantly + // reinserting into the vector. Rather we build the vector once and then sort it + + if (species.empty()) return; + + const size_t total_size = m_species.size() + species.size(); + m_species.reserve(total_size); + m_molarAbundances.reserve(total_size); + + for (const auto& sp : species) { + m_species.push_back(sp); + m_molarAbundances.push_back(0.0); } + + auto combined = std::views::zip(m_species, m_molarAbundances); + + std::ranges::sort(combined, [](const auto& a, const auto& b) { + const auto& speciesA = std::get<0>(a); + const auto& speciesB = std::get<0>(b); + + if (speciesA != speciesB) { + return speciesA < speciesB; + } + + return std::get<1>(a) > std::get<1>(b); + }); + + auto [first, last] = std::ranges::unique(combined, [](const auto& a, const auto& b) { + return std::get<0>(a) == std::get<0>(b); + }); + + const auto newEndIndex = std::distance(combined.begin(), first); + + m_species.erase(m_species.begin() + newEndIndex, m_species.end()); + m_molarAbundances.erase(m_molarAbundances.begin() + newEndIndex, m_molarAbundances.end()); + + m_cache.clear(); } std::set Composition::getRegisteredSymbols() const noexcept { std::set symbols; - for (const auto& species : m_registeredSpecies) { + for (const auto& species : m_species) { symbols.insert(std::string(species.name())); } return symbols; } - const std::set &Composition::getRegisteredSpecies() const noexcept { - return m_registeredSpecies; + const std::vector &Composition::getRegisteredSpecies() const noexcept { + return m_species; } + //------------------------------------------ + // Molar abundance setters + //------------------------------------------ + + void Composition::setMolarAbundance( + const std::string &symbol, + const double &molar_abundance + ) { + const auto species = getSpecies(symbol); + if (__builtin_expect(!species, 0)) { + throw_unknown_symbol(getLogger(), symbol); + } + + setMolarAbundance(species.value(), molar_abundance); + } + + void Composition::setMolarAbundance( + const atomic::Species &species, + const double &molar_abundance + ) { + if (__builtin_expect(molar_abundance < 0.0, 0)) { + LOG_ERROR(getLogger(), "Molar abundance must be non-negative for symbol {}. Currently it is {}.", species.name(), molar_abundance); + throw exceptions::InvalidCompositionError("Molar abundance must be non-negative, got " + std::to_string(molar_abundance) + " for symbol " + std::string(species.name()) + "."); + } + + const std::expected speciesIndexResult = findSpeciesIndex(species); + if (__builtin_expect(!speciesIndexResult, 0)) { + throw_unregistered_symbol(getLogger(), std::string(species.name())); + } + + assert(speciesIndexResult.value() < m_molarAbundances.size()); + + m_molarAbundances[speciesIndexResult.value()] = molar_abundance; + m_cache.clear(); + } + + + ////---------------------------------------------- + /// Methods which set multiple molar abundances + /// delegate to vector, vector + ///----------------------------------------------- + + void Composition::setMolarAbundance( + const std::vector &symbols, + const std::vector &molar_abundances + ) { + setMolarAbundance(symbolVectorToSpeciesVector(symbols), molar_abundances); + } + + void Composition::setMolarAbundance( + const std::set &symbols, + const std::vector &molar_abundances + ) { + setMolarAbundance(symbolVectorToSpeciesVector(symbols | std::ranges::to()), molar_abundances); + } + + void Composition::setMolarAbundance( + const std::set &species, + const std::vector &molar_abundances + ) { + setMolarAbundance(species | std::ranges::to(), molar_abundances); + } + + void Composition::setMolarAbundance( + const std::vector &species, + const std::vector &molar_abundances + ) { + if (__builtin_expect(species.size() != molar_abundances.size(), 0)) { + LOG_CRITICAL(getLogger(), "The number of species and molar_abundances must be equal (got {} species and {} molar_abundances).", species.size(), molar_abundances.size()); + throw exceptions::InvalidCompositionError("The number of species and fractions must be equal. Got " + std::to_string(species.size()) + " species and " + std::to_string(molar_abundances.size()) + " fractions."); + } + + if (species.empty()) return; + + if (species.size() == m_species.size()) { + if (species == m_species) { + for (const auto& [sp, y] : std::views::zip(species, molar_abundances)) { + if (__builtin_expect(y < 0.0, 0)) { + LOG_ERROR(getLogger(), "Molar abundance must be non-negative. Instead got {} for species {}.", y, sp.name()); + throw exceptions::InvalidCompositionError("Molar abundance must be non-negative. Instead got " + std::to_string(y) + " for species " + std::string(sp.name()) + "."); + } + } + + m_molarAbundances = molar_abundances; + m_cache.clear(); + return; + } + } + + for (size_t i = 0; i < species.size(); ++i) { + const double y = molar_abundances[i]; + const auto& sp = species[i]; + if (__builtin_expect(y < 0.0, 0)) { + LOG_CRITICAL(getLogger(), "Molar abundance must be non-negative. Instead got {} for species {}.", y, sp.name()); + throw exceptions::InvalidCompositionError("Molar abundance must be non-negative. Instead got " + std::to_string(y) + " for species " + std::string(sp.name()) + "."); + } + + const std::expected speciesIndexResult = findSpeciesIndex(sp); + if (__builtin_expect(!speciesIndexResult, 0)) { + throw_unregistered_symbol(getLogger(), std::string(sp.name())); + } + + const std::ptrdiff_t speciesIndex = speciesIndexResult.value(); + + m_molarAbundances[speciesIndex] = y; + } + + m_cache.clear(); + } + + + //------------------------------------------ + // Fraction and abundance getters + //------------------------------------------ + double Composition::getMassFraction(const std::string& symbol) const { const auto species = getSpecies(symbol); if (!species) { @@ -277,22 +440,26 @@ namespace fourdst::composition { double Composition::getMassFraction( const atomic::Species &species ) const { - if (!m_molarAbundances.contains(species)) { + const std::expected speciesIndexResult = findSpeciesIndex(species); + if (!speciesIndexResult) { throw_unregistered_symbol(getLogger(), std::string(species.name())); } - std::map raw_mass; + double totalMass = 0; - for (const auto& [sp, y] : m_molarAbundances) { + double speciesMass = 0; + for (const auto& [sp, y] : *this) { const double contrib = y * sp.mass(); totalMass += contrib; - raw_mass.emplace(sp, contrib); + if (sp == species) { + speciesMass = contrib; + } } - return raw_mass.at(species) / totalMass; + return speciesMass / totalMass; } std::unordered_map Composition::getMassFraction() const noexcept { std::unordered_map mass_fractions; - for (const auto &species: m_molarAbundances | std::views::keys) { + for (const auto &species: *this | std::views::keys) { mass_fractions.emplace(species, getMassFraction(species)); } return mass_fractions; @@ -312,19 +479,23 @@ namespace fourdst::composition { double Composition::getNumberFraction( const atomic::Species &species ) const { - if (!m_molarAbundances.contains(species)) { + const std::expected speciesIndexResult = findSpeciesIndex(species); + if (!speciesIndexResult) { throw_unregistered_symbol(getLogger(), std::string(species.name())); } - double total_moles_per_gram = 0.0; - for (const auto &y: m_molarAbundances | std::views::values) { - total_moles_per_gram += y; - } - return m_molarAbundances.at(species) / total_moles_per_gram; + const std::ptrdiff_t speciesIndex = speciesIndexResult.value(); + + const double total_moles_per_gram = std::accumulate( + m_molarAbundances.begin(), + m_molarAbundances.end(), + 0.0 + ); + return m_molarAbundances[speciesIndex] / total_moles_per_gram; } std::unordered_map Composition::getNumberFraction() const noexcept { std::unordered_map number_fractions; - for (const auto &species: m_molarAbundances | std::views::keys) { + for (const auto &species: m_species) { number_fractions.emplace(species, getNumberFraction(species)); } return number_fractions; @@ -344,25 +515,34 @@ namespace fourdst::composition { double Composition::getMolarAbundance( const atomic::Species &species ) const { - if (!m_molarAbundances.contains(species)) { + const std::expected speciesIndexResult = findSpeciesIndex(species); + if (!speciesIndexResult) { throw_unregistered_symbol(getLogger(), std::string(species.name())); } - return m_molarAbundances.at(species); + const std::ptrdiff_t speciesIndex = speciesIndexResult.value(); + return m_molarAbundances[speciesIndex]; } + //------------------------------------------ + // Derived property getters + //------------------------------------------ + double Composition::getMeanParticleMass() const noexcept { - std::vector X = getMassFractionVector(); - double sum = 0.0; - for (const auto& [species, x] : std::views::zip(m_registeredSpecies, X)) { - sum += x/species.mass(); + double totalMass = 0.0; + double totalMoles = 0.0; + + for (size_t i = 0; i < m_species.size(); ++i) { + totalMoles += m_molarAbundances[i]; + totalMass += m_molarAbundances[i] * m_species[i].mass(); } - return 1.0 / sum; + return totalMass / totalMoles; } + double Composition::getElectronAbundance() const noexcept { double Ye = 0.0; - for (const auto& [species, y] : m_molarAbundances) { + for (const auto& [species, y] : *this) { Ye += species.z() * y; } return Ye; @@ -377,8 +557,8 @@ namespace fourdst::composition { return m_cache.canonicalComp.value(); // Short circuit if we have cached the canonical composition } CanonicalComposition canonicalComposition; - const std::set canonicalH = {H_1, H_2, H_3, H_4, H_5, H_6, H_7}; - const std::set canonicalHe = {He_3, He_4, He_5, He_6, He_7, He_8, He_9, He_10}; + static const std::unordered_set canonicalH = {H_1, H_2, H_3, H_4, H_5, H_6, H_7}; + static const std::unordered_set canonicalHe = {He_3, He_4, He_5, He_6, He_7, He_8, He_9, He_10}; for (const auto& symbol : canonicalH) { if (contains(symbol)) { @@ -391,11 +571,8 @@ namespace fourdst::composition { } } - for (const auto& species : m_molarAbundances | std::views::keys) { - const bool isHIsotope = canonicalH.contains(species); - const bool isHeIsotope = canonicalHe.contains(species); - - if (isHIsotope || isHeIsotope) { + for (const auto& species : m_species) { + if (canonicalH.contains(species) || canonicalHe.contains(species)) { continue; // Skip canonical H and He symbols } @@ -412,25 +589,25 @@ namespace fourdst::composition { return canonicalComposition; } + //------------------------------------------ + // Vector getters + //------------------------------------------ + std::vector Composition::getMassFractionVector() const noexcept { if (m_cache.massFractions.has_value()) { return m_cache.massFractions.value(); // Short circuit if we have cached the mass fractions } std::vector massFractionVector; - std::vector speciesMass; massFractionVector.reserve(m_molarAbundances.size()); - speciesMass.reserve(m_molarAbundances.size()); - for (const auto &species: m_molarAbundances | std::views::keys) { + for (const auto &species: m_species) { massFractionVector.push_back(getMassFraction(species)); - speciesMass.push_back(species.mass()); } - std::vector massFractions = sortVectorBy(massFractionVector, speciesMass); - m_cache.massFractions = massFractions; // Cache the result - return massFractions; + m_cache.massFractions = massFractionVector; // Cache the result + return massFractionVector; } @@ -440,43 +617,25 @@ namespace fourdst::composition { } std::vector numberFractionVector; - std::vector speciesMass; numberFractionVector.reserve(m_molarAbundances.size()); - speciesMass.reserve(m_molarAbundances.size()); - for (const auto &species: m_molarAbundances | std::views::keys) { + for (const auto &species: m_species) { numberFractionVector.push_back(getNumberFraction(species)); - speciesMass.push_back(species.mass()); } - std::vector numberFractions = sortVectorBy(numberFractionVector, speciesMass); - m_cache.numberFractions = numberFractions; // Cache the result - return numberFractions; + m_cache.numberFractions = numberFractionVector; // Cache the result + return numberFractionVector; } std::vector Composition::getMolarAbundanceVector() const noexcept { - if (m_cache.molarAbundances.has_value()) { - return m_cache.molarAbundances.value(); // Short circuit if we have cached the molar abundances - } - - std::vector molarAbundanceVector; - std::vector speciesMass; - - molarAbundanceVector.reserve(m_molarAbundances.size()); - speciesMass.reserve(m_molarAbundances.size()); - - for (const auto &[species, y]: m_molarAbundances) { - molarAbundanceVector.push_back(y); - speciesMass.push_back(species.mass()); - } - - std::vector molarAbundances = sortVectorBy(molarAbundanceVector, speciesMass); - m_cache.molarAbundances = molarAbundances; // Cache the result - return molarAbundances; - + return m_molarAbundances; } + //------------------------------------------ + // Species index getters and lookups + //------------------------------------------ + size_t Composition::getSpeciesIndex( const std::string &symbol ) const { @@ -491,66 +650,36 @@ namespace fourdst::composition { size_t Composition::getSpeciesIndex( const atomic::Species &species ) const { - if (!m_registeredSpecies.contains(species)) { - LOG_ERROR(getLogger(), "Species {} is not in the composition.", species.name()); - throw exceptions::UnregisteredSymbolError("Species " + std::string(species.name()) + " is not in the composition."); - } - if (m_cache.sortedSpecies.has_value()) { - return std::distance( - m_cache.sortedSpecies->begin(), - std::ranges::find( - m_cache.sortedSpecies.value().begin(), - m_cache.sortedSpecies.value().end(), - species - ) - ); + std::expected speciesIndexResult = findSpeciesIndex(species); + if (!speciesIndexResult) { + switch (speciesIndexResult.error()) { + case SpeciesIndexLookupError::NO_REGISTERED_SPECIES: + [[fallthrough]]; + case SpeciesIndexLookupError::SPECIES_NOT_FOUND: + throw_unregistered_symbol(getLogger(), std::string(species.name())); + default: + throw std::logic_error("Unhandled SpeciesIndexLookupError in Composition::getSpeciesIndex"); + } } - std::vector speciesVector; - std::vector speciesMass; - - speciesVector.reserve(m_molarAbundances.size()); - speciesMass.reserve(m_molarAbundances.size()); - - for (const auto &s: m_registeredSpecies) { - speciesVector.emplace_back(s); - speciesMass.push_back(s.mass()); - } - - std::vector sortedSpecies = sortVectorBy(speciesVector, speciesMass); - m_cache.sortedSpecies = sortedSpecies; - return std::distance(sortedSpecies.begin(), std::ranges::find(sortedSpecies, species)); + return static_cast(speciesIndexResult.value()); } atomic::Species Composition::getSpeciesAtIndex( const size_t index ) const { - if (m_cache.sortedSpecies.has_value()) { - return m_cache.sortedSpecies.value().at(index); + if (index >= m_species.size()) { + LOG_ERROR(getLogger(), "Index {} is out of bounds for registered species (size {}).", index, m_species.size()); + throw std::out_of_range("Index " + std::to_string(index) + " is out of bounds for registered species (size " + std::to_string(m_species.size()) + ")."); } - std::vector speciesVector; - std::vector speciesMass; - - speciesVector.reserve(m_molarAbundances.size()); - speciesMass.reserve(m_molarAbundances.size()); - - for (const auto &species: m_registeredSpecies) { - speciesVector.emplace_back(species); - speciesMass.push_back(species.mass()); - } - - std::vector sortedSymbols = sortVectorBy(speciesVector, speciesMass); - if (index >= sortedSymbols.size()) { - LOG_ERROR(getLogger(), "Index {} is out of range for composition of size {}.", index, sortedSymbols.size()); - throw std::out_of_range("Index " + std::to_string(index) + " is out of range for composition of size " + std::to_string(sortedSymbols.size()) + "."); - } - return sortedSymbols.at(index); + return m_species[index]; } - std::unique_ptr Composition::clone() const { - return std::make_unique(*this); - } + + //------------------------------------------ + // Utility methods + //------------------------------------------ std::size_t Composition::hash() const { if (m_cache.hash.has_value()) { @@ -564,7 +693,7 @@ namespace fourdst::composition { bool Composition::contains( const atomic::Species &species ) const noexcept { - return m_registeredSpecies.contains(species); + return std::ranges::binary_search(m_species, species); } bool Composition::contains( @@ -578,74 +707,41 @@ namespace fourdst::composition { } size_t Composition::size() const noexcept { - return m_registeredSpecies.size(); + return m_species.size(); } - void Composition::setMolarAbundance( - const std::string &symbol, - const double &molar_abundance - ) { - const auto species = getSpecies(symbol); - if (!species) { - throw_unknown_symbol(getLogger(), symbol); + std::expected Composition::findSpeciesIndex(const atomic::Species &species) const noexcept { + if (m_species.empty()) return std::unexpected(SpeciesIndexLookupError::NO_REGISTERED_SPECIES); + + const auto it = std::ranges::lower_bound(m_species, species); + + if (it == m_species.end() || *it != species) { + return std::unexpected(SpeciesIndexLookupError::SPECIES_NOT_FOUND); } - setMolarAbundance(species.value(), molar_abundance); + return std::distance(m_species.begin(), it); } - void Composition::setMolarAbundance( - const atomic::Species &species, - const double &molar_abundance - ) { - if (!m_registeredSpecies.contains(species)) { - throw_unregistered_symbol(getLogger(), std::string(species.name())); - } - if (molar_abundance < 0.0) { - LOG_ERROR(getLogger(), "Molar abundance must be non-negative for symbol {}. Currently it is {}.", species.name(), molar_abundance); - throw exceptions::InvalidCompositionError("Molar abundance must be non-negative, got " + std::to_string(molar_abundance) + " for symbol " + std::string(species.name()) + "."); + std::vector Composition::symbolVectorToSpeciesVector(const std::vector &symbols) { + std::vector species; + species.reserve(symbols.size()); + + + for (const auto& symbol : symbols) { + const auto speciesResult = getSpecies(symbol); + if (!speciesResult) { + throw_unknown_symbol(getLogger(), symbol); + } + species.push_back(speciesResult.value()); } - m_cache.clear(); - m_molarAbundances.at(species) = molar_abundance; + return species; } - void Composition::setMolarAbundance( - const std::vector &symbols, - const std::vector &molar_abundances - ) { - for (const auto& [symbol, y] : std::views::zip(symbols, molar_abundances)) { - setMolarAbundance(symbol, y); - } - } - void Composition::setMolarAbundance( - const std::vector &species, - const std::vector &molar_abundances - ) { - for (const auto& [s, y] : std::views::zip(species, molar_abundances)) { - setMolarAbundance(s, y); - } - } - - void Composition::setMolarAbundance( - const std::set &symbols, - const std::vector &molar_abundances - ) { - for (const auto& [symbol, y] : std::views::zip(symbols, molar_abundances)) { - setMolarAbundance(symbol, y); - } - } - - void Composition::setMolarAbundance( - const std::set &species, - const std::vector &molar_abundances - ) { - for (const auto& [s, y] : std::views::zip(species, molar_abundances)) { - setMolarAbundance(s, y); - } - } - - /// OVERLOADS + //------------------------------------------ + // Stream operator + //------------------------------------------ std::ostream& operator<<( std::ostream& os, @@ -653,7 +749,7 @@ namespace fourdst::composition { ) { os << "Composition(Mass Fractions => ["; size_t count = 0; - for (const auto &species : composition.m_registeredSpecies) { + for (const auto &species : composition.m_species) { os << species << ": " << composition.getMassFraction(species); if (count < composition.size() - 1) { os << ", "; diff --git a/src/composition/lib/decorators/composition_masked.cpp b/src/composition/lib/decorators/composition_masked.cpp index 1edbd11..0052077 100644 --- a/src/composition/lib/decorators/composition_masked.cpp +++ b/src/composition/lib/decorators/composition_masked.cpp @@ -1,29 +1,41 @@ #include "fourdst/composition/decorators/composition_masked.h" - +#include "fourdst/composition/exceptions/exceptions_composition.h" #include "fourdst/atomic/species.h" + +#include #include +#include +#include +#include +#include + +#include "fourdst/composition/utils/composition_hash.h" namespace fourdst::composition { -MaskedComposition::MaskedComposition( + MaskedComposition::MaskedComposition( const CompositionAbstract& baseComposition, - const std::set& activeSpecies + const std::vector& activeSpecies ) : CompositionDecorator(baseComposition.clone()), m_activeSpecies(activeSpecies) { + + std::ranges::sort(m_activeSpecies, [](const auto &a, const auto &b) { + return a < b; + }); + + m_molarAbundances.reserve(m_activeSpecies.size()); for (const auto& species : m_activeSpecies) { - if (CompositionDecorator::contains(species)) { - m_masked_composition.emplace(species, CompositionDecorator::getMolarAbundance(species)); + if (!CompositionDecorator::contains(species)) { + m_molarAbundances.push_back(0.0); } else { - m_masked_composition.emplace(species, 0.0); + m_molarAbundances.push_back(CompositionDecorator::getMolarAbundance(species)); } } + } bool MaskedComposition::contains(const atomic::Species &species) const noexcept{ - if (m_activeSpecies.contains(species)) { - return true; - } - return false; + return std::ranges::contains(m_activeSpecies, species); } bool MaskedComposition::contains(const std::string &symbol) const { @@ -31,14 +43,10 @@ MaskedComposition::MaskedComposition( throw exceptions::UnknownSymbolError("Cannot find species '" + symbol + "' in base composition"); } const atomic::Species& species = atomic::species.at(symbol); - if (m_activeSpecies.contains(species)) { - return true; - } - - return false; + return contains(species); } - const std::set& MaskedComposition::getRegisteredSpecies() const noexcept { + const std::vector& MaskedComposition::getRegisteredSpecies() const noexcept { return m_activeSpecies; } @@ -179,11 +187,19 @@ MaskedComposition::MaskedComposition( if (!contains(symbol)) { throw exceptions::UnregisteredSymbolError("Species '" + symbol + "' is not part of the active species in the MaskedComposition."); } - return std::distance(m_activeSpecies.begin(), m_activeSpecies.find(atomic::species.at(symbol))); + return std::distance( + m_activeSpecies.begin(), + std::ranges::find_if(m_activeSpecies, + [&symbol](const atomic::Species& sp) { + return std::string(sp.name()) == symbol; + })); } size_t MaskedComposition::getSpeciesIndex(const atomic::Species &species) const { - return std::distance(m_activeSpecies.begin(), m_activeSpecies.find(species)); + return std::distance( + m_activeSpecies.begin(), + std::ranges::find(m_activeSpecies, species) + ); } atomic::Species MaskedComposition::getSpeciesAtIndex(const size_t index) const { @@ -199,19 +215,23 @@ MaskedComposition::MaskedComposition( return std::make_unique(*m_base_composition, m_activeSpecies); } - std::map::iterator MaskedComposition::begin() { - return m_masked_composition.begin(); + MaskedComposition::iterator MaskedComposition::begin() { + return {m_activeSpecies.begin(), m_molarAbundances.begin()}; } - std::map::iterator MaskedComposition::end() { - return m_masked_composition.end(); + MaskedComposition::iterator MaskedComposition::end() { + return {m_activeSpecies.end(), m_molarAbundances.end()}; } - std::map::const_iterator MaskedComposition::begin() const { - return m_masked_composition.cbegin(); + MaskedComposition::const_iterator MaskedComposition::begin() const { + return {m_activeSpecies.cbegin(), m_molarAbundances.cbegin()}; } - std::map::const_iterator MaskedComposition::end() const { - return m_masked_composition.cend(); + MaskedComposition::const_iterator MaskedComposition::end() const { + return {m_activeSpecies.cend(), m_molarAbundances.cend()}; + } + + size_t MaskedComposition::hash() const { + return utils::CompositionHash::hash_exact(*this); } }; diff --git a/tests/composition/compositionTest.cpp b/tests/composition/compositionTest.cpp index 6e962b5..75fa64e 100644 --- a/tests/composition/compositionTest.cpp +++ b/tests/composition/compositionTest.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "fourdst/atomic/atomicSpecies.h" #include "fourdst/atomic/species.h" @@ -176,12 +177,9 @@ TEST_F(compositionTest, setGetComposition) { TEST_F(compositionTest, getRegisteredSpecies) { fourdst::composition::Composition comp; comp.registerSpecies({fourdst::atomic::Be_7, fourdst::atomic::H_1, fourdst::atomic::He_4}); - auto registeredSpecies = comp.getRegisteredSpecies(); - EXPECT_TRUE(registeredSpecies.contains(fourdst::atomic::H_1)); - EXPECT_TRUE(registeredSpecies.contains(fourdst::atomic::He_4)); - EXPECT_FALSE(registeredSpecies.contains(fourdst::atomic::Li_6)); - const auto it1 = registeredSpecies.begin(); - EXPECT_EQ(*it1, fourdst::atomic::H_1); + EXPECT_TRUE(comp.contains(fourdst::atomic::H_1)); + EXPECT_TRUE(comp.contains(fourdst::atomic::He_4)); + EXPECT_FALSE(comp.contains(fourdst::atomic::Li_6)); } /** @@ -397,7 +395,7 @@ TEST_F(compositionTest, canonicalizeNaNIfAllowed) { fourdst::composition::Composition a, b; a.registerSymbol("H-1"); b.registerSymbol("H-1"); double qnan1 = std::numeric_limits::quiet_NaN(); - double qnan2 = std::bit_cast(std::uint64_t{0x7ff80000'00000042ULL}); + auto qnan2 = std::bit_cast(std::uint64_t{0x7ff80000'00000042ULL}); a.setMolarAbundance("H-1", qnan1); b.setMolarAbundance("H-1", qnan2); ASSERT_EQ(fourdst::composition::utils::CompositionHash::hash_exact(a), @@ -437,4 +435,51 @@ TEST_F(compositionTest, hashStaleing) { a.setMolarAbundance("C-12", 0.002); const std::size_t hash2 = a.hash(); EXPECT_NE(hash1, hash2); +} + +TEST_F(compositionTest, speciesOrdering) { + using namespace fourdst::atomic; + const std::unordered_map abundances ={ + {C_12, 0.001}, + {H_1, 0.702}, + {O_16, 0.22}, + {N_14, 0.0005}, + {He_4, 0.06}, + {Ar_30, 0.0001}, + {Li_6, 0.0002} + }; + + const fourdst::composition::Composition a(abundances); + static const std::vector speciesInOrder = { + H_1, He_4, Li_6, C_12, N_14, O_16, Ar_30 + }; + + const std::vector& specieFromComposition = a.getRegisteredSpecies(); + for (const auto& [spe, spo] : std::views::zip(speciesInOrder, specieFromComposition)) { + EXPECT_EQ(spe, spo); + } +} + +TEST_F(compositionTest, iterationOrdering) { + using namespace fourdst::atomic; + const std::unordered_map abundances ={ + {C_12, 0.001}, + {H_1, 0.702}, + {O_16, 0.22}, + {N_14, 0.0005}, + {He_4, 0.06}, + {Ar_30, 0.0001}, + {Li_6, 0.0002} + }; + + const fourdst::composition::Composition a(abundances); + static const std::vector speciesInOrder = { + H_1, He_4, Li_6, C_12, N_14, O_16, Ar_30 + }; + + size_t count = 0; + for (const auto &sp: a | std::views::keys) { + EXPECT_EQ(sp, speciesInOrder[count]); + ++count; + } } \ No newline at end of file