diff --git a/src/composition/include/fourdst/composition/composition.h b/src/composition/include/fourdst/composition/composition.h index 69af37a..5ffdaa6 100644 --- a/src/composition/include/fourdst/composition/composition.h +++ b/src/composition/include/fourdst/composition/composition.h @@ -28,6 +28,7 @@ #include "fourdst/config/config.h" #include "fourdst/logging/logging.h" +#include "fourdst/composition/composition_abstract.h" #include "fourdst/composition/atomicSpecies.h" namespace fourdst::composition { @@ -251,7 +252,7 @@ namespace fourdst::composition { * } * @endcode */ - class Composition { + class Composition : public CompositionAbstract { private: struct CompositionCache { std::optional globalComp; ///< Cached global composition data. @@ -340,7 +341,7 @@ namespace fourdst::composition { /** * @brief Default destructor. */ - ~Composition() = default; + ~Composition() override = default; /** * @brief Finalizes the composition, making it ready for querying. @@ -351,7 +352,7 @@ namespace fourdst::composition { * @return True if the composition is valid and successfully finalized, false otherwise. * @post If successful, `m_finalized` is true and global properties are computed. */ - bool finalize(bool norm=false); + [[nodiscard]] bool finalize(bool norm=false); /** * @brief Constructs a Composition and registers the given symbols. @@ -480,13 +481,13 @@ namespace fourdst::composition { * @brief Gets the registered symbols. * @return A set of registered symbols. */ - [[nodiscard]] std::set getRegisteredSymbols() const; + [[nodiscard]] std::set getRegisteredSymbols() const override; /** * @brief Get a set of all species that are registered in the composition. * @return A set of `atomic::Species` objects registered in the composition. */ - [[nodiscard]] std::set getRegisteredSpecies() const; + [[nodiscard]] std::set getRegisteredSpecies() const override; /** * @brief Sets the mass fraction for a given symbol. @@ -599,7 +600,7 @@ namespace fourdst::composition { * @return An unordered map of symbols to their mass fractions. * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] std::unordered_map getMassFraction() const; + [[nodiscard]] std::unordered_map getMassFraction() const override; /** * @brief Gets the mass fraction for a given symbol. @@ -609,7 +610,7 @@ namespace fourdst::composition { * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. * @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition. */ - [[nodiscard]] double getMassFraction(const std::string& symbol) const; + [[nodiscard]] double getMassFraction(const std::string& symbol) const override; /** * @brief Gets the mass fraction for a given isotope. @@ -619,7 +620,7 @@ namespace fourdst::composition { * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. * @throws exceptions::UnregisteredSymbolError if the isotope is not registered in the composition. */ - [[nodiscard]] double getMassFraction(const fourdst::atomic::Species& species) const; + [[nodiscard]] double getMassFraction(const fourdst::atomic::Species& species) const override; /** * @brief Gets the number fraction for a given symbol. @@ -629,7 +630,7 @@ namespace fourdst::composition { * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. * @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition. */ - [[nodiscard]] double getNumberFraction(const std::string& symbol) const; + [[nodiscard]] double getNumberFraction(const std::string& symbol) const override; /** * @brief Gets the number fraction for a given isotope. @@ -639,7 +640,7 @@ namespace fourdst::composition { * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. * @throws exceptions::UnregisteredSymbolError if the isotope is not registered in the composition. */ - [[nodiscard]] double getNumberFraction(const fourdst::atomic::Species& species) const; + [[nodiscard]] double getNumberFraction(const fourdst::atomic::Species& species) const override; /** * @brief Gets the number fractions of all species in the composition. @@ -647,7 +648,7 @@ namespace fourdst::composition { * @return An unordered map of symbols to their number fractions. * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] std::unordered_map getNumberFraction() const; + [[nodiscard]] std::unordered_map getNumberFraction() const override; /** * @brief Gets the molar abundance (X_i / A_i) for a given symbol. @@ -657,7 +658,7 @@ namespace fourdst::composition { * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. * @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition. */ - [[nodiscard]] double getMolarAbundance(const std::string& symbol) const; + [[nodiscard]] double getMolarAbundance(const std::string& symbol) const override; /** * @brief Gets the molar abundance for a given isotope. @@ -667,7 +668,7 @@ namespace fourdst::composition { * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. * @throws exceptions::UnregisteredSymbolError if the isotope is not registered in the composition. */ - [[nodiscard]] double getMolarAbundance(const fourdst::atomic::Species& species) const; + [[nodiscard]] double getMolarAbundance(const fourdst::atomic::Species& species) const override; /** * @brief Gets the composition entry and global composition data for a given symbol. @@ -703,7 +704,7 @@ namespace fourdst::composition { * @return Mean particle mass in atomic mass units (g/mol). * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] double getMeanParticleMass() const; + [[nodiscard]] double getMeanParticleMass() const override; /** * @brief Compute the mean atomic number of the composition. @@ -711,7 +712,7 @@ namespace fourdst::composition { * @return Mean atomic number . * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] double getMeanAtomicNumber() const; + [[nodiscard]] double getMeanAtomicNumber() const override; /** * @brief Compute the electron abundance of the composition. @@ -719,7 +720,7 @@ namespace fourdst::composition { * @return Ye (electron abundance). * @pre The composition must be finalized. */ - [[nodiscard]] double getElectronAbundance() const; + [[nodiscard]] double getElectronAbundance() const override; /** * @brief Creates a new Composition object containing a subset of species from this one. @@ -737,7 +738,14 @@ namespace fourdst::composition { * @param symbol The symbol to check. * @return True if the symbol is registered, false otherwise. */ - [[nodiscard]] bool hasSymbol(const std::string& symbol) const; + [[nodiscard]] bool hasSymbol(const std::string& symbol) const override; + + /** + * @brief Checks if a species is registered in the composition. + * @param species The species to check. + * @return True if the species is registered, false otherwise. + */ + [[nodiscard]] bool hasSpecies(const fourdst::atomic::Species &species) const override; /** * @brief Checks if a given isotope is present in the composition. @@ -746,7 +754,7 @@ namespace fourdst::composition { * @return True if the isotope is in the composition, false otherwise. * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] bool contains(const atomic::Species& isotope) const; + [[nodiscard]] bool contains(const atomic::Species& isotope) const override; /** * @brief Sets the composition mode (mass fraction vs. number fraction). @@ -776,7 +784,7 @@ namespace fourdst::composition { * @pre The composition must be finalized. * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] std::vector getMassFractionVector() const; + [[nodiscard]] std::vector getMassFractionVector() const override; /** * @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. @@ -785,7 +793,7 @@ namespace fourdst::composition { * @pre The composition must be finalized. * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] std::vector getNumberFractionVector() const; + [[nodiscard]] std::vector getNumberFractionVector() const override; /** * @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. @@ -794,7 +802,7 @@ namespace fourdst::composition { * @pre The composition must be finalized. * @throws exceptions::CompositionNotFinalizedError if the composition is not finalized. */ - [[nodiscard]] std::vector getMolarAbundanceVector() const; + [[nodiscard]] std::vector getMolarAbundanceVector() const override; /** * @brief get the index in the sorted vector representation for a given symbol @@ -806,7 +814,7 @@ namespace fourdst::composition { * @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; + [[nodiscard]] size_t getSpeciesIndex(const std::string& symbol) const override; /** * @brief get the index in the sorted vector representation for a given symbol @@ -818,7 +826,7 @@ namespace fourdst::composition { * @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; + [[nodiscard]] size_t getSpeciesIndex(const atomic::Species& species) const override; /** * @brief Get the species at a given index in the sorted vector representation. @@ -829,7 +837,7 @@ namespace fourdst::composition { * @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; + [[nodiscard]] atomic::Species getSpeciesAtIndex(size_t index) const override; /** * @brief Overloaded output stream operator for Composition. diff --git a/src/composition/include/fourdst/composition/composition_abstract.h b/src/composition/include/fourdst/composition/composition_abstract.h new file mode 100644 index 0000000..8da4851 --- /dev/null +++ b/src/composition/include/fourdst/composition/composition_abstract.h @@ -0,0 +1,44 @@ +#pragma once + +#include "fourdst/composition/atomicSpecies.h" + +#include +#include +#include + +class CompositionAbstract { +public: + virtual ~CompositionAbstract() = default; + [[nodiscard]] virtual bool hasSymbol(const std::string& symbol) const = 0; + [[nodiscard]] virtual bool hasSpecies(const fourdst::atomic::Species& species) const = 0; + + [[nodiscard]] virtual bool contains(const fourdst::atomic::Species& species) const = 0; + + [[nodiscard]] virtual std::set getRegisteredSymbols() const = 0; + [[nodiscard]] virtual std::set getRegisteredSpecies() const = 0; + + [[nodiscard]] virtual std::unordered_map getMassFraction() const = 0; + [[nodiscard]] virtual std::unordered_map getNumberFraction() const = 0; + + [[nodiscard]] virtual double getMassFraction(const std::string& symbol) const = 0; + [[nodiscard]] virtual double getMassFraction(const fourdst::atomic::Species& species) const = 0; + + [[nodiscard]] virtual double getNumberFraction(const std::string& symbol) const = 0; + [[nodiscard]] virtual double getNumberFraction(const fourdst::atomic::Species& species) const = 0; + + [[nodiscard]] virtual double getMolarAbundance(const std::string& symbol) const = 0; + [[nodiscard]] virtual double getMolarAbundance(const fourdst::atomic::Species& species) const = 0; + + [[nodiscard]] virtual double getMeanParticleMass() const = 0; + [[nodiscard]] virtual double getMeanAtomicNumber() const = 0; + [[nodiscard]] virtual double getElectronAbundance() const = 0; + + [[nodiscard]] virtual std::vector getMassFractionVector() const = 0; + [[nodiscard]] virtual std::vector getNumberFractionVector() const = 0; + [[nodiscard]] virtual std::vector getMolarAbundanceVector() const = 0; + + [[nodiscard]] virtual size_t getSpeciesIndex(const std::string& symbol) const = 0; + [[nodiscard]] virtual size_t getSpeciesIndex(const fourdst::atomic::Species& species) const = 0; + + [[nodiscard]] virtual fourdst::atomic::Species getSpeciesAtIndex(size_t index) const = 0; +}; \ No newline at end of file diff --git a/src/composition/lib/composition.cpp b/src/composition/lib/composition.cpp index 9574035..396f144 100644 --- a/src/composition/lib/composition.cpp +++ b/src/composition/lib/composition.cpp @@ -223,7 +223,12 @@ namespace fourdst::composition { setNumberFraction(symbols[i], fractions[i]); } } - finalize(); + if (const bool didFinalize = finalize(); !didFinalize) { + std::string msg = "Failed to finalize composition on construction. "; + msg += "Construction of a composition object requires that the sum of the fractions vector be 1.\n"; + LOG_CRITICAL(m_logger, "{}", msg); + throw exceptions::InvalidCompositionError(msg); + } } Composition::Composition(const Composition &composition) { @@ -327,7 +332,7 @@ namespace fourdst::composition { } bool Composition::isValidComposition(const std::vector& fractions) const { - double sum = std::accumulate(fractions.begin(), fractions.end(), 0.0); + const double sum = std::accumulate(fractions.begin(), fractions.end(), 0.0); if (sum < 0.999999 || sum > 1.000001) { LOG_ERROR(m_logger, "The sum of fractions must be equal to 1 (expected 1, got {}).", sum); return false; @@ -416,7 +421,7 @@ namespace fourdst::composition { ) { std::vector symbols; symbols.reserve(species.size()); - for(const auto& s : species) symbols.push_back(std::string(s.name())); + for(const auto& s : species) symbols.emplace_back(s.name()); return setMassFraction(symbols, mass_fractions); } @@ -551,7 +556,12 @@ namespace fourdst::composition { double massFraction = fraction * thisMassFrac + otherMassFrac * (1-fraction); mixedComposition.setMassFraction(symbol, massFraction); } - mixedComposition.finalize(); + if (const bool didFinalize = mixedComposition.finalize(); !didFinalize) { + std::string msg = "Failed to finalize mixed composition. "; + msg += "This likely indicates an issue with the input compositions not summing to 1.\n"; + LOG_CRITICAL(m_logger, "{}", msg); + throw exceptions::InvalidCompositionError(msg); + } return mixedComposition; } @@ -1016,6 +1026,15 @@ namespace fourdst::composition { return m_compositions.contains(symbol); } + bool Composition::hasSpecies(const fourdst::atomic::Species &species) const { + for (const auto &entry: m_compositions | std::views::values) { + if (entry.isotope() == species) { + return true; + } + } + return false; + } + bool Composition::contains( const atomic::Species &isotope ) const { diff --git a/tests/composition/compositionTest.cpp b/tests/composition/compositionTest.cpp index 19b2b0d..8e0deee 100644 --- a/tests/composition/compositionTest.cpp +++ b/tests/composition/compositionTest.cpp @@ -146,7 +146,7 @@ TEST_F(compositionTest, setGetComposition) { EXPECT_DOUBLE_EQ(comp.setMassFraction("H-1", 0.6), 0.5); EXPECT_DOUBLE_EQ(comp.setMassFraction("He-4", 0.4), 0.5); - EXPECT_NO_THROW(comp.finalize()); + EXPECT_NO_THROW(static_cast(comp.finalize())); EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.6); EXPECT_THROW(comp.setMassFraction("He-3", 0.3), fourdst::composition::exceptions::UnregisteredSymbolError); @@ -186,7 +186,7 @@ TEST_F(compositionTest, setGetNumberFraction) { EXPECT_DOUBLE_EQ(comp.setNumberFraction("H-1", 0.6), 0.5); EXPECT_DOUBLE_EQ(comp.setNumberFraction("He-4", 0.4), 0.5); - EXPECT_NO_THROW(comp.finalize()); + EXPECT_NO_THROW(static_cast(comp.finalize())); EXPECT_DOUBLE_EQ(comp.getNumberFraction("H-1"), 0.6); EXPECT_THROW(comp.setNumberFraction("He-3", 0.3), fourdst::composition::exceptions::UnregisteredSymbolError); @@ -211,7 +211,7 @@ TEST_F(compositionTest, subset) { comp.registerSymbol("He-4"); comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4); - EXPECT_NO_THROW(comp.finalize()); + EXPECT_NO_THROW(static_cast(comp.finalize())); std::vector symbols = {"H-1"}; fourdst::composition::Composition subsetComp = comp.subset(symbols, "norm"); @@ -282,7 +282,7 @@ TEST_F(compositionTest, getComposition) { comp.registerSymbol("He-4"); comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4); - EXPECT_NO_THROW(comp.finalize()); + EXPECT_NO_THROW(static_cast(comp.finalize())); const auto compositionEntry = comp.getComposition("H-1"); EXPECT_DOUBLE_EQ(compositionEntry.first.mass_fraction(), 0.6); @@ -309,7 +309,7 @@ TEST_F(compositionTest, setCompositionMode) { comp.registerSymbol("He-4"); comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4); - EXPECT_NO_THROW(comp.finalize()); + EXPECT_NO_THROW(static_cast(comp.finalize())); EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.6); EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.4); @@ -320,7 +320,7 @@ TEST_F(compositionTest, setCompositionMode) { EXPECT_NO_THROW(comp.setNumberFraction("He-4", 0.1)); EXPECT_THROW(comp.setCompositionMode(true), fourdst::composition::exceptions::CompositionNotFinalizedError); - EXPECT_NO_THROW(comp.finalize()); + EXPECT_NO_THROW(static_cast(comp.finalize())); EXPECT_NO_THROW(comp.setCompositionMode(true)); } @@ -340,7 +340,7 @@ TEST_F(compositionTest, hasSymbol) { comp.registerSymbol("He-4"); comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4); - EXPECT_NO_THROW(comp.finalize()); + EXPECT_NO_THROW(static_cast(comp.finalize())); EXPECT_TRUE(comp.hasSymbol("H-1")); EXPECT_TRUE(comp.hasSymbol("He-4")); @@ -366,14 +366,14 @@ TEST_F(compositionTest, mix) { comp1.registerSymbol("He-4"); comp1.setMassFraction("H-1", 0.6); comp1.setMassFraction("He-4", 0.4); - EXPECT_NO_THROW(comp1.finalize()); + EXPECT_NO_THROW(static_cast(comp1.finalize())); fourdst::composition::Composition comp2; comp2.registerSymbol("H-1"); comp2.registerSymbol("He-4"); comp2.setMassFraction("H-1", 0.4); comp2.setMassFraction("He-4", 0.6); - EXPECT_NO_THROW(comp2.finalize()); + EXPECT_NO_THROW(static_cast(comp2.finalize())); fourdst::composition::Composition mixedComp = comp1 + comp2; EXPECT_TRUE(mixedComp.finalize()); @@ -401,8 +401,9 @@ TEST_F(compositionTest, molarAbundance) { comp1.registerSymbol("He-4"); comp1.setMassFraction("H-1", 0.5); comp1.setMassFraction("He-4", 0.5); - comp1.finalize(); + const bool didFinalize = comp1.finalize(); + EXPECT_TRUE(didFinalize); EXPECT_DOUBLE_EQ(comp1.getMolarAbundance("H-1"), 0.5/fourdst::atomic::H_1.mass()); EXPECT_DOUBLE_EQ(comp1.getMolarAbundance("He-4"), 0.5/fourdst::atomic::He_4.mass()); } @@ -567,11 +568,14 @@ TEST_F(compositionTest, mixErrorCases) { fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME); using fourdst::composition::Composition; - Composition a; a.registerSymbol("H-1"); a.registerSymbol("He-4"); a.setMassFraction("H-1", 0.6); a.setMassFraction("He-4", 0.4); a.finalize(); + Composition a; a.registerSymbol("H-1"); a.registerSymbol("He-4"); a.setMassFraction("H-1", 0.6); a.setMassFraction("He-4", 0.4); + bool didFinalizeA = a.finalize(); + EXPECT_TRUE(didFinalizeA); Composition b; b.registerSymbol("H-1"); b.registerSymbol("He-4"); b.setMassFraction("H-1", 0.5); b.setMassFraction("He-4", 0.5); // Not finalized second comp EXPECT_THROW(static_cast(a.mix(b, 0.5)), fourdst::composition::exceptions::CompositionNotFinalizedError); - b.finalize(); + bool didFinalizeB = b.finalize(); + EXPECT_TRUE(didFinalizeB); // Invalid fraction EXPECT_THROW(static_cast(a.mix(b, -0.1)), fourdst::composition::exceptions::InvalidCompositionError); EXPECT_THROW(static_cast(a.mix(b, 1.1)), fourdst::composition::exceptions::InvalidCompositionError); @@ -706,7 +710,8 @@ TEST_F(compositionTest, vectorsAndIndexingAndSpeciesAtIndex) { // Molar abundance vector X_i/A_i in mass mode; switch back to mass mode to verify comp.setCompositionMode(true); - comp.finalize(true); + bool didFinalize = comp.finalize(true); + EXPECT_TRUE(didFinalize); auto av = comp.getMolarAbundanceVector(); ASSERT_EQ(av.size(), 3u); EXPECT_NEAR(av[iH], 0.5/species.at("H-1").mass(), 1e-12); @@ -804,3 +809,36 @@ TEST_F(compositionTest, iterationBeginEndAndIndexOutOfRange) { EXPECT_THROW(static_cast(comp.getSpeciesAtIndex(100)), std::out_of_range); } +TEST_F(compositionTest, abstractBase) { + class UnrestrictedComposition : public fourdst::composition::Composition { + private: + fourdst::atomic::Species m_species; + const Composition& m_composition; + public: + UnrestrictedComposition(const Composition& base, const fourdst::atomic::Species& species): + Composition(base), + m_species(species), + m_composition(base) + {} + + double getMolarAbundance(const fourdst::atomic::Species &species) const override { + if (species == m_species) { + return 1.0; + } + return m_composition.getMolarAbundance(species); + } + }; + + fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME); + fourdst::composition::Composition comp; + comp.registerSymbol("H-1"); comp.registerSymbol("He-4"); comp.registerSymbol("O-16"); + comp.setMassFraction("H-1", 0.5); comp.setMassFraction("He-4", 0.3); comp.setMassFraction("O-16", 0.2); + ASSERT_TRUE(comp.finalize()); + + const UnrestrictedComposition uComp(comp, fourdst::atomic::H_1); + + ASSERT_DOUBLE_EQ(uComp.getMolarAbundance(fourdst::atomic::H_1), 1.0); + ASSERT_DOUBLE_EQ(uComp.getMassFraction("He-4"), comp.getMassFraction("He-4")); + +} +