feat(python): added robust python bindings covering the entire codebase
This commit is contained in:
529
src/lib/engine/engine_approx8.cpp
Normal file
529
src/lib/engine/engine_approx8.cpp
Normal file
@@ -0,0 +1,529 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <array>
|
||||
|
||||
#include <boost/numeric/odeint.hpp>
|
||||
|
||||
#include "fourdst/constants/const.h"
|
||||
#include "fourdst/config/config.h"
|
||||
#include "quill/LogMacros.h"
|
||||
|
||||
#include "gridfire/engine/engine_approx8.h"
|
||||
#include "gridfire/network.h"
|
||||
|
||||
/* Nuclear reaction network in cgs units based on Frank Timmes' "approx8".
|
||||
At this time it does neither screening nor neutrino losses. It includes
|
||||
the following 8 isotopes:
|
||||
|
||||
h1
|
||||
he3
|
||||
he4
|
||||
c12
|
||||
n14
|
||||
o16
|
||||
ne20
|
||||
mg24
|
||||
|
||||
and the following nuclear reactions:
|
||||
|
||||
---pp chain---
|
||||
p(p,e+)d
|
||||
d(p,g)he3
|
||||
he3(he3,2p)he4
|
||||
|
||||
---CNO cycle---
|
||||
c12(p,g)n13 - n13 -> c13 + p -> n14
|
||||
n14(p,g)o15 - o15 + p -> c12 + he4
|
||||
n14(a,g)f18 - proceeds to ne20
|
||||
n15(p,a)c12 - / these two n15 reactions are \ CNO I
|
||||
n15(p,g)o16 - \ used to calculate a fraction / CNO II
|
||||
o16(p,g)f17 - f17 + e -> o17 and then o17 + p -> n14 + he4
|
||||
|
||||
---alpha captures---
|
||||
c12(a,g)o16
|
||||
triple alpha
|
||||
o16(a,g)ne20
|
||||
ne20(a,g)mg24
|
||||
c12(c12,a)ne20
|
||||
c12(o16,a)mg24
|
||||
|
||||
At present the rates are all evaluated using a fitting function.
|
||||
The coefficients to the fit are from reaclib.jinaweb.org .
|
||||
|
||||
*/
|
||||
|
||||
namespace gridfire::approx8{
|
||||
|
||||
// using namespace std;
|
||||
using namespace boost::numeric::odeint;
|
||||
|
||||
//helper functions
|
||||
// a function to multiply two arrays and then sum the resulting elements: sum(a*b)
|
||||
double sum_product( const vec7 &a, const vec7 &b){
|
||||
double sum=0;
|
||||
for (size_t i=0; i < a.size(); i++) {
|
||||
sum += a[i] * b[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
// the fit to the nuclear reaction rates is of the form:
|
||||
// exp( a0 + a1/T9 + a2/T9^(1/3) + a3*T9^(1/3) + a4*T9 + a5*T9^(5/3) + log(T9) )
|
||||
// this function returns an array of the T9 terms in that order, where T9 is the temperatures in GigaKelvin
|
||||
vec7 get_T9_array(const double &T) {
|
||||
vec7 arr;
|
||||
const double T9=1e-9*T;
|
||||
const double T913=pow(T9,1./3.);
|
||||
|
||||
arr[0]=1;
|
||||
arr[1]=1/T9;
|
||||
arr[2]=1/T913;
|
||||
arr[3]=T913;
|
||||
arr[4]=T9;
|
||||
arr[5]=pow(T9,5./3.);
|
||||
arr[6]=log(T9);
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
// this function uses the two preceding functions to evaluate the rate given the T9 array and the coefficients
|
||||
double rate_fit(const vec7 &T9, const vec7 &coef){
|
||||
return exp(sum_product(T9,coef));
|
||||
}
|
||||
|
||||
// p + p -> d; this, like some of the other rates, this is a composite of multiple fits
|
||||
double pp_rate(const vec7 &T9) {
|
||||
constexpr vec7 a1 = {-34.78630, 0,-3.511930, 3.100860, -0.1983140, 1.262510e-2, -1.025170};
|
||||
constexpr vec7 a2 = { -4.364990e+1,-2.460640e-3,-2.750700,-4.248770e-1,1.598700e-2,-6.908750e-4,-2.076250e-1};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2);
|
||||
}
|
||||
|
||||
// p + d -> he3
|
||||
double dp_rate(const vec7 &T9) {
|
||||
constexpr vec7 a1 = {7.528980, 0, -3.720800, 0.8717820, 0, 0,-0.6666670};
|
||||
constexpr vec7 a2 = {8.935250, 0, -3.720800, 0.1986540, 0, 0, 0.3333330};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2);
|
||||
}
|
||||
|
||||
// he3 + he3 -> he4 + 2p
|
||||
double he3he3_rate(const vec7 &T9){
|
||||
constexpr vec7 a = {2.477880e+01,0,-12.27700,-0.1036990,-6.499670e-02,1.681910e-02,-6.666670e-01};
|
||||
return rate_fit(T9,a);
|
||||
}
|
||||
|
||||
// he3(he3,2p)he4 ** (missing both coefficients but have a reaction)
|
||||
double he3he4_rate(const vec7 &T9){
|
||||
constexpr vec7 a1 = {1.560990e+01,0.000000e+00,-1.282710e+01,-3.082250e-02,-6.546850e-01,8.963310e-02,-6.666670e-01};
|
||||
constexpr vec7 a2 = {1.770750e+01,0.000000e+00,-1.282710e+01,-3.812600e+00,9.422850e-02,-3.010180e-03,1.333330e+00};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2);
|
||||
}
|
||||
|
||||
// he4 + he4 + he4 -> c12 ** (missing middle coefficient but have other two)
|
||||
double triple_alpha_rate(const vec7 &T9){
|
||||
constexpr vec7 a1 = {-9.710520e-01,0.000000e+00,-3.706000e+01,2.934930e+01,-1.155070e+02,-1.000000e+01,-1.333330e+00};
|
||||
constexpr vec7 a2 = {-1.178840e+01,-1.024460e+00,-2.357000e+01,2.048860e+01,-1.298820e+01,-2.000000e+01,-2.166670e+00};
|
||||
constexpr vec7 a3 = {-2.435050e+01,-4.126560e+00,-1.349000e+01,2.142590e+01,-1.347690e+00,8.798160e-02,-1.316530e+01};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2) + rate_fit(T9,a3);
|
||||
}
|
||||
|
||||
// c12 + p -> n13
|
||||
double c12p_rate(const vec7 &T9){
|
||||
constexpr vec7 a1={1.714820e+01,0.000000e+00,-1.369200e+01,-2.308810e-01,4.443620e+00,-3.158980e+00,-6.666670e-01};
|
||||
constexpr vec7 a2={1.754280e+01,-3.778490e+00,-5.107350e+00,-2.241110e+00,1.488830e-01,0.000000e+00,-1.500000e+00};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2);
|
||||
}
|
||||
|
||||
// c12 + he4 -> o16 ** (missing first coefficient but have the second)
|
||||
double c12a_rate(const vec7 &T9){
|
||||
constexpr vec7 a1={6.965260e+01,-1.392540e+00,5.891280e+01,-1.482730e+02,9.083240e+00,-5.410410e-01,7.035540e+01};
|
||||
constexpr vec7 a2={2.546340e+02,-1.840970e+00,1.034110e+02,-4.205670e+02,6.408740e+01,-1.246240e+01,1.373030e+02};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2);
|
||||
}
|
||||
|
||||
// n14(p,g)o15 - o15 + p -> c12 + he4
|
||||
double n14p_rate(const vec7 &T9){
|
||||
constexpr vec7 a1={1.701000e+01,0.000000e+00,-1.519300e+01,-1.619540e-01,-7.521230e+00,-9.875650e-01,-6.666670e-01};
|
||||
constexpr vec7 a2={2.011690e+01,0.000000e+00,-1.519300e+01,-4.639750e+00,9.734580e+00,-9.550510e+00,3.333330e-01};
|
||||
constexpr vec7 a3={7.654440e+00,-2.998000e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
constexpr vec7 a4={6.735780e+00,-4.891000e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,6.820000e-02};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2) + rate_fit(T9,a3) + rate_fit(T9,a4);
|
||||
}
|
||||
|
||||
// n14(a,g)f18 assumed to go on to ne20
|
||||
double n14a_rate(const vec7 &T9){
|
||||
constexpr vec7 a1={2.153390e+01,0.000000e+00,-3.625040e+01,0.000000e+00,0.000000e+00,-5.000000e+00,-6.666670e-01};
|
||||
constexpr vec7 a2={1.968380e-01,-5.160340e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
constexpr vec7 a3={1.389950e+01,-1.096560e+01,-5.622700e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2) + rate_fit(T9,a3);
|
||||
}
|
||||
|
||||
// n15(p,a)c12 (CNO I)
|
||||
double n15pa_rate(const vec7 &T9){
|
||||
constexpr vec7 a1 = {2.747640e+01,0.000000e+00,-1.525300e+01,1.593180e+00,2.447900e+00,-2.197080e+00,-6.666670e-01};
|
||||
constexpr vec7 a2 = {-4.873470e+00,-2.021170e+00,0.000000e+00,3.084970e+01,-8.504330e+00,-1.544260e+00,-1.500000e+00};
|
||||
constexpr vec7 a3 = {2.089720e+01,-7.406000e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
constexpr vec7 a4 = {-6.575220e+00,-1.163800e+00,0.000000e+00,2.271050e+01,-2.907070e+00,2.057540e-01,-1.500000e+00};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2) + rate_fit(T9,a3) + rate_fit(T9,a4);
|
||||
}
|
||||
|
||||
// n15(p,g)o16 (CNO II)
|
||||
double n15pg_rate(const vec7 &T9){
|
||||
constexpr vec7 a1 = {2.001760e+01,0.000000e+00,-1.524000e+01,3.349260e-01,4.590880e+00,-4.784680e+00,-6.666670e-01};
|
||||
constexpr vec7 a2 = {6.590560e+00,-2.923150e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
constexpr vec7 a3 = {1.454440e+01,-1.022950e+01,0.000000e+00,0.000000e+00,4.590370e-02,0.000000e+00,-1.500000e+00};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2) + rate_fit(T9,a3);
|
||||
}
|
||||
|
||||
double n15pg_frac(const vec7 &T9){
|
||||
const double f1=n15pg_rate(T9);
|
||||
const double f2=n15pa_rate(T9);
|
||||
return f1/(f1+f2);
|
||||
}
|
||||
|
||||
// o16(p,g)f17 then f17 -> o17(p,a)n14
|
||||
double o16p_rate(const vec7 &T9){
|
||||
constexpr vec7 a={1.909040e+01,0.000000e+00,-1.669600e+01,-1.162520e+00,2.677030e-01,-3.384110e-02,-6.666670e-01};
|
||||
return rate_fit(T9,a);
|
||||
}
|
||||
|
||||
// o16(a,g)ne20
|
||||
double o16a_rate(const vec7 &T9){
|
||||
constexpr vec7 a1={2.390300e+01,0.000000e+00,-3.972620e+01,-2.107990e-01,4.428790e-01,-7.977530e-02,-6.666670e-01};
|
||||
constexpr vec7 a2={3.885710e+00,-1.035850e+01,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
constexpr vec7 a3={9.508480e+00,-1.276430e+01,0.000000e+00,-3.659250e+00,7.142240e-01,-1.075080e-03,-1.500000e+00};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2) + rate_fit(T9,a3);
|
||||
}
|
||||
|
||||
// ne20(a,g)mg24
|
||||
double ne20a_rate(const vec7 &T9){
|
||||
constexpr vec7 a1={2.450580e+01,0.000000e+00,-4.625250e+01,5.589010e+00,7.618430e+00,-3.683000e+00,-6.666670e-01};
|
||||
constexpr vec7 a2={-3.870550e+01,-2.506050e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
constexpr vec7 a3={1.983070e+00,-9.220260e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,-1.500000e+00};
|
||||
constexpr vec7 a4={-8.798270e+00,-1.278090e+01,0.000000e+00,1.692290e+01,-2.573250e+00,2.089970e-01,-1.500000e+00};
|
||||
return rate_fit(T9,a1) + rate_fit(T9,a2) + rate_fit(T9,a3) + rate_fit(T9,a4);
|
||||
}
|
||||
|
||||
// c12(c12,a)ne20
|
||||
double c12c12_rate(const vec7 &T9){
|
||||
constexpr vec7 a={6.128630e+01,0.000000e+00,-8.416500e+01,-1.566270e+00,-7.360840e-02,-7.279700e-02,-6.666670e-01};
|
||||
return rate_fit(T9,a);
|
||||
}
|
||||
|
||||
// c12(o16,a)mg24
|
||||
double c12o16_rate(const vec7 &T9){
|
||||
constexpr vec7 a={4.853410e+01,3.720400e-01,-1.334130e+02,5.015720e+01,-3.159870e+00,1.782510e-02,-2.370270e+01};
|
||||
return rate_fit(T9,a);
|
||||
}
|
||||
|
||||
|
||||
// for Boost ODE solvers either a struct or a class is required
|
||||
|
||||
// a Jacobian matrix for implicit solvers
|
||||
|
||||
void Jacobian::operator() ( const vector_type &y, matrix_type &J, double /* t */, vector_type &dfdt ) const {
|
||||
fourdst::constant::Constants& constants = fourdst::constant::Constants::getInstance();
|
||||
const double avo = constants.get("N_a").value;
|
||||
const double clight = constants.get("c").value;
|
||||
// EOS
|
||||
const vec7 T9=get_T9_array(y[Approx8Net::iTemp]);
|
||||
|
||||
// evaluate rates once per call
|
||||
const double rpp=pp_rate(T9);
|
||||
const double r33=he3he3_rate(T9);
|
||||
const double r34=he3he4_rate(T9);
|
||||
const double r3a=triple_alpha_rate(T9);
|
||||
const double rc12p=c12p_rate(T9);
|
||||
const double rc12a=c12a_rate(T9);
|
||||
const double rn14p=n14p_rate(T9);
|
||||
const double rn14a=n14a_rate(T9);
|
||||
const double ro16p=o16p_rate(T9);
|
||||
const double ro16a=o16a_rate(T9);
|
||||
const double rne20a=ne20a_rate(T9);
|
||||
const double r1212=c12c12_rate(T9);
|
||||
const double r1216=c12o16_rate(T9);
|
||||
|
||||
const double pFrac=n15pg_frac(T9);
|
||||
const double aFrac=1-pFrac;
|
||||
|
||||
const double yh1 = y[Approx8Net::ih1];
|
||||
const double yhe3 = y[Approx8Net::ihe3];
|
||||
const double yhe4 = y[Approx8Net::ihe4];
|
||||
const double yc12 = y[Approx8Net::ic12];
|
||||
const double yn14 = y[Approx8Net::in14];
|
||||
const double yo16 = y[Approx8Net::io16];
|
||||
const double yne20 = y[Approx8Net::ine20];
|
||||
|
||||
// zero all elements to begin
|
||||
for (int i=0; i < Approx8Net::nVar; i++) {
|
||||
for (int j=0; j < Approx8Net::nVar; j++) {
|
||||
J(i,j)=0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// h1 jacobian elements
|
||||
J(Approx8Net::ih1,Approx8Net::ih1) = -3*yh1*rpp - 2*yc12*rc12p -2*yn14*rn14p -2*yo16*ro16p;
|
||||
J(Approx8Net::ih1,Approx8Net::ihe3) = 2*yhe3*r33 - yhe4*r34;
|
||||
J(Approx8Net::ih1,Approx8Net::ihe4) = -yhe3*r34;
|
||||
J(Approx8Net::ih1,Approx8Net::ic12) = -2*yh1*rc12p;
|
||||
J(Approx8Net::ih1,Approx8Net::in14) = -2*yh1*rn14p;
|
||||
J(Approx8Net::ih1,Approx8Net::io16) = -2*yh1*ro16p;
|
||||
|
||||
// he3 jacobian elements
|
||||
J(Approx8Net::ihe3,Approx8Net::ih1) = yh1*rpp;
|
||||
J(Approx8Net::ihe3,Approx8Net::ihe3) = -2*yhe3*r33 - yhe4*r34;
|
||||
J(Approx8Net::ihe3,Approx8Net::ihe4) = -yhe3*r34;
|
||||
|
||||
// he4 jacobian elements
|
||||
J(Approx8Net::ihe4,Approx8Net::ih1) = yn14*aFrac*rn14p + yo16*ro16p;
|
||||
J(Approx8Net::ihe4,Approx8Net::ihe3) = yhe3*r33 - yhe4*r34;
|
||||
J(Approx8Net::ihe4,Approx8Net::ihe4) = yhe3*r34 - 1.5*yhe4*yhe4*r3a - yc12*rc12a - 1.5*yn14*rn14a - yo16*ro16a - yne20*rne20a;
|
||||
J(Approx8Net::ihe4,Approx8Net::ic12) = -yhe4*rc12a + yc12*r1212 + yo16*r1216;
|
||||
J(Approx8Net::ihe4,Approx8Net::in14) = yh1*aFrac*rn14p - 1.5*yhe4*rn14a;
|
||||
J(Approx8Net::ihe4,Approx8Net::io16) = yh1*ro16p - yhe4*ro16a + yc12*r1216;
|
||||
J(Approx8Net::ihe4,Approx8Net::ine20) = -yhe4*rne20a;
|
||||
|
||||
// c12 jacobian elements
|
||||
J(Approx8Net::ic12,Approx8Net::ih1) = -yc12*rc12p + yn14*aFrac*rn14p;
|
||||
J(Approx8Net::ic12,Approx8Net::ihe4) = 0.5*yhe4*yhe4*r3a - yhe4*rc12a;
|
||||
J(Approx8Net::ic12,Approx8Net::ic12) = -yh1*rc12p - yhe4*rc12a - yo16*r1216 - 2*yc12*r1212;
|
||||
J(Approx8Net::ic12,Approx8Net::in14) = yh1*yn14*aFrac*rn14p;
|
||||
J(Approx8Net::ic12,Approx8Net::io16) = -yc12*r1216;
|
||||
|
||||
// n14 jacobian elements
|
||||
J(Approx8Net::in14,Approx8Net::ih1) = yc12*rc12p - yn14*rn14p + yo16*ro16p;
|
||||
J(Approx8Net::in14,Approx8Net::ihe4) = -yn14*rn14a;
|
||||
J(Approx8Net::in14,Approx8Net::ic12) = yh1*rc12p;
|
||||
J(Approx8Net::in14,Approx8Net::in14) = -yh1*rn14p - yhe4*rn14a;
|
||||
J(Approx8Net::in14,Approx8Net::io16) = yo16*ro16p;
|
||||
|
||||
// o16 jacobian elements
|
||||
J(Approx8Net::io16,Approx8Net::ih1) = yn14*pFrac*rn14p - yo16*ro16p;
|
||||
J(Approx8Net::io16,Approx8Net::ihe4) = yc12*rc12a - yo16*ro16a;
|
||||
J(Approx8Net::io16,Approx8Net::ic12) = yhe4*rc12a - yo16*r1216;
|
||||
J(Approx8Net::io16,Approx8Net::in14) = yh1*pFrac*rn14p;
|
||||
J(Approx8Net::io16,Approx8Net::io16) = yh1*ro16p - yc12*r1216 -yhe4*ro16a;
|
||||
|
||||
// ne20 jacobian elements
|
||||
J(Approx8Net::ine20,Approx8Net::ihe4) = yn14*rn14a + yo16*ro16a - yne20*rne20a;
|
||||
J(Approx8Net::ine20,Approx8Net::ic12) = yc12*r1212;
|
||||
J(Approx8Net::ine20,Approx8Net::in14) = yhe4*rn14a;
|
||||
J(Approx8Net::ine20,Approx8Net::io16) = yo16*ro16a;
|
||||
J(Approx8Net::ine20,Approx8Net::ine20) = -yhe4*rne20a;
|
||||
|
||||
// mg24 jacobian elements
|
||||
J(Approx8Net::img24,Approx8Net::ihe4) = yne20*rne20a;
|
||||
J(Approx8Net::img24,Approx8Net::ic12) = yo16*r1216;
|
||||
J(Approx8Net::img24,Approx8Net::io16) = yc12*r1216;
|
||||
J(Approx8Net::img24,Approx8Net::ine20) = yhe4*rne20a;
|
||||
|
||||
// energy accounting
|
||||
for (int j=0; j<Approx8Net::nIso; j++) {
|
||||
for (int i=0; i<Approx8Net::nIso; i++) {
|
||||
J(Approx8Net::iEnergy,j) += J(i,j)*Approx8Net::mIon[i];
|
||||
}
|
||||
J(Approx8Net::iEnergy,j) *= -avo*clight*clight;
|
||||
}
|
||||
}
|
||||
|
||||
void ODE::operator() ( const vector_type &y, vector_type &dydt, double /* t */) const {
|
||||
const fourdst::constant::Constants& constants = fourdst::constant::Constants::getInstance();
|
||||
const double avo = constants.get("N_a").value;
|
||||
const double clight = constants.get("c").value;
|
||||
|
||||
// EOS
|
||||
const double T = y[Approx8Net::iTemp];
|
||||
const double den = y[Approx8Net::iDensity];
|
||||
const vec7 T9=get_T9_array(T);
|
||||
|
||||
// rates
|
||||
const double rpp=den*pp_rate(T9);
|
||||
const double r33=den*he3he3_rate(T9);
|
||||
const double r34=den*he3he4_rate(T9);
|
||||
const double r3a=den*den*triple_alpha_rate(T9);
|
||||
const double rc12p=den*c12p_rate(T9);
|
||||
const double rc12a=den*c12a_rate(T9);
|
||||
const double rn14p=den*n14p_rate(T9);
|
||||
const double rn14a=n14a_rate(T9);
|
||||
const double ro16p=den*o16p_rate(T9);
|
||||
const double ro16a=den*o16a_rate(T9);
|
||||
const double rne20a=den*ne20a_rate(T9);
|
||||
const double r1212=den*c12c12_rate(T9);
|
||||
const double r1216=den*c12o16_rate(T9);
|
||||
|
||||
const double pFrac=n15pg_frac(T9);
|
||||
const double aFrac=1-pFrac;
|
||||
|
||||
const double yh1 = y[Approx8Net:: ih1];
|
||||
const double yhe3 = y[Approx8Net:: ihe3];
|
||||
const double yhe4 = y[Approx8Net:: ihe4];
|
||||
const double yc12 = y[Approx8Net:: ic12];
|
||||
const double yn14 = y[Approx8Net:: in14];
|
||||
const double yo16 = y[Approx8Net:: io16];
|
||||
const double yne20 = y[Approx8Net::ine20];
|
||||
|
||||
dydt[Approx8Net::ih1] = -1.5*yh1*yh1*rpp;
|
||||
dydt[Approx8Net::ih1] += yhe3*yhe3*r33;
|
||||
dydt[Approx8Net::ih1] += -yhe3*yhe4*r34;
|
||||
dydt[Approx8Net::ih1] += -2*yh1*yc12*rc12p;
|
||||
dydt[Approx8Net::ih1] += -2*yh1*yn14*rn14p;
|
||||
dydt[Approx8Net::ih1] += -2*yh1*yo16*ro16p;
|
||||
|
||||
dydt[Approx8Net::ihe3] = 0.5*yh1*yh1*rpp;
|
||||
dydt[Approx8Net::ihe3] += -yhe3*yhe3*r33;
|
||||
dydt[Approx8Net::ihe3] += -yhe3*yhe4*r34;
|
||||
|
||||
dydt[Approx8Net::ihe4] = 0.5*yhe3*yhe3*r33;
|
||||
dydt[Approx8Net::ihe4] += yhe3*yhe4*r34;
|
||||
dydt[Approx8Net::ihe4] += -yhe4*yc12*rc12a;
|
||||
dydt[Approx8Net::ihe4] += yh1*yn14*aFrac*rn14p;
|
||||
dydt[Approx8Net::ihe4] += yh1*yo16*ro16p;
|
||||
dydt[Approx8Net::ihe4] += -0.5*yhe4*yhe4*yhe4*r3a;
|
||||
dydt[Approx8Net::ihe4] += -yhe4*yo16*ro16a;
|
||||
dydt[Approx8Net::ihe4] += 0.5*yc12*yc12*r1212;
|
||||
dydt[Approx8Net::ihe4] += yc12*yo16*r1216;
|
||||
dydt[Approx8Net::ihe4] += -yhe4*yne20*rne20a;
|
||||
|
||||
dydt[Approx8Net::ic12] = (1./6.)*yhe4*yhe4*yhe4*r3a;
|
||||
dydt[Approx8Net::ic12] += -yhe4*yc12*rc12a;
|
||||
dydt[Approx8Net::ic12] += -yh1*yc12*rc12p;
|
||||
dydt[Approx8Net::ic12] += yh1*yn14*aFrac*rn14p;
|
||||
dydt[Approx8Net::ic12] += -yc12*yc12*r1212;
|
||||
dydt[Approx8Net::ic12] += -yc12*yo16*r1216;
|
||||
|
||||
dydt[Approx8Net::in14] = yh1*yc12*rc12p;
|
||||
dydt[Approx8Net::in14] += -yh1*yn14*rn14p;
|
||||
dydt[Approx8Net::in14] += yh1*yo16*ro16p;
|
||||
dydt[Approx8Net::in14] += -yhe4*yn14*rn14a;
|
||||
|
||||
dydt[Approx8Net::io16] = yhe4*yc12*rc12a;
|
||||
dydt[Approx8Net::io16] += yh1*yn14*pFrac*rn14p;
|
||||
dydt[Approx8Net::io16] += -yh1*yo16*ro16p;
|
||||
dydt[Approx8Net::io16] += -yc12*yo16*r1216;
|
||||
dydt[Approx8Net::io16] += -yhe4*yo16*ro16a;
|
||||
|
||||
dydt[Approx8Net::ine20] = 0.5*yc12*yc12*r1212;
|
||||
dydt[Approx8Net::ine20] += yhe4*yn14*rn14a;
|
||||
dydt[Approx8Net::ine20] += yhe4*yo16*ro16a;
|
||||
dydt[Approx8Net::ine20] += -yhe4*yne20*rne20a;
|
||||
|
||||
dydt[Approx8Net::img24] = yc12*yo16*r1216;
|
||||
dydt[Approx8Net::img24] += yhe4*yne20*rne20a;
|
||||
|
||||
dydt[Approx8Net::iTemp] = 0.;
|
||||
dydt[Approx8Net::iDensity] = 0.;
|
||||
|
||||
// energy accounting
|
||||
double eNuc = 0.;
|
||||
for (int i=0; i<Approx8Net::nIso; i++) {
|
||||
eNuc += Approx8Net::mIon[i]*dydt[i];
|
||||
}
|
||||
dydt[Approx8Net::iEnergy] = -eNuc*avo*clight*clight;
|
||||
}
|
||||
|
||||
Approx8Network::Approx8Network() : Network(APPROX8) {}
|
||||
|
||||
NetOut Approx8Network::evaluate(const NetIn &netIn) {
|
||||
m_y = convert_netIn(netIn);
|
||||
m_tMax = netIn.tMax;
|
||||
m_dt0 = netIn.dt0;
|
||||
|
||||
const double stiff_abs_tol = m_config.get<double>("Network:Approx8:Stiff:AbsTol", 1.0e-6);
|
||||
const double stiff_rel_tol = m_config.get<double>("Network:Approx8:Stiff:RelTol", 1.0e-6);
|
||||
const double nonstiff_abs_tol = m_config.get<double>("Network:Approx8:NonStiff:AbsTol", 1.0e-6);
|
||||
const double nonstiff_rel_tol = m_config.get<double>("Network:Approx8:NonStiff:RelTol", 1.0e-6);
|
||||
|
||||
int num_steps = -1;
|
||||
|
||||
if (m_stiff) {
|
||||
LOG_DEBUG(m_logger, "Using stiff solver for Approx8Network");
|
||||
num_steps = integrate_const(
|
||||
make_dense_output<rosenbrock4<double>>(stiff_abs_tol, stiff_rel_tol),
|
||||
std::make_pair(ODE(), Jacobian()),
|
||||
m_y,
|
||||
0.0,
|
||||
m_tMax,
|
||||
m_dt0
|
||||
);
|
||||
|
||||
} else {
|
||||
LOG_DEBUG(m_logger, "Using non stiff solver for Approx8Network");
|
||||
num_steps = integrate_const (
|
||||
make_dense_output<runge_kutta_dopri5<vector_type>>(nonstiff_abs_tol, nonstiff_rel_tol),
|
||||
ODE(),
|
||||
m_y,
|
||||
0.0,
|
||||
m_tMax,
|
||||
m_dt0
|
||||
);
|
||||
}
|
||||
|
||||
double ySum = 0.0;
|
||||
for (int i = 0; i < Approx8Net::nIso; i++) {
|
||||
m_y[i] *= Approx8Net::aIon[i];
|
||||
ySum += m_y[i];
|
||||
}
|
||||
for (int i = 0; i < Approx8Net::nIso; i++) {
|
||||
m_y[i] /= ySum;
|
||||
}
|
||||
|
||||
NetOut netOut;
|
||||
std::vector<double> outComposition;
|
||||
outComposition.reserve(Approx8Net::nVar);
|
||||
|
||||
for (int i = 0; i < Approx8Net::nIso; i++) {
|
||||
outComposition.push_back(m_y[i]);
|
||||
}
|
||||
netOut.energy = m_y[Approx8Net::iEnergy];
|
||||
netOut.num_steps = num_steps;
|
||||
|
||||
const std::vector<std::string> symbols = {"H-1", "He-3", "He-4", "C-12", "N-14", "O-16", "Ne-20", "Mg-24"};
|
||||
netOut.composition = fourdst::composition::Composition(symbols, outComposition);
|
||||
|
||||
return netOut;
|
||||
}
|
||||
|
||||
void Approx8Network::setStiff(bool stiff) {
|
||||
m_stiff = stiff;
|
||||
}
|
||||
|
||||
vector_type Approx8Network::convert_netIn(const NetIn &netIn) {
|
||||
vector_type y(Approx8Net::nVar, 0.0);
|
||||
y[Approx8Net::ih1] = netIn.composition.getNumberFraction("H-1");
|
||||
y[Approx8Net::ihe3] = netIn.composition.getNumberFraction("He-3");
|
||||
y[Approx8Net::ihe4] = netIn.composition.getNumberFraction("He-4");
|
||||
y[Approx8Net::ic12] = netIn.composition.getNumberFraction("C-12");
|
||||
y[Approx8Net::in14] = netIn.composition.getNumberFraction("N-14");
|
||||
y[Approx8Net::io16] = netIn.composition.getNumberFraction("O-16");
|
||||
y[Approx8Net::ine20] = netIn.composition.getNumberFraction("Ne-20");
|
||||
y[Approx8Net::img24] = netIn.composition.getNumberFraction("Mg-24");
|
||||
y[Approx8Net::iTemp] = netIn.temperature;
|
||||
y[Approx8Net::iDensity] = netIn.density;
|
||||
y[Approx8Net::iEnergy] = netIn.energy;
|
||||
|
||||
return y;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// main program
|
||||
|
||||
1147
src/lib/engine/engine_graph.cpp
Normal file
1147
src/lib/engine/engine_graph.cpp
Normal file
File diff suppressed because it is too large
Load Diff
113
src/lib/engine/procedures/construction.cpp
Normal file
113
src/lib/engine/procedures/construction.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "gridfire/engine/procedures/construction.h"
|
||||
|
||||
#include <ranges>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "gridfire/reaction/reaction.h"
|
||||
#include "gridfire/reaction/reaclib.h"
|
||||
|
||||
#include "fourdst/composition/composition.h"
|
||||
|
||||
#include "fourdst/logging/logging.h"
|
||||
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/LogMacros.h"
|
||||
|
||||
namespace gridfire {
|
||||
using reaction::LogicalReactionSet;
|
||||
using reaction::ReactionSet;
|
||||
using reaction::Reaction;
|
||||
using fourdst::composition::Composition;
|
||||
using fourdst::atomic::Species;
|
||||
|
||||
|
||||
LogicalReactionSet build_reaclib_nuclear_network(
|
||||
const Composition &composition,
|
||||
BuildDepthType maxLayers,
|
||||
bool reverse
|
||||
) {
|
||||
int depth;
|
||||
if (std::holds_alternative<NetworkBuildDepth>(maxLayers)) {
|
||||
depth = static_cast<int>(std::get<NetworkBuildDepth>(maxLayers));
|
||||
} else {
|
||||
depth = std::get<int>(maxLayers);
|
||||
}
|
||||
auto logger = fourdst::logging::LogManager::getInstance().getLogger("log");
|
||||
if (depth == 0) {
|
||||
LOG_ERROR(logger, "Network build depth is set to 0. No reactions will be collected.");
|
||||
throw std::logic_error("Network build depth is set to 0. No reactions will be collected.");
|
||||
}
|
||||
|
||||
const auto allReactions = reaclib::get_all_reactions();
|
||||
std::vector<Reaction> remainingReactions;
|
||||
for (const auto& reaction : allReactions) {
|
||||
if (reaction.is_reverse() == reverse) {
|
||||
remainingReactions.push_back(reaction);
|
||||
}
|
||||
}
|
||||
|
||||
if (depth == static_cast<int>(NetworkBuildDepth::Full)) {
|
||||
LOG_INFO(logger, "Building full nuclear network with a total of {} reactions.", allReactions.size());
|
||||
const ReactionSet reactionSet(remainingReactions);
|
||||
return reaction::packReactionSetToLogicalReactionSet(reactionSet);
|
||||
}
|
||||
|
||||
std::unordered_set<Species> availableSpecies;
|
||||
for (const auto &entry: composition | std::views::values) {
|
||||
if (entry.mass_fraction() > 0.0) {
|
||||
availableSpecies.insert(entry.isotope());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<Reaction> collectedReactions;
|
||||
|
||||
LOG_INFO(logger, "Starting network construction with {} available species.", availableSpecies.size());
|
||||
for (int layer = 0; layer < depth && !remainingReactions.empty(); ++layer) {
|
||||
LOG_TRACE_L1(logger, "Collecting reactions for layer {} with {} remaining reactions. Currently there are {} available species", layer, remainingReactions.size(), availableSpecies.size());
|
||||
std::vector<Reaction> reactionsForNextPass;
|
||||
std::unordered_set<Species> newProductsThisLayer;
|
||||
bool newReactionsAdded = false;
|
||||
|
||||
reactionsForNextPass.reserve(remainingReactions.size());
|
||||
|
||||
for (const auto &reaction : remainingReactions) {
|
||||
bool allReactantsAvailable = true;
|
||||
for (const auto& reactant : reaction.reactants()) {
|
||||
if (!availableSpecies.contains(reactant)) {
|
||||
allReactantsAvailable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allReactantsAvailable) {
|
||||
collectedReactions.push_back(reaction);
|
||||
newReactionsAdded = true;
|
||||
|
||||
for (const auto& product : reaction.products()) {
|
||||
newProductsThisLayer.insert(product);
|
||||
}
|
||||
} else {
|
||||
reactionsForNextPass.push_back(reaction);
|
||||
}
|
||||
}
|
||||
|
||||
if (!newReactionsAdded) {
|
||||
LOG_INFO(logger, "No new reactions added in layer {}. Stopping network construction with {} reactions collected.", layer, collectedReactions.size());
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_TRACE_L1(logger, "Layer {}: Collected {} reactions. New products this layer: {}", layer, collectedReactions.size(), newProductsThisLayer.size());
|
||||
availableSpecies.insert(newProductsThisLayer.begin(), newProductsThisLayer.end());
|
||||
|
||||
remainingReactions = std::move(reactionsForNextPass);
|
||||
}
|
||||
|
||||
LOG_INFO(logger, "Network construction completed with {} reactions collected.", collectedReactions.size());
|
||||
const ReactionSet reactionSet(collectedReactions);
|
||||
return reaction::packReactionSetToLogicalReactionSet(reactionSet);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
229
src/lib/engine/procedures/priming.cpp
Normal file
229
src/lib/engine/procedures/priming.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "gridfire/engine/procedures/priming.h"
|
||||
#include "gridfire/engine/views/engine_priming.h"
|
||||
#include "gridfire/engine/procedures/construction.h"
|
||||
#include "gridfire/solver/solver.h"
|
||||
|
||||
#include "gridfire/engine/engine_abstract.h"
|
||||
#include "gridfire/network.h"
|
||||
|
||||
#include "fourdst/logging/logging.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/LogMacros.h"
|
||||
|
||||
namespace gridfire {
|
||||
using fourdst::composition::Composition;
|
||||
using fourdst::atomic::Species;
|
||||
|
||||
const reaction::Reaction* findDominantCreationChannel (
|
||||
const DynamicEngine& engine,
|
||||
const Species& species,
|
||||
const std::vector<double>& Y,
|
||||
const double T9,
|
||||
const double rho
|
||||
) {
|
||||
const reaction::Reaction* dominateReaction = nullptr;
|
||||
double maxFlow = -1.0;
|
||||
for (const auto& reaction : engine.getNetworkReactions()) {
|
||||
if (reaction.contains(species) && reaction.stoichiometry(species) > 0) {
|
||||
const double flow = engine.calculateMolarReactionFlow(reaction, Y, T9, rho);
|
||||
if (flow > maxFlow) {
|
||||
maxFlow = flow;
|
||||
dominateReaction = &reaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dominateReaction;
|
||||
}
|
||||
|
||||
|
||||
PrimingReport primeNetwork(const NetIn& netIn, DynamicEngine& engine) {
|
||||
auto logger = LogManager::getInstance().getLogger("log");
|
||||
|
||||
std::vector<Species> speciesToPrime;
|
||||
for (const auto &entry: netIn.composition | std::views::values) {
|
||||
if (entry.mass_fraction() == 0.0) {
|
||||
speciesToPrime.push_back(entry.isotope());
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(logger, "Priming {} species in the network.", speciesToPrime.size());
|
||||
|
||||
PrimingReport report;
|
||||
if (speciesToPrime.empty()) {
|
||||
report.primedComposition = netIn.composition;
|
||||
report.success = true;
|
||||
report.status = PrimingReportStatus::NO_SPECIES_TO_PRIME;
|
||||
return report;
|
||||
}
|
||||
|
||||
const double T9 = netIn.temperature / 1e9;
|
||||
const double rho = netIn.density;
|
||||
const auto initialReactionSet = engine.getNetworkReactions();
|
||||
|
||||
report.status = PrimingReportStatus::FULL_SUCCESS;
|
||||
report.success = true;
|
||||
|
||||
// --- 1: pack composition into internal map ---
|
||||
std::unordered_map<Species, double> currentMassFractions;
|
||||
for (const auto& entry : netIn.composition | std::views::values) {
|
||||
currentMassFractions[entry.isotope()] = entry.mass_fraction();
|
||||
}
|
||||
for (const auto& entry : speciesToPrime) {
|
||||
currentMassFractions[entry] = 0.0; // Initialize priming species with 0 mass fraction
|
||||
}
|
||||
|
||||
std::unordered_map<Species, double> totalMassFractionChanges;
|
||||
|
||||
engine.rebuild(netIn.composition, NetworkBuildDepth::Full);
|
||||
|
||||
for (const auto& primingSpecies : speciesToPrime) {
|
||||
LOG_TRACE_L3(logger, "Priming species: {}", primingSpecies.name());
|
||||
|
||||
// Create a temporary composition from the current internal state for the primer
|
||||
Composition tempComp;
|
||||
for(const auto& [sp, mf] : currentMassFractions) {
|
||||
tempComp.registerSymbol(std::string(sp.name()));
|
||||
if (mf < 0.0 && std::abs(mf) < 1e-16) {
|
||||
tempComp.setMassFraction(sp, 0.0); // Avoid negative mass fractions
|
||||
} else {
|
||||
tempComp.setMassFraction(sp, mf);
|
||||
}
|
||||
}
|
||||
tempComp.finalize(true); // Finalize with normalization
|
||||
|
||||
NetIn tempNetIn = netIn;
|
||||
tempNetIn.composition = tempComp;
|
||||
|
||||
NetworkPrimingEngineView primer(primingSpecies, engine);
|
||||
|
||||
if (primer.getNetworkReactions().size() == 0) {
|
||||
LOG_ERROR(logger, "No priming reactions found for species {}.", primingSpecies.name());
|
||||
report.success = false;
|
||||
report.status = PrimingReportStatus::FAILED_TO_FIND_PRIMING_REACTIONS;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto Y = primer.mapNetInToMolarAbundanceVector(tempNetIn);
|
||||
const double destructionRateConstant = calculateDestructionRateConstant(primer, primingSpecies, Y, T9, rho);
|
||||
|
||||
double equilibriumMassFraction = 0.0;
|
||||
|
||||
if (destructionRateConstant > 1e-99) {
|
||||
const double creationRate = calculateCreationRate(primer, primingSpecies, Y, T9, rho);
|
||||
equilibriumMassFraction = (creationRate / destructionRateConstant) * primingSpecies.mass();
|
||||
if (std::isnan(equilibriumMassFraction)) {
|
||||
LOG_WARNING(logger, "Equilibrium mass fraction for {} is NaN. Setting to 0.0. This is likely not an issue. It probably originates from all reactions leading to creation and destruction being frozen out. In that case 0.0 should be a good approximation. Hint: This happens often when the network temperature is very the low. ", primingSpecies.name());
|
||||
equilibriumMassFraction = 0.0;
|
||||
}
|
||||
LOG_TRACE_L3(logger, "Found equilibrium for {}: X_eq = {:.4e}", primingSpecies.name(), equilibriumMassFraction);
|
||||
|
||||
const reaction::Reaction* dominantChannel = findDominantCreationChannel(primer, primingSpecies, Y, T9, rho);
|
||||
|
||||
if (dominantChannel) {
|
||||
LOG_TRACE_L3(logger, "Dominant creation channel for {}: {}", primingSpecies.name(), dominantChannel->peName());
|
||||
|
||||
double totalReactantMass = 0.0;
|
||||
for (const auto& reactant : dominantChannel->reactants()) {
|
||||
totalReactantMass += reactant.mass();
|
||||
}
|
||||
|
||||
double scalingFactor = 1.0;
|
||||
for (const auto& reactant : dominantChannel->reactants()) {
|
||||
const double massToSubtract = equilibriumMassFraction * (reactant.mass() / totalReactantMass);
|
||||
double availableMass = 0.0;
|
||||
if (currentMassFractions.contains(reactant)) {
|
||||
availableMass = currentMassFractions.at(reactant);
|
||||
}
|
||||
if (massToSubtract > availableMass && availableMass > 0) {
|
||||
scalingFactor = std::min(scalingFactor, availableMass / massToSubtract);
|
||||
}
|
||||
}
|
||||
|
||||
if (scalingFactor < 1.0) {
|
||||
LOG_WARNING(logger, "Priming for {} was limited by reactant availability. Scaling transfer by {:.4e}", primingSpecies.name(), scalingFactor);
|
||||
equilibriumMassFraction *= scalingFactor;
|
||||
}
|
||||
|
||||
// Update the internal mass fraction map and accumulate total changes
|
||||
totalMassFractionChanges[primingSpecies] += equilibriumMassFraction;
|
||||
currentMassFractions[primingSpecies] += equilibriumMassFraction;
|
||||
|
||||
for (const auto& reactant : dominantChannel->reactants()) {
|
||||
const double massToSubtract = equilibriumMassFraction * (reactant.mass() / totalReactantMass);
|
||||
totalMassFractionChanges[reactant] -= massToSubtract;
|
||||
currentMassFractions[reactant] -= massToSubtract;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(logger, "Failed to find dominant creation channel for {}.", primingSpecies.name());
|
||||
report.status = PrimingReportStatus::FAILED_TO_FIND_CREATION_CHANNEL;
|
||||
totalMassFractionChanges[primingSpecies] += 1e-40;
|
||||
currentMassFractions[primingSpecies] += 1e-40;
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(logger, "No destruction channel found for {}. Using fallback abundance.", primingSpecies.name());
|
||||
totalMassFractionChanges[primingSpecies] += 1e-40;
|
||||
currentMassFractions[primingSpecies] += 1e-40;
|
||||
report.status = PrimingReportStatus::BASE_NETWORK_TOO_SHALLOW;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Final Composition Construction ---
|
||||
std::vector<std::string> final_symbols;
|
||||
std::vector<double> final_mass_fractions;
|
||||
for(const auto& [species, mass_fraction] : currentMassFractions) {
|
||||
final_symbols.push_back(std::string(species.name()));
|
||||
if (mass_fraction < 0.0 && std::abs(mass_fraction) < 1e-16) {
|
||||
final_mass_fractions.push_back(0.0); // Avoid negative mass fractions
|
||||
} else {
|
||||
final_mass_fractions.push_back(mass_fraction);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the final composition object from the pre-normalized mass fractions
|
||||
Composition primedComposition(final_symbols, final_mass_fractions, true);
|
||||
|
||||
report.primedComposition = primedComposition;
|
||||
for (const auto& [species, change] : totalMassFractionChanges) {
|
||||
report.massFractionChanges.emplace_back(species, change);
|
||||
}
|
||||
|
||||
engine.setNetworkReactions(initialReactionSet);
|
||||
return report;
|
||||
}
|
||||
|
||||
double calculateDestructionRateConstant(
|
||||
const DynamicEngine& engine,
|
||||
const fourdst::atomic::Species& species,
|
||||
const std::vector<double>& Y,
|
||||
const double T9,
|
||||
const double rho
|
||||
) {
|
||||
const int speciesIndex = engine.getSpeciesIndex(species);
|
||||
std::vector<double> Y_scaled(Y.begin(), Y.end());
|
||||
Y_scaled[speciesIndex] = 1.0; // Set the abundance of the species to 1.0 for rate constant calculation
|
||||
double destructionRateConstant = 0.0;
|
||||
for (const auto& reaction: engine.getNetworkReactions()) {
|
||||
if (reaction.contains_reactant(species)) {
|
||||
const int stoichiometry = reaction.stoichiometry(species);
|
||||
destructionRateConstant += std::abs(stoichiometry) * engine.calculateMolarReactionFlow(reaction, Y_scaled, T9, rho);
|
||||
}
|
||||
}
|
||||
return destructionRateConstant;
|
||||
}
|
||||
|
||||
double calculateCreationRate(
|
||||
const DynamicEngine& engine,
|
||||
const fourdst::atomic::Species& species,
|
||||
const std::vector<double>& Y,
|
||||
const double T9,
|
||||
const double rho
|
||||
) {
|
||||
double creationRate = 0.0;
|
||||
for (const auto& reaction: engine.getNetworkReactions()) {
|
||||
const int stoichiometry = reaction.stoichiometry(species);
|
||||
if (stoichiometry > 0) {
|
||||
creationRate += stoichiometry * engine.calculateMolarReactionFlow(reaction, Y, T9, rho);
|
||||
}
|
||||
}
|
||||
return creationRate;
|
||||
}
|
||||
}
|
||||
673
src/lib/engine/views/engine_adaptive.cpp
Normal file
673
src/lib/engine/views/engine_adaptive.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
#include "gridfire/engine/views/engine_adaptive.h"
|
||||
|
||||
#include <ranges>
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
#include "gridfire/network.h"
|
||||
#include "gridfire/exceptions/error_engine.h"
|
||||
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
namespace gridfire {
|
||||
using fourdst::atomic::Species;
|
||||
AdaptiveEngineView::AdaptiveEngineView(
|
||||
DynamicEngine &baseEngine
|
||||
) :
|
||||
m_baseEngine(baseEngine),
|
||||
m_activeSpecies(baseEngine.getNetworkSpecies()),
|
||||
m_activeReactions(baseEngine.getNetworkReactions()),
|
||||
m_speciesIndexMap(constructSpeciesIndexMap()),
|
||||
m_reactionIndexMap(constructReactionIndexMap())
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<size_t> AdaptiveEngineView::constructSpeciesIndexMap() const {
|
||||
LOG_TRACE_L1(m_logger, "Constructing species index map for adaptive engine view...");
|
||||
std::unordered_map<Species, size_t> fullSpeciesReverseMap;
|
||||
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
|
||||
|
||||
fullSpeciesReverseMap.reserve(fullSpeciesList.size());
|
||||
|
||||
for (size_t i = 0; i < fullSpeciesList.size(); ++i) {
|
||||
fullSpeciesReverseMap[fullSpeciesList[i]] = i;
|
||||
}
|
||||
|
||||
std::vector<size_t> speciesIndexMap;
|
||||
speciesIndexMap.reserve(m_activeSpecies.size());
|
||||
|
||||
for (const auto& active_species : m_activeSpecies) {
|
||||
auto it = fullSpeciesReverseMap.find(active_species);
|
||||
if (it != fullSpeciesReverseMap.end()) {
|
||||
speciesIndexMap.push_back(it->second);
|
||||
} else {
|
||||
LOG_ERROR(m_logger, "Species '{}' not found in full species map.", active_species.name());
|
||||
m_logger -> flush_log();
|
||||
throw std::runtime_error("Species not found in full species map: " + std::string(active_species.name()));
|
||||
}
|
||||
}
|
||||
LOG_TRACE_L1(m_logger, "Species index map constructed with {} entries.", speciesIndexMap.size());
|
||||
return speciesIndexMap;
|
||||
|
||||
}
|
||||
|
||||
std::vector<size_t> AdaptiveEngineView::constructReactionIndexMap() const {
|
||||
LOG_TRACE_L1(m_logger, "Constructing reaction index map for adaptive engine view...");
|
||||
|
||||
// --- Step 1: Create a reverse map using the reaction's unique ID as the key. ---
|
||||
std::unordered_map<std::string_view, size_t> fullReactionReverseMap;
|
||||
const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
|
||||
fullReactionReverseMap.reserve(fullReactionSet.size());
|
||||
|
||||
for (size_t i_full = 0; i_full < fullReactionSet.size(); ++i_full) {
|
||||
fullReactionReverseMap[fullReactionSet[i_full].id()] = i_full;
|
||||
}
|
||||
|
||||
// --- Step 2: Build the final index map using the active reaction set. ---
|
||||
std::vector<size_t> reactionIndexMap;
|
||||
reactionIndexMap.reserve(m_activeReactions.size());
|
||||
|
||||
for (const auto& active_reaction_ptr : m_activeReactions) {
|
||||
auto it = fullReactionReverseMap.find(active_reaction_ptr.id());
|
||||
|
||||
if (it != fullReactionReverseMap.end()) {
|
||||
reactionIndexMap.push_back(it->second);
|
||||
} else {
|
||||
LOG_ERROR(m_logger, "Active reaction '{}' not found in base engine during reaction index map construction.", active_reaction_ptr.id());
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("Mismatch between active reactions and base engine.");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_TRACE_L1(m_logger, "Reaction index map constructed with {} entries.", reactionIndexMap.size());
|
||||
return reactionIndexMap;
|
||||
}
|
||||
|
||||
fourdst::composition::Composition AdaptiveEngineView::update(const NetIn &netIn) {
|
||||
fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.update(netIn);
|
||||
NetIn updatedNetIn = netIn;
|
||||
|
||||
// for (const auto &entry: netIn.composition | std::views::values) {
|
||||
// if (baseUpdatedComposition.contains(entry.isotope())) {
|
||||
// updatedNetIn.composition.setMassFraction(entry.isotope(), baseUpdatedComposition.getMassFraction(entry.isotope()));
|
||||
// }
|
||||
// }
|
||||
updatedNetIn.composition = baseUpdatedComposition;
|
||||
|
||||
updatedNetIn.composition.finalize(false);
|
||||
|
||||
LOG_TRACE_L1(m_logger, "Updating AdaptiveEngineView with new network input...");
|
||||
|
||||
std::vector<double> Y_Full;
|
||||
std::vector<ReactionFlow> allFlows = calculateAllReactionFlows(updatedNetIn, Y_Full);
|
||||
|
||||
double maxFlow = 0.0;
|
||||
|
||||
for (const auto&[reactionPtr, flowRate]: allFlows) {
|
||||
if (flowRate > maxFlow) {
|
||||
maxFlow = flowRate;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(m_logger, "Maximum reaction flow rate in adaptive engine view: {:0.3E} [mol/s]", maxFlow);
|
||||
|
||||
const std::unordered_set<Species> reachableSpecies = findReachableSpecies(updatedNetIn);
|
||||
LOG_DEBUG(m_logger, "Found {} reachable species in adaptive engine view.", reachableSpecies.size());
|
||||
|
||||
const std::vector<const reaction::LogicalReaction*> finalReactions = cullReactionsByFlow(allFlows, reachableSpecies, Y_Full, maxFlow);
|
||||
|
||||
finalizeActiveSet(finalReactions);
|
||||
|
||||
auto [rescuedReactions, rescuedSpecies] = rescueEdgeSpeciesDestructionChannel(Y_Full, netIn.temperature/1e9, netIn.density, m_activeSpecies, m_activeReactions);
|
||||
|
||||
for (const auto& reactionPtr : rescuedReactions) {
|
||||
m_activeReactions.add_reaction(*reactionPtr);
|
||||
}
|
||||
|
||||
for (const auto& species : rescuedSpecies) {
|
||||
if (!std::ranges::contains(m_activeSpecies, species)) {
|
||||
m_activeSpecies.push_back(species);
|
||||
}
|
||||
}
|
||||
|
||||
m_speciesIndexMap = constructSpeciesIndexMap();
|
||||
m_reactionIndexMap = constructReactionIndexMap();
|
||||
|
||||
m_isStale = false;
|
||||
|
||||
LOG_INFO(m_logger, "AdaptiveEngineView updated successfully with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
|
||||
|
||||
return updatedNetIn.composition;
|
||||
}
|
||||
|
||||
bool AdaptiveEngineView::isStale(const NetIn &netIn) {
|
||||
return m_isStale || m_baseEngine.isStale(netIn);
|
||||
}
|
||||
|
||||
const std::vector<Species> & AdaptiveEngineView::getNetworkSpecies() const {
|
||||
return m_activeSpecies;
|
||||
}
|
||||
|
||||
std::expected<StepDerivatives<double>, expectations::StaleEngineError> AdaptiveEngineView::calculateRHSAndEnergy(
|
||||
const std::vector<double> &Y_culled,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateState();
|
||||
|
||||
const auto Y_full = mapCulledToFull(Y_culled);
|
||||
|
||||
auto result = m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
|
||||
|
||||
if (!result) {
|
||||
return std::unexpected{result.error()};
|
||||
}
|
||||
|
||||
const auto [dydt, nuclearEnergyGenerationRate] = result.value();
|
||||
StepDerivatives<double> culledResults;
|
||||
culledResults.nuclearEnergyGenerationRate = nuclearEnergyGenerationRate;
|
||||
culledResults.dydt = mapFullToCulled(dydt);
|
||||
|
||||
return culledResults;
|
||||
}
|
||||
|
||||
void AdaptiveEngineView::generateJacobianMatrix(
|
||||
const std::vector<double> &Y_dynamic,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateState();
|
||||
const auto Y_full = mapCulledToFull(Y_dynamic);
|
||||
|
||||
m_baseEngine.generateJacobianMatrix(Y_full, T9, rho);
|
||||
}
|
||||
|
||||
double AdaptiveEngineView::getJacobianMatrixEntry(
|
||||
const int i_culled,
|
||||
const int j_culled
|
||||
) const {
|
||||
validateState();
|
||||
const size_t i_full = mapCulledToFullSpeciesIndex(i_culled);
|
||||
const size_t j_full = mapCulledToFullSpeciesIndex(j_culled);
|
||||
|
||||
return m_baseEngine.getJacobianMatrixEntry(i_full, j_full);
|
||||
}
|
||||
|
||||
void AdaptiveEngineView::generateStoichiometryMatrix() {
|
||||
validateState();
|
||||
m_baseEngine.generateStoichiometryMatrix();
|
||||
}
|
||||
|
||||
int AdaptiveEngineView::getStoichiometryMatrixEntry(
|
||||
const int speciesIndex_culled,
|
||||
const int reactionIndex_culled
|
||||
) const {
|
||||
validateState();
|
||||
const size_t speciesIndex_full = mapCulledToFullSpeciesIndex(speciesIndex_culled);
|
||||
const size_t reactionIndex_full = mapCulledToFullReactionIndex(reactionIndex_culled);
|
||||
return m_baseEngine.getStoichiometryMatrixEntry(speciesIndex_full, reactionIndex_full);
|
||||
}
|
||||
|
||||
double AdaptiveEngineView::calculateMolarReactionFlow(
|
||||
const reaction::Reaction &reaction,
|
||||
const std::vector<double> &Y_culled,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateState();
|
||||
if (!m_activeReactions.contains(reaction)) {
|
||||
LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the adaptive engine view.", reaction.id());
|
||||
m_logger -> flush_log();
|
||||
throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
|
||||
}
|
||||
const auto Y = mapCulledToFull(Y_culled);
|
||||
|
||||
return m_baseEngine.calculateMolarReactionFlow(reaction, Y, T9, rho);
|
||||
}
|
||||
|
||||
const reaction::LogicalReactionSet & AdaptiveEngineView::getNetworkReactions() const {
|
||||
return m_activeReactions;
|
||||
}
|
||||
|
||||
void AdaptiveEngineView::setNetworkReactions(const reaction::LogicalReactionSet &reactions) {
|
||||
LOG_CRITICAL(m_logger, "AdaptiveEngineView does not support setting network reactions directly. Use update() with NetIn instead. Perhaps you meant to call this on the base engine?");
|
||||
throw exceptions::UnableToSetNetworkReactionsError("AdaptiveEngineView does not support setting network reactions directly. Use update() with NetIn instead. Perhaps you meant to call this on the base engine?");
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError> AdaptiveEngineView::getSpeciesTimescales(
|
||||
const std::vector<double> &Y_culled,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateState();
|
||||
const auto Y_full = mapCulledToFull(Y_culled);
|
||||
const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
|
||||
|
||||
if (!result) {
|
||||
return std::unexpected{result.error()};
|
||||
}
|
||||
|
||||
const std::unordered_map<Species, double> fullTimescales = result.value();
|
||||
|
||||
|
||||
std::unordered_map<Species, double> culledTimescales;
|
||||
culledTimescales.reserve(m_activeSpecies.size());
|
||||
for (const auto& active_species : m_activeSpecies) {
|
||||
if (fullTimescales.contains(active_species)) {
|
||||
culledTimescales[active_species] = fullTimescales.at(active_species);
|
||||
}
|
||||
}
|
||||
return culledTimescales;
|
||||
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError>
|
||||
AdaptiveEngineView::getSpeciesDestructionTimescales(
|
||||
const std::vector<double> &Y,
|
||||
double T9,
|
||||
double rho
|
||||
) const {
|
||||
validateState();
|
||||
|
||||
const auto Y_full = mapCulledToFull(Y);
|
||||
const auto result = m_baseEngine.getSpeciesDestructionTimescales(Y_full, T9, rho);
|
||||
|
||||
if (!result) {
|
||||
return std::unexpected{result.error()};
|
||||
}
|
||||
|
||||
const std::unordered_map<Species, double> destructionTimescales = result.value();
|
||||
|
||||
std::unordered_map<Species, double> culledTimescales;
|
||||
culledTimescales.reserve(m_activeSpecies.size());
|
||||
for (const auto& active_species : m_activeSpecies) {
|
||||
if (destructionTimescales.contains(active_species)) {
|
||||
culledTimescales[active_species] = destructionTimescales.at(active_species);
|
||||
}
|
||||
}
|
||||
return culledTimescales;
|
||||
}
|
||||
|
||||
void AdaptiveEngineView::setScreeningModel(const screening::ScreeningType model) {
|
||||
m_baseEngine.setScreeningModel(model);
|
||||
}
|
||||
|
||||
screening::ScreeningType AdaptiveEngineView::getScreeningModel() const {
|
||||
return m_baseEngine.getScreeningModel();
|
||||
}
|
||||
|
||||
std::vector<double> AdaptiveEngineView::mapNetInToMolarAbundanceVector(const NetIn &netIn) const {
|
||||
std::vector<double> Y(m_activeSpecies.size(), 0.0); // Initialize with zeros
|
||||
for (const auto& [symbol, entry] : netIn.composition) {
|
||||
Y[getSpeciesIndex(entry.isotope())] = netIn.composition.getMolarAbundance(symbol); // Map species to their molar abundance
|
||||
}
|
||||
return Y; // Return the vector of molar abundances
|
||||
}
|
||||
|
||||
PrimingReport AdaptiveEngineView::primeEngine(const NetIn &netIn) {
|
||||
return m_baseEngine.primeEngine(netIn);
|
||||
}
|
||||
|
||||
int AdaptiveEngineView::getSpeciesIndex(const fourdst::atomic::Species &species) const {
|
||||
const auto it = std::ranges::find(m_activeSpecies, species);
|
||||
if (it != m_activeSpecies.end()) {
|
||||
return static_cast<int>(std::distance(m_activeSpecies.begin(), it));
|
||||
} else {
|
||||
LOG_ERROR(m_logger, "Species '{}' not found in active species list.", species.name());
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("Species not found in active species list: " + std::string(species.name()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> AdaptiveEngineView::mapCulledToFull(const std::vector<double>& culled) const {
|
||||
std::vector<double> full(m_baseEngine.getNetworkSpecies().size(), 0.0);
|
||||
for (size_t i_culled = 0; i_culled < culled.size(); ++i_culled) {
|
||||
const size_t i_full = m_speciesIndexMap[i_culled];
|
||||
full[i_full] += culled[i_culled];
|
||||
}
|
||||
return full;
|
||||
}
|
||||
|
||||
std::vector<double> AdaptiveEngineView::mapFullToCulled(const std::vector<double>& full) const {
|
||||
std::vector<double> culled(m_activeSpecies.size(), 0.0);
|
||||
for (size_t i_culled = 0; i_culled < m_activeSpecies.size(); ++i_culled) {
|
||||
const size_t i_full = m_speciesIndexMap[i_culled];
|
||||
culled[i_culled] = full[i_full];
|
||||
}
|
||||
return culled;
|
||||
}
|
||||
|
||||
size_t AdaptiveEngineView::mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const {
|
||||
if (culledSpeciesIndex < 0 || culledSpeciesIndex >= static_cast<int>(m_speciesIndexMap.size())) {
|
||||
LOG_ERROR(m_logger, "Culled index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
|
||||
m_logger->flush_log();
|
||||
throw std::out_of_range("Culled index " + std::to_string(culledSpeciesIndex) + " is out of bounds for species index map of size " + std::to_string(m_speciesIndexMap.size()) + ".");
|
||||
}
|
||||
return m_speciesIndexMap[culledSpeciesIndex];
|
||||
}
|
||||
|
||||
size_t AdaptiveEngineView::mapCulledToFullReactionIndex(size_t culledReactionIndex) const {
|
||||
if (culledReactionIndex < 0 || culledReactionIndex >= static_cast<int>(m_reactionIndexMap.size())) {
|
||||
LOG_ERROR(m_logger, "Culled index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
|
||||
m_logger->flush_log();
|
||||
throw std::out_of_range("Culled index " + std::to_string(culledReactionIndex) + " is out of bounds for reaction index map of size " + std::to_string(m_reactionIndexMap.size()) + ".");
|
||||
}
|
||||
return m_reactionIndexMap[culledReactionIndex];
|
||||
}
|
||||
|
||||
void AdaptiveEngineView::validateState() const {
|
||||
if (m_isStale) {
|
||||
LOG_ERROR(m_logger, "AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Change this to use a return value instead of an output parameter.
|
||||
std::vector<AdaptiveEngineView::ReactionFlow> AdaptiveEngineView::calculateAllReactionFlows(
|
||||
const NetIn &netIn,
|
||||
std::vector<double> &out_Y_Full
|
||||
) const {
|
||||
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
|
||||
out_Y_Full.clear();
|
||||
out_Y_Full.reserve(fullSpeciesList.size());
|
||||
|
||||
for (const auto& species: fullSpeciesList) {
|
||||
if (netIn.composition.contains(species)) {
|
||||
out_Y_Full.push_back(netIn.composition.getMolarAbundance(std::string(species.name())));
|
||||
} else {
|
||||
LOG_TRACE_L2(m_logger, "Species '{}' not found in composition. Setting abundance to 0.0.", species.name());
|
||||
out_Y_Full.push_back(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
const double T9 = netIn.temperature / 1e9; // Convert temperature from Kelvin to T9 (T9 = T / 1e9)
|
||||
const double rho = netIn.density; // Density in g/cm^3
|
||||
|
||||
std::vector<ReactionFlow> reactionFlows;
|
||||
const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
|
||||
reactionFlows.reserve(fullReactionSet.size());
|
||||
for (const auto& reaction : fullReactionSet) {
|
||||
const double flow = m_baseEngine.calculateMolarReactionFlow(reaction, out_Y_Full, T9, rho);
|
||||
reactionFlows.push_back({&reaction, flow});
|
||||
LOG_TRACE_L1(m_logger, "Reaction '{}' has flow rate: {:0.3E} [mol/s/g]", reaction.id(), flow);
|
||||
}
|
||||
return reactionFlows;
|
||||
}
|
||||
|
||||
std::unordered_set<Species> AdaptiveEngineView::findReachableSpecies(
|
||||
const NetIn &netIn
|
||||
) const {
|
||||
std::unordered_set<Species> reachable;
|
||||
std::queue<Species> to_vist;
|
||||
|
||||
constexpr double ABUNDANCE_FLOOR = 1e-12; // Abundance floor for a species to be considered part of the initial fuel
|
||||
for (const auto& species: m_baseEngine.getNetworkSpecies()) {
|
||||
if (netIn.composition.contains(species) && netIn.composition.getMassFraction(std::string(species.name())) > ABUNDANCE_FLOOR) {
|
||||
if (!reachable.contains(species)) {
|
||||
to_vist.push(species);
|
||||
reachable.insert(species);
|
||||
LOG_TRACE_L2(m_logger, "Network Connectivity Analysis: Species '{}' is part of the initial fuel.", species.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool new_species_found_in_pass = true;
|
||||
while (new_species_found_in_pass) {
|
||||
new_species_found_in_pass = false;
|
||||
for (const auto& reaction: m_baseEngine.getNetworkReactions()) {
|
||||
bool all_reactants_reachable = true;
|
||||
for (const auto& reactant: reaction.reactants()) {
|
||||
if (!reachable.contains(reactant)) {
|
||||
all_reactants_reachable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_reactants_reachable) {
|
||||
for (const auto& product: reaction.products()) {
|
||||
if (!reachable.contains(product)) {
|
||||
reachable.insert(product);
|
||||
new_species_found_in_pass = true;
|
||||
LOG_TRACE_L2(m_logger, "Network Connectivity Analysis: Species '{}' is reachable via reaction '{}'.", product.name(), reaction.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reachable;
|
||||
}
|
||||
|
||||
std::vector<const reaction::LogicalReaction *> AdaptiveEngineView::cullReactionsByFlow(
|
||||
const std::vector<ReactionFlow> &allFlows,
|
||||
const std::unordered_set<fourdst::atomic::Species> &reachableSpecies,
|
||||
const std::vector<double> &Y_full,
|
||||
const double maxFlow
|
||||
) const {
|
||||
LOG_TRACE_L1(m_logger, "Culling reactions based on flow rates...");
|
||||
const auto relative_culling_threshold = m_config.get<double>("gridfire:AdaptiveEngineView:RelativeCullingThreshold", 1e-75);
|
||||
double absoluteCullingThreshold = relative_culling_threshold * maxFlow;
|
||||
LOG_DEBUG(m_logger, "Relative culling threshold: {:0.3E} ({})", relative_culling_threshold, absoluteCullingThreshold);
|
||||
std::vector<const reaction::LogicalReaction*> culledReactions;
|
||||
for (const auto& [reactionPtr, flowRate]: allFlows) {
|
||||
bool keepReaction = false;
|
||||
if (flowRate > absoluteCullingThreshold) {
|
||||
LOG_TRACE_L2(m_logger, "Maintaining reaction '{}' with relative (abs) flow rate: {:0.3E} ({:0.3E} [mol/s])", reactionPtr->id(), flowRate/maxFlow, flowRate);
|
||||
keepReaction = true;
|
||||
} else {
|
||||
bool zero_flow_due_to_reachable_reactants = false;
|
||||
if (flowRate < 1e-99 && flowRate > 0.0) {
|
||||
for (const auto& reactant: reactionPtr->reactants()) {
|
||||
const auto it = std::ranges::find(m_baseEngine.getNetworkSpecies(), reactant);
|
||||
const size_t index = std::distance(m_baseEngine.getNetworkSpecies().begin(), it);
|
||||
if (Y_full[index] < 1e-99 && reachableSpecies.contains(reactant)) {
|
||||
LOG_TRACE_L1(m_logger, "Maintaining reaction '{}' with low flow ({:0.3E} [mol/s/g]) due to reachable reactant '{}'.", reactionPtr->id(), flowRate, reactant.name());
|
||||
zero_flow_due_to_reachable_reactants = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zero_flow_due_to_reachable_reactants) {
|
||||
keepReaction = true;
|
||||
}
|
||||
}
|
||||
if (keepReaction) {
|
||||
culledReactions.push_back(reactionPtr);
|
||||
} else {
|
||||
LOG_TRACE_L1(m_logger, "Culling reaction '{}' due to low flow rate or lack of connectivity.", reactionPtr->id());
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(m_logger, "Selected {} (total: {}, culled: {}) reactions based on flow rates.", culledReactions.size(), allFlows.size(), allFlows.size() - culledReactions.size());
|
||||
return culledReactions;
|
||||
}
|
||||
|
||||
AdaptiveEngineView::RescueSet AdaptiveEngineView::rescueEdgeSpeciesDestructionChannel(
|
||||
const std::vector<double> &Y_full,
|
||||
const double T9,
|
||||
const double rho,
|
||||
const std::vector<Species> &activeSpecies,
|
||||
const reaction::LogicalReactionSet &activeReactions
|
||||
) const {
|
||||
const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
|
||||
if (!result) {
|
||||
LOG_ERROR(m_logger, "Failed to get species timescales due to stale engine state.");
|
||||
throw exceptions::StaleEngineError("Failed to get species timescales");
|
||||
}
|
||||
std::unordered_map<Species, double> timescales = result.value();
|
||||
std::set<Species> onlyProducedSpecies;
|
||||
for (const auto& reaction : activeReactions) {
|
||||
const std::vector<Species> products = reaction.products();
|
||||
onlyProducedSpecies.insert(products.begin(), products.end());
|
||||
}
|
||||
|
||||
// Remove species that are consumed by any one of the active reactions.
|
||||
std::erase_if(
|
||||
onlyProducedSpecies,
|
||||
[&](const Species &species) {
|
||||
for (const auto& reaction : activeReactions) {
|
||||
if (reaction.contains_reactant(species)) {
|
||||
return true; // If any active reaction consumes the species then erase it from the set.
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Remove species that have a non-zero timescale (these are expected to be +inf as they should be the equilibrium species if using with a MultiscalePartitioningEngineView)
|
||||
std::erase_if(
|
||||
onlyProducedSpecies,
|
||||
[&](const Species &species) {
|
||||
return std::isinf(timescales.at(species));
|
||||
}
|
||||
);
|
||||
|
||||
LOG_TRACE_L1(
|
||||
m_logger,
|
||||
"Found {} species {}that are produced but not consumed in any reaction and do not have an inf timescale (those are expected to be equilibrium species and do not contribute to the stiffness of the network).",
|
||||
onlyProducedSpecies.size(),
|
||||
[&]() -> std::string {
|
||||
std::ostringstream ss;
|
||||
if (onlyProducedSpecies.empty()) {
|
||||
return "";
|
||||
}
|
||||
int count = 0;
|
||||
ss << "(";
|
||||
for (const auto& species : onlyProducedSpecies) {
|
||||
ss << species.name();
|
||||
if (count < onlyProducedSpecies.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
count++;
|
||||
}
|
||||
ss << ") ";
|
||||
return ss.str();
|
||||
}()
|
||||
);
|
||||
|
||||
std::unordered_map<Species, const reaction::LogicalReaction*> reactionsToRescue;
|
||||
for (const auto& species : onlyProducedSpecies) {
|
||||
double maxSpeciesConsumptionRate = 0.0;
|
||||
for (const auto& reaction : m_baseEngine.getNetworkReactions()) {
|
||||
const bool speciesToCheckIsConsumed = reaction.contains_reactant(species);
|
||||
if (!speciesToCheckIsConsumed) {
|
||||
continue; // If the species is not consumed by this reaction, skip it.
|
||||
}
|
||||
bool allOtherReactantsAreAvailable = true;
|
||||
for (const auto& reactant : reaction.reactants()) {
|
||||
const bool reactantIsAvailable = std::ranges::contains(activeSpecies, reactant);
|
||||
if (!reactantIsAvailable && reactant != species) {
|
||||
allOtherReactantsAreAvailable = false;
|
||||
}
|
||||
}
|
||||
if (allOtherReactantsAreAvailable && speciesToCheckIsConsumed) {
|
||||
double rate = reaction.calculate_rate(T9);
|
||||
if (rate > maxSpeciesConsumptionRate) {
|
||||
maxSpeciesConsumptionRate = rate;
|
||||
reactionsToRescue[species] = &reaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_TRACE_L1(
|
||||
m_logger,
|
||||
"Rescuing {} {}reactions",
|
||||
reactionsToRescue.size(),
|
||||
[&]() -> std::string {
|
||||
std::ostringstream ss;
|
||||
if (reactionsToRescue.empty()) {
|
||||
return "";
|
||||
}
|
||||
int count = 0;
|
||||
ss << "(";
|
||||
for (const auto &reaction : reactionsToRescue | std::views::values) {
|
||||
ss << reaction->id();
|
||||
if (count < reactionsToRescue.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
count++;
|
||||
}
|
||||
ss << ") ";
|
||||
return ss.str();
|
||||
}()
|
||||
);
|
||||
|
||||
LOG_TRACE_L1(
|
||||
m_logger,
|
||||
"Timescale adjustments due to reaction rescue: {}",
|
||||
[&]() -> std::string {
|
||||
std::stringstream ss;
|
||||
if (reactionsToRescue.empty()) {
|
||||
return "No reactions rescued...";
|
||||
}
|
||||
int count = 0;
|
||||
for (const auto& [species, reaction] : reactionsToRescue) {
|
||||
ss << "(Species: " << species.name() << " started with a timescale of " << timescales.at(species);
|
||||
ss << ", rescued by reaction: " << reaction->id();
|
||||
ss << " whose product timescales are --- [";
|
||||
int icount = 0;
|
||||
for (const auto& product : reaction->products()) {
|
||||
ss << product.name() << ": " << timescales.at(product);
|
||||
if (icount < reaction->products().size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
icount++;
|
||||
}
|
||||
ss << "])";
|
||||
|
||||
if (count < reactionsToRescue.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}()
|
||||
);
|
||||
|
||||
RescueSet rescueSet;
|
||||
std::unordered_set<const reaction::LogicalReaction*> newReactions;
|
||||
std::unordered_set<Species> newSpecies;
|
||||
|
||||
for (const auto &reactionPtr: reactionsToRescue | std::views::values) {
|
||||
newReactions.insert(reactionPtr);
|
||||
for (const auto& product : reactionPtr->products()) {
|
||||
newSpecies.insert(product);
|
||||
}
|
||||
}
|
||||
return {std::move(newReactions), std::move(newSpecies)};
|
||||
}
|
||||
|
||||
void AdaptiveEngineView::finalizeActiveSet(
|
||||
const std::vector<const reaction::LogicalReaction *> &finalReactions
|
||||
) {
|
||||
std::unordered_set<Species>finalSpeciesSet;
|
||||
m_activeReactions.clear();
|
||||
for (const auto* reactionPtr: finalReactions) {
|
||||
m_activeReactions.add_reaction(*reactionPtr);
|
||||
for (const auto& reactant : reactionPtr->reactants()) {
|
||||
if (!finalSpeciesSet.contains(reactant)) {
|
||||
LOG_TRACE_L1(m_logger, "Adding reactant '{}' to active species set through reaction {}.", reactant.name(), reactionPtr->id());
|
||||
} else {
|
||||
LOG_TRACE_L1(m_logger, "Reactant '{}' already in active species set through another reaction.", reactant.name());
|
||||
}
|
||||
finalSpeciesSet.insert(reactant);
|
||||
}
|
||||
for (const auto& product : reactionPtr->products()) {
|
||||
if (!finalSpeciesSet.contains(product)) {
|
||||
LOG_TRACE_L1(m_logger, "Adding product '{}' to active species set through reaction {}.", product.name(), reactionPtr->id());
|
||||
} else {
|
||||
LOG_TRACE_L1(m_logger, "Product '{}' already in active species set through another reaction.", product.name());
|
||||
}
|
||||
finalSpeciesSet.insert(product);
|
||||
}
|
||||
}
|
||||
m_activeSpecies.clear();
|
||||
m_activeSpecies = std::vector<Species>(finalSpeciesSet.begin(), finalSpeciesSet.end());
|
||||
std::ranges::sort(
|
||||
m_activeSpecies,
|
||||
[](const Species &a, const Species &b) { return a.mass() < b.mass(); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
387
src/lib/engine/views/engine_defined.cpp
Normal file
387
src/lib/engine/views/engine_defined.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#include "gridfire/engine/views/engine_defined.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "quill/LogMacros.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace gridfire {
|
||||
using fourdst::atomic::Species;
|
||||
|
||||
DefinedEngineView::DefinedEngineView(const std::vector<std::string>& peNames, DynamicEngine& baseEngine) :
|
||||
m_baseEngine(baseEngine) {
|
||||
collect(peNames);
|
||||
}
|
||||
|
||||
const DynamicEngine & DefinedEngineView::getBaseEngine() const {
|
||||
return m_baseEngine;
|
||||
}
|
||||
|
||||
const std::vector<Species> & DefinedEngineView::getNetworkSpecies() const {
|
||||
return m_activeSpecies;
|
||||
}
|
||||
|
||||
std::expected<StepDerivatives<double>, expectations::StaleEngineError> DefinedEngineView::calculateRHSAndEnergy(
|
||||
const std::vector<double> &Y_defined,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateNetworkState();
|
||||
|
||||
const auto Y_full = mapViewToFull(Y_defined);
|
||||
const auto result = m_baseEngine.calculateRHSAndEnergy(Y_full, T9, rho);
|
||||
|
||||
if (!result) {
|
||||
return std::unexpected{result.error()};
|
||||
}
|
||||
|
||||
const auto [dydt, nuclearEnergyGenerationRate] = result.value();
|
||||
StepDerivatives<double> definedResults;
|
||||
definedResults.nuclearEnergyGenerationRate = nuclearEnergyGenerationRate;
|
||||
definedResults.dydt = mapFullToView(dydt);
|
||||
return definedResults;
|
||||
}
|
||||
|
||||
void DefinedEngineView::generateJacobianMatrix(
|
||||
const std::vector<double> &Y_dynamic,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateNetworkState();
|
||||
|
||||
const auto Y_full = mapViewToFull(Y_dynamic);
|
||||
m_baseEngine.generateJacobianMatrix(Y_full, T9, rho);
|
||||
}
|
||||
|
||||
double DefinedEngineView::getJacobianMatrixEntry(
|
||||
const int i_defined,
|
||||
const int j_defined
|
||||
) const {
|
||||
validateNetworkState();
|
||||
|
||||
const size_t i_full = mapViewToFullSpeciesIndex(i_defined);
|
||||
const size_t j_full = mapViewToFullSpeciesIndex(j_defined);
|
||||
|
||||
return m_baseEngine.getJacobianMatrixEntry(i_full, j_full);
|
||||
}
|
||||
|
||||
void DefinedEngineView::generateStoichiometryMatrix() {
|
||||
validateNetworkState();
|
||||
|
||||
m_baseEngine.generateStoichiometryMatrix();
|
||||
}
|
||||
|
||||
int DefinedEngineView::getStoichiometryMatrixEntry(
|
||||
const int speciesIndex_defined,
|
||||
const int reactionIndex_defined
|
||||
) const {
|
||||
validateNetworkState();
|
||||
|
||||
const size_t i_full = mapViewToFullSpeciesIndex(speciesIndex_defined);
|
||||
const size_t j_full = mapViewToFullReactionIndex(reactionIndex_defined);
|
||||
return m_baseEngine.getStoichiometryMatrixEntry(i_full, j_full);
|
||||
}
|
||||
|
||||
double DefinedEngineView::calculateMolarReactionFlow(
|
||||
const reaction::Reaction &reaction,
|
||||
const std::vector<double> &Y_defined,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateNetworkState();
|
||||
|
||||
if (!m_activeReactions.contains(reaction)) {
|
||||
LOG_ERROR(m_logger, "Reaction '{}' is not part of the active reactions in the DefinedEngineView.", reaction.id());
|
||||
m_logger -> flush_log();
|
||||
throw std::runtime_error("Reaction not found in active reactions: " + std::string(reaction.id()));
|
||||
}
|
||||
const auto Y_full = mapViewToFull(Y_defined);
|
||||
return m_baseEngine.calculateMolarReactionFlow(reaction, Y_full, T9, rho);
|
||||
}
|
||||
|
||||
const reaction::LogicalReactionSet & DefinedEngineView::getNetworkReactions() const {
|
||||
validateNetworkState();
|
||||
|
||||
return m_activeReactions;
|
||||
}
|
||||
|
||||
void DefinedEngineView::setNetworkReactions(const reaction::LogicalReactionSet &reactions) {
|
||||
std::vector<std::string> peNames;
|
||||
for (const auto& reaction : reactions) {
|
||||
peNames.push_back(std::string(reaction.id()));
|
||||
}
|
||||
collect(peNames);
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError> DefinedEngineView::getSpeciesTimescales(
|
||||
const std::vector<double> &Y_defined,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateNetworkState();
|
||||
|
||||
const auto Y_full = mapViewToFull(Y_defined);
|
||||
const auto result = m_baseEngine.getSpeciesTimescales(Y_full, T9, rho);
|
||||
if (!result) {
|
||||
return std::unexpected{result.error()};
|
||||
}
|
||||
const auto& fullTimescales = result.value();
|
||||
|
||||
std::unordered_map<Species, double> definedTimescales;
|
||||
for (const auto& active_species : m_activeSpecies) {
|
||||
if (fullTimescales.contains(active_species)) {
|
||||
definedTimescales[active_species] = fullTimescales.at(active_species);
|
||||
}
|
||||
}
|
||||
return definedTimescales;
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError>
|
||||
DefinedEngineView::getSpeciesDestructionTimescales(
|
||||
const std::vector<double> &Y_defined,
|
||||
const double T9,
|
||||
const double rho
|
||||
) const {
|
||||
validateNetworkState();
|
||||
|
||||
const auto Y_full = mapViewToFull(Y_defined);
|
||||
const auto result = m_baseEngine.getSpeciesDestructionTimescales(Y_full, T9, rho);
|
||||
|
||||
if (!result) {
|
||||
return std::unexpected{result.error()};
|
||||
}
|
||||
|
||||
const auto& destructionTimescales = result.value();
|
||||
|
||||
std::unordered_map<Species, double> definedTimescales;
|
||||
for (const auto& active_species : m_activeSpecies) {
|
||||
if (destructionTimescales.contains(active_species)) {
|
||||
definedTimescales[active_species] = destructionTimescales.at(active_species);
|
||||
}
|
||||
}
|
||||
return definedTimescales;
|
||||
}
|
||||
|
||||
fourdst::composition::Composition DefinedEngineView::update(const NetIn &netIn) {
|
||||
return m_baseEngine.update(netIn);
|
||||
}
|
||||
|
||||
bool DefinedEngineView::isStale(const NetIn &netIn) {
|
||||
return m_baseEngine.isStale(netIn);
|
||||
}
|
||||
|
||||
|
||||
void DefinedEngineView::setScreeningModel(const screening::ScreeningType model) {
|
||||
m_baseEngine.setScreeningModel(model);
|
||||
}
|
||||
|
||||
screening::ScreeningType DefinedEngineView::getScreeningModel() const {
|
||||
return m_baseEngine.getScreeningModel();
|
||||
}
|
||||
|
||||
int DefinedEngineView::getSpeciesIndex(const Species &species) const {
|
||||
validateNetworkState();
|
||||
|
||||
const auto it = std::ranges::find(m_activeSpecies, species);
|
||||
if (it != m_activeSpecies.end()) {
|
||||
return static_cast<int>(std::distance(m_activeSpecies.begin(), it));
|
||||
} else {
|
||||
LOG_ERROR(m_logger, "Species '{}' not found in active species list.", species.name());
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("Species not found in active species list: " + std::string(species.name()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> DefinedEngineView::mapNetInToMolarAbundanceVector(const NetIn &netIn) const {
|
||||
std::vector<double> Y(m_activeSpecies.size(), 0.0); // Initialize with zeros
|
||||
for (const auto& [symbol, entry] : netIn.composition) {
|
||||
auto it = std::ranges::find(m_activeSpecies, entry.isotope());
|
||||
if (it != m_activeSpecies.end()) {
|
||||
Y[getSpeciesIndex(entry.isotope())] = netIn.composition.getMolarAbundance(symbol); // Map species to their molar abundance
|
||||
}
|
||||
}
|
||||
return Y; // Return the vector of molar abundances
|
||||
}
|
||||
|
||||
PrimingReport DefinedEngineView::primeEngine(const NetIn &netIn) {
|
||||
return m_baseEngine.primeEngine(netIn);
|
||||
}
|
||||
|
||||
std::vector<size_t> DefinedEngineView::constructSpeciesIndexMap() const {
|
||||
LOG_TRACE_L3(m_logger, "Constructing species index map for DefinedEngineView...");
|
||||
std::unordered_map<Species, size_t> fullSpeciesReverseMap;
|
||||
const auto& fullSpeciesList = m_baseEngine.getNetworkSpecies();
|
||||
|
||||
fullSpeciesReverseMap.reserve(fullSpeciesList.size());
|
||||
|
||||
for (size_t i = 0; i < fullSpeciesList.size(); ++i) {
|
||||
fullSpeciesReverseMap[fullSpeciesList[i]] = i;
|
||||
}
|
||||
|
||||
std::vector<size_t> speciesIndexMap;
|
||||
speciesIndexMap.reserve(m_activeSpecies.size());
|
||||
|
||||
for (const auto& active_species : m_activeSpecies) {
|
||||
auto it = fullSpeciesReverseMap.find(active_species);
|
||||
if (it != fullSpeciesReverseMap.end()) {
|
||||
speciesIndexMap.push_back(it->second);
|
||||
} else {
|
||||
LOG_ERROR(m_logger, "Species '{}' not found in full species map.", active_species.name());
|
||||
m_logger -> flush_log();
|
||||
throw std::runtime_error("Species not found in full species map: " + std::string(active_species.name()));
|
||||
}
|
||||
}
|
||||
LOG_TRACE_L3(m_logger, "Species index map constructed with {} entries.", speciesIndexMap.size());
|
||||
return speciesIndexMap;
|
||||
|
||||
}
|
||||
|
||||
std::vector<size_t> DefinedEngineView::constructReactionIndexMap() const {
|
||||
LOG_TRACE_L3(m_logger, "Constructing reaction index map for DefinedEngineView...");
|
||||
|
||||
// --- Step 1: Create a reverse map using the reaction's unique ID as the key. ---
|
||||
std::unordered_map<std::string_view, size_t> fullReactionReverseMap;
|
||||
const auto& fullReactionSet = m_baseEngine.getNetworkReactions();
|
||||
fullReactionReverseMap.reserve(fullReactionSet.size());
|
||||
|
||||
for (size_t i_full = 0; i_full < fullReactionSet.size(); ++i_full) {
|
||||
fullReactionReverseMap[fullReactionSet[i_full].id()] = i_full;
|
||||
}
|
||||
|
||||
// --- Step 2: Build the final index map using the active reaction set. ---
|
||||
std::vector<size_t> reactionIndexMap;
|
||||
reactionIndexMap.reserve(m_activeReactions.size());
|
||||
|
||||
for (const auto& active_reaction_ptr : m_activeReactions) {
|
||||
auto it = fullReactionReverseMap.find(active_reaction_ptr.id());
|
||||
|
||||
if (it != fullReactionReverseMap.end()) {
|
||||
reactionIndexMap.push_back(it->second);
|
||||
} else {
|
||||
LOG_ERROR(m_logger, "Active reaction '{}' not found in base engine during reaction index map construction.", active_reaction_ptr.id());
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("Mismatch between active reactions and base engine.");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_TRACE_L3(m_logger, "Reaction index map constructed with {} entries.", reactionIndexMap.size());
|
||||
return reactionIndexMap;
|
||||
}
|
||||
|
||||
std::vector<double> DefinedEngineView::mapViewToFull(const std::vector<double>& culled) const {
|
||||
std::vector<double> full(m_baseEngine.getNetworkSpecies().size(), 0.0);
|
||||
for (size_t i_culled = 0; i_culled < culled.size(); ++i_culled) {
|
||||
const size_t i_full = m_speciesIndexMap[i_culled];
|
||||
full[i_full] += culled[i_culled];
|
||||
}
|
||||
return full;
|
||||
}
|
||||
|
||||
std::vector<double> DefinedEngineView::mapFullToView(const std::vector<double>& full) const {
|
||||
std::vector<double> culled(m_activeSpecies.size(), 0.0);
|
||||
for (size_t i_culled = 0; i_culled < m_activeSpecies.size(); ++i_culled) {
|
||||
const size_t i_full = m_speciesIndexMap[i_culled];
|
||||
culled[i_culled] = full[i_full];
|
||||
}
|
||||
return culled;
|
||||
}
|
||||
|
||||
size_t DefinedEngineView::mapViewToFullSpeciesIndex(size_t culledSpeciesIndex) const {
|
||||
if (culledSpeciesIndex < 0 || culledSpeciesIndex >= static_cast<int>(m_speciesIndexMap.size())) {
|
||||
LOG_ERROR(m_logger, "Defined index {} is out of bounds for species index map of size {}.", culledSpeciesIndex, m_speciesIndexMap.size());
|
||||
m_logger->flush_log();
|
||||
throw std::out_of_range("Defined index " + std::to_string(culledSpeciesIndex) + " is out of bounds for species index map of size " + std::to_string(m_speciesIndexMap.size()) + ".");
|
||||
}
|
||||
return m_speciesIndexMap[culledSpeciesIndex];
|
||||
}
|
||||
|
||||
size_t DefinedEngineView::mapViewToFullReactionIndex(size_t culledReactionIndex) const {
|
||||
if (culledReactionIndex < 0 || culledReactionIndex >= static_cast<int>(m_reactionIndexMap.size())) {
|
||||
LOG_ERROR(m_logger, "Defined index {} is out of bounds for reaction index map of size {}.", culledReactionIndex, m_reactionIndexMap.size());
|
||||
m_logger->flush_log();
|
||||
throw std::out_of_range("Defined index " + std::to_string(culledReactionIndex) + " is out of bounds for reaction index map of size " + std::to_string(m_reactionIndexMap.size()) + ".");
|
||||
}
|
||||
return m_reactionIndexMap[culledReactionIndex];
|
||||
}
|
||||
|
||||
void DefinedEngineView::validateNetworkState() const {
|
||||
if (m_isStale) {
|
||||
LOG_ERROR(m_logger, "DefinedEngineView is stale. Please call update() with a valid NetIn object.");
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("DefinedEngineView is stale. Please call update() with a valid NetIn object.");
|
||||
}
|
||||
}
|
||||
|
||||
void DefinedEngineView::collect(const std::vector<std::string> &peNames) {
|
||||
std::unordered_set<Species> seenSpecies;
|
||||
|
||||
const auto& fullNetworkReactionSet = m_baseEngine.getNetworkReactions();
|
||||
for (const auto& peName : peNames) {
|
||||
if (!fullNetworkReactionSet.contains(peName)) {
|
||||
LOG_ERROR(m_logger, "Reaction with name '{}' not found in the base engine's network reactions. Aborting...", peName);
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("Reaction with name '" + std::string(peName) + "' not found in the base engine's network reactions.");
|
||||
}
|
||||
auto reaction = fullNetworkReactionSet[peName];
|
||||
for (const auto& reactant : reaction.reactants()) {
|
||||
if (!seenSpecies.contains(reactant)) {
|
||||
seenSpecies.insert(reactant);
|
||||
m_activeSpecies.push_back(reactant);
|
||||
}
|
||||
}
|
||||
for (const auto& product : reaction.products()) {
|
||||
if (!seenSpecies.contains(product)) {
|
||||
seenSpecies.insert(product);
|
||||
m_activeSpecies.push_back(product);
|
||||
}
|
||||
}
|
||||
m_activeReactions.add_reaction(reaction);
|
||||
}
|
||||
LOG_TRACE_L3(m_logger, "DefinedEngineView built with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
|
||||
LOG_TRACE_L3(m_logger, "Active species: {}", [this]() -> std::string {
|
||||
std::string result;
|
||||
for (const auto& species : m_activeSpecies) {
|
||||
result += std::string(species.name()) + ", ";
|
||||
}
|
||||
if (!result.empty()) {
|
||||
result.pop_back(); // Remove last space
|
||||
result.pop_back(); // Remove last comma
|
||||
}
|
||||
return result;
|
||||
}());
|
||||
LOG_TRACE_L3(m_logger, "Active reactions: {}", [this]() -> std::string {
|
||||
std::string result;
|
||||
for (const auto& reaction : m_activeReactions) {
|
||||
result += std::string(reaction.id()) + ", ";
|
||||
}
|
||||
if (!result.empty()) {
|
||||
result.pop_back(); // Remove last space
|
||||
result.pop_back(); // Remove last comma
|
||||
}
|
||||
return result;
|
||||
}());
|
||||
m_speciesIndexMap = constructSpeciesIndexMap();
|
||||
m_reactionIndexMap = constructReactionIndexMap();
|
||||
m_isStale = false;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
/// FileDefinedEngineView Implementation ///
|
||||
/////////////////////////////////////////////
|
||||
|
||||
FileDefinedEngineView::FileDefinedEngineView(
|
||||
DynamicEngine &baseEngine,
|
||||
const std::string &fileName,
|
||||
const io::NetworkFileParser &parser
|
||||
):
|
||||
DefinedEngineView(parser.parse(fileName), baseEngine),
|
||||
m_fileName(fileName),
|
||||
m_parser(parser) {}
|
||||
}
|
||||
1626
src/lib/engine/views/engine_multiscale.cpp
Normal file
1626
src/lib/engine/views/engine_multiscale.cpp
Normal file
File diff suppressed because it is too large
Load Diff
73
src/lib/engine/views/engine_priming.cpp
Normal file
73
src/lib/engine/views/engine_priming.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "gridfire/engine/views/engine_priming.h"
|
||||
#include "gridfire/solver/solver.h"
|
||||
|
||||
#include "fourdst/composition/species.h"
|
||||
#include "fourdst/logging/logging.h"
|
||||
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <ranges>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace gridfire {
|
||||
using fourdst::atomic::species;
|
||||
|
||||
NetworkPrimingEngineView::NetworkPrimingEngineView(
|
||||
const std::string &primingSymbol,
|
||||
DynamicEngine &baseEngine
|
||||
) :
|
||||
DefinedEngineView(
|
||||
constructPrimingReactionSet(
|
||||
species.at(primingSymbol),
|
||||
baseEngine
|
||||
),
|
||||
baseEngine
|
||||
),
|
||||
m_primingSpecies(species.at(primingSymbol)) {}
|
||||
|
||||
NetworkPrimingEngineView::NetworkPrimingEngineView(
|
||||
const fourdst::atomic::Species &primingSpecies,
|
||||
DynamicEngine &baseEngine
|
||||
) :
|
||||
DefinedEngineView(
|
||||
constructPrimingReactionSet(
|
||||
primingSpecies,
|
||||
baseEngine
|
||||
),
|
||||
baseEngine
|
||||
),
|
||||
m_primingSpecies(primingSpecies) {
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::string> NetworkPrimingEngineView::constructPrimingReactionSet(
|
||||
const fourdst::atomic::Species &primingSpecies,
|
||||
const DynamicEngine &baseEngine
|
||||
) const {
|
||||
std::unordered_set<std::string> primeReactions;
|
||||
for (const auto &reaction : baseEngine.getNetworkReactions()) {
|
||||
if (reaction.contains(primingSpecies)) {
|
||||
primeReactions.insert(std::string(reaction.peName()));
|
||||
}
|
||||
}
|
||||
if (primeReactions.empty()) {
|
||||
LOG_ERROR(m_logger, "No priming reactions found for species '{}'.", primingSpecies.name());
|
||||
m_logger->flush_log();
|
||||
throw std::runtime_error("No priming reactions found for species '" + std::string(primingSpecies.name()) + "'.");
|
||||
}
|
||||
std::vector<std::string> primingReactionSet(primeReactions.begin(), primeReactions.end());
|
||||
// LOG_INFO(m_logger, "Constructed priming reaction set with {} reactions for species '{}'.", primingReactionSet.size(), primingSpecies.name());
|
||||
return primingReactionSet;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user