fix(graph_engine): fixed major bug with jacobian sparsity

previousl the sparsity calculations for the jacobian matrix were completly broken. The method subgraph_sparsity was returning that all derivities were only depenednt on temperature and density. It should have been reporting that they also depended on some of the abundances. This was resolved by switching to a different structural sparsity engine (for_jac_sparsity). This bug had turned the solver into a fixed point iteration solver which failed for the stiff system we have. Now that it is resolved the solver can once again evolved over Gyr timescales.
This commit is contained in:
2025-10-29 14:47:11 -04:00
parent 66b2471c13
commit 23df87f915
9 changed files with 258 additions and 107 deletions

View File

@@ -25,6 +25,7 @@
#include "cppad/cppad.hpp"
#include "cppad/utility/sparse_rc.hpp"
#include "cppad/speed/sparse_jac_fun.hpp"
#include "gridfire/reaction/weak/weak_interpolator.h"
#include "gridfire/reaction/weak/weak_rate_library.h"
@@ -729,6 +730,8 @@ namespace gridfire {
BuildDepthType depth
) override;
void lumpReactions();
private:
struct PrecomputedReaction {
@@ -754,6 +757,20 @@ namespace gridfire {
const double c = Constants::getInstance().get("c").value; ///< Speed of light in cm/s.
const double kB = Constants::getInstance().get("kB").value; ///< Boltzmann constant in erg/K.
};
enum class JacobianMatrixState {
UNINITIALIZED,
STALE,
READY_DENSE,
READY_SPARSE
};
std::unordered_map<JacobianMatrixState, std::string> m_jacobianMatrixStateNameMap = {
{JacobianMatrixState::UNINITIALIZED, "Uninitialized"},
{JacobianMatrixState::STALE, "Stale"},
{JacobianMatrixState::READY_DENSE, "Ready (dense)"},
{JacobianMatrixState::READY_SPARSE, "Ready (sparse)"},
};
private:
class AtomicReverseRate final : public CppAD::atomic_base<double> {
public:
@@ -790,6 +807,18 @@ namespace gridfire {
const CppAD::vector<std::set<size_t>>&rt,
CppAD::vector<std::set<size_t>>& st
) override;
bool for_sparse_jac(
size_t q,
const CppAD::vector<bool> &r,
CppAD::vector<bool> &s,
const CppAD::vector<double> &x
) override;
bool rev_sparse_jac(
size_t q,
const CppAD::vector<bool> &rt,
CppAD::vector<bool> &st,
const CppAD::vector<double> &x
) override;
private:
const reaction::Reaction& m_reaction;
@@ -811,10 +840,11 @@ namespace gridfire {
std::unordered_map<fourdst::atomic::Species, size_t> m_speciesToIndexMap; ///< Map from species to their index in the stoichiometry matrix.
std::unordered_map<size_t, fourdst::atomic::Species> m_indexToSpeciesMap; ///< Map from index to species in the stoichiometry matrix.
boost::numeric::ublas::compressed_matrix<int> m_stoichiometryMatrix; ///< Stoichiometry matrix (species x reactions).
mutable boost::numeric::ublas::compressed_matrix<double> m_jacobianMatrix; ///< Jacobian matrix (species x species).
mutable JacobianMatrixState m_jacobianMatrixState = JacobianMatrixState::UNINITIALIZED;
mutable CppAD::ADFun<double> m_rhsADFun; ///< CppAD function for the right-hand side of the ODE.
mutable CppAD::ADFun<double> m_epsADFun; ///< CppAD function for the energy generation rate.
mutable CppAD::sparse_jac_work m_jac_work; ///< Work object for sparse Jacobian calculations.
@@ -827,7 +857,6 @@ namespace gridfire {
std::unique_ptr<screening::ScreeningModel> m_screeningModel = screening::selectScreeningModel(m_screeningType);
bool m_usePrecomputation = true; ///< Flag to enable or disable using precomputed reactions for efficiency. Mathematically, this should not change the results. Generally end users should not need to change this.
bool m_useReverseReactions = true; ///< Flag to enable or disable reverse reactions. If false, only forward reactions are considered.
BuildDepthType m_depth;
@@ -978,46 +1007,8 @@ namespace gridfire {
std::function<std::optional<size_t>(const fourdst::atomic::Species &)> speciesLookup, const std::function<bool(const
reaction::Reaction &)>& reactionLookup
) const;
// /**
// * @brief Calculates all derivatives (dY/dt) and the energy generation rate (double precision).
// *
// * @param Y Vector of molar abundances for all species in the network.
// * @param T9 Temperature in units of 10^9 K.
// * @param rho Density in g/cm^3.
// * @return StepDerivatives<double> containing dY/dt and energy generation rate.
// *
// * This method calculates the time derivatives of all species and the
// * specific nuclear energy generation rate for the current state using
// * double precision arithmetic.
// */
// [[nodiscard]] StepDerivatives<double> calculateAllDerivatives(
// const std::vector<double>& Y,
// double T9,
// double rho
// ) const;
//
// /**
// * @brief Calculates all derivatives (dY/dt) and the energy generation rate (automatic differentiation).
// *
// * @param Y Vector of molar abundances for all species in the network.
// * @param T9 Temperature in units of 10^9 K.
// * @param rho Density in g/cm^3.
// * @return StepDerivatives<ADDouble> containing dY/dt and energy generation rate.
// *
// * This method calculates the time derivatives of all species and the
// * specific nuclear energy generation rate for the current state using
// * automatic differentiation.
// */
// [[nodiscard]] StepDerivatives<ADDouble> calculateAllDerivatives(
// const std::vector<ADDouble> &Y,
// ADDouble T9,
// ADDouble rho
// ) const;
};
template <IsArithmeticOrAD T>
T GraphEngine::calculateReverseMolarReactionFlow(
T T9,

View File

@@ -110,4 +110,37 @@ namespace gridfire::exceptions {
std::string m_message;
};
class JacobianError : public EngineError {};
class StaleJacobianError final : public JacobianError {
public:
explicit StaleJacobianError(std::string message) : m_message(std::move(message)) {}
[[nodiscard]] const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
class UninitializedJacobianError final: public JacobianError {
public:
explicit UninitializedJacobianError(std::string message): m_message(std::move(message)) {}
[[nodiscard]] const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
class UnknownJacobianError final : public JacobianError {
public:
explicit UnknownJacobianError(std::string message): m_message(std::move(message)) {}
[[nodiscard]] const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <expected>
#include <ranges>
#include <string_view>
@@ -739,7 +740,7 @@ namespace gridfire::reaction {
rate.a5 * T953 +
rate.a6 * logT9;
sum += CppAD::exp(exponent);
// return sum; // TODO: REMOVE THIS ITS FOR TESTING ONLY
// return sum; // TODO: REMOVE OR COMMENT THIS. ITS FOR TESTING ONLY
}
return sum;
}

View File

@@ -257,7 +257,7 @@ namespace gridfire::solver {
* sorted table of species with the highest error ratios; then invokes diagnostic routines to
* inspect Jacobian stiffness and species balance.
*/
void log_step_diagnostics(const CVODEUserData& user_data) const;
void log_step_diagnostics(const CVODEUserData& user_data, bool displayJacobianStiffness) const;
private:
SUNContext m_sun_ctx = nullptr; ///< SUNDIALS context (lifetime of the solver).
void* m_cvode_mem = nullptr; ///< CVODE memory block.

View File

@@ -82,7 +82,7 @@ namespace gridfire::utils {
// --- Helper to draw horizontal border ---
auto draw_border = [&]() {
table_ss << "+";
for (size_t width : col_widths) {
for (const size_t width : col_widths) {
table_ss << std::string(width + 2, '-'); // +2 for padding
table_ss << "+";
}