fix(engine_multiscale): resolved a major species index ordering bug

All jacobian calculations were broken because the indexing used to record the AD tape was broken (see not parallel to) the indexing used by the composition object. A fix for this was to sort the network species by mass. However, more generally we should introduce a mechanism to ensure these two indexed sets always remain parallel
This commit is contained in:
2025-10-14 13:37:48 -04:00
parent 408f6d83a2
commit 3b8a0a1f33
10 changed files with 276 additions and 232 deletions

View File

@@ -515,6 +515,7 @@ namespace gridfire {
*/
void rebuild(const fourdst::composition::Composition& comp, const BuildDepthType depth) override;
private:
struct PrecomputedReaction {
// Forward cacheing
@@ -983,7 +984,6 @@ namespace gridfire {
const T mue,
const std::function<std::optional<size_t>(const fourdst::atomic::Species &)>& speciesIDLookup
) const {
// --- Pre-setup (flags to control conditionals in an AD safe / branch aware manner) ---
// ----- Constants for AD safe calculations ---
const T zero = static_cast<T>(0.0);
@@ -992,10 +992,10 @@ namespace gridfire {
const T k_reaction = reaction.calculate_rate(T9, rho, Ye, mue, Y, m_indexToSpeciesMap);
// --- Cound the number of each reactant species to account for species multiplicity ---
std::unordered_map<std::string, int> reactant_counts;
std::unordered_map<fourdst::atomic::Species, int> reactant_counts;
reactant_counts.reserve(reaction.reactants().size());
for (const auto& reactant : reaction.reactants()) {
reactant_counts[std::string(reactant.name())]++;
reactant_counts[reactant]++;
}
const int totalReactants = static_cast<int>(reaction.reactants().size());
@@ -1003,10 +1003,9 @@ namespace gridfire {
auto molar_concentration_product = static_cast<T>(1.0);
// --- Loop through each unique reactant species and calculate the molar concentration for that species then multiply that into the accumulator ---
for (const auto& [species_name, count] : reactant_counts) {
for (const auto& [species, count] : reactant_counts) {
// --- Resolve species to molar abundance ---
// TODO: We need some way to handle the case when a species in the reaction is not part of the composition being tracked
const std::optional<size_t> species_index = speciesIDLookup(m_networkSpeciesMap.at(species_name));
const std::optional<size_t> species_index = speciesIDLookup(species);
if (!species_index.has_value()) {
return static_cast<T>(0.0); // If any reactant is not present, the reaction cannot proceed
}

View File

@@ -19,6 +19,7 @@ namespace gridfire {
*
* @param netIn Input network data containing initial composition, temperature, and density.
* @param engine DynamicEngine used to build and evaluate the reaction network.
* @param ignoredReactionTypes Types of reactions to ignore during priming (e.g., weak reactions).
* @pre netIn.composition defines species and their mass fractions; engine is constructed with a valid network.
* @post engine.networkReactions restored to its initial state; returned report contains primedComposition,
* massFractionChanges for each species, success flag, and status code.
@@ -26,7 +27,8 @@ namespace gridfire {
*/
PrimingReport primeNetwork(
const NetIn& netIn,
DynamicEngine& engine
DynamicEngine& engine,
const std::optional<std::vector<reaction::ReactionType>>& ignoredReactionTypes
);
/**
@@ -40,6 +42,7 @@ namespace gridfire {
* @param composition Current composition providing abundances for all species.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density of the medium.
* @param reactionTypesToIgnore types of reactions to ignore during calculation.
* @pre Y.size() matches engine.getNetworkReactions().size() mapping species order.
* @post Returned rate constant is non-negative.
* @return Sum of absolute stoichiometry-weighted destruction flows for the species.
@@ -49,7 +52,8 @@ namespace gridfire {
const fourdst::atomic::Species& species,
const fourdst::composition::Composition& composition,
double T9,
double rho
double rho, const std::optional<std::vector<reaction::ReactionType>> &
reactionTypesToIgnore
);
/**
@@ -63,6 +67,7 @@ namespace gridfire {
* @param composition Composition object containing current abundances.
* @param T9 Temperature in units of 10^9 K.
* @param rho Density of the medium.
* @param reactionTypesToIgnore types of reactions to ignore during calculation.
* @pre Y.size() matches engine.getNetworkReactions().size() mapping species order.
* @post Returned creation rate is non-negative.
* @return Sum of stoichiometry-weighted creation flows for the species.
@@ -72,6 +77,6 @@ namespace gridfire {
const fourdst::atomic::Species& species,
const fourdst::composition::Composition& composition,
double T9,
double rho
double rho, const std::optional<std::vector<reaction::ReactionType>> &reactionTypesToIgnore
);
}

View File

@@ -193,7 +193,7 @@ namespace gridfire {
* It must be a `GraphEngine` and not a more general `DynamicEngine`
* because this view relies on its specific implementation details.
*/
explicit MultiscalePartitioningEngineView(GraphEngine& baseEngine);
explicit MultiscalePartitioningEngineView(DynamicEngine& baseEngine);
/**
* @brief Gets the list of species in the network.
@@ -977,7 +977,7 @@ namespace gridfire {
/**
* @brief The base engine to which this view delegates calculations.
*/
GraphEngine& m_baseEngine;
DynamicEngine& m_baseEngine;
/**
* @brief The list of identified equilibrium groups.
*/

View File

@@ -28,6 +28,18 @@ namespace gridfire::reaction {
REACLIB,
LOGICAL_REACLIB,
};
static std::unordered_map<ReactionType, std::string> ReactionTypeNames = {
{ReactionType::WEAK, "weak"},
{ReactionType::REACLIB, "reaclib"},
{ReactionType::LOGICAL_REACLIB, "logical_reaclib"},
};
static std::unordered_map<ReactionType, std::string> ReactionPhysicalTypeNames = {
{ReactionType::WEAK, "Weak"},
{ReactionType::REACLIB, "Strong"},
{ReactionType::LOGICAL_REACLIB, "Strong"},
};
/**
* @struct RateCoefficientSet
* @brief Holds the seven coefficients for the REACLIB rate equation.