From 7a303b8d3acee0dc2139f5c8ee0178742abb0ab4 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sat, 8 Nov 2025 06:42:41 -0500 Subject: [PATCH] docs(fourdst::composition-and-fourdst::atomic): update documentation --- Doxyfile | 6 +- .../include/fourdst/atomic/atomic.dox | 120 ++++ .../fourdst/composition/composition.dox | 126 ++++ .../include/fourdst/composition/composition.h | 569 +++++++++++++----- .../composition/composition_abstract.h | 23 +- .../exceptions/exceptions_composition.h | 92 +-- .../include/fourdst/composition/utils.h | 27 + src/composition/lib/composition.cpp | 65 +- src/composition/lib/utils.cpp | 37 +- 9 files changed, 787 insertions(+), 278 deletions(-) create mode 100644 src/composition/include/fourdst/atomic/atomic.dox create mode 100644 src/composition/include/fourdst/composition/composition.dox diff --git a/Doxyfile b/Doxyfile index 26fe876..e76f4a4 100644 --- a/Doxyfile +++ b/Doxyfile @@ -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. diff --git a/src/composition/include/fourdst/atomic/atomic.dox b/src/composition/include/fourdst/atomic/atomic.dox new file mode 100644 index 0000000..b2600aa --- /dev/null +++ b/src/composition/include/fourdst/atomic/atomic.dox @@ -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; \ No newline at end of file diff --git a/src/composition/include/fourdst/composition/composition.dox b/src/composition/include/fourdst/composition/composition.dox new file mode 100644 index 0000000..fcc9791 --- /dev/null +++ b/src/composition/include/fourdst/composition/composition.dox @@ -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 symbols = {"H-1", "He-4", "C-12"}; + * std::vector 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; \ No newline at end of file diff --git a/src/composition/include/fourdst/composition/composition.h b/src/composition/include/fourdst/composition/composition.h index 7c1c033..0030629 100644 --- a/src/composition/include/fourdst/composition/composition.h +++ b/src/composition/include/fourdst/composition/composition.h @@ -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 canonicalComp; ///< Cached canonical composition data. std::optional> massFractions; ///< Cached vector of mass fractions. @@ -111,6 +112,9 @@ namespace fourdst::composition { std::optional> sortedSymbols; ///< Cached vector of sorted species (by mass). std::optional 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 m_registeredSpecies; - std::map m_molarAbundances; + std::set m_registeredSpecies; ///< Set of registered species in the composition. + std::map 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 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& symbols); + /** + * @brief Constructs a Composition and registers the given species from a vector. + * @param species The species to register. + * @par Example: + * @code + * std::vector 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& 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 symbols = {"H-1", "O-16"}; * Composition comp(symbols); @@ -179,28 +203,69 @@ namespace fourdst::composition { */ explicit Composition(const std::set& symbols); + /** + * @brief Constructs a Composition and registers the given species from a set. + * @param species The species to register. + * @par Example: + * @code + * std::set 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& 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 symbols = {"H-1", "O-16"}; - * std::vector mass_fractions = {0.1119, 0.8881}; - * Composition comp(symbols, mass_fractions); // Finalized on construction + * std::vector 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& symbols, const std::vector& 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 species = {fourdst::atomic::H_1, fourdst::atomic::O_16}; + * std::vector 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& species, const std::vector& 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 symbols = {"H-1", "O-16"}; + * std::vector 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& symbols, const std::vector& 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 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& 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 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& species); + void registerSpecies(const std::vector& 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& symbols, const std::vector& 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& species, const std::vector& 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 symbols = {"H-1", "He-4"}; + * Composition comp(symbols); + * comp.setMolarAbundance(symbols, {1.0, 0.5}); + * @endcode + */ void setMolarAbundance( const std::set& symbols, const std::vector& 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 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& species, const std::vector& 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 getRegisteredSymbols() const override; + [[nodiscard]] std::set 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 &getRegisteredSpecies() const override; + [[nodiscard]] const std::set &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 getMassFraction() const override; + [[nodiscard]] std::unordered_map 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 getNumberFraction() const override; + [[nodiscard]] std::unordered_map 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 getMassFractionVector() const override; + [[nodiscard]] std::vector 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 getNumberFractionVector() const override; + [[nodiscard]] std::vector 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 getMolarAbundanceVector() const override; + [[nodiscard]] std::vector 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(); diff --git a/src/composition/include/fourdst/composition/composition_abstract.h b/src/composition/include/fourdst/composition/composition_abstract.h index 2c1b7cc..9d1df48 100644 --- a/src/composition/include/fourdst/composition/composition_abstract.h +++ b/src/composition/include/fourdst/composition/composition_abstract.h @@ -5,6 +5,7 @@ #include #include #include +#include /** * @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 getRegisteredSymbols() const = 0; + [[nodiscard]] virtual std::set 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 &getRegisteredSpecies() const = 0; + [[nodiscard]] virtual const std::set &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 getMassFraction() const = 0; + [[nodiscard]] virtual std::unordered_map 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 getNumberFraction() const = 0; + [[nodiscard]] virtual std::unordered_map 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 getMassFractionVector() const = 0; + [[nodiscard]] virtual std::vector 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 getNumberFractionVector() const = 0; + [[nodiscard]] virtual std::vector 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 getMolarAbundanceVector() const = 0; + [[nodiscard]] virtual std::vector getMolarAbundanceVector() const noexcept = 0; /** * @brief Get the index of a species by symbol. diff --git a/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h b/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h index c10754e..6e06015 100644 --- a/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h +++ b/src/composition/include/fourdst/composition/exceptions/exceptions_composition.h @@ -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; }; diff --git a/src/composition/include/fourdst/composition/utils.h b/src/composition/include/fourdst/composition/utils.h index 81969bc..9925e40 100644 --- a/src/composition/include/fourdst/composition/utils.h +++ b/src/composition/include/fourdst/composition/utils.h @@ -6,16 +6,43 @@ #include 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& symbols, const std::vector& 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& species, const std::vector& 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& species, const std::vector& massFractions diff --git a/src/composition/lib/composition.cpp b/src/composition/lib/composition.cpp index e3b4857..b1e17fe 100644 --- a/src/composition/lib/composition.cpp +++ b/src/composition/lib/composition.cpp @@ -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 &species - ) { + ) noexcept { for (const auto& s : species) { registerSpecies(s); } } - std::set Composition::getRegisteredSymbols() const { + std::set Composition::getRegisteredSymbols() const noexcept { std::set 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 &Composition::getRegisteredSpecies() const { + const std::set &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 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 Composition::getMassFraction() const { + std::unordered_map Composition::getMassFraction() const noexcept { std::unordered_map 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 Composition::getNumberFraction() const { + std::unordered_map Composition::getNumberFraction() const noexcept { std::unordered_map 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 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 Composition::getMassFractionVector() const { + std::vector 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 Composition::getNumberFractionVector() const { + std::vector 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 Composition::getMolarAbundanceVector() const { + std::vector 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 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; } diff --git a/src/composition/lib/utils.cpp b/src/composition/lib/utils.cpp index 819eda0..291d804 100644 --- a/src/composition/lib/utils.cpp +++ b/src/composition/lib/utils.cpp @@ -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 #include #include #include #include +#include "quill/LogMacros.h" + namespace { std::optional 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 &species, const std::vector &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); } - - - } \ No newline at end of file