docs(ridfire)
Added more documentation, also moved all engine code into gridfire::engine namespace to be more in line with other parts of teh code base
This commit is contained in:
@@ -5,17 +5,17 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace gridfire::diagnostics {
|
||||
void report_limiting_species(
|
||||
const DynamicEngine& engine,
|
||||
const std::vector<double>& Y_full,
|
||||
const std::vector<double>& E_full,
|
||||
namespace gridfire::engine::diagnostics {
|
||||
std::optional<nlohmann::json> report_limiting_species(
|
||||
const DynamicEngine &engine,
|
||||
const std::vector<double> &Y_full,
|
||||
const std::vector<double> &E_full,
|
||||
const double relTol,
|
||||
const double absTol,
|
||||
const size_t top_n
|
||||
const size_t top_n,
|
||||
bool json
|
||||
) {
|
||||
struct SpeciesError {
|
||||
std::string name;
|
||||
@@ -66,15 +66,21 @@ namespace gridfire::diagnostics {
|
||||
columns.push_back(std::make_unique<utils::Column<double>>("Abundance", sorted_abundances));
|
||||
columns.push_back(std::make_unique<utils::Column<double>>("Error", sorted_errors));
|
||||
|
||||
std::cout << utils::format_table("Timestep Limiting Species", columns) << std::endl;
|
||||
if (json) {
|
||||
return utils::to_json(columns);
|
||||
}
|
||||
|
||||
utils::print_table("Timestep Limiting Species", columns);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void inspect_species_balance(
|
||||
std::optional<nlohmann::json> inspect_species_balance(
|
||||
const DynamicEngine& engine,
|
||||
const std::string& species_name,
|
||||
const fourdst::composition::Composition &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
const double rho,
|
||||
bool json
|
||||
) {
|
||||
const auto& species_obj = fourdst::atomic::species.at(species_name);
|
||||
|
||||
@@ -103,12 +109,18 @@ namespace gridfire::diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json j;
|
||||
{
|
||||
std::vector<std::unique_ptr<utils::ColumnBase>> columns;
|
||||
columns.push_back(std::make_unique<utils::Column<std::string>>("Reaction ID", creation_ids));
|
||||
columns.push_back(std::make_unique<utils::Column<int>>("Stoichiometry", creation_stoichiometry));
|
||||
columns.push_back(std::make_unique<utils::Column<double>>("Molar Flow", creation_flows));
|
||||
std::cout << utils::format_table("Creation Reactions for " + species_name, columns) << std::endl;
|
||||
if (json) {
|
||||
j["Creation_Reactions_" + species_name] = utils::to_json(columns);
|
||||
}
|
||||
else {
|
||||
utils::print_table("Creation Reactions for " + species_name, columns);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -116,43 +128,44 @@ namespace gridfire::diagnostics {
|
||||
columns.push_back(std::make_unique<utils::Column<std::string>>("Reaction ID", destruction_ids));
|
||||
columns.push_back(std::make_unique<utils::Column<int>>("Stoichiometry", destruction_stoichiometry));
|
||||
columns.push_back(std::make_unique<utils::Column<double>>("Molar Flow", destruction_flows));
|
||||
std::cout << utils::format_table("Destruction Reactions for " + species_name, columns) << std::endl;
|
||||
if (json) {
|
||||
j["Destruction_Reactions_" + species_name] = utils::to_json(columns);
|
||||
} else {
|
||||
utils::print_table("Destruction Reactions for " + species_name, columns);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "--- Balance Summary for " << species_name << " ---" << std::endl;
|
||||
std::cout << " Total Creation Rate: " << std::scientific << total_creation_flow << " [mol/g/s]" << std::endl;
|
||||
std::cout << " Total Destruction Rate: " << std::scientific << total_destruction_flow << " [mol/g/s]" << std::endl;
|
||||
std::cout << " Net dY/dt: " << std::scientific << (total_creation_flow - total_destruction_flow) << std::endl;
|
||||
std::cout << "-----------------------------------" << std::endl;
|
||||
std::vector<std::unique_ptr<utils::ColumnBase>> summary_columns;
|
||||
summary_columns.push_back(std::make_unique<utils::Column<std::string>>("Metric", std::vector<std::string>{
|
||||
"Total Creation Rate [mol/g/s]",
|
||||
"Total Destruction Rate [mol/g/s]",
|
||||
"Net dY/dt [mol/g/s]"
|
||||
}));
|
||||
summary_columns.push_back(std::make_unique<utils::Column<double>>("Value", std::vector<double>{
|
||||
total_creation_flow,
|
||||
total_destruction_flow,
|
||||
total_creation_flow - total_destruction_flow
|
||||
}));
|
||||
|
||||
if (json) {
|
||||
j["Species_Balance_Summary_" + species_name] = utils::to_json(summary_columns);
|
||||
return j;
|
||||
}
|
||||
|
||||
utils::print_table("Species Balance Summary for " + species_name, summary_columns);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void inspect_jacobian_stiffness(
|
||||
std::optional<nlohmann::json> inspect_jacobian_stiffness(
|
||||
const DynamicEngine &engine,
|
||||
const fourdst::composition::Composition &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
) {
|
||||
inspect_jacobian_stiffness(engine, comp, T9, rho, false, std::nullopt);
|
||||
}
|
||||
|
||||
|
||||
void inspect_jacobian_stiffness(
|
||||
const DynamicEngine& engine,
|
||||
const fourdst::composition::Composition &comp,
|
||||
const double T9,
|
||||
const double rho,
|
||||
const bool save,
|
||||
const std::optional<std::string> &filename
|
||||
const bool json
|
||||
) {
|
||||
NetworkJacobian jac = engine.generateJacobianMatrix(comp, T9, rho);
|
||||
|
||||
jac = regularize_jacobian(jac, comp);
|
||||
if (save) {
|
||||
if (!filename.has_value()) {
|
||||
throw std::invalid_argument("Filename must be provided when save is true.");
|
||||
}
|
||||
jac.to_csv(filename.value());
|
||||
}
|
||||
|
||||
const auto& species_list = engine.getNetworkSpecies();
|
||||
|
||||
@@ -172,16 +185,28 @@ namespace gridfire::diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n--- Jacobian Stiffness Report ---" << std::endl;
|
||||
if (max_diag_species.has_value()) {
|
||||
std::cout << " Largest Diagonal Element (d(dYi/dt)/dYi): " << std::scientific << max_diag
|
||||
<< " for species " << max_diag_species->name() << std::endl;
|
||||
std::vector<std::unique_ptr<utils::ColumnBase>> jacobian_columns;
|
||||
jacobian_columns.push_back(std::make_unique<utils::Column<std::string>>("Metric", std::vector<std::string>{
|
||||
"Largest Diagonal Element (d(dYi/dt)/dYi)",
|
||||
"Largest Off-Diagonal Element (d(dYi/dt)/dYj)"
|
||||
}));
|
||||
jacobian_columns.push_back(std::make_unique<utils::Column<double>>("Value", std::vector<double>{
|
||||
max_diag,
|
||||
max_off_diag
|
||||
}));
|
||||
jacobian_columns.push_back(std::make_unique<utils::Column<std::string>>("Species", std::vector<std::string>{
|
||||
max_diag_species.has_value() ? std::string(max_diag_species->name()) : "N/A",
|
||||
max_off_diag_species.has_value() ?
|
||||
("d(" + std::string(max_off_diag_species->first.name()) + ")/d(" + std::string(max_off_diag_species->second.name()) + ")")
|
||||
: "N/A"
|
||||
}));
|
||||
|
||||
if (json) {
|
||||
nlohmann::json j;
|
||||
j["Jacobian_Stiffness"] = utils::to_json(jacobian_columns);
|
||||
return j;
|
||||
}
|
||||
if (max_off_diag_species.has_value()) {
|
||||
std::cout << " Largest Off-Diagonal Element (d(dYi/dt)/dYj): " << std::scientific << max_off_diag
|
||||
<< " for d(" << max_off_diag_species->first.name()
|
||||
<< ")/d(" << max_off_diag_species->second.name() << ")" << std::endl;
|
||||
}
|
||||
std::cout << "---------------------------------" << std::endl;
|
||||
utils::print_table("Jacobian Stiffness Diagnostics", jacobian_columns);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "gridfire/engine/engine_graph.h"
|
||||
#include "gridfire/reaction/reaction.h"
|
||||
#include "gridfire/network.h"
|
||||
#include "gridfire/types/types.h"
|
||||
#include "gridfire/screening/screening_types.h"
|
||||
#include "gridfire/engine/procedures/priming.h"
|
||||
#include "gridfire/partition/partition_ground.h"
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "cppad/utility/sparse_rcv.hpp"
|
||||
|
||||
|
||||
namespace gridfire {
|
||||
namespace gridfire::engine {
|
||||
GraphEngine::GraphEngine(
|
||||
const fourdst::composition::Composition &composition,
|
||||
const BuildDepthType buildDepth
|
||||
@@ -66,7 +66,7 @@ namespace gridfire {
|
||||
syncInternalMaps();
|
||||
}
|
||||
|
||||
std::expected<StepDerivatives<double>, expectations::StaleEngineError> GraphEngine::calculateRHSAndEnergy(
|
||||
std::expected<StepDerivatives<double>, EngineStatus> GraphEngine::calculateRHSAndEnergy(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -74,17 +74,17 @@ namespace gridfire {
|
||||
return calculateRHSAndEnergy(comp, T9, rho, m_reactions);
|
||||
}
|
||||
|
||||
std::expected<StepDerivatives<double>, expectations::StaleEngineError> GraphEngine::calculateRHSAndEnergy(
|
||||
std::expected<StepDerivatives<double>, EngineStatus> GraphEngine::calculateRHSAndEnergy(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho,
|
||||
const reaction::ReactionSet &activeReactions
|
||||
) const {
|
||||
LOG_TRACE_L2(m_logger, "Calculating RHS and Energy in GraphEngine at T9 = {}, rho = {}.", T9, rho);
|
||||
LOG_TRACE_L3(m_logger, "Calculating RHS and Energy in GraphEngine at T9 = {}, rho = {}.", T9, rho);
|
||||
const double Ye = comp.getElectronAbundance();
|
||||
const double mue = 0.0; // TODO: Remove
|
||||
if (m_usePrecomputation) {
|
||||
LOG_TRACE_L2(m_logger, "Using precomputation for reaction rates in GraphEngine calculateRHSAndEnergy.");
|
||||
LOG_TRACE_L3(m_logger, "Using precomputation for reaction rates in GraphEngine calculateRHSAndEnergy.");
|
||||
std::vector<double> bare_rates;
|
||||
std::vector<double> bare_reverse_rates;
|
||||
bare_rates.reserve(activeReactions.size());
|
||||
@@ -98,7 +98,7 @@ namespace gridfire {
|
||||
}
|
||||
}
|
||||
|
||||
LOG_TRACE_L2(m_logger, "Precomputed {} forward and {} reverse reaction rates for active reactions.", bare_rates.size(), bare_reverse_rates.size());
|
||||
LOG_TRACE_L3(m_logger, "Precomputed {} forward and {} reverse reaction rates for active reactions.", bare_rates.size(), bare_reverse_rates.size());
|
||||
|
||||
// --- The public facing interface can always use the precomputed version since taping is done internally ---
|
||||
return calculateAllDerivativesUsingPrecomputation(comp, bare_rates, bare_reverse_rates, T9, rho, activeReactions);
|
||||
@@ -543,6 +543,15 @@ namespace gridfire {
|
||||
fullNetIn.temperature = netIn.temperature;
|
||||
fullNetIn.density = netIn.density;
|
||||
|
||||
// Short circuit path if already primed
|
||||
// if (m_has_been_primed) {
|
||||
// PrimingReport report;
|
||||
// report.primedComposition = composition;
|
||||
// report.success = true;
|
||||
// report.status = PrimingReportStatus::ALREADY_PRIMED;
|
||||
// return report;
|
||||
// }
|
||||
|
||||
std::optional<std::vector<reaction::ReactionType>> reactionTypesToIgnore = std::nullopt;
|
||||
if (!m_useReverseReactions) {
|
||||
reactionTypesToIgnore = {reaction::ReactionType::WEAK};
|
||||
@@ -550,6 +559,7 @@ namespace gridfire {
|
||||
|
||||
auto primingReport = primeNetwork(fullNetIn, *this, reactionTypesToIgnore);
|
||||
|
||||
m_has_been_primed = true;
|
||||
return primingReport;
|
||||
}
|
||||
|
||||
@@ -603,7 +613,7 @@ namespace gridfire {
|
||||
const double rho,
|
||||
const reaction::ReactionSet &activeReactions
|
||||
) const {
|
||||
LOG_TRACE_L2(m_logger, "Computing screening factors for {} active reactions.", activeReactions.size());
|
||||
LOG_TRACE_L3(m_logger, "Computing screening factors for {} active reactions.", activeReactions.size());
|
||||
// --- Calculate screening factors ---
|
||||
const std::vector<double> screeningFactors = m_screeningModel->calculateScreeningFactors(
|
||||
activeReactions,
|
||||
@@ -636,6 +646,11 @@ namespace gridfire {
|
||||
forwardAbundanceProduct = 0.0;
|
||||
break; // No need to continue if one of the reactants has zero abundance
|
||||
}
|
||||
double factor = std::pow(comp.getMolarAbundance(reactant), power);
|
||||
if (!std::isfinite(factor)) {
|
||||
LOG_CRITICAL(m_logger, "Non-finite factor encountered in forward abundance product for reaction '{}'. Check input abundances for validity.", reaction->id());
|
||||
throw exceptions::BadRHSEngineError("Non-finite factor encountered in forward abundance product.");
|
||||
}
|
||||
forwardAbundanceProduct *= std::pow(comp.getMolarAbundance(reactant), power);
|
||||
}
|
||||
|
||||
@@ -652,7 +667,10 @@ namespace gridfire {
|
||||
precomputedReaction.symmetry_factor *
|
||||
forwardAbundanceProduct *
|
||||
std::pow(rho, numReactants > 1 ? static_cast<double>(numReactants) - 1 : 0.0);
|
||||
|
||||
if (!std::isfinite(forwardMolarReactionFlow)) {
|
||||
LOG_CRITICAL(m_logger, "Non-finite forward molar reaction flow computed for reaction '{}'. Check input abundances and rates for validity.", reaction->id());
|
||||
throw exceptions::BadRHSEngineError("Non-finite forward molar reaction flow computed.");
|
||||
}
|
||||
|
||||
// --- Reverse reaction flow ---
|
||||
// Only do this is the reaction has a non-zero reverse symmetry factor (i.e. is reversible)
|
||||
@@ -678,7 +696,7 @@ namespace gridfire {
|
||||
reactionCounter++;
|
||||
}
|
||||
|
||||
LOG_TRACE_L2(m_logger, "Computed {} molar reaction flows for active reactions. Assembling these into RHS", molarReactionFlows.size());
|
||||
LOG_TRACE_L3(m_logger, "Computed {} molar reaction flows for active reactions. Assembling these into RHS", molarReactionFlows.size());
|
||||
|
||||
// --- Assemble molar abundance derivatives ---
|
||||
StepDerivatives<double> result;
|
||||
@@ -860,10 +878,6 @@ namespace gridfire {
|
||||
for (size_t j = 0; j < numSpecies; ++j) {
|
||||
double value = dotY[i * (numSpecies + 2) + j];
|
||||
if (std::abs(value) > MIN_JACOBIAN_THRESHOLD || i == j) { // Always keep diagonal elements to avoid pathological stiffness
|
||||
if (i == j && value == 0) {
|
||||
LOG_WARNING(m_logger, "While generating the Jacobian matrix, a zero diagonal element was encountered at index ({}, {}) (species: {}, abundance: {}). This may lead to numerical instability. Setting to -1 to avoid singularity", i, j, m_networkSpecies[i].name(), adInput[i]);
|
||||
// value = -1.0;
|
||||
}
|
||||
triplets.emplace_back(i, j, value);
|
||||
}
|
||||
}
|
||||
@@ -966,7 +980,7 @@ namespace gridfire {
|
||||
|
||||
CppAD::sparse_rcv<std::vector<size_t>, std::vector<double>> jac_subset(CppAD_sparsity_pattern);
|
||||
|
||||
// PERF: one of *the* most pressing things that needs to be done is remove the nead for this call every
|
||||
// PERF: one of *the* most pressing things that needs to be done is remove the need for this call every
|
||||
// time the jacobian is needed since coloring is expensive and we are throwing away the caching
|
||||
// power of CppAD by clearing the work vector each time. We do this since we make a new subset every
|
||||
// time. However, a better solution would be to make the subset stateful so it only changes if the requested
|
||||
@@ -1100,7 +1114,7 @@ namespace gridfire {
|
||||
LOG_TRACE_L1(m_logger, "Successfully exported network graph to {}", filename);
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesTimescales(
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -1108,7 +1122,7 @@ namespace gridfire {
|
||||
return getSpeciesTimescales(comp, T9, rho, m_reactions);
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesTimescales(
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho,
|
||||
@@ -1144,7 +1158,7 @@ namespace gridfire {
|
||||
return speciesTimescales;
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesDestructionTimescales(
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesDestructionTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -1152,7 +1166,7 @@ namespace gridfire {
|
||||
return getSpeciesDestructionTimescales(comp, T9, rho, m_reactions);
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, expectations::StaleEngineError> GraphEngine::getSpeciesDestructionTimescales(
|
||||
std::expected<std::unordered_map<fourdst::atomic::Species, double>, EngineStatus> GraphEngine::getSpeciesDestructionTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho,
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#include "quill/LogMacros.h"
|
||||
namespace {
|
||||
// Simple heuristic to check if a reaclib reaction is a strong or weak reaction
|
||||
/* A weak reaction is defined here as one where:
|
||||
- The number of reactants is equal to the number of products
|
||||
- There is only one reactant and one product
|
||||
- The mass number (A) of the reactant is equal to the mass number (A) of the product
|
||||
*/
|
||||
bool reaclib_reaction_is_weak(const gridfire::reaction::Reaction& reaction) {
|
||||
const std::vector<fourdst::atomic::Species>& reactants = reaction.reactants();
|
||||
const std::vector<fourdst::atomic::Species>& products = reaction.products();
|
||||
@@ -41,10 +46,10 @@ namespace {
|
||||
|
||||
gridfire::reaction::ReactionSet register_weak_reactions(
|
||||
const gridfire::rates::weak::WeakRateInterpolator &weakInterpolator,
|
||||
const gridfire::NetworkConstructionFlags reactionTypes
|
||||
const gridfire::engine::NetworkConstructionFlags reactionTypes
|
||||
) {
|
||||
gridfire::reaction::ReactionSet weak_reaction_pool;
|
||||
if (!has_flag(reactionTypes, gridfire::NetworkConstructionFlags::WRL_WEAK)) {
|
||||
if (!has_flag(reactionTypes, gridfire::engine::NetworkConstructionFlags::WRL_WEAK)) {
|
||||
return weak_reaction_pool;
|
||||
}
|
||||
|
||||
@@ -58,7 +63,7 @@ namespace {
|
||||
parent_species.z() - 1
|
||||
);
|
||||
if (downProduct.has_value()) { // Only add the reaction if the Species map contains the product
|
||||
if (has_flag(reactionTypes, gridfire::NetworkConstructionFlags::BETA_PLUS)) {
|
||||
if (has_flag(reactionTypes, gridfire::engine::NetworkConstructionFlags::BETA_PLUS)) {
|
||||
weak_reaction_pool.add_reaction(
|
||||
std::make_unique<gridfire::rates::weak::WeakReaction>(
|
||||
parent_species,
|
||||
@@ -67,7 +72,7 @@ namespace {
|
||||
)
|
||||
);
|
||||
}
|
||||
if (has_flag(reactionTypes, gridfire::NetworkConstructionFlags::ELECTRON_CAPTURE)) {
|
||||
if (has_flag(reactionTypes, gridfire::engine::NetworkConstructionFlags::ELECTRON_CAPTURE)) {
|
||||
weak_reaction_pool.add_reaction(
|
||||
std::make_unique<gridfire::rates::weak::WeakReaction>(
|
||||
parent_species,
|
||||
@@ -78,7 +83,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
if (upProduct.has_value()) { // Only add the reaction if the Species map contains the product
|
||||
if (has_flag(reactionTypes, gridfire::NetworkConstructionFlags::BETA_MINUS)) {
|
||||
if (has_flag(reactionTypes, gridfire::engine::NetworkConstructionFlags::BETA_MINUS)) {
|
||||
weak_reaction_pool.add_reaction(
|
||||
std::make_unique<gridfire::rates::weak::WeakReaction>(
|
||||
parent_species,
|
||||
@@ -87,7 +92,7 @@ namespace {
|
||||
)
|
||||
);
|
||||
}
|
||||
if (has_flag(reactionTypes, gridfire::NetworkConstructionFlags::POSITRON_CAPTURE)) {
|
||||
if (has_flag(reactionTypes, gridfire::engine::NetworkConstructionFlags::POSITRON_CAPTURE)) {
|
||||
weak_reaction_pool.add_reaction(
|
||||
std::make_unique<gridfire::rates::weak::WeakReaction>(
|
||||
parent_species,
|
||||
@@ -103,14 +108,14 @@ namespace {
|
||||
}
|
||||
|
||||
gridfire::reaction::ReactionSet register_strong_reactions(
|
||||
const gridfire::NetworkConstructionFlags reaction_types
|
||||
const gridfire::engine::NetworkConstructionFlags reaction_types
|
||||
) {
|
||||
gridfire::reaction::ReactionSet strong_reaction_pool;
|
||||
if (has_flag(reaction_types, gridfire::NetworkConstructionFlags::STRONG)) {
|
||||
if (has_flag(reaction_types, gridfire::engine::NetworkConstructionFlags::STRONG)) {
|
||||
const auto& allReaclibReactions = gridfire::reaclib::get_all_reaclib_reactions();
|
||||
for (const auto& reaction : allReaclibReactions) {
|
||||
const bool isWeakReaction = reaclib_reaction_is_weak(*reaction);
|
||||
const bool okayToUseReaclibWeakReaction = has_flag(reaction_types, gridfire::NetworkConstructionFlags::REACLIB_WEAK);
|
||||
const bool okayToUseReaclibWeakReaction = has_flag(reaction_types, gridfire::engine::NetworkConstructionFlags::REACLIB_WEAK);
|
||||
|
||||
const bool reaclibWeakOkay = !isWeakReaction || okayToUseReaclibWeakReaction;
|
||||
if (!reaction->is_reverse() && reaclibWeakOkay) {
|
||||
@@ -121,17 +126,17 @@ namespace {
|
||||
return strong_reaction_pool;
|
||||
}
|
||||
|
||||
bool validate_unique_weak_set(gridfire::NetworkConstructionFlags flag) {
|
||||
bool validate_unique_weak_set(gridfire::engine::NetworkConstructionFlags flag) {
|
||||
// This method ensures that weak reactions will only be fetched from either reaclib or the weak reaction library (WRL)
|
||||
// but not both
|
||||
std::array<gridfire::NetworkConstructionFlags, 4> WRL_Flags = {
|
||||
gridfire::NetworkConstructionFlags::BETA_PLUS,
|
||||
gridfire::NetworkConstructionFlags::ELECTRON_CAPTURE,
|
||||
gridfire::NetworkConstructionFlags::POSITRON_CAPTURE,
|
||||
gridfire::NetworkConstructionFlags::BETA_MINUS
|
||||
std::array<gridfire::engine::NetworkConstructionFlags, 4> WRL_Flags = {
|
||||
gridfire::engine::NetworkConstructionFlags::BETA_PLUS,
|
||||
gridfire::engine::NetworkConstructionFlags::ELECTRON_CAPTURE,
|
||||
gridfire::engine::NetworkConstructionFlags::POSITRON_CAPTURE,
|
||||
gridfire::engine::NetworkConstructionFlags::BETA_MINUS
|
||||
};
|
||||
|
||||
if (!has_flag(flag, gridfire::NetworkConstructionFlags::REACLIB_WEAK)) {
|
||||
if (!has_flag(flag, gridfire::engine::NetworkConstructionFlags::REACLIB_WEAK)) {
|
||||
return true;
|
||||
}
|
||||
for (const auto& WRLReactionType : WRL_Flags) {
|
||||
@@ -143,7 +148,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
namespace gridfire {
|
||||
namespace gridfire::engine {
|
||||
using reaction::ReactionSet;
|
||||
using reaction::Reaction;
|
||||
using fourdst::atomic::Species;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "gridfire/solver/solver.h"
|
||||
|
||||
#include "gridfire/engine/engine_abstract.h"
|
||||
#include "gridfire/network.h"
|
||||
#include "gridfire/types/types.h"
|
||||
#include "gridfire/exceptions/error_solver.h"
|
||||
|
||||
#include "fourdst/logging/logging.h"
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "quill/LogMacros.h"
|
||||
|
||||
|
||||
namespace gridfire {
|
||||
namespace gridfire::engine {
|
||||
using fourdst::composition::Composition;
|
||||
using fourdst::atomic::Species;
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace gridfire {
|
||||
) {
|
||||
const auto logger = LogManager::getInstance().getLogger("log");
|
||||
solver::CVODESolverStrategy integrator(engine);
|
||||
|
||||
// Do not need high precision for priming
|
||||
integrator.set_absTol(1e-3);
|
||||
integrator.set_relTol(1e-3);
|
||||
|
||||
integrator.set_stdout_logging_enabled(false);
|
||||
NetIn solverInput(netIn);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "quill/LogMacros.h"
|
||||
|
||||
namespace gridfire {
|
||||
namespace gridfire::engine {
|
||||
NetworkJacobian::NetworkJacobian(
|
||||
const Eigen::SparseMatrix<double>& jacobianMatrix,
|
||||
const std::function<fourdst::atomic::Species(size_t)> &indexToSpeciesFunc
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
#include "gridfire/network.h"
|
||||
#include "gridfire/types/types.h"
|
||||
#include "gridfire/exceptions/error_engine.h"
|
||||
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
namespace gridfire {
|
||||
namespace gridfire::engine {
|
||||
using fourdst::atomic::Species;
|
||||
AdaptiveEngineView::AdaptiveEngineView(
|
||||
DynamicEngine &baseEngine
|
||||
@@ -77,7 +77,7 @@ namespace gridfire {
|
||||
return m_activeSpecies;
|
||||
}
|
||||
|
||||
std::expected<StepDerivatives<double>, expectations::StaleEngineError> AdaptiveEngineView::calculateRHSAndEnergy(
|
||||
std::expected<StepDerivatives<double>, EngineStatus> AdaptiveEngineView::calculateRHSAndEnergy(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -205,7 +205,7 @@ namespace gridfire {
|
||||
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(
|
||||
std::expected<std::unordered_map<Species, double>, EngineStatus> AdaptiveEngineView::getSpeciesTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -231,8 +231,7 @@ namespace gridfire {
|
||||
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError>
|
||||
AdaptiveEngineView::getSpeciesDestructionTimescales(
|
||||
std::expected<std::unordered_map<Species, double>, EngineStatus> AdaptiveEngineView::getSpeciesDestructionTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -357,7 +356,7 @@ namespace gridfire {
|
||||
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());
|
||||
LOG_TRACE_L2(m_logger, "Network Connectivity Analysis: Species {:5} is part of the initial fuel", species.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,7 +377,7 @@ namespace gridfire {
|
||||
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());
|
||||
LOG_TRACE_L2(m_logger, "Network Connectivity Analysis: Species {:5} is reachable via reaction {:20}", product.name(), reaction->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,19 +396,19 @@ namespace gridfire {
|
||||
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);
|
||||
LOG_DEBUG(m_logger, "Relative culling threshold: {:7.3E} ({:7.3E})", relative_culling_threshold, absoluteCullingThreshold);
|
||||
std::vector<const reaction::Reaction*> 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);
|
||||
LOG_TRACE_L2(m_logger, "Maintaining reaction '{:20}' with relative (abs) flow rate: {:7.3E} ({:7.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()) {
|
||||
if (comp.getMolarAbundance(reactant) < 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());
|
||||
LOG_TRACE_L1(m_logger, "Maintaining reaction {:20} with low flow ({:7.3E} [mol/s/g]) due to reachable reactant '{:6}'.", reactionPtr->id(), flowRate, reactant.name());
|
||||
zero_flow_due_to_reachable_reactants = true;
|
||||
break;
|
||||
}
|
||||
@@ -422,10 +421,10 @@ namespace gridfire {
|
||||
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_TRACE_L1(m_logger, "Culling reaction '{:20}' 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());
|
||||
LOG_DEBUG(m_logger, "Selected {:5} (total: {:5}, culled: {:5}) reactions based on flow rates.", culledReactions.size(), allFlows.size(), allFlows.size() - culledReactions.size());
|
||||
return culledReactions;
|
||||
}
|
||||
|
||||
@@ -438,8 +437,9 @@ namespace gridfire {
|
||||
) const {
|
||||
const auto result = m_baseEngine.getSpeciesTimescales(comp, 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");
|
||||
LOG_CRITICAL(m_logger, "Failed to get species timescales due to base engine failure");
|
||||
m_logger->flush_log();
|
||||
throw exceptions::EngineError("Failed to get species timescales due base engine failure");
|
||||
}
|
||||
std::unordered_map<Species, double> timescales = result.value();
|
||||
std::set<Species> onlyProducedSpecies;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
#include "fourdst/composition/exceptions/exceptions_composition.h"
|
||||
|
||||
namespace gridfire {
|
||||
namespace gridfire::engine {
|
||||
using fourdst::atomic::Species;
|
||||
|
||||
DefinedEngineView::DefinedEngineView(
|
||||
@@ -40,7 +40,7 @@ namespace gridfire {
|
||||
return m_activeSpeciesVectorCache.value();
|
||||
}
|
||||
|
||||
std::expected<StepDerivatives<double>, expectations::StaleEngineError> DefinedEngineView::calculateRHSAndEnergy(
|
||||
std::expected<StepDerivatives<double>, EngineStatus> DefinedEngineView::calculateRHSAndEnergy(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -170,7 +170,7 @@ namespace gridfire {
|
||||
m_activeSpeciesVectorCache = std::nullopt; // Invalidate species vector cache
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError> DefinedEngineView::getSpeciesTimescales(
|
||||
std::expected<std::unordered_map<Species, double>, EngineStatus> DefinedEngineView::getSpeciesTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -193,7 +193,7 @@ namespace gridfire {
|
||||
return definedTimescales;
|
||||
}
|
||||
|
||||
std::expected<std::unordered_map<Species, double>, expectations::StaleEngineError> DefinedEngineView::getSpeciesDestructionTimescales(
|
||||
std::expected<std::unordered_map<Species, double>, EngineStatus> DefinedEngineView::getSpeciesDestructionTimescales(
|
||||
const fourdst::composition::CompositionAbstract &comp,
|
||||
const double T9,
|
||||
const double rho
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace gridfire {
|
||||
namespace gridfire::engine {
|
||||
using fourdst::atomic::species;
|
||||
|
||||
NetworkPrimingEngineView::NetworkPrimingEngineView(
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace gridfire::io::gen {
|
||||
|
||||
}
|
||||
|
||||
std::string exportEngineToPy(const gridfire::DynamicEngine& engine) {
|
||||
std::string exportEngineToPy(const engine::DynamicEngine& engine) {
|
||||
auto reactions = engine.getNetworkReactions();
|
||||
std::vector<std::string> functions;
|
||||
functions.emplace_back(R"(import numpy as np
|
||||
@@ -150,7 +150,7 @@ from typing import Dict, List, Tuple, Callable)");
|
||||
return join<std::string>(functions, "\n\n");
|
||||
}
|
||||
|
||||
void exportEngineToPy(const DynamicEngine &engine, const std::string &fileName) {
|
||||
void exportEngineToPy(const engine::DynamicEngine &engine, const std::string &fileName) {
|
||||
const std::string funcCode = exportEngineToPy(engine);
|
||||
std::ofstream outFile(fileName);
|
||||
outFile << funcCode;
|
||||
|
||||
@@ -47,22 +47,22 @@ namespace gridfire::policy {
|
||||
m_partition_function = build_partition_function();
|
||||
}
|
||||
|
||||
DynamicEngine& MainSequencePolicy::construct() {
|
||||
engine::DynamicEngine& MainSequencePolicy::construct() {
|
||||
m_network_stack.clear();
|
||||
|
||||
m_network_stack.emplace_back(
|
||||
std::make_unique<GraphEngine>(m_initializing_composition, *m_partition_function, NetworkBuildDepth::ThirdOrder, NetworkConstructionFlags::DEFAULT)
|
||||
std::make_unique<engine::GraphEngine>(m_initializing_composition, *m_partition_function, engine::NetworkBuildDepth::ThirdOrder, engine::NetworkConstructionFlags::DEFAULT)
|
||||
);
|
||||
|
||||
auto& graphRepr = dynamic_cast<GraphEngine&>(*m_network_stack.back().get());
|
||||
auto& graphRepr = dynamic_cast<engine::GraphEngine&>(*m_network_stack.back().get());
|
||||
graphRepr.setUseReverseReactions(false);
|
||||
|
||||
|
||||
m_network_stack.emplace_back(
|
||||
std::make_unique<MultiscalePartitioningEngineView>(*m_network_stack.back().get())
|
||||
std::make_unique<engine::MultiscalePartitioningEngineView>(*m_network_stack.back().get())
|
||||
);
|
||||
m_network_stack.emplace_back(
|
||||
std::make_unique<AdaptiveEngineView>(*m_network_stack.back().get())
|
||||
std::make_unique<engine::AdaptiveEngineView>(*m_network_stack.back().get())
|
||||
);
|
||||
|
||||
m_status = NetworkPolicyStatus::INITIALIZED_UNVERIFIED;
|
||||
@@ -96,18 +96,18 @@ namespace gridfire::policy {
|
||||
return m_status;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<DynamicEngine>> &MainSequencePolicy::get_engine_stack() const {
|
||||
const std::vector<std::unique_ptr<engine::DynamicEngine>> &MainSequencePolicy::get_engine_stack() const {
|
||||
if (m_status != NetworkPolicyStatus::INITIALIZED_VERIFIED) {
|
||||
throw exceptions::PolicyError("Cannot get engine stack from MainSequencePolicy: Policy is not initialized and verified. Call construct() first.");
|
||||
}
|
||||
return m_network_stack;
|
||||
}
|
||||
|
||||
std::vector<EngineTypes> MainSequencePolicy::get_engine_types_stack() const {
|
||||
std::vector<engine::EngineTypes> MainSequencePolicy::get_engine_types_stack() const {
|
||||
return {
|
||||
EngineTypes::GRAPH_ENGINE,
|
||||
EngineTypes::MULTISCALE_PARTITIONING_ENGINE_VIEW,
|
||||
EngineTypes::ADAPTIVE_ENGINE_VIEW
|
||||
engine::EngineTypes::GRAPH_ENGINE,
|
||||
engine::EngineTypes::MULTISCALE_PARTITIONING_ENGINE_VIEW,
|
||||
engine::EngineTypes::ADAPTIVE_ENGINE_VIEW
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "gridfire/reaction/reaclib.h"
|
||||
#include "gridfire/reaction/reactions_data.h"
|
||||
#include "gridfire/network.h"
|
||||
#include "gridfire/types/types.h"
|
||||
#include "gridfire/exceptions/error_reaction.h"
|
||||
|
||||
#include <stdexcept>
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <format>
|
||||
#include <print>
|
||||
#include <expected>
|
||||
|
||||
namespace {
|
||||
@@ -38,7 +37,7 @@ namespace {
|
||||
BadFormat // Generic error (e.g., from an out_of_range exception)
|
||||
};
|
||||
|
||||
std::string error_to_string(ReactionParseError err) {
|
||||
std::string error_to_string(const ReactionParseError err) {
|
||||
switch (err) {
|
||||
case ReactionParseError::MissingOpenParenthesis:
|
||||
return "Missing '('";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "gridfire/solver/strategies/CVODE_solver_strategy.h"
|
||||
|
||||
#include "gridfire/network.h"
|
||||
#include "gridfire/types/types.h"
|
||||
#include "gridfire/utils/table_format.h"
|
||||
#include "gridfire/engine/diagnostics/dynamic_engine_diagnostics.h"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "fourdst/atomic/species.h"
|
||||
#include "fourdst/composition/exceptions/exceptions_composition.h"
|
||||
#include "gridfire/engine/engine_graph.h"
|
||||
#include "gridfire/engine/types/engine_types.h"
|
||||
#include "gridfire/solver/strategies/triggers/engine_partitioning_trigger.h"
|
||||
#include "gridfire/trigger/procedures/trigger_pprint.h"
|
||||
#include "gridfire/exceptions/error_solver.h"
|
||||
@@ -26,6 +27,7 @@
|
||||
|
||||
|
||||
namespace gridfire::solver {
|
||||
using namespace gridfire::engine;
|
||||
|
||||
CVODESolverStrategy::TimestepContext::TimestepContext(
|
||||
const double t,
|
||||
@@ -99,14 +101,26 @@ namespace gridfire::solver {
|
||||
) {
|
||||
LOG_TRACE_L1(m_logger, "Starting solver evaluation with T9: {} and rho: {}", netIn.temperature/1e9, netIn.density);
|
||||
LOG_TRACE_L1(m_logger, "Building engine update trigger....");
|
||||
auto trigger = trigger::solver::CVODE::makeEnginePartitioningTrigger(1e12, 1e10, 1e-6, 10);
|
||||
auto trigger = trigger::solver::CVODE::makeEnginePartitioningTrigger(1e12, 1e10, 0.5, 2);
|
||||
LOG_TRACE_L1(m_logger, "Engine update trigger built!");
|
||||
|
||||
|
||||
const double T9 = netIn.temperature / 1e9; // Convert temperature from Kelvin to T9 (T9 = T / 1e9)
|
||||
|
||||
const auto absTol = m_config.get<double>("gridfire:solver:CVODESolverStrategy:absTol", 1.0e-8);
|
||||
const auto relTol = m_config.get<double>("gridfire:solver:CVODESolverStrategy:relTol", 1.0e-5);
|
||||
// The tolerance selection algorithm is that
|
||||
// 1. Default tolerances are taken from the config file
|
||||
// 2. If the user has set tolerances in code, those override the config
|
||||
// 3. If the user has not set tolerances in code and the config does not have them, use hardcoded defaults
|
||||
|
||||
auto absTol = m_config.get<double>("gridfire:solver:CVODESolverStrategy:absTol", 1.0e-8);
|
||||
auto relTol = m_config.get<double>("gridfire:solver:CVODESolverStrategy:relTol", 1.0e-5);
|
||||
|
||||
if (m_absTol) {
|
||||
absTol = *m_absTol;
|
||||
}
|
||||
if (m_relTol) {
|
||||
relTol = *m_relTol;
|
||||
}
|
||||
|
||||
LOG_TRACE_L1(m_logger, "Starting engine update chain...");
|
||||
fourdst::composition::Composition equilibratedComposition = m_engine.update(netIn);
|
||||
@@ -143,6 +157,7 @@ namespace gridfire::solver {
|
||||
size_t total_steps = 0;
|
||||
|
||||
LOG_TRACE_L1(m_logger, "Starting CVODE iteration");
|
||||
fourdst::composition::Composition postStep = equilibratedComposition;
|
||||
while (current_time < netIn.tMax) {
|
||||
user_data.T9 = T9;
|
||||
user_data.rho = netIn.density;
|
||||
@@ -156,13 +171,10 @@ namespace gridfire::solver {
|
||||
LOG_TRACE_L2(m_logger, "CVODE step complete. Current time: {}, step status: {}", current_time, utils::cvode_ret_code_map.at(flag));
|
||||
|
||||
if (user_data.captured_exception){
|
||||
LOG_CRITICAL(m_logger, "An exception was captured during RHS evaluation ({}). Rethrowing...", user_data.captured_exception->what());
|
||||
std::rethrow_exception(std::make_exception_ptr(*user_data.captured_exception));
|
||||
}
|
||||
|
||||
|
||||
// TODO: Come up with some way to save these to a file rather than spamming stdout. JSON maybe? OPAT?
|
||||
// log_step_diagnostics(user_data, true, false, false);
|
||||
// exit(0);
|
||||
utils::check_cvode_flag(flag, "CVode");
|
||||
|
||||
long int n_steps;
|
||||
@@ -178,21 +190,41 @@ namespace gridfire::solver {
|
||||
size_t iter_diff = (total_nonlinear_iterations + nliters) - prev_nonlinear_iterations;
|
||||
size_t convFail_diff = (total_convergence_failures + nlcfails) - prev_convergence_failures;
|
||||
if (m_stdout_logging_enabled) {
|
||||
std::cout << std::scientific << std::setprecision(3)
|
||||
<< "Step: " << std::setw(6) << total_steps + n_steps
|
||||
<< " | Updates: " << std::setw(3) << total_update_stages_triggered
|
||||
<< " | Epoch Steps: " << std::setw(4) << n_steps
|
||||
<< " | t: " << current_time << " [s]"
|
||||
<< " | dt: " << last_step_size << " [s]"
|
||||
// << " | Molar Abundance (min a): " << y_data[0] << " [mol/g]"
|
||||
// << " | Accumulated Energy: " << current_energy << " [erg/g]"
|
||||
<< " | Iterations: " << std::setw(6) << total_nonlinear_iterations + nliters
|
||||
<< " (+" << std::setw(2) << iter_diff << ")"
|
||||
<< " | Total Convergence Failures: " << std::setw(2) << total_convergence_failures + nlcfails
|
||||
<< " (+" << std::setw(2) << convFail_diff << ")"
|
||||
<< "\n";
|
||||
std::println(
|
||||
"Step: {:6} | Updates: {:3} | Epoch Steps: {:4} | t: {:.3e} [s] | dt: {:15.6E} [s] | Iterations: {:6} (+{:2}) | Total Convergence Failures: {:2} (+{:2})",
|
||||
total_steps + n_steps,
|
||||
total_update_stages_triggered,
|
||||
n_steps,
|
||||
current_time,
|
||||
last_step_size,
|
||||
total_nonlinear_iterations + nliters,
|
||||
iter_diff,
|
||||
total_convergence_failures + nlcfails,
|
||||
convFail_diff
|
||||
);
|
||||
}
|
||||
LOG_INFO(m_logger, "Completed {} steps to time {} [s] (dt = {} [s]). Current specific energy: {} [erg/g]", n_steps, current_time, last_step_size, current_energy);
|
||||
for (size_t i = 0; i < numSpecies; ++i) {
|
||||
const auto& species = m_engine.getNetworkSpecies()[i];
|
||||
if (y_data[i] > 0.0) {
|
||||
postStep.setMolarAbundance(species, y_data[i]);
|
||||
}
|
||||
}
|
||||
fourdst::composition::Composition collectedComposition = m_engine.collectComposition(postStep, netIn.temperature/1e9, netIn.density);
|
||||
for (size_t i = 0; i < numSpecies; ++i) {
|
||||
y_data[i] = collectedComposition.getMolarAbundance(m_engine.getNetworkSpecies()[i]);
|
||||
}
|
||||
LOG_INFO(m_logger, "Completed {:5} steps to time {:10.4E} [s] (dt = {:15.6E} [s]). Current specific energy: {:15.6E} [erg/g]", total_steps + n_steps, current_time, last_step_size, current_energy);
|
||||
LOG_DEBUG(m_logger, "Current composition (molar abundance): {}", [&]() -> std::string {
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < numSpecies; ++i) {
|
||||
const auto& species = m_engine.getNetworkSpecies()[i];
|
||||
ss << species.name() << ": (y_data = " << y_data[i] << ", collected = " << collectedComposition.getMolarAbundance(species) << ")";
|
||||
if (i < numSpecies - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}());
|
||||
|
||||
static const std::map<fourdst::atomic::Species,
|
||||
std::unordered_map<std::string,double>> kEmptyMap{};
|
||||
@@ -224,8 +256,9 @@ namespace gridfire::solver {
|
||||
}
|
||||
trigger->step(ctx);
|
||||
|
||||
// log_step_diagnostics(user_data, true, true, false);
|
||||
// exit(0);
|
||||
if (m_detailed_step_logging) {
|
||||
log_step_diagnostics(user_data, true, true, true, "step_" + std::to_string(total_steps + n_steps) + ".json");
|
||||
}
|
||||
|
||||
if (trigger->check(ctx)) {
|
||||
if (m_stdout_logging_enabled && displayTrigger) {
|
||||
@@ -389,6 +422,7 @@ namespace gridfire::solver {
|
||||
numSpecies = m_engine.getNetworkSpecies().size();
|
||||
N = numSpecies + 1;
|
||||
|
||||
LOG_INFO(m_logger, "Starting CVODE reinitialization after engine update...");
|
||||
cleanup_cvode_resources(true);
|
||||
|
||||
m_cvode_mem = CVodeCreate(CV_BDF, m_sun_ctx);
|
||||
@@ -397,6 +431,8 @@ namespace gridfire::solver {
|
||||
initialize_cvode_integration_resources(N, numSpecies, current_time, currentComposition, absTol, relTol, accumulated_energy);
|
||||
|
||||
utils::check_cvode_flag(CVodeReInit(m_cvode_mem, current_time, m_Y), "CVodeReInit");
|
||||
// throw exceptions::DebugException("Debug");
|
||||
LOG_INFO(m_logger, "Done reinitializing CVODE after engine update. The next log messages will be from the first step after reinitialization...");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -405,7 +441,7 @@ namespace gridfire::solver {
|
||||
std::cout << std::flush;
|
||||
}
|
||||
|
||||
LOG_TRACE_L2(m_logger, "CVODE iteration complete");
|
||||
LOG_INFO(m_logger, "CVODE iteration complete");
|
||||
|
||||
sunrealtype* y_data = N_VGetArrayPointer(m_Y);
|
||||
accumulated_energy += y_data[numSpecies];
|
||||
@@ -417,16 +453,41 @@ namespace gridfire::solver {
|
||||
}
|
||||
}
|
||||
|
||||
LOG_TRACE_L2(m_logger, "Constructing final composition= with {} species", numSpecies);
|
||||
LOG_INFO(m_logger, "Constructing final composition= with {} species", numSpecies);
|
||||
|
||||
fourdst::composition::Composition topLevelComposition(m_engine.getNetworkSpecies(), y_vec);
|
||||
LOG_INFO(m_logger, "Final composition constructed from solver state successfully! ({})", [&topLevelComposition]() -> std::string {
|
||||
std::ostringstream ss;
|
||||
size_t i = 0;
|
||||
for (const auto& [species, abundance] : topLevelComposition) {
|
||||
ss << species.name() << ": " << abundance;
|
||||
if (i < topLevelComposition.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return ss.str();
|
||||
}());
|
||||
|
||||
LOG_INFO(m_logger, "Collecting final composition...");
|
||||
fourdst::composition::Composition outputComposition = m_engine.collectComposition(topLevelComposition, netIn.temperature/1e9, netIn.density);
|
||||
|
||||
assert(outputComposition.getRegisteredSymbols().size() == equilibratedComposition.getRegisteredSymbols().size());
|
||||
|
||||
LOG_TRACE_L2(m_logger, "Final composition constructed successfully!");
|
||||
LOG_INFO(m_logger, "Final composition constructed successfully! ({})", [&outputComposition]() -> std::string {
|
||||
std::ostringstream ss;
|
||||
size_t i = 0;
|
||||
for (const auto& [species, abundance] : outputComposition) {
|
||||
ss << species.name() << ": " << abundance;
|
||||
if (i < outputComposition.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return ss.str();
|
||||
}());
|
||||
|
||||
LOG_TRACE_L2(m_logger, "Constructing output data...");
|
||||
LOG_INFO(m_logger, "Constructing output data...");
|
||||
NetOut netOut;
|
||||
netOut.composition = outputComposition;
|
||||
netOut.energy = accumulated_energy;
|
||||
@@ -461,6 +522,30 @@ namespace gridfire::solver {
|
||||
m_stdout_logging_enabled = logging_enabled;
|
||||
}
|
||||
|
||||
void CVODESolverStrategy::set_absTol(double absTol) {
|
||||
m_absTol = absTol;
|
||||
}
|
||||
|
||||
void CVODESolverStrategy::set_relTol(double relTol) {
|
||||
m_relTol = relTol;
|
||||
}
|
||||
|
||||
double CVODESolverStrategy::get_absTol() const {
|
||||
if (m_absTol.has_value()) {
|
||||
return m_absTol.value();
|
||||
} else {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
double CVODESolverStrategy::get_relTol() const {
|
||||
if (m_relTol.has_value()) {
|
||||
return m_relTol.value();
|
||||
} else {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<std::string, std::string>> CVODESolverStrategy::describe_callback_context() const {
|
||||
return {};
|
||||
}
|
||||
@@ -476,13 +561,13 @@ namespace gridfire::solver {
|
||||
|
||||
try {
|
||||
LOG_TRACE_L2(instance->m_logger, "CVODE RHS wrapper called at time {}", t);
|
||||
const CVODERHSOutputData out = instance->calculate_rhs(t, y, ydot, data);
|
||||
data->reaction_contribution_map = out.reaction_contribution_map;
|
||||
const auto [reaction_contribution_map] = instance->calculate_rhs(t, y, ydot, data);
|
||||
data->reaction_contribution_map = reaction_contribution_map;
|
||||
LOG_TRACE_L2(instance->m_logger, "CVODE RHS wrapper completed successfully at time {}", t);
|
||||
return 0;
|
||||
} catch (const exceptions::StaleEngineTrigger& e) {
|
||||
LOG_ERROR(instance->m_logger, "StaleEngineTrigger caught in CVODE RHS wrapper at time {}: {}", t, e.what());
|
||||
data->captured_exception = std::make_unique<exceptions::StaleEngineTrigger>(e);
|
||||
} catch (const exceptions::EngineError& e) {
|
||||
LOG_ERROR(instance->m_logger, "EngineError caught in CVODE RHS wrapper at time {}: {}", t, e.what());
|
||||
data->captured_exception = std::make_unique<exceptions::EngineError>(e);
|
||||
return 1; // 1 Indicates a recoverable error, CVODE will retry the step
|
||||
} catch (...) {
|
||||
LOG_CRITICAL(instance->m_logger, "Unrecoverable and Unknown exception caught in CVODE RHS wrapper at time {}", t);
|
||||
@@ -634,8 +719,8 @@ namespace gridfire::solver {
|
||||
LOG_TRACE_L2(m_logger, "Calculating RHS at time {} with {} species in composition", t, composition.size());
|
||||
const auto result = m_engine.calculateRHSAndEnergy(composition, data->T9, data->rho);
|
||||
if (!result) {
|
||||
LOG_WARNING(m_logger, "StaleEngineTrigger thrown during RHS calculation at time {}", t);
|
||||
throw exceptions::StaleEngineTrigger({data->T9, data->rho, y_vec, t, m_num_steps, y_data[numSpecies]});
|
||||
LOG_CRITICAL(m_logger, "Failed to calculate RHS at time {}: {}", t, EngineStatus_to_string(result.error()));
|
||||
throw exceptions::BadRHSEngineError(std::format("Failed to calculate RHS at time {}: {}", t, EngineStatus_to_string(result.error())));
|
||||
}
|
||||
|
||||
sunrealtype* ydot_data = N_VGetArrayPointer(ydot);
|
||||
@@ -746,12 +831,21 @@ namespace gridfire::solver {
|
||||
LOG_TRACE_L2(m_logger, "Done Cleaning up cvode resources");
|
||||
}
|
||||
|
||||
void CVODESolverStrategy::set_detailed_step_logging(const bool enabled) {
|
||||
m_detailed_step_logging = enabled;
|
||||
}
|
||||
|
||||
void CVODESolverStrategy::log_step_diagnostics(
|
||||
const CVODEUserData &user_data,
|
||||
bool displayJacobianStiffness,
|
||||
bool saveIntermediateJacobians,
|
||||
bool displaySpeciesBalance
|
||||
bool displaySpeciesBalance,
|
||||
bool to_file,
|
||||
std::optional<std::string> filename
|
||||
) const {
|
||||
if (to_file && !filename.has_value()) {
|
||||
LOG_ERROR(m_logger, "Filename must be provided when logging diagnostics to file.");
|
||||
throw exceptions::UtilityError("Filename must be provided when logging diagnostics to file.");
|
||||
}
|
||||
|
||||
// --- 1. Get CVODE Step Statistics ---
|
||||
sunrealtype hlast, hcur, tcur;
|
||||
@@ -762,6 +856,7 @@ namespace gridfire::solver {
|
||||
utils::check_cvode_flag(CVodeGetLastOrder(m_cvode_mem, &qlast), "CVodeGetLastOrder");
|
||||
utils::check_cvode_flag(CVodeGetCurrentTime(m_cvode_mem, &tcur), "CVodeGetCurrentTime");
|
||||
|
||||
nlohmann::json j;
|
||||
{
|
||||
std::vector<std::string> labels = {"Current Time (tcur)", "Last Step (hlast)", "Current Step (hcur)", "Last Order (qlast)"};
|
||||
std::vector<double> values = {static_cast<double>(tcur), static_cast<double>(hlast), static_cast<double>(hcur), static_cast<double>(qlast)};
|
||||
@@ -770,7 +865,11 @@ namespace gridfire::solver {
|
||||
columns.push_back(std::make_unique<utils::Column<std::string>>("Statistic", labels));
|
||||
columns.push_back(std::make_unique<utils::Column<double>>("Value", values));
|
||||
|
||||
std::cout << utils::format_table("CVODE Step Stats", columns) << std::endl;
|
||||
if (to_file) {
|
||||
j["CVODE_Step_Stats"] = utils::to_json(columns);
|
||||
} else {
|
||||
utils::print_table("CVODE Step Stats", columns);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2. Get CVODE Cumulative Solver Statistics ---
|
||||
@@ -803,7 +902,11 @@ namespace gridfire::solver {
|
||||
columns.push_back(std::make_unique<utils::Column<std::string>>("Counter", labels));
|
||||
columns.push_back(std::make_unique<utils::Column<long int>>("Count", values));
|
||||
|
||||
std::cout << utils::format_table("CVODE Cumulative Stats", columns) << std::endl;
|
||||
if (to_file) {
|
||||
j["CVODE_Cumulative_Stats"] = utils::to_json(columns);
|
||||
} else {
|
||||
utils::print_table("CVODE Cumulative Stats", columns);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 3. Get Estimated Local Errors (Your Original Logic) ---
|
||||
@@ -822,7 +925,10 @@ namespace gridfire::solver {
|
||||
std::vector<double> Y_full(y_data, y_data + num_components - 1);
|
||||
std::vector<double> E_full(y_err_data, y_err_data + num_components - 1);
|
||||
|
||||
diagnostics::report_limiting_species(*user_data.engine, Y_full, E_full, relTol, absTol);
|
||||
auto result = diagnostics::report_limiting_species(*user_data.engine, Y_full, E_full, relTol, absTol, 10, to_file);
|
||||
if (to_file && result.has_value()) {
|
||||
j["Limiting_Species"] = result.value();
|
||||
}
|
||||
|
||||
std::ranges::replace_if(
|
||||
Y_full,
|
||||
@@ -880,7 +986,11 @@ namespace gridfire::solver {
|
||||
table.push_back(std::make_unique<utils::Column<double>>(destructionTimescaleColumn));
|
||||
table.push_back(std::make_unique<utils::Column<std::string>>(speciesStatusColumn));
|
||||
|
||||
utils::print_table("Species Timescales", table);
|
||||
if (to_file) {
|
||||
j["Species_Timescales"] = utils::to_json(table);
|
||||
} else {
|
||||
utils::print_table("Species Timescales", table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -917,29 +1027,41 @@ namespace gridfire::solver {
|
||||
columns.push_back(std::make_unique<utils::Column<fourdst::atomic::Species>>("Species", sorted_species));
|
||||
columns.push_back(std::make_unique<utils::Column<double>>("Error Ratio", sorted_err_ratios));
|
||||
|
||||
std::cout << utils::format_table("Species Error Ratios (Log)", columns) << std::endl;
|
||||
if (to_file) {
|
||||
j["Species_Error_Ratios_Linear"] = utils::to_json(columns);
|
||||
} else {
|
||||
utils::print_table("Species Error Ratios (Linear)", columns);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. Call Your Jacobian and Balance Diagnostics ---
|
||||
if (displayJacobianStiffness) {
|
||||
std::cout << "--- Starting Jacobian Diagnostics ---" << std::endl;
|
||||
std::optional<std::string> filename;
|
||||
if (saveIntermediateJacobians) {
|
||||
filename = std::format("jacobian_s{}.csv", nsteps);
|
||||
auto jStiff = diagnostics::inspect_jacobian_stiffness(*user_data.engine, composition, user_data.T9, user_data.rho, to_file);
|
||||
if (to_file && jStiff.has_value()) {
|
||||
j["Jacobian_Stiffness_Diagnostics"] = jStiff.value();
|
||||
}
|
||||
diagnostics::inspect_jacobian_stiffness(*user_data.engine, composition, user_data.T9, user_data.rho, saveIntermediateJacobians, filename);
|
||||
std::cout << "--- Finished Jacobian Diagnostics ---" << std::endl;
|
||||
}
|
||||
if (displaySpeciesBalance) {
|
||||
std::cout << "--- Starting Species Balance Diagnostics ---" << std::endl;
|
||||
// Limit this to the top N species to avoid spamming
|
||||
const size_t num_species_to_inspect = std::min(sorted_species.size(), static_cast<size_t>(5));
|
||||
std::cout << " > Inspecting balance for top " << num_species_to_inspect << " species with highest error ratio:" << std::endl;
|
||||
for (size_t i = 0; i < num_species_to_inspect; ++i) {
|
||||
const auto& species = sorted_species[i];
|
||||
diagnostics::inspect_species_balance(*user_data.engine, std::string(species.name()), composition, user_data.T9, user_data.rho);
|
||||
auto sbr = diagnostics::inspect_species_balance(*user_data.engine, std::string(species.name()), composition, user_data.T9, user_data.rho, to_file);
|
||||
if (to_file && sbr.has_value()) {
|
||||
j[std::string("Species_Balance_Diagnostics_") + species.name().data()] = sbr.value();
|
||||
}
|
||||
}
|
||||
std::cout << "--- Finished Species Balance Diagnostics ---" << std::endl;
|
||||
}
|
||||
|
||||
if (to_file) {
|
||||
std::ofstream ofs(filename.value());
|
||||
if (!ofs.is_open()) {
|
||||
LOG_ERROR(m_logger, "Failed to open file {} for writing diagnostics.", filename.value());
|
||||
throw exceptions::UtilityError(std::format("Failed to open file {} for writing diagnostics.", filename.value()));
|
||||
}
|
||||
ofs << j.dump(4);
|
||||
ofs.close();
|
||||
LOG_TRACE_L2(m_logger, "Diagnostics written to file {}", filename.value());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -203,6 +203,7 @@ namespace gridfire::trigger::solver::CVODE {
|
||||
|
||||
void TimestepCollapseTrigger::update(const gridfire::solver::CVODESolverStrategy::TimestepContext &ctx) {
|
||||
m_updates++;
|
||||
m_timestep_window.clear();
|
||||
}
|
||||
|
||||
void TimestepCollapseTrigger::step(
|
||||
@@ -279,7 +280,7 @@ namespace gridfire::trigger::solver::CVODE {
|
||||
void ConvergenceFailureTrigger::update(
|
||||
const gridfire::solver::CVODESolverStrategy::TimestepContext &ctx
|
||||
) {
|
||||
// --- ConvergenceFailureTrigger::update does nothing and is intentionally left blank --- //
|
||||
m_window.clear();
|
||||
}
|
||||
|
||||
void ConvergenceFailureTrigger::step(
|
||||
@@ -371,27 +372,38 @@ namespace gridfire::trigger::solver::CVODE {
|
||||
std::unique_ptr<Trigger<gridfire::solver::CVODESolverStrategy::TimestepContext>> makeEnginePartitioningTrigger(
|
||||
const double simulationTimeInterval,
|
||||
const double offDiagonalThreshold,
|
||||
const double relativeTimestepCollapseThreshold,
|
||||
const size_t timestepGrowthWindowSize
|
||||
const double timestepCollapseRatio,
|
||||
const size_t maxConvergenceFailures
|
||||
) {
|
||||
using ctx_t = gridfire::solver::CVODESolverStrategy::TimestepContext;
|
||||
|
||||
// Create the individual conditions that can trigger a repartitioning
|
||||
// The current trigger logic is as follows
|
||||
// 1. Trigger every 1000th time that the simulation time exceeds the simulationTimeInterval
|
||||
// 2. OR if any off-diagonal Jacobian entry exceeds the offDiagonalThreshold
|
||||
// 3. OR every 10th time that the timestep growth exceeds the timestepGrowthThreshold (relative or absolute)
|
||||
// 4. OR if the number of convergence failures begins to grow
|
||||
// 1. INSTABILITY TRIGGERS (High Priority)
|
||||
auto convergenceFailureTrigger = std::make_unique<ConvergenceFailureTrigger>(
|
||||
maxConvergenceFailures,
|
||||
1.0f,
|
||||
10
|
||||
);
|
||||
|
||||
// TODO: This logic likely needs to be revisited; however, for now it is easy enough to change and test and it works reasonably well
|
||||
auto simulationTimeTrigger = std::make_unique<EveryNthTrigger<ctx_t>>(std::make_unique<SimulationTimeTrigger>(simulationTimeInterval), 1000);
|
||||
auto timestepCollapseTrigger = std::make_unique<TimestepCollapseTrigger>(
|
||||
timestepCollapseRatio,
|
||||
true, // relative
|
||||
5
|
||||
);
|
||||
|
||||
auto instabilityGroup = std::make_unique<OrTrigger<ctx_t>>(
|
||||
std::move(convergenceFailureTrigger),
|
||||
std::move(timestepCollapseTrigger)
|
||||
);
|
||||
|
||||
// 2. MAINTENANCE TRIGGERS
|
||||
auto offDiagTrigger = std::make_unique<OffDiagonalTrigger>(offDiagonalThreshold);
|
||||
auto timestepGrowthTrigger = std::make_unique<EveryNthTrigger<ctx_t>>(std::make_unique<TimestepCollapseTrigger>(relativeTimestepCollapseThreshold, true, timestepGrowthWindowSize), 10);
|
||||
auto convergenceFailureTrigger = std::make_unique<ConvergenceFailureTrigger>(5, 1.0f, 10);
|
||||
|
||||
|
||||
auto convergenceOrTimestepTrigger = std::make_unique<OrTrigger<ctx_t>>(std::move(timestepGrowthTrigger), std::move(convergenceFailureTrigger));
|
||||
|
||||
return convergenceOrTimestepTrigger;
|
||||
// Combine: (Instability) OR (Structure Change)
|
||||
return std::make_unique<OrTrigger<ctx_t>>(
|
||||
std::move(instabilityGroup),
|
||||
std::move(offDiagTrigger)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <string>
|
||||
|
||||
std::string gridfire::utils::formatNuclearTimescaleLogString(
|
||||
const DynamicEngine& engine,
|
||||
const engine::DynamicEngine& engine,
|
||||
const fourdst::composition::Composition& composition,
|
||||
const double T9,
|
||||
const double rho
|
||||
@@ -17,7 +17,7 @@ std::string gridfire::utils::formatNuclearTimescaleLogString(
|
||||
auto const& result = engine.getSpeciesTimescales(composition, T9, rho);
|
||||
if (!result) {
|
||||
std::ostringstream ss;
|
||||
ss << "Failed to get species timescales: " << result.error();
|
||||
ss << "Failed to get species timescales: " << engine::EngineStatus_to_string(result.error());
|
||||
return ss.str();
|
||||
}
|
||||
const std::unordered_map<fourdst::atomic::Species, double>& timescales = result.value();
|
||||
|
||||
Reference in New Issue
Block a user