feat(Jacobian): Jacobian generation is now stateless.
Previously Jacobians were stored by engines and accessed through engine
accessors (e.g getJacobianMatrixEntry); however, this resulted in
desynced jacobian states. We have changed to a pattern of Engine creates
a jacobian and returns it to the caller. The caller can then do what
they will with it. Because of this the getJacobianMatrixEntry method has
been removed.
BREAKING CHANGE:
- There is no longer any getJacobianMatrixEntry method on
DynamicEngine classes
- the generateJacobian method signature has changed to return a
NetworkJacobian object. Internally this uses an Eigen Sparse Matrix to
store its data.
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "gridfire/engine/types/reporting.h"
|
#include "gridfire/engine/types/reporting.h"
|
||||||
#include "gridfire/engine/types/building.h"
|
#include "gridfire/engine/types/building.h"
|
||||||
|
#include "gridfire/engine/types/jacobian.h"
|
||||||
|
|
||||||
#include "gridfire/expectations/expected_engine.h"
|
#include "gridfire/expectations/expected_engine.h"
|
||||||
|
|
||||||
@@ -155,40 +156,26 @@ namespace gridfire {
|
|||||||
* This method must compute and store the Jacobian matrix (∂(dY/dt)_i/∂Y_j)
|
* This method must compute and store the Jacobian matrix (∂(dY/dt)_i/∂Y_j)
|
||||||
* for the current state. The matrix can then be accessed via getJacobianMatrixEntry().
|
* for the current state. The matrix can then be accessed via getJacobianMatrixEntry().
|
||||||
*/
|
*/
|
||||||
virtual void generateJacobianMatrix(
|
[[nodiscard]] virtual NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho
|
double rho
|
||||||
) const = 0;
|
) const = 0;
|
||||||
|
|
||||||
virtual void generateJacobianMatrix(
|
[[nodiscard]] virtual NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
const std::vector<fourdst::atomic::Species>& activeSpecies
|
const std::vector<fourdst::atomic::Species>& activeSpecies
|
||||||
) const = 0;
|
) const = 0;
|
||||||
|
|
||||||
virtual void generateJacobianMatrix(
|
[[nodiscard]] virtual NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
const SparsityPattern& sparsityPattern
|
const SparsityPattern& sparsityPattern
|
||||||
) const = 0;
|
) const = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get an entry from the previously generated Jacobian matrix.
|
|
||||||
*
|
|
||||||
* @param rowSpecies The species corresponding to the row index (i)
|
|
||||||
* @param colSpecies The species corresponding to the column index (j)
|
|
||||||
* @return Value of the Jacobian matrix at (i, j).
|
|
||||||
*
|
|
||||||
* The Jacobian must have been generated by generateJacobianMatrix() before calling this.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] virtual double getJacobianMatrixEntry(
|
|
||||||
const fourdst::atomic::Species& rowSpecies,
|
|
||||||
const fourdst::atomic::Species& colSpecies
|
|
||||||
) const = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generate the stoichiometry matrix for the network.
|
* @brief Generate the stoichiometry matrix for the network.
|
||||||
@@ -418,5 +405,7 @@ namespace gridfire {
|
|||||||
double rho
|
double rho
|
||||||
) const = 0;
|
) const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual SpeciesStatus getSpeciesStatus(const fourdst::atomic::Species& species) const = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -73,6 +73,7 @@ namespace gridfire {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class GraphEngine
|
* @class GraphEngine
|
||||||
* @brief A reaction network engine that uses a graph-based representation.
|
* @brief A reaction network engine that uses a graph-based representation.
|
||||||
@@ -236,7 +237,7 @@ namespace gridfire {
|
|||||||
*
|
*
|
||||||
* @see getJacobianMatrixEntry()
|
* @see getJacobianMatrixEntry()
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho
|
double rho
|
||||||
@@ -254,7 +255,7 @@ namespace gridfire {
|
|||||||
* @see getJacobianMatrixEntry()
|
* @see getJacobianMatrixEntry()
|
||||||
* @see generateJacobianMatrix()
|
* @see generateJacobianMatrix()
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
@@ -276,7 +277,7 @@ namespace gridfire {
|
|||||||
*
|
*
|
||||||
* @see getJacobianMatrixEntry()
|
* @see getJacobianMatrixEntry()
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
@@ -334,22 +335,6 @@ namespace gridfire {
|
|||||||
*/
|
*/
|
||||||
void setNetworkReactions(const reaction::ReactionSet& reactions) override;
|
void setNetworkReactions(const reaction::ReactionSet& reactions) override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gets an entry from the previously generated Jacobian matrix.
|
|
||||||
*
|
|
||||||
* @param rowSpecies Species corresponding to the row index (i).
|
|
||||||
* @param colSpecies Species corresponding to the column index (j).
|
|
||||||
* @return Value of the Jacobian matrix at (i, j).
|
|
||||||
*
|
|
||||||
* The Jacobian must have been generated by `generateJacobianMatrix()` before calling this.
|
|
||||||
*
|
|
||||||
* @see generateJacobianMatrix()
|
|
||||||
*/
|
|
||||||
[[nodiscard]] double getJacobianMatrixEntry(
|
|
||||||
const fourdst::atomic::Species& rowSpecies,
|
|
||||||
const fourdst::atomic::Species& colSpecies
|
|
||||||
) const override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets the net stoichiometry for a given reaction.
|
* @brief Gets the net stoichiometry for a given reaction.
|
||||||
*
|
*
|
||||||
@@ -754,6 +739,8 @@ namespace gridfire {
|
|||||||
*/
|
*/
|
||||||
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
||||||
|
|
||||||
|
SpeciesStatus getSpeciesStatus(const fourdst::atomic::Species &species) const override;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PrecomputedReaction {
|
struct PrecomputedReaction {
|
||||||
@@ -864,9 +851,6 @@ namespace gridfire {
|
|||||||
|
|
||||||
boost::numeric::ublas::compressed_matrix<int> m_stoichiometryMatrix; ///< Stoichiometry matrix (species x reactions).
|
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_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::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.
|
mutable CppAD::sparse_jac_work m_jac_work; ///< Work object for sparse Jacobian calculations.
|
||||||
@@ -921,14 +905,6 @@ namespace gridfire {
|
|||||||
*/
|
*/
|
||||||
void populateSpeciesToIndexMap();
|
void populateSpeciesToIndexMap();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Reserves space for the Jacobian matrix.
|
|
||||||
*
|
|
||||||
* This method reserves space for the Jacobian matrix, which is used
|
|
||||||
* to store the partial derivatives of the right-hand side of the ODE
|
|
||||||
* with respect to the species abundances.
|
|
||||||
*/
|
|
||||||
void reserveJacobianMatrix() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Records the AD tape for the right-hand side of the ODE.
|
* @brief Records the AD tape for the right-hand side of the ODE.
|
||||||
@@ -1254,7 +1230,7 @@ namespace gridfire {
|
|||||||
std::unordered_map<fourdst::atomic::Species, int> reactant_counts;
|
std::unordered_map<fourdst::atomic::Species, int> reactant_counts;
|
||||||
reactant_counts.reserve(reaction.reactants().size());
|
reactant_counts.reserve(reaction.reactants().size());
|
||||||
for (const auto& reactant : reaction.reactants()) {
|
for (const auto& reactant : reaction.reactants()) {
|
||||||
reactant_counts[reactant]++;
|
reactant_counts[reactant] = reaction.countReactantOccurrences(reactant);
|
||||||
}
|
}
|
||||||
const int totalReactants = static_cast<int>(reaction.reactants().size());
|
const int totalReactants = static_cast<int>(reaction.reactants().size());
|
||||||
|
|
||||||
@@ -1285,7 +1261,6 @@ namespace gridfire {
|
|||||||
// the tape more expensive to record, but it will also mean that we only need to record it once for
|
// the tape more expensive to record, but it will also mean that we only need to record it once for
|
||||||
// the entire network.
|
// the entire network.
|
||||||
const T densityTerm = CppAD::pow(rho, totalReactants > 1 ? static_cast<T>(totalReactants - 1) : zero); // Density raised to the power of (N-1) for N reactants
|
const T densityTerm = CppAD::pow(rho, totalReactants > 1 ? static_cast<T>(totalReactants - 1) : zero); // Density raised to the power of (N-1) for N reactants
|
||||||
|
|
||||||
return molar_concentration_product * k_reaction * densityTerm;
|
return molar_concentration_product * k_reaction * densityTerm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fourdst/atomic/atomicSpecies.h"
|
||||||
|
#include <Eigen/SparseCore>
|
||||||
|
#include <Eigen/SparseQR>
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <functional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace gridfire {
|
||||||
|
class NetworkJacobian {
|
||||||
|
public:
|
||||||
|
explicit NetworkJacobian(
|
||||||
|
const Eigen::SparseMatrix<double>& jacobianMatrix,
|
||||||
|
const std::function<fourdst::atomic::Species(size_t)> &indexToSpeciesFunc
|
||||||
|
);
|
||||||
|
|
||||||
|
double operator()(
|
||||||
|
const fourdst::atomic::Species& row,
|
||||||
|
const fourdst::atomic::Species& col
|
||||||
|
) const;
|
||||||
|
|
||||||
|
double operator()(
|
||||||
|
size_t i,
|
||||||
|
size_t j
|
||||||
|
) const;
|
||||||
|
|
||||||
|
std::tuple<size_t, size_t> shape() const;
|
||||||
|
size_t rank() const;
|
||||||
|
size_t nnz() const;
|
||||||
|
|
||||||
|
bool singular() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Eigen::SparseMatrix<double> m_jacobianMatrix;
|
||||||
|
std::unordered_map<fourdst::atomic::Species, size_t> m_speciesToIndexMap;
|
||||||
|
size_t m_rank;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -134,45 +134,26 @@ namespace gridfire {
|
|||||||
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
|
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
|
||||||
* @see AdaptiveEngineView::update()
|
* @see AdaptiveEngineView::update()
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho
|
double rho
|
||||||
) const override;
|
) const override;
|
||||||
|
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
const std::vector<fourdst::atomic::Species> &activeSpecies
|
const std::vector<fourdst::atomic::Species> &activeSpecies
|
||||||
) const override;
|
) const override;
|
||||||
|
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
const SparsityPattern &sparsityPattern
|
const SparsityPattern &sparsityPattern
|
||||||
) const override;
|
) const override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gets an entry from the Jacobian matrix for the active species.
|
|
||||||
*
|
|
||||||
* @param rowSpecies The species corresponding to the row index in the culled species list.
|
|
||||||
* @param colSpecies The species corresponding to the column index in the culled species list
|
|
||||||
* @return The value of the Jacobian matrix at (i_culled, j_culled).
|
|
||||||
*
|
|
||||||
* This method maps the culled indices to the full network indices and calls the base engine
|
|
||||||
* to get the Jacobian matrix entry.
|
|
||||||
*
|
|
||||||
* @throws std::runtime_error If the AdaptiveEngineView is stale (i.e., `update()` has not been called).
|
|
||||||
* @throws std::out_of_range If the culled index is out of bounds for the species index map.
|
|
||||||
* @see AdaptiveEngineView::update()
|
|
||||||
*/
|
|
||||||
[[nodiscard]] double getJacobianMatrixEntry(
|
|
||||||
const fourdst::atomic::Species& rowSpecies,
|
|
||||||
const fourdst::atomic::Species& colSpecies
|
|
||||||
) const override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generates the stoichiometry matrix for the active reactions and species.
|
* @brief Generates the stoichiometry matrix for the active reactions and species.
|
||||||
*
|
*
|
||||||
@@ -304,6 +285,8 @@ namespace gridfire {
|
|||||||
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
|
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
|
||||||
|
|
||||||
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] SpeciesStatus getSpeciesStatus(const fourdst::atomic::Species &species) const override;
|
||||||
private:
|
private:
|
||||||
using Config = fourdst::config::Config;
|
using Config = fourdst::config::Config;
|
||||||
using LogManager = fourdst::logging::LogManager;
|
using LogManager = fourdst::logging::LogManager;
|
||||||
@@ -321,11 +304,6 @@ namespace gridfire {
|
|||||||
/** @brief The set of reactions that are currently active in the network. */
|
/** @brief The set of reactions that are currently active in the network. */
|
||||||
reaction::ReactionSet m_activeReactions;
|
reaction::ReactionSet m_activeReactions;
|
||||||
|
|
||||||
/** @brief A map from the indices of the active species to the indices of the corresponding species in the full network. */
|
|
||||||
std::vector<size_t> m_speciesIndexMap;
|
|
||||||
/** @brief A map from the indices of the active reactions to the indices of the corresponding reactions in the full network. */
|
|
||||||
std::vector<size_t> m_reactionIndexMap;
|
|
||||||
|
|
||||||
/** @brief A flag indicating whether the view is stale and needs to be updated. */
|
/** @brief A flag indicating whether the view is stale and needs to be updated. */
|
||||||
bool m_isStale = true;
|
bool m_isStale = true;
|
||||||
|
|
||||||
@@ -338,67 +316,6 @@ namespace gridfire {
|
|||||||
double flowRate;
|
double flowRate;
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
/**
|
|
||||||
* @brief Constructs the species index map.
|
|
||||||
*
|
|
||||||
* @return A vector mapping culled species indices to full species indices.
|
|
||||||
*
|
|
||||||
* This method creates a map from the indices of the active species to the indices of the
|
|
||||||
* corresponding species in the full network.
|
|
||||||
*
|
|
||||||
* @see AdaptiveEngineView::update()
|
|
||||||
*/
|
|
||||||
[[nodiscard]] std::vector<size_t> constructSpeciesIndexMap() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructs the reaction index map.
|
|
||||||
*
|
|
||||||
* @return A vector mapping culled reaction indices to full reaction indices.
|
|
||||||
*
|
|
||||||
* This method creates a map from the indices of the active reactions to the indices of the
|
|
||||||
* corresponding reactions in the full network.
|
|
||||||
*
|
|
||||||
* @see AdaptiveEngineView::update()
|
|
||||||
*/
|
|
||||||
[[nodiscard]] std::vector<size_t> constructReactionIndexMap() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Maps a vector of culled abundances to a vector of full abundances.
|
|
||||||
*
|
|
||||||
* @param culled A vector of abundances for the active species.
|
|
||||||
* @return A vector of abundances for the full network, with the abundances of the active
|
|
||||||
* species copied from the culled vector.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] std::vector<double> mapCulledToFull(const std::vector<double>& culled) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Maps a vector of full abundances to a vector of culled abundances.
|
|
||||||
*
|
|
||||||
* @param full A vector of abundances for the full network.
|
|
||||||
* @return A vector of abundances for the active species, with the abundances of the active
|
|
||||||
* species copied from the full vector.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] std::vector<double> mapFullToCulled(const std::vector<double>& full) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Maps a culled species index to a full species index.
|
|
||||||
*
|
|
||||||
* @param culledSpeciesIndex The index of the species in the culled species list.
|
|
||||||
* @return The index of the corresponding species in the full network.
|
|
||||||
*
|
|
||||||
* @throws std::out_of_range If the culled index is out of bounds for the species index map.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] size_t mapCulledToFullSpeciesIndex(size_t culledSpeciesIndex) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Maps a culled reaction index to a full reaction index.
|
|
||||||
*
|
|
||||||
* @param culledReactionIndex The index of the reaction in the culled reaction list.
|
|
||||||
* @return The index of the corresponding reaction in the full network.
|
|
||||||
*
|
|
||||||
* @throws std::out_of_range If the culled index is out of bounds for the reaction index map.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] size_t mapCulledToFullReactionIndex(size_t culledReactionIndex) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Validates that the AdaptiveEngineView is not stale.
|
* @brief Validates that the AdaptiveEngineView is not stale.
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ namespace gridfire{
|
|||||||
*
|
*
|
||||||
* @throws std::runtime_error If the view is stale.
|
* @throws std::runtime_error If the view is stale.
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho
|
double rho
|
||||||
@@ -79,7 +79,7 @@ namespace gridfire{
|
|||||||
*
|
*
|
||||||
* @throws std::runtime_error If the view is stale.
|
* @throws std::runtime_error If the view is stale.
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
@@ -96,33 +96,20 @@ namespace gridfire{
|
|||||||
*
|
*
|
||||||
* @throws std::runtime_error If the view is stale.
|
* @throws std::runtime_error If the view is stale.
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
const SparsityPattern &sparsityPattern
|
const SparsityPattern &sparsityPattern
|
||||||
) const override;
|
) const override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gets an entry from the Jacobian matrix for the active species.
|
|
||||||
*
|
|
||||||
* @param rowSpecies The species corresponding to the row index.
|
|
||||||
* @param colSpecies The species corresponding to the column index.
|
|
||||||
* @return The value of the Jacobian matrix at (row species index, col species index).
|
|
||||||
*
|
|
||||||
* @throws std::runtime_error If the view is stale.
|
|
||||||
* @throws std::out_of_range If an index is out of bounds.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] double getJacobianMatrixEntry(
|
|
||||||
const fourdst::atomic::Species& rowSpecies,
|
|
||||||
const fourdst::atomic::Species& colSpecies
|
|
||||||
) const override;
|
|
||||||
/**
|
/**
|
||||||
* @brief Generates the stoichiometry matrix for the active reactions and species.
|
* @brief Generates the stoichiometry matrix for the active reactions and species.
|
||||||
*
|
*
|
||||||
* @throws std::runtime_error If the view is stale.
|
* @throws std::runtime_error If the view is stale.
|
||||||
*/
|
*/
|
||||||
void generateStoichiometryMatrix() override;
|
void generateStoichiometryMatrix() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets an entry from the stoichiometry matrix for the active species and reactions.
|
* @brief Gets an entry from the stoichiometry matrix for the active species and reactions.
|
||||||
*
|
*
|
||||||
@@ -222,6 +209,8 @@ namespace gridfire{
|
|||||||
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
|
[[nodiscard]] PrimingReport primeEngine(const NetIn &netIn) override;
|
||||||
|
|
||||||
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] SpeciesStatus getSpeciesStatus(const fourdst::atomic::Species &species) const override;
|
||||||
protected:
|
protected:
|
||||||
bool m_isStale = true;
|
bool m_isStale = true;
|
||||||
GraphEngine& m_baseEngine;
|
GraphEngine& m_baseEngine;
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ namespace gridfire {
|
|||||||
* @throws exceptions::StaleEngineError If the QSE cache misses, as it cannot proceed
|
* @throws exceptions::StaleEngineError If the QSE cache misses, as it cannot proceed
|
||||||
* without a valid partition.
|
* without a valid partition.
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho
|
double rho
|
||||||
@@ -177,7 +177,7 @@ namespace gridfire {
|
|||||||
*
|
*
|
||||||
* @throws exceptions::StaleEngineError If the QSE cache misses.
|
* @throws exceptions::StaleEngineError If the QSE cache misses.
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
@@ -205,35 +205,13 @@ namespace gridfire {
|
|||||||
*
|
*
|
||||||
* @throws exceptions::StaleEngineError If the QSE cache misses.
|
* @throws exceptions::StaleEngineError If the QSE cache misses.
|
||||||
*/
|
*/
|
||||||
void generateJacobianMatrix(
|
[[nodiscard]] NetworkJacobian generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
double T9,
|
double T9,
|
||||||
double rho,
|
double rho,
|
||||||
const SparsityPattern &sparsityPattern
|
const SparsityPattern &sparsityPattern
|
||||||
) const override;
|
) const override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gets an entry from the previously generated Jacobian matrix.
|
|
||||||
*
|
|
||||||
* @param rowSpecies Species corresponding to the row index (i_full).
|
|
||||||
* @param colSpecies Species corresponding to the column index (j_full).
|
|
||||||
* @return Value of the Jacobian matrix at (i_full, j_full).
|
|
||||||
*
|
|
||||||
* @par Purpose
|
|
||||||
* To provide Jacobian entries to an implicit solver.
|
|
||||||
*
|
|
||||||
* @par How
|
|
||||||
* This method directly delegates to the base engine's `getJacobianMatrixEntry`.
|
|
||||||
* It does not currently modify the Jacobian to reflect the QSE algebraic constraints,
|
|
||||||
* as these are handled by setting `dY/dt = 0` in `calculateRHSAndEnergy`.
|
|
||||||
*
|
|
||||||
* @pre `generateJacobianMatrix()` must have been called for the current state.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] double getJacobianMatrixEntry(
|
|
||||||
const fourdst::atomic::Species& rowSpecies,
|
|
||||||
const fourdst::atomic::Species& colSpecies
|
|
||||||
) const override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generates the stoichiometry matrix for the network.
|
* @brief Generates the stoichiometry matrix for the network.
|
||||||
*
|
*
|
||||||
@@ -578,6 +556,8 @@ namespace gridfire {
|
|||||||
*/
|
*/
|
||||||
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
fourdst::composition::Composition collectComposition(const fourdst::composition::CompositionAbstract &comp, double T9, double rho) const override;
|
||||||
|
|
||||||
|
SpeciesStatus getSpeciesStatus(const fourdst::atomic::Species &species) const override;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,4 +18,8 @@ namespace gridfire::exceptions {
|
|||||||
class CVODESolverFailureError final : public SolverError {
|
class CVODESolverFailureError final : public SolverError {
|
||||||
using SolverError::SolverError;
|
using SolverError::SolverError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SingularJacobianError final : public SolverError {
|
||||||
|
using SolverError::SolverError;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ namespace gridfire::diagnostics {
|
|||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) {
|
) {
|
||||||
engine.generateJacobianMatrix(comp, T9, rho);
|
const NetworkJacobian jac = engine.generateJacobianMatrix(comp, T9, rho);
|
||||||
const auto& species_list = engine.getNetworkSpecies();
|
const auto& species_list = engine.getNetworkSpecies();
|
||||||
|
|
||||||
double max_diag = 0.0;
|
double max_diag = 0.0;
|
||||||
@@ -143,7 +143,7 @@ namespace gridfire::diagnostics {
|
|||||||
|
|
||||||
for (const auto& rowSpecies : species_list) {
|
for (const auto& rowSpecies : species_list) {
|
||||||
for (const auto& colSpecies : species_list) {
|
for (const auto& colSpecies : species_list) {
|
||||||
const double val = std::abs(engine.getJacobianMatrixEntry(rowSpecies, colSpecies));
|
const double val = std::abs(jac(rowSpecies, colSpecies));
|
||||||
if (rowSpecies == colSpecies) {
|
if (rowSpecies == colSpecies) {
|
||||||
if (val > max_diag) { max_diag = val; max_diag_species = colSpecies; }
|
if (val > max_diag) { max_diag = val; max_diag_species = colSpecies; }
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#include "gridfire/engine/types/jacobian.h"
|
||||||
|
#include <Eigen/SparseCore>
|
||||||
|
#include <Eigen/SparseQR>
|
||||||
|
|
||||||
|
namespace gridfire {
|
||||||
|
NetworkJacobian::NetworkJacobian(
|
||||||
|
const Eigen::SparseMatrix<double>& jacobianMatrix,
|
||||||
|
const std::function<fourdst::atomic::Species(size_t)> &indexToSpeciesFunc
|
||||||
|
): m_jacobianMatrix(jacobianMatrix) {
|
||||||
|
for (size_t i = 0; i < jacobianMatrix.rows(); ++i) {
|
||||||
|
fourdst::atomic::Species species = indexToSpeciesFunc(i);
|
||||||
|
m_speciesToIndexMap[species] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_jacobianMatrix.rows() == 0 || m_jacobianMatrix.cols() == 0) {
|
||||||
|
m_rank = 0;
|
||||||
|
} else {
|
||||||
|
Eigen::SparseQR<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int>> solver;
|
||||||
|
solver.compute(m_jacobianMatrix);
|
||||||
|
m_rank = solver.rank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double NetworkJacobian::operator()(const fourdst::atomic::Species &row, const fourdst::atomic::Species &col) const {
|
||||||
|
if (!m_speciesToIndexMap.contains(row) || !m_speciesToIndexMap.contains(col)) {
|
||||||
|
throw std::out_of_range("Species not found in NetworkJacobian operator().");
|
||||||
|
}
|
||||||
|
const size_t i = m_speciesToIndexMap.at(row);
|
||||||
|
const size_t j = m_speciesToIndexMap.at(col);
|
||||||
|
return this->operator()(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
double NetworkJacobian::operator()(const size_t i, const size_t j) const {
|
||||||
|
if (i >= m_jacobianMatrix.rows() || j >= m_jacobianMatrix.cols()) {
|
||||||
|
throw std::out_of_range("Index out of bounds in NetworkJacobian operator().");
|
||||||
|
}
|
||||||
|
return m_jacobianMatrix.coeff(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<size_t, size_t> NetworkJacobian::shape() const {
|
||||||
|
return {m_jacobianMatrix.rows(), m_jacobianMatrix.cols()};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NetworkJacobian::nnz() const {
|
||||||
|
return m_jacobianMatrix.nonZeros();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NetworkJacobian::rank() const {
|
||||||
|
return m_rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkJacobian::singular() const {
|
||||||
|
const size_t rows = m_jacobianMatrix.rows();
|
||||||
|
const size_t cols = m_jacobianMatrix.cols();
|
||||||
|
const size_t minDim = (rows < cols) ? rows : cols;
|
||||||
|
return m_rank < minDim;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,74 +18,13 @@ namespace gridfire {
|
|||||||
) :
|
) :
|
||||||
m_baseEngine(baseEngine),
|
m_baseEngine(baseEngine),
|
||||||
m_activeSpecies(baseEngine.getNetworkSpecies()),
|
m_activeSpecies(baseEngine.getNetworkSpecies()),
|
||||||
m_activeReactions(baseEngine.getNetworkReactions()),
|
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 AdaptiveEngineView::update(const NetIn &netIn) {
|
||||||
|
m_activeReactions.clear();
|
||||||
|
m_activeSpecies.clear();
|
||||||
|
|
||||||
fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.update(netIn);
|
fourdst::composition::Composition baseUpdatedComposition = m_baseEngine.update(netIn);
|
||||||
NetIn updatedNetIn = netIn;
|
NetIn updatedNetIn = netIn;
|
||||||
|
|
||||||
@@ -118,14 +57,11 @@ namespace gridfire {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& species : rescuedSpecies) {
|
for (const auto& species : rescuedSpecies) {
|
||||||
if (!std::ranges::contains(m_activeSpecies, species)) {
|
if (!std::ranges::contains(m_activeSpecies, species) && m_baseEngine.getSpeciesStatus(species) == SpeciesStatus::ACTIVE) {
|
||||||
m_activeSpecies.push_back(species);
|
m_activeSpecies.push_back(species);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_speciesIndexMap = constructSpeciesIndexMap();
|
|
||||||
m_reactionIndexMap = constructReactionIndexMap();
|
|
||||||
|
|
||||||
m_isStale = false;
|
m_isStale = false;
|
||||||
|
|
||||||
LOG_INFO(m_logger, "AdaptiveEngineView updated successfully with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
|
LOG_INFO(m_logger, "AdaptiveEngineView updated successfully with {} active species and {} active reactions.", m_activeSpecies.size(), m_activeReactions.size());
|
||||||
@@ -166,42 +102,33 @@ namespace gridfire {
|
|||||||
return m_baseEngine.calculateEpsDerivatives(comp, T9, rho);
|
return m_baseEngine.calculateEpsDerivatives(comp, T9, rho);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdaptiveEngineView::generateJacobianMatrix(
|
NetworkJacobian AdaptiveEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) const {
|
) const {
|
||||||
generateJacobianMatrix(comp, T9, rho, m_activeSpecies);
|
return generateJacobianMatrix(comp, T9, rho, m_activeSpecies);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdaptiveEngineView::generateJacobianMatrix(
|
NetworkJacobian AdaptiveEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho,
|
const double rho,
|
||||||
const std::vector<Species> &activeSpecies
|
const std::vector<Species> &activeSpecies
|
||||||
) const {
|
) const {
|
||||||
validateState();
|
validateState();
|
||||||
m_baseEngine.generateJacobianMatrix(comp, T9, rho, activeSpecies);
|
return m_baseEngine.generateJacobianMatrix(comp, T9, rho, activeSpecies);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdaptiveEngineView::generateJacobianMatrix(
|
NetworkJacobian AdaptiveEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho,
|
const double rho,
|
||||||
const SparsityPattern &sparsityPattern
|
const SparsityPattern &sparsityPattern
|
||||||
) const {
|
) const {
|
||||||
validateState();
|
validateState();
|
||||||
m_baseEngine.generateJacobianMatrix(comp, T9, rho, sparsityPattern);
|
return m_baseEngine.generateJacobianMatrix(comp, T9, rho, sparsityPattern);
|
||||||
}
|
|
||||||
|
|
||||||
double AdaptiveEngineView::getJacobianMatrixEntry(
|
|
||||||
const Species &rowSpecies,
|
|
||||||
const Species &colSpecies
|
|
||||||
) const {
|
|
||||||
validateState();
|
|
||||||
|
|
||||||
return m_baseEngine.getJacobianMatrixEntry(rowSpecies, colSpecies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdaptiveEngineView::generateStoichiometryMatrix() {
|
void AdaptiveEngineView::generateStoichiometryMatrix() {
|
||||||
@@ -328,6 +255,14 @@ namespace gridfire {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpeciesStatus AdaptiveEngineView::getSpeciesStatus(const fourdst::atomic::Species &species) const {
|
||||||
|
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(species);
|
||||||
|
if (status == SpeciesStatus::ACTIVE && std::ranges::find(m_activeSpecies, species) == m_activeSpecies.end()) {
|
||||||
|
return SpeciesStatus::INACTIVE_FLOW;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
size_t AdaptiveEngineView::getSpeciesIndex(const fourdst::atomic::Species &species) const {
|
size_t AdaptiveEngineView::getSpeciesIndex(const fourdst::atomic::Species &species) const {
|
||||||
const auto it = std::ranges::find(m_activeSpecies, species);
|
const auto it = std::ranges::find(m_activeSpecies, species);
|
||||||
if (it != m_activeSpecies.end()) {
|
if (it != m_activeSpecies.end()) {
|
||||||
@@ -339,42 +274,6 @@ namespace gridfire {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 >= 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 >= 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 {
|
void AdaptiveEngineView::validateState() const {
|
||||||
if (m_isStale) {
|
if (m_isStale) {
|
||||||
LOG_ERROR(m_logger, "AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
|
LOG_ERROR(m_logger, "AdaptiveEngineView is stale. Please call update() before calculating RHS and energy.");
|
||||||
@@ -534,29 +433,6 @@ namespace gridfire {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
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::Reaction*> reactionsToRescue;
|
std::unordered_map<Species, const reaction::Reaction*> reactionsToRescue;
|
||||||
for (const auto& species : onlyProducedSpecies) {
|
for (const auto& species : onlyProducedSpecies) {
|
||||||
double maxSpeciesConsumptionRate = 0.0;
|
double maxSpeciesConsumptionRate = 0.0;
|
||||||
@@ -576,8 +452,6 @@ namespace gridfire {
|
|||||||
std::vector<double> Y = comp.getMolarAbundanceVector();
|
std::vector<double> Y = comp.getMolarAbundanceVector();
|
||||||
|
|
||||||
const double Ye = comp.getElectronAbundance();
|
const double Ye = comp.getElectronAbundance();
|
||||||
// TODO: This is a dummy placeholder which must be replaced with an EOS call
|
|
||||||
const double mue = 5.0e-3 * std::pow(rho * Ye, 1.0 / 3.0) + 0.5 * T9;
|
|
||||||
|
|
||||||
std::unordered_map<Species, double> speciesMassMap;
|
std::unordered_map<Species, double> speciesMassMap;
|
||||||
for (const auto &sp: comp | std::views::keys) {
|
for (const auto &sp: comp | std::views::keys) {
|
||||||
@@ -588,7 +462,7 @@ namespace gridfire {
|
|||||||
size_t distance = std::distance(speciesMassMap.begin(), speciesMassMap.find(sp));
|
size_t distance = std::distance(speciesMassMap.begin(), speciesMassMap.find(sp));
|
||||||
speciesIndexMap.emplace(distance, sp);
|
speciesIndexMap.emplace(distance, sp);
|
||||||
}
|
}
|
||||||
double rate = reaction->calculate_rate(T9, rho, Ye, mue, Y, speciesIndexMap);
|
double rate = reaction->calculate_rate(T9, rho, Ye, 0.0, Y, speciesIndexMap);
|
||||||
if (rate > maxSpeciesConsumptionRate) {
|
if (rate > maxSpeciesConsumptionRate) {
|
||||||
maxSpeciesConsumptionRate = rate;
|
maxSpeciesConsumptionRate = rate;
|
||||||
reactionsToRescue[species] = reaction.get();
|
reactionsToRescue[species] = reaction.get();
|
||||||
@@ -652,7 +526,6 @@ namespace gridfire {
|
|||||||
}()
|
}()
|
||||||
);
|
);
|
||||||
|
|
||||||
RescueSet rescueSet;
|
|
||||||
std::unordered_set<const reaction::Reaction*> newReactions;
|
std::unordered_set<const reaction::Reaction*> newReactions;
|
||||||
std::unordered_set<Species> newSpecies;
|
std::unordered_set<Species> newSpecies;
|
||||||
|
|
||||||
@@ -673,22 +546,20 @@ namespace gridfire {
|
|||||||
for (const auto* reactionPtr: finalReactions) {
|
for (const auto* reactionPtr: finalReactions) {
|
||||||
m_activeReactions.add_reaction(*reactionPtr);
|
m_activeReactions.add_reaction(*reactionPtr);
|
||||||
for (const auto& reactant : reactionPtr->reactants()) {
|
for (const auto& reactant : reactionPtr->reactants()) {
|
||||||
if (!finalSpeciesSet.contains(reactant)) {
|
const SpeciesStatus reactantStatus = m_baseEngine.getSpeciesStatus(reactant);
|
||||||
LOG_TRACE_L1(m_logger, "Adding reactant '{}' to active species set through reaction {}.", reactant.name(), reactionPtr->id());
|
if (!finalSpeciesSet.contains(reactant) && (reactantStatus == SpeciesStatus::ACTIVE || reactantStatus == SpeciesStatus::EQUILIBRIUM)) {
|
||||||
} else {
|
LOG_TRACE_L3(m_logger, "Adding reactant '{}' to active species set through reaction {}.", reactant.name(), reactionPtr->id());
|
||||||
LOG_TRACE_L1(m_logger, "Reactant '{}' already in active species set through another reaction.", reactant.name());
|
|
||||||
}
|
|
||||||
finalSpeciesSet.insert(reactant);
|
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());
|
|
||||||
}
|
}
|
||||||
|
for (const auto& product : reactionPtr->products()) {
|
||||||
|
const SpeciesStatus productStatus = m_baseEngine.getSpeciesStatus(product);
|
||||||
|
if (!finalSpeciesSet.contains(product) && (productStatus == SpeciesStatus::ACTIVE || productStatus == SpeciesStatus::EQUILIBRIUM)) {
|
||||||
|
LOG_TRACE_L3(m_logger, "Adding product '{}' to active species set through reaction {}.", product.name(), reactionPtr->id());
|
||||||
finalSpeciesSet.insert(product);
|
finalSpeciesSet.insert(product);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
m_activeSpecies.clear();
|
m_activeSpecies.clear();
|
||||||
m_activeSpecies = std::vector<Species>(finalSpeciesSet.begin(), finalSpeciesSet.end());
|
m_activeSpecies = std::vector<Species>(finalSpeciesSet.begin(), finalSpeciesSet.end());
|
||||||
std::ranges::sort(
|
std::ranges::sort(
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ namespace gridfire {
|
|||||||
return m_baseEngine.calculateEpsDerivatives(masked, T9, rho, m_activeReactions);
|
return m_baseEngine.calculateEpsDerivatives(masked, T9, rho, m_activeReactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefinedEngineView::generateJacobianMatrix(
|
NetworkJacobian DefinedEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
@@ -79,10 +79,10 @@ namespace gridfire {
|
|||||||
m_activeSpeciesVectorCache = std::vector<Species>(m_activeSpecies.begin(), m_activeSpecies.end());
|
m_activeSpeciesVectorCache = std::vector<Species>(m_activeSpecies.begin(), m_activeSpecies.end());
|
||||||
}
|
}
|
||||||
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies);
|
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies);
|
||||||
m_baseEngine.generateJacobianMatrix(masked, T9, rho, m_activeSpeciesVectorCache.value());
|
return m_baseEngine.generateJacobianMatrix(masked, T9, rho, m_activeSpeciesVectorCache.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefinedEngineView::generateJacobianMatrix(
|
NetworkJacobian DefinedEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho,
|
const double rho,
|
||||||
@@ -96,10 +96,10 @@ namespace gridfire {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fourdst::composition::MaskedComposition masked(comp, activeSpeciesSet);
|
const fourdst::composition::MaskedComposition masked(comp, activeSpeciesSet);
|
||||||
m_baseEngine.generateJacobianMatrix(masked, T9, rho, activeSpecies);
|
return m_baseEngine.generateJacobianMatrix(masked, T9, rho, activeSpecies);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefinedEngineView::generateJacobianMatrix(
|
NetworkJacobian DefinedEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho,
|
const double rho,
|
||||||
@@ -107,27 +107,7 @@ namespace gridfire {
|
|||||||
) const {
|
) const {
|
||||||
validateNetworkState();
|
validateNetworkState();
|
||||||
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies);
|
const fourdst::composition::MaskedComposition masked(comp, m_activeSpecies);
|
||||||
m_baseEngine.generateJacobianMatrix(masked, T9, rho, sparsityPattern);
|
return m_baseEngine.generateJacobianMatrix(masked, T9, rho, sparsityPattern);
|
||||||
}
|
|
||||||
|
|
||||||
double DefinedEngineView::getJacobianMatrixEntry(
|
|
||||||
const Species& rowSpecies,
|
|
||||||
const Species& colSpecies
|
|
||||||
) const {
|
|
||||||
validateNetworkState();
|
|
||||||
|
|
||||||
if (!m_activeSpecies.contains(rowSpecies)) {
|
|
||||||
LOG_ERROR(m_logger, "Row species '{}' is not part of the active species in the DefinedEngineView.", rowSpecies.name());
|
|
||||||
m_logger -> flush_log();
|
|
||||||
throw std::runtime_error("Row species not found in active species: " + std::string(rowSpecies.name()));
|
|
||||||
}
|
|
||||||
if (!m_activeSpecies.contains(colSpecies)) {
|
|
||||||
LOG_ERROR(m_logger, "Column species '{}' is not part of the active species in the DefinedEngineView.", colSpecies.name());
|
|
||||||
m_logger -> flush_log();
|
|
||||||
throw std::runtime_error("Column species not found in active species: " + std::string(colSpecies.name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_baseEngine.getJacobianMatrixEntry(rowSpecies, colSpecies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefinedEngineView::generateStoichiometryMatrix() {
|
void DefinedEngineView::generateStoichiometryMatrix() {
|
||||||
@@ -298,6 +278,14 @@ namespace gridfire {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpeciesStatus DefinedEngineView::getSpeciesStatus(const Species &species) const {
|
||||||
|
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(species);
|
||||||
|
if (status == SpeciesStatus::ACTIVE && !m_activeSpecies.contains(species)) {
|
||||||
|
return SpeciesStatus::INACTIVE_FLOW;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<size_t> DefinedEngineView::constructSpeciesIndexMap() const {
|
std::vector<size_t> DefinedEngineView::constructSpeciesIndexMap() const {
|
||||||
LOG_TRACE_L3(m_logger, "Constructing species index map for DefinedEngineView...");
|
LOG_TRACE_L3(m_logger, "Constructing species index map for DefinedEngineView...");
|
||||||
std::unordered_map<Species, size_t> fullSpeciesReverseMap;
|
std::unordered_map<Species, size_t> fullSpeciesReverseMap;
|
||||||
|
|||||||
@@ -163,9 +163,9 @@ namespace gridfire {
|
|||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) const {
|
) const {
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
|
|
||||||
const auto result = m_baseEngine.calculateRHSAndEnergy(qseComposition, T9, rho);
|
const auto result = m_baseEngine.calculateRHSAndEnergy(comp, T9, rho);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return std::unexpected{result.error()};
|
return std::unexpected{result.error()};
|
||||||
@@ -184,28 +184,29 @@ namespace gridfire {
|
|||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) const {
|
) const {
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
return m_baseEngine.calculateEpsDerivatives(qseComposition, T9, rho);
|
return m_baseEngine.calculateEpsDerivatives(comp, T9, rho);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiscalePartitioningEngineView::generateJacobianMatrix(
|
NetworkJacobian MultiscalePartitioningEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) const {
|
) const {
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
m_baseEngine.generateJacobianMatrix(qseComposition, T9, rho, m_dynamic_species);
|
return m_baseEngine.generateJacobianMatrix(comp, T9, rho, m_dynamic_species);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiscalePartitioningEngineView::generateJacobianMatrix(
|
NetworkJacobian MultiscalePartitioningEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho,
|
const double rho,
|
||||||
const std::vector<Species> &activeSpecies
|
const std::vector<Species> &activeSpecies
|
||||||
) const {
|
) const {
|
||||||
const bool activeSpeciesIsSubset = std::ranges::any_of(activeSpecies, [&](const auto& species) -> bool {
|
bool activeSpeciesIsSubset = true;
|
||||||
return !involvesSpecies(species);
|
for (const auto& species : activeSpecies) {
|
||||||
});
|
if (!involvesSpecies(species)) activeSpeciesIsSubset = false;
|
||||||
|
}
|
||||||
if (!activeSpeciesIsSubset) {
|
if (!activeSpeciesIsSubset) {
|
||||||
std::string msg = std::format(
|
std::string msg = std::format(
|
||||||
"Active species set contains species ({}) not present in network partition. Cannot generate jacobian matrix due to this.",
|
"Active species set contains species ({}) not present in network partition. Cannot generate jacobian matrix due to this.",
|
||||||
@@ -230,33 +231,19 @@ namespace gridfire {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
|
|
||||||
m_baseEngine.generateJacobianMatrix(qseComposition, T9, rho, dynamicActiveSpeciesIntersection);
|
return m_baseEngine.generateJacobianMatrix(comp, T9, rho, dynamicActiveSpeciesIntersection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiscalePartitioningEngineView::generateJacobianMatrix(
|
NetworkJacobian MultiscalePartitioningEngineView::generateJacobianMatrix(
|
||||||
const fourdst::composition::CompositionAbstract &comp,
|
const fourdst::composition::CompositionAbstract &comp,
|
||||||
const double T9,
|
const double T9,
|
||||||
const double rho,
|
const double rho,
|
||||||
const SparsityPattern &sparsityPattern
|
const SparsityPattern &sparsityPattern
|
||||||
) const {
|
) const {
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
return m_baseEngine.generateJacobianMatrix(qseComposition, T9, rho, sparsityPattern);
|
return m_baseEngine.generateJacobianMatrix(comp, T9, rho, sparsityPattern);
|
||||||
}
|
|
||||||
|
|
||||||
double MultiscalePartitioningEngineView::getJacobianMatrixEntry(
|
|
||||||
const Species& rowSpecies,
|
|
||||||
const Species& colSpecies
|
|
||||||
) const {
|
|
||||||
// Check if the species we are differentiating with respect to is algebraic or dynamic. If it is algebraic we can reduce the work significantly...
|
|
||||||
if (std::ranges::contains(m_algebraic_species, colSpecies)) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
if (std::ranges::contains(m_algebraic_species, rowSpecies)) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
return m_baseEngine.getJacobianMatrixEntry(rowSpecies, colSpecies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiscalePartitioningEngineView::generateStoichiometryMatrix() {
|
void MultiscalePartitioningEngineView::generateStoichiometryMatrix() {
|
||||||
@@ -276,9 +263,9 @@ namespace gridfire {
|
|||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) const {
|
) const {
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
|
|
||||||
return m_baseEngine.calculateMolarReactionFlow(reaction, qseComposition, T9, rho);
|
return m_baseEngine.calculateMolarReactionFlow(reaction, comp, T9, rho);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reaction::ReactionSet & MultiscalePartitioningEngineView::getNetworkReactions() const {
|
const reaction::ReactionSet & MultiscalePartitioningEngineView::getNetworkReactions() const {
|
||||||
@@ -295,8 +282,8 @@ namespace gridfire {
|
|||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) const {
|
) const {
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
const auto result = m_baseEngine.getSpeciesTimescales(qseComposition, T9, rho);
|
const auto result = m_baseEngine.getSpeciesTimescales(comp, T9, rho);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return std::unexpected{result.error()};
|
return std::unexpected{result.error()};
|
||||||
}
|
}
|
||||||
@@ -313,8 +300,8 @@ namespace gridfire {
|
|||||||
const double T9,
|
const double T9,
|
||||||
const double rho
|
const double rho
|
||||||
) const {
|
) const {
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
const auto result = m_baseEngine.getSpeciesDestructionTimescales(qseComposition, T9, rho);
|
const auto result = m_baseEngine.getSpeciesDestructionTimescales(comp, T9, rho);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return std::unexpected{result.error()};
|
return std::unexpected{result.error()};
|
||||||
}
|
}
|
||||||
@@ -524,6 +511,14 @@ namespace gridfire {
|
|||||||
std::ranges::sort(m_qse_groups, [](const QSEGroup& a, const QSEGroup& b) {
|
std::ranges::sort(m_qse_groups, [](const QSEGroup& a, const QSEGroup& b) {
|
||||||
return a.mean_timescale < b.mean_timescale;
|
return a.mean_timescale < b.mean_timescale;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const auto& species : m_baseEngine.getNetworkSpecies()) {
|
||||||
|
bool involvesAlgebraic = involvesSpeciesInQSE(species);
|
||||||
|
if (std::ranges::find(m_dynamic_species, species) == m_dynamic_species.end() && !involvesAlgebraic) {
|
||||||
|
// Species is classed as neither dynamic nor algebraic at end of partitioning → add to dynamic set
|
||||||
|
m_dynamic_species.push_back(species);
|
||||||
|
}
|
||||||
|
}
|
||||||
return getNormalizedEquilibratedComposition(comp, T9, rho);
|
return getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,7 +549,7 @@ namespace gridfire {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
// const fourdst::composition::Composition qseComposition = getNormalizedEquilibratedComposition(comp, T9, rho);
|
||||||
// Calculate reaction flows and find min/max for logarithmic scaling of transparency
|
// Calculate reaction flows and find min/max for logarithmic scaling of transparency
|
||||||
std::vector<double> reaction_flows;
|
std::vector<double> reaction_flows;
|
||||||
reaction_flows.reserve(all_reactions.size());
|
reaction_flows.reserve(all_reactions.size());
|
||||||
@@ -562,7 +557,7 @@ namespace gridfire {
|
|||||||
double max_log_flow = std::numeric_limits<double>::lowest();
|
double max_log_flow = std::numeric_limits<double>::lowest();
|
||||||
|
|
||||||
for (const auto& reaction : all_reactions) {
|
for (const auto& reaction : all_reactions) {
|
||||||
double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(*reaction, qseComposition, T9, rho));
|
double flow = std::abs(m_baseEngine.calculateMolarReactionFlow(*reaction, comp, T9, rho));
|
||||||
reaction_flows.push_back(flow);
|
reaction_flows.push_back(flow);
|
||||||
if (flow > 1e-99) { // Avoid log(0)
|
if (flow > 1e-99) { // Avoid log(0)
|
||||||
double log_flow = std::log10(flow);
|
double log_flow = std::log10(flow);
|
||||||
@@ -826,6 +821,14 @@ namespace gridfire {
|
|||||||
return qseComposition;
|
return qseComposition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpeciesStatus MultiscalePartitioningEngineView::getSpeciesStatus(const Species &species) const {
|
||||||
|
const SpeciesStatus status = m_baseEngine.getSpeciesStatus(species);
|
||||||
|
if (status == SpeciesStatus::ACTIVE && involvesSpeciesInQSE(species)) {
|
||||||
|
return SpeciesStatus::EQUILIBRIUM;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
size_t MultiscalePartitioningEngineView::getSpeciesIndex(const Species &species) const {
|
size_t MultiscalePartitioningEngineView::getSpeciesIndex(const Species &species) const {
|
||||||
return m_baseEngine.getSpeciesIndex(species);
|
return m_baseEngine.getSpeciesIndex(species);
|
||||||
}
|
}
|
||||||
@@ -1499,7 +1502,7 @@ namespace gridfire {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Species> qse_species_vector(m_qse_solve_species.begin(), m_qse_solve_species.end());
|
std::vector<Species> qse_species_vector(m_qse_solve_species.begin(), m_qse_solve_species.end());
|
||||||
m_view.getBaseEngine().generateJacobianMatrix(comp_trial, m_T9, m_rho, qse_species_vector);
|
NetworkJacobian jac = m_view.getBaseEngine().generateJacobianMatrix(comp_trial, m_T9, m_rho, qse_species_vector);
|
||||||
const auto result = m_view.getBaseEngine().calculateRHSAndEnergy(comp_trial, m_T9, m_rho);
|
const auto result = m_view.getBaseEngine().calculateRHSAndEnergy(comp_trial, m_T9, m_rho);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw exceptions::StaleEngineError("Failed to calculate RHS and energy due to stale engine state");
|
throw exceptions::StaleEngineError("Failed to calculate RHS and energy due to stale engine state");
|
||||||
@@ -1512,10 +1515,7 @@ namespace gridfire {
|
|||||||
for (const auto& rowSpecies : m_qse_solve_species) {
|
for (const auto& rowSpecies : m_qse_solve_species) {
|
||||||
long colID = 0;
|
long colID = 0;
|
||||||
for (const auto& colSpecies: m_qse_solve_species) {
|
for (const auto& colSpecies: m_qse_solve_species) {
|
||||||
J_qse(rowID, colID) = m_view.getBaseEngine().getJacobianMatrixEntry(
|
J_qse(rowID, colID) = jac(rowSpecies, colSpecies);
|
||||||
rowSpecies,
|
|
||||||
colSpecies
|
|
||||||
);
|
|
||||||
colID += 1;
|
colID += 1;
|
||||||
LOG_TRACE_L3(m_view.m_logger, "Jacobian[{}, {}] (d(dY({}))/dY({})) = {}", rowID, colID - 1, rowSpecies.name(), colSpecies.name(), J_qse(rowID, colID - 1));
|
LOG_TRACE_L3(m_view.m_logger, "Jacobian[{}, {}] (d(dY({}))/dY({})) = {}", rowID, colID - 1, rowSpecies.name(), colSpecies.name(), J_qse(rowID, colID - 1));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,13 +170,12 @@ namespace gridfire::solver {
|
|||||||
size_t numSpecies = m_engine.getNetworkSpecies().size();
|
size_t numSpecies = m_engine.getNetworkSpecies().size();
|
||||||
uint64_t N = numSpecies + 1;
|
uint64_t N = numSpecies + 1;
|
||||||
|
|
||||||
LOG_TRACE_L1(m_logger, "Number of species: {}", N);
|
LOG_TRACE_L1(m_logger, "Number of species: {} ({} independent variables)", numSpecies, N);
|
||||||
LOG_TRACE_L1(m_logger, "Initializing CVODE resources");
|
LOG_TRACE_L1(m_logger, "Initializing CVODE resources");
|
||||||
m_cvode_mem = CVodeCreate(CV_BDF, m_sun_ctx);
|
m_cvode_mem = CVodeCreate(CV_BDF, m_sun_ctx);
|
||||||
check_cvode_flag(m_cvode_mem == nullptr ? -1 : 0, "CVodeCreate");
|
check_cvode_flag(m_cvode_mem == nullptr ? -1 : 0, "CVodeCreate");
|
||||||
|
|
||||||
initialize_cvode_integration_resources(N, numSpecies, 0.0, equilibratedComposition, absTol, relTol, 0.0);
|
initialize_cvode_integration_resources(N, numSpecies, 0.0, equilibratedComposition, absTol, relTol, 0.0);
|
||||||
m_engine.generateJacobianMatrix(equilibratedComposition, T9, netIn.density);
|
|
||||||
|
|
||||||
CVODEUserData user_data;
|
CVODEUserData user_data;
|
||||||
user_data.solver_instance = this;
|
user_data.solver_instance = this;
|
||||||
@@ -275,6 +274,22 @@ namespace gridfire::solver {
|
|||||||
}
|
}
|
||||||
trigger->step(ctx);
|
trigger->step(ctx);
|
||||||
|
|
||||||
|
// log_step_diagnostics(user_data, true);
|
||||||
|
|
||||||
|
// std::ofstream jout("Jacobian.dat");
|
||||||
|
// for (const auto& row: m_engine.getNetworkSpecies()) {
|
||||||
|
// size_t i = 0;
|
||||||
|
// for (const auto& col : m_engine.getNetworkSpecies()) {
|
||||||
|
// jout << m_engine.getJacobianMatrixEntry(row, col);
|
||||||
|
// if (i < m_engine.getNetworkSpecies().size() - 1) {
|
||||||
|
// jout << ", ";
|
||||||
|
// }
|
||||||
|
// i++;
|
||||||
|
// }
|
||||||
|
// jout << "\n";
|
||||||
|
// }
|
||||||
|
// jout.close();
|
||||||
|
|
||||||
if (trigger->check(ctx)) {
|
if (trigger->check(ctx)) {
|
||||||
if (m_stdout_logging_enabled && displayTrigger) {
|
if (m_stdout_logging_enabled && displayTrigger) {
|
||||||
trigger::printWhy(trigger->why(ctx));
|
trigger::printWhy(trigger->why(ctx));
|
||||||
@@ -446,9 +461,6 @@ namespace gridfire::solver {
|
|||||||
|
|
||||||
check_cvode_flag(CVodeReInit(m_cvode_mem, current_time, m_Y), "CVodeReInit");
|
check_cvode_flag(CVodeReInit(m_cvode_mem, current_time, m_Y), "CVodeReInit");
|
||||||
|
|
||||||
LOG_TRACE_L1(m_logger, "Regenerating jacobian matrix...");
|
|
||||||
m_engine.generateJacobianMatrix(currentComposition, T9, netIn.density);
|
|
||||||
LOG_TRACE_L1(m_logger, "Done regenerating jacobian matrix...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -552,6 +564,20 @@ namespace gridfire::solver {
|
|||||||
const auto* engine = data->engine;
|
const auto* engine = data->engine;
|
||||||
|
|
||||||
const size_t numSpecies = engine->getNetworkSpecies().size();
|
const size_t numSpecies = engine->getNetworkSpecies().size();
|
||||||
|
sunrealtype* y_data = N_VGetArrayPointer(y);
|
||||||
|
|
||||||
|
// Solver constraints should keep these values very close to 0 but floating point noise can still result in very
|
||||||
|
// small negative numbers which can result in NaN's and more immediate crashes in the composition
|
||||||
|
// finalization stage
|
||||||
|
for (size_t i = 0; i < numSpecies; ++i) {
|
||||||
|
if (y_data[i] < 0.0) {
|
||||||
|
y_data[i] = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<double> y_vec(y_data, y_data + numSpecies);
|
||||||
|
fourdst::composition::Composition composition(engine->getNetworkSpecies(), y_vec);
|
||||||
|
|
||||||
|
NetworkJacobian jac = engine->generateJacobianMatrix(composition, data->T9, data->rho);
|
||||||
|
|
||||||
sunrealtype* J_data = SUNDenseMatrix_Data(J);
|
sunrealtype* J_data = SUNDenseMatrix_Data(J);
|
||||||
const long int N = SUNDenseMatrix_Columns(J);
|
const long int N = SUNDenseMatrix_Columns(J);
|
||||||
@@ -562,7 +588,14 @@ namespace gridfire::solver {
|
|||||||
const fourdst::atomic::Species& species_i = engine->getNetworkSpecies()[i];
|
const fourdst::atomic::Species& species_i = engine->getNetworkSpecies()[i];
|
||||||
// J(i,j) = d(f_i)/d(y_j)
|
// J(i,j) = d(f_i)/d(y_j)
|
||||||
// Column-major order format for SUNDenseMatrix: J_data[j*N + i] indexes J(i,j)
|
// Column-major order format for SUNDenseMatrix: J_data[j*N + i] indexes J(i,j)
|
||||||
const double dYi_dt = engine->getJacobianMatrixEntry(species_i, species_j);
|
const double dYi_dt = jac(species_i, species_j);
|
||||||
|
// if (i == j && dYi_dt == 0 && engine->getSpeciesStatus(species_i) == SpeciesStatus::ACTIVE) {
|
||||||
|
// std::cerr << "Warning: Jacobian matrix has a zero on the diagonal for species " << species_i.name() << ". This may lead to solver failure or pathological stiffness.\n";
|
||||||
|
// // throw exceptions::SingularJacobianError(
|
||||||
|
// // "Jacobian matrix has a zero on the diagonal for species " + std::string(species_i.name()) +
|
||||||
|
// // ". This will either lead to solver failure or pathological stiffness. In order to ensure tractability GridFire will not proceed. Focus on improving conditioning of the Jacobian matrix. If you believe this is an error please contact the GridFire developers."
|
||||||
|
// // );
|
||||||
|
// }
|
||||||
J_data[j * N + i] = dYi_dt;
|
J_data[j * N + i] = dYi_dt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -695,19 +728,75 @@ namespace gridfire::solver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CVODESolverStrategy::log_step_diagnostics(const CVODEUserData &user_data, bool displayJacobianStiffness) const {
|
void CVODESolverStrategy::log_step_diagnostics(const CVODEUserData &user_data, bool displayJacobianStiffness) const {
|
||||||
|
|
||||||
|
// --- 1. Get CVODE Step Statistics ---
|
||||||
|
sunrealtype hlast, hcur, tcur;
|
||||||
|
int qlast;
|
||||||
|
|
||||||
|
check_cvode_flag(CVodeGetLastStep(m_cvode_mem, &hlast), "CVodeGetLastStep");
|
||||||
|
check_cvode_flag(CVodeGetCurrentStep(m_cvode_mem, &hcur), "CVodeGetCurrentStep");
|
||||||
|
check_cvode_flag(CVodeGetLastOrder(m_cvode_mem, &qlast), "CVodeGetLastOrder");
|
||||||
|
check_cvode_flag(CVodeGetCurrentTime(m_cvode_mem, &tcur), "CVodeGetCurrentTime");
|
||||||
|
|
||||||
|
{
|
||||||
|
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)};
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<utils::ColumnBase>> columns;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. Get CVODE Cumulative Solver Statistics ---
|
||||||
|
// These are the CRITICAL counters for diagnosing your problem
|
||||||
|
long int nsteps, nfevals, nlinsetups, netfails, nniters, nconvfails, nsetfails;
|
||||||
|
|
||||||
|
check_cvode_flag(CVodeGetNumSteps(m_cvode_mem, &nsteps), "CVodeGetNumSteps");
|
||||||
|
check_cvode_flag(CVodeGetNumRhsEvals(m_cvode_mem, &nfevals), "CVodeGetNumRhsEvals");
|
||||||
|
check_cvode_flag(CVodeGetNumLinSolvSetups(m_cvode_mem, &nlinsetups), "CVodeGetNumLinSolvSetups");
|
||||||
|
check_cvode_flag(CVodeGetNumErrTestFails(m_cvode_mem, &netfails), "CVodeGetNumErrTestFails");
|
||||||
|
check_cvode_flag(CVodeGetNumNonlinSolvIters(m_cvode_mem, &nniters), "CVodeGetNumNonlinSolvIters");
|
||||||
|
check_cvode_flag(CVodeGetNumNonlinSolvConvFails(m_cvode_mem, &nconvfails), "CVodeGetNumNonlinSolvConvFails");
|
||||||
|
check_cvode_flag(CVodeGetNumLinConvFails(m_cvode_mem, &nsetfails), "CVodeGetNumLinConvFails");
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<std::string> labels = {
|
||||||
|
"Total Steps",
|
||||||
|
"RHS Evals",
|
||||||
|
"Linear Solver Setups (Jacobians)",
|
||||||
|
"Total Newton Iters",
|
||||||
|
"Error Test Fails",
|
||||||
|
"Convergence Fails",
|
||||||
|
"Linear Convergence Failures"
|
||||||
|
};
|
||||||
|
// --- ADDED nsetfails TO THIS LIST ---
|
||||||
|
std::vector<long int> values = {nsteps, nfevals, nlinsetups, nniters, netfails, nconvfails, nsetfails};
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<utils::ColumnBase>> columns;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Get Estimated Local Errors (Your Original Logic) ---
|
||||||
check_cvode_flag(CVodeGetEstLocalErrors(m_cvode_mem, m_YErr), "CVodeGetEstLocalErrors");
|
check_cvode_flag(CVodeGetEstLocalErrors(m_cvode_mem, m_YErr), "CVodeGetEstLocalErrors");
|
||||||
|
|
||||||
sunrealtype *y_data = N_VGetArrayPointer(m_Y);
|
sunrealtype *y_data = N_VGetArrayPointer(m_Y);
|
||||||
|
sunrealtype *y_err_data = N_VGetArrayPointer(m_YErr);
|
||||||
|
|
||||||
|
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-8);
|
||||||
|
|
||||||
std::vector<double> err_ratios;
|
std::vector<double> err_ratios;
|
||||||
|
|
||||||
const size_t num_components = N_VGetLength(m_Y);
|
const size_t num_components = N_VGetLength(m_Y);
|
||||||
err_ratios.resize(num_components - 1);
|
err_ratios.resize(num_components - 1); // Assuming -1 is for Energy or similar
|
||||||
|
|
||||||
std::vector<double> Y_full(y_data, y_data + num_components - 1);
|
std::vector<double> Y_full(y_data, y_data + num_components - 1);
|
||||||
|
|
||||||
|
|
||||||
std::ranges::replace_if(
|
std::ranges::replace_if(
|
||||||
Y_full,
|
Y_full,
|
||||||
[](const double val) {
|
[](const double val) {
|
||||||
@@ -716,9 +805,20 @@ namespace gridfire::solver {
|
|||||||
0.0
|
0.0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_components - 1; i++) {
|
||||||
|
const double weight = relTol * std::abs(y_data[i]) + absTol;
|
||||||
|
if (weight == 0.0) {
|
||||||
|
err_ratios[i] = 0.0; // Avoid division by zero
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double err_ratio = std::abs(y_err_data[i]) / weight;
|
||||||
|
err_ratios[i] = err_ratio;
|
||||||
|
}
|
||||||
|
|
||||||
fourdst::composition::Composition composition(user_data.engine->getNetworkSpecies(), Y_full);
|
fourdst::composition::Composition composition(user_data.engine->getNetworkSpecies(), Y_full);
|
||||||
|
|
||||||
if (err_ratios.empty()) {
|
if (err_ratios.empty()) {
|
||||||
|
std::cout << "Error ratios vector is empty." << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,20 +845,29 @@ namespace gridfire::solver {
|
|||||||
sorted_err_ratios.push_back(err_ratios[idx]);
|
sorted_err_ratios.push_back(err_ratios[idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
std::vector<std::unique_ptr<utils::ColumnBase>> columns;
|
std::vector<std::unique_ptr<utils::ColumnBase>> columns;
|
||||||
columns.push_back(std::make_unique<utils::Column<fourdst::atomic::Species>>("Species", sorted_species));
|
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));
|
columns.push_back(std::make_unique<utils::Column<double>>("Error Ratio", sorted_err_ratios));
|
||||||
|
|
||||||
std::cout << utils::format_table("Species Error Ratios", columns) << std::endl;
|
std::cout << utils::format_table("Species Error Ratios (Log)", columns) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. Call Your Jacobian and Balance Diagnostics ---
|
||||||
if (displayJacobianStiffness) {
|
if (displayJacobianStiffness) {
|
||||||
|
std::cout << "--- Starting Jacobian and Species Balance Diagnostics ---" << std::endl;
|
||||||
diagnostics::inspect_jacobian_stiffness(*user_data.engine, composition, user_data.T9, user_data.rho);
|
diagnostics::inspect_jacobian_stiffness(*user_data.engine, composition, user_data.T9, user_data.rho);
|
||||||
for (const auto& species : sorted_species) {
|
|
||||||
|
// Limit this to the top N species to avoid spamming
|
||||||
|
const size_t num_species_to_inspect = std::min(sorted_species.size(), (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);
|
diagnostics::inspect_species_balance(*user_data.engine, std::string(species.name()), composition, user_data.T9, user_data.rho);
|
||||||
}
|
}
|
||||||
|
std::cout << "--- Finished Jacobian and Species Balance Diagnostics ---" << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,17 +100,7 @@ namespace gridfire::trigger::solver::CVODE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool OffDiagonalTrigger::check(const gridfire::solver::CVODESolverStrategy::TimestepContext &ctx) const {
|
bool OffDiagonalTrigger::check(const gridfire::solver::CVODESolverStrategy::TimestepContext &ctx) const {
|
||||||
for (const auto& rowSpecies : ctx.engine.getNetworkSpecies()) {
|
//TODO : This currently does nothing
|
||||||
for (const auto& colSpecies : ctx.engine.getNetworkSpecies()) {
|
|
||||||
double DRowDCol = std::abs(ctx.engine.getJacobianMatrixEntry(rowSpecies, colSpecies));
|
|
||||||
if (rowSpecies != colSpecies && DRowDCol > m_threshold) {
|
|
||||||
m_hits++;
|
|
||||||
LOG_TRACE_L2(m_logger, "OffDiagonalTrigger triggered at t = {} due to entry ({}, {}) = {}", ctx.t, rowSpecies.name(), colSpecies.name(), DRowDCol);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_misses++;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user