From 82d74c7bb2bb2ed13ac94f0344780c15b2467e26 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Wed, 26 Mar 2025 08:07:11 -0400 Subject: [PATCH] feat(composition): added mix method to combine compositions. Also overloaded the + operator to mix with an assumed fraction of 50/50 --- src/composition/private/composition.cpp | 55 ++++++++++++++++++++++++- src/composition/public/composition.h | 41 ++++++++++++++---- tests/composition/compositionTest.cpp | 42 +++++++++++++++++++ 3 files changed, 130 insertions(+), 8 deletions(-) diff --git a/src/composition/private/composition.cpp b/src/composition/private/composition.cpp index 822d67d..f9277c5 100644 --- a/src/composition/private/composition.cpp +++ b/src/composition/private/composition.cpp @@ -124,6 +124,12 @@ Composition::Composition(const std::vector& symbols) { } } +Composition::Composition(const std::set& symbols) { + for (const auto& symbol : symbols) { + registerSymbol(symbol); + } +} + Composition::Composition(const std::vector& symbols, const std::vector& fractions, bool massFracMode) : m_massFracMode(massFracMode) { if (symbols.size() != fractions.size()) { LOG_ERROR(m_logger, "The number of symbols and fractions must be equal."); @@ -363,6 +369,35 @@ bool Composition::finalizeNumberFracMode(bool norm) { return true; } +Composition Composition::mix(const Composition& other, double fraction) const { + if (!m_finalized || !other.m_finalized) { + LOG_ERROR(m_logger, "Compositions have not both been finalized."); + throw std::runtime_error("Compositions have not been finalized (Consider running .finalize())."); + } + + if (fraction < 0.0 || fraction > 1.0) { + LOG_ERROR(m_logger, "Fraction must be between 0 and 1."); + throw std::runtime_error("Fraction must be between 0 and 1."); + } + + std::set mixedSymbols = other.getRegisteredSymbols(); + // Get the union of the two sets + mixedSymbols.insert(m_registeredSymbols.begin(), m_registeredSymbols.end()); + + Composition mixedComposition(mixedSymbols); + for (const auto& symbol : mixedSymbols) { + double thisMassFrac, otherMassFrac = 0.0; + + thisMassFrac = hasSymbol(symbol) ? getMassFraction(symbol) : 0.0; + otherMassFrac = other.hasSymbol(symbol) ? other.getMassFraction(symbol) : 0.0; + + double massFraction = fraction * thisMassFrac + otherMassFrac * (1-fraction); + mixedComposition.setMassFraction(symbol, massFraction); + } + mixedComposition.finalize(); + return mixedComposition; +} + double Composition::getMassFraction(const std::string& symbol) const { if (!m_finalized) { LOG_ERROR(m_logger, "Composition has not been finalized."); @@ -476,8 +511,26 @@ void Composition::setCompositionMode(bool massFracMode) { } if (!okay) { LOG_ERROR(m_logger, "Composition mode could not be set."); - throw std::runtime_error("Composition mode could not be set."); + throw std::runtime_error("Composition mode could not be set due to an unknown error."); } } m_massFracMode = massFracMode; +} + +bool Composition::hasSymbol(const std::string& symbol) const { + return m_compositions.count(symbol) > 0; +} + +/// OVERLOADS + +Composition Composition::operator+(const Composition& other) const { + return mix(other, 0.5); +} + +std::ostream& composition::operator<<(std::ostream& os, const Composition& composition) { + os << "Composition: \n"; + for (const auto& [symbol, entry] : composition.m_compositions) { + os << entry << "\n"; + } + return os; } \ No newline at end of file diff --git a/src/composition/public/composition.h b/src/composition/public/composition.h index 6893ab7..30c61fc 100644 --- a/src/composition/public/composition.h +++ b/src/composition/public/composition.h @@ -267,6 +267,17 @@ namespace composition{ */ Composition(const std::vector& symbols); + /** + * @brief Constructs a Composition with the given symbols as a set. + * @param symbols The symbols to initialize the composition with. + * @example + * @code + * std::set symbols = {"H", "O"}; + * Composition comp(symbols); + * @endcode + */ + Composition(const std::set& symbols); + /** * @brief Constructs a Composition with the given symbols and mass fractions. * @param symbols The symbols to initialize the composition with. @@ -356,6 +367,13 @@ namespace composition{ */ std::vector setNumberFraction(const std::vector& symbols, const std::vector& number_fractions); + /** + * @brief Mix two compositions together with a given fraction. + * @param other The other composition to mix with. + * @param fraction The fraction of the other composition to mix with. This is the fraction of the other composition wrt. to the current. i.e. fraction=1 would mean that 50% of the new composition is from the other and 50% from the current). + */ + Composition mix(const Composition& other, double fraction) const; + /** * @brief Gets the mass fractions of all compositions. * @return An unordered map of compositions with their mass fractions. @@ -403,6 +421,13 @@ namespace composition{ */ Composition subset(const std::vector& symbols, std::string method="norm") const; + /** + * @brief Check if a symbol is registered. + * @param symbol The symbol to check. + * @return True if the symbol is registered, false otherwise. + */ + bool hasSymbol(const std::string& symbol) const; + /** * @brief Sets the composition mode. * @param massFracMode True if mass fraction mode, false if number fraction mode. @@ -415,13 +440,15 @@ namespace composition{ * @param composition The Composition to output. * @return The output stream. */ - friend std::ostream& operator<<(std::ostream& os, const Composition& composition) { - os << "Composition: \n"; - for (const auto& [symbol, entry] : composition.m_compositions) { - os << entry << "\n"; - } - return os; - } + friend std::ostream& operator<<(std::ostream& os, const Composition& composition); + + // Overload the + operator to call mix with a fraction of 0.5 + /** + * @brief Overloads the + operator to mix two compositions together with a fraction of 0.5. + * @param other The other composition to mix with. + * @return The mixed composition. + */ + Composition operator+(const Composition& other) const; }; }; diff --git a/tests/composition/compositionTest.cpp b/tests/composition/compositionTest.cpp index 4dc9ae6..afb0a0f 100644 --- a/tests/composition/compositionTest.cpp +++ b/tests/composition/compositionTest.cpp @@ -161,4 +161,46 @@ TEST_F(compositionTest, setCompositionMode) { EXPECT_THROW(comp.setCompositionMode(true), std::runtime_error); EXPECT_NO_THROW(comp.finalize()); EXPECT_NO_THROW(comp.setCompositionMode(true)); +} + +TEST_F(compositionTest, hasSymbol) { + Config::getInstance().loadConfig(EXAMPLE_FILENAME); + composition::Composition comp; + comp.registerSymbol("H-1"); + comp.registerSymbol("He-4"); + comp.setMassFraction("H-1", 0.6); + comp.setMassFraction("He-4", 0.4); + EXPECT_NO_THROW(comp.finalize()); + + EXPECT_TRUE(comp.hasSymbol("H-1")); + EXPECT_TRUE(comp.hasSymbol("He-4")); + EXPECT_FALSE(comp.hasSymbol("H-2")); + EXPECT_FALSE(comp.hasSymbol("He-3")); +} + +TEST_F(compositionTest, mix) { + Config::getInstance().loadConfig(EXAMPLE_FILENAME); + composition::Composition comp1; + comp1.registerSymbol("H-1"); + comp1.registerSymbol("He-4"); + comp1.setMassFraction("H-1", 0.6); + comp1.setMassFraction("He-4", 0.4); + EXPECT_NO_THROW(comp1.finalize()); + + 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()); + + composition::Composition mixedComp = comp1 + comp2; + EXPECT_TRUE(mixedComp.finalize()); + EXPECT_DOUBLE_EQ(mixedComp.getMassFraction("H-1"), 0.5); + EXPECT_DOUBLE_EQ(mixedComp.getMassFraction("He-4"), 0.5); + + composition::Composition mixedComp2 = comp1.mix(comp2, 0.25); + EXPECT_TRUE(mixedComp2.finalize()); + EXPECT_DOUBLE_EQ(mixedComp2.getMassFraction("H-1"), 0.45); + EXPECT_DOUBLE_EQ(mixedComp2.getMassFraction("He-4"), 0.55); } \ No newline at end of file