feat(composition): changed how composition is conmstructed

Composition objects now must be built from vectors of molar abundances.  Things such as mass fraction and number fraction are computed on the fly (with some caching for performance). This will allow for many fewer issues when converting from solver space to composition space.

BREAKING CHANGE: The entire old API has been broken. There is no longer any need to finalize. In fact the entire concept of finalization has been removed. Further, the entire CompositionEntry and GlobalComposition data structure has been removed. Any code written for the old version will no longer work and major reworking will be needed to use the new version.
This commit is contained in:
2025-11-07 15:49:25 -05:00
parent 106f7c1c96
commit 84947a2b12
11 changed files with 519 additions and 1966 deletions

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