feat(python): added robust python bindings covering the entire codebase

This commit is contained in:
2025-07-23 16:26:30 -04:00
parent 6a22cb65b8
commit f20bffc411
134 changed files with 2202 additions and 170 deletions

View File

@@ -0,0 +1,8 @@
#pragma once
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/engine/engine_graph.h"
#include "gridfire/engine/views/engine_views.h"
#include "gridfire/engine/procedures/engine_procedures.h"
#include "gridfire/engine/types/engine_types.h"

View File

@@ -0,0 +1,314 @@
#pragma once
#include "gridfire/reaction/reaction.h"
#include "gridfire/network.h"
#include "gridfire/screening/screening_abstract.h"
#include "gridfire/screening/screening_types.h"
#include "gridfire/engine/types/reporting.h"
#include "gridfire/engine/types/building.h"
#include "gridfire/expectations/expected_engine.h"
#include <vector>
#include <unordered_map>
#include <utility>
#include <expected>
/**
* @file engine_abstract.h
* @brief Abstract interfaces for reaction network engines in GridFire.
*
* This header defines the abstract base classes and concepts for implementing
* reaction network solvers in the GridFire framework. It provides the contract
* for calculating right-hand sides, energy generation, Jacobians, stoichiometry,
* and other core operations required for time integration of nuclear reaction networks.
*
* @author
* Emily M. Boudreaux
*/
namespace gridfire {
/**
* @brief Concept for types allowed in engine calculations.
*
* This concept restricts template parameters to either double or CppAD::AD<double>,
* enabling both standard and automatic differentiation types.
*/
template<typename T>
concept IsArithmeticOrAD = std::is_same_v<T, double> || std::is_same_v<T, CppAD::AD<double>>;
/**
* @brief Structure holding derivatives and energy generation for a network step.
*
* @tparam T Numeric type (double or CppAD::AD<double>).
*
* This struct is used to return both the time derivatives of all species abundances
* and the specific nuclear energy generation rate for a single network evaluation.
*
* Example usage:
* @code
* StepDerivatives<double> result = engine.calculateRHSAndEnergy(Y, T9, rho);
* for (double dydt_i : result.dydt) {
* // Use derivative
* }
* double energyRate = result.nuclearEnergyGenerationRate;
* @endcode
*/
template <IsArithmeticOrAD T>
struct StepDerivatives {
std::vector<T> dydt; ///< Derivatives of abundances (dY/dt for each species).
T nuclearEnergyGenerationRate = T(0.0); ///< Specific energy generation rate (e.g., erg/g/s).
};
using SparsityPattern = std::vector<std::pair<size_t, size_t>>;
/**
* @brief Abstract base class for a reaction network engine.
*
* This class defines the minimal interface for a reaction network engine,
* which is responsible for evaluating the right-hand side (dY/dt) and
* energy generation for a given set of abundances, temperature, and density.
*
* Intended usage: Derive from this class to implement a concrete engine
* for a specific network or integration method.
*
* Example:
* @code
* class MyEngine : public gridfire::Engine {
* // Implement required methods...
* };
* @endcode
*/
class Engine {
public:
/**
* @brief Virtual destructor.
*/
virtual ~Engine() = default;
/**
* @brief Get the list of species in the network.
* @return Vector of Species objects representing all network species.
*/
[[nodiscard]] virtual const std::vector<fourdst::atomic::Species>& getNetworkSpecies() const = 0;
/**
* @brief Calculate the right-hand side (dY/dt) and energy generation.
*
* @param Y Vector of current abundances for all species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return StepDerivatives<double> containing dY/dt and energy generation rate.
*
* This function must be implemented by derived classes to compute the
* time derivatives of all species and the specific nuclear energy generation
* rate for the current state.
*/
[[nodiscard]] virtual std::expected<StepDerivatives<double>, expectations::StaleEngineError> calculateRHSAndEnergy(
const std::vector<double>& Y,
double T9,
double rho
) const = 0;
};
/**
* @brief Abstract class for engines supporting Jacobian and stoichiometry operations.
*
* Extends Engine with additional methods for:
* - Generating and accessing the Jacobian matrix (for implicit solvers).
* - Generating and accessing the stoichiometry matrix.
* - Calculating molar reaction flows for individual reactions.
* - Accessing the set of logical reactions in the network.
* - Computing timescales for each species.
*
* Intended usage: Derive from this class to implement engines that support
* advanced solver features such as implicit integration, sensitivity analysis,
* QSE (Quasi-Steady-State Equilibrium) handling, and more.
*/
class DynamicEngine : public Engine {
public:
/**
* @brief Generate the Jacobian matrix for the current state.
*
* @param Y_dynamic Vector of current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
*
* This method must compute and store the Jacobian matrix (∂(dY/dt)_i/∂Y_j)
* for the current state. The matrix can then be accessed via getJacobianMatrixEntry().
*/
virtual void generateJacobianMatrix(
const std::vector<double>& Y_dynamic,
double T9,
double rho
) const = 0;
virtual void generateJacobianMatrix(
const std::vector<double>& Y_dynamic,
double T9,
double rho,
const SparsityPattern& sparsityPattern
) const {
throw std::logic_error("Sparsity pattern not supported by this engine.");
}
/**
* @brief Get an entry from the previously generated Jacobian matrix.
*
* @param i Row index (species index).
* @param j Column index (species index).
* @return Value of the Jacobian matrix at (i, j).
*
* The Jacobian must have been generated by generateJacobianMatrix() before calling this.
*/
[[nodiscard]] virtual double getJacobianMatrixEntry(
int i,
int j
) const = 0;
/**
* @brief Generate the stoichiometry matrix for the network.
*
* This method must compute and store the stoichiometry matrix,
* which encodes the net change of each species in each reaction.
*/
virtual void generateStoichiometryMatrix() = 0;
/**
* @brief Get an entry from the stoichiometry matrix.
*
* @param speciesIndex Index of the species.
* @param reactionIndex Index of the reaction.
* @return Stoichiometric coefficient for the species in the reaction.
*
* The stoichiometry matrix must have been generated by generateStoichiometryMatrix().
*/
[[nodiscard]] virtual int getStoichiometryMatrixEntry(
int speciesIndex,
int reactionIndex
) const = 0;
/**
* @brief Calculate the molar reaction flow for a given reaction.
*
* @param reaction The reaction for which to calculate the flow.
* @param Y Vector of current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Molar flow rate for the reaction (e.g., mol/g/s).
*
* This method computes the net rate at which the given reaction proceeds
* under the current state.
*/
[[nodiscard]] virtual double calculateMolarReactionFlow(
const reaction::Reaction& reaction,
const std::vector<double>& Y,
double T9,
double rho
) const = 0;
/**
* @brief Get the set of logical reactions in the network.
*
* @return Reference to the LogicalReactionSet containing all reactions.
*/
[[nodiscard]] virtual const reaction::LogicalReactionSet& getNetworkReactions() const = 0;
virtual void setNetworkReactions(const reaction::LogicalReactionSet& reactions) = 0;
/**
* @brief Compute timescales for all species in the network.
*
* @param Y Vector of current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Map from Species to their characteristic timescales (s).
*
* This method estimates the timescale for abundance change of each species,
* which can be used for timestep control, diagnostics, and reaction network culling.
*/
[[nodiscard]] virtual std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesTimescales(
const std::vector<double>& Y,
double T9,
double rho
) const = 0;
[[nodiscard]] virtual std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesDestructionTimescales(
const std::vector<double>& Y,
double T9,
double rho
) const = 0;
/**
* @brief Update the internal state of the engine.
*
* @param netIn A struct containing the current network input, such as
* temperature, density, and composition.
*
* This method is intended to be implemented by derived classes to update
* their internal state based on the provided network conditions. For example,
* an adaptive engine might use this to re-evaluate which reactions and species
* are active. For other engines that do not support manually updating, this
* method might do nothing.
*
* @par Usage Example:
* @code
* NetIn input = { ... };
* myEngine.update(input);
* @endcode
*
* @post The internal state of the engine is updated to reflect the new conditions.
*/
virtual fourdst::composition::Composition update(const NetIn &netIn) = 0;
virtual bool isStale(const NetIn& netIn) = 0;
/**
* @brief Set the electron screening model.
*
* @param model The type of screening model to use for reaction rate calculations.
*
* This method allows changing the screening model at runtime. Screening corrections
* account for the electrostatic shielding of nuclei by electrons, which affects
* reaction rates in dense stellar plasmas.
*
* @par Usage Example:
* @code
* myEngine.setScreeningModel(screening::ScreeningType::WEAK);
* @endcode
*
* @post The engine will use the specified screening model for subsequent rate calculations.
*/
virtual void setScreeningModel(screening::ScreeningType model) = 0;
/**
* @brief Get the current electron screening model.
*
* @return The currently active screening model type.
*
* @par Usage Example:
* @code
* screening::ScreeningType currentModel = myEngine.getScreeningModel();
* @endcode
*/
[[nodiscard]] virtual screening::ScreeningType getScreeningModel() const = 0;
[[nodiscard]] virtual int getSpeciesIndex(const fourdst::atomic::Species &species) const = 0;
[[nodiscard]] virtual std::vector<double> mapNetInToMolarAbundanceVector(const NetIn &netIn) const = 0;
[[nodiscard]] virtual PrimingReport primeEngine(const NetIn &netIn) = 0;
[[nodiscard]] virtual BuildDepthType getDepth() const {
throw std::logic_error("Network depth not supported by this engine.");
}
virtual void rebuild(const fourdst::composition::Composition& comp, BuildDepthType depth) {
throw std::logic_error("Setting network depth not supported by this engine.");
}
};
}

View File

@@ -0,0 +1,331 @@
/* ***********************************************************************
//
// Copyright (C) 2025 -- The 4D-STAR Collaboration
// File Author: Emily Boudreaux
// Last Modified: March 21, 2025
//
// 4DSSE is free software; you can use it and/or modify
// it under the terms and restrictions the GNU General Library Public
// License version 3 (GPLv3) as published by the Free Software Foundation.
//
// 4DSSE is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with this software; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// *********************************************************************** */
#pragma once
#include <array>
#include <boost/numeric/odeint.hpp>
#include "gridfire/network.h"
/**
* @file approx8.h
* @brief Header file for the Approx8 nuclear reaction network.
*
* This file contains the definitions and declarations for the Approx8 nuclear reaction network.
* The network is based on Frank Timmes' "approx8" and includes 8 isotopes and various nuclear reactions.
* The rates are evaluated using a fitting function with coefficients from reaclib.jinaweb.org.
*/
namespace gridfire::approx8{
/**
* @typedef vector_type
* @brief Alias for a vector of doubles using Boost uBLAS.
*/
typedef boost::numeric::ublas::vector< double > vector_type;
/**
* @typedef matrix_type
* @brief Alias for a matrix of doubles using Boost uBLAS.
*/
typedef boost::numeric::ublas::matrix< double > matrix_type;
/**
* @typedef vec7
* @brief Alias for a std::array of 7 doubles.
*/
typedef std::array<double,7> vec7;
/**
* @struct Approx8Net
* @brief Contains constants and arrays related to the nuclear network.
*/
struct Approx8Net{
static constexpr int ih1=0;
static constexpr int ihe3=1;
static constexpr int ihe4=2;
static constexpr int ic12=3;
static constexpr int in14=4;
static constexpr int io16=5;
static constexpr int ine20=6;
static constexpr int img24=7;
static constexpr int iTemp=img24+1;
static constexpr int iDensity =iTemp+1;
static constexpr int iEnergy=iDensity+1;
static constexpr int nIso=img24+1; // number of isotopes
static constexpr int nVar=iEnergy+1; // number of variables
static constexpr std::array<int,nIso> aIon = {
1,
3,
4,
12,
14,
16,
20,
24
};
static constexpr std::array<double,nIso> mIon = {
1.67262164e-24,
5.00641157e-24,
6.64465545e-24,
1.99209977e-23,
2.32462686e-23,
2.65528858e-23,
3.31891077e-23,
3.98171594e-23
};
};
/**
* @brief Multiplies two arrays and sums the resulting elements.
* @param a First array.
* @param b Second array.
* @return Sum of the product of the arrays.
* @example
* @code
* vec7 a = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
* vec7 b = {0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5};
* double result = sum_product(a, b);
* @endcode
*/
double sum_product( const vec7 &a, const vec7 &b);
/**
* @brief Returns an array of T9 terms for the nuclear reaction rate fit.
* @param T Temperature in GigaKelvin.
* @return Array of T9 terms.
* @example
* @code
* double T = 1.5;
* vec7 T9_array = get_T9_array(T);
* @endcode
*/
vec7 get_T9_array(const double &T);
/**
* @brief Evaluates the nuclear reaction rate given the T9 array and coefficients.
* @param T9 Array of T9 terms.
* @param coef Array of coefficients.
* @return Evaluated rate.
* @example
* @code
* vec7 T9 = get_T9_array(1.5);
* vec7 coef = {1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001};
* double rate = rate_fit(T9, coef);
* @endcode
*/
double rate_fit(const vec7 &T9, const vec7 &coef);
/**
* @brief Calculates the rate for the reaction p + p -> d.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double pp_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction p + d -> he3.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double dp_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction he3 + he3 -> he4 + 2p.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double he3he3_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction he3(he3,2p)he4.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double he3he4_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction he4 + he4 + he4 -> c12.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double triple_alpha_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction c12 + p -> n13.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double c12p_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction c12 + he4 -> o16.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double c12a_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction n14(p,g)o15 - o15 + p -> c12 + he4.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double n14p_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction n14(a,g)f18 assumed to go on to ne20.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double n14a_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction n15(p,a)c12 (CNO I).
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double n15pa_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction n15(p,g)o16 (CNO II).
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double n15pg_rate(const vec7 &T9);
/**
* @brief Calculates the fraction for the reaction n15(p,g)o16.
* @param T9 Array of T9 terms.
* @return Fraction of the reaction.
*/
double n15pg_frac(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction o16(p,g)f17 then f17 -> o17(p,a)n14.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double o16p_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction o16(a,g)ne20.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double o16a_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction ne20(a,g)mg24.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double ne20a_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction c12(c12,a)ne20.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double c12c12_rate(const vec7 &T9);
/**
* @brief Calculates the rate for the reaction c12(o16,a)mg24.
* @param T9 Array of T9 terms.
* @return Rate of the reaction.
*/
double c12o16_rate(const vec7 &T9);
/**
* @struct Jacobian
* @brief Functor to calculate the Jacobian matrix for implicit solvers.
*/
struct Jacobian {
/**
* @brief Calculates the Jacobian matrix.
* @param y State vector.
* @param J Jacobian matrix.
* @param dfdt Derivative of the state vector.
*/
void operator() ( const vector_type &y, matrix_type &J, double /* t */, vector_type &dfdt ) const;
};
/**
* @struct ODE
* @brief Functor to calculate the derivatives for the ODE solver.
*/
struct ODE {
/**
* @brief Calculates the derivatives of the state vector.
* @param y State vector.
* @param dydt Derivative of the state vector.
*/
void operator() ( const vector_type &y, vector_type &dydt, double /* t */) const;
};
/**
* @class Approx8Network
* @brief Class for the Approx8 nuclear reaction network.
*/
class Approx8Network final : public Network {
public:
Approx8Network();
/**
* @brief Evaluates the nuclear network.
* @param netIn Input parameters for the network.
* @return Output results from the network.
*/
NetOut evaluate(const NetIn &netIn) override;
/**
* @brief Sets whether the solver should use a stiff method.
* @param stiff Boolean indicating if a stiff method should be used.
*/
void setStiff(bool stiff) override;
/**
* @brief Checks if the solver is using a stiff method.
* @return Boolean indicating if a stiff method is being used.
*/
bool isStiff() const override { return m_stiff; }
private:
vector_type m_y;
double m_tMax = 0;
double m_dt0 = 0;
bool m_stiff = false;
/**
* @brief Converts the input parameters to the internal state vector.
* @param netIn Input parameters for the network.
* @return Internal state vector.
*/
static vector_type convert_netIn(const NetIn &netIn);
};
} // namespace nnApprox8

View File

@@ -0,0 +1,887 @@
#pragma once
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/composition/composition.h"
#include "fourdst/logging/logging.h"
#include "fourdst/config/config.h"
#include "gridfire/network.h"
#include "gridfire/reaction/reaction.h"
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/screening/screening_abstract.h"
#include "gridfire/screening/screening_types.h"
#include "gridfire/partition/partition_abstract.h"
#include "gridfire/engine/procedures/construction.h"
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
#include <ranges>
#include <boost/numeric/ublas/matrix_sparse.hpp>
#include "cppad/cppad.hpp"
#include "cppad/utility/sparse_rc.hpp"
#include "cppad/speed/sparse_jac_fun.hpp"
#include "procedures/priming.h"
#include "quill/LogMacros.h"
// PERF: The function getNetReactionStoichiometry returns a map of species to their stoichiometric coefficients for a given reaction.
// this makes extra copies of the species, which is not ideal and could be optimized further.
// Even more relevant is the member m_reactionIDMap which makes copies of a REACLIBReaction for each reaction ID.
// REACLIBReactions are quite large data structures, so this could be a performance bottleneck.
// static bool isF17 = false;
namespace gridfire {
static bool s_debug = false; // Global debug flag for the GraphEngine
/**
* @brief Alias for CppAD AD type for double precision.
*
* This alias simplifies the use of the CppAD automatic differentiation type.
*/
typedef CppAD::AD<double> ADDouble;
using fourdst::config::Config;
using fourdst::logging::LogManager;
using fourdst::constant::Constants;
/**
* @brief Minimum density threshold below which reactions are ignored.
*
* Reactions are not calculated if the density falls below this threshold.
* This helps to improve performance by avoiding unnecessary calculations
* in very low-density regimes.
*/
static constexpr double MIN_DENSITY_THRESHOLD = 1e-18;
/**
* @brief Minimum abundance threshold below which species are ignored.
*
* Species with abundances below this threshold are treated as zero in
* reaction rate calculations. This helps to improve performance by
* avoiding unnecessary calculations for trace species.
*/
static constexpr double MIN_ABUNDANCE_THRESHOLD = 1e-18;
/**
* @brief Minimum value for Jacobian matrix entries.
*
* Jacobian matrix entries with absolute values below this threshold are
* treated as zero to maintain sparsity and improve performance.
*/
static constexpr double MIN_JACOBIAN_THRESHOLD = 1e-24;
/**
* @class GraphEngine
* @brief A reaction network engine that uses a graph-based representation.
*
* The GraphEngine class implements the DynamicEngine interface using a
* graph-based representation of the reaction network. It uses sparse
* matrices for efficient storage and computation of the stoichiometry
* and Jacobian matrices. Automatic differentiation (AD) is used to
* calculate the Jacobian matrix.
*
* The engine supports:
* - Calculation of the right-hand side (dY/dt) and energy generation rate.
* - Generation and access to the Jacobian matrix.
* - Generation and access to the stoichiometry matrix.
* - Calculation of molar reaction flows.
* - Access to the set of logical reactions in the network.
* - Computation of timescales for each species.
* - Exporting the network to DOT and CSV formats for visualization and analysis.
*
* @implements DynamicEngine
*
* @see engine_abstract.h
*/
class GraphEngine final : public DynamicEngine{
public:
/**
* @brief Constructs a GraphEngine from a composition.
*
* @param composition The composition of the material.
*
* This constructor builds the reaction network from the given composition
* using the `build_reaclib_nuclear_network` function.
*
* @see build_reaclib_nuclear_network
*/
explicit GraphEngine(
const fourdst::composition::Composition &composition,
const BuildDepthType = NetworkBuildDepth::Full
);
explicit GraphEngine(
const fourdst::composition::Composition &composition,
const partition::PartitionFunction& partitionFunction,
const BuildDepthType buildDepth = NetworkBuildDepth::Full
);
/**
* @brief Constructs a GraphEngine from a set of reactions.
*
* @param reactions The set of reactions to use in the network.
*
* This constructor uses the given set of reactions to construct the
* reaction network.
*/
explicit GraphEngine(const reaction::LogicalReactionSet &reactions);
/**
* @brief Calculates the right-hand side (dY/dt) and energy generation rate.
*
* @param Y Vector of current abundances for all species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return StepDerivatives<double> containing dY/dt and energy generation rate.
*
* This method calculates the time derivatives of all species and the
* specific nuclear energy generation rate for the current state.
*
* @see StepDerivatives
*/
[[nodiscard]] std::expected<StepDerivatives<double>, expectations::StaleEngineError> calculateRHSAndEnergy(
const std::vector<double>& Y,
const double T9,
const double rho
) const override;
/**
* @brief Generates the Jacobian matrix for the current state.
*
* @param Y_dynamic Vector of current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
*
* This method computes and stores the Jacobian matrix (∂(dY/dt)_i/∂Y_j)
* for the current state using automatic differentiation. The matrix can
* then be accessed via `getJacobianMatrixEntry()`.
*
* @see getJacobianMatrixEntry()
*/
void generateJacobianMatrix(
const std::vector<double>& Y_dynamic,
const double T9,
const double rho
) const override;
void generateJacobianMatrix(
const std::vector<double> &Y_dynamic,
double T9,
double rho,
const SparsityPattern &sparsityPattern
) const override;
/**
* @brief Generates the stoichiometry matrix for the network.
*
* This method computes and stores the stoichiometry matrix,
* which encodes the net change of each species in each reaction.
*/
void generateStoichiometryMatrix() override;
/**
* @brief Calculates the molar reaction flow for a given reaction.
*
* @param reaction The reaction for which to calculate the flow.
* @param Y Vector of current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Molar flow rate for the reaction (e.g., mol/g/s).
*
* This method computes the net rate at which the given reaction proceeds
* under the current state.
*/
[[nodiscard]] double calculateMolarReactionFlow(
const reaction::Reaction& reaction,
const std::vector<double>&Y,
const double T9,
const double rho
) const override;
/**
* @brief Gets the list of species in the network.
* @return Vector of Species objects representing all network species.
*/
[[nodiscard]] const std::vector<fourdst::atomic::Species>& getNetworkSpecies() const override;
/**
* @brief Gets the set of logical reactions in the network.
* @return Reference to the LogicalReactionSet containing all reactions.
*/
[[nodiscard]] const reaction::LogicalReactionSet& getNetworkReactions() const override;
void setNetworkReactions(const reaction::LogicalReactionSet& reactions) override;
/**
* @brief Gets an entry from the previously generated Jacobian matrix.
*
* @param i Row index (species index).
* @param j Column index (species index).
* @return Value of the Jacobian matrix at (i, j).
*
* The Jacobian must have been generated by `generateJacobianMatrix()` before calling this.
*
* @see generateJacobianMatrix()
*/
[[nodiscard]] double getJacobianMatrixEntry(
const int i,
const int j
) const override;
/**
* @brief Gets the net stoichiometry for a given reaction.
*
* @param reaction The reaction for which to get the stoichiometry.
* @return Map of species to their stoichiometric coefficients.
*/
[[nodiscard]] static std::unordered_map<fourdst::atomic::Species, int> getNetReactionStoichiometry(
const reaction::Reaction& reaction
);
/**
* @brief Gets an entry from the stoichiometry matrix.
*
* @param speciesIndex Index of the species.
* @param reactionIndex Index of the reaction.
* @return Stoichiometric coefficient for the species in the reaction.
*
* The stoichiometry matrix must have been generated by `generateStoichiometryMatrix()`.
*
* @see generateStoichiometryMatrix()
*/
[[nodiscard]] int getStoichiometryMatrixEntry(
const int speciesIndex,
const int reactionIndex
) const override;
/**
* @brief Computes timescales for all species in the network.
*
* @param Y Vector of current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Map from Species to their characteristic timescales (s).
*
* This method estimates the timescale for abundance change of each species,
* which can be used for timestep control or diagnostics.
*/
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesTimescales(
const std::vector<double>& Y,
double T9,
double rho
) const override;
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesDestructionTimescales(
const std::vector<double>& Y,
double T9,
double rho
) const override;
fourdst::composition::Composition update(const NetIn &netIn) override;
bool isStale(const NetIn &netIn) override;
/**
* @brief Checks if a given species is involved in the network.
*
* @param species The species to check.
* @return True if the species is involved in the network, false otherwise.
*/
[[nodiscard]] bool involvesSpecies(
const fourdst::atomic::Species& species
) const;
/**
* @brief Exports the network to a DOT file for visualization.
*
* @param filename The name of the DOT file to create.
*
* This method generates a DOT file that can be used to visualize the
* reaction network as a graph. The DOT file can be converted to a
* graphical image using Graphviz.
*
* @throws std::runtime_error If the file cannot be opened for writing.
*
* Example usage:
* @code
* engine.exportToDot("network.dot");
* @endcode
*/
void exportToDot(
const std::string& filename
) const;
/**
* @brief Exports the network to a CSV file for analysis.
*
* @param filename The name of the CSV file to create.
*
* This method generates a CSV file containing information about the
* reactions in the network, including the reactants, products, Q-value,
* and reaction rate coefficients.
*
* @throws std::runtime_error If the file cannot be opened for writing.
*
* Example usage:
* @code
* engine.exportToCSV("network.csv");
* @endcode
*/
void exportToCSV(
const std::string& filename
) const;
void setScreeningModel(screening::ScreeningType) override;
[[nodiscard]] screening::ScreeningType getScreeningModel() const override;
void setPrecomputation(bool precompute);
[[nodiscard]] bool isPrecomputationEnabled() const;
[[nodiscard]] const partition::PartitionFunction& getPartitionFunction() const;
[[nodiscard]] double calculateReverseRate(
const reaction::Reaction &reaction,
double T9
) const;
[[nodiscard]] double calculateReverseRateTwoBody(
const reaction::Reaction &reaction,
const double T9,
const double forwardRate,
const double expFactor
) const;
[[nodiscard]] double calculateReverseRateTwoBodyDerivative(
const reaction::Reaction &reaction,
const double T9,
const double reverseRate
) const;
[[nodiscard]] bool isUsingReverseReactions() const;
void setUseReverseReactions(bool useReverse);
[[nodiscard]] int getSpeciesIndex(
const fourdst::atomic::Species& species
) const override;
[[nodiscard]] std::vector<double> mapNetInToMolarAbundanceVector(const NetIn &netIn) const override;
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
[[nodiscard]] BuildDepthType getDepth() const override;
void rebuild(const fourdst::composition::Composition& comp, const BuildDepthType depth) override;
private:
struct PrecomputedReaction {
// Forward cacheing
size_t reaction_index;
std::vector<size_t> unique_reactant_indices;
std::vector<int> reactant_powers;
double symmetry_factor;
std::vector<size_t> affected_species_indices;
std::vector<int> stoichiometric_coefficients;
// Reverse cacheing
std::vector<size_t> unique_product_indices; ///< Unique product indices for reverse reactions.
std::vector<int> product_powers; ///< Powers of each unique product in the reverse reaction.
double reverse_symmetry_factor; ///< Symmetry factor for reverse reactions.
};
struct constants {
const double u = Constants::getInstance().get("u").value; ///< Atomic mass unit in g.
const double Na = Constants::getInstance().get("N_a").value; ///< Avogadro's number.
const double c = Constants::getInstance().get("c").value; ///< Speed of light in cm/s.
const double kB = Constants::getInstance().get("kB").value; ///< Boltzmann constant in erg/K.
};
private:
class AtomicReverseRate final : public CppAD::atomic_base<double> {
public:
AtomicReverseRate(
const reaction::Reaction& reaction,
const GraphEngine& engine
):
atomic_base<double>("AtomicReverseRate"),
m_reaction(reaction),
m_engine(engine) {}
bool forward(
size_t p,
size_t q,
const CppAD::vector<bool>& vx,
CppAD::vector<bool>& vy,
const CppAD::vector<double>& tx,
CppAD::vector<double>& ty
) override;
bool reverse(
size_t q,
const CppAD::vector<double>& tx,
const CppAD::vector<double>& ty,
CppAD::vector<double>& px,
const CppAD::vector<double>& py
) override;
bool for_sparse_jac(
size_t q,
const CppAD::vector<std::set<size_t>>&r,
CppAD::vector<std::set<size_t>>& s
) override;
bool rev_sparse_jac(
size_t q,
const CppAD::vector<std::set<size_t>>&rt,
CppAD::vector<std::set<size_t>>& st
) override;
private:
const reaction::Reaction& m_reaction;
const GraphEngine& m_engine;
};
private:
Config& m_config = Config::getInstance();
quill::Logger* m_logger = LogManager::getInstance().getLogger("log");
constants m_constants;
reaction::LogicalReactionSet m_reactions; ///< Set of REACLIB reactions in the network.
std::unordered_map<std::string_view, reaction::Reaction*> m_reactionIDMap; ///< Map from reaction ID to REACLIBReaction. //PERF: This makes copies of REACLIBReaction and could be a performance bottleneck.
std::vector<fourdst::atomic::Species> m_networkSpecies; ///< Vector of unique species in the network.
std::unordered_map<std::string_view, fourdst::atomic::Species> m_networkSpeciesMap; ///< Map from species name to Species object.
std::unordered_map<fourdst::atomic::Species, size_t> m_speciesToIndexMap; ///< Map from species to their index in the stoichiometry matrix.
boost::numeric::ublas::compressed_matrix<int> m_stoichiometryMatrix; ///< Stoichiometry matrix (species x reactions).
mutable boost::numeric::ublas::compressed_matrix<double> m_jacobianMatrix; ///< Jacobian matrix (species x species).
mutable CppAD::ADFun<double> m_rhsADFun; ///< CppAD function for the right-hand side of the ODE.
mutable CppAD::sparse_jac_work m_jac_work; ///< Work object for sparse Jacobian calculations.
CppAD::sparse_rc<std::vector<size_t>> m_full_jacobian_sparsity_pattern; ///< Full sparsity pattern for the Jacobian matrix.
std::vector<std::unique_ptr<AtomicReverseRate>> m_atomicReverseRates;
screening::ScreeningType m_screeningType = screening::ScreeningType::BARE; ///< Screening type for the reaction network. Default to no screening.
std::unique_ptr<screening::ScreeningModel> m_screeningModel = screening::selectScreeningModel(m_screeningType);
bool m_usePrecomputation = true; ///< Flag to enable or disable using precomputed reactions for efficiency. Mathematically, this should not change the results. Generally end users should not need to change this.
bool m_useReverseReactions = true; ///< Flag to enable or disable reverse reactions. If false, only forward reactions are considered.
BuildDepthType m_depth;
std::vector<PrecomputedReaction> m_precomputedReactions; ///< Precomputed reactions for efficiency.
std::unique_ptr<partition::PartitionFunction> m_partitionFunction; ///< Partition function for the network.
private:
/**
* @brief Synchronizes the internal maps.
*
* This method synchronizes the internal maps used by the engine,
* including the species map, reaction ID map, and species-to-index map.
* It also generates the stoichiometry matrix and records the AD tape.
*/
void syncInternalMaps();
/**
* @brief Collects the unique species in the network.
*
* This method collects the unique species in the network from the
* reactants and products of all reactions.
*/
void collectNetworkSpecies();
/**
* @brief Populates the reaction ID map.
*
* This method populates the reaction ID map, which maps reaction IDs
* to REACLIBReaction objects.
*/
void populateReactionIDMap();
/**
* @brief Populates the species-to-index map.
*
* This method populates the species-to-index map, which maps species
* to their index in the stoichiometry matrix.
*/
void populateSpeciesToIndexMap();
/**
* @brief Reserves space for the Jacobian matrix.
*
* This method reserves space for the Jacobian matrix, which is used
* to store the partial derivatives of the right-hand side of the ODE
* with respect to the species abundances.
*/
void reserveJacobianMatrix() const;
/**
* @brief Records the AD tape for the right-hand side of the ODE.
*
* This method records the AD tape for the right-hand side of the ODE,
* which is used to calculate the Jacobian matrix using automatic
* differentiation.
*
* @throws std::runtime_error If there are no species in the network.
*/
void recordADTape();
void collectAtomicReverseRateAtomicBases();
void precomputeNetwork();
/**
* @brief Validates mass and charge conservation across all reactions.
*
* @return True if all reactions conserve mass and charge, false otherwise.
*
* This method checks that all reactions in the network conserve mass
* and charge. If any reaction does not conserve mass or charge, an
* error message is logged and false is returned.
*/
[[nodiscard]] bool validateConservation() const;
/**
* @brief Validates the composition against the current reaction set.
*
* @param composition The composition to validate.
* @param culling The culling threshold to use.
* @param T9 The temperature to use.
*
* This method validates the composition against the current reaction set.
* If the composition is not compatible with the reaction set, the
* reaction set is rebuilt from the composition.
*/
void validateComposition(
const fourdst::composition::Composition &composition,
double culling,
double T9
);
[[nodiscard]] StepDerivatives<double> calculateAllDerivativesUsingPrecomputation(
const std::vector<double> &Y_in,
const std::vector<double>& bare_rates,
const std::vector<double> &bare_reverse_rates,
double T9, double rho
) const;
/**
* @brief Calculates the molar reaction flow for a given reaction.
*
* @tparam T The numeric type to use for the calculation.
* @param reaction The reaction for which to calculate the flow.
* @param Y Vector of current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Molar flow rate for the reaction (e.g., mol/g/s).
*
* This method computes the net rate at which the given reaction proceeds
* under the current state.
*/
template <IsArithmeticOrAD T>
T calculateMolarReactionFlow(
const reaction::Reaction &reaction,
const std::vector<T> &Y,
const T T9,
const T rho
) const;
template<IsArithmeticOrAD T>
T calculateReverseMolarReactionFlow(
T T9,
T rho,
std::vector<T> screeningFactors,
std::vector<T> Y,
size_t reactionIndex,
const reaction::LogicalReaction &reaction
) const;
/**
* @brief Calculates all derivatives (dY/dt) and the energy generation rate.
*
* @tparam T The numeric type to use for the calculation.
* @param Y_in Vector of current abundances for all species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return StepDerivatives<T> containing dY/dt and energy generation rate.
*
* This method calculates the time derivatives of all species and the
* specific nuclear energy generation rate for the current state.
*/
template<IsArithmeticOrAD T>
[[nodiscard]] StepDerivatives<T> calculateAllDerivatives(
const std::vector<T> &Y_in,
T T9,
T rho
) const;
/**
* @brief Calculates all derivatives (dY/dt) and the energy generation rate (double precision).
*
* @param Y_in Vector of current abundances for all species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return StepDerivatives<double> containing dY/dt and energy generation rate.
*
* This method calculates the time derivatives of all species and the
* specific nuclear energy generation rate for the current state using
* double precision arithmetic.
*/
[[nodiscard]] StepDerivatives<double> calculateAllDerivatives(
const std::vector<double>& Y_in,
const double T9,
const double rho
) const;
/**
* @brief Calculates all derivatives (dY/dt) and the energy generation rate (automatic differentiation).
*
* @param Y_in Vector of current abundances for all species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return StepDerivatives<ADDouble> containing dY/dt and energy generation rate.
*
* This method calculates the time derivatives of all species and the
* specific nuclear energy generation rate for the current state using
* automatic differentiation.
*/
[[nodiscard]] StepDerivatives<ADDouble> calculateAllDerivatives(
const std::vector<ADDouble>& Y_in,
const ADDouble &T9,
const ADDouble &rho
) const;
};
template <IsArithmeticOrAD T>
T GraphEngine::calculateReverseMolarReactionFlow(
T T9,
T rho,
std::vector<T> screeningFactors,
std::vector<T> Y,
size_t reactionIndex,
const reaction::LogicalReaction &reaction
) const {
if (!m_useReverseReactions) {
return static_cast<T>(0.0); // If reverse reactions are not used, return zero
}
s_debug = false;
if (reaction.peName() == "p(p,e+)d" || reaction.peName() =="d(d,n)he3" || reaction.peName() == "c12(p,g)n13") {
if constexpr (!std::is_same_v<T, ADDouble>) {
s_debug = true;
std::cout << "Calculating reverse molar flow for reaction: " << reaction.peName() << std::endl;
std::cout << "\tT9: " << T9 << ", rho: " << rho << std::endl;
}
}
T reverseMolarFlow = static_cast<T>(0.0);
if (reaction.qValue() != 0.0) {
T reverseRateConstant = static_cast<T>(0.0);
if constexpr (std::is_same_v<T, ADDouble>) { // Check if T is an AD type at compile time
const auto& atomic_func_ptr = m_atomicReverseRates[reactionIndex];
if (atomic_func_ptr != nullptr) {
// A. Instantiate the atomic operator for the specific reaction
// B. Marshal the input vector
std::vector<T> ax = { T9 };
std::vector<T> ay(1);
(*atomic_func_ptr)(ax, ay);
reverseRateConstant = static_cast<T>(ay[0]);
} else {
return reverseMolarFlow; // If no atomic function is available, return zero
}
} else {
// A,B If not calling with an AD type, calculate the reverse rate directly
if (s_debug) {
std::cout << "\tUsing double overload\n";
}
reverseRateConstant = calculateReverseRate(reaction, T9);
}
// C. Get product multiplicities
std::unordered_map<fourdst::atomic::Species, int> productCounts;
for (const auto& product : reaction.products()) {
productCounts[product]++;
}
// D. Calculate the symmetry factor
T reverseSymmetryFactor = static_cast<T>(1.0);
for (const auto &count: productCounts | std::views::values) {
reverseSymmetryFactor /= static_cast<T>(std::tgamma(static_cast<double>(count + 1))); // Gamma function for factorial
}
// E. Calculate the abundance term
T productAbundanceTerm = static_cast<T>(1.0);
for (const auto& [species, count] : productCounts) {
const unsigned long speciesIndex = m_speciesToIndexMap.at(species);
productAbundanceTerm *= CppAD::pow(Y[speciesIndex], count);
}
// F. Determine the power for the density term
const size_t num_products = reaction.products().size();
const T rho_power = CppAD::pow(rho, static_cast<T>(num_products > 1 ? num_products - 1 : 0)); // Density raised to the power of (N-1) for N products
// G. Assemble the reverse molar flow rate
reverseMolarFlow = screeningFactors[reactionIndex] *
reverseRateConstant *
productAbundanceTerm *
reverseSymmetryFactor *
rho_power;
}
return reverseMolarFlow;
}
template<IsArithmeticOrAD T>
StepDerivatives<T> GraphEngine::calculateAllDerivatives(
const std::vector<T> &Y_in, T T9, T rho) const {
std::vector<T> screeningFactors = m_screeningModel->calculateScreeningFactors(
m_reactions,
m_networkSpecies,
Y_in,
T9,
rho
);
// --- Setup output derivatives structure ---
StepDerivatives<T> result;
result.dydt.resize(m_networkSpecies.size(), static_cast<T>(0.0));
// --- AD Pre-setup (flags to control conditionals in an AD safe / branch aware manner) ---
// ----- Constants for AD safe calculations ---
const T zero = static_cast<T>(0.0);
const T one = static_cast<T>(1.0);
// ----- Initialize variables for molar concentration product and thresholds ---
// Note: the logic here is that we use CppAD::CondExprLt to test thresholds and if they are less we set the flag
// to zero so that the final returned reaction flow is 0. This is as opposed to standard if statements
// which create branches that break the AD tape.
const T rho_threshold = static_cast<T>(MIN_DENSITY_THRESHOLD);
// --- Check if the density is below the threshold where we ignore reactions ---
T threshold_flag = CppAD::CondExpLt(rho, rho_threshold, zero, one); // If rho < threshold, set flag to 0
std::vector<T> Y = Y_in;
for (size_t i = 0; i < m_networkSpecies.size(); ++i) {
// We use CppAD::CondExpLt to handle AD taping and prevent branching
// Note that while this is syntactically more complex this is equivalent to
// if (Y[i] < 0) {Y[i] = 0;}
// The issue is that this would introduce a branch which would require the auto diff tape to be re-recorded
// each timestep, which is very inefficient.
Y[i] = CppAD::CondExpLt(Y[i], zero, zero, Y[i]); // Ensure no negative abundances
}
const T u = static_cast<T>(m_constants.u); // Atomic mass unit in grams
const T N_A = static_cast<T>(m_constants.Na); // Avogadro's number in mol^-1
const T c = static_cast<T>(m_constants.c); // Speed of light in cm/s
// --- SINGLE LOOP OVER ALL REACTIONS ---
for (size_t reactionIndex = 0; reactionIndex < m_reactions.size(); ++reactionIndex) {
const auto& reaction = m_reactions[reactionIndex];
// 1. Calculate forward reaction rate
const T forwardMolarReactionFlow = screeningFactors[reactionIndex] *
calculateMolarReactionFlow<T>(reaction, Y, T9, rho);
// 2. Calculate reverse reaction rate
T reverseMolarFlow = calculateReverseMolarReactionFlow<T>(
T9,
rho,
screeningFactors,
Y,
reactionIndex,
reaction
);
const T molarReactionFlow = forwardMolarReactionFlow - reverseMolarFlow; // Net molar reaction flow
// 3. Use the rate to update all relevant species derivatives (dY/dt)
for (size_t speciesIndex = 0; speciesIndex < m_networkSpecies.size(); ++speciesIndex) {
const T nu_ij = static_cast<T>(m_stoichiometryMatrix(speciesIndex, reactionIndex));
result.dydt[speciesIndex] += threshold_flag * nu_ij * molarReactionFlow;
}
}
T massProductionRate = static_cast<T>(0.0); // [mol][s^-1]
for (const auto& [species, index] : m_speciesToIndexMap) {
massProductionRate += result.dydt[index] * species.mass() * u;
}
result.nuclearEnergyGenerationRate = -massProductionRate * N_A * c * c; // [cm^2][s^-3] = [erg][s^-1][g^-1]
return result;
}
template <IsArithmeticOrAD T>
T GraphEngine::calculateMolarReactionFlow(
const reaction::Reaction &reaction,
const std::vector<T> &Y,
const T T9,
const T rho
) const {
// --- Pre-setup (flags to control conditionals in an AD safe / branch aware manner) ---
// ----- Constants for AD safe calculations ---
const T zero = static_cast<T>(0.0);
const T one = static_cast<T>(1.0);
// ----- Initialize variables for molar concentration product and thresholds ---
// Note: the logic here is that we use CppAD::CondExprLt to test thresholds and if they are less we set the flag
// to zero so that the final returned reaction flow is 0. This is as opposed to standard if statements
// which create branches that break the AD tape.
const T Y_threshold = static_cast<T>(MIN_ABUNDANCE_THRESHOLD);
T threshold_flag = one;
// --- Calculate the molar reaction rate (in units of [s^-1][cm^3(N-1)][mol^(1-N)] for N reactants) ---
const T k_reaction = reaction.calculate_rate(T9);
// --- Cound the number of each reactant species to account for species multiplicity ---
std::unordered_map<std::string, int> reactant_counts;
reactant_counts.reserve(reaction.reactants().size());
for (const auto& reactant : reaction.reactants()) {
reactant_counts[std::string(reactant.name())]++;
}
const int totalReactants = static_cast<int>(reaction.reactants().size());
// --- Accumulator for the molar concentration ---
auto molar_concentration_product = static_cast<T>(1.0);
// --- Loop through each unique reactant species and calculate the molar concentration for that species then multiply that into the accumulator ---
for (const auto& [species_name, count] : reactant_counts) {
// --- Resolve species to molar abundance ---
// PERF: Could probably optimize out this lookup
const auto species_it = m_speciesToIndexMap.find(m_networkSpeciesMap.at(species_name));
const size_t species_index = species_it->second;
const T Yi = Y[species_index];
// --- Check if the species abundance is below the threshold where we ignore reactions ---
// threshold_flag *= CppAD::CondExpLt(Yi, Y_threshold, zero, one);
// --- If count is > 1 , we need to raise the molar concentration to the power of count since there are really count bodies in that reaction ---
molar_concentration_product *= CppAD::pow(Yi, static_cast<T>(count)); // ni^count
// --- Apply factorial correction for identical reactions ---
if (count > 1) {
molar_concentration_product /= static_cast<T>(std::tgamma(static_cast<double>(count + 1))); // Gamma function for factorial
}
}
// --- Final reaction flow calculation [mol][s^-1][g^-1] ---
// Note: If the threshold flag ever gets set to zero this will return zero.
// This will result basically in multiple branches being written to the AD tape, which will make
// the tape more expensive to record, but it will also mean that we only need to record it once for
// the entire network.
const T densityTerm = CppAD::pow(rho, totalReactants > 1 ? static_cast<T>(totalReactants - 1) : zero); // Density raised to the power of (N-1) for N reactants
return molar_concentration_product * k_reaction * threshold_flag * densityTerm;
}
};

View File

@@ -0,0 +1,17 @@
#pragma once
#include "gridfire/reaction/reaction.h"
#include "gridfire/engine/types/building.h"
#include "fourdst/composition/composition.h"
#include <variant>
namespace gridfire {
reaction::LogicalReactionSet build_reaclib_nuclear_network(
const fourdst::composition::Composition &composition,
BuildDepthType maxLayers = NetworkBuildDepth::Full,
bool reverse = false
);
}

View File

@@ -0,0 +1,4 @@
#pragma once
#include "gridfire/engine/procedures/construction.h"
#include "gridfire/engine/procedures/priming.h"

View File

@@ -0,0 +1,37 @@
#pragma once
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/network.h"
#include "fourdst/composition/composition.h"
#include "fourdst/composition/atomicSpecies.h"
#include <map>
#include <ranges>
#include <sstream>
namespace gridfire {
PrimingReport primeNetwork(
const NetIn&,
DynamicEngine& engine
);
double calculateDestructionRateConstant(
const DynamicEngine& engine,
const fourdst::atomic::Species& species,
const std::vector<double>& Y,
double T9,
double rho
);
double calculateCreationRate(
const DynamicEngine& engine,
const fourdst::atomic::Species& species,
const std::vector<double>& Y,
double T9,
double rho
);
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <variant>
namespace gridfire {
enum class NetworkBuildDepth {
Full = -1,
Shallow = 1,
SecondOrder = 2,
ThirdOrder = 3,
FourthOrder = 4,
FifthOrder = 5
};
using BuildDepthType = std::variant<NetworkBuildDepth, int>;
}

View File

@@ -0,0 +1,4 @@
#pragma once
#include "gridfire/engine/types/building.h"
#include "gridfire/engine/types/reporting.h"

View File

@@ -0,0 +1,42 @@
#pragma once
#include <map>
#include <string>
#include <ranges>
namespace gridfire {
enum class PrimingReportStatus {
FULL_SUCCESS = 0,
NO_SPECIES_TO_PRIME = 1,
MAX_ITERATIONS_REACHED = 2,
FAILED_TO_FINALIZE_COMPOSITION = 3,
FAILED_TO_FIND_CREATION_CHANNEL = 4,
FAILED_TO_FIND_PRIMING_REACTIONS = 5,
BASE_NETWORK_TOO_SHALLOW = 6
};
inline std::map<PrimingReportStatus, std::string> PrimingReportStatusStrings = {
{PrimingReportStatus::FULL_SUCCESS, "Full Success"},
{PrimingReportStatus::NO_SPECIES_TO_PRIME, "No Species to Prime"},
{PrimingReportStatus::MAX_ITERATIONS_REACHED, "Max Iterations Reached"},
{PrimingReportStatus::FAILED_TO_FINALIZE_COMPOSITION, "Failed to Finalize Composition"},
{PrimingReportStatus::FAILED_TO_FIND_CREATION_CHANNEL, "Failed to Find Creation Channel"},
{PrimingReportStatus::FAILED_TO_FIND_PRIMING_REACTIONS, "Failed to Find Priming Reactions"},
{PrimingReportStatus::BASE_NETWORK_TOO_SHALLOW, "Base Network Too Shallow"}
};
struct PrimingReport {
fourdst::composition::Composition primedComposition;
std::vector<std::pair<fourdst::atomic::Species, double>> massFractionChanges; ///< Species and their destruction/creation rates
bool success;
PrimingReportStatus status;
friend std::ostream& operator<<(std::ostream& os, const PrimingReport& report) {
std::stringstream ss;
const std::string successStr = report.success ? "true" : "false";
ss << "PrimingReport(success=" << successStr
<< ", status=" << PrimingReportStatusStrings[report.status] << ")";
return os << ss.str();
}
};
}

View File

@@ -0,0 +1,479 @@
#pragma once
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/engine/views/engine_view_abstract.h"
#include "gridfire/screening/screening_abstract.h"
#include "gridfire/screening/screening_types.h"
#include "gridfire/network.h"
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/config/config.h"
#include "fourdst/logging/logging.h"
#include "gridfire/engine/procedures/priming.h"
#include "gridfire/engine/procedures/construction.h"
#include "quill/Logger.h"
namespace gridfire {
/**
* @class AdaptiveEngineView
* @brief An engine view that dynamically adapts the reaction network based on runtime conditions.
*
* This class implements an EngineView that dynamically culls species and reactions from the
* full reaction network based on their reaction flow rates and connectivity. This allows for
* efficient simulation of reaction networks by focusing computational effort on the most
* important species and reactions.
*
* The AdaptiveEngineView maintains a subset of "active" species and reactions, and maps
* between the full network indices and the active subset indices. This allows the base engine
* to operate on the full network data, while the AdaptiveEngineView provides a reduced view
* for external clients.
*
* The adaptation process is driven by the `update()` method, which performs the following steps:
* 1. **Reaction Flow Calculation:** Calculates the molar reaction flow rate for each reaction
* in the full network based on the current temperature, density, and composition.
* 2. **Reaction Culling:** Culls reactions with flow rates below a threshold, determined by
* a relative culling threshold multiplied by the maximum flow rate.
* 3. **Connectivity Analysis:** Performs a connectivity analysis to identify species that are
* reachable from the initial fuel species through the culled reaction network.
* 4. **Species Culling:** Culls species that are not reachable from the initial fuel.
* 5. **Index Map Construction:** Constructs index maps to map between the full network indices
* and the active subset indices for species and reactions.
*
* @implements DynamicEngine
* @implements EngineView<DynamicEngine>
*
* @see engine_abstract.h
* @see engine_view_abstract.h
* @see AdaptiveEngineView::update()
*/
class AdaptiveEngineView final : public DynamicEngine, public EngineView<DynamicEngine> {
public:
/**
* @brief Constructs an AdaptiveEngineView.
*
* @param baseEngine The underlying DynamicEngine to which this view delegates calculations.
*
* Initializes the active species and reactions to the full network, and constructs the
* initial index maps.
*/
explicit AdaptiveEngineView(DynamicEngine& baseEngine);
/**
* @brief Updates the active species and reactions based on the current conditions.
*
* @param netIn The current network input, containing temperature, density, and composition.
*
* This method performs the reaction flow calculation, reaction culling, connectivity analysis,
* and index map construction steps described above.
*
* The culling thresholds are read from the configuration using the following keys:
* - `gridfire:AdaptiveEngineView:RelativeCullingThreshold` (default: 1e-75)
*
* @throws std::runtime_error If there is a mismatch between the active reactions and the base engine.
* @post The active species and reactions are updated, and the index maps are reconstructed.
* @see AdaptiveEngineView
* @see AdaptiveEngineView::constructSpeciesIndexMap()
* @see AdaptiveEngineView::constructReactionIndexMap()
*/
fourdst::composition::Composition update(const NetIn &netIn) override;
bool isStale(const NetIn& netIn) override;
/**
* @brief Gets the list of active species in the network.
* @return A const reference to the vector of active species.
*/
[[nodiscard]] const std::vector<fourdst::atomic::Species>& getNetworkSpecies() const override;
/**
* @brief Calculates the right-hand side (dY/dt) and energy generation for the active species.
*
* @param Y_culled A vector of abundances for the active species.
* @param T9 The temperature in units of 10^9 K.
* @param rho The density in g/cm^3.
* @return A StepDerivatives struct containing the derivatives of the active species and the
* nuclear energy generation rate.
*
* This method maps the culled abundances to the full network abundances, calls the base engine
* to calculate the RHS and energy generation, and then maps the full network derivatives back
* to the culled derivatives.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
* @see AdaptiveEngineView::update()
*/
[[nodiscard]] std::expected<StepDerivatives<double>, expectations::StaleEngineError> calculateRHSAndEnergy(
const std::vector<double> &Y_culled,
const double T9,
const double rho
) const override;
/**
* @brief Generates the Jacobian matrix for the active species.
*
* @param Y_dynamic A vector of abundances for the active species.
* @param T9 The temperature in units of 10^9 K.
* @param rho The density in g/cm^3.
*
* This method maps the culled abundances to the full network abundances and calls the base engine
* to generate the Jacobian matrix.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
* @see AdaptiveEngineView::update()
*/
void generateJacobianMatrix(
const std::vector<double> &Y_dynamic,
const double T9,
const double rho
) const override;
/**
* @brief Gets an entry from the Jacobian matrix for the active species.
*
* @param i_culled The row index (species index) in the culled matrix.
* @param j_culled The column index (species index) in the culled matrix.
* @return The value of the Jacobian matrix at (i_culled, j_culled).
*
* This method maps the culled indices to the full network indices and calls the base engine
* to get the Jacobian matrix entry.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
* @throws std::out_of_range If the culled index is out of bounds for the species index map.
* @see AdaptiveEngineView::update()
*/
[[nodiscard]] double getJacobianMatrixEntry(
const int i_culled,
const int j_culled
) const override;
/**
* @brief Generates the stoichiometry matrix for the active reactions and species.
*
* This method calls the base engine to generate the stoichiometry matrix.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
* @note The stoichiometry matrix generated by the base engine is assumed to be consistent with
* the active species and reactions in this view.
*/
void generateStoichiometryMatrix() override;
/**
* @brief Gets an entry from the stoichiometry matrix for the active species and reactions.
*
* @param speciesIndex_culled The index of the species in the culled species list.
* @param reactionIndex_culled The index of the reaction in the culled reaction list.
* @return The stoichiometric coefficient for the given species and reaction.
*
* This method maps the culled indices to the full network indices and calls the base engine
* to get the stoichiometry matrix entry.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
* @throws std::out_of_range If the culled index is out of bounds for the species or reaction index map.
* @see AdaptiveEngineView::update()
*/
[[nodiscard]] int getStoichiometryMatrixEntry(
const int speciesIndex_culled,
const int reactionIndex_culled
) const override;
/**
* @brief Calculates the molar reaction flow for a given reaction in the active network.
*
* @param reaction The reaction for which to calculate the flow.
* @param Y_culled Vector of current abundances for the active species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Molar flow rate for the reaction (e.g., mol/g/s).
*
* This method maps the culled abundances to the full network abundances and calls the base engine
* to calculate the molar reaction flow.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
* @throws std::runtime_error If the reaction is not part of the active reactions in the adaptive engine view.
*/
[[nodiscard]] double calculateMolarReactionFlow(
const reaction::Reaction &reaction,
const std::vector<double> &Y_culled,
double T9,
double rho
) const override;
/**
* @brief Gets the set of active logical reactions in the network.
*
* @return Reference to the LogicalReactionSet containing all active reactions.
*/
[[nodiscard]] const reaction::LogicalReactionSet& getNetworkReactions() const override;
void setNetworkReactions(const reaction::LogicalReactionSet& reactions) override;
/**
* @brief Computes timescales for all active species in the network.
*
* @param Y_culled Vector of current abundances for the active species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Map from Species to their characteristic timescales (s).
*
* This method maps the culled abundances to the full network abundances and calls the base engine
* to compute the species timescales.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
*/
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesTimescales(
const std::vector<double> &Y_culled,
double T9,
double rho
) const override;
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesDestructionTimescales(
const std::vector<double> &Y,
double T9,
double rho
) const override;
/**
* @brief Gets the base engine.
* @return A const reference to the base engine.
*/
[[nodiscard]] const DynamicEngine& getBaseEngine() const override { return m_baseEngine; }
/**
* @brief Sets the screening model for the base engine.
*
* This method delegates the call to the base engine to set the electron screening model.
*
* @param model The electron screening model to set.
*
* @par Usage Example:
* @code
* AdaptiveEngineView engineView(...);
* engineView.setScreeningModel(screening::ScreeningType::WEAK);
* @endcode
*
* @post The screening model of the base engine is updated.
*/
void setScreeningModel(screening::ScreeningType model) override;
/**
* @brief Gets the screening model from the base engine.
*
* This method delegates the call to the base engine to get the screening model.
*
* @return The current screening model type.
*
* @par Usage Example:
* @code
* AdaptiveEngineView engineView(...);
* screening::ScreeningType model = engineView.getScreeningModel();
* @endcode
*/
[[nodiscard]] screening::ScreeningType getScreeningModel() const override;
[[nodiscard]] int getSpeciesIndex(const fourdst::atomic::Species &species) const override;
[[nodiscard]] std::vector<double> mapNetInToMolarAbundanceVector(const NetIn &netIn) const override;
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
private:
using Config = fourdst::config::Config;
using LogManager = fourdst::logging::LogManager;
/** @brief A reference to the singleton Config instance, used for retrieving configuration parameters. */
Config& m_config = Config::getInstance();
/** @brief A pointer to the logger instance, used for logging messages. */
quill::Logger* m_logger = LogManager::getInstance().getLogger("log");
/** @brief The underlying engine to which this view delegates calculations. */
DynamicEngine& m_baseEngine;
/** @brief The set of species that are currently active in the network. */
std::vector<fourdst::atomic::Species> m_activeSpecies;
/** @brief The set of reactions that are currently active in the network. */
reaction::LogicalReactionSet m_activeReactions;
/** @brief A map from the indices of the active species to the indices of the corresponding species in the full network. */
std::vector<size_t> m_speciesIndexMap;
/** @brief A map from the indices of the active reactions to the indices of the corresponding reactions in the full network. */
std::vector<size_t> m_reactionIndexMap;
/** @brief A flag indicating whether the view is stale and needs to be updated. */
bool m_isStale = true;
private:
/**
* @brief A struct to hold a reaction and its flow rate.
*/
struct ReactionFlow {
const reaction::LogicalReaction* reactionPtr;
double flowRate;
};
private:
/**
* @brief Constructs the species index map.
*
* @return A vector mapping culled species indices to full species indices.
*
* This method creates a map from the indices of the active species to the indices of the
* corresponding species in the full network.
*
* @see AdaptiveEngineView::update()
*/
[[nodiscard]] std::vector<size_t> constructSpeciesIndexMap() const;
/**
* @brief Constructs the reaction index map.
*
* @return A vector mapping culled reaction indices to full reaction indices.
*
* This method creates a map from the indices of the active reactions to the indices of the
* corresponding reactions in the full network.
*
* @see AdaptiveEngineView::update()
*/
[[nodiscard]] std::vector<size_t> constructReactionIndexMap() const;
/**
* @brief Maps a vector of culled abundances to a vector of full abundances.
*
* @param culled A vector of abundances for the active species.
* @return A vector of abundances for the full network, with the abundances of the active
* species copied from the culled vector.
*/
[[nodiscard]] std::vector<double> mapCulledToFull(const std::vector<double>& culled) const;
/**
* @brief Maps a vector of full abundances to a vector of culled abundances.
*
* @param full A vector of abundances for the full network.
* @return A vector of abundances for the active species, with the abundances of the active
* species copied from the full vector.
*/
[[nodiscard]] std::vector<double> mapFullToCulled(const std::vector<double>& full) const;
/**
* @brief Maps a culled species index to a full species index.
*
* @param culledSpeciesIndex The index of the species in the culled species list.
* @return The index of the corresponding species in the full network.
*
* @throws std::out_of_range If the culled index is out of bounds for the species index map.
*/
[[nodiscard]] size_t mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const;
/**
* @brief Maps a culled reaction index to a full reaction index.
*
* @param culledReactionIndex The index of the reaction in the culled reaction list.
* @return The index of the corresponding reaction in the full network.
*
* @throws std::out_of_range If the culled index is out of bounds for the reaction index map.
*/
[[nodiscard]] size_t mapCulledToFullReactionIndex(size_t culledReactionIndex) const;
/**
* @brief Validates that the AdaptiveEngineView is not stale.
*
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
*/
void validateState() const;
/**
* @brief Calculates the molar reaction flow rate for all reactions in the full network.
*
* This method iterates through all reactions in the base engine's network and calculates
* their molar flow rates based on the provided network input conditions (temperature, density,
* and composition). It also constructs a vector of molar abundances for all species in the
* full network.
*
* @param netIn The current network input, containing temperature, density, and composition.
* @param out_Y_Full A vector that will be populated with the molar abundances of all species in the full network.
* @return A vector of ReactionFlow structs, each containing a pointer to a reaction and its calculated flow rate.
*
* @par Algorithm:
* 1. Clears and reserves space in `out_Y_Full`.
* 2. Iterates through all species in the base engine's network.
* 3. For each species, it retrieves the molar abundance from `netIn.composition`. If the species is not found, its abundance is set to 0.0.
* 4. Converts the temperature from Kelvin to T9.
* 5. Iterates through all reactions in the base engine's network.
* 6. For each reaction, it calls the base engine's `calculateMolarReactionFlow` to get the flow rate.
* 7. Stores the reaction pointer and its flow rate in a `ReactionFlow` struct and adds it to the returned vector.
*/
std::vector<ReactionFlow> calculateAllReactionFlows(
const NetIn& netIn,
std::vector<double>& out_Y_Full
) const;
/**
* @brief Finds all species that are reachable from the initial fuel through the reaction network.
*
* This method performs a connectivity analysis to identify all species that can be produced
* starting from the initial fuel species. A species is considered part of the initial fuel if its
* mass fraction is above a certain threshold (`ABUNDANCE_FLOOR`).
*
* @param netIn The current network input, containing the initial composition.
* @return An unordered set of all reachable species.
*
* @par Algorithm:
* 1. Initializes a set `reachable` and a queue `to_visit` with the initial fuel species.
* 2. Iteratively processes the reaction network until no new species can be reached.
* 3. In each pass, it iterates through all reactions in the base engine's network.
* 4. If all reactants of a reaction are in the `reachable` set, all products of that reaction are added to the `reachable` set.
* 5. The process continues until a full pass over all reactions does not add any new species to the `reachable` set.
*/
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> findReachableSpecies(
const NetIn& netIn
) const;
/**
* @brief Culls reactions from the network based on their flow rates.
*
* This method filters the list of all reactions, keeping only those with a flow rate
* above an absolute culling threshold. The threshold is calculated by multiplying the
* maximum flow rate by a relative culling threshold read from the configuration.
*
* @param allFlows A vector of all reactions and their flow rates.
* @param reachableSpecies A set of all species reachable from the initial fuel.
* @param Y_full A vector of molar abundances for all species in the full network.
* @param maxFlow The maximum reaction flow rate in the network.
* @return A vector of pointers to the reactions that have been kept after culling.
*
* @par Algorithm:
* 1. Retrieves the `RelativeCullingThreshold` from the configuration.
* 2. Calculates the `absoluteCullingThreshold` by multiplying `maxFlow` with the relative threshold.
* 3. Iterates through `allFlows`.
* 4. A reaction is kept if its `flowRate` is greater than the `absoluteCullingThreshold`.
* 5. The pointers to the kept reactions are stored in a vector and returned.
*/
[[nodiscard]] std::vector<const reaction::LogicalReaction*> cullReactionsByFlow(
const std::vector<ReactionFlow>& allFlows,
const std::unordered_set<fourdst::atomic::Species>& reachableSpecies,
const std::vector<double>& Y_full,
double maxFlow
) const;
typedef std::pair<std::unordered_set<const reaction::LogicalReaction*>, std::unordered_set<fourdst::atomic::Species>> RescueSet;
[[nodiscard]] RescueSet rescueEdgeSpeciesDestructionChannel(
const std::vector<double>& Y_full,
const double T9,
const double rho,
const std::vector<fourdst::atomic::Species>& activeSpecies,
const reaction::LogicalReactionSet& activeReactions
) const;
/**
* @brief Finalizes the set of active species and reactions.
*
* This method takes the final list of culled reactions and populates the
* `m_activeReactions` and `m_activeSpecies` members. The active species are
* determined by collecting all reactants and products from the final reactions.
* The active species list is then sorted by mass.
*
* @param finalReactions A vector of pointers to the reactions to be included in the active set.
*
* @post
* - `m_activeReactions` is cleared and populated with the reactions from `finalReactions`.
* - `m_activeSpecies` is cleared and populated with all unique species present in `finalReactions`.
* - `m_activeSpecies` is sorted by atomic mass.
*/
void finalizeActiveSet(
const std::vector<const reaction::LogicalReaction*>& finalReactions
);
};
}

View File

@@ -0,0 +1,277 @@
#pragma once
#include "gridfire/engine/views/engine_view_abstract.h"
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/io/network_file.h"
#include "gridfire/network.h"
#include "fourdst/config/config.h"
#include "fourdst/logging/logging.h"
#include "quill/Logger.h"
#include <string>
namespace gridfire{
class DefinedEngineView : public DynamicEngine, public EngineView<DynamicEngine> {
public:
DefinedEngineView(const std::vector<std::string>& peNames, DynamicEngine& baseEngine);
const DynamicEngine& getBaseEngine() const override;
// --- Engine Interface ---
/**
* @brief Gets the list of active species in the network defined by the file.
* @return A const reference to the vector of active species.
*/
const std::vector<fourdst::atomic::Species>& getNetworkSpecies() const override;
// --- DynamicEngine Interface ---
/**
* @brief Calculates the right-hand side (dY/dt) and energy generation for the active species.
*
* @param Y_defined A vector of abundances for the active species.
* @param T9 The temperature in units of 10^9 K.
* @param rho The density in g/cm^3.
* @return A StepDerivatives struct containing the derivatives of the active species and the
* nuclear energy generation rate.
*
* @throws std::runtime_error If the view is stale (i.e., `update()` has not been called after `setNetworkFile()`).
*/
std::expected<StepDerivatives<double>, expectations::StaleEngineError> calculateRHSAndEnergy(
const std::vector<double>& Y_defined,
const double T9,
const double rho
) const override;
/**
* @brief Generates the Jacobian matrix for the active species.
*
* @param Y_dynamic A vector of abundances for the active species.
* @param T9 The temperature in units of 10^9 K.
* @param rho The density in g/cm^3.
*
* @throws std::runtime_error If the view is stale.
*/
void generateJacobianMatrix(
const std::vector<double>& Y_dynamic,
const double T9,
const double rho
) const override;
/**
* @brief Gets an entry from the Jacobian matrix for the active species.
*
* @param i_defined The row index (species index) in the defined matrix.
* @param j_defined The column index (species index) in the defined matrix.
* @return The value of the Jacobian matrix at (i_defined, j_defined).
*
* @throws std::runtime_error If the view is stale.
* @throws std::out_of_range If an index is out of bounds.
*/
double getJacobianMatrixEntry(
const int i_defined,
const int j_defined
) const override;
/**
* @brief Generates the stoichiometry matrix for the active reactions and species.
*
* @throws std::runtime_error If the view is stale.
*/
void generateStoichiometryMatrix() override;
/**
* @brief Gets an entry from the stoichiometry matrix for the active species and reactions.
*
* @param speciesIndex_defined The index of the species in the defined species list.
* @param reactionIndex_defined The index of the reaction in the defined reaction list.
* @return The stoichiometric coefficient for the given species and reaction.
*
* @throws std::runtime_error If the view is stale.
* @throws std::out_of_range If an index is out of bounds.
*/
int getStoichiometryMatrixEntry(
const int speciesIndex_defined,
const int reactionIndex_defined
) const override;
/**
* @brief Calculates the molar reaction flow for a given reaction in the active network.
*
* @param reaction The reaction for which to calculate the flow.
* @param Y_defined Vector of current abundances for the active species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Molar flow rate for the reaction (e.g., mol/g/s).
*
* @throws std::runtime_error If the view is stale or if the reaction is not in the active set.
*/
double calculateMolarReactionFlow(
const reaction::Reaction& reaction,
const std::vector<double>& Y_defined,
const double T9,
const double rho
) const override;
/**
* @brief Gets the set of active logical reactions in the network.
*
* @return Reference to the LogicalReactionSet containing all active reactions.
*
* @throws std::runtime_error If the view is stale.
*/
const reaction::LogicalReactionSet& getNetworkReactions() const override;
void setNetworkReactions(const reaction::LogicalReactionSet& reactions) override;
/**
* @brief Computes timescales for all active species in the network.
*
* @param Y_defined Vector of current abundances for the active species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
* @return Map from Species to their characteristic timescales (s).
*
* @throws std::runtime_error If the view is stale.
*/
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesTimescales(
const std::vector<double>& Y_defined,
const double T9,
const double rho
) const override;
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesDestructionTimescales(
const std::vector<double>& Y_defined,
const double T9,
const double rho
) const override;
/**
* @brief Updates the engine view if it is marked as stale.
*
* This method checks if the view is stale (e.g., after `setNetworkFile` was called).
* If it is, it rebuilds the active network from the currently set file.
* The `netIn` parameter is not used by this implementation but is required by the interface.
*
* @param netIn The current network input (unused).
*
* @post If the view was stale, it is rebuilt and is no longer stale.
*/
fourdst::composition::Composition update(const NetIn &netIn) override;
bool isStale(const NetIn& netIn) override;
/**
* @brief Sets the screening model for the base engine.
*
* @param model The screening model to set.
*/
void setScreeningModel(screening::ScreeningType model) override;
/**
* @brief Gets the screening model from the base engine.
*
* @return The current screening model type.
*/
[[nodiscard]] screening::ScreeningType getScreeningModel() const override;
[[nodiscard]] int getSpeciesIndex(const fourdst::atomic::Species &species) const override;
[[nodiscard]] std::vector<double> mapNetInToMolarAbundanceVector(const NetIn &netIn) const override;
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
protected:
bool m_isStale = true;
DynamicEngine& m_baseEngine;
private:
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log"); ///< Logger instance for trace and debug information.
///< Active species in the defined engine.
std::vector<fourdst::atomic::Species> m_activeSpecies;
///< Active reactions in the defined engine.
reaction::LogicalReactionSet m_activeReactions;
///< Maps indices of active species to indices in the full network.
std::vector<size_t> m_speciesIndexMap;
///< Maps indices of active reactions to indices in the full network.
std::vector<size_t> m_reactionIndexMap;
private:
/**
* @brief Constructs the species index map.
*
* @return A vector mapping defined species indices to full species indices.
*
* This method creates a map from the indices of the active species to the indices of the
* corresponding species in the full network.
*
* @throws std::runtime_error If an active species is not found in the base engine's species list.
*/
std::vector<size_t> constructSpeciesIndexMap() const;
/**
* @brief Constructs the reaction index map.
*
* @return A vector mapping defined reaction indices to full reaction indices.
*
* This method creates a map from the indices of the active reactions to the indices of the
* corresponding reactions in the full network.
*
* @throws std::runtime_error If an active reaction is not found in the base engine's reaction list.
*/
std::vector<size_t> constructReactionIndexMap() const;
/**
* @brief Maps a vector of culled abundances to a vector of full abundances.
*
* @param defined A vector of abundances for the active species.
* @return A vector of abundances for the full network, with the abundances of the active
* species copied from the defined vector.
*/
std::vector<double> mapViewToFull(const std::vector<double>& defined) const;
/**
* @brief Maps a vector of full abundances to a vector of culled abundances.
*
* @param full A vector of abundances for the full network.
* @return A vector of abundances for the active species, with the abundances of the active
* species copied from the full vector.
*/
std::vector<double> mapFullToView(const std::vector<double>& full) const;
/**
* @brief Maps a culled species index to a full species index.
*
* @param definedSpeciesIndex The index of the species in the defined species list.
* @return The index of the corresponding species in the full network.
*
* @throws std::out_of_range If the defined index is out of bounds for the species index map.
*/
size_t mapViewToFullSpeciesIndex(size_t definedSpeciesIndex) const;
/**
* @brief Maps a culled reaction index to a full reaction index.
*
* @param definedReactionIndex The index of the reaction in the defined reaction list.
* @return The index of the corresponding reaction in the full network.
*
* @throws std::out_of_range If the defined index is out of bounds for the reaction index map.
*/
size_t mapViewToFullReactionIndex(size_t definedReactionIndex) const;
void validateNetworkState() const;
void collect(const std::vector<std::string>& peNames);
};
class FileDefinedEngineView final: public DefinedEngineView {
public:
explicit FileDefinedEngineView(
DynamicEngine& baseEngine,
const std::string& fileName,
const io::NetworkFileParser& parser
);
std::string getNetworkFile() const { return m_fileName; }
const io::NetworkFileParser& getParser() const { return m_parser; }
private:
using Config = fourdst::config::Config;
using LogManager = fourdst::logging::LogManager;
Config& m_config = Config::getInstance();
quill::Logger* m_logger = LogManager::getInstance().getLogger("log");
std::string m_fileName;
///< Parser for the network file.
const io::NetworkFileParser& m_parser;
};
}

View File

@@ -0,0 +1,392 @@
#pragma once
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/engine/views/engine_view_abstract.h"
#include "gridfire/engine/engine_graph.h"
#include "unsupported/Eigen/NonLinearOptimization"
namespace gridfire {
struct QSECacheConfig {
double T9_tol; ///< Absolute tolerance to produce the same hash for T9.
double rho_tol; ///< Absolute tolerance to produce the same hash for rho.
double Yi_tol; ///< Absolute tolerance to produce the same hash for species abundances.
};
struct QSECacheKey {
double m_T9;
double m_rho;
std::vector<double> m_Y; ///< Note that the ordering of Y must match the dynamic species indices in the view.
std::size_t m_hash = 0; ///< Precomputed hash value for this key.
// TODO: We should probably sort out how to adjust these from absolute to relative tolerances.
QSECacheConfig m_cacheConfig = {
1e-3, // Default tolerance for T9
1e-1, // Default tolerance for rho
1e-3 // Default tolerance for species abundances
};
QSECacheKey(
const double T9,
const double rho,
const std::vector<double>& Y
);
size_t hash() const;
static long bin(double value, double tol);
bool operator==(const QSECacheKey& other) const;
};
}
// Needs to be in this order (splitting gridfire namespace up) to avoid some issues with forward declarations and the () operator.
namespace std {
template <>
struct hash<gridfire::QSECacheKey> {
/**
* @brief Computes the hash of a QSECacheKey.
* @param key The QSECacheKey to hash.
* @return The pre-computed hash value of the key.
*/
size_t operator()(const gridfire::QSECacheKey& key) const noexcept {
// The hash is pre-computed, so we just return it.
return key.m_hash;
}
};
} // namespace std
namespace gridfire {
class MultiscalePartitioningEngineView final: public DynamicEngine, public EngineView<DynamicEngine> {
typedef std::tuple<std::vector<fourdst::atomic::Species>, std::vector<size_t>, std::vector<fourdst::atomic::Species>, std::vector<size_t>> QSEPartition;
public:
explicit MultiscalePartitioningEngineView(GraphEngine& baseEngine);
[[nodiscard]] const std::vector<fourdst::atomic::Species> & getNetworkSpecies() const override;
[[nodiscard]] std::expected<StepDerivatives<double>, expectations::StaleEngineError> calculateRHSAndEnergy(
const std::vector<double> &Y_full,
double T9,
double rho
) const override;
void generateJacobianMatrix(
const std::vector<double> &Y_full,
double T9,
double rho
) const override;
[[nodiscard]] double getJacobianMatrixEntry(
int i_full,
int j_full
) const override;
void generateStoichiometryMatrix() override;
[[nodiscard]] int getStoichiometryMatrixEntry(
int speciesIndex,
int reactionIndex
) const override;
[[nodiscard]] double calculateMolarReactionFlow(
const reaction::Reaction &reaction,
const std::vector<double> &Y_full,
double T9,
double rho
) const override;
[[nodiscard]] const reaction::LogicalReactionSet & getNetworkReactions() const override;
void setNetworkReactions(
const reaction::LogicalReactionSet &reactions
) override;
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesTimescales(
const std::vector<double> &Y,
double T9,
double rho
) const override;
[[nodiscard]] std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> getSpeciesDestructionTimescales(
const std::vector<double> &Y,
double T9,
double rho
) const override;
fourdst::composition::Composition update(
const NetIn &netIn
) override;
bool isStale(const NetIn& netIn) override;
void setScreeningModel(
screening::ScreeningType model
) override;
[[nodiscard]] screening::ScreeningType getScreeningModel() const override;
const DynamicEngine & getBaseEngine() const override;
std::vector<std::vector<size_t>> analyzeTimescalePoolConnectivity(
const std::vector<std::vector<size_t>> &timescale_pools,
const std::vector<double> &Y,
double T9,
double rho
) const;
void partitionNetwork(
const std::vector<double>& Y,
double T9,
double rho
);
void partitionNetwork(
const NetIn& netIn
);
void exportToDot(
const std::string& filename,
const std::vector<double>& Y,
const double T9,
const double rho
) const;
[[nodiscard]] int getSpeciesIndex(const fourdst::atomic::Species &species) const override;
[[nodiscard]] std::vector<double> mapNetInToMolarAbundanceVector(const NetIn &netIn) const override;
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
[[nodiscard]] std::vector<fourdst::atomic::Species> getFastSpecies() const;
[[nodiscard]] const std::vector<fourdst::atomic::Species>& getDynamicSpecies() const;
fourdst::composition::Composition equilibrateNetwork(
const std::vector<double> &Y,
double T9,
double rho
);
fourdst::composition::Composition equilibrateNetwork(
const NetIn &netIn
);
private:
struct QSEGroup {
std::set<size_t> species_indices; ///< Indices of all species in this group.
bool is_in_equilibrium = false; ///< Flag set by flux analysis.
std::set<size_t> algebraic_indices; ///< Indices of algebraic species in this group.
std::set<size_t> seed_indices; ///< Indices of dynamic species in this group.
double mean_timescale; ///< Mean timescale of the group.
bool operator<(const QSEGroup& other) const;
bool operator>(const QSEGroup& other) const;
bool operator==(const QSEGroup& other) const;
bool operator!=(const QSEGroup& other) const;
friend std::ostream& operator<<(std::ostream& os, const QSEGroup& group) {
os << "QSEGroup(species_indices: [";
int count = 0;
for (const auto& idx : group.species_indices) {
os << idx;
if (count < group.species_indices.size() - 1) {
os << ", ";
}
count++;
}
count = 0;
os << "], is_in_equilibrium: " << group.is_in_equilibrium
<< ", mean_timescale: " << group.mean_timescale
<< " s, algebraic_indices: [";
for (const auto& idx : group.algebraic_indices) {
os << idx;
if (count < group.algebraic_indices.size() - 1) {
os << ", ";
}
count++;
}
count = 0;
os << "], seed_indices: [";
for (const auto& idx : group.seed_indices) {
os << idx;
if (count < group.seed_indices.size() - 1) {
os << ", ";
}
count++;
}
os << "])";
return os;
}
};
struct EigenFunctor {
using InputType = Eigen::Matrix<double, Eigen::Dynamic, 1>;
using OutputType = Eigen::Matrix<double, Eigen::Dynamic, 1>;
using JacobianType = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>;
enum {
InputsAtCompileTime = Eigen::Dynamic,
ValuesAtCompileTime = Eigen::Dynamic
};
MultiscalePartitioningEngineView* m_view;
const std::vector<size_t>& m_qse_solve_indices;
const std::vector<double>& m_Y_full_initial;
const double m_T9;
const double m_rho;
const Eigen::VectorXd& m_Y_scale;
EigenFunctor(
MultiscalePartitioningEngineView& view,
const std::vector<size_t>& qse_solve_indices,
const std::vector<double>& Y_full_initial,
const double T9,
const double rho,
const Eigen::VectorXd& Y_scale
) :
m_view(&view),
m_qse_solve_indices(qse_solve_indices),
m_Y_full_initial(Y_full_initial),
m_T9(T9),
m_rho(rho),
m_Y_scale(Y_scale) {}
[[nodiscard]] int values() const { return m_qse_solve_indices.size(); }
[[nodiscard]] int inputs() const { return m_qse_solve_indices.size(); }
int operator()(const InputType& v_qse, OutputType& f_qse) const;
int df(const InputType& v_qse, JacobianType& J_qse) const;
};
struct CacheStats {
enum class operators {
CalculateRHSAndEnergy,
GenerateJacobianMatrix,
CalculateMolarReactionFlow,
GetSpeciesTimescales,
GetSpeciesDestructionTimescales,
Other,
All
};
std::map<operators, std::string> operatorsNameMap = {
{operators::CalculateRHSAndEnergy, "calculateRHSAndEnergy"},
{operators::GenerateJacobianMatrix, "generateJacobianMatrix"},
{operators::CalculateMolarReactionFlow, "calculateMolarReactionFlow"},
{operators::GetSpeciesTimescales, "getSpeciesTimescales"},
{operators::GetSpeciesDestructionTimescales, "getSpeciesDestructionTimescales"},
{operators::Other, "other"}
};
size_t m_hit = 0;
size_t m_miss = 0;
std::map<operators, size_t> m_operatorHits = {
{operators::CalculateRHSAndEnergy, 0},
{operators::GenerateJacobianMatrix, 0},
{operators::CalculateMolarReactionFlow, 0},
{operators::GetSpeciesTimescales, 0},
{operators::GetSpeciesDestructionTimescales, 0},
{operators::Other, 0}
};
friend std::ostream& operator<<(std::ostream& os, const CacheStats& stats) {
os << "CacheStats(hit: " << stats.m_hit << ", miss: " << stats.m_miss << ")";
return os;
}
void hit(const operators op=operators::Other) {
if (op==operators::All) {
throw std::invalid_argument("Cannot use 'All' as an operator for hit/miss.");
}
m_hit++;
m_operatorHits[op]++;
}
void miss(const operators op=operators::Other) {
if (op==operators::All) {
throw std::invalid_argument("Cannot use 'All' as an operator for hit/miss.");
}
m_miss++;
m_operatorHits[op]++;
}
[[nodiscard]] size_t hits(const operators op=operators::All) const {
if (op==operators::All) {
return m_hit;
}
return m_operatorHits.at(op);
}
[[nodiscard]] size_t misses(const operators op=operators::All) const {
if (op==operators::All) {
return m_miss;
}
return m_operatorHits.at(op);
}
};
private:
quill::Logger* m_logger = LogManager::getInstance().getLogger("log");
GraphEngine& m_baseEngine; ///< The base engine to which this view delegates calculations.
std::vector<QSEGroup> m_qse_groups; ///< The list of identified equilibrium groups.
std::vector<fourdst::atomic::Species> m_dynamic_species; ///< The simplified set of species presented to the solver.
std::vector<size_t> m_dynamic_species_indices; ///< Indices mapping the dynamic species back to the master engine's list.
std::vector<fourdst::atomic::Species> m_algebraic_species; ///< Species that are algebraic in the QSE groups.
std::vector<size_t> m_algebraic_species_indices; ///< Indices of algebraic species in the full network.
std::vector<size_t> m_activeSpeciesIndices; ///< Indices of active species in the full network.
std::vector<size_t> m_activeReactionIndices; ///< Indices of active reactions in the full network.
// TODO: Enhance the hashing for the cache to consider not just T and rho but also the current abundance in some careful way that automatically ignores small changes (i.e. network should only be repartitioned sometimes)
std::unordered_map<QSECacheKey, std::vector<double>> m_qse_abundance_cache; ///< Cache for QSE abundances based on T9 and rho.
mutable CacheStats m_cacheStats; ///< Statistics for the QSE abundance cache.
private:
std::vector<std::vector<size_t>> partitionByTimescale(
const std::vector<double> &Y_full,
double T9,
double rho
) const;
std::unordered_map<size_t, std::vector<size_t>> buildConnectivityGraph(
const std::unordered_set<size_t> &fast_reaction_indices
) const;
std::vector<QSEGroup> validateGroupsWithFluxAnalysis(
const std::vector<QSEGroup> &candidate_groups,
const std::vector<double>& Y,
double T9,
double rho
) const;
std::vector<double> solveQSEAbundances(
const std::vector<double> &Y_full,
double T9,
double rho
);
size_t identifyMeanSlowestPool(
const std::vector<std::vector<size_t>>& pools,
const std::vector<double> &Y,
double T9,
double rho
) const;
std::unordered_map<size_t, std::vector<size_t>> buildConnectivityGraph(
const std::vector<size_t>& species_pool
) const;
std::vector<QSEGroup> constructCandidateGroups(
const std::vector<std::vector<size_t>>& candidate_pools,
const std::vector<double>& Y,
double T9, double rho
) const;
};
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/engine/views/engine_defined.h"
#include "gridfire/network.h"
#include "fourdst/logging/logging.h"
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/composition/composition.h"
#include "quill/Logger.h"
#include <vector>
#include <string>
namespace gridfire {
class NetworkPrimingEngineView final : public DefinedEngineView {
public:
NetworkPrimingEngineView(const std::string& primingSymbol, DynamicEngine& baseEngine);
NetworkPrimingEngineView(const fourdst::atomic::Species& primingSpecies, DynamicEngine& baseEngine);
private:
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
fourdst::atomic::Species m_primingSpecies; ///< The priming species, if specified.
private:
std::vector<std::string> constructPrimingReactionSet(
const fourdst::atomic::Species& primingSpecies,
const DynamicEngine& baseEngine
) const;
};
}

View File

@@ -0,0 +1,98 @@
#pragma once
#include "gridfire/engine/engine_abstract.h"
/**
* @file engine_view_abstract.h
* @brief Abstract interfaces for engine "views" in GridFire.
*
* This header defines the abstract base classes and concepts for "views" of reaction network engines.
* The primary purpose of an EngineView is to enable dynamic or adaptive network topologies
* (such as species/reaction culling, masking, or remapping) without modifying the underlying
* physics engine or its implementation. Engine views act as a flexible interface layer,
* allowing the network structure to be changed at runtime while preserving the core
* physics and solver logic in the base engine.
*
* Typical use cases include:
* - Adaptive or reduced networks for computational efficiency.
* - Dynamic masking or culling of species/reactions based on runtime criteria.
* - Providing a filtered or remapped view of the network for integration or analysis.
* - Supporting dynamic network reconfiguration in multi-zone or multi-physics contexts.
*
* The base engine types referenced here are defined in @ref engine_abstract.h.
*
* @author
* Emily M. Boudreaux
*/
namespace gridfire {
/**
* @brief Concept for types allowed as engine bases in EngineView.
*
* This concept restricts template parameters to types derived from either
* gridfire::Engine or gridfire::DynamicEngine, as defined in engine_abstract.h.
*
* Example usage:
* @code
* static_assert(EngineType<MyEngine>);
* @endcode
*/
template<typename EngineT>
concept EngineType = std::is_base_of_v<Engine, EngineT> || std::is_base_of_v<DynamicEngine, EngineT>;
/**
* @brief Abstract base class for a "view" of a reaction network engine.
*
* @tparam EngineT The engine type being viewed (must satisfy EngineType).
*
* EngineView provides an interface for accessing an underlying engine instance,
* while presenting a potentially modified or reduced network structure to the user.
* This enables dynamic or adaptive network topologies (e.g., culling, masking, or
* remapping of species and reactions) without altering the core physics engine.
*
* Intended usage: Derive from this class to implement a custom view or wrapper
* that manages a dynamic or adaptive network structure, delegating core calculations
* to the base engine. The contract is that getBaseEngine() must return a reference
* to the underlying engine instance, which remains responsible for the full physics.
*
* Example (see also AdaptiveEngineView):
* @code
* class MyAdaptiveView : public gridfire::EngineView<DynamicEngine> {
* public:
* MyAdaptiveView(DynamicEngine& engine) : engine_(engine) {}
* const DynamicEngine& getBaseEngine() const override { return engine_; }
* // Implement dynamic masking/culling logic...
* private:
* DynamicEngine& engine_;
* };
* @endcode
*
* @see gridfire::AdaptiveEngineView for a concrete example of dynamic culling.
*/
template<EngineType EngineT>
class EngineView {
public:
/**
* @brief Virtual destructor.
*/
virtual ~EngineView() = default;
/**
* @brief Access the underlying engine instance.
*
* @return Const reference to the underlying engine.
*
* This method must be implemented by derived classes to provide access
* to the base engine. The returned reference should remain valid for the
* lifetime of the EngineView.
*
* Example:
* @code
* const DynamicEngine& engine = myView.getBaseEngine();
* @endcode
*/
virtual const EngineT& getBaseEngine() const = 0;
};
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "gridfire/engine/views/engine_adaptive.h"
#include "gridfire/engine/views/engine_defined.h"
#include "gridfire/engine/views/engine_multiscale.h"
#include "gridfire/engine/views/engine_priming.h"
#include "gridfire/engine/views/engine_view_abstract.h"

View File

@@ -0,0 +1,112 @@
#pragma once
#include <exception>
#include <string>
#include <iostream>
namespace gridfire::exceptions {
class EngineError : public std::exception {};
class StaleEngineTrigger final : public EngineError {
public:
struct state {
double m_T9;
double m_rho;
std::vector<double> m_Y;
double m_t;
int m_total_steps;
double m_eps_nuc;
};
explicit StaleEngineTrigger(const state &s)
: m_state(s) {}
const char* what() const noexcept override{
return "Engine reports stale state. This means that the caller should trigger a update of the engine state before continuing with the integration. If you as an end user are seeing this error, it is likely a bug in the code that should be reported. Please provide the input parameters and the context in which this error occurred. Thank you for your help!";
}
state getState() const {
return m_state;
}
size_t numSpecies() const {
return m_state.m_Y.size();
}
size_t totalSteps() const {
return m_state.m_total_steps;
}
double energy() const {
return m_state.m_eps_nuc;
}
double getMolarAbundance(const size_t index) const {
if (index > m_state.m_Y.size() - 1) {
throw std::out_of_range("Index out of bounds for molar abundance vector.");
}
return m_state.m_Y[index];
}
double temperature() const {
return m_state.m_T9 * 1e9; // Convert T9 back to Kelvin
}
double density() const {
return m_state.m_rho;
}
private:
state m_state;
};
class StaleEngineError final : public EngineError {
public:
explicit StaleEngineError(const std::string& message)
: m_message(message) {}
const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
class FailedToPartitionEngineError final : public EngineError {
public:
explicit FailedToPartitionEngineError(const std::string& message)
: m_message(message) {}
const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
class NetworkResizedError final : public EngineError {
public:
explicit NetworkResizedError(const std::string& message)
: m_message(message) {}
const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
class UnableToSetNetworkReactionsError final : public EngineError {
public:
explicit UnableToSetNetworkReactionsError(const std::string& message)
: m_message(message) {}
const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
}

View File

@@ -0,0 +1,3 @@
#pragma once
#include "gridfire/exceptions/error_engine.h"

View File

@@ -0,0 +1,3 @@
#pragma once
#include "gridfire/expectations/expected_engine.h"

View File

@@ -0,0 +1,59 @@
#pragma once
#include <string>
#include <iostream>
namespace gridfire::expectations {
enum class EngineErrorTypes {
FAILURE,
INDEX,
STALE
};
enum class StaleEngineErrorTypes {
SYSTEM_RESIZED
};
// TODO: rename this to EngineExpectation or something similar
struct EngineError {
std::string m_message;
const EngineErrorTypes type = EngineErrorTypes::FAILURE;
explicit EngineError(const std::string &message, const EngineErrorTypes type)
: m_message(message), type(type) {}
virtual ~EngineError() = default;
friend std::ostream& operator<<(std::ostream& os, const EngineError& e) {
os << "EngineError: " << e.m_message;
return os;
}
};
struct EngineIndexError : EngineError {
int m_index;
explicit EngineIndexError(const int index)
: EngineError("Index error occurred", EngineErrorTypes::INDEX), m_index(index) {}
friend std::ostream& operator<<(std::ostream& os, const EngineIndexError& e) {
os << "EngineIndexError: " << e.m_message << " at index " << e.m_index;
return os;
}
};
struct StaleEngineError : EngineError {
StaleEngineErrorTypes staleType;
explicit StaleEngineError(const StaleEngineErrorTypes sType)
: EngineError("Stale engine error occurred", EngineErrorTypes::STALE), staleType(sType) {}
explicit operator std::string() const {
switch (staleType) {
case (StaleEngineErrorTypes::SYSTEM_RESIZED):
return "StaleEngineError: System resized, please update the engine.";
default:
return "StaleEngineError: Unknown stale error type.";
}
}
};
}

View File

@@ -0,0 +1,3 @@
#pragma once
#include "gridfire/io/network_file.h"

View File

@@ -0,0 +1,151 @@
#pragma once
#include "fourdst/config/config.h"
#include "fourdst/logging/logging.h"
#include "quill/Logger.h"
#include <string>
#include <vector>
namespace gridfire::io {
typedef std::vector<std::string> ParsedNetworkData;
/**
* @class NetworkFileParser
* @brief An abstract base class for network file parsers.
*
* This class defines the interface for parsing files that contain
* reaction network definitions. Derived classes must implement the `parse`
* method to handle specific file formats.
*/
class NetworkFileParser {
public:
/**
* @brief Virtual destructor for the base class.
*/
virtual ~NetworkFileParser() = default;
/**
* @brief Parses a network file and returns the parsed data.
*
* This is a pure virtual function that must be implemented by derived
* classes. It takes a filename as input and returns a `ParsedNetworkData`
* struct containing the information extracted from the file.
*
* @param filename The path to the network file to parse.
* @return A `ParsedNetworkData` struct containing the parsed reaction data.
*
* @throws std::runtime_error If the file cannot be opened or a parsing
* error occurs.
*
* @b Usage
* @code
* std::unique_ptr<NetworkFileParser> parser = std::make_unique<SimpleReactionListFileParser>();
* try {
* ParsedNetworkData data = parser->parse("my_reactions.txt");
* for (const auto& reaction_name : data.reactionPENames) {
* // ... process reaction name
* }
* } catch (const std::runtime_error& e) {
* // ... handle error
* }
* @endcode
*/
[[nodiscard]] virtual ParsedNetworkData parse(const std::string& filename) const = 0;
};
/**
* @class SimpleReactionListFileParser
* @brief A parser for simple text files containing a list of reactions.
*
* This parser reads a file where each line contains a single reaction name.
* It supports comments (lines starting with '#') and ignores empty lines.
*
* @implements NetworkFileParser
*/
class SimpleReactionListFileParser final : public NetworkFileParser {
public:
/**
* @brief Constructs a SimpleReactionListFileParser.
*
* @post The parser is initialized and ready to parse files.
*/
explicit SimpleReactionListFileParser();
/**
* @brief Parses a simple reaction list file.
*
* This method reads the specified file line by line. It trims whitespace
* from each line, ignores lines that are empty or start with a '#'
* comment character, and stores the remaining lines as reaction names.
*
* @param filename The path to the simple reaction list file.
* @return A `ParsedNetworkData` struct containing the list of reaction names.
*
* @throws std::runtime_error If the file cannot be opened for reading.
*
* @b Algorithm
* 1. Opens the specified file.
* 2. Reads the file line by line.
* 3. For each line, it removes any trailing comments (starting with '#').
* 4. Trims leading and trailing whitespace.
* 5. If the line is not empty, it is added to the list of reaction names.
* 6. Returns the populated `ParsedNetworkData` struct.
*
* @b Usage
* @code
* SimpleReactionListFileParser parser;
* ParsedNetworkData data = parser.parse("reactions.txt");
* @endcode
*/
ParsedNetworkData parse(const std::string& filename) const override;
private:
using Config = fourdst::config::Config;
using LogManager = fourdst::logging::LogManager;
Config& m_config = Config::getInstance();
quill::Logger* m_logger = LogManager::getInstance().getLogger("log");
};
/**
* @class MESANetworkFileParser
* @brief A parser for MESA-format network files.
*
* This class is designed to parse reaction network files that follow the
* format used by the MESA stellar evolution code.
*
* @implements NetworkFileParser
*/
class MESANetworkFileParser final : public NetworkFileParser {
public:
/**
* @brief Constructs a MESANetworkFileParser.
*
* @param filename The path to the MESA network file. This may be used
* to pre-configure the parser.
*
* @post The parser is initialized with the context of the given file.
*/
explicit MESANetworkFileParser(const std::string& filename);
/**
* @brief Parses a MESA-format network file.
*
* This method will read and interpret the structure of a MESA network
* file to extract the list of reactions.
*
* @param filename The path to the MESA network file.
* @return A `ParsedNetworkData` struct containing the list of reaction names.
*
* @throws std::runtime_error If the file cannot be opened or if it
* contains formatting errors.
*/
ParsedNetworkData parse(const std::string& filename) const override;
private:
using Config = fourdst::config::Config;
using LogManager = fourdst::logging::LogManager;
Config& m_config = Config::getInstance();
quill::Logger* m_logger = LogManager::getInstance().getLogger("log");
std::string m_filename;
};
}

View File

@@ -0,0 +1,108 @@
/* ***********************************************************************
//
// Copyright (C) 2025 -- The 4D-STAR Collaboration
// File Authors: Emily Boudreaux, Aaron Dotter
// Last Modified: March 21, 2025
//
// 4DSSE is free software; you can use it and/or modify
// it under the terms and restrictions the GNU General Library Public
// License version 3 (GPLv3) as published by the Free Software Foundation.
//
// 4DSSE is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with this software; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// *********************************************************************** */
#pragma once
#include <vector>
#include "fourdst/logging/logging.h"
#include "fourdst/config/config.h"
#include "fourdst/composition/species.h"
#include "fourdst/composition/composition.h"
#include "fourdst/constants/const.h"
#include "gridfire/reaction/reaction.h"
#include "quill/Logger.h"
#include <unordered_map>
namespace gridfire {
enum NetworkFormat {
APPROX8, ///< Approx8 nuclear reaction network format.
REACLIB, ///< General REACLIB nuclear reaction network format.
UNKNOWN,
};
static inline std::unordered_map<NetworkFormat, std::string> FormatStringLookup = {
{APPROX8, "Approx8"},
{REACLIB, "REACLIB"},
{UNKNOWN, "Unknown"}
};
struct NetIn {
fourdst::composition::Composition composition; ///< Composition of the network
double tMax; ///< Maximum time
double dt0; ///< Initial time step
double temperature; ///< Temperature in Kelvin
double density; ///< Density in g/cm^3
double energy; ///< Energy in ergs
double culling = 0.0; ///< Culling threshold for reactions (default is 0.0, meaning no culling)
std::vector<double> MolarAbundance() const;
};
struct NetOut {
fourdst::composition::Composition composition; ///< Composition of the network after evaluation
int num_steps; ///< Number of steps taken in the evaluation
double energy; ///< Energy in ergs after evaluation
friend std::ostream& operator<<(std::ostream& os, const NetOut& netOut) {
os << "NetOut(composition=" << netOut.composition << ", num_steps=" << netOut.num_steps << ", energy=" << netOut.energy << ")";
return os;
}
};
class Network {
public:
explicit Network(const NetworkFormat format = NetworkFormat::APPROX8);
virtual ~Network() = default;
[[nodiscard]] NetworkFormat getFormat() const;
NetworkFormat setFormat(const NetworkFormat format);
/**
* @brief Evaluate the network based on the input parameters.
*
* @param netIn Input parameters for the network evaluation.
* @return NetOut Output results from the network evaluation.
*/
virtual NetOut evaluate(const NetIn &netIn) = 0;
virtual bool isStiff() const { return m_stiff; }
virtual void setStiff(const bool stiff) { m_stiff = stiff; }
protected:
fourdst::config::Config& m_config; ///< Configuration instance
fourdst::logging::LogManager& m_logManager; ///< Log manager instance
quill::Logger* m_logger; ///< Logger instance
NetworkFormat m_format; ///< Format of the network
fourdst::constant::Constants& m_constants;
bool m_stiff = false; ///< Flag indicating if the network is stiff
};
} // namespace nuclearNetwork

View File

@@ -0,0 +1,33 @@
#pragma once
#include "gridfire/partition/partition_abstract.h"
#include "gridfire/partition/partition_types.h"
#include "fourdst/logging/logging.h"
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
namespace gridfire::partition {
class CompositePartitionFunction final : public PartitionFunction {
public:
explicit CompositePartitionFunction(const std::vector<BasePartitionType>& partitionFunctions);
CompositePartitionFunction(const CompositePartitionFunction& other);
[[nodiscard]] double evaluate(int z, int a, double T9) const override;
[[nodiscard]] double evaluateDerivative(int z, int a, double T9) const override;
[[nodiscard]] bool supports(int z, int a) const override;
[[nodiscard]] std::string type() const override;
[[nodiscard]] std::unique_ptr<PartitionFunction> clone() const override {
return std::make_unique<CompositePartitionFunction>(*this);
}
private:
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
std::vector<std::unique_ptr<PartitionFunction>> m_partitionFunctions; ///< Set of partition functions to use in the composite partition function.
private:
std::unique_ptr<PartitionFunction> selectPartitionFunction(const BasePartitionType type) const;
};
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "gridfire/partition/partition_types.h"
#include "gridfire/partition/partition_abstract.h"
#include "gridfire/partition/partition_ground.h"
#include "gridfire/partition/partition_rauscher_thielemann.h"
#include "gridfire/partition/rauscher_thielemann_partition_data_record.h"
#include "gridfire/partition/composite/partition_composite.h"

View File

@@ -0,0 +1,16 @@
#pragma once
#include <string>
#include <memory>
namespace gridfire::partition {
class PartitionFunction {
public:
virtual ~PartitionFunction() = default;
[[nodiscard]] virtual double evaluate(int z, int a, double T9) const = 0;
[[nodiscard]] virtual double evaluateDerivative(int z, int a, double T9) const = 0;
[[nodiscard]] virtual bool supports(int z, int a) const = 0;
[[nodiscard]] virtual std::string type() const = 0;
[[nodiscard]] virtual std::unique_ptr<PartitionFunction> clone() const = 0;
};
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include "gridfire/partition/partition_abstract.h"
#include "fourdst/logging/logging.h"
#include <unordered_map>
#include <memory>
#include "quill/Logger.h"
namespace gridfire::partition {
class GroundStatePartitionFunction final : public PartitionFunction {
public:
GroundStatePartitionFunction();
double evaluate(
const int z,
const int a,
const double T9
) const override;
double evaluateDerivative(
const int z,
const int a,
const double T9
) const override;
bool supports(
const int z,
const int a
) const override;
std::string type() const override { return "GroundState"; }
std::unique_ptr<PartitionFunction> clone() const override {
return std::make_unique<GroundStatePartitionFunction>(*this);
}
private:
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
std::unordered_map<int, double> m_ground_state_spin;
static constexpr int make_key(
const int z,
const int a);
};
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include "gridfire/partition/partition_abstract.h"
#include "fourdst/logging/logging.h"
#include <string>
#include <vector>
#include <unordered_map>
#include <array>
#include <utility>
#include <memory>
namespace gridfire::partition {
class RauscherThielemannPartitionFunction final : public PartitionFunction {
public:
RauscherThielemannPartitionFunction();
double evaluate(int z, int a, double T9) const override;
double evaluateDerivative(int z, int a, double T9) const override;
bool supports(int z, int a) const override;
std::string type() const override { return "RauscherThielemann"; }
private:
enum Bounds {
FRONT,
BACK,
MIDDLE
};
private:
struct IsotopeData {
double ground_state_spin;
std::array<double, 24> normalized_g_values;
};
struct InterpolationPoints {
double T9_high;
double G_norm_high;
double T9_low;
double G_norm_low;
};
struct IdentifiedIsotope {
Bounds bound;
const IsotopeData& data;
size_t upperIndex;
size_t lowerIndex;
};
std::unique_ptr<PartitionFunction> clone() const override {
return std::make_unique<RauscherThielemannPartitionFunction>(*this);
}
private:
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
std::unordered_map<int, IsotopeData> m_partitionData;
private:
static InterpolationPoints get_interpolation_points(
const size_t upper_index,
const size_t lower_index,
const std::array<double, 24>& normalized_g_values
);
IdentifiedIsotope find(int z, int a, double T9) const;
static constexpr int make_key(int z, int a);
};
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <unordered_map>
#include <string>
namespace gridfire::partition {
enum BasePartitionType {
RauscherThielemann, ///< Rauscher-Thielemann partition function
GroundState, ///< Ground state partition function
};
inline std::unordered_map<BasePartitionType, std::string> basePartitionTypeToString = {
{RauscherThielemann, "RauscherThielemann"},
{GroundState, "GroundState"}
};
inline std::unordered_map<std::string, BasePartitionType> stringToBasePartitionType = {
{"RauscherThielemann", RauscherThielemann},
{"GroundState", GroundState}
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace gridfire::partition::record {
#pragma pack(push, 1)
struct RauscherThielemannPartitionDataRecord {
uint32_t z; ///< Atomic number
uint32_t a; ///< Mass number
double ground_state_spin; ///< Ground state spin
double normalized_g_values[24]; ///< Normalized g-values for the first 24 energy levels
};
#pragma pack(pop)
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "gridfire/reaction/reaction.h"
namespace gridfire::reaclib {
static bool s_initialized = false;
/**
* @brief Provides global access to the fully initialized REACLIB reaction set.
*
* This function uses a singleton pattern to ensure the reaction data is loaded
* from the embedded binary only once, the very first time it is requested.
*
* @return A constant reference to the application-wide reaction set.
*/
const reaction::LogicalReactionSet& get_all_reactions();
} // namespace gridfire::reaclib

View File

@@ -0,0 +1,743 @@
#pragma once
#include <string_view>
#include "fourdst/composition/atomicSpecies.h"
#include "fourdst/logging/logging.h"
#include "quill/Logger.h"
#include <unordered_map>
#include <vector>
#include <unordered_set>
#include "cppad/cppad.hpp"
#include "xxhash64.h"
/**
* @file reaction.h
* @brief Defines classes for representing and managing nuclear reactions.
*
* This file contains the core data structures for handling nuclear reactions,
* including individual reactions from specific sources (`Reaction`), collections
* of reactions (`ReactionSet`), and logical reactions that aggregate rates from
* multiple sources (`LogicalReaction`, `LogicalReactionSet`).
*/
namespace gridfire::reaction {
/**
* @struct RateCoefficientSet
* @brief Holds the seven coefficients for the REACLIB rate equation.
*
* This struct stores the parameters (a0-a6) used to calculate reaction rates
* as a function of temperature.
*/
struct RateCoefficientSet {
double a0; ///< Coefficient a0
double a1; ///< Coefficient a1
double a2; ///< Coefficient a2
double a3; ///< Coefficient a3
double a4; ///< Coefficient a4
double a5; ///< Coefficient a5
double a6; ///< Coefficient a6
/**
* @brief Overloads the stream insertion operator for easy printing.
* @param os The output stream.
* @param r The RateCoefficientSet to print.
* @return The output stream.
*/
friend std::ostream& operator<<(std::ostream& os, const RateCoefficientSet& r) {
os << "[" << r.a0 << ", " << r.a1 << ", " << r.a2 << ", "
<< r.a3 << ", " << r.a4 << ", " << r.a5 << ", " << r.a6 << "]";
return os;
}
};
/**
* @class Reaction
* @brief Represents a single nuclear reaction from a specific data source.
*
* This class encapsulates all properties of a single nuclear reaction as defined
* in formats like REACLIB, including reactants, products, Q-value, and rate
* coefficients from a particular evaluation (source).
*
* Example:
* @code
* // Assuming species and rate coefficients are defined
* Reaction p_gamma_d(
* "H_1_H_1_to_H_2", "p(p,g)d", 1, {H_1, H_1}, {H_2}, 5.493, "st08", rate_coeffs
* );
* double rate = p_gamma_d.calculate_rate(0.1); // T9 = 0.1
* @endcode
*/
class Reaction {
public:
/**
* @brief Virtual destructor.
*/
virtual ~Reaction() = default;
/**
* @brief Constructs a Reaction object.
* @param id A unique identifier for the reaction.
* @param peName The name in (projectile, ejectile) notation (e.g., "p(p,g)d").
* @param chapter The REACLIB chapter number, defining reaction structure.
* @param reactants A vector of reactant species.
* @param products A vector of product species.
* @param qValue The Q-value of the reaction in MeV.
* @param label The source label for the rate data (e.g., "wc12", "st08").
* @param sets The set of rate coefficients.
* @param reverse True if this is a reverse reaction rate.
*/
Reaction(
const std::string_view id,
const std::string_view peName,
const int chapter,
const std::vector<fourdst::atomic::Species> &reactants,
const std::vector<fourdst::atomic::Species> &products,
const double qValue,
const std::string_view label,
const RateCoefficientSet &sets,
const bool reverse = false);
/**
* @brief Calculates the reaction rate for a given temperature.
* @param T9 The temperature in units of 10^9 K.
* @return The calculated reaction rate.
*/
[[nodiscard]] virtual double calculate_rate(const double T9) const;
/**
* @brief Calculates the reaction rate for a given temperature using CppAD types.
* @param T9 The temperature in units of 10^9 K, as a CppAD::AD<double>.
* @return The calculated reaction rate, as a CppAD::AD<double>.
*/
[[nodiscard]] virtual CppAD::AD<double> calculate_rate(const CppAD::AD<double> T9) const;
[[nodiscard]] virtual double calculate_forward_rate_log_derivative(const double T9) const;
/**
* @brief Gets the reaction name in (projectile, ejectile) notation.
* @return The reaction name (e.g., "p(p,g)d").
*/
[[nodiscard]] virtual std::string_view peName() const { return m_peName; }
/**
* @brief Gets the REACLIB chapter number.
* @return The chapter number.
*/
[[nodiscard]] int chapter() const { return m_chapter; }
/**
* @brief Gets the source label for the rate data.
* @return The source label (e.g., "wc12w", "st08").
*/
[[nodiscard]] std::string_view sourceLabel() const { return m_sourceLabel; }
/**
* @brief Gets the set of rate coefficients.
* @return A const reference to the RateCoefficientSet.
*/
[[nodiscard]] const RateCoefficientSet& rateCoefficients() const { return m_rateCoefficients; }
/**
* @brief Checks if the reaction involves a given species as a reactant or product.
* @param species The species to check for.
* @return True if the species is involved, false otherwise.
*/
[[nodiscard]] bool contains(const fourdst::atomic::Species& species) const;
/**
* @brief Checks if the reaction involves a given species as a reactant.
* @param species The species to check for.
* @return True if the species is a reactant, false otherwise.
*/
[[nodiscard]] bool contains_reactant(const fourdst::atomic::Species& species) const;
/**
* @brief Checks if the reaction involves a given species as a product.
* @param species The species to check for.
* @return True if the species is a product, false otherwise.
*/
[[nodiscard]] bool contains_product(const fourdst::atomic::Species& species) const;
/**
* @brief Gets a set of all unique species involved in the reaction.
* @return An unordered_set of all reactant and product species.
*/
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> all_species() const;
/**
* @brief Gets a set of all unique reactant species.
* @return An unordered_set of reactant species.
*/
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> reactant_species() const;
/**
* @brief Gets a set of all unique product species.
* @return An unordered_set of product species.
*/
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> product_species() const;
/**
* @brief Gets the number of unique species involved in the reaction.
* @return The count of unique species.
*/
[[nodiscard]] size_t num_species() const;
/**
* @brief Calculates the stoichiometric coefficient for a given species.
* @param species The species for which to find the coefficient.
* @return The stoichiometric coefficient (negative for reactants, positive for products).
*/
[[nodiscard]] int stoichiometry(const fourdst::atomic::Species& species) const;
/**
* @brief Gets a map of all species to their stoichiometric coefficients.
* @return An unordered_map from species to their integer coefficients.
*/
[[nodiscard]] std::unordered_map<fourdst::atomic::Species, int> stoichiometry() const;
/**
* @brief Gets the unique identifier of the reaction.
* @return The reaction ID.
*/
[[nodiscard]] std::string_view id() const { return m_id; }
/**
* @brief Gets the Q-value of the reaction.
* @return The Q-value in whatever units the reaction was defined in (usually MeV).
*/
[[nodiscard]] double qValue() const { return m_qValue; }
/**
* @brief Gets the vector of reactant species.
* @return A const reference to the vector of reactants.
*/
[[nodiscard]] const std::vector<fourdst::atomic::Species>& reactants() const { return m_reactants; }
/**
* @brief Gets the vector of product species.
* @return A const reference to the vector of products.
*/
[[nodiscard]] const std::vector<fourdst::atomic::Species>& products() const { return m_products; }
/**
* @brief Checks if this is a reverse reaction rate.
* @return True if it is a reverse rate, false otherwise.
*/
[[nodiscard]] bool is_reverse() const { return m_reverse; }
/**
* @brief Calculates the excess energy from the mass difference of reactants and products.
* @return The excess energy in MeV.
*/
[[nodiscard]] double excess_energy() const;
/**
* @brief Compares this reaction with another for equality based on their IDs.
* @param other The other Reaction to compare with.
* @return True if the reaction IDs are the same.
*/
bool operator==(const Reaction& other) const { return m_id == other.m_id; }
/**
* @brief Compares this reaction with another for inequality.
* @param other The other Reaction to compare with.
* @return True if the reactions are not equal.
*/
bool operator!=(const Reaction& other) const { return !(*this == other); }
/**
* @brief Computes a hash for the reaction based on its ID.
* @param seed The seed for the hash function.
* @return A 64-bit hash value.
* @details Uses the XXHash64 algorithm on the reaction's ID string.
*/
[[nodiscard]] uint64_t hash(uint64_t seed = 0) const;
friend std::ostream& operator<<(std::ostream& os, const Reaction& r) {
return os << "(Reaction:" << r.m_id << ")";
}
protected:
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
std::string m_id; ///< Unique identifier for the reaction (e.g., "h1+h1=>h2+e+nu").
std::string m_peName; ///< Name of the reaction in (projectile, ejectile) notation (e.g. "p(p,g)d").
int m_chapter; ///< Chapter number from the REACLIB database, defining the reaction structure.
double m_qValue = 0.0; ///< Q-value of the reaction in MeV.
std::vector<fourdst::atomic::Species> m_reactants; ///< Reactants of the reaction.
std::vector<fourdst::atomic::Species> m_products; ///< Products of the reaction.
std::string m_sourceLabel; ///< Source label for the rate data (e.g., "wc12w", "st08").
RateCoefficientSet m_rateCoefficients; ///< The seven rate coefficients.
bool m_reverse = false; ///< Flag indicating if this is a reverse reaction rate.
private:
/**
* @brief Template implementation for calculating the reaction rate.
* @tparam T The numeric type (double or CppAD::AD<double>).
* @param T9 The temperature in units of 10^9 K.
* @return The calculated reaction rate.
* @details The rate is calculated using the standard REACLIB formula:
* `rate = exp(a0 + a1/T9 + a2/T9^(1/3) + a3*T9^(1/3) + a4*T9 + a5*T9^(5/3) + a6*ln(T9))`
*/
template <typename T>
[[nodiscard]] T calculate_rate(const T T9) const {
const T T913 = CppAD::pow(T9, 1.0/3.0);
const T T953 = CppAD::pow(T9, 5.0/3.0);
const T logT9 = CppAD::log(T9);
const T exponent = m_rateCoefficients.a0 +
m_rateCoefficients.a1 / T9 +
m_rateCoefficients.a2 / T913 +
m_rateCoefficients.a3 * T913 +
m_rateCoefficients.a4 * T9 +
m_rateCoefficients.a5 * T953 +
m_rateCoefficients.a6 * logT9;
return CppAD::exp(exponent);
}
};
/**
* @class LogicalReaction
* @brief Represents a "logical" reaction that aggregates rates from multiple sources.
*
* A LogicalReaction shares the same reactants and products but combines rates
* from different evaluations (e.g., "wc12" and "st08" for the same physical
* reaction). The total rate is the sum of the individual rates.
* It inherits from Reaction, using the properties of the first provided reaction
* as its base properties (reactants, products, Q-value, etc.).
*/
class LogicalReaction final : public Reaction {
public:
/**
* @brief Constructs a LogicalReaction from a vector of `Reaction` objects.
* @param reactions A vector of reactions that represent the same logical process.
* @throws std::runtime_error if the provided reactions have inconsistent Q-values.
*/
explicit LogicalReaction(const std::vector<Reaction> &reactions);
/**
* @brief Adds another `Reaction` source to this logical reaction.
* @param reaction The reaction to add.
* @throws std::runtime_error if the reaction has a different `peName`, a duplicate
* source label, or an inconsistent Q-value.
*/
void add_reaction(const Reaction& reaction);
/**
* @brief Gets the number of source rates contributing to this logical reaction.
* @return The number of aggregated rates.
*/
[[nodiscard]] size_t size() const { return m_rates.size(); }
/**
* @brief Gets the list of source labels for the aggregated rates.
* @return A vector of source label strings.
*/
[[nodiscard]] std::vector<std::string> sources() const { return m_sources; }
/**
* @brief Calculates the total reaction rate by summing all source rates.
* @param T9 The temperature in units of 10^9 K.
* @return The total calculated reaction rate.
*/
[[nodiscard]] double calculate_rate(const double T9) const override;
[[nodiscard]] virtual double calculate_forward_rate_log_derivative(const double T9) const override;
/**
* @brief Calculates the total reaction rate using CppAD types.
* @param T9 The temperature in units of 10^9 K, as a CppAD::AD<double>.
* @return The total calculated reaction rate, as a CppAD::AD<double>.
*/
[[nodiscard]] CppAD::AD<double> calculate_rate(const CppAD::AD<double> T9) const override;
/** @name Iterators
* Provides iterators to loop over the rate coefficient sets.
*/
///@{
auto begin() { return m_rates.begin(); }
[[nodiscard]] auto begin() const { return m_rates.cbegin(); }
auto end() { return m_rates.end(); }
[[nodiscard]] auto end() const { return m_rates.cend(); }
///@}
///
friend std::ostream& operator<<(std::ostream& os, const LogicalReaction& r) {
os << "(LogicalReaction: " << r.id() << ", reverse: " << r.is_reverse() << ")";
return os;
}
private:
std::vector<std::string> m_sources; ///< List of source labels.
std::vector<RateCoefficientSet> m_rates; ///< List of rate coefficient sets from each source.
private:
/**
* @brief Template implementation for calculating the total reaction rate.
* @tparam T The numeric type (double or CppAD::AD<double>).
* @param T9 The temperature in units of 10^9 K.
* @return The total calculated reaction rate.
* @details This method iterates through all stored `RateCoefficientSet`s,
* calculates the rate for each, and returns their sum.
*/
template <typename T>
[[nodiscard]] T calculate_rate(const T T9) const {
T sum = static_cast<T>(0.0);
const T T913 = CppAD::pow(T9, 1.0/3.0);
const T T953 = CppAD::pow(T9, 5.0/3.0);
const T logT9 = CppAD::log(T9);
// ReSharper disable once CppUseStructuredBinding
for (const auto& rate : m_rates) {
const T exponent = rate.a0 +
rate.a1 / T9 +
rate.a2 / T913 +
rate.a3 * T913 +
rate.a4 * T9 +
rate.a5 * T953 +
rate.a6 * logT9;
sum += CppAD::exp(exponent);
}
return sum;
}
};
template <typename ReactionT>
class TemplatedReactionSet final {
public:
/**
* @brief Constructs a ReactionSet from a vector of reactions.
* @param reactions The initial vector of Reaction objects.
*/
explicit TemplatedReactionSet(std::vector<ReactionT> reactions);
TemplatedReactionSet();
/**
* @brief Copy constructor.
* @param other The ReactionSet to copy.
*/
TemplatedReactionSet(const TemplatedReactionSet<ReactionT>& other);
/**
* @brief Copy assignment operator.
* @param other The ReactionSet to assign from.
* @return A reference to this ReactionSet.
*/
TemplatedReactionSet<ReactionT>& operator=(const TemplatedReactionSet<ReactionT>& other);
/**
* @brief Adds a reaction to the set.
* @param reaction The Reaction to add.
*/
void add_reaction(ReactionT reaction);
/**
* @brief Removes a reaction from the set.
* @param reaction The Reaction to remove.
*/
void remove_reaction(const ReactionT& reaction);
/**
* @brief Checks if the set contains a reaction with the given ID.
* @param id The ID of the reaction to find.
* @return True if the reaction is in the set, false otherwise.
*/
[[nodiscard]] bool contains(const std::string_view& id) const;
/**
* @brief Checks if the set contains the given reaction.
* @param reaction The Reaction to find.
* @return True if the reaction is in the set, false otherwise.
*/
[[nodiscard]] bool contains(const Reaction& reaction) const;
/**
* @brief Gets the number of reactions in the set.
* @return The size of the set.
*/
[[nodiscard]] size_t size() const { return m_reactions.size(); }
/**
* @brief Removes all reactions from the set.
*/
void clear();
/**
* @brief Checks if any reaction in the set involves the given species.
* @param species The species to check for.
* @return True if the species is involved in any reaction.
*/
[[nodiscard]] bool contains_species(const fourdst::atomic::Species& species) const;
/**
* @brief Checks if any reaction in the set contains the given species as a reactant.
* @param species The species to check for.
* @return True if the species is a reactant in any reaction.
*/
[[nodiscard]] bool contains_reactant(const fourdst::atomic::Species& species) const;
/**
* @brief Checks if any reaction in the set contains the given species as a product.
* @param species The species to check for.
* @return True if the species is a product in any reaction.
*/
[[nodiscard]] bool contains_product(const fourdst::atomic::Species& species) const;
/**
* @brief Accesses a reaction by its index.
* @param index The index of the reaction to access.
* @return A const reference to the Reaction.
* @throws std::out_of_range if the index is out of bounds.
*/
[[nodiscard]] const ReactionT& operator[](size_t index) const;
/**
* @brief Accesses a reaction by its ID.
* @param id The ID of the reaction to access.
* @return A const reference to the Reaction.
* @throws std::out_of_range if no reaction with the given ID exists.
*/
[[nodiscard]] const ReactionT& operator[](const std::string_view& id) const;
/**
* @brief Compares this set with another for equality.
* @param other The other ReactionSet to compare with.
* @return True if the sets are equal (same size and hash).
*/
bool operator==(const TemplatedReactionSet& other) const;
/**
* @brief Compares this set with another for inequality.
* @param other The other ReactionSet to compare with.
* @return True if the sets are not equal.
*/
bool operator!=(const TemplatedReactionSet& other) const;
/**
* @brief Computes a hash for the entire set.
* @param seed The seed for the hash function.
* @return A 64-bit hash value.
* @details The algorithm computes the hash of each individual reaction,
* sorts the hashes, and then computes a final hash over the sorted list
* of hashes. This ensures the hash is order-independent.
*/
[[nodiscard]] uint64_t hash(uint64_t seed = 0) const;
/** @name Iterators
* Provides iterators to loop over the reactions in the set.
*/
///@{
auto begin() { return m_reactions.begin(); }
[[nodiscard]] auto begin() const { return m_reactions.cbegin(); }
auto end() { return m_reactions.end(); }
[[nodiscard]] auto end() const { return m_reactions.cend(); }
///@}
///
friend std::ostream& operator<<(std::ostream& os, const TemplatedReactionSet<ReactionT>& r) {
os << "(ReactionSet: [";
int counter = 0;
for (const auto& reaction : r.m_reactions) {
os << reaction;
if (counter < r.m_reactions.size() - 2) {
os << ", ";
} else if (counter == r.m_reactions.size() - 2) {
os << " and ";
}
++counter;
}
os << "])";
return os;
}
[[nodiscard]] std::unordered_set<fourdst::atomic::Species> getReactionSetSpecies() const;
private:
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
std::vector<ReactionT> m_reactions;
std::string m_id;
std::unordered_map<std::string, ReactionT> m_reactionNameMap; ///< Maps reaction IDs to Reaction objects for quick lookup.
};
using ReactionSet = TemplatedReactionSet<Reaction>; ///< A set of reactions, typically from a single source like REACLIB.
using LogicalReactionSet = TemplatedReactionSet<LogicalReaction>; ///< A set of logical reactions.
LogicalReactionSet packReactionSetToLogicalReactionSet(const ReactionSet& reactionSet);
template <typename ReactionT>
TemplatedReactionSet<ReactionT>::TemplatedReactionSet(
std::vector<ReactionT> reactions
) :
m_reactions(std::move(reactions)) {
if (m_reactions.empty()) {
return; // Case where the reactions will be added later.
}
m_reactionNameMap.reserve(reactions.size());
for (const auto& reaction : m_reactions) {
m_id += reaction.id();
m_reactionNameMap.emplace(reaction.id(), reaction);
}
}
template<typename ReactionT>
TemplatedReactionSet<ReactionT>::TemplatedReactionSet() {}
template <typename ReactionT>
TemplatedReactionSet<ReactionT>::TemplatedReactionSet(const TemplatedReactionSet<ReactionT> &other) {
m_reactions.reserve(other.m_reactions.size());
for (const auto& reaction_ptr: other.m_reactions) {
m_reactions.push_back(reaction_ptr);
}
m_reactionNameMap.reserve(other.m_reactionNameMap.size());
for (const auto& reaction_ptr : m_reactions) {
m_reactionNameMap.emplace(reaction_ptr.id(), reaction_ptr);
}
}
template <typename ReactionT>
TemplatedReactionSet<ReactionT>& TemplatedReactionSet<ReactionT>::operator=(const TemplatedReactionSet<ReactionT> &other) {
if (this != &other) {
TemplatedReactionSet temp(other);
std::swap(m_reactions, temp.m_reactions);
std::swap(m_reactionNameMap, temp.m_reactionNameMap);
}
return *this;
}
template <typename ReactionT>
void TemplatedReactionSet<ReactionT>::add_reaction(ReactionT reaction) {
m_reactions.emplace_back(reaction);
m_id += m_reactions.back().id();
m_reactionNameMap.emplace(m_reactions.back().id(), m_reactions.back());
}
template <typename ReactionT>
void TemplatedReactionSet<ReactionT>::remove_reaction(const ReactionT& reaction) {
if (!m_reactionNameMap.contains(std::string(reaction.id()))) {
return;
}
m_reactionNameMap.erase(std::string(reaction.id()));
std::erase_if(m_reactions, [&reaction](const Reaction& r) {
return r == reaction;
});
}
template <typename ReactionT>
bool TemplatedReactionSet<ReactionT>::contains(const std::string_view& id) const {
for (const auto& reaction : m_reactions) {
if (reaction.id() == id) {
return true;
}
}
return false;
}
template <typename ReactionT>
bool TemplatedReactionSet<ReactionT>::contains(const Reaction& reaction) const {
for (const auto& r : m_reactions) {
if (r == reaction) {
return true;
}
}
return false;
}
template <typename ReactionT>
void TemplatedReactionSet<ReactionT>::clear() {
m_reactions.clear();
m_reactionNameMap.clear();
}
template <typename ReactionT>
bool TemplatedReactionSet<ReactionT>::contains_species(const fourdst::atomic::Species& species) const {
for (const auto& reaction : m_reactions) {
if (reaction.contains(species)) {
return true;
}
}
return false;
}
template <typename ReactionT>
bool TemplatedReactionSet<ReactionT>::contains_reactant(const fourdst::atomic::Species& species) const {
for (const auto& r : m_reactions) {
if (r.contains_reactant(species)) {
return true;
}
}
return false;
}
template <typename ReactionT>
bool TemplatedReactionSet<ReactionT>::contains_product(const fourdst::atomic::Species& species) const {
for (const auto& r : m_reactions) {
if (r.contains_product(species)) {
return true;
}
}
return false;
}
template <typename ReactionT>
const ReactionT& TemplatedReactionSet<ReactionT>::operator[](const size_t index) const {
if (index >= m_reactions.size()) {
m_logger -> flush_log();
throw std::out_of_range("Index" + std::to_string(index) + " out of range for ReactionSet of size " + std::to_string(m_reactions.size()) + ".");
}
return m_reactions[index];
}
template <typename ReactionT>
const ReactionT& TemplatedReactionSet<ReactionT>::operator[](const std::string_view& id) const {
if (auto it = m_reactionNameMap.find(std::string(id)); it != m_reactionNameMap.end()) {
return it->second;
}
m_logger -> flush_log();
throw std::out_of_range("Species " + std::string(id) + " does not exist in ReactionSet.");
}
template <typename ReactionT>
bool TemplatedReactionSet<ReactionT>::operator==(const TemplatedReactionSet<ReactionT>& other) const {
if (size() != other.size()) {
return false;
}
return hash() == other.hash();
}
template <typename ReactionT>
bool TemplatedReactionSet<ReactionT>::operator!=(const TemplatedReactionSet<ReactionT>& other) const {
return !(*this == other);
}
template <typename ReactionT>
uint64_t TemplatedReactionSet<ReactionT>::hash(uint64_t seed) const {
if (m_reactions.empty()) {
return XXHash64::hash(nullptr, 0, seed);
}
std::vector<uint64_t> individualReactionHashes;
individualReactionHashes.reserve(m_reactions.size());
for (const auto& reaction : m_reactions) {
individualReactionHashes.push_back(reaction.hash(seed));
}
std::ranges::sort(individualReactionHashes);
const auto data = static_cast<const void*>(individualReactionHashes.data());
const size_t sizeInBytes = individualReactionHashes.size() * sizeof(uint64_t);
return XXHash64::hash(data, sizeInBytes, seed);
}
template<typename ReactionT>
std::unordered_set<fourdst::atomic::Species> TemplatedReactionSet<ReactionT>::getReactionSetSpecies() const {
std::unordered_set<fourdst::atomic::Species> species;
for (const auto& reaction : m_reactions) {
const auto reactionSpecies = reaction.all_species();
species.insert(reactionSpecies.begin(), reactionSpecies.end());
}
return species;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
#pragma once
#include "gridfire/screening/screening_types.h"
#include "gridfire/screening/screening_abstract.h"
#include "gridfire/screening/screening_bare.h"
#include "gridfire/screening/screening_weak.h"

View File

@@ -0,0 +1,108 @@
#pragma once
#include "gridfire/reaction/reaction.h"
#include "fourdst/composition/atomicSpecies.h"
#include "cppad/cppad.hpp"
#include <vector>
namespace gridfire::screening {
/**
* @class ScreeningModel
* @brief An abstract base class for plasma screening models.
*
* This class defines the interface for models that calculate the enhancement
* factor for nuclear reaction rates due to the electrostatic screening of
* interacting nuclei by the surrounding plasma. Concrete implementations of
* this class will provide specific screening prescriptions (e.g., WEAK,
* BARE, STRONG, etc.).
*
* The interface provides methods for calculating screening factors for both
* standard double-precision inputs and for CppAD's automatic differentiation
* types, allowing the screening contributions to be included in Jacobian
* calculations.
*/
class ScreeningModel {
public:
/// @brief Alias for CppAD Automatic Differentiation type for double precision.
using ADDouble = CppAD::AD<double>;
/**
* @brief Virtual destructor.
*
* Ensures that derived class destructors are called correctly.
*/
virtual ~ScreeningModel() = default;
/**
* @brief Calculates screening factors for a set of reactions.
*
* This is a pure virtual function that must be implemented by derived
* classes. It computes the screening enhancement factor for each reaction
* in the provided set based on the given plasma conditions.
*
* @param reactions The set of logical reactions in the network.
* @param species A vector of all atomic species involved in the network.
* @param Y A vector of the molar abundances (mol/g) for each species.
* @param T9 The temperature in units of 10^9 K.
* @param rho The plasma density in g/cm^3.
* @return A vector of screening factors (dimensionless), one for each reaction
* in the `reactions` set, in the same order.
*
* @b Pre-conditions
* - The size of the `Y` vector must match the size of the `species` vector.
* - `T9` and `rho` must be positive.
*
* @b Post-conditions
* - The returned vector will have the same size as the `reactions` set.
* - Each element in the returned vector will be >= 1.0.
*
* @b Usage
* @code
* // Assume 'model' is a std::unique_ptr<ScreeningModel> to a concrete implementation
* // and other parameters (reactions, species, Y, T9, rho) are initialized.
* std::vector<double> screening_factors = model->calculateScreeningFactors(
* reactions, species, Y, T9, rho
* );
* for (size_t i = 0; i < reactions.size(); ++i) {
* // ... use screening_factors[i] ...
* }
* @endcode
*/
virtual std::vector<double> calculateScreeningFactors(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<double>& Y,
const double T9,
const double rho
) const = 0;
/**
* @brief Calculates screening factors using CppAD types for automatic differentiation.
*
* This is a pure virtual function that provides an overload of
* `calculateScreeningFactors` for use with CppAD. It allows the derivatives
* of the screening factors with respect to abundances, temperature, and
* density to be computed automatically.
*
* @param reactions The set of logical reactions in the network.
* @param species A vector of all atomic species involved in the network.
* @param Y A vector of the molar abundances (mol/g) for each species, as AD types.
* @param T9 The temperature in units of 10^9 K, as an AD type.
* @param rho The plasma density in g/cm^3, as an AD type.
* @return A vector of screening factors (dimensionless), as AD types.
*
* @b Note
* This method is essential for including the effects of screening in the
* Jacobian matrix of the reaction network.
*/
virtual std::vector<ADDouble> calculateScreeningFactors(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<ADDouble>& Y,
const ADDouble T9,
const ADDouble rho
) const = 0;
};
}

View File

@@ -0,0 +1,135 @@
#pragma once
#include "gridfire/screening/screening_abstract.h"
#include "gridfire/reaction/reaction.h"
#include "cppad/cppad.hpp"
namespace gridfire::screening {
/**
* @class BareScreeningModel
* @brief A screening model that applies no screening effect.
*
* This class implements the `ScreeningModel` interface but returns a
* screening factor of 1.0 for all reactions, regardless of the plasma
* conditions. It represents the case of bare, unscreened nuclei and serves
* as a baseline or can be used when screening effects are negligible or
* intentionally ignored.
*
* @implements ScreeningModel
*/
class BareScreeningModel final : public ScreeningModel {
/// @brief Alias for CppAD Automatic Differentiation type for double precision.
using ADDouble = CppAD::AD<double>;
public:
/**
* @brief Calculates screening factors, which are always 1.0.
*
* This implementation returns a vector of screening factors where every
* element is 1.0, effectively applying no screening correction to the
* reaction rates.
*
* @param reactions The set of logical reactions in the network.
* @param species A vector of all atomic species (unused).
* @param Y A vector of the molar abundances (unused).
* @param T9 The temperature (unused).
* @param rho The plasma density (unused).
* @return A vector of doubles, with each element being 1.0, of the same
* size as the `reactions` set.
*
* @b Algorithm
* The function simply creates and returns a `std::vector<double>` of the
* same size as the input `reactions` set, with all elements initialized to 1.0.
*
* @b Usage
* @code
* BareScreeningModel bare_model;
* // ... (initialize reactions, species, Y, T9, rho)
* std::vector<double> factors = bare_model.calculateScreeningFactors(
* reactions, species, Y, T9, rho
* );
* // 'factors' will contain [1.0, 1.0, ...]
* @endcode
*/
[[nodiscard]] std::vector<double> calculateScreeningFactors(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<double>& Y,
const double T9,
const double rho
) const override;
/**
* @brief Calculates screening factors for AD types, which are always 1.0.
*
* This implementation returns a vector of AD-typed screening factors where
* every element is 1.0. This is the automatic differentiation-compatible
* version.
*
* @param reactions The set of logical reactions in the network.
* @param species A vector of all atomic species (unused).
* @param Y A vector of the molar abundances as AD types (unused).
* @param T9 The temperature as an AD type (unused).
* @param rho The plasma density as an AD type (unused).
* @return A vector of ADDouble, with each element being 1.0, of the same
* size as the `reactions` set.
*/
[[nodiscard]] std::vector<ADDouble> calculateScreeningFactors(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<ADDouble>& Y,
const ADDouble T9,
const ADDouble rho
) const override;
private:
/**
* @brief Template implementation for calculating screening factors.
*
* This private helper function contains the core logic for both the `double`
* and `ADDouble` versions of `calculateScreeningFactors`. It is templated
* to handle both numeric types seamlessly.
*
* @tparam T The numeric type, either `double` or `CppAD::AD<double>`.
* @param reactions The set of reactions for which to calculate factors.
* @param species A vector of all atomic species (unused).
* @param Y A vector of molar abundances (unused).
* @param T9 The temperature (unused).
* @param rho The density (unused).
* @return A vector of type `T` with all elements initialized to 1.0.
*/
template <typename T>
[[nodiscard]] std::vector<T> calculateFactors_impl(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<T>& Y,
const T T9,
const T rho
) const;
};
/**
* @brief Template implementation for the bare screening model.
*
* This function provides the actual implementation for `calculateFactors_impl`.
* It creates a vector of the appropriate numeric type (`T`) and size, and
* initializes all its elements to 1.0, representing no screening.
*
* @tparam T The numeric type, either `double` or `CppAD::AD<double>`.
* @param reactions The set of reactions, used to determine the size of the output vector.
* @param species Unused parameter.
* @param Y Unused parameter.
* @param T9 Unused parameter.
* @param rho Unused parameter.
* @return A `std::vector<T>` of the same size as `reactions`, with all elements set to 1.0.
*/
template<typename T>
std::vector<T> BareScreeningModel::calculateFactors_impl(
const reaction::LogicalReactionSet &reactions,
const std::vector<fourdst::atomic::Species> &species,
const std::vector<T> &Y,
const T T9,
const T rho
) const {
return std::vector<T>(reactions.size(), T(1.0)); // Bare screening returns 1.0 for all reactions
}
}

View File

@@ -0,0 +1,70 @@
#pragma once
#include "gridfire/screening/screening_abstract.h"
#include <memory>
namespace gridfire::screening {
/**
* @enum ScreeningType
* @brief Enumerates the available plasma screening models.
*
* This enum provides a set of identifiers for the different screening
* prescriptions that can be used in the reaction rate calculations.
*/
enum class ScreeningType {
BARE, ///< No screening applied. The screening factor is always 1.0.
/**
* @brief Weak screening model (Salpeter, 1954).
*
* This model is suitable for non-degenerate, non-relativistic plasmas
* where the electrostatic potential energy between ions is small compared
* to their thermal kinetic energy. The screening enhancement factor is
* calculated as `exp(H_12)`.
*
* @b Algorithm
* 1. A composition-dependent term, `ζ = ∑(Z_i^2 + Z_i) * Y_i`, is calculated,
* where Z_i is the charge and Y_i is the molar abundance of each species.
* 2. A prefactor is computed: `prefactor = 0.188 * sqrt(ρ / T₇³) * sqrt(ζ)`,
* where ρ is the density and T₇ is the temperature in 10^7 K.
* 3. For a reaction between two nuclei with charges Z₁ and Z₂, the enhancement
* term is `H_12 = prefactor * Z₁ * Z₂`.
* 4. The final screening factor is `exp(H_12)`.
* A special calculation is performed for the triple-alpha reaction.
*/
WEAK,
};
/**
* @brief A factory function to select and create a screening model.
*
* This function returns a `std::unique_ptr` to a concrete implementation of
* the `ScreeningModel` abstract base class, based on the specified `ScreeningType`.
* This allows for easy switching between different screening prescriptions at runtime.
*
* @param type The `ScreeningType` enum value specifying which model to create.
* @return A `std::unique_ptr<ScreeningModel>` holding an instance of the
* requested screening model.
*
* @b Algorithm
* The function uses a `switch` statement to determine which concrete model to
* instantiate. If the provided `type` does not match a known case, it defaults
* to creating a `BareScreeningModel` to ensure safe behavior.
*
* @b Post-conditions
* - A non-null `std::unique_ptr<ScreeningModel>` is always returned.
*
* @b Usage
* @code
* // Select the weak screening model
* auto screening_model = gridfire::screening::selectScreeningModel(gridfire::screening::ScreeningType::WEAK);
*
* // Use the model to calculate screening factors
* // (assuming other parameters are initialized)
* std::vector<double> factors = screening_model->calculateScreeningFactors(
* reactions, species, Y, T9, rho
* );
* @endcode
*/
std::unique_ptr<ScreeningModel> selectScreeningModel(ScreeningType type);
}

View File

@@ -0,0 +1,213 @@
#pragma once
#include "gridfire/screening/screening_abstract.h"
#include "gridfire/reaction/reaction.h"
#include "fourdst/logging/logging.h"
#include "quill/Logger.h"
#include "quill/LogMacros.h"
#include "cppad/cppad.hpp"
namespace gridfire::screening {
/**
* @class WeakScreeningModel
* @brief Implements the weak screening model based on the Debye-Hückel approximation.
*
* This class provides a concrete implementation of the `ScreeningModel`
* interface for the weak screening regime, following the formulation of
* Salpeter (1954). This approach applies the Debye-Hückel theory to model the
* electrostatic shielding of nuclei in a plasma. It is applicable to
* non-degenerate, non-relativistic plasmas where thermal energy dominates
* the electrostatic potential energy.
*
* @implements ScreeningModel
*/
class WeakScreeningModel final : public ScreeningModel {
public:
/**
* @brief Calculates weak screening factors for a set of reactions.
*
* This method computes the screening enhancement factor for each reaction
* based on the Salpeter (1954) formula.
*
* @param reactions The set of logical reactions in the network.
* @param species A vector of all atomic species involved in the network.
* @param Y A vector of the molar abundances (mol/g) for each species.
* @param T9 The temperature in units of 10^9 K.
* @param rho The plasma density in g/cm^3.
* @return A vector of screening factors (dimensionless), one for each reaction.
*
* @b Usage
* @code
* WeakScreeningModel weak_model;
* // ... (initialize reactions, species, Y, T9, rho)
* std::vector<double> factors = weak_model.calculateScreeningFactors(
* reactions, species, Y, T9, rho
* );
* @endcode
*/
[[nodiscard]] std::vector<double> calculateScreeningFactors(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<double>& Y,
const double T9,
const double rho
) const override;
/**
* @brief Calculates weak screening factors using CppAD types.
*
* This is the automatic differentiation-compatible version of the method.
* It allows the derivatives of the screening factors to be computed with
* respect to plasma conditions.
*
* @param reactions The set of logical reactions in the network.
* @param species A vector of all atomic species involved in the network.
* @param Y A vector of the molar abundances as AD types.
* @param T9 The temperature as an AD type.
* @param rho The plasma density as an AD type.
* @return A vector of screening factors as AD types.
*/
[[nodiscard]] std::vector<CppAD::AD<double>> calculateScreeningFactors(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<CppAD::AD<double>>& Y,
const CppAD::AD<double> T9,
const CppAD::AD<double> rho
) const override;
private:
/// @brief Logger instance for recording trace and debug information.
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
private:
/**
* @brief Template implementation for calculating weak screening factors.
*
* This private helper function contains the core logic for calculating
* weak screening factors. It is templated to handle both `double` and
* `CppAD::AD<double>` numeric types, avoiding code duplication.
*
* @tparam T The numeric type, either `double` or `CppAD::AD<double>`.
* @param reactions The set of reactions.
* @param species A vector of all species in the network.
* @param Y A vector of molar abundances.
* @param T9 The temperature in 10^9 K.
* @param rho The density in g/cm^3.
* @return A vector of screening factors of type `T`.
*/
template <typename T>
[[nodiscard]] std::vector<T> calculateFactors_impl(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<T>& Y,
const T T9,
const T rho
) const;
};
/**
* @brief Core implementation of the weak screening calculation (Debye-Hückel model).
*
* This function calculates the screening factor `exp(H_12)` for each reaction,
* based on the Debye-Hückel approximation as formulated by Salpeter (1954).
*
* @tparam T The numeric type (`double` or `CppAD::AD<double>`).
* @param reactions The set of reactions to be screened.
* @param species The list of all species in the network.
* @param Y The molar abundances of the species.
* @param T9 The temperature in 10^9 K.
* @param rho The density in g/cm^3.
* @return A vector of screening factors, one for each reaction.
*
* @b Algorithm
* 1. **Low-Temperature Cutoff**: If T9 is below a small threshold (1e-9),
* screening is effectively turned off to prevent numerical instability.
* 2. **Zeta Factor (ζ)**: A composition-dependent term is calculated:
* `ζ = ∑(Z_i² + Z_i) * Y_i`, where Z_i is the charge and Y_i is the
* molar abundance of species i.
* 3. **Prefactor**: A key prefactor is computed:
* `prefactor = 0.188 * sqrt(ρ / T₇³) * sqrt(ζ)`,
* where T₇ is the temperature in units of 10^7 K.
* 4. **Screening Term (H_12)**: For each reaction, the term H_12 is calculated:
* - For a two-body reaction (reactants Z₁ and Z₂): `H_12 = prefactor * Z₁ * Z₂`.
* - For the triple-alpha reaction (3 * He4): `H_12 = 3 * (prefactor * Z_α * Z_α)`.
* - For one-body reactions (decays), H_12 is 0, so the factor is 1.
* 5. **Capping**: The value of H_12 is capped at 2.0 to prevent excessively large
* and unphysical screening factors (exp(2) ≈ 7.4).
* 6. **Final Factor**: The screening factor for the reaction is `exp(H_12)`.
*/
template <typename T>
std::vector<T> WeakScreeningModel::calculateFactors_impl(
const reaction::LogicalReactionSet& reactions,
const std::vector<fourdst::atomic::Species>& species,
const std::vector<T>& Y,
const T T9,
const T rho
) const {
LOG_TRACE_L3(
m_logger,
"Calculating weak screening factors for {} reactions...",
reactions.size()
);
// --- CppAD Safe low temp checking ---
const T zero(0.0);
const T one(1.0);
const T low_temp_threshold(1e-9);
const T low_T_flag = CppAD::CondExpLt(T9, low_temp_threshold, zero, one);
// --- Calculate composition-dependent terms ---
// ζ = ∑(Z_i^2 + Z_i) * X_i / A_i
// This simplifies somewhat to ζ = ∑ (Z_i^2 + Z_i) * Y_i
// Where Y_i is the molar abundance (mol/g)
T zeta(0.0);
for (size_t i = 0; i < species.size(); ++i) {
const T Z = species[i].m_z;
zeta += (Z * Z + Z) * Y[i];
}
// --- Constant prefactors ---
const T T7 = T9 * static_cast<T>(100.00);
const T T7_safe = CppAD::CondExpLe(T7, low_temp_threshold, low_temp_threshold, T7);
const T prefactor = static_cast<T>(0.188) * CppAD::sqrt(rho / (T7_safe * T7_safe * T7_safe)) * CppAD::sqrt(zeta);
// --- Loop through reactions and calculate screening factors for each ---
std::vector<T> factors;
factors.reserve(reactions.size());
for (const auto& reaction : reactions) {
T H_12(0.0); // screening abundance term
const auto& reactants = reaction.reactants();
const bool isTripleAlpha = (
reactants.size() == 3 &&
reactants[0].m_z == 2 &&
reactants[1].m_z == 2 &&
reactants[2].m_z == 2 &&
reactants[0] == reactants[1] &&
reactants[1] == reactants[2]
);
if (reactants.size() == 2) {
LOG_TRACE_L3(m_logger, "Calculating screening factor for reaction: {}", reaction.peName());
const T Z1 = static_cast<T>(reactants[0].m_z);
const T Z2 = static_cast<T>(reactants[1].m_z);
H_12 = prefactor * Z1 * Z2;
}
else if (isTripleAlpha) {
LOG_TRACE_L3(m_logger, "Special case for triple alpha process in reaction: {}", reaction.peName());
// Special case for triple alpha process
const T Z_alpha = static_cast<T>(2.0);
const T H_alpha_alpha = prefactor * Z_alpha * Z_alpha;
H_12 = static_cast<T>(3.0) * H_alpha_alpha; // Triple alpha process
}
// For 1 body reactions H_12 remains 0 so e^H_12 will be 1.0 (screening does not apply)
// Aside from triple alpha, all other astrophysically relevant reactions are 2-body in the weak screening regime
H_12 *= low_T_flag; // Apply low temperature flag to screening factor
H_12 = CppAD::CondExpGe(H_12, static_cast<T>(2.0), static_cast<T>(2.0), H_12); // Caps the screening factor at 10 to avoid numerical issues
factors.push_back(CppAD::exp(H_12));
// std::cout << "Screening factor: " << reaction.peName() << " : " << factors.back() << "(" << H_12 << ")" << std::endl;
}
return factors;
}
}

View File

@@ -0,0 +1,183 @@
#pragma once
#include "gridfire/engine/engine_graph.h"
#include "gridfire/engine/engine_abstract.h"
#include "../engine/views/engine_adaptive.h"
#include "gridfire/network.h"
#include "fourdst/logging/logging.h"
#include "fourdst/config/config.h"
#include "quill/Logger.h"
#include <vector>
namespace gridfire::solver {
/**
* @class NetworkSolverStrategy
* @brief Abstract base class for network solver strategies.
*
* This class defines the interface for network solver strategies, which are responsible
* for integrating the reaction network ODEs over a given timestep. It is templated on the
* engine type to allow for different engine implementations to be used with the same solver.
*
* @tparam EngineT The type of engine to use with this solver strategy. Must inherit from Engine.
*/
template <typename EngineT>
class NetworkSolverStrategy {
public:
/**
* @brief Constructor for the NetworkSolverStrategy.
* @param engine The engine to use for evaluating the network.
*/
explicit NetworkSolverStrategy(EngineT& engine) : m_engine(engine) {};
/**
* @brief Virtual destructor.
*/
virtual ~NetworkSolverStrategy() = default;
/**
* @brief Evaluates the network for a given timestep.
* @param netIn The input conditions for the network.
* @return The output conditions after the timestep.
*/
virtual NetOut evaluate(const NetIn& netIn) = 0;
protected:
EngineT& m_engine; ///< The engine used by this solver strategy.
};
/**
* @brief Type alias for a network solver strategy that uses a DynamicEngine.
*/
using DynamicNetworkSolverStrategy = NetworkSolverStrategy<DynamicEngine>;
/**
* @class DirectNetworkSolver
* @brief A network solver that directly integrates the reaction network ODEs.
*
* This solver uses a Runge-Kutta method to directly integrate the reaction network
* ODEs. It is simpler than the QSENetworkSolver, but it can be less efficient for
* stiff networks with disparate timescales.
*
* @implements DynamicNetworkSolverStrategy
*/
class DirectNetworkSolver final : public DynamicNetworkSolverStrategy {
public:
/**
* @brief Constructor for the DirectNetworkSolver.
* @param engine The dynamic engine to use for evaluating the network.
*/
using DynamicNetworkSolverStrategy::DynamicNetworkSolverStrategy;
/**
* @brief Evaluates the network for a given timestep using direct integration.
* @param netIn The input conditions for the network.
* @return The output conditions after the timestep.
*/
NetOut evaluate(const NetIn& netIn) override;
private:
/**
* @struct RHSFunctor
* @brief Functor for calculating the right-hand side of the ODEs.
*
* This functor is used by the ODE solver to calculate the time derivatives of the
* species abundances. It takes the current abundances as input and returns the
* time derivatives.
*/
struct RHSManager {
DynamicEngine& m_engine; ///< The engine used to evaluate the network.
const double m_T9; ///< Temperature in units of 10^9 K.
const double m_rho; ///< Density in g/cm^3.
mutable double m_cached_time;
mutable std::optional<StepDerivatives<double>> m_cached_result;
mutable double m_last_observed_time = 0.0; ///< Last time the state was observed.
quill::Logger* m_logger = LogManager::getInstance().newFileLogger("integration.log", "GridFire"); ///< Logger instance.
mutable int m_num_steps = 0;
mutable double m_last_step_time = 1e-20;
/**
* @brief Constructor for the RHSFunctor.
* @param engine The engine used to evaluate the network.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
*/
RHSManager(
DynamicEngine& engine,
const double T9,
const double rho
) :
m_engine(engine),
m_T9(T9),
m_rho(rho),
m_cached_time(0) {}
/**
* @brief Calculates the time derivatives of the species abundances.
* @param Y Vector of current abundances.
* @param dYdt Vector to store the time derivatives.
* @param t Current time.
*/
void operator()(
const boost::numeric::ublas::vector<double>& Y,
boost::numeric::ublas::vector<double>& dYdt,
double t
) const;
void observe(const boost::numeric::ublas::vector<double>& state, double t) const;
void compute_and_cache(const boost::numeric::ublas::vector<double>& state, double t) const;
};
/**
* @struct JacobianFunctor
* @brief Functor for calculating the Jacobian matrix.
*
* This functor is used by the ODE solver to calculate the Jacobian matrix of the
* ODEs. It takes the current abundances as input and returns the Jacobian matrix.
*/
struct JacobianFunctor {
DynamicEngine& m_engine; ///< The engine used to evaluate the network.
const double m_T9; ///< Temperature in units of 10^9 K.
const double m_rho; ///< Density in g/cm^3.
/**
* @brief Constructor for the JacobianFunctor.
* @param engine The engine used to evaluate the network.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density in g/cm^3.
*/
JacobianFunctor(
DynamicEngine& engine,
const double T9,
const double rho
) :
m_engine(engine),
m_T9(T9),
m_rho(rho) {}
/**
* @brief Calculates the Jacobian matrix.
* @param Y Vector of current abundances.
* @param J Matrix to store the Jacobian matrix.
* @param t Current time.
* @param dfdt Vector to store the time derivatives (not used).
*/
void operator()(
const boost::numeric::ublas::vector<double>& Y,
boost::numeric::ublas::matrix<double>& J,
double t,
boost::numeric::ublas::vector<double>& dfdt
) const;
};
private:
quill::Logger* m_logger = LogManager::getInstance().getLogger("log"); ///< Logger instance.
Config& m_config = Config::getInstance(); ///< Configuration instance.
};
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include "gridfire/engine/engine_abstract.h"
#include <string>
#include <vector>
namespace gridfire::utils {
/**
* @brief Formats a map of nuclear species timescales into a human-readable string.
*
* This function takes a reaction network engine and the current plasma
* conditions to calculate the characteristic timescales for each species.
* It then formats this information into a neatly aligned ASCII table, which
* is suitable for logging or printing to the console.
*
* @param engine A constant reference to a `DynamicEngine` object, used to
* calculate the species timescales.
* @param Y A vector of the molar abundances (mol/g) for each species.
* @param T9 The temperature in units of 10^9 K.
* @param rho The plasma density in g/cm^3.
* @return A std::string containing the formatted table of species and their
* timescales.
*
* @b Pre-conditions
* - The `engine` must be in a valid state.
* - The size of the `Y` vector must be consistent with the number of species
* expected by the `engine`.
*
* @b Algorithm
* 1. Calls the `getSpeciesTimescales` method on the provided `engine` to get
* the timescale for each species under the given conditions.
* 2. Determines the maximum length of the species names to dynamically set the
* width of the "Species" column for proper alignment.
* 3. Uses a `std::ostringstream` to build the output string.
* 4. Constructs a header for the table with titles "Species" and "Timescale (s)".
* 5. Iterates through the map of timescales, adding a row to the table for
* each species.
* 6. Timescales are formatted in scientific notation with 3 digits of precision.
* 7. Special handling is included to print "inf" for infinite timescales.
* 8. The final string, including header and footer lines, is returned.
*
* @b Usage
* @code
* // Assume 'my_engine' is a valid DynamicEngine object and Y, T9, rho are initialized.
* std::string log_output = gridfire::utils::formatNuclearTimescaleLogString(my_engine, Y, T9, rho);
* std::cout << log_output;
*
* // Example Output:
* // == Timescales (s) ==
* // Species Timescale (s)
* // ==========================
* // h1 1.234e+05
* // he4 inf
* // c12 8.765e-02
* // ==========================
* @endcode
*/
std::string formatNuclearTimescaleLogString(
const DynamicEngine& engine,
const std::vector<double>& Y,
const double T9,
const double rho
);
}