GridFire 0.0.1a
General Purpose Nuclear Network
Loading...
Searching...
No Matches
solver.cpp
Go to the documentation of this file.
3#include "gridfire/network.h"
5
6#include "fourdst/composition/atomicSpecies.h"
7#include "fourdst/composition/composition.h"
8#include "fourdst/config/config.h"
9
10#include "unsupported/Eigen/NonLinearOptimization"
11
12#include <boost/numeric/odeint.hpp>
13
14#include <vector>
15#include <string>
16#include <stdexcept>
17#include <iomanip>
18
19#include "quill/LogMacros.h"
20
21namespace gridfire::solver {
23 namespace ublas = boost::numeric::ublas;
24 namespace odeint = boost::numeric::odeint;
25 using fourdst::composition::Composition;
26
27
28 const double T9 = netIn.temperature / 1e9; // Convert temperature from Kelvin to T9 (T9 = T / 1e9)
29
30 const auto absTol = m_config.get<double>("gridfire:solver:DirectNetworkSolver:absTol", 1.0e-8);
31 const auto relTol = m_config.get<double>("gridfire:solver:DirectNetworkSolver:relTol", 1.0e-8);
32
33 Composition equilibratedComposition = m_engine.update(netIn);
34 size_t numSpecies = m_engine.getNetworkSpecies().size();
35 ublas::vector<double> Y(numSpecies + 1);
36
37 RHSManager manager(m_engine, T9, netIn.density);
38 JacobianFunctor jacobianFunctor(m_engine, T9, netIn.density);
39
40 auto populateY = [&](const Composition& comp) {
41 const size_t numSpeciesInternal = m_engine.getNetworkSpecies().size();
42 Y.resize(numSpeciesInternal + 1);
43 for (size_t i = 0; i < numSpeciesInternal; i++) {
44 const auto& species = m_engine.getNetworkSpecies()[i];
45 if (!comp.contains(species)) {
46 double lim = std::numeric_limits<double>::min();
47 LOG_DEBUG(m_logger, "Species '{}' not found in composition. Setting abundance to {:0.3E}.", species.name(), lim);
48 Y(i) = lim; // Species not in the composition, set to zero
49 } else {
50 Y(i) = comp.getMolarAbundance(species);
51 }
52 }
53
54 // TODO: a good starting point to make the temperature, density, and energy self consistent would be to turn this into an accumulator
55 Y(numSpeciesInternal) = 0.0; // Specific energy rate, initialized to zero
56 };
57
58 // This is a quick debug that can be turned on. For solar code input parameters (T~1.5e7K, ρ~1.5e3 g/cm^3) this should be near 8e-17
59 // std::cout << "D/H: " << equilibratedComposition.getMolarAbundance("H-2") / equilibratedComposition.getMolarAbundance("H-1") << std::endl;
60
61 populateY(equilibratedComposition);
62 const auto stepper = odeint::make_controlled<odeint::rosenbrock4<double>>(absTol, relTol);
63
64 double current_time = 0.0;
65 double current_initial_timestep = netIn.dt0;
66 double accumulated_energy = 0.0;
67 // size_t total_update_stages_triggered = 0;
68
69 while (current_time < netIn.tMax) {
70 try {
71 odeint::integrate_adaptive(
72 stepper,
73 std::make_pair(manager, jacobianFunctor),
74 Y,
75 current_time,
76 netIn.tMax,
77 current_initial_timestep,
78 [&](const auto& state, double t) {
79 current_time = t;
80 manager.observe(state, t);
81 }
82 );
83 current_time = netIn.tMax;
84 } catch (const exceptions::StaleEngineTrigger &e) {
85 LOG_INFO(m_logger, "Catching StaleEngineTrigger at t = {:0.3E} with T9 = {:0.3E}, rho = {:0.3E}. Triggering update stage (last stage took {} steps).", current_time, T9, netIn.density, e.totalSteps());
87 accumulated_energy += e.energy(); // Add the specific energy rate to the accumulated energy
88 // total_update_stages_triggered++;
89
90 Composition temp_comp;
91 std::vector<double> mass_fractions;
92 size_t num_species_at_stop = e.numSpecies();
93
94 if (num_species_at_stop != m_engine.getNetworkSpecies().size()) {
95 throw std::runtime_error(
96 "StaleEngineError state has a different number of species than the engine. This should not happen."
97 );
98 }
99 mass_fractions.reserve(num_species_at_stop);
100
101 for (size_t i = 0; i < num_species_at_stop; ++i) {
102 const auto& species = m_engine.getNetworkSpecies()[i];
103 temp_comp.registerSpecies(species);
104 mass_fractions.push_back(e.getMolarAbundance(i) * species.mass()); // Convert from molar abundance to mass fraction
105 }
106 temp_comp.setMassFraction(m_engine.getNetworkSpecies(), mass_fractions);
107 temp_comp.finalize(true);
108
109 NetIn netInTemp = netIn;
110 netInTemp.temperature = e.temperature();
111 netInTemp.density = e.density();
112 netInTemp.composition = std::move(temp_comp);
113
114 Composition currentComposition = m_engine.update(netInTemp);
115 populateY(currentComposition);
116 Y(Y.size() - 1) = e.energy(); // Set the specific energy rate from the stale state
117 numSpecies = m_engine.getNetworkSpecies().size();
118
119 // current_initial_timestep = 0.001 * manager.m_last_step_time; // set the new timestep to the last successful timestep before repartitioning
120 }
121 }
122
123 accumulated_energy += Y(Y.size() - 1); // Add the specific energy rate to the accumulated energy
124
125 std::vector<double> finalMassFractions(numSpecies);
126 for (size_t i = 0; i < numSpecies; ++i) {
127 const double molarMass = m_engine.getNetworkSpecies()[i].mass();
128 finalMassFractions[i] = Y(i) * molarMass; // Convert from molar abundance to mass fraction
129 if (finalMassFractions[i] < MIN_ABUNDANCE_THRESHOLD) {
130 finalMassFractions[i] = 0.0;
131 }
132 }
133
134 std::vector<std::string> speciesNames;
135 speciesNames.reserve(numSpecies);
136 for (const auto& species : m_engine.getNetworkSpecies()) {
137 speciesNames.push_back(std::string(species.name()));
138 }
139
140 Composition outputComposition(speciesNames);
141 outputComposition.setMassFraction(speciesNames, finalMassFractions);
142 outputComposition.finalize(true);
143
144 NetOut netOut;
145 netOut.composition = std::move(outputComposition);
146 netOut.energy = accumulated_energy; // Specific energy rate
147 netOut.num_steps = manager.m_num_steps;
148
149 return netOut;
150 }
151
153 const boost::numeric::ublas::vector<double> &Y,
154 boost::numeric::ublas::vector<double> &dYdt,
155 const double t
156 ) const {
157 const size_t numSpecies = m_engine.getNetworkSpecies().size();
158 if (t != m_cached_time || !m_cached_result.has_value() || m_cached_result.value().dydt.size() != numSpecies + 1) {
159 compute_and_cache(Y, t);
160 }
161 const auto&[dydt, nuclearEnergyGenerationRate] = m_cached_result.value();
162 dYdt.resize(numSpecies + 1);
163 std::ranges::copy(dydt, dYdt.begin());
164 dYdt(numSpecies) = nuclearEnergyGenerationRate; // Set the last element to the specific energy rate
165 }
166
168 const boost::numeric::ublas::vector<double> &state,
169 const double t
170 ) const {
171 double dt = t - m_last_observed_time;
172 compute_and_cache(state, t);
173 LOG_INFO(
174 m_logger,
175 "(Step {}) Observed state at t = {:0.3E} (dt = {:0.3E})",
177 t,
178 dt
179 );
180 std::ostringstream oss;
181 oss << std::scientific << std::setprecision(3);
182 oss << "(Step: " << std::setw(10) << m_num_steps << ") t = " << t << " (dt = " << dt << ", eps_nuc: " << state(state.size() - 1) << " [erg])\n";
183 std::cout << oss.str();
185 m_last_step_time = dt;
186
187 }
188
190 const boost::numeric::ublas::vector<double> &state,
191 double t
192 ) const {
193 std::vector<double> y_vec(state.begin(), state.end() - 1);
194 std::ranges::replace_if(
195 y_vec,
196 [](const double yi){
197 return yi < 0.0;
198 },
199 0.0 // Avoid negative abundances
200 );
201
202 const auto result = m_engine.calculateRHSAndEnergy(y_vec, m_T9, m_rho);
203 if (!result) {
204 LOG_INFO(m_logger,
205 "Triggering update stage due to stale engine detected at t = {:0.3E} with T9 = {:0.3E}, rho = {:0.3E}, y_vec (size: {})",
206 t, m_T9, m_rho, y_vec.size());
207 throw exceptions::StaleEngineTrigger({m_T9, m_rho, y_vec, t, m_num_steps, state(state.size() - 1)});
208 }
209 m_cached_result = result.value();
210 m_cached_time = t;
211
212 m_num_steps++;
213 }
214
216 const boost::numeric::ublas::vector<double> &Y,
217 boost::numeric::ublas::matrix<double> &J,
218 double t,
219 boost::numeric::ublas::vector<double> &dfdt
220 ) const {
221 size_t numSpecies = m_engine.getNetworkSpecies().size();
222 J.resize(numSpecies+1, numSpecies+1);
223 J.clear();
224 for (size_t i = 0; i < numSpecies; ++i) {
225 for (size_t j = 0; j < numSpecies; ++j) {
226 J(i, j) = m_engine.getJacobianMatrixEntry(i, j);
227 }
228 }
229 }
230
231}
double getMolarAbundance(const size_t index) const
quill::Logger * m_logger
Logger instance.
Definition solver.h:180
NetOut evaluate(const NetIn &netIn) override
Evaluates the network for a given timestep using direct integration.
Definition solver.cpp:22
Config & m_config
Configuration instance.
Definition solver.h:181
static constexpr double MIN_ABUNDANCE_THRESHOLD
Minimum abundance threshold below which species are ignored.
double density
Density in g/cm^3.
Definition network.h:58
double tMax
Maximum time.
Definition network.h:55
fourdst::composition::Composition composition
Composition of the network.
Definition network.h:54
double dt0
Initial time step.
Definition network.h:56
double temperature
Temperature in Kelvin.
Definition network.h:57
fourdst::composition::Composition composition
Composition of the network after evaluation.
Definition network.h:66
double energy
Energy in ergs after evaluation.
Definition network.h:68
int num_steps
Number of steps taken in the evaluation.
Definition network.h:67
Functor for calculating the Jacobian matrix.
Definition solver.h:143
DynamicEngine & m_engine
The engine used to evaluate the network.
Definition solver.h:144
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.
Definition solver.cpp:215
DynamicEngine & m_engine
The engine used to evaluate the network.
Definition solver.h:89
void observe(const boost::numeric::ublas::vector< double > &state, double t) const
Definition solver.cpp:167
const double m_T9
Temperature in units of 10^9 K.
Definition solver.h:90
double m_last_observed_time
Last time the state was observed.
Definition solver.h:96
void compute_and_cache(const boost::numeric::ublas::vector< double > &state, double t) const
Definition solver.cpp:189
quill::Logger * m_logger
Logger instance.
Definition solver.h:99
const double m_rho
Density in g/cm^3.
Definition solver.h:91
std::optional< StepDerivatives< double > > m_cached_result
Definition solver.h:94
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.
Definition solver.cpp:152