#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 factors = weak_model.calculateScreeningFactors( * reactions, species, Y, T9, rho * ); * @endcode */ [[nodiscard]] std::vector calculateScreeningFactors( const reaction::LogicalReactionSet& reactions, const std::vector& species, const std::vector& 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> calculateScreeningFactors( const reaction::LogicalReactionSet& reactions, const std::vector& species, const std::vector>& Y, const CppAD::AD T9, const CppAD::AD 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` numeric types, avoiding code duplication. * * @tparam T The numeric type, either `double` or `CppAD::AD`. * @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 [[nodiscard]] std::vector calculateFactors_impl( const reaction::LogicalReactionSet& reactions, const std::vector& species, const std::vector& 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`). * @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 std::vector WeakScreeningModel::calculateFactors_impl( const reaction::LogicalReactionSet& reactions, const std::vector& species, const std::vector& Y, const T T9, const T rho ) const { LOG_TRACE_L1( 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(100.00); const T T7_safe = CppAD::CondExpLe(T7, low_temp_threshold, low_temp_threshold, T7); const T prefactor = static_cast(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 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(reactants[0].m_z); const T Z2 = static_cast(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(2.0); const T H_alpha_alpha = prefactor * Z_alpha * Z_alpha; H_12 = static_cast(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(2.0), static_cast(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; } }