Merge pull request #7 from tboudreaux/feature/molarAbundance

feat(composition): changed how composition is conmstructed
This commit is contained in:
2025-11-07 15:51:34 -05:00
committed by GitHub
13 changed files with 521 additions and 1968 deletions

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = fourdst::libcomposition
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = v1.9.1
PROJECT_NUMBER = v2.0.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewers a

View File

@@ -18,7 +18,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# *********************************************************************** #
project('libcomposition', 'cpp', version: 'v1.9.1', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
project('libcomposition', 'cpp', version: 'v2.0.0', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
# Add default visibility for all C++ targets
add_project_arguments('-fvisibility=default', language: 'cpp')

View File

@@ -3,7 +3,7 @@
#include <string_view>
#include <string>
#include <iostream>
#include <optional>
#include <limits>
@@ -17,7 +17,7 @@ namespace fourdst::atomic {
* @param jpi_string The spin-parity string to convert (e.g., "1/2+", "5/2-", "0+").
* @return The spin value as a double. Returns `NaN` for invalid or unparsable strings.
*/
inline double convert_jpi_to_double(const std::string& jpi_string);
inline double convert_jpi_to_double(const std::string& jpi_string) noexcept;
/**
* @struct Species
@@ -63,7 +63,7 @@ namespace fourdst::atomic {
std::string m_decayModes; ///< Decay modes as a string.
double m_atomicMass; ///< Atomic mass in atomic mass units (u).
double m_atomicMassUnc; ///< Uncertainty in the atomic mass.
double m_spin = 0.0; ///< Nuclear spin as a double, derived from m_spinParity.
mutable std::optional<double> m_spin = std::nullopt; ///< Nuclear spin as a double, derived from m_spinParity.
/**
* @brief Constructs a Species object with detailed properties.
@@ -114,9 +114,7 @@ namespace fourdst::atomic {
m_spinParity(spinParity),
m_decayModes(decayModes),
m_atomicMass(atomicMass),
m_atomicMassUnc(atomicMassUnc) {
m_spin = convert_jpi_to_double(m_spinParity);
};
m_atomicMassUnc(atomicMassUnc) {};
/**
* @brief Copy constructor for Species.
@@ -138,7 +136,6 @@ namespace fourdst::atomic {
m_decayModes = species.m_decayModes;
m_atomicMass = species.m_atomicMass;
m_atomicMassUnc = species.m_atomicMassUnc;
m_spin = convert_jpi_to_double(m_spinParity);
}
@@ -259,7 +256,10 @@ namespace fourdst::atomic {
* @return The spin as a double.
*/
[[nodiscard]] double spin() const {
return m_spin;
if (!m_spin.has_value()) { // The spin calculation is very expensive, and we almost never need it so we only compute it the first time it is requested
m_spin = convert_jpi_to_double(m_spinParity);
}
return m_spin.value();
}
/**
@@ -345,7 +345,7 @@ namespace fourdst::atomic {
*
* @return The spin value as a `double`. Returns `NaN` for invalid or unparsable strings.
*/
inline double convert_jpi_to_double(const std::string& jpi_string) {
inline double convert_jpi_to_double(const std::string& jpi_string) noexcept {
std::string s = jpi_string;
if (s.empty()) {

View File

@@ -3,8 +3,8 @@
#include <string_view>
#include <string>
#include <limits> // Required for std::numeric_limits
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/composition/elements.h"
#include "fourdst/atomic/atomicSpecies.h"
#include "elements.h"
#include <expected> // For std::expected

View File

@@ -24,13 +24,12 @@
#include <unordered_map>
#include <set>
#include <utility>
#include <optional>
#include "fourdst/config/config.h"
#include "fourdst/logging/logging.h"
#include "fourdst/composition/composition_abstract.h"
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/atomic/atomicSpecies.h"
namespace fourdst::composition {
/**
@@ -62,158 +61,6 @@ namespace fourdst::composition {
}
};
/**
* @brief Represents global properties of a finalized composition.
* @details This struct holds derived quantities that describe the entire composition,
* such as mean particle mass. It is typically returned by `Composition` methods
* after the composition has been finalized and is intended for internal or advanced use.
*/
struct GlobalComposition {
double specificNumberDensity; ///< The specific number density (moles per unit mass, sum of X_i/M_i), where X_i is mass fraction and M_i is molar mass. Units: mol/g.
double meanParticleMass; ///< The mean mass per particle (inverse of specific number density). Units: g/mol.
// Overload the output stream operator for GlobalComposition
friend std::ostream& operator<<(std::ostream& os, const GlobalComposition& comp);
};
/**
* @brief Represents a single entry (an isotope) within a composition.
* @details This struct holds the properties of one component, including its symbol,
* the corresponding `atomic::Species` object, and its abundance (either as a mass
* fraction or number fraction). It manages the state and conversions for that single entry.
*/
struct CompositionEntry {
std::optional<std::string> m_symbol = std::nullopt; ///< The chemical symbol of the species (e.g., "H-1", "Fe-56").
std::optional<atomic::Species> m_isotope = std::nullopt; ///< The `atomic::Species` object containing detailed isotope data.
bool m_massFracMode = true; ///< The mode of the composition entry. True if mass fraction, false if number fraction.
double m_massFraction = 0.0; ///< The mass fraction of the species. Valid only if `m_massFracMode` is true.
double m_numberFraction = 0.0; ///< The number fraction (mole fraction) of the species. Valid only if `m_massFracMode` is false.
double m_relAbundance = 0.0; ///< The relative abundance, used internally for conversions. For mass fraction mode, this is X_i / A_i; for number fraction mode, it's n_i * A_i.
double m_molesPerMass = 0.0;
double m_cachedNumberFraction = 0.0; ///< Cached number fraction for conversions when in mass fraction mode.
bool m_initialized = false; ///< True if the composition entry has been initialized with a valid species.
/**
* @brief Default constructor. Initializes a default entry (H-1), but in an uninitialized state.
*/
CompositionEntry();
/**
* @brief Constructs a CompositionEntry for a given symbol and abundance mode.
* @param symbol The chemical symbol of the species (e.g., "He-4").
* @param massFracMode True to operate in mass fraction mode, false for number fraction mode.
* @throws exceptions::InvalidSpeciesSymbolError if the symbol does not exist in the atomic species database.
* @throws exceptions::EntryAlreadyInitializedError if setSpecies is called on an already initialized entry.
* @par Usage Example:
* @code
* CompositionEntry entry("H-1", true); // Entry for H-1 in mass fraction mode.
* @endcode
*/
explicit CompositionEntry(const std::string& symbol, bool massFracMode=true);
/**
* @brief Copy constructor.
* @param entry The CompositionEntry to copy.
*/
CompositionEntry(const CompositionEntry& entry);
/**
* @brief Sets the species for the composition entry. This can only be done once.
* @param symbol The chemical symbol of the species.
* @throws exceptions::EntryAlreadyInitializedError if the entry has already been initialized.
* @throws exceptions::InvalidSpeciesSymbolError if the symbol is not found in the atomic species database.
*/
void setSpecies(const std::string& symbol);
/**
* @brief Gets the chemical symbol of the species.
* @return The chemical symbol.
*/
[[nodiscard]] std::string symbol() const;
/**
* @brief Gets the mass fraction of the species.
* @pre The entry must be in mass fraction mode.
* @return The mass fraction of the species.
* @throws exceptions::CompositionModeError if the entry is in number fraction mode.
*/
[[nodiscard]] double mass_fraction() const;
/**
* @brief Gets the number fraction of the species.
* @pre The entry must be in number fraction mode.
* @return The number fraction of the species.
* @throws exceptions::CompositionModeError if the entry is in mass fraction mode.
*/
[[nodiscard]] double number_fraction() const;
/**
* @brief Gets the number fraction, converting from mass fraction if necessary.
* @param totalMolesPerMass The total moles per unit mass (specific number density) of the entire composition.
* @return The number fraction of the species.
*/
[[nodiscard]] double number_fraction(double totalMolesPerMass) const;
/**
* @brief Gets the relative abundance of the species.
* @return The relative abundance.
*/
[[nodiscard]] double rel_abundance() const;
/**
* @brief Gets the isotope data for the species.
* @return A const reference to the `atomic::Species` object.
*/
[[nodiscard]] atomic::Species isotope() const;
/**
* @brief Gets the mode of the composition entry.
* @return True if in mass fraction mode, false if in number fraction mode.
*/
[[nodiscard]] bool getMassFracMode() const;
/**
* @brief Sets the mass fraction of the species.
* @param mass_fraction The mass fraction to set. Must be in [0, 1].
* @pre The entry must be in mass fraction mode.
* @throws exceptions::CompositionModeError if the entry is in number fraction mode.
*/
void setMassFraction(double mass_fraction);
/**
* @brief Sets the number fraction of the species.
* @param number_fraction The number fraction to set. Must be in [0, 1].
* @pre The entry must be in number fraction mode.
* @throws exceptions::CompositionModeError if the entry is in mass fraction mode.
*/
void setNumberFraction(double number_fraction);
/**
* @brief Switches the mode to mass fraction mode.
* @param meanMolarMass The mean molar mass of the composition, required for conversion.
* @return True if the mode was successfully set, false otherwise.
*/
bool setMassFracMode(double meanMolarMass);
/**
* @brief Switches the mode to number fraction mode.
* @param totalMolesPerMass The total moles per unit mass (specific number density) of the composition.
* @return True if the mode was successfully set, false otherwise.
*/
bool setNumberFracMode(double totalMolesPerMass);
/**
* @brief Overloaded output stream operator for CompositionEntry.
* @param os The output stream.
* @param entry The CompositionEntry to output.
* @return The output stream.
*/
friend std::ostream& operator<<(std::ostream& os, const CompositionEntry& entry);
};
/**
* @class Composition
* @brief Manages a collection of chemical species and their abundances.
@@ -256,7 +103,6 @@ namespace fourdst::composition {
class Composition : public CompositionAbstract {
private:
struct CompositionCache {
std::optional<GlobalComposition> globalComp; ///< Cached global composition data.
std::optional<CanonicalComposition> canonicalComp; ///< Cached canonical composition data.
std::optional<std::vector<double>> massFractions; ///< Cached vector of mass fractions.
std::optional<std::vector<double>> numberFractions; ///< Cached vector of number fractions.
@@ -266,7 +112,6 @@ namespace fourdst::composition {
std::optional<double> Ye; ///< Cached electron abundance.
void clear() {
globalComp = std::nullopt;
canonicalComp = std::nullopt;
massFractions = std::nullopt;
numberFractions = std::nullopt;
@@ -277,7 +122,7 @@ namespace fourdst::composition {
}
[[nodiscard]] bool is_clear() const {
return !globalComp.has_value() && !canonicalComp.has_value() && !massFractions.has_value() &&
return !canonicalComp.has_value() && !massFractions.has_value() &&
!numberFractions.has_value() && !molarAbundances.has_value() && !sortedSymbols.has_value() &&
!Ye.has_value() && !sortedSpecies.has_value();
}
@@ -289,52 +134,11 @@ namespace fourdst::composition {
return logger;
}
bool m_finalized = false; ///< True if the composition is finalized.
double m_specificNumberDensity = 0.0; ///< The specific number density of the composition (\sum_{i} X_i m_i. Where X_i is the number fraction of the ith species and m_i is the mass of the ith species).
double m_meanParticleMass = 0.0; ///< The mean particle mass of the composition (\sum_{i} \frac{n_i}{m_i}. where n_i is the number fraction of the ith species and m_i is the mass of the ith species).
bool m_massFracMode = true; ///< True if mass fraction mode, false if number fraction mode.
std::set<std::string> m_registeredSymbols; ///< The registered symbols.
std::unordered_map<std::string, CompositionEntry> m_compositions; ///< The compositions.
std::set<atomic::Species> m_registeredSpecies;
std::map<atomic::Species, double> m_molarAbundances;
mutable CompositionCache m_cache; ///< Cache for computed properties to avoid redundant calculations.
/**
* @brief Checks if the given symbol is valid by checking against the global species database.
* @param symbol The symbol to check.
* @return True if the symbol is valid, false otherwise.
*/
static bool isValidSymbol(const std::string& symbol);
/**
* @brief Checks if the given fractions are valid (sum to ~1.0).
* @param fractions The fractions to check.
* @return True if the fractions are valid, false otherwise.
*/
[[nodiscard]] static bool isValidComposition(const std::vector<double>& fractions) ;
/**
* @brief Validates the given fractions, throwing an exception on failure.
* @param fractions The fractions to validate.
* @throws exceptions::InvalidCompositionError if the fractions are invalid.
*/
static void validateComposition(const std::vector<double>& fractions) ;
/**
* @brief Finalizes the composition in mass fraction mode.
* @param norm If true, the composition will be normalized to sum to 1.
* @return True if the composition is successfully finalized, false otherwise.
*/
bool finalizeMassFracMode(bool norm);
/**
* @brief Finalizes the composition in number fraction mode.
* @param norm If true, the composition will be normalized to sum to 1.
* @return True if the composition is successfully finalized, false otherwise.
*/
bool finalizeNumberFracMode(bool norm);
public:
/**
* @brief Default constructor.
@@ -346,17 +150,6 @@ namespace fourdst::composition {
*/
~Composition() override = default;
/**
* @brief Finalizes the composition, making it ready for querying.
* @details This method checks if the sum of all fractions (mass or number) is approximately 1.0.
* It also computes global properties like mean particle mass. This **must** be called before
* any `get...` method can be used.
* @param norm If true, the composition will be normalized to sum to 1 before validation. [Default: false]
* @return True if the composition is valid and successfully finalized, false otherwise.
* @post If successful, `m_finalized` is true and global properties are computed.
*/
[[nodiscard]] bool finalize(bool norm=false);
/**
* @brief Constructs a Composition and registers the given symbols.
* @param symbols The symbols to register. The composition will be in mass fraction mode by default.
@@ -372,6 +165,8 @@ namespace fourdst::composition {
*/
explicit Composition(const std::vector<std::string>& symbols);
explicit Composition(const std::vector<atomic::Species>& species);
/**
* @brief Constructs a Composition and registers the given symbols from a set.
* @param symbols The symbols to register. The composition will be in mass fraction mode by default.
@@ -384,13 +179,14 @@ namespace fourdst::composition {
*/
explicit Composition(const std::set<std::string>& symbols);
explicit Composition(const std::set<atomic::Species>& species);
/**
* @brief Constructs and finalizes a Composition with the given symbols and fractions.
* @details This constructor provides a convenient way to create a fully-formed, finalized composition in one step.
* The provided fractions must be valid and sum to 1.0.
* @param symbols The symbols to initialize the composition with.
* @param fractions The fractions (mass or number) corresponding to the symbols.
* @param massFracMode True if `fractions` are mass fractions, false if they are number fractions. [Default: true]
* @param molarAbundances The corresponding molar abundances for each symbol.
* @throws exceptions::InvalidCompositionError if the number of symbols and fractions do not match, or if the fractions do not sum to ~1.0.
* @throws exceptions::InvalidSymbolError if any symbol is invalid.
* @post The composition is immediately finalized.
@@ -401,7 +197,11 @@ namespace fourdst::composition {
* Composition comp(symbols, mass_fractions); // Finalized on construction
* @endcode
*/
Composition(const std::vector<std::string>& symbols, const std::vector<double>& fractions, bool massFracMode=true);
Composition(const std::vector<std::string>& symbols, const std::vector<double>& molarAbundances);
Composition(const std::vector<atomic::Species>& species, const std::vector<double>& molarAbundances);
Composition(const std::set<std::string>& symbols, const std::vector<double>& molarAbundances);
/**
* @brief Constructs a Composition from another Composition.
@@ -420,7 +220,6 @@ namespace fourdst::composition {
* @brief Registers a new symbol for inclusion in the composition.
* @details A symbol must be registered before its abundance can be set. The first registration sets the mode (mass/number fraction) for the entire composition.
* @param symbol The symbol to register (e.g., "Fe-56").
* @param massFracMode True for mass fraction mode, false for number fraction mode. This is only effective for the first symbol registered.
* @throws exceptions::InvalidSymbolError if the symbol is not in the atomic species database.
* @throws exceptions::CompositionModeError if attempting to register with a mode that conflicts with the existing mode.
* @par Usage Example:
@@ -430,12 +229,11 @@ namespace fourdst::composition {
* comp.registerSymbol("He-4"); // Must also be mass fraction mode
* @endcode
*/
void registerSymbol(const std::string& symbol, bool massFracMode=true);
void registerSymbol(const std::string& symbol);
/**
* @brief Registers multiple new symbols.
* @param symbols The symbols to register.
* @param massFracMode True for mass fraction mode, false for number fraction mode.
* @throws exceptions::InvalidSymbolError if any symbol is invalid.
* @throws exceptions::CompositionModeError if the mode conflicts with an already set mode.
* @par Usage Example:
@@ -445,12 +243,11 @@ namespace fourdst::composition {
* comp.registerSymbol(symbols);
* @endcode
*/
void registerSymbol(const std::vector<std::string>& symbols, bool massFracMode=true);
void registerSymbol(const std::vector<std::string>& symbols);
/**
* @brief Registers a new species by extracting its symbol.
* @param species The species to register.
* @param massFracMode True for mass fraction mode, false for number fraction mode.
* @throws exceptions::InvalidSymbolError if the species' symbol is invalid.
* @throws exceptions::CompositionModeError if the mode conflicts.
* @par Usage Example:
@@ -460,13 +257,12 @@ namespace fourdst::composition {
* comp.registerSpecies(fourdst::atomic::species.at("H-1"));
* @endcode
*/
void registerSpecies(const fourdst::atomic::Species& species, bool massFracMode=true);
void registerSpecies(const atomic::Species& species);
/**
* @brief Registers a vector of new species.
* @param species The vector of species to register.
* @param massFracMode True for mass fraction mode, false for number fraction mode.
* @throws exceptions::InvalidSymbolError if any species' symbol is invalid.
* @throws exceptions::CompositionModeError if the mode conflicts.
* @par Usage Example:
@@ -477,8 +273,50 @@ namespace fourdst::composition {
* comp.registerSpecies(my_species, false); // Number fraction mode
* @endcode
*/
void registerSpecies(const std::vector<fourdst::atomic::Species>& species, bool massFracMode=true);
void registerSpecies(const std::vector<atomic::Species>& species);
/**
* @brief Checks if a given isotope is present in the composition.
* @pre The composition must be finalized.
* @param species The isotope to check for.
* @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& species) const override;
[[nodiscard]] bool contains(const std::string& symbol) const override;
[[nodiscard]] size_t size() const override;
void setMolarAbundance(
const std::string& symbol,
const double& molar_abundance
);
void setMolarAbundance(
const atomic::Species& species,
const double& molar_abundance
);
void setMolarAbundance(
const std::vector<std::string>& symbols,
const std::vector<double>& molar_abundances
);
void setMolarAbundance(
const std::vector<atomic::Species>& species,
const std::vector<double>& molar_abundances
);
void setMolarAbundance(
const std::set<std::string>& symbols,
const std::vector<double>& molar_abundances
);
void setMolarAbundance(
const std::set<atomic::Species>& species,
const std::vector<double>& molar_abundances
);
/**
* @brief Gets the registered symbols.
@@ -490,112 +328,7 @@ namespace fourdst::composition {
* @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<fourdst::atomic::Species> getRegisteredSpecies() const override;
/**
* @brief Sets the mass fraction for a given symbol.
* @param symbol The symbol to set the mass fraction for.
* @param mass_fraction The mass fraction to set (must be in [0, 1]).
* @return The previous mass fraction that was set for the symbol.
* @throws exceptions::UnregisteredSymbolError if the symbol is not registered.
* @throws exceptions::CompositionModeError if the composition is in number fraction mode.
* @throws exceptions::InvalidCompositionError if the mass fraction is not between 0 and 1.
* @post The composition is marked as not finalized.
* @par Usage Example:
* @code
* Composition comp;
* comp.registerSymbol("H-1");
* comp.setMassFraction("H-1", 0.7);
* @endcode
*/
double setMassFraction(const std::string& symbol, const double& mass_fraction);
/**
* @brief Sets the mass fraction for multiple symbols.
* @param symbols The symbols to set the mass fractions for.
* @param mass_fractions The mass fractions corresponding to the symbols.
* @return A vector of the previous mass fractions that were set.
* @throws exceptions::InvalidCompositionError if symbol and fraction counts differ.
* @throws See `setMassFraction(const std::string&, const double&)` for other exceptions.
* @post The composition is marked as not finalized.
*/
std::vector<double> setMassFraction(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions);
/**
* @brief Sets the mass fraction for a given species.
* @param species The species to set the mass fraction for.
* @param mass_fraction The mass fraction to set.
* @return The previous mass fraction that was set for the species.
* @throws exceptions::UnregisteredSymbolError if the species is not registered.
* @throws exceptions::CompositionModeError if the composition is in number fraction mode.
* @throws exceptions::InvalidCompositionError if the mass fraction is not between 0 and 1.
*/
double setMassFraction(const fourdst::atomic::Species& species, const double& mass_fraction);
/**
* @brief Sets the mass fraction for multiple species.
* @param species The vector of species to set the mass fractions for.
* @param mass_fractions The vector of mass fractions corresponding to the species.
* @return A vector of the previous mass fractions that were set.
* @throws See `setMassFraction(const std::vector<std::string>&, const std::vector<double>&)` for exceptions.
*/
std::vector<double> setMassFraction(const std::vector<fourdst::atomic::Species>& species, const std::vector<double>& mass_fractions);
/**
* @brief Sets the number fraction for a given symbol.
* @param symbol The symbol to set the number fraction for.
* @param number_fraction The number fraction to set (must be in [0, 1]).
* @return The previous number fraction that was set.
* @throws exceptions::UnregisteredSymbolError if the symbol is not registered.
* @throws exceptions::CompositionModeError if the composition is in mass fraction mode.
* @throws exceptions::InvalidCompositionError if the number fraction is not between 0 and 1.
* @post The composition is marked as not finalized.
*/
double setNumberFraction(const std::string& symbol, const double& number_fraction);
/**
* @brief Sets the number fraction for multiple symbols.
* @param symbols The symbols to set the number fractions for.
* @param number_fractions The number fractions corresponding to the symbols.
* @return A vector of the previous number fractions that were set.
* @throws exceptions::InvalidCompositionError if symbol and fraction counts differ.
* @throws See `setNumberFraction(const std::string&, const double&)` for other exceptions.
*/
std::vector<double> setNumberFraction(const std::vector<std::string>& symbols, const std::vector<double>& number_fractions);
/**
* @brief Sets the number fraction for a given species.
* @param species The species to set the number fraction for.
* @param number_fraction The number fraction to set for the species.
* @return The previous number fraction that was set for the species.
* @throws exceptions::UnregisteredSymbolError if the species is not registered.
* @throws exceptions::CompositionModeError if the composition is in mass fraction mode.
* @throws exceptions::InvalidCompositionError if the number fraction is not between 0 and 1.
*/
double setNumberFraction(const fourdst::atomic::Species& species, const double& number_fraction);
/**
* @brief Sets the number fraction for multiple species.
* @param species The vector of species to set the number fractions for.
* @param number_fractions The vector of number fractions corresponding to the species.
* @return The vector of the previous number fractions that were set.
* @throws See `setNumberFraction(const std::vector<std::string>&, const std::vector<double>&)` for exceptions.
*/
std::vector<double> setNumberFraction(const std::vector<fourdst::atomic::Species>& species, const std::vector<double>& number_fractions);
/**
* @brief Mixes this composition with another to produce a new composition.
* @details The mixing is performed linearly on the mass fractions. The formula for each species is:
* `new_X_i = fraction * this_X_i + (1 - fraction) * other_X_i`.
* The resulting composition is automatically finalized.
* @param other The other composition to mix with.
* @param fraction The mixing fraction. A value of 1.0 means the new composition is 100% `this`, 0.0 means 100% `other`.
* @return A new, finalized `Composition` object representing the mixture.
* @pre Both `this` and `other` compositions must be finalized.
* @throws exceptions::CompositionNotFinalizedError if either composition is not finalized.
* @throws exceptions::InvalidCompositionError if the fraction is not between 0 and 1.
*/
[[nodiscard]] Composition mix(const Composition& other, double fraction) const;
[[nodiscard]] const std::set<atomic::Species> &getRegisteredSpecies() const override;
/**
* @brief Gets the mass fractions of all species in the composition.
@@ -603,7 +336,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<std::string, double> getMassFraction() const override;
[[nodiscard]] std::unordered_map<atomic::Species, double> getMassFraction() const override;
/**
* @brief Gets the mass fraction for a given symbol.
@@ -651,7 +384,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<std::string, double> getNumberFraction() const override;
[[nodiscard]] std::unordered_map<atomic::Species, double> getNumberFraction() const override;
/**
* @brief Gets the molar abundance (X_i / A_i) for a given symbol.
@@ -671,35 +404,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 override;
/**
* @brief Gets the composition entry and global composition data for a given symbol.
* @pre The composition must be finalized.
* @param symbol The symbol to get the composition for.
* @return A pair containing the CompositionEntry and GlobalComposition for the given symbol.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition.
*/
[[nodiscard]] std::pair<CompositionEntry, GlobalComposition> getComposition(const std::string& symbol) const;
/**
* @brief Gets the composition entry and global composition data for a given species.
* @pre The composition must be finalized.
* @param species The species to get the composition for.
* @return A pair containing the CompositionEntry and GlobalComposition for the given species.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnregisteredSymbolError if the species is not in the composition.
*/
[[nodiscard]] std::pair<CompositionEntry, GlobalComposition> getComposition(const fourdst::atomic::Species& species) const;
/**
* @brief Gets all composition entries and the global composition data.
* @pre The composition must be finalized.
* @return A pair containing an unordered map of all CompositionEntries and the GlobalComposition.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*/
[[nodiscard]] std::pair<std::unordered_map<std::string, CompositionEntry>, GlobalComposition> getComposition() const;
[[nodiscard]] double getMolarAbundance(const atomic::Species& species) const override;
/**
* @brief Compute the mean particle mass of the composition.
@@ -709,14 +414,6 @@ namespace fourdst::composition {
*/
[[nodiscard]] double getMeanParticleMass() const override;
/**
* @brief Compute the mean atomic number of the composition.
* @pre The composition must be finalized.
* @return Mean atomic number <Z>.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*/
[[nodiscard]] double getMeanAtomicNumber() const override;
/**
* @brief Compute the electron abundance of the composition.
* @details Ye is defined as the sum over all species of (Z_i * X_i / A_i), where Z_i is the atomic number, X_i is the mass fraction, and A_i is the atomic mass of species i.
@@ -725,49 +422,6 @@ namespace fourdst::composition {
*/
[[nodiscard]] double getElectronAbundance() const override;
/**
* @brief Creates a new Composition object containing a subset of species from this one.
* @param symbols The symbols to include in the subset.
* @param method The method for handling the abundances of the new subset. Can be "norm" (normalize abundances to sum to 1) or "none" (keep original abundances).
* @return A new `Composition` object containing the subset.
* @throws exceptions::UnregisteredSymbolError if any requested symbol is not in the original composition.
* @throws exceptions::InvalidMixingMode if an invalid method is provided.
* @throws exceptions::FailedToFinalizeCompositionError if normalization fails.
*/
[[nodiscard]] Composition subset(const std::vector<std::string>& symbols, const std::string& method="norm") const;
/**
* @brief Checks if a symbol is registered in the composition.
* @param symbol The symbol to check.
* @return True if the symbol is registered, false otherwise.
*/
[[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.
* @pre The composition must be finalized.
* @param isotope The isotope to check for.
* @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 override;
/**
* @brief Sets the composition mode (mass fraction vs. number fraction).
* @details This function converts all entries in the composition to the specified mode.
* @pre The composition must be finalized before the mode can be switched.
* @param massFracMode True to switch to mass fraction mode, false for number fraction mode.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws std::runtime_error if the conversion fails for an unknown reason.
*/
void setCompositionMode(bool massFracMode);
/**
* @brief Gets the current canonical composition (X, Y, Z).
@@ -850,22 +504,12 @@ namespace fourdst::composition {
*/
friend std::ostream& operator<<(std::ostream& os, const Composition& composition);
/**
* @brief Overloads the + operator to mix two compositions with a 50/50 fraction.
* @details This is a convenience operator that calls `mix(other, 0.5)`.
* @param other The other composition to mix with.
* @return The new, mixed composition.
* @pre Both compositions must be finalized.
* @throws See `mix()` for exceptions.
*/
Composition operator+(const Composition& other) const;
/**
* @brief Returns an iterator to the beginning of the composition map.
* @return An iterator to the beginning.
*/
auto begin() {
return m_compositions.begin();
return m_molarAbundances.begin();
}
/**
@@ -873,7 +517,7 @@ namespace fourdst::composition {
* @return A const iterator to the beginning.
*/
[[nodiscard]] auto begin() const {
return m_compositions.cbegin();
return m_molarAbundances.cbegin();
}
/**
@@ -881,7 +525,7 @@ namespace fourdst::composition {
* @return An iterator to the end.
*/
auto end() {
return m_compositions.end();
return m_molarAbundances.end();
}
/**
@@ -889,7 +533,7 @@ namespace fourdst::composition {
* @return A const iterator to the end.
*/
[[nodiscard]] auto end() const {
return m_compositions.cend();
return m_molarAbundances.cend();
}
};

View File

@@ -1,6 +1,6 @@
#pragma once
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/atomic/atomicSpecies.h"
#include <string>
#include <unordered_map>
@@ -36,20 +36,6 @@ public:
*/
virtual ~CompositionAbstract() = default;
/**
* @brief Check if a chemical symbol is registered in the composition.
* @param symbol The chemical symbol to check (e.g., "H", "He").
* @return True if the symbol is present, false otherwise.
*/
[[nodiscard]] virtual bool hasSymbol(const std::string& symbol) const = 0;
/**
* @brief Check if a species is registered in the composition.
* @param species The atomic species to check.
* @return True if the species is present, false otherwise.
*/
[[nodiscard]] virtual bool hasSpecies(const fourdst::atomic::Species& species) const = 0;
/**
* @brief Check if the composition contains the given species.
* @param species The atomic species to check.
@@ -57,6 +43,15 @@ public:
*/
[[nodiscard]] virtual bool contains(const fourdst::atomic::Species& species) const = 0;
/**
* @brief Check if the composition contains the given species.
* @param symbol The symbol of the atomic species to check.
* @return True if the species is contained, false otherwise.
*/
[[nodiscard]] virtual bool contains(const std::string& symbol) const = 0;
[[nodiscard]] virtual size_t size() const = 0;
/**
* @brief Get all registered chemical symbols in the composition.
* @return A set of registered chemical symbols.
@@ -67,19 +62,19 @@ public:
* @brief Get all registered atomic species in the composition.
* @return A set of registered atomic species.
*/
[[nodiscard]] virtual std::set<fourdst::atomic::Species> getRegisteredSpecies() const = 0;
[[nodiscard]] virtual const std::set<fourdst::atomic::Species> &getRegisteredSpecies() const = 0;
/**
* @brief Get the mass fraction for all registered symbols.
* @return An unordered map from symbol to mass fraction.
*/
[[nodiscard]] virtual std::unordered_map<std::string, double> getMassFraction() const = 0;
[[nodiscard]] virtual std::unordered_map<fourdst::atomic::Species, double> getMassFraction() const = 0;
/**
* @brief Get the number fraction for all registered symbols.
* @return An unordered map from symbol to number fraction.
*/
[[nodiscard]] virtual std::unordered_map<std::string, double> getNumberFraction() const = 0;
[[nodiscard]] virtual std::unordered_map<fourdst::atomic::Species, double> getNumberFraction() const = 0;
/**
* @brief Get the mass fraction for a given symbol.
@@ -129,12 +124,6 @@ public:
*/
[[nodiscard]] virtual double getMeanParticleMass() const = 0;
/**
* @brief Get the mean atomic number of the composition.
* @return The mean atomic number.
*/
[[nodiscard]] virtual double getMeanAtomicNumber() const = 0;
/**
* @brief Get the electron abundance of the composition.
* @return The electron abundance.

View File

@@ -94,14 +94,6 @@ namespace fourdst::composition::exceptions {
using CompositionError::CompositionError;
};
/**
* @class InvalidSymbolError
* @brief Exception thrown when a symbol used in a composition is invalid.
*/
class InvalidSymbolError final : public CompositionError {
using CompositionError::CompositionError;
};
/**
* @class UnregisteredSymbolError
* @brief Exception thrown when a symbol is used that has not been registered.
@@ -146,4 +138,20 @@ namespace fourdst::composition::exceptions {
using CompositionEntryError::CompositionEntryError;
};
class SpeciesError : public std::exception {
protected:
std::string m_message;
public:
explicit SpeciesError(const std::string& message)
: m_message(std::move(message)) {}
const char* what() const noexcept override {
return m_message.c_str();
}
};
class UnknownSymbolError final : public SpeciesError {
using SpeciesError::SpeciesError;
};
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "fourdst/composition/composition.h"
#include "fourdst/atomic/atomicSpecies.h"
#include <vector>
namespace fourdst::composition {
Composition buildCompositionFromMassFractions(
const std::vector<std::string>& symbols,
const std::vector<double>& massFractions
);
Composition buildCompositionFromMassFractions(
const std::vector<atomic::Species>& species,
const std::vector<double>& massFractions
);
Composition buildCompositionFromMassFractions(
const std::set<atomic::Species>& species,
const std::vector<double>& massFractions
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
#include "fourdst/composition/composition.h"
#include "fourdst/composition/exceptions/exceptions_composition.h"
#include "fourdst/atomic/atomicSpecies.h"
#include "fourdst/atomic/species.h"
#include "fourdst/composition/utils.h"
#include <ranges>
#include <vector>
#include <set>
#include <string>
namespace {
std::optional<fourdst::atomic::Species> getSpecies(const std::string& symbol) {
if (!fourdst::atomic::species.contains(symbol)) {
return std::nullopt;
}
return fourdst::atomic::species.at(symbol);
}
void throw_unknown_symbol(quill::Logger* logger, const std::string& symbol) {
throw fourdst::composition::exceptions::UnknownSymbolError("Symbol " + symbol + " is not a valid species symbol (not in the species database)");
}
}
namespace fourdst::composition {
Composition buildCompositionFromMassFractions(
const std::set<atomic::Species> &species,
const std::vector<double> &massFractions
) {
Composition composition;
for (const auto& [sp, xi] : std::views::zip(species, massFractions)) {
composition.registerSpecies(sp);
composition.setMolarAbundance(sp, xi/sp.mass());
}
return composition;
}
Composition buildCompositionFromMassFractions(const std::vector<atomic::Species> &species, const std::vector<double> &massFractions) {
return buildCompositionFromMassFractions(std::set<atomic::Species>(species.begin(), species.end()), massFractions);
}
Composition buildCompositionFromMassFractions(const std::vector<std::string> &symbols, const std::vector<double> &massFractions) {
std::set<atomic::Species> species;
for (const auto& symbol : symbols) {
auto result = getSpecies(symbol);
if (!result) {
throw_unknown_symbol(nullptr, symbol);
}
species.insert(result.value());
}
return buildCompositionFromMassFractions(species, massFractions);
}
}

View File

@@ -1,7 +1,10 @@
required_headers = [
'fourdst/composition/atomicSpecies.h',
'fourdst/composition/species.h',
'fourdst/composition/composition.h'
'fourdst/composition/composition.h',
'fourdst/composition/utils.h',
'fourdst/composition/composition_abstract.h',
'fourdst/composition/exceptions/exceptions_composition.h'
]
foreach h : required_headers
@@ -18,6 +21,7 @@ message('✅ libcomposition species_weight dependency declared')
composition_sources = files(
'lib/composition.cpp',
'lib/utils.cpp',
)
@@ -44,12 +48,21 @@ composition_dep = declare_dependency(
# Make headers accessible
composition_headers = files(
'include/fourdst/composition/composition.h',
'include/fourdst/composition/atomicSpecies.h',
'include/fourdst/composition/species.h',
'include/fourdst/composition/elements.h'
)
install_headers(composition_headers, subdir : 'fourdst/fourdst/composition')
composition_headers_utils = files(
'include/fourdst/composition/utils.h',
)
install_headers(composition_headers_utils, subdir : 'fourdst/fourdst/composition/utils')
composition_headers_atomic = files(
'include/fourdst/atomic/atomicSpecies.h',
'include/fourdst/atomic/elements.h',
'include/fourdst/atomic/species.h',
)
install_headers(composition_headers_atomic, subdir : 'fourdst/fourdst/atomic')
composition_exception_headers = files(
'include/fourdst/composition/exceptions/exceptions_composition.h',
)

View File

@@ -3,14 +3,14 @@
#include <string>
#include <algorithm>
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/composition/species.h"
#include "fourdst/atomic/atomicSpecies.h"
#include "fourdst/atomic/species.h"
#include "fourdst/composition/composition.h"
#include "fourdst/composition/exceptions/exceptions_composition.h"
#include "fourdst/composition/utils.h"
#include "fourdst/config/config.h"
std::string EXAMPLE_FILENAME = std::string(getenv("MESON_SOURCE_ROOT")) + "/tests/config/example.yaml";
/**
* @brief Test suite for the Composition class and related data structures.
@@ -92,7 +92,6 @@ TEST_F(compositionTest, isotopeSpin) {
* - The state of the constructed object or the correctness of its methods.
*/
TEST_F(compositionTest, constructor) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
EXPECT_NO_THROW(fourdst::composition::Composition comp);
}
@@ -108,12 +107,11 @@ TEST_F(compositionTest, constructor) {
* - The handling of mode conflicts (e.g., trying to register a symbol in number fraction mode when the composition is already in mass fraction mode).
*/
TEST_F(compositionTest, registerSymbol) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::composition::Composition comp;
EXPECT_NO_THROW(comp.registerSymbol("H-1"));
EXPECT_NO_THROW(comp.registerSymbol("He-4"));
EXPECT_THROW(comp.registerSymbol("H-19"), fourdst::composition::exceptions::InvalidSymbolError);
EXPECT_THROW(comp.registerSymbol("He-21"), fourdst::composition::exceptions::InvalidSymbolError);
EXPECT_THROW(comp.registerSymbol("H-19"), fourdst::composition::exceptions::UnknownSymbolError);
EXPECT_THROW(comp.registerSymbol("He-21"), fourdst::composition::exceptions::UnknownSymbolError);
std::set<std::string> registeredSymbols = comp.getRegisteredSymbols();
EXPECT_TRUE(registeredSymbols.contains("H-1"));
@@ -136,276 +134,29 @@ TEST_F(compositionTest, registerSymbol) {
* - The correctness of number fraction mode, which is tested separately.
*/
TEST_F(compositionTest, setGetComposition) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
// fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::composition::Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
EXPECT_NO_THROW(comp.registerSymbol("H-1"));
EXPECT_NO_THROW(comp.registerSymbol("He-4"));
EXPECT_DOUBLE_EQ(comp.setMassFraction("H-1", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setMassFraction("He-4", 0.5), 0.0);
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.setMolarAbundance("H-1", 0.6));
EXPECT_NO_THROW(comp.setMolarAbundance("He-4", 0.4));
EXPECT_NO_THROW(static_cast<void>(comp.finalize()));
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.6);
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.27414655751871775);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.7258534424812823);
EXPECT_THROW(comp.setMassFraction("He-3", 0.3), fourdst::composition::exceptions::UnregisteredSymbolError);
EXPECT_THROW(comp.setMolarAbundance("He-3", 0.3), fourdst::composition::exceptions::UnregisteredSymbolError);
const std::vector<std::string> symbols = {"H-1", "He-4"};
EXPECT_NO_THROW(comp.registerSymbol("C-12"));
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.27414655751871775);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.7258534424812823);
EXPECT_NO_THROW(comp.setMassFraction(symbols, {0.5, 0.5}));
EXPECT_THROW(auto r = comp.getComposition("H-1"), fourdst::composition::exceptions::CompositionNotFinalizedError);
EXPECT_TRUE(comp.finalize());
EXPECT_DOUBLE_EQ(comp.getComposition("H-1").first.mass_fraction(), 0.5);
EXPECT_NO_THROW(comp.setMolarAbundance("C-12", 0.1));
EXPECT_NO_THROW(comp.setMassFraction(symbols, {0.6, 0.6}));
EXPECT_FALSE(comp.finalize());
EXPECT_THROW(auto r = comp.getComposition("H-1"), fourdst::composition::exceptions::CompositionNotFinalizedError);
}
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.177551918933757);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.4701013674717613);
EXPECT_DOUBLE_EQ(comp.getMassFraction("C-12"), 0.3523467135944818);
/**
* @brief Tests the workflow of setting and getting number fractions.
* @details This test mirrors `setGetComposition` but for number fraction mode. It verifies
* that symbols can be registered in number fraction mode and that `setNumberFraction` and
* `getNumberFraction` work as expected.
* @par What this test proves:
* - The composition can be correctly initialized and operated in number fraction mode.
* - `setNumberFraction` and `getNumberFraction` function correctly.
* - An attempt to set a fraction for an unregistered symbol throws the correct exception.
* @par What this test does not prove:
* - The correctness of conversions between mass and number fraction modes.
*/
TEST_F(compositionTest, setGetNumberFraction) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::composition::Composition comp;
comp.registerSymbol("H-1", false);
comp.registerSymbol("He-4", false);
EXPECT_DOUBLE_EQ(comp.setNumberFraction("H-1", 0.5), 0.0);
EXPECT_DOUBLE_EQ(comp.setNumberFraction("He-4", 0.5), 0.0);
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(static_cast<void>(comp.finalize()));
EXPECT_DOUBLE_EQ(comp.getNumberFraction("H-1"), 0.6);
EXPECT_THROW(comp.setNumberFraction("He-3", 0.3), fourdst::composition::exceptions::UnregisteredSymbolError);
}
/**
* @brief Tests the creation of a normalized subset of a composition.
* @details This test creates a composition, finalizes it, and then extracts a subset
* containing only one of the original elements. It verifies that the `subset` method with
* the "norm" option creates a new, valid composition where the single element's mass
* fraction is normalized to 1.0.
* @par What this test proves:
* - The `subset` method can extract a subset of symbols.
* - The "norm" method correctly renormalizes the fractions in the new subset to sum to 1.0.
* @par What this test does not prove:
* - The behavior of the `subset` method with the "none" option.
*/
TEST_F(compositionTest, subset) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::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(static_cast<void>(comp.finalize()));
std::vector<std::string> symbols = {"H-1"};
fourdst::composition::Composition subsetComp = comp.subset(symbols, "norm");
EXPECT_TRUE(subsetComp.finalize());
EXPECT_DOUBLE_EQ(subsetComp.getMassFraction("H-1"), 1.0);
}
/**
* @brief Tests the auto-normalization feature of the `finalize` method.
* @details This test sets mass fractions that do not sum to 1.0 and then calls
* `finalize(true)`. It verifies that the composition is successfully finalized and that
* the mass fractions are correctly scaled to sum to 1.0.
* @par What this test proves:
* - `finalize(true)` correctly calculates the sum of fractions and normalizes each entry.
* - The resulting composition is valid and its normalized values can be retrieved.
* @par What this test does not prove:
* - The behavior of `finalize(false)`, which is tested separately.
*/
TEST_F(compositionTest, finalizeWithNormalization) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::composition::Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.3);
comp.setMassFraction("He-4", 0.3);
EXPECT_TRUE(comp.finalize(true));
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.5);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.5);
}
/**
* @brief Tests the default (non-normalizing) behavior of the `finalize` method.
* @details This test sets mass fractions that already sum to 1.0 and calls `finalize(false)`.
* It verifies that the composition is successfully finalized and the fractions remain unchanged.
* @par What this test proves:
* - `finalize(false)` or `finalize()` correctly validates a pre-normalized composition without altering its values.
* @par What this test does not prove:
* - That `finalize(false)` would fail for a non-normalized composition (this is implicitly tested in `setGetComposition`).
*/
TEST_F(compositionTest, finalizeWithoutNormalization) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::composition::Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.5);
comp.setMassFraction("He-4", 0.5);
EXPECT_TRUE(comp.finalize(false));
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.5);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.5);
}
/**
* @brief Tests the retrieval of global composition properties.
* @details After creating and finalizing a composition, this test retrieves the
* `CompositionEntry` and `GlobalComposition` data. It verifies that the mass fraction
* in the entry and the calculated global properties (mean particle mass, specific number density)
* are correct for the given input composition.
* @par What this test proves:
* - The `finalize` method correctly computes `meanParticleMass` and `specificNumberDensity`.
* - The `getComposition` method returns a pair containing the correct entry-level and global data.
* @par What this test does not prove:
* - The correctness of these calculations for all possible compositions, particularly complex ones. It validates the mechanism for a simple binary mixture.
*/
TEST_F(compositionTest, getComposition) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::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(static_cast<void>(comp.finalize()));
const auto compositionEntry = comp.getComposition("H-1");
EXPECT_DOUBLE_EQ(compositionEntry.first.mass_fraction(), 0.6);
EXPECT_DOUBLE_EQ(compositionEntry.second.meanParticleMass, 1.4382769310381101);
EXPECT_DOUBLE_EQ(compositionEntry.second.specificNumberDensity, 1.0/1.4382769310381101);
}
/**
* @brief Tests the ability to switch between mass and number fraction modes.
* @details This test creates a composition in mass fraction mode, finalizes it, and then
* switches to number fraction mode using `setCompositionMode(false)`. It then modifies the
* composition using number fractions and verifies that it must be re-finalized before switching back.
* @par What this test proves:
* - `setCompositionMode` can be called on a finalized composition.
* - After switching modes, the appropriate `set...Fraction` method can be used.
* - Switching modes requires the composition to be finalized, and modifying it after the switch un-finalizes it again.
* @par What this test does not prove:
* - The numerical correctness of the fraction conversions that happen internally when the mode is switched.
*/
TEST_F(compositionTest, setCompositionMode) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::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(static_cast<void>(comp.finalize()));
EXPECT_DOUBLE_EQ(comp.getMassFraction("H-1"), 0.6);
EXPECT_DOUBLE_EQ(comp.getMassFraction("He-4"), 0.4);
EXPECT_NO_THROW(comp.setCompositionMode(false));
EXPECT_NO_THROW(comp.setNumberFraction("H-1", 0.9));
EXPECT_NO_THROW(comp.setNumberFraction("He-4", 0.1));
EXPECT_THROW(comp.setCompositionMode(true), fourdst::composition::exceptions::CompositionNotFinalizedError);
EXPECT_NO_THROW(static_cast<void>(comp.finalize()));
EXPECT_NO_THROW(comp.setCompositionMode(true));
}
/**
* @brief Tests the `hasSymbol` utility method.
* @details This test verifies that `hasSymbol` correctly reports the presence of registered
* symbols and the absence of non-registered symbols.
* @par What this test proves:
* - The `hasSymbol` method accurately checks for the existence of a key in the internal composition map.
* @par What this test does not prove:
* - Anything about the state (e.g., mass fraction) of the symbol, only its presence.
*/
TEST_F(compositionTest, hasSymbol) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::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(static_cast<void>(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"));
}
/**
* @brief Tests the mixing of two compositions.
* @details This test creates two distinct compositions, finalizes them, and then mixes them
* using both the `+` operator (50/50 mix) and the `mix` method with a specific fraction (25/75).
* It verifies that the resulting mass fractions in the new compositions are correct.
* @par What this test proves:
* - The `mix` method and the `+` operator correctly perform linear interpolation of mass fractions.
* - The resulting mixed composition is valid and its properties are correctly calculated.
* @par What this test does not prove:
* - The behavior when mixing compositions with non-overlapping sets of symbols.
*/
TEST_F(compositionTest, mix) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::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(static_cast<void>(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(static_cast<void>(comp2.finalize()));
fourdst::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);
fourdst::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);
}
/**
* @brief Tests the calculation of molar abundance.
* @details This test creates a simple composition and verifies that `getMolarAbundance`
* returns the correct value, which is defined as (mass fraction / atomic mass).
* @par What this test proves:
* - The `getMolarAbundance` calculation is performed correctly.
* @par What this test does not prove:
* - The correctness of the underlying mass data, which is tested separately.
*/
TEST_F(compositionTest, molarAbundance) {
fourdst::composition::Composition comp1;
comp1.registerSymbol("H-1");
comp1.registerSymbol("He-4");
comp1.setMassFraction("H-1", 0.5);
comp1.setMassFraction("He-4", 0.5);
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());
}
/**
@@ -421,12 +172,12 @@ TEST_F(compositionTest, molarAbundance) {
*/
TEST_F(compositionTest, getRegisteredSpecies) {
fourdst::composition::Composition comp;
comp.registerSpecies({fourdst::atomic::Be_7, fourdst::atomic::H_1, fourdst::atomic::He_4}, true);
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));
auto it1 = registeredSpecies.begin();
const auto it1 = registeredSpecies.begin();
EXPECT_EQ(*it1, fourdst::atomic::H_1);
}
@@ -444,236 +195,6 @@ TEST_F(compositionTest, getSpeciesFromAZ) {
EXPECT_EQ(fourdst::atomic::SpeciesErrorType::ELEMENT_SYMBOL_NOT_FOUND, fourdst::atomic::az_to_species(120, 500).error());
}
/**
* @brief Tests constructors that take vectors and sets of symbols.
* @details This test verifies that the Composition can be constructed from a vector or set of symbols,
* and that the resulting object correctly registers those symbols.
* @par What this test proves:
* - Constructors accepting std::vector and std::set of symbols work as expected.
* - Registered symbols are correctly tracked.
*/
TEST_F(compositionTest, constructorWithSymbolsVectorAndSet) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
std::vector<std::string> vs = {"H-1", "He-4"};
std::set<std::string> ss = {"H-1", "He-4"};
fourdst::composition::Composition compVec(vs);
EXPECT_TRUE(compVec.hasSymbol("H-1"));
EXPECT_TRUE(compVec.hasSymbol("He-4"));
fourdst::composition::Composition compSet(ss);
EXPECT_TRUE(compSet.hasSymbol("H-1"));
EXPECT_TRUE(compSet.hasSymbol("He-4"));
}
/**
* @brief Tests constructors that take symbols and fractions for both mass and number fraction modes.
* @details This test verifies that the Composition can be constructed directly from vectors of symbols and fractions,
* and that the resulting mass and number fractions are correct. It also checks conversions between modes.
* @par What this test proves:
* - Constructors for both mass and number fraction modes work as expected.
* - Conversion between mass and number fraction modes is correct.
*/
TEST_F(compositionTest, constructorWithSymbolsAndFractionsMassAndNumber) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
using fourdst::atomic::species;
// Mass-fraction constructor
std::vector<std::string> symM = {"H-1", "He-4"};
std::vector<double> fracM = {0.6, 0.4};
Composition compM(symM, fracM, true);
EXPECT_NEAR(compM.getMassFraction("H-1"), 0.6, 1e-12);
EXPECT_NEAR(compM.getMassFraction("He-4"), 0.4, 1e-12);
// Mean particle mass and specific number density are reciprocals
double sn = 0.6/species.at("H-1").mass() + 0.4/species.at("He-4").mass();
double mp = 1.0/sn;
EXPECT_NEAR(compM.getMeanParticleMass(), mp, 1e-12);
// Number-fraction constructor
std::vector<std::string> symN = {"H-1", "He-4"};
std::vector<double> fracN = {0.9, 0.1};
Composition compN(symN, fracN, false);
EXPECT_NEAR(compN.getNumberFraction("H-1"), 0.9, 1e-12);
EXPECT_NEAR(compN.getNumberFraction("He-4"), 0.1, 1e-12);
double meanA = 0.9*species.at("H-1").mass() + 0.1*species.at("He-4").mass();
EXPECT_NEAR(compN.getMeanParticleMass(), meanA, 1e-12);
// Check converted mass fractions X_i = n_i * A_i / <A>
double xH = 0.9*species.at("H-1").mass()/meanA;
double xHe = 0.1*species.at("He-4").mass()/meanA;
compN.setCompositionMode(true);
EXPECT_NEAR(compN.getMassFraction("H-1"), xH, 1e-12);
EXPECT_NEAR(compN.getMassFraction("He-4"), xHe, 1e-12);
}
/**
* @brief Tests registering symbols via vector, single Species, and mode mismatch error.
* @details This test checks that symbols can be registered via a vector, that registering by Species works,
* and that attempting to register a symbol with a mismatched mode throws the correct exception.
* @par What this test proves:
* - registerSymbol works with vectors.
* - registerSpecies works with single Species.
* - Mode mismatch throws CompositionModeError.
*/
TEST_F(compositionTest, registerSymbolVectorAndSingleSpeciesAndModeMismatch) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
Composition comp;
comp.registerSymbol(std::vector<std::string>{"H-1", "He-4"});
EXPECT_TRUE(comp.hasSymbol("H-1"));
EXPECT_TRUE(comp.hasSymbol("He-4"));
// Register by Species
Composition comp2;
comp2.registerSpecies(fourdst::atomic::H_1);
comp2.registerSpecies(fourdst::atomic::He_4);
EXPECT_TRUE(comp2.hasSymbol("H-1"));
EXPECT_TRUE(comp2.hasSymbol("He-4"));
// Mode mismatch should throw
Composition comp3;
comp3.registerSymbol("H-1", true); // mass mode
EXPECT_THROW(comp3.registerSymbol("He-4", false), fourdst::composition::exceptions::CompositionModeError);
}
/**
* @brief Tests setMassFraction overloads for Species and vectors.
* @details This test verifies that setMassFraction works for both single Species and vectors of Species,
* and that the returned old values are correct.
* @par What this test proves:
* - setMassFraction overloads work as expected.
* - Old values are returned correctly.
*/
TEST_F(compositionTest, setMassFractionBySpeciesAndVector) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
using fourdst::atomic::H_1;
using fourdst::atomic::He_4;
Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
// Single species overload
double old = comp.setMassFraction(H_1, 0.7);
EXPECT_NEAR(old, 0.0, 1e-15);
old = comp.setMassFraction(He_4, 0.3);
EXPECT_NEAR(old, 0.0, 1e-15);
// Vector overload
std::vector<fourdst::atomic::Species> sp = {H_1, He_4};
std::vector<double> xs = {0.6, 0.4};
auto olds = comp.setMassFraction(sp, xs);
ASSERT_EQ(olds.size(), 2u);
EXPECT_NEAR(olds[0], 0.7, 1e-12);
EXPECT_NEAR(olds[1], 0.3, 1e-12);
EXPECT_TRUE(comp.finalize());
EXPECT_NEAR(comp.getMassFraction("H-1"), 0.6, 1e-12);
EXPECT_NEAR(comp.getMassFraction(He_4), 0.4, 1e-12);
}
/**
* @brief Tests setNumberFraction overloads for symbols and Species.
* @details This test verifies that setNumberFraction works for both single symbols/Species and vectors,
* and that the returned old values are correct.
* @par What this test proves:
* - setNumberFraction overloads work as expected.
* - Old values are returned correctly.
*/
TEST_F(compositionTest, setNumberFractionOverloads) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
using fourdst::atomic::H_1;
using fourdst::atomic::He_4;
Composition comp;
comp.registerSymbol("H-1", false);
comp.registerSymbol("He-4", false);
// Single symbol
double old = comp.setNumberFraction("H-1", 0.8);
EXPECT_NEAR(old, 0.0, 1e-15);
// Vector of symbols
auto oldv = comp.setNumberFraction(std::vector<std::string>{"H-1", "He-4"}, std::vector<double>{0.75, 0.25});
ASSERT_EQ(oldv.size(), 2u);
EXPECT_NEAR(oldv[0], 0.8, 1e-12);
EXPECT_NEAR(oldv[1], 0.0, 1e-12);
// Species and vector<Species>
old = comp.setNumberFraction(H_1, 0.7);
EXPECT_NEAR(old, 0.75, 1e-12);
auto oldsv = comp.setNumberFraction(std::vector<fourdst::atomic::Species>{H_1, He_4}, std::vector<double>{0.6, 0.4});
ASSERT_EQ(oldsv.size(), 2u);
EXPECT_NEAR(oldsv[0], 0.7, 1e-12);
EXPECT_NEAR(oldsv[1], 0.25, 1e-12);
EXPECT_TRUE(comp.finalize());
EXPECT_NEAR(comp.getNumberFraction("H-1"), 0.6, 1e-12);
EXPECT_NEAR(comp.getNumberFraction(He_4), 0.4, 1e-12);
}
/**
* @brief Tests error cases for mixing compositions.
* @details This test checks that mixing with a non-finalized composition or with invalid fractions throws the correct exceptions.
* @par What this test proves:
* - Mixing with a non-finalized composition throws CompositionNotFinalizedError.
* - Mixing with invalid fractions throws InvalidCompositionError.
*/
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);
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<void>(a.mix(b, 0.5)), fourdst::composition::exceptions::CompositionNotFinalizedError);
bool didFinalizeB = b.finalize();
EXPECT_TRUE(didFinalizeB);
// Invalid fraction
EXPECT_THROW(static_cast<void>(a.mix(b, -0.1)), fourdst::composition::exceptions::InvalidCompositionError);
EXPECT_THROW(static_cast<void>(a.mix(b, 1.1)), fourdst::composition::exceptions::InvalidCompositionError);
}
/**
* @brief Tests getMassFraction and getNumberFraction maps and Species overloads.
* @details This test verifies that the getter methods for mass and number fractions return correct maps,
* and that overloads for Species work as expected.
* @par What this test proves:
* - getMassFraction and getNumberFraction return correct maps.
* - Overloads for Species work as expected.
*/
TEST_F(compositionTest, getMassAndNumberFractionMapsAndSpeciesOverloads) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
Composition comp; comp.registerSymbol("H-1"); comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4);
ASSERT_TRUE(comp.finalize());
auto m = comp.getMassFraction();
ASSERT_EQ(m.size(), 2u);
EXPECT_NEAR(m.at("H-1"), 0.6, 1e-12);
EXPECT_NEAR(m.at("He-4"), 0.4, 1e-12);
EXPECT_NEAR(comp.getMassFraction(fourdst::atomic::H_1), 0.6, 1e-12);
EXPECT_NEAR(comp.getMolarAbundance(fourdst::atomic::H_1), m.at("H-1")/fourdst::atomic::H_1.mass(), 1e-12);
// Switch to number-fraction mode and verify number maps
comp.setCompositionMode(false);
// Must re-finalize after modifications (mode switch itself keeps values consistent but not finalized status changed? setCompositionMode requires to be finalized; here we just switched modes)
// Set specific number fractions and finalize
comp.setNumberFraction("H-1", 0.7);
comp.setNumberFraction("He-4", 0.3);
ASSERT_TRUE(comp.finalize());
auto n = comp.getNumberFraction();
ASSERT_EQ(n.size(), 2u);
EXPECT_NEAR(n.at("H-1"), 0.7, 1e-12);
EXPECT_NEAR(n.at("He-4"), 0.3, 1e-12);
EXPECT_NEAR(comp.getNumberFraction(fourdst::atomic::He_4), 0.3, 1e-12);
}
/**
* @brief Tests mean atomic number and electron abundance calculations.
@@ -682,255 +203,40 @@ TEST_F(compositionTest, getMassAndNumberFractionMapsAndSpeciesOverloads) {
* @par What this test proves:
* - getElectronAbundance and getMeanAtomicNumber are calculated correctly.
*/
TEST_F(compositionTest, meanAtomicNumberAndElectronAbundance) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
TEST_F(compositionTest, meanElectronAbundance) {
using fourdst::atomic::species;
using fourdst::composition::Composition;
Composition comp; comp.registerSymbol("H-1"); comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4);
ASSERT_TRUE(comp.finalize());
Composition comp;
comp.registerSymbol("H-1");
comp.registerSymbol("He-4");
// Compute expected Ye = sum(X_i * Z_i / A_i)
constexpr double xH = 0.6, xHe = 0.4;
const double aH = species.at("H-1").a();
const double aHe = species.at("He-4").a();
const double zH = species.at("H-1").z();
const double zHe = species.at("He-4").z();
const double expectedYe = xH*zH/aH + xHe*zHe/aHe;
comp.setMolarAbundance("H-1", 0.6);
comp.setMolarAbundance("He-4", 0.4);
const double expectedYe = 0.6 * species.at("H-1").z() + 0.4 * species.at("He-4").z();
EXPECT_NEAR(comp.getElectronAbundance(), expectedYe, 1e-12);
// <Z> = <A> * sum(X_i * Z_i / A_i)
const double expectedZ = comp.getMeanParticleMass() * expectedYe;
EXPECT_NEAR(comp.getMeanAtomicNumber(), expectedZ, 1e-12);
}
/**
* @brief Tests canonical composition and caching behavior.
* @details This test verifies that getCanonicalComposition returns correct X, Y, Z values,
* and that repeated calls use the cached result.
* @par What this test proves:
* - getCanonicalComposition returns correct canonical values.
* - Caching works as expected.
*/
TEST_F(compositionTest, canonicalCompositionAndCaching) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
TEST_F(compositionTest, buildFromMassFractions) {
using fourdst::atomic::Species;
using namespace fourdst::atomic;
using fourdst::composition::Composition;
Composition comp; comp.registerSymbol("H-1"); comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4);
ASSERT_TRUE(comp.finalize());
const std::vector<Species> sVec = {H_1, He_4, C_12};
const std::vector<double> massFractions = {0.7, 0.28, 0.02};
auto canon1 = comp.getCanonicalComposition();
EXPECT_NEAR(canon1.X, 0.6, 1e-12);
EXPECT_NEAR(canon1.Y, 0.4, 1e-12);
EXPECT_NEAR(canon1.Z, 0.0, 1e-12);
const Composition comp = fourdst::composition::buildCompositionFromMassFractions(sVec, massFractions);
// Call again to exercise caching code path
auto canon2 = comp.getCanonicalComposition();
EXPECT_NEAR(canon2.X, 0.6, 1e-12);
EXPECT_NEAR(canon2.Y, 0.4, 1e-12);
EXPECT_NEAR(canon2.Z, 0.0, 1e-12);
EXPECT_DOUBLE_EQ(comp.getMassFraction(H_1), 0.7);
EXPECT_DOUBLE_EQ(comp.getMassFraction(He_4), 0.28);
EXPECT_DOUBLE_EQ(comp.getMassFraction(C_12), 0.02);
// Add a metal and re-check
Composition comp2; comp2.registerSymbol("H-1"); comp2.registerSymbol("He-4"); comp2.registerSymbol("O-16");
comp2.setMassFraction("H-1", 0.6); comp2.setMassFraction("He-4", 0.35); comp2.setMassFraction("O-16", 0.05);
ASSERT_TRUE(comp2.finalize());
auto canon3 = comp2.getCanonicalComposition(true);
EXPECT_NEAR(canon3.X, 0.6, 1e-12);
EXPECT_NEAR(canon3.Y, 0.35, 1e-12);
EXPECT_NEAR(canon3.Z, 0.05, 1e-12);
}
/**
* @brief Tests vector getters, indexing, and species-at-index functionality.
* @details This test verifies that the vector getters for mass, number, and molar abundance fractions
* return correct values, and that species can be accessed by index.
* @par What this test proves:
* - Vector getters return correct values.
* - Indexing and species-at-index work as expected.
*/
TEST_F(compositionTest, vectorsAndIndexingAndSpeciesAtIndex) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
using fourdst::atomic::species;
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());
// Mass fraction vector sorted by mass: H-1, He-4, O-16
auto mv = comp.getMassFractionVector();
ASSERT_EQ(mv.size(), 3u);
EXPECT_NEAR(mv[0], 0.5, 1e-12);
EXPECT_NEAR(mv[1], 0.3, 1e-12);
EXPECT_NEAR(mv[2], 0.2, 1e-12);
// Species indices ordering
size_t iH = comp.getSpeciesIndex("H-1");
size_t iHe = comp.getSpeciesIndex("He-4");
size_t iO = comp.getSpeciesIndex("O-16");
EXPECT_LT(iH, iHe);
EXPECT_LT(iHe, iO);
EXPECT_EQ(comp.getSpeciesIndex(fourdst::atomic::H_1), iH);
EXPECT_EQ(comp.getSpeciesIndex(species.at("He-4")), iHe);
// Species at index
auto sAtHe = comp.getSpeciesAtIndex(iHe);
EXPECT_EQ(std::string(sAtHe.name()), std::string("He-4"));
// Number fraction vector after switching modes
comp.setCompositionMode(false);
// Tweak number fractions and finalize
// Compute expected number fractions from original mass fractions first
double denom = 0.5/species.at("H-1").mass() + 0.3/species.at("He-4").mass() + 0.2/species.at("O-16").mass();
double nH_exp = (0.5/species.at("H-1").mass())/denom;
double nHe_exp = (0.3/species.at("He-4").mass())/denom;
double nO_exp = (0.2/species.at("O-16").mass())/denom;
auto nv0 = comp.getNumberFractionVector();
ASSERT_EQ(nv0.size(), 3u);
EXPECT_NEAR(nv0[iH], nH_exp, 1e-12);
EXPECT_NEAR(nv0[iHe], nHe_exp, 1e-12);
EXPECT_NEAR(nv0[iO], nO_exp, 1e-12);
// Molar abundance vector X_i/A_i in mass mode; switch back to mass mode to verify
comp.setCompositionMode(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);
EXPECT_NEAR(av[iHe], 0.3/species.at("He-4").mass(), 1e-12);
EXPECT_NEAR(av[iO], 0.2/species.at("O-16").mass(), 1e-12);
}
/**
* @brief Tests contains method and pre-finalization guards.
* @details This test verifies that contains throws before finalization and works correctly after.
* @par What this test proves:
* - contains throws before finalize.
* - contains works as expected after finalize.
*/
TEST_F(compositionTest, containsAndPreFinalizationGuards) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
fourdst::composition::Composition comp;
comp.registerSymbol("H-1"); comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4);
// contains should throw before finalize
EXPECT_THROW(static_cast<void>(comp.contains(fourdst::atomic::H_1)), fourdst::composition::exceptions::CompositionNotFinalizedError);
ASSERT_TRUE(comp.finalize());
EXPECT_TRUE(comp.contains(fourdst::atomic::H_1));
EXPECT_FALSE(comp.contains(fourdst::atomic::Li_6));
}
/**
* @brief Tests subset method with "none" normalization and normalization flow.
* @details This test verifies that subset with "none" does not normalize by default,
* and that normalization can be forced with finalize(true).
* @par What this test proves:
* - subset with "none" does not normalize by default.
* - finalize(true) normalizes the subset.
*/
TEST_F(compositionTest, subsetNoneMethodAndNormalizationFlow) {
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());
fourdst::composition::Composition sub = comp.subset(std::vector<std::string>{"H-1", "He-4"}, "none");
// Not normalized: finalize without normalization should fail
EXPECT_FALSE(sub.finalize(false));
// With normalization, it should succeed and scale to sum to 1
EXPECT_TRUE(sub.finalize(true));
double sum = sub.getMassFraction("H-1") + sub.getMassFraction("He-4");
EXPECT_NEAR(sum, 1.0, 1e-12);
EXPECT_NEAR(sub.getMassFraction("H-1"), 0.5/(0.5+0.3), 1e-12);
EXPECT_NEAR(sub.getMassFraction("He-4"), 0.3/(0.5+0.3), 1e-12);
}
/**
* @brief Tests copy constructor and assignment operator for independence.
* @details This test verifies that copies of a Composition object are independent of the original.
* @par What this test proves:
* - Copy constructor and assignment operator create independent objects.
*/
TEST_F(compositionTest, copyConstructorAndAssignmentIndependence) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
Composition comp; comp.registerSymbol("H-1"); comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4);
ASSERT_TRUE(comp.finalize());
Composition copy(comp); // copy ctor
EXPECT_NEAR(copy.getMassFraction("H-1"), 0.6, 1e-12);
EXPECT_NEAR(copy.getMassFraction("He-4"), 0.4, 1e-12);
Composition assigned; assigned = comp; // assignment
EXPECT_NEAR(assigned.getMassFraction("H-1"), 0.6, 1e-12);
EXPECT_NEAR(assigned.getMassFraction("He-4"), 0.4, 1e-12);
// Modify original and ensure copies do not change
comp.setMassFraction("H-1", 0.7); comp.setMassFraction("He-4", 0.3); ASSERT_TRUE(comp.finalize());
EXPECT_NEAR(copy.getMassFraction("H-1"), 0.6, 1e-12);
EXPECT_NEAR(assigned.getMassFraction("He-4"), 0.4, 1e-12);
}
/**
* @brief Tests getComposition by Species and retrieval of all entries.
* @details This test verifies that getComposition works for both single Species and all entries,
* and that the returned values are correct.
* @par What this test proves:
* - getComposition works for both single Species and all entries.
*/
TEST_F(compositionTest, getCompositionBySpeciesAndAllEntries) {
fourdst::config::Config::getInstance().loadConfig(EXAMPLE_FILENAME);
using fourdst::composition::Composition;
Composition comp; comp.registerSymbol("H-1"); comp.registerSymbol("He-4");
comp.setMassFraction("H-1", 0.6); comp.setMassFraction("He-4", 0.4);
ASSERT_TRUE(comp.finalize());
auto pairBySpec = comp.getComposition(fourdst::atomic::H_1);
EXPECT_NEAR(pairBySpec.first.mass_fraction(), 0.6, 1e-12);
EXPECT_NEAR(pairBySpec.second.meanParticleMass, comp.getMeanParticleMass(), 1e-15);
auto all = comp.getComposition();
ASSERT_EQ(all.first.size(), 2u);
EXPECT_NEAR(all.first.at("H-1").mass_fraction(), 0.6, 1e-12);
EXPECT_NEAR(all.first.at("He-4").mass_fraction(), 0.4, 1e-12);
EXPECT_NEAR(all.second.meanParticleMass, comp.getMeanParticleMass(), 1e-15);
}
/**
* @brief Tests iteration over composition and out-of-range index access.
* @details This test verifies that begin/end iteration covers all entries,
* and that accessing an out-of-range index throws an exception.
* @par What this test proves:
* - Iteration covers all entries.
* - Out-of-range index throws std::out_of_range.
*/
TEST_F(compositionTest, iterationBeginEndAndIndexOutOfRange) {
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());
// Iterate and count entries
size_t count = 0;
for (auto it = comp.begin(); it != comp.end(); ++it) {
count++;
}
EXPECT_EQ(count, 3u);
// Out-of-range access
EXPECT_THROW(static_cast<void>(comp.getSpeciesAtIndex(100)), std::out_of_range);
}
/**
* @brief Tests inheritance from CompositionAbstract and overriding a getter.
@@ -960,15 +266,12 @@ TEST_F(compositionTest, abstractBase) {
}
};
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());
comp.setMolarAbundance("H-1", 0.6); comp.setMolarAbundance("He-4", 0.6);
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"));
}