docs(fourdst::composition-and-fourdst::atomic): update documentation

This commit is contained in:
2025-11-08 06:42:41 -05:00
parent a790d21df9
commit 7a303b8d3a
9 changed files with 787 additions and 278 deletions

View File

@@ -2570,7 +2570,7 @@ HIDE_UNDOC_RELATIONS = YES
# set to NO
# The default value is: NO.
HAVE_DOT = NO
HAVE_DOT = YES
# The DOT_NUM_THREADS specifies the number of dot invocations Doxygen is allowed
# to run in parallel. When set to 0 Doxygen will base this on the number of
@@ -2798,7 +2798,7 @@ DIR_GRAPH_MAX_DEPTH = 1
# The default value is: png.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_IMAGE_FORMAT = png
DOT_IMAGE_FORMAT = svg
# If DOT_IMAGE_FORMAT is set to svg or svg:svg or svg:svg:core, then this option
# can be set to YES to enable generation of interactive SVG images that allow
@@ -2812,7 +2812,7 @@ DOT_IMAGE_FORMAT = png
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
INTERACTIVE_SVG = NO
INTERACTIVE_SVG = YES
# The DOT_PATH tag can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.

View File

@@ -0,0 +1,120 @@
/**
* @file atomic.dox
* @brief Documentation for the fourdst::atomic namespace
*/
/**
* @namespace fourdst::atomic
* @brief Contains canonical information about atomic species and elements used by 4D-STAR.
*
* @details
* The `fourdst::atomic` namespace provides a comprehensive, compile-time-accessible
* database of predefined atomic isotopes (Species) and helper utilities for working
* with them. Species are available as strongly-typed constants following the pattern
* `fourdst::atomic::H_1`, `fourdst::atomic::He_4`, `fourdst::atomic::C_12`, etc.
*
* Each atomic Species object encapsulates detailed metadata about the isotope, including:
* - Atomic number (Z)
* - Mass number (A)
* - Neutron number (N)
* - Element symbol (e.g., "H" for Hydrogen)
* - Species symbol (e.g., "H-1" for Hydrogen-1)
* - Atomic mass (in atomic mass units)
* - Uncertainty in atomic mass (in atomic mass units)
* - Half-life (seconds; a value of -1 or another sentinel indicates stability)
* - Binding energy (in keV)
* - Beta decay code (classification code from the atomic database)
* - Beta decay energy (in keV)
* - Spin parity string (e.g., "1/2+")
* - Decay modes as a descriptive string
* - Nuclear spin
*
* Species instances are intended to be lightweight, immutable value objects providing
* easy accessors for these properties. They are the canonical representation of an isotope
* throughout the codebase and are safe to pass and store by value.
*
* Species lookup
* ----------------
* The namespace exposes a map lookup structure `fourdst::atomic::species` which
* maps the human-readable species symbol (e.g. "C-12") to the corresponding
* `fourdst::atomic::Species` object. This is useful for APIs that accept string
* symbols at runtime and need to resolve them to the strongly-typed species value.
*
* Comparison semantics
* --------------------
* Comparison operators for `Species` are overloaded to provide sensible ordering and
* equality semantics:
* - Equality (`==`) and inequality (`!=`) evaluate true/false based on the species symbol
* — two species are equal only if they share the same species symbol (same isotope).
* - Relational operators (`<`, `>`, `<=`, `>=`) are defined based on atomic mass. This
* provides a natural ordering from lightest to heaviest isotope which is convenient
* when creating sorted containers or deterministic vector representations.
*
* Stream output
* -------------
* The stream insertion operator (`operator<<`) is overloaded for Species objects and
* prints the species symbol (for example "C-12") to output streams. This makes logging
* and test output concise and human-readable.
*
* Typical usage
* -------------
* Example: Using predefined species directly
* @code
* #include "fourdst/atomic/species.h"
*
* using namespace fourdst::atomic;
*
* Species s = H_1; // Hydrogen-1 species object
* std::cout << s << std::endl; // prints "H-1"
*
* int Z = s.getAtomicNumber(); // atomic number (1 for hydrogen)
* double mass = s.getAtomicMass();
* bool isStable = (s.getHalfLife() < 0);
* @endcode
*
* Example: Resolving a runtime symbol to a Species
* @code
* #include "fourdst/atomic/species.h"
*
* std::string sym = "C-12";
* auto sp = fourdst::atomic::species.at(sym); // throws if sym not found
* // now sp is the canonical Species for Carbon-12
* @endcode
*
* Example: Using Species in sorted containers and compositions
* @code
* #include "fourdst/composition/composition.h"
*
* using namespace fourdst::atomic;
* using namespace fourdst::composition;
*
* Composition comp;
* comp.registerSpecies(H_1);
* comp.registerSpecies(He_4);
* comp.registerSpecies(C_12);
*
* // Iteration and vector representations rely on species ordering by mass
* for (const auto &sp : comp.getRegisteredSpecies()) {
* std::cout << sp << "\n";
* }
* @endcode
*
* Notes and recommendations
* -------------------------
* - The species map (`fourdst::atomic::species`) is the canonical runtime symbol-to-species
* translation. Code that must accept user-provided symbols should use this map and handle
* potential exceptions (e.g. `std::out_of_range` or a library-specific exception) when a
* symbol is unknown.
* - Because comparison operators are mass-based, using ordered containers (std::set, std::map)
* with Species will order entries from lightest to heaviest by default.
* - The printed representation of a Species is the species symbol only. For more verbose
* output (mass, half-life, binding energy), use the individual accessors.
* - Floating-point values (e.g., atomic mass) are provided as doubles. When comparing such
* values in tests, use an appropriate tolerance.
*
* See also
* --------
* - `fourdst/atomic/species.h` — definitions of Species constants and the `species` map.
* - `fourdst/composition/composition.h` — Composition API which consumes Species objects.
*/
namespace fourdst::atomic;

View File

@@ -0,0 +1,126 @@
/**
* @file composition.dox
* @brief High-level documentation for the fourdst::composition namespace.
*
* This file provides namespace-level Doxygen documentation for the composition module.
* It summarizes the public API (classes, utilities, and exceptions) and shows small
* examples demonstrating typical usage patterns.
*/
/**
* @namespace fourdst::composition
* @brief Utilities and types for representing and manipulating chemical compositions.
*
* The composition module provides a small, but expressive, API for constructing and
* querying material compositions used throughout the 4D-STAR codebase. A Composition
* represents a collection of atomic species together with their molar abundances. From
* these molar abundances the module can compute derived quantities such as mass
* fractions, number fractions, canonical (X, Y, Z) composition, mean particle mass,
* and the electron abundance (Y_e).
*
* Key concepts:
* - Species and Symbols: Atomic isotopes are represented by the strongly-typed
* fourdst::atomic::Species values (see `fourdst/atomic/species.h`). Each species
* also has a human-readable string symbol (e.g. "H-1", "He-4") used by some
* constructors and convenience overloads.
* - Molar abundances: The Composition API accepts and stores molar abundances
* (absolute mole counts). Many derived quantities (mass fraction, number
* fraction, mean particle mass) are computed from these molar abundances.
* - Canonical composition: A CanonicalComposition (X, Y, Z) is provided which
* groups mass fractions into hydrogen (X), helium (Y), and metals (Z). A
* lightweight struct `CanonicalComposition` holds these values and provides an
* ostream operator for easy logging and testing.
* - Caching: The concrete Composition implementation caches computed vectors and
* scalars to avoid repeated work. The cache is invalidated automatically when
* molar abundances or registered species are changed.
*
* Main types and functions
* ------------------------
* - Composition: The primary concrete class for building and interrogating
* compositions. It implements the CompositionAbstract interface and exposes
* methods to register symbols/species, set molar abundances, and query all
* commonly-needed derived quantities. Multiple constructors are provided for
* convenience (from vectors/sets of symbols or species, with optional
* molar-abundance initialization).
*
* Important member functions include:
* - registerSymbol / registerSpecies (single or many overloads)
* - setMolarAbundance (many overloads accepting symbols or species)
* - getMolarAbundance, getMassFraction, getNumberFraction (symbol and species overloads)
* - getMassFractionVector, getNumberFractionVector, getMolarAbundanceVector
* - getMeanParticleMass, getElectronAbundance
* - getCanonicalComposition
* - Iteration support (begin/end) which iterates species from lightest to heaviest
* because species ordering is defined by atomic mass.
*
* - CompositionAbstract: A compact abstract interface implemented by Composition
* which guarantees the presence of all getter/query methods. This allows other
* components to accept composition-like objects without depending on the concrete
* implementation.
*
* - Utilities (fourdst::composition::buildCompositionFromMassFractions):
* Convenience helpers exist to construct a Composition from mass fractions
* (instead of molar abundances). Those helpers validate that the provided mass
* fractions sum to unity within a tight tolerance and convert them into the
* corresponding molar abundances before returning a populated Composition.
*
* - Exceptions (namespace fourdst::composition::exceptions):
* The module defines a small hierarchy of exceptions for error handling:
* - CompositionError: Base class for composition-related errors.
* - InvalidCompositionError: Thrown when the composition is inconsistent or
* when mass fractions fail validation.
* - UnregisteredSymbolError: Thrown when an operation requires a symbol that
* hasn't been registered on the Composition object.
* - UnknownSymbolError: Thrown when a provided string symbol does not map to
* any known atomic species in the atomic species database.
*
* Usage examples
* --------------
* Example 1 basic construction and queries:
* @code
* #include "fourdst/composition/composition.h"
*
* using namespace fourdst::composition;
*
* Composition comp;
* comp.registerSymbol("H-1");
* comp.registerSymbol("He-4");
* comp.setMolarAbundance("H-1", 1.0);
* comp.setMolarAbundance("He-4", 0.5);
*
* double X_h1 = comp.getMassFraction("H-1");
* double meanA = comp.getMeanParticleMass();
* CanonicalComposition canon = comp.getCanonicalComposition();
* std::cout << canon << std::endl; // prints X, Y, Z
* @endcode
*
* Example 2 constructing from mass fractions:
* @code
* #include "fourdst/composition/utils.h"
*
* std::vector<std::string> symbols = {"H-1", "He-4", "C-12"};
* std::vector<double> massFractions = {0.70, 0.28, 0.02};
* Composition comp = buildCompositionFromMassFractions(symbols, massFractions);
* @endcode
*
* Notes and remarks
* -----------------
* - Molar abundances are the canonical input for the Composition class. When
* passing mass fractions, use the `buildCompositionFromMassFractions` helper
* which performs the safe conversion and validation.
* - Many methods throw exceptions from the `fourdst::composition::exceptions`
* namespace on invalid usage (unknown symbols, unregistered species, or
* invalid abundance values). Callers should catch and handle these where
* appropriate.
* - Floating point results (mass/number fractions, mean particle mass, Y_e)
* are computed as doubles and may have small numerical round-off. Callers
* comparing values in tests should use an appropriate tolerance.
*
* See also
* --------
* - fourdst/atomic/species.h — canonical atomic species definitions and symbols.
* - fourdst/composition/composition.h — concrete Composition implementation.
* - fourdst/composition/composition_abstract.h — abstract composition interface.
* - fourdst/composition/utils.h — helpers for constructing compositions from mass fractions.
*/
namespace fourdst::composition;

View File

@@ -65,43 +65,44 @@ namespace fourdst::composition {
* @class Composition
* @brief Manages a collection of chemical species and their abundances.
* @details This class is a primary interface for defining and manipulating material compositions.
* It can operate in two modes: mass fraction or number fraction.
* In order to use the Composition class a user must first register symbols or species. Symbols are
* the string representation of a species (i.e. deuterium would be "H-2" whereas Beryllium 7 would
* be "Be-7") and then set the molar abundances. Species are the data structure
* fourdst::atomic::Species version. Here Deuterium would be represented by the Species
* fourdst::atomic::H_2 whereas Beryllium 7 would be fourdst::atomic::Be_7. Once the symbols/species have been registered
* the user can then set molar abundances.
*
* **Key Rules and Workflow:**
* 1. **Registration:** Before setting an abundance for a species, its symbol (e.g., "He-4") must be registered using `registerSymbol()` or `registerSpecies()`. All registered species must conform to the same abundance mode (mass or number fraction).
* 2. **Setting Abundances:** Use `setMassFraction()` or `setNumberFraction()` to define the composition.
* 3. **Finalization:** Before querying any compositional data (e.g., `getMassFraction()`, `getMeanParticleMass()`), the composition must be **finalized** by calling `finalize()`. This step validates the composition (abundances sum to ~1.0) and computes global properties.
* 4. **Modification:** Any modification to abundances after finalization will un-finalize the composition, requiring another call to `finalize()` before data can be retrieved again.
* 5. **Construction:** A pre-finalized composition can be created by providing symbols and valid, normalized abundances to the constructor.
* Once the Composition object has been populated the user can query mass fractions, number fractions, electron
* abundances, mean particle mass, molar abundance, and Canonical (X, Y, Z) composition.
*
* @throws This class throws various exceptions from `fourdst::composition::exceptions` for invalid operations, such as using unregistered symbols, providing invalid abundances, or accessing data from a non-finalized composition.
* @note This class only accepts molar abundances as input. If you
* wish to construct a Composition using a vector of mass fractions, those must first be converted
* to molar abundances. There is a helper function `fourdst::composition::buildCompositionFromMassFractions` which
* wll facilitate just that.
*
* @par Mass Fraction Example:
*
* @throws This class throws various exceptions from `fourdst::composition::exceptions` for invalid operations, such as using unregistered symbols or providing invalid abundances.
*
* @par Basic Example:
* @code
* Composition comp;
* comp.registerSymbol("H-1");
* comp.registerSymbol("He-4");
* comp.setMassFraction("H-1", 0.75);
* comp.setMassFraction("He-4", 0.25);
* if (comp.finalize()) {
* double he_mass_frac = comp.getMassFraction("He-4"); // Returns 0.25
* }
* comp.setMolarAbundance("H-1", 0.75);
* comp.setMolarAbundance("He-4", 0.25); // Note Molar Abundances do not need to sum to 1
* @endcode
*
* @par Number Fraction Example:
* @code
* Composition comp;
* comp.registerSymbol("H-1", false); // Register in number fraction mode
* comp.registerSymbol("He-4", false);
* comp.setNumberFraction("H-1", 0.9);
* comp.setNumberFraction("He-4", 0.1);
* if (comp.finalize()) {
* double he_num_frac = comp.getNumberFraction("He-4"); // Returns 0.1
* }
* @endcode
*/
// ReSharper disable once CppClassCanBeFinal
class Composition : public CompositionAbstract {
private:
/**
* @struct CompositionCache
* @brief Caches computed properties of the composition to avoid redundant calculations.
* @details This struct holds optional cached values for various computed properties of the composition,
* such as canonical composition, mass fractions, number fractions, molar abundances, sorted species,
* sorted symbols, and electron abundance. The cache can be cleared when the composition is modified.
*/
struct CompositionCache {
std::optional<CanonicalComposition> canonicalComp; ///< Cached canonical composition data.
std::optional<std::vector<double>> massFractions; ///< Cached vector of mass fractions.
@@ -111,6 +112,9 @@ namespace fourdst::composition {
std::optional<std::vector<std::string>> sortedSymbols; ///< Cached vector of sorted species (by mass).
std::optional<double> Ye; ///< Cached electron abundance.
/**
* @brief Clears all cached values.
*/
void clear() {
canonicalComp = std::nullopt;
massFractions = std::nullopt;
@@ -121,6 +125,10 @@ namespace fourdst::composition {
Ye = std::nullopt;
}
/**
* @brief Checks if the cache is clear (i.e., all cached values are empty).
* @return True if the cache is clear, false otherwise.
*/
[[nodiscard]] bool is_clear() const {
return !canonicalComp.has_value() && !massFractions.has_value() &&
!numberFractions.has_value() && !molarAbundances.has_value() && !sortedSymbols.has_value() &&
@@ -128,20 +136,26 @@ namespace fourdst::composition {
}
};
private:
// logging::LogManager& m_logManager = logging::LogManager::getInstance();
/**
* @brief Gets the logger instance for the Composition class. This is static to ensure that all composition
* objects share the same logger instance.
* @return pointer to the logger instance.
*/
static quill::Logger* getLogger() {
static quill::Logger* logger = logging::LogManager::getInstance().getLogger("log");
return logger;
}
std::set<atomic::Species> m_registeredSpecies;
std::map<atomic::Species, double> m_molarAbundances;
std::set<atomic::Species> m_registeredSpecies; ///< Set of registered species in the composition.
std::map<atomic::Species, double> m_molarAbundances; ///< Map of species to their molar abundances.
mutable CompositionCache m_cache; ///< Cache for computed properties to avoid redundant calculations.
public:
/**
* @brief Default constructor.
* @details Creates an empty Composition object. No symbols or species are registered initially; however,
* the user can register symbols or species later using the provided methods.
*/
Composition() = default;
@@ -151,27 +165,37 @@ namespace fourdst::composition {
~Composition() override = default;
/**
* @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.
* @throws exceptions::InvalidSymbolError if any symbol is invalid.
* @par Usage Example:
* @brief Constructs a Composition and registers the given symbols from a vector.
* @param symbols The symbols to register.
* @throws exceptions::UnknownSymbolError if any symbol is invalid. Symbols are invalid if they are not registered at compile time in the atomic species database (`fourdst/atomic/species.h`).
* @par Example:
* @code
* std::vector<std::string> symbols = {"H-1", "O-16"};
* Composition comp(symbols);
* comp.setMassFraction("H-1", 0.11);
* comp.setMassFraction("O-16", 0.89);
* comp.finalize();
* @endcode
*/
explicit Composition(const std::vector<std::string>& symbols);
/**
* @brief Constructs a Composition and registers the given species from a vector.
* @param species The species to register.
* @par Example:
* @code
* std::vector<fourdst::atomic::Species> species = {fourdst::atomic::H_1, fourdst::atomic::O_16};
* Composition comp(species);
* @endcode
*
* @note Because species are strongly typed, this constructor does not need to check if the species is valid.
* that is to say that the compiler will only allow valid species to be passed in. Therefore, this constructor
* is marked noexcept and may therefore be slightly more performant than the symbol-based constructor.
*/
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.
* @throws exceptions::InvalidSymbolError if any symbol is invalid.
* @par Usage Example:
* @param symbols The symbols to register.
* @throws exceptions::UnknownSymbolError if any symbol is invalid. Symbols are invalid if they are not registered at compile time in the atomic species database (`fourdst/atomic/species.h`).
* @par Example:
* @code
* std::set<std::string> symbols = {"H-1", "O-16"};
* Composition comp(symbols);
@@ -179,28 +203,69 @@ namespace fourdst::composition {
*/
explicit Composition(const std::set<std::string>& symbols);
/**
* @brief Constructs a Composition and registers the given species from a set.
* @param species The species to register.
* @par Example:
* @code
* std::set<fourdst::atomic::Species> species = {fourdst::atomic::H_1, fourdst::atomic::O_16};
* Composition comp(species);
* @endcode
*
* @note Because species are strongly typed, this constructor does not need to check if the species is valid.
* that is to say that the compiler will only allow valid species to be passed in. Therefore, this constructor
* is marked noexcept and may therefore be slightly more performant than the symbol-based constructor.
*/
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.
* @brief Constructs a Composition from symbols and their corresponding molar abundances.
* @param symbols The symbols to register.
* @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.
* @par Usage Example:
* @throws exceptions::UnknownSymbolError if any symbol is invalid. Symbols are invalid if they are not registered at compile time in the atomic species database (`fourdst/atomic/species.h`).
* @throws exceptions::InvalidCompositionError if the number of symbols does not match the number of molar abundances.
* @par Example:
* @code
* std::vector<std::string> symbols = {"H-1", "O-16"};
* std::vector<double> mass_fractions = {0.1119, 0.8881};
* Composition comp(symbols, mass_fractions); // Finalized on construction
* std::vector<double> molarAbundances = {1.03, 0.6};
* Composition comp(symbols, molarAbundances);
* @endcode
*
* @note Molar abundances do not need to sum to 1.0, they are an absolute quantity.
*/
Composition(const std::vector<std::string>& symbols, const std::vector<double>& molarAbundances);
/**
* @brief Constructs a Composition from species and their corresponding molar abundances.
* @param species The species to register.
* @param molarAbundances The corresponding molar abundances for each species.
* @throws exceptions::InvalidCompositionError if the number of species does not match the number of molar abundances.
* @par Example:
* @code
* std::vector<fourdst::atomic::Species> species = {fourdst::atomic::H_1, fourdst::atomic::O_16};
* std::vector<double> molarAbundances = {1.03, 0.6};
* Composition comp(species, molarAbundances);
* @endcode
*
* @note Molar abundances do not need to sum to 1.0, they are an absolute quantity.
*/
Composition(const std::vector<atomic::Species>& species, const std::vector<double>& molarAbundances);
/**
* @brief Constructs a Composition from symbols in a set and their corresponding molar abundances.
* @param symbols The symbols to register.
* @param molarAbundances The corresponding molar abundances for each symbol.
* @throws exceptions::UnknownSymbolError if any symbol is invalid. Symbols are invalid if they are not registered at compile time in the atomic species database (`fourdst/atomic/species.h`).
* @throws exceptions::InvalidCompositionError if the number of symbols does not match the number of molar abundances.
* @par Example:
* @code
* std::set<std::string> symbols = {"H-1", "O-16"};
* std::vector<double> molarAbundances = {1.03, 0.6};
* Composition comp(symbols, molarAbundances);
* @endcode
*
* @note Molar abundances do not need to sum to 1.0, they are an absolute quantity.
*/
Composition(const std::set<std::string>& symbols, const std::vector<double>& molarAbundances);
/**
@@ -218,101 +283,231 @@ 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.
* @details A symbol must be registered before its abundance can be set.
* @param symbol The symbol to register (e.g., "Fe-56").
* @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:
* @throws exceptions::UnknownSymbolError if the symbol is not in the atomic species database.
* @par Example:
* @code
* Composition comp;
* comp.registerSymbol("H-1"); // Now in mass fraction mode
* comp.registerSymbol("He-4"); // Must also be mass fraction mode
* comp.registerSymbol("H-1");
* comp.registerSymbol("He-4");
* @endcode
*
* @note upon registering a symbol, its molar abundance is initialized to 0.0.
*/
void registerSymbol(const std::string& symbol);
/**
* @brief Registers multiple new symbols.
* @param symbols The symbols to register.
* @throws exceptions::InvalidSymbolError if any symbol is invalid.
* @throws exceptions::CompositionModeError if the mode conflicts with an already set mode.
* @par Usage Example:
* @throws exceptions::UnknownSymbolError if any symbol is invalid.
* @par Example:
* @code
* std::vector<std::string> symbols = {"H-1", "O-16"};
* Composition comp;
* comp.registerSymbol(symbols);
* @endcode
*
* @note upon registering a symbol, its molar abundance is initialized to 0.0. Therefore, registering a vector
* of symbols will initialize all their molar abundances to 0.0.
*/
void registerSymbol(const std::vector<std::string>& symbols);
/**
* @brief Registers a new species by extracting its symbol.
* @param species The species to register.
* @throws exceptions::InvalidSymbolError if the species' symbol is invalid.
* @throws exceptions::CompositionModeError if the mode conflicts.
* @par Usage Example:
* @par Example:
* @code
* #include "fourdst/composition/species.h" // Assuming species like H1 are defined here
* #include "fourdst/composition/species.h"
*
* Composition comp;
* comp.registerSpecies(fourdst::atomic::species.at("H-1"));
* comp.registerSpecies(fourdst::atomic::C_12);
* @endcode
*
* @note Because species are strongly typed, this method does not need to check if the species is valid.
* that is to say that the compiler will only allow valid species to be passed in. Therefore, this method
* is marked noexcept and may therefore be slightly more performant than the symbol-based method.
*
* @note upon registering a species, its molar abundance is initialized to 0.0.
*
* @note All species are in the `fourdst/atomic/species.h` header file. These can be accessed directly through
* their fully qualified names (e.g., `fourdst::atomic::C_12` for Carbon-12). Alternatively, these can also
* be accessed through a string-indexed map located in `fourdst/atomic/species.h` called `fourdst::atomic::species` (
* e.g., `fourdst::atomic::species.at("C-12")` for Carbon-12).
*/
void registerSpecies(const atomic::Species& species);
void registerSpecies(const atomic::Species& species) noexcept;
/**
* @brief Registers a vector of new species.
* @param species The vector of species to register.
* @throws exceptions::InvalidSymbolError if any species' symbol is invalid.
* @throws exceptions::CompositionModeError if the mode conflicts.
* @par Usage Example:
* @par Example:
* @code
* #include "fourdst/composition/species.h"
* Composition comp;
* std::vector<fourdst::atomic::Species> my_species = { ... };
* comp.registerSpecies(my_species, false); // Number fraction mode
* comp.registerSpecies(my_species);
* @endcode
*
* @note upon registering a species, its molar abundance is initialized to 0.0. Therefore, registering a vector
* of species will initialize all their molar abundances to 0.0.
*
* @note All species are in the `fourdst/atomic/species.h` header file. These can be accessed directly through
* their fully qualified names (e.g., `fourdst::atomic::C_12` for Carbon-12). Alternatively, these can also
* be accessed through a string-indexed map located in `fourdst/atomic/species.h` called `fourdst::atomic::species` (
* e.g., `fourdst::atomic::species.at("C-12")` for Carbon-12).
*/
void registerSpecies(const std::vector<atomic::Species>& species);
void registerSpecies(const std::vector<atomic::Species>& species) noexcept;
/**
* @brief Checks if a given isotope is present in the composition.
* @pre The composition must be finalized.
* @brief Checks if a given species is present in the composition.
* @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.
* @return True if the species is in the composition, false otherwise.
*/
[[nodiscard]] bool contains(const atomic::Species& species) const override;
[[nodiscard]] bool contains(const atomic::Species& species) const noexcept override;
/**
* @brief Checks if a given symbol is present in the composition.
* @param symbol The symbol to check for.
* @return True if the symbol is in the composition, false otherwise.
* @throws exceptions::UnknownSymbolError if the symbol is not in the atomic species database.
*/
[[nodiscard]] bool contains(const std::string& symbol) const override;
[[nodiscard]] size_t size() const override;
/**
* @brief Gets the number of registered species in the composition.
* @return The number of registered species.
*/
[[nodiscard]] size_t size() const noexcept override;
/**
* @brief Sets the molar abundance for a given symbol.
* @param symbol The symbol to set the molar abundance for.
* @param molar_abundance The molar abundance to set.
*
* @throws exceptions::UnknownSymbolError if the symbol is not in the atomic species database.
* @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition.
* @throws exceptions::InvalidAbundanceError if the molar abundance is negative.
*
* @par Example:
* @code
* Composition comp({"H-1", "He-4"});
* comp.setMolarAbundance("H-1", 1.0);
* comp.setMolarAbundance("He-4", 0.5);
* @endcode
*/
void setMolarAbundance(
const std::string& symbol,
const double& molar_abundance
);
/**
* @brief Sets the molar abundance for a given isotope.
* @param species The isotope to set the molar abundance for.
* @param molar_abundance The molar abundance to set.
*
* @throws exceptions::UnregisteredSymbolError if the isotope is not registered in the composition.
* @throws exceptions::InvalidAbundanceError if the molar abundance is negative.
*
* @par Example:
* @code
* #include "fourdst/composition/species.h"
* Composition comp({fourdst::atomic::H_1, fourdst::atomic::He_4});
* comp.setMolarAbundance(fourdst::atomic::H_1, 1.0);
* comp.setMolarAbundance(fourdst::atomic::He_4, 0.5);
* @endcode
*
* @note Since this method does not need to validate the species exists in the database, it will generally be
* slightly more performant than the symbol-based method.
*/
void setMolarAbundance(
const atomic::Species& species,
const double& molar_abundance
);
/**
* @brief Sets the molar abundances for a list of symbols.
* @param symbols The symbols to set the molar abundances for.
* @param molar_abundances The molar abundances to set.
*
* @throws exceptions::UnknownSymbolError if any symbol is not in the atomic species database.
* @throws exceptions::UnregisteredSymbolError if any symbol is not in the composition.
* @throws exceptions::InvalidAbundanceError if any molar abundance is negative.
*
* @par Example:
* @code
* Composition comp({"H-1", "He-4"});
* comp.setMolarAbundance({"H-1", "He-4"}, {1.0, 0.5});
* @endcode
*/
void setMolarAbundance(
const std::vector<std::string>& symbols,
const std::vector<double>& molar_abundances
);
/**
* @brief Sets the molar abundances for a list of isotopes.
* @param species The isotopes to set the molar abundances for.
* @param molar_abundances The molar abundances to set.
*
* @throws exceptions::UnregisteredSymbolError if any isotope is not registered in the composition.
* @throws exceptions::InvalidAbundanceError if any molar abundance is negative.
*
* @par Example:
* @code
* #include "fourdst/composition/species.h"
* Composition comp({fourdst::atomic::H_1, fourdst::atomic::He_4});
* comp.setMolarAbundance({fourdst::atomic::H_1, fourdst::atomic::He_4}, {1.0, 0.5});
* @endcode
*
* @note Since this method does not need to validate the species exists in the database, it will generally be
* slightly more performant than the symbol-based method.
*/
void setMolarAbundance(
const std::vector<atomic::Species>& species,
const std::vector<double>& molar_abundances
);
/**
* @brief Sets the molar abundances for a set of symbols.
* @param symbols The symbols to set the molar abundances for.
* @param molar_abundances The molar abundances to set.
*
* @throws exceptions::UnknownSymbolError if any symbol is not in the atomic species database.
* @throws exceptions::UnregisteredSymbolError if any symbol is not in the composition.
* @throws exceptions::InvalidAbundanceError if any molar abundance is negative.
*
* @par Example:
* @code
* std::set<std::string> symbols = {"H-1", "He-4"};
* Composition comp(symbols);
* comp.setMolarAbundance(symbols, {1.0, 0.5});
* @endcode
*/
void setMolarAbundance(
const std::set<std::string>& symbols,
const std::vector<double>& molar_abundances
);
/**
* @brief Sets the molar abundances for a set of isotopes.
* @param species The isotopes to set the molar abundances for.
* @param molar_abundances The molar abundances to set.
*
* @throws exceptions::UnregisteredSymbolError if any isotope is not registered in the composition.
* @throws exceptions::InvalidAbundanceError if any molar abundance is negative.
*
* @par Example:
* @code
* #include "fourdst/composition/species.h"
* std::set<fourdst::atomic::Species> species = {fourdst::atomic::H_1, fourdst::atomic::He_4};
* Composition comp(species);
* comp.setMolarAbundance(species, {1.0, 0.5});
* @endcode
*
* @note Since this method does not need to validate the species exists in the database, it will generally be
* slightly more performant than the symbol-based method.
*/
void setMolarAbundance(
const std::set<atomic::Species>& species,
const std::vector<double>& molar_abundances
@@ -321,153 +516,192 @@ namespace fourdst::composition {
/**
* @brief Gets the registered symbols.
* @return A set of registered symbols.
*
* @note This method will construct a new set each time it is called. If you need just need access to the
* registered species, consider using `getRegisteredSpecies()` instead which returns a constant reference
* to the internal set.
*/
[[nodiscard]] std::set<std::string> getRegisteredSymbols() const override;
[[nodiscard]] std::set<std::string> getRegisteredSymbols() const noexcept override;
/**
* @brief Get a set of all species that are registered in the composition.
* @return A set of `atomic::Species` objects registered in the composition.
*
* @note This will return a constant reference to the internal m_registeredSpecies set, therefore the return
* value of this method will only be valid as long as the Composition object is valid (i.e. it cannot
* outlive the Composition object it was called on).
*/
[[nodiscard]] const std::set<atomic::Species> &getRegisteredSpecies() const override;
[[nodiscard]] const std::set<atomic::Species> &getRegisteredSpecies() const noexcept override;
/**
* @brief Gets the mass fractions of all species in the composition.
* @pre The composition must be finalized.
* @return An unordered map of symbols to their mass fractions.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*
* @note This method will construct a new unordered map each time it is called.
*/
[[nodiscard]] std::unordered_map<atomic::Species, double> getMassFraction() const override;
[[nodiscard]] std::unordered_map<atomic::Species, double> getMassFraction() const noexcept override;
/**
* @brief Gets the mass fraction for a given symbol.
* @pre The composition must be finalized.
* @brief Gets the mass fraction for a given symbol. See the overload for species-based lookup for more details
* on how mass fractions are calculated.
* @param symbol The symbol to get the mass fraction for.
* @return The mass fraction for the given symbol.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnknownSymbolError if the symbol is not in the atomic species database.
* @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition.
*/
[[nodiscard]] double getMassFraction(const std::string& symbol) const override;
/**
* @brief Gets the mass fraction for a given isotope.
* @pre The composition must be finalized.
* @param species The isotope to get the mass fraction for.
* @brief Gets the mass fraction for a given species.
* @details The mass fraction X_i for a species is calculated using the formula:
* \f[
* X_i = \frac{(Y_i \cdot A_i)}{\sum_j (Y_j \cdot A_j)}
* \f]
* where:
* - \f$Y_i\f$ is the molar abundance of species i.
* - \f$A_i\f$ is the atomic mass of species i.
* - The denominator sums over all species j in the composition.
*
* This formula ensures that the mass fractions of all species sum to 1.0.
*
* @param species The species to get the mass fraction for.
* @return The mass fraction for the given isotope.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnregisteredSymbolError if the isotope is not registered in the composition.
*/
[[nodiscard]] double getMassFraction(const fourdst::atomic::Species& species) const override;
[[nodiscard]] double getMassFraction(const atomic::Species& species) const override;
/**
* @brief Gets the number fraction for a given symbol.
* @pre The composition must be finalized.
* @brief Gets the number fraction for a given symbol. See the overload for species-based lookup for more details
* on how number fractions are calculated.
* @param symbol The symbol to get the number fraction for.
* @return The number fraction for the given symbol.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnknownSymbolError if the symbol is not in the atomic species database.
* @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition.
*/
[[nodiscard]] double getNumberFraction(const std::string& symbol) const override;
/**
* @brief Gets the number fraction for a given isotope.
* @pre The composition must be finalized.
* @param species The isotope to get the number fraction for.
* @brief Gets the number fraction for a given species.
* @details The number fraction Y_i for a species is calculated using the formula:
* \f[
* X_i = \frac{Y_i}{\sum_j Y_j}
* \f]
* where:
* - \f$Y_i\f$ is the molar abundance of species i.
* - The denominator sums over all species j in the composition.
*
* This formula ensures that the number fractions of all species sum to 1.0.
*
* @param species The species to get the number fraction for.
* @return The number fraction for the given isotope.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnregisteredSymbolError if the isotope is not registered in the composition.
*/
[[nodiscard]] double getNumberFraction(const fourdst::atomic::Species& species) const override;
[[nodiscard]] double getNumberFraction(const atomic::Species& species) const override;
/**
* @brief Gets the number fractions of all species in the composition.
* @pre The composition must be finalized.
* @return An unordered map of symbols to their number fractions.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*
* @note This method will construct a new unordered map each time it is called.
*/
[[nodiscard]] std::unordered_map<atomic::Species, double> getNumberFraction() const override;
[[nodiscard]] std::unordered_map<atomic::Species, double> getNumberFraction() const noexcept override;
/**
* @brief Gets the molar abundance (X_i / A_i) for a given symbol.
* @pre The composition must be finalized.
* @param symbol The symbol to get the molar abundance for.
* @return The molar abundance for the given symbol.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnregisteredSymbolError if the symbol is not in the composition.
*/
* @brief Gets the molar abundances of all species in the composition.
* @throws exceptions::UnknownSymbolError if any symbol is not in the atomic species database.
* @throws exceptions::UnregisteredSymbolError if any symbol is not in the composition.
* @return The molar abundance of the symbol.
*
* @note These are the most performant quantities to retrieve since they are stored directly in the composition object and
* require no computation. This overload is slightly less performant than the species-based overload since it
* needs to validate the symbol exists in the atomic species database.
*/
[[nodiscard]] double getMolarAbundance(const std::string& symbol) const override;
/**
* @brief Gets the molar abundance for a given isotope.
* @pre The composition must be finalized.
* @param species The isotope to get the molar abundance for.
* @brief Gets the molar abundance for a given species.
* @param species The species to get the molar abundance for.
* @return The molar abundance for the given isotope.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnregisteredSymbolError if the isotope is not registered in the composition.
*
* @note These are the most performant quantities to retrieve since they are stored directly in the composition object and
* require no computation.
*/
[[nodiscard]] double getMolarAbundance(const atomic::Species& species) const override;
/**
* @brief Compute the mean particle mass of the composition.
* @pre The composition must be finalized.
* @details The mean particle mass is calculated using the formula:
* \f[
* \bar{A} = \frac{\sum_i (Y_i \cdot A_i)}{\sum_j Y_j}
* \f]
* where:
* - \f$Y_i\f$ is the molar abundance of species i.
* - \f$A_i\f$ is the atomic mass of species i.
* - The sums run over all species i in the composition.
*
* @return Mean particle mass in atomic mass units (g/mol).
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*/
[[nodiscard]] double getMeanParticleMass() const override;
[[nodiscard]] double getMeanParticleMass() const noexcept 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.
* @details The electron abundance is calculated using the formula:
* \f[
* Y_e = \sum_i (Y_i \cdot Z_i)
* \f]
* where:
* - \f$Y_i\f$ is the molar abundance of species i.
* - \f$Z_i\f$ is the atomic number (number of protons) of species i.
*
*
* @return Ye (electron abundance).
* @pre The composition must be finalized.
*/
[[nodiscard]] double getElectronAbundance() const override;
[[nodiscard]] double getElectronAbundance() const noexcept override;
/**
* @brief Gets the current canonical composition (X, Y, Z).
* @details Calculates the total mass fractions for H, He, and metals.
* @pre The composition must be finalized.
* @param harsh If true, this will throw an error if `1 - (X + Y)` is not equal to the directly summed `Z` (within a tolerance). If false, it will only log a warning.
* @return The `CanonicalComposition` struct.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws std::runtime_error if `harsh` is true and the canonical composition is not self-consistent.
* @brief Compute the canonical composition (X, Y, Z) of the composition.
* @details The canonical composition is defined as:
* - X: mass fraction of hydrogen (\f$\sum_{i=1}^{7}X_{^{i}H}\f$)
* - Y: mass fraction of helium (\f$\sum_{i=3}^{10}X_{^{i}He}\f$)
* - Z: mass fraction of all other elements (Everything else)
*
* The canonical composition is computed by summing the mass fractions of all registered species
* in the composition according to their element type.
*
* @return A CanonicalComposition struct containing the X, Y, and Z values.
*
* @throws exceptions::InvalidCompositionError if, after constructing the canonical composition, the sum X + Y + Z is not approximately equal to 1.0 (within a tolerance of 1e-16)
*/
[[nodiscard]] CanonicalComposition getCanonicalComposition(bool harsh=false) const;
[[nodiscard]] CanonicalComposition getCanonicalComposition() const;
/**
* @brief Get a uniform vector representation of the mass fraction stored in the composition object sorted such that the lightest species is at index 0 and the heaviest is at the last index.
* @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained
* @return the vector of mass fractions sorted by species mass (lightest to heaviest).
* @pre The composition must be finalized.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*/
[[nodiscard]] std::vector<double> getMassFractionVector() const override;
[[nodiscard]] std::vector<double> getMassFractionVector() const noexcept override;
/**
* @brief Get a uniform vector representation of the number fractions stored in the composition object sorted such that the lightest species is at index 0 and the heaviest is at the last index.
* @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained
* @return the vector of number fractions sorted by species mass (lightest to heaviest).
* @pre The composition must be finalized.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*/
[[nodiscard]] std::vector<double> getNumberFractionVector() const override;
[[nodiscard]] std::vector<double> getNumberFractionVector() const noexcept override;
/**
* @brief Get a uniform vector representation of the molar abundances stored in the composition object sorted such that the lightest species is at index 0 and the heaviest is at the last index.
* @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained
* @return the vector of molar abundances sorted by species mass (lightest to heaviest).
* @pre The composition must be finalized.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
*/
[[nodiscard]] std::vector<double> getMolarAbundanceVector() const override;
[[nodiscard]] std::vector<double> getMolarAbundanceVector() const noexcept override;
/**
* @brief get the index in the sorted vector representation for a given symbol
* @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained
* @pre The composition must be finalized.
* @pre symbol must be registered in the composition
* @param symbol the symbol to look up the index for. Note that this is the index species data will be at if you were to call getMolarAbundanceVector(), getMassFractionVector(), or getNumberFractionVector()
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnknownSymbolError if the symbol is not in the atomic species database.
* @throws exceptions::UnregisteredSymbolError if the symbol is not registered in the composition
* @return The index of the symbol in the sorted vector representation.
*/
@@ -476,10 +710,7 @@ namespace fourdst::composition {
/**
* @brief get the index in the sorted vector representation for a given symbol
* @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained
* @pre The composition must be finalized.
* @pre symbol must be registered in the composition
* @param species the species to look up the index for. Note that this is the index species data will be at if you were to call getMolarAbundanceVector(), getMassFractionVector(), or getNumberFractionVector()
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws exceptions::UnregisteredSymbolError if the symbol is not registered in the composition
* @return The index of the symbol in the sorted vector representation.
*/
@@ -488,9 +719,7 @@ namespace fourdst::composition {
/**
* @brief Get the species at a given index in the sorted vector representation.
* @details This is primarily useful for external libraries which need to ensure that vector representation uniformity is maintained
* @pre The composition must be finalized.
* @param index The index in the sorted vector representation for which to return the species. Must be in [0, N-1] where N is the number of registered species.
* @throws exceptions::CompositionNotFinalizedError if the composition is not finalized.
* @throws std::out_of_range if the index is out of range.
* @return The species at the given index in the sorted vector representation.
*/
@@ -505,32 +734,84 @@ namespace fourdst::composition {
friend std::ostream& operator<<(std::ostream& os, const Composition& composition);
/**
* @brief Returns an iterator to the beginning of the composition map.
* @brief Returns an iterator to the beginning of the molar abundance map
* @return An iterator to the beginning.
*
* @par Example:
* @code
* Composition comp({"H-1", "He-4", "C-12"}, {1.02, 0.56, 0.02});
*
* for (const auto& [sp, y] : comp) {
* std::cout << "Species: " << sp << ", Molar Abundance: " << y << std::endl;
* }
* @endcode
*
* @note Because we store molar abundances as a sorted map, keyed by species. And Because the < and > operators
* for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be
* seen in order from lightest to heaviest.
*/
auto begin() {
return m_molarAbundances.begin();
}
/**
* @brief Returns a const iterator to the beginning of the composition map.
* @brief Returns a const iterator to the beginning of the molar abundance map.
* @return A const iterator to the beginning.
*
* @par Example:
* @code
* Composition comp({"H-1", "He-4", "C-12"}, {1.02, 0.56, 0.02});
*
* for (const auto& [sp, y] : comp) {
* std::cout << "Species: " << sp << ", Molar Abundance: " << y << std::endl;
* }
* @endcode
*
* @note Because we store molar abundances as a sorted map, keyed by species. And Because the < and > operators
* for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be
* seen in order from lightest to heaviest.
*/
[[nodiscard]] auto begin() const {
return m_molarAbundances.cbegin();
}
/**
* @brief Returns an iterator to the end of the composition map.
* @brief Returns an iterator to the end of the molar abundance map.
* @return An iterator to the end.
*
* @par Example:
* @code
* Composition comp({"H-1", "He-4", "C-12"}, {1.02, 0.56, 0.02});
*
* for (const auto& [sp, y] : comp) {
* std::cout << "Species: " << sp << ", Molar Abundance: " << y << std::endl;
* }
* @endcode
*
* @note Because we store molar abundances as a sorted map, keyed by species. And Because the < and > operators
* for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be
* seen in order from lightest to heaviest.
*/
auto end() {
return m_molarAbundances.end();
}
/**
* @brief Returns a const iterator to the end of the composition map.
* @brief Returns a const iterator to the end of the molar abundance map.
* @return A const iterator to the end.
*
* @par Example:
* @code
* Composition comp({"H-1", "He-4", "C-12"}, {1.02, 0.56, 0.02});
*
* for (const auto& [sp, y] : comp) {
* std::cout << "Species: " << sp << ", Molar Abundance: " << y << std::endl;
* }
* @endcode
*
* @note Because we store molar abundances as a sorted map, keyed by species. And Because the < and > operators
* for species are defined based on their atomic mass. When iterating over the molar abundance map, species will be
* seen in order from lightest to heaviest.
*/
[[nodiscard]] auto end() const {
return m_molarAbundances.cend();

View File

@@ -5,6 +5,7 @@
#include <string>
#include <unordered_map>
#include <set>
#include <vector>
/**
* @brief Abstract base class for chemical composition representations.
@@ -41,7 +42,7 @@ public:
* @param species The atomic species to check.
* @return True if the species is contained, false otherwise.
*/
[[nodiscard]] virtual bool contains(const fourdst::atomic::Species& species) const = 0;
[[nodiscard]] virtual bool contains(const fourdst::atomic::Species& species) const noexcept = 0;
/**
* @brief Check if the composition contains the given species.
@@ -50,31 +51,31 @@ public:
*/
[[nodiscard]] virtual bool contains(const std::string& symbol) const = 0;
[[nodiscard]] virtual size_t size() const = 0;
[[nodiscard]] virtual size_t size() const noexcept = 0;
/**
* @brief Get all registered chemical symbols in the composition.
* @return A set of registered chemical symbols.
*/
[[nodiscard]] virtual std::set<std::string> getRegisteredSymbols() const = 0;
[[nodiscard]] virtual std::set<std::string> getRegisteredSymbols() const noexcept = 0;
/**
* @brief Get all registered atomic species in the composition.
* @return A set of registered atomic species.
*/
[[nodiscard]] virtual const std::set<fourdst::atomic::Species> &getRegisteredSpecies() const = 0;
[[nodiscard]] virtual const std::set<fourdst::atomic::Species> &getRegisteredSpecies() const noexcept = 0;
/**
* @brief Get the mass fraction for all registered symbols.
* @return An unordered map from symbol to mass fraction.
*/
[[nodiscard]] virtual std::unordered_map<fourdst::atomic::Species, double> getMassFraction() const = 0;
[[nodiscard]] virtual std::unordered_map<fourdst::atomic::Species, double> getMassFraction() const noexcept = 0;
/**
* @brief Get the number fraction for all registered symbols.
* @return An unordered map from symbol to number fraction.
*/
[[nodiscard]] virtual std::unordered_map<fourdst::atomic::Species, double> getNumberFraction() const = 0;
[[nodiscard]] virtual std::unordered_map<fourdst::atomic::Species, double> getNumberFraction() const noexcept = 0;
/**
* @brief Get the mass fraction for a given symbol.
@@ -122,31 +123,31 @@ public:
* @brief Get the mean particle mass of the composition.
* @return The mean particle mass.
*/
[[nodiscard]] virtual double getMeanParticleMass() const = 0;
[[nodiscard]] virtual double getMeanParticleMass() const noexcept = 0;
/**
* @brief Get the electron abundance of the composition.
* @return The electron abundance.
*/
[[nodiscard]] virtual double getElectronAbundance() const = 0;
[[nodiscard]] virtual double getElectronAbundance() const noexcept = 0;
/**
* @brief Get the mass fraction as a vector.
* @return A vector of mass fractions for all species.
*/
[[nodiscard]] virtual std::vector<double> getMassFractionVector() const = 0;
[[nodiscard]] virtual std::vector<double> getMassFractionVector() const noexcept = 0;
/**
* @brief Get the number fraction as a vector.
* @return A vector of number fractions for all species.
*/
[[nodiscard]] virtual std::vector<double> getNumberFractionVector() const = 0;
[[nodiscard]] virtual std::vector<double> getNumberFractionVector() const noexcept = 0;
/**
* @brief Get the molar abundance as a vector.
* @return A vector of molar abundances for all species.
*/
[[nodiscard]] virtual std::vector<double> getMolarAbundanceVector() const = 0;
[[nodiscard]] virtual std::vector<double> getMolarAbundanceVector() const noexcept = 0;
/**
* @brief Get the index of a species by symbol.

View File

@@ -34,47 +34,6 @@ namespace fourdst::composition::exceptions {
}
};
/**
* @class CompositionEntryError
* @brief Base class for exceptions related to individual entries within a composition.
*
* This exception is thrown for errors specific to a single component or entry
* in a composition, such as an invalid species symbol or duplicate initialization.
*/
class CompositionEntryError : public std::exception {
protected:
/**
* @brief The error message.
*/
std::string m_message;
public:
/**
* @brief Constructs a CompositionEntryError with an error message.
* @param message The error message.
*/
explicit CompositionEntryError(const std::string& message)
: m_message(std::move(message)) {}
/**
* @brief Returns the error message.
* @return A C-style string containing the error message.
*/
const char* what() const noexcept override {
return m_message.c_str();
}
};
/**
* @class CompositionNotFinalizedError
* @brief Exception thrown when an operation is attempted on a composition that has not been finalized.
*
* Certain operations require the composition to be in a "finalized" state.
* This error indicates that such an operation was called prematurely.
*/
class CompositionNotFinalizedError final : public CompositionError {
using CompositionError::CompositionError;
};
/**
* @class InvalidCompositionError
* @brief Exception thrown when a composition is in an invalid or inconsistent state.
@@ -83,17 +42,6 @@ namespace fourdst::composition::exceptions {
using CompositionError::CompositionError;
};
/**
* @class InvalidMixingMode
* @brief Exception thrown for an invalid or unsupported mixing mode.
*
* Compositions can be defined with different mixing modes (e.g., by mass, by mole).
* This error is thrown if an invalid mode is specified.
*/
class InvalidMixingMode final : public CompositionError {
using CompositionError::CompositionError;
};
/**
* @class UnregisteredSymbolError
* @brief Exception thrown when a symbol is used that has not been registered.
@@ -105,39 +53,9 @@ namespace fourdst::composition::exceptions {
};
/**
* @class FailedToFinalizeCompositionError
* @brief Exception thrown when the finalization process of a composition fails.
* @class SpeciesError
* @brief Base class for exceptions related to atomic species.
*/
class FailedToFinalizeCompositionError final : public CompositionError {
using CompositionError::CompositionError;
};
/**
* @class InvalidSpeciesSymbolError
* @brief Exception thrown for an invalid chemical species symbol in a composition entry.
*/
class InvalidSpeciesSymbolError final : public CompositionEntryError {
using CompositionEntryError::CompositionEntryError;
};
/**
* @class EntryAlreadyInitializedError
* @brief Exception thrown when attempting to initialize a composition entry that has already been initialized.
*/
class EntryAlreadyInitializedError final : public CompositionEntryError {
using CompositionEntryError::CompositionEntryError;
};
/**
* @class CompositionModeError
* @brief Exception thrown due to a conflict in composition modes at the entry level.
*
* This may occur if an entry's configuration is incompatible with the overall composition's mode.
*/
class CompositionModeError final : public CompositionEntryError {
using CompositionEntryError::CompositionEntryError;
};
class SpeciesError : public std::exception {
protected:
std::string m_message;
@@ -150,6 +68,12 @@ namespace fourdst::composition::exceptions {
}
};
/**
* @class UnknownSymbolError
* @brief Exception thrown when an unknown symbol is encountered.
*
* This typically occurs when a symbol does not correspond to any known atomic species.
*/
class UnknownSymbolError final : public SpeciesError {
using SpeciesError::SpeciesError;
};

View File

@@ -6,16 +6,43 @@
#include <vector>
namespace fourdst::composition {
/**
* @brief Build a Composition object from symbols and their corresponding mass fractions.
* @param symbols The symbols to register.
* @param massFractions The corresponding mass fractions for each symbol.
* @return A Composition object constructed from the provided symbols and mass fractions.
* @throws exceptions::UnknownSymbolError if any symbol is invalid. Symbols are invalid if they are not registered at compile time in the atomic species database (`fourdst/atomic/species.h`).
* @throws exceptions::InvalidCompositionError if the provided mass fractions do not sum to within one part in 10^10 of 1.0.
* @throws exceptions::InvalidCompositionError if the number of symbols does not match the number of mass fractions.
*/
Composition buildCompositionFromMassFractions(
const std::vector<std::string>& symbols,
const std::vector<double>& massFractions
);
/**
* @brief Build a Composition object from species and their corresponding mass fractions.
* @param species The species to register.
* @param massFractions The corresponding mass fractions for each species.
* @return A Composition object constructed from the provided species and mass fractions.
* @throws exceptions::InvalidCompositionError if the provided mass fractions do not sum to within one part in 10^10 of 1.0.
* @throws exceptions::InvalidCompositionError if the number of species does not match the number of mass fractions.
*/
Composition buildCompositionFromMassFractions(
const std::vector<atomic::Species>& species,
const std::vector<double>& massFractions
);
/**
* @brief Build a Composition object from species in a set and their corresponding mass fractions.
* @param species The species to register.
* @param massFractions The corresponding mass fractions for each species.
* @return A Composition object constructed from the provided species and mass fractions.
* @throws exceptions::InvalidCompositionError if the provided mass fractions do not sum to within one part in 10^10 of 1.0.
* @throws exceptions::InvalidCompositionError if the number of species does not match the number of mass fractions.
*
* @note This is the version of the function which the other overloads ultimately call.
*/
Composition buildCompositionFromMassFractions(
const std::set<atomic::Species>& species,
const std::vector<double>& massFractions

View File

@@ -196,7 +196,7 @@ namespace fourdst::composition {
void Composition::registerSpecies(
const atomic::Species &species
) {
) noexcept {
m_registeredSpecies.insert(species);
if (!m_molarAbundances.contains(species)) {
m_molarAbundances.emplace(species, 0.0);
@@ -205,13 +205,13 @@ namespace fourdst::composition {
void Composition::registerSpecies(
const std::vector<atomic::Species> &species
) {
) noexcept {
for (const auto& s : species) {
registerSpecies(s);
}
}
std::set<std::string> Composition::getRegisteredSymbols() const {
std::set<std::string> Composition::getRegisteredSymbols() const noexcept {
std::set<std::string> symbols;
for (const auto& species : m_registeredSpecies) {
symbols.insert(std::string(species.name()));
@@ -219,7 +219,7 @@ namespace fourdst::composition {
return symbols;
}
const std::set<atomic::Species> &Composition::getRegisteredSpecies() const {
const std::set<atomic::Species> &Composition::getRegisteredSpecies() const noexcept {
return m_registeredSpecies;
}
@@ -235,6 +235,9 @@ namespace fourdst::composition {
double Composition::getMassFraction(
const atomic::Species &species
) const {
if (!m_molarAbundances.contains(species)) {
throw_unregistered_symbol(getLogger(), std::string(species.name()));
}
std::map<atomic::Species, double> raw_mass;
double totalMass = 0;
for (const auto& [sp, y] : m_molarAbundances) {
@@ -245,7 +248,7 @@ namespace fourdst::composition {
return raw_mass.at(species) / totalMass;
}
std::unordered_map<atomic::Species, double> Composition::getMassFraction() const {
std::unordered_map<atomic::Species, double> Composition::getMassFraction() const noexcept {
std::unordered_map<atomic::Species, double> mass_fractions;
for (const auto &species: m_molarAbundances | std::views::keys) {
mass_fractions.emplace(species, getMassFraction(species));
@@ -257,7 +260,7 @@ namespace fourdst::composition {
double Composition::getNumberFraction(
const std::string& symbol
) const {
auto species = getSpecies(symbol);
const auto species = getSpecies(symbol);
if (!species) {
throw_unknown_symbol(getLogger(), symbol);
}
@@ -267,6 +270,9 @@ namespace fourdst::composition {
double Composition::getNumberFraction(
const atomic::Species &species
) const {
if (!m_molarAbundances.contains(species)) {
throw_unregistered_symbol(getLogger(), std::string(species.name()));
}
double total_moles_per_gram = 0.0;
for (const auto &y: m_molarAbundances | std::views::values) {
total_moles_per_gram += y;
@@ -274,7 +280,7 @@ namespace fourdst::composition {
return m_molarAbundances.at(species) / total_moles_per_gram;
}
std::unordered_map<atomic::Species, double> Composition::getNumberFraction() const {
std::unordered_map<atomic::Species, double> Composition::getNumberFraction() const noexcept {
std::unordered_map<atomic::Species, double> number_fractions;
for (const auto &species: m_molarAbundances | std::views::keys) {
number_fractions.emplace(species, getNumberFraction(species));
@@ -285,7 +291,7 @@ namespace fourdst::composition {
double Composition::getMolarAbundance(
const std::string &symbol
) const {
auto species = getSpecies(symbol);
const auto species = getSpecies(symbol);
if (!species) {
throw_unknown_symbol(getLogger(), symbol);
}
@@ -297,13 +303,12 @@ namespace fourdst::composition {
const atomic::Species &species
) const {
if (!m_molarAbundances.contains(species)) {
LOG_CRITICAL(getLogger(), "Species {} is not registered in the composition.", species.name());
throw exceptions::UnregisteredSymbolError("Species " + std::string(species.name()) + " is not registered in the composition.");
throw_unregistered_symbol(getLogger(), std::string(species.name()));
}
return m_molarAbundances.at(species);
}
double Composition::getMeanParticleMass() const {
double Composition::getMeanParticleMass() const noexcept {
std::vector<double> X = getMassFractionVector();
double sum = 0.0;
for (const auto& [species, x] : std::views::zip(m_registeredSpecies, X)) {
@@ -313,7 +318,7 @@ namespace fourdst::composition {
return 1.0 / sum;
}
double Composition::getElectronAbundance() const {
double Composition::getElectronAbundance() const noexcept {
double Ye = 0.0;
for (const auto& [species, y] : m_molarAbundances) {
Ye += species.z() * y;
@@ -323,7 +328,6 @@ namespace fourdst::composition {
CanonicalComposition Composition::getCanonicalComposition(
const bool harsh
) const {
using namespace fourdst::atomic;
@@ -358,20 +362,15 @@ namespace fourdst::composition {
// ReSharper disable once CppTooWideScopeInitStatement
const double Z = 1.0 - (canonicalComposition.X + canonicalComposition.Y);
if (std::abs(Z - canonicalComposition.Z) > 1e-6) {
if (!harsh) {
LOG_WARNING(getLogger(), "Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He).", Z, canonicalComposition.Z);
}
else {
LOG_ERROR(getLogger(), "Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He).", Z, canonicalComposition.Z);
throw std::runtime_error("Validation composition Z (X-Y = " + std::to_string(Z) + ") is different than canonical composition Z (" + std::to_string(canonicalComposition.Z) + ") (∑a_i where a_i != H/He).");
}
if (std::abs(Z - canonicalComposition.Z) > 1e-16) {
LOG_ERROR(getLogger(), "Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He).", Z, canonicalComposition.Z);
throw exceptions::InvalidCompositionError("Validation composition Z (X-Y = " + std::to_string(Z) + ") is different than canonical composition Z (" + std::to_string(canonicalComposition.Z) + ") (∑a_i where a_i != H/He).");
}
m_cache.canonicalComp = canonicalComposition;
return canonicalComposition;
}
std::vector<double> Composition::getMassFractionVector() const {
std::vector<double> Composition::getMassFractionVector() const noexcept {
if (m_cache.massFractions.has_value()) {
return m_cache.massFractions.value(); // Short circuit if we have cached the mass fractions
}
@@ -393,7 +392,7 @@ namespace fourdst::composition {
}
std::vector<double> Composition::getNumberFractionVector() const {
std::vector<double> Composition::getNumberFractionVector() const noexcept {
if (m_cache.numberFractions.has_value()) {
return m_cache.numberFractions.value(); // Short circuit if we have cached the number fractions
}
@@ -414,7 +413,7 @@ namespace fourdst::composition {
return numberFractions;
}
std::vector<double> Composition::getMolarAbundanceVector() const {
std::vector<double> Composition::getMolarAbundanceVector() const noexcept {
if (m_cache.molarAbundances.has_value()) {
return m_cache.molarAbundances.value(); // Short circuit if we have cached the molar abundances
}
@@ -500,12 +499,16 @@ namespace fourdst::composition {
}
std::vector<atomic::Species> sortedSymbols = sortVectorBy(speciesVector, speciesMass);
if (index >= sortedSymbols.size()) {
LOG_ERROR(getLogger(), "Index {} is out of range for composition of size {}.", index, sortedSymbols.size());
throw std::out_of_range("Index " + std::to_string(index) + " is out of range for composition of size " + std::to_string(sortedSymbols.size()) + ".");
}
return sortedSymbols.at(index);
}
bool Composition::contains(
const atomic::Species &species
) const {
) const noexcept {
return m_registeredSpecies.contains(species);
}
@@ -519,7 +522,7 @@ namespace fourdst::composition {
return contains(species.value());
}
size_t Composition::size() const {
size_t Composition::size() const noexcept {
return m_registeredSpecies.size();
}
@@ -532,11 +535,7 @@ namespace fourdst::composition {
throw_unknown_symbol(getLogger(), symbol);
}
if (!m_registeredSpecies.contains(species.value())) {
throw_unregistered_symbol(getLogger(), symbol);
}
m_molarAbundances.at(species.value()) = molar_abundance;
setMolarAbundance(species.value(), molar_abundance);
}
void Composition::setMolarAbundance(
@@ -546,6 +545,10 @@ namespace fourdst::composition {
if (!m_registeredSpecies.contains(species)) {
throw_unregistered_symbol(getLogger(), std::string(species.name()));
}
if (molar_abundance < 0.0) {
LOG_ERROR(getLogger(), "Molar abundance must be non-negative for symbol {}. Currently it is {}.", species.name(), molar_abundance);
throw exceptions::InvalidCompositionError("Molar abundance must be non-negative, got " + std::to_string(molar_abundance) + " for symbol " + std::string(species.name()) + ".");
}
m_molarAbundances.at(species) = molar_abundance;
}

View File

@@ -3,12 +3,16 @@
#include "fourdst/atomic/atomicSpecies.h"
#include "fourdst/atomic/species.h"
#include "fourdst/composition/utils.h"
#include "fourdst/logging/logging.h"
#include <numeric>
#include <ranges>
#include <vector>
#include <set>
#include <string>
#include "quill/LogMacros.h"
namespace {
std::optional<fourdst::atomic::Species> getSpecies(const std::string& symbol) {
if (!fourdst::atomic::species.contains(symbol)) {
@@ -17,7 +21,13 @@ namespace {
return fourdst::atomic::species.at(symbol);
}
void throw_unknown_symbol(quill::Logger* logger, const std::string& symbol) {
quill::Logger* getLogger() {
static quill::Logger* logger = fourdst::logging::LogManager::getInstance().getLogger("log");
return logger;
}
void throw_unknown_symbol(const std::string& symbol) {
LOG_ERROR(getLogger(), "Symbol {} is not a valid species symbol (not in the species database)", symbol);
throw fourdst::composition::exceptions::UnknownSymbolError("Symbol " + symbol + " is not a valid species symbol (not in the species database)");
}
}
@@ -27,6 +37,26 @@ namespace fourdst::composition {
const std::set<atomic::Species> &species,
const std::vector<double> &massFractions
) {
const double sum = std::accumulate(
massFractions.begin(),
massFractions.end(),
0.0
);
if (std::abs(sum - 1.0) > 1e-10) {
throw exceptions::InvalidCompositionError(
"Mass fractions must sum to 1.0, got " + std::to_string(sum)
);
}
if (species.size() != massFractions.size()) {
throw exceptions::InvalidCompositionError(
"The number of species and mass fractions must be equal. Got " +
std::to_string(species.size()) + " species and " +
std::to_string(massFractions.size()) + " mass fractions."
);
}
Composition composition;
for (const auto& [sp, xi] : std::views::zip(species, massFractions)) {
@@ -46,13 +76,10 @@ namespace fourdst::composition {
for (const auto& symbol : symbols) {
auto result = getSpecies(symbol);
if (!result) {
throw_unknown_symbol(nullptr, symbol);
throw_unknown_symbol(symbol);
}
species.insert(result.value());
}
return buildCompositionFromMassFractions(species, massFractions);
}
}