5#include "fourdst/composition/atomicSpecies.h"
6#include "fourdst/composition/composition.h"
7#include "fourdst/config/config.h"
10#include "unsupported/Eigen/NonLinearOptimization"
12#include <boost/numeric/odeint.hpp>
15#include <unordered_map>
19#include "quill/LogMacros.h"
26 LOG_DEBUG(
m_logger,
"Solver update policy triggered, network view updating...");
28 LOG_DEBUG(
m_logger,
"Network view updated!");
34 using state_type = boost::numeric::ublas::vector<double>;
35 using namespace boost::numeric::odeint;
39 constexpr double abundance_floor = 1.0e-30;
40 std::vector<double>Y_sanitized_initial;
41 Y_sanitized_initial.reserve(
m_engine.getNetworkSpecies().size());
43 LOG_DEBUG(
m_logger,
"Sanitizing initial abundances with a floor of {:0.3E}...", abundance_floor);
44 for (
const auto& species :
m_engine.getNetworkSpecies()) {
45 double molar_abundance = 0.0;
47 molar_abundance = postIgnition.
composition.getMolarAbundance(std::string(species.name()));
50 if (molar_abundance < abundance_floor) {
51 molar_abundance = abundance_floor;
53 Y_sanitized_initial.push_back(molar_abundance);
56 const double rho = netIn.
density;
59 Eigen::VectorXd Y_QSE;
62 }
catch (
const std::runtime_error& e) {
63 LOG_ERROR(
m_logger,
"Failed to calculate steady state abundances. Aborting QSE evaluation.");
65 throw std::runtime_error(
"Failed to calculate steady state abundances: " + std::string(e.what()));
68 state_type YDynamic_ublas(indices.dynamicSpeciesIndices.size() + 1);
69 for (
size_t i = 0; i < indices.dynamicSpeciesIndices.size(); ++i) {
70 YDynamic_ublas(i) = Y_sanitized_initial[indices.dynamicSpeciesIndices[i]];
72 YDynamic_ublas(indices.dynamicSpeciesIndices.size()) = 0.0;
74 const RHSFunctor rhs_functor(
m_engine, indices.dynamicSpeciesIndices, indices.QSESpeciesIndices, Y_QSE, T9, rho);
75 const auto stepper = make_controlled<runge_kutta_dopri5<state_type>>(1.0e-8, 1.0e-8);
77 size_t stepCount = integrate_adaptive(
86 std::vector<double> YFinal = Y_sanitized_initial;
87 for (
size_t i = 0; i <indices.dynamicSpeciesIndices.size(); ++i) {
88 YFinal[indices.dynamicSpeciesIndices[i]] = YDynamic_ublas(i);
90 for (
size_t i = 0; i < indices.QSESpeciesIndices.size(); ++i) {
91 YFinal[indices.QSESpeciesIndices[i]] = Y_QSE(i);
94 const double finalSpecificEnergyRate = YDynamic_ublas(indices.dynamicSpeciesIndices.size());
97 std::vector<std::string> speciesNames(
m_engine.getNetworkSpecies().size());
98 std::vector<double> finalMassFractions(
m_engine.getNetworkSpecies().size());
99 double massFractionSum = 0.0;
101 for (
size_t i = 0; i < speciesNames.size(); ++i) {
102 const auto& species =
m_engine.getNetworkSpecies()[i];
103 speciesNames[i] = species.name();
104 finalMassFractions[i] = YFinal[i] * species.mass();
105 massFractionSum += finalMassFractions[i];
107 for (
auto& mf : finalMassFractions) {
108 mf /= massFractionSum;
111 fourdst::composition::Composition outputComposition(speciesNames, finalMassFractions);
114 netOut.
energy = finalSpecificEnergyRate;
120 const std::vector<double>& Y,
124 constexpr double timescaleCutoff = 1.0e-5;
125 constexpr double abundanceCutoff = 1.0e-15;
127 LOG_INFO(
m_logger,
"Partitioning species using T9={:0.2f} and ρ={:0.2e}", T9, rho);
128 LOG_INFO(
m_logger,
"Timescale Cutoff: {:.1e} s, Abundance Cutoff: {:.1e}", timescaleCutoff, abundanceCutoff);
130 std::vector<size_t>dynamicSpeciesIndices;
131 std::vector<size_t>QSESpeciesIndices;
133 std::unordered_map<fourdst::atomic::Species, double> speciesTimescale =
m_engine.getSpeciesTimescales(Y, T9, rho);
135 for (
size_t i = 0; i <
m_engine.getNetworkSpecies().size(); ++i) {
136 const auto& species =
m_engine.getNetworkSpecies()[i];
137 const double network_timescale = speciesTimescale.at(species);
138 const double abundance = Y[i];
140 double decay_timescale = std::numeric_limits<double>::infinity();
141 const double half_life = species.halfLife();
142 if (half_life > 0 && !std::isinf(half_life)) {
143 constexpr double LN2 = 0.69314718056;
144 decay_timescale = half_life / LN2;
147 const double final_timescale = std::min(network_timescale, decay_timescale);
149 if (std::isinf(final_timescale) || abundance < abundanceCutoff || final_timescale <= timescaleCutoff) {
150 QSESpeciesIndices.push_back(i);
152 dynamicSpeciesIndices.push_back(i);
155 LOG_INFO(
m_logger,
"Partitioning complete. Dynamical species: {}, QSE species: {}.", dynamicSpeciesIndices.size(), QSESpeciesIndices.size());
156 LOG_INFO(
m_logger,
"Dynamic species: {}", [dynamicSpeciesIndices](
const DynamicEngine& engine_wrapper) -> std::string {
159 for (
const auto& i : dynamicSpeciesIndices) {
161 if (count < dynamicSpeciesIndices.size() - 2) {
163 }
else if (count == dynamicSpeciesIndices.size() - 2) {
170 LOG_INFO(
m_logger,
"QSE species: {}", [QSESpeciesIndices](
const DynamicEngine& engine_wrapper) -> std::string {
173 for (
const auto& i : QSESpeciesIndices) {
175 if (count < QSESpeciesIndices.size() - 2) {
177 }
else if (count == QSESpeciesIndices.size() - 2) {
184 return {dynamicSpeciesIndices, QSESpeciesIndices};
188 const std::vector<double> &Y,
193 LOG_TRACE_L1(
m_logger,
"Calculating steady state abundances for QSE species...");
194 LOG_WARNING(
m_logger,
"QSE solver logic not yet implemented, assuming all QSE species have 0 abundance.");
199 return v_qse.array();
203 const auto ignitionTemperature =
m_config.get<
double>(
204 "gridfire:solver:QSE:ignition:temperature",
207 const auto ignitionDensity =
m_config.get<
double>(
208 "gridfire:solver:QSE:ignition:density",
211 const auto ignitionTime =
m_config.get<
double>(
212 "gridfire:solver:QSE:ignition:tMax",
215 const auto ignitionStepSize =
m_config.get<
double>(
216 "gridfire:solver:QSE:ignition:dt0",
222 "Igniting network with T={:<5.3E}, ρ={:<5.3E}, tMax={:<5.3E}, dt0={:<5.3E}...",
229 NetIn preIgnition = netIn;
231 preIgnition.
density = ignitionDensity;
232 preIgnition.
tMax = ignitionTime;
233 preIgnition.
dt0 = ignitionStepSize;
237 LOG_INFO(
m_logger,
"Network ignition completed in {} steps.", postIgnition.
num_steps);
249 const double temp_threshold =
m_config.get<
double>(
"gridfire:solver:policy:temp_threshold", 0.05);
251 if (temp_relative_change > temp_threshold) {
252 LOG_DEBUG(
m_logger,
"Temperature changed by {:.1f}%, triggering view update.", temp_relative_change * 100);
257 const double rho_threshold =
m_config.get<
double>(
"gridfire:solver:policy:rho_threshold", 0.10);
259 if (rho_relative_change > rho_threshold) {
260 LOG_DEBUG(
m_logger,
"Density changed by {:.1f}%, triggering view update.", rho_relative_change * 100);
266 const double fuel_threshold =
m_config.get<
double>(
"gridfire:solver:policy:fuel_threshold", 0.15);
270 const double h1_new = conditions.
composition.getMassFraction(
"H-1");
271 if (h1_old > 1e-12) {
272 const double h1_relative_change = std::abs(h1_new - h1_old) / h1_old;
273 if (h1_relative_change > fuel_threshold) {
274 LOG_DEBUG(
m_logger,
"H-1 mass fraction changed by {:.1f}%, triggering view update.", h1_relative_change * 100);
284 const boost::numeric::ublas::vector<double> &YDynamic,
285 boost::numeric::ublas::vector<double> &dYdtDynamic,
289 std::vector<double> YFull(
m_engine.getNetworkSpecies().size());
299 auto [full_dYdt, specificEnergyRate] =
m_engine.calculateRHSAndEnergy(YFull,
m_T9,
m_rho);
309 namespace ublas = boost::numeric::ublas;
310 namespace odeint = boost::numeric::odeint;
311 using fourdst::composition::Composition;
315 const unsigned long numSpecies =
m_engine.getNetworkSpecies().size();
317 const auto absTol =
m_config.get<
double>(
"gridfire:solver:DirectNetworkSolver:absTol", 1.0e-8);
318 const auto relTol =
m_config.get<
double>(
"gridfire:solver:DirectNetworkSolver:relTol", 1.0e-8);
320 size_t stepCount = 0;
325 ublas::vector<double> Y(numSpecies + 1);
327 for (
size_t i = 0; i < numSpecies; ++i) {
328 const auto& species =
m_engine.getNetworkSpecies()[i];
330 Y(i) = netIn.
composition.getMolarAbundance(std::string(species.name()));
331 }
catch (
const std::runtime_error) {
332 LOG_DEBUG(
m_logger,
"Species '{}' not found in composition. Setting abundance to 0.0.", species.name());
338 const auto stepper = odeint::make_controlled<odeint::rosenbrock4<double>>(absTol, relTol);
339 stepCount = odeint::integrate_adaptive(
341 std::make_pair(rhsFunctor, jacobianFunctor),
348 std::vector<double> finalMassFractions(numSpecies);
349 for (
size_t i = 0; i < numSpecies; ++i) {
350 const double molarMass =
m_engine.getNetworkSpecies()[i].mass();
351 finalMassFractions[i] = Y(i) * molarMass;
353 finalMassFractions[i] = 0.0;
357 std::vector<std::string> speciesNames;
358 speciesNames.reserve(numSpecies);
359 for (
const auto& species :
m_engine.getNetworkSpecies()) {
360 speciesNames.push_back(std::string(species.name()));
363 Composition outputComposition(speciesNames, finalMassFractions);
364 outputComposition.finalize(
true);
368 netOut.
energy = Y(numSpecies);
375 const boost::numeric::ublas::vector<double> &Y,
376 boost::numeric::ublas::vector<double> &dYdt,
379 const std::vector<double> y(Y.begin(),
m_numSpecies + Y.begin());
382 std::ranges::copy(dydt, dYdt.begin());
387 const boost::numeric::ublas::vector<double> &Y,
388 boost::numeric::ublas::matrix<double> &J,
390 boost::numeric::ublas::vector<double> &dfdt
396 J(i, j) =
m_engine.getJacobianMatrixEntry(i, j);
Abstract class for engines supporting Jacobian and stoichiometry operations.
virtual const std::vector< fourdst::atomic::Species > & getNetworkSpecies() const =0
Get the list of species in the network.
A network solver that directly integrates the reaction network ODEs.
quill::Logger * m_logger
Logger instance.
fourdst::config::Config & m_config
Configuration instance.
NetOut evaluate(const NetIn &netIn) override
Evaluates the network for a given timestep using direct integration.
AdaptiveEngineView & m_engine
Eigen::VectorXd calculateSteadyStateAbundances(const std::vector< double > &Y, const double T9, const double rho, const dynamicQSESpeciesIndices &indices) const
Calculates the steady-state abundances of the QSE species.
bool shouldUpdateView(const NetIn &conditions) const
Determines whether the adaptive engine view should be updated.
NetIn m_lastSeenConditions
The last seen input conditions.
quill::Logger * m_logger
Logger instance.
NetOut evaluate(const NetIn &netIn) override
Evaluates the network for a given timestep using the QSE approach.
dynamicQSESpeciesIndices packSpeciesTypeIndexVectors(const std::vector< double > &Y, const double T9, const double rho) const
Packs the species indices into vectors based on their type (dynamic or QSE).
fourdst::config::Config & m_config
Configuration instance.
bool m_isViewInitialized
Flag indicating whether the adaptive engine view has been initialized.
NetOut initializeNetworkWithShortIgnition(const NetIn &netIn) const
Initializes the network with a short ignition phase.
static constexpr double MIN_ABUNDANCE_THRESHOLD
Minimum abundance threshold below which species are ignored.
double density
Density in g/cm^3.
fourdst::composition::Composition composition
Composition of the network.
std::vector< double > MolarAbundance() const
double dt0
Initial time step.
double temperature
Temperature in Kelvin.
fourdst::composition::Composition composition
Composition of the network after evaluation.
double energy
Energy in ergs after evaluation.
int num_steps
Number of steps taken in the evaluation.
Functor for calculating the Jacobian matrix.
const size_t m_numSpecies
The number of species in the network.
DynamicEngine & m_engine
The engine used to evaluate the network.
void operator()(const boost::numeric::ublas::vector< double > &Y, boost::numeric::ublas::matrix< double > &J, double t, boost::numeric::ublas::vector< double > &dfdt) const
Calculates the Jacobian matrix.
Functor for calculating the right-hand side of the ODEs.
DynamicEngine & m_engine
The engine used to evaluate the network.
const double m_T9
Temperature in units of 10^9 K.
void operator()(const boost::numeric::ublas::vector< double > &Y, boost::numeric::ublas::vector< double > &dYdt, double t) const
Calculates the time derivatives of the species abundances.
const double m_rho
Density in g/cm^3.
const size_t m_numSpecies
The number of species in the network.
Functor for calculating the right-hand side of the ODEs for the dynamic species.
const Eigen::VectorXd & m_Y_QSE
Steady-state abundances of the QSE species.
DynamicEngine & m_engine
The engine used to evaluate the network.
const double m_T9
Temperature in units of 10^9 K.
const std::vector< size_t > & m_dynamicSpeciesIndices
Indices of the dynamic species.
const std::vector< size_t > & m_QSESpeciesIndices
Indices of the QSE species.
const double m_rho
Density in g/cm^3.
void operator()(const boost::numeric::ublas::vector< double > &YDynamic, boost::numeric::ublas::vector< double > &dYdtDynamic, double t) const
Calculates the time derivatives of the dynamic species.
Structure to hold indices of dynamic and QSE species.
std::vector< size_t > QSESpeciesIndices
Indices of fast species that are in QSE.