fix(GridFire): changes based on ref report

This commit is contained in:
2026-04-20 12:37:53 -04:00
parent f4d988fa25
commit 3a22792fd1
17 changed files with 291 additions and 68 deletions

1
.gitignore vendored
View File

@@ -132,3 +132,4 @@ meson-boost-test/
cross/python_includes
*.whl
*.pdf

View File

@@ -152,25 +152,6 @@ the same for the other shared object file (make sure to count the duplicate rpat
We also include a script at `pip_install_mac_patch.sh` which will do this automatically for you.
## Automatic Build and Installation
### Script Build and Installation Instructions
The easiest way to build GridFire is using the `install.sh` or `install-tui.sh`
scripts in the root directory. To use these scripts, simply run:
```bash
./install.sh
# or
./install-tui.sh
```
The regular installation script will select a standard "ideal" set of build
options for you. If you want more control over the build options, you can use
the `install-tui.sh` script, which will provide a text-based user interface to
select the build options you want.
Generally, both are intended to be easy to use and will prompt you
automatically to install any missing dependencies.
### Currently, known good platforms
The installation script has been tested and found to work on clean
installations of the following platforms:
@@ -179,11 +160,6 @@ installations of the following platforms:
- Ubuntu 25.04 (aarch64)
- Ubuntu 22.04 (X86_64)
> **Note:** On Ubuntu 22.04 the user needs to install boost libraries manually
> as the versions in the Ubuntu repositories
> are too old. The installer automatically detects this and will instruct the
> user in how to do this.
## Manual Build Instructions
### Prerequisites
@@ -197,8 +173,6 @@ These only need to be manually installed if the user is not making use of the
- CMake 3.20 or newer
- ninja 1.10.0 or newer
- Python packages: `meson-python>=0.15.0`
- Boost libraries (>= 1.83.0) installed system-wide (or at least findable by
meson with pkg-config)
#### Optional
- dialog (used by the `install.sh` script, not needed if using pip or meson
@@ -206,15 +180,10 @@ These only need to be manually installed if the user is not making use of the
- pip (used by the `install.sh` script or by calling pip directly, not needed
if using meson directly)
> **Note:** Boost is the only external library dependency used by GridFire directly.
> **Note:** Windows is not supported at this time and *there are no plans to
> support it in the future*. Windows users are encouraged to use WSL2 or a
> Linux VM.
> **Note:** If `install-tui.sh` is not able to find a usable version of boost
> it will provide directions to fetch, compile, and install a usable version.
### Install Scripts
GridFire ships with an installer (`install.sh`) which is intended to make the
process of installation both easier and more repeatable.
@@ -447,7 +416,6 @@ likely to be one of adding new `EngineViews`.
| View Name | Purpose | Algorithm / Reference | When to Use |
|----------------------------------|-----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------|
| AdaptiveEngineView | Dynamically culls low-flow species and reactions during runtime | Iterative flux thresholding to remove reactions below a flow threshold | Large networks to reduce computational cost |
| DefinedEngineView | Restricts the network to a user-specified subset of species and reactions | Static network masking based on user-provided species/reaction lists | Targeted pathway studies or code-to-code comparisons |
| FileDefinedEngineView | Load a defined engine view from a file using some parser | Same as DefinedEngineView but loads from a file | Same as DefinedEngineView |
| MultiscalePartitioningEngineView | Partitions the network into fast and slow subsets based on reaction timescales | Network partitioning following Hix & Thielemann Silicon Burning I & II (DOI:10.1086/177016,10.1086/306692) | Stiff, multi-scale networks requiring tailored integration |
@@ -523,7 +491,7 @@ A `NetOut` struct contains
- The total specific energy lost to neutrinos while evolving to `tMax` (`NetOut::total_neutrino_loss`) [erg/g]
- The total flux of neutrinos while evolving to `tMax` (`NetOut::total_neutrino_flux`)
### CVODESolverStrategy
### PointSolver
We use the CVODE module from [SUNDIALS](https://computing.llnl.gov/projects/sundials/cvode) as our primary numerical
solver. Specifically we use the BDF linear multistep method from that which includes advanced adaptive timestepping.

View File

@@ -8,7 +8,8 @@ cvode_cmake_options.add_cmake_defines({
'BUILD_STATIC_LIBS' : 'ON',
'EXAMPLES_ENABLE_C' : 'OFF',
'CMAKE_POSITION_INDEPENDENT_CODE': true,
'CMAKE_PLATFORM_NO_VERSIONED_SONAME': 'ON'
'CMAKE_PLATFORM_NO_VERSIONED_SONAME': 'ON',
'SUNDIALS_LOGGING_LEVEL': 1
})

View File

@@ -9,7 +9,8 @@ kinsol_cmake_options.add_cmake_defines({
'BUILD_STATIC_LIBS' : 'ON',
'EXAMPLES_ENABLE_C' : 'OFF',
'CMAKE_POSITION_INDEPENDENT_CODE': true,
'CMAKE_PLATFORM_NO_VERSIONED_SONAME': 'ON'
'CMAKE_PLATFORM_NO_VERSIONED_SONAME': 'ON',
'SUNDIALS_LOGGING_LEVEL': 1
})
kinsol_cmake_options.add_cmake_defines({

View File

@@ -70,6 +70,7 @@ struct AdaptiveEngineViewScratchPad final : AbstractScratchPad {
/// @brief Flag indicating whether the scratchpad has been initialized.
bool has_initialized = false;
/// @brief Vector of species currently active in the adaptive network.
std::vector<fourdst::atomic::Species> active_species;

View File

@@ -103,6 +103,9 @@ struct MultiscalePartitioningEngineViewScratchPad final : AbstractScratchPad {
/// @brief Flag indicating whether the scratchpad has been initialized.
bool has_initialized = false;
/// @breif User configurable parameter to control flux coupling threshold used
double flux_coupling_threshold = 5.0;
/// @brief Vector of QSE groups representing equilibrium clusters.
std::vector<QSEGroup> qse_groups;

View File

@@ -1,3 +1,4 @@
#pragma once
#include "gridfire/io/generative/python.h"
#include "gridfire/io/generative/mesa.h"

View File

@@ -0,0 +1,17 @@
#pragma once
#include "fourdst/atomic/atomicSpecies.h"
#include "gridfire/reaction/reaction.h"
#include "gridfire/engine/engine_abstract.h"
#include <format>
namespace gridfire::io::generative {
std::string get_mesa_iso_name(const fourdst::atomic::Species& species);
bool is_proton(const fourdst::atomic::Species& species);
bool is_alpha(const fourdst::atomic::Species& species);
bool is_neutron(const fourdst::atomic::Species& species);
std::string get_mesa_reaction_name(const reaction::Reaction& reaction);
std::string export_engine_to_mesa_net(const engine::DynamicEngine& engine, engine::scratch::StateBlob& ctx, bool skip_weak);
}

View File

@@ -37,5 +37,5 @@ configure_file(
output : 'config.h',
configuration : conf_data,
install : do_install_version_file,
install_dir : get_option('includedir') / '/gridfire/utils'
install_dir : get_option('includedir') / 'gridfire/utils'
)

View File

@@ -1337,7 +1337,8 @@ namespace gridfire::engine {
const double rho,
const QSEGroup &group
) const {
constexpr double FLUX_RATIO_THRESHOLD = 5;
auto* state = scratch::get_state<scratch::MultiscalePartitioningEngineViewScratchPad, true>(ctx);
double FLUX_RATIO_THRESHOLD = state->flux_coupling_threshold;
const std::unordered_set<Species> algebraic_group_members(
group.algebraic_species.begin(),
@@ -1484,8 +1485,8 @@ namespace gridfire::engine {
const double diff_total = std::abs(total_prod - total_dest);
bool total_balanced = (mean_total > 0) && ((diff_total / mean_total) < 0.05);
// Check 2: Charged-Particle Balance (The "Neutron-Exclusion" Check)
// Only valid if there IS charged flow (avoid 0/0 success)
// Check 2: Charged-Particle Balance
// Only valid if there IS charged flow
const double mean_charged = (charged_prod + charged_dest) / 2.0;
const double diff_charged = std::abs(charged_prod - charged_dest);
bool charged_balanced = (mean_charged > 0) && ((diff_charged / mean_charged) < 0.05);

View File

@@ -0,0 +1,155 @@
#include "gridfire/io/generative/mesa.h"
#include "gridfire/engine/engine_abstract.h"
#include "gridfire/reaction/reaction.h"
#include "fourdst/atomic/atomicSpecies.h"
#include "gridfire/utils/config.h"
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
#include <chrono>
#include <cctype>
namespace gridfire::io::generative {
std::string get_mesa_iso_name(const fourdst::atomic::Species& species) {
auto name = std::string(species.name());
std::ranges::transform(name, name.begin(), ::tolower);
name.erase(std::ranges::remove(name, '-').begin(), name.end());
if (name == "p") return "h1";
if (name == "n" || name == "n1") return "neut";
if (name == "d") return "h2";
if (name == "t") return "h3";
if (name == "a") return "he4";
return name;
}
bool is_proton(const fourdst::atomic::Species& s) { return get_mesa_iso_name(s) == "h1"; }
bool is_alpha(const fourdst::atomic::Species& s) { return get_mesa_iso_name(s) == "he4"; }
bool is_neutron(const fourdst::atomic::Species& s) { return get_mesa_iso_name(s) == "neut"; }
std::string get_mesa_reaction_name(const reaction::Reaction& reaction) {
std::vector<fourdst::atomic::Species> react_sorted = reaction.reactants();
std::vector<fourdst::atomic::Species> prod_sorted = reaction.products();
auto sort_species = [](std::vector<fourdst::atomic::Species>& list) {
std::ranges::sort(list, [](const auto& a, const auto& b) {
if (a.z() != b.z()) return a.z() < b.z();
return a.a() < b.a();
});
};
sort_species(react_sorted);
sort_species(prod_sorted);
if (react_sorted.size() == 1 && prod_sorted.size() == 1) {
if (reaction.type() == reaction::ReactionType::WEAK ||
reaction.type() == reaction::ReactionType::REACLIB_WEAK ||
reaction.type() == reaction::ReactionType::LOGICAL_REACLIB_WEAK) {
return "r_" + get_mesa_iso_name(react_sorted[0]) + "_wk_" + get_mesa_iso_name(prod_sorted[0]);
}
}
if (react_sorted.size() == 2 && prod_sorted.size() == 1) {
std::string x, cap;
if (is_proton(react_sorted[0]) || is_proton(react_sorted[1])) {
cap = "pg";
x = is_proton(react_sorted[0]) ? get_mesa_iso_name(react_sorted[1]) : get_mesa_iso_name(react_sorted[0]);
}
else if (is_alpha(react_sorted[0]) || is_alpha(react_sorted[1])) {
cap = "ag";
x = is_alpha(react_sorted[0]) ? get_mesa_iso_name(react_sorted[1]) : get_mesa_iso_name(react_sorted[0]);
}
else if (is_neutron(react_sorted[0]) || is_neutron(react_sorted[1])) {
cap = "ng";
x = is_neutron(react_sorted[0]) ? get_mesa_iso_name(react_sorted[1]) : get_mesa_iso_name(react_sorted[0]);
}
if (!cap.empty()) return "r_" + x + "_" + cap + "_" + get_mesa_iso_name(prod_sorted[0]);
}
if (react_sorted.size() == 1 && prod_sorted.size() == 2) {
std::string x, em;
if (is_proton(prod_sorted[0]) || is_proton(prod_sorted[1])) {
em = "gp";
x = is_proton(prod_sorted[0]) ? get_mesa_iso_name(prod_sorted[1]) : get_mesa_iso_name(prod_sorted[0]);
}
else if (is_alpha(prod_sorted[0]) || is_alpha(prod_sorted[1])) {
em = "ga";
x = is_alpha(prod_sorted[0]) ? get_mesa_iso_name(prod_sorted[1]) : get_mesa_iso_name(prod_sorted[0]);
}
else if (is_neutron(prod_sorted[0]) || is_neutron(prod_sorted[1])) {
em = "gn";
x = is_neutron(prod_sorted[0]) ? get_mesa_iso_name(prod_sorted[1]) : get_mesa_iso_name(prod_sorted[0]);
}
if (!em.empty()) return "r_" + get_mesa_iso_name(react_sorted[0]) + "_" + em + "_" + x;
}
if (react_sorted.size() == 2 && prod_sorted.size() == 2) {
int r_p = -1, r_a = -1, r_n = -1;
int p_p = -1, p_a = -1, p_n = -1;
for(int i=0; i<2; ++i) {
if(is_proton(react_sorted[i])) r_p = i;
if(is_alpha(react_sorted[i])) r_a = i;
if(is_neutron(react_sorted[i])) r_n = i;
if(is_proton(prod_sorted[i])) p_p = i;
if(is_alpha(prod_sorted[i])) p_a = i;
if(is_neutron(prod_sorted[i])) p_n = i;
}
std::string x, y, exc;
if (r_a != -1 && p_p != -1) { exc = "ap"; x = get_mesa_iso_name(react_sorted[1-r_a]); y = get_mesa_iso_name(prod_sorted[1-p_p]); }
else if (r_p != -1 && p_a != -1) { exc = "pa"; x = get_mesa_iso_name(react_sorted[1-r_p]); y = get_mesa_iso_name(prod_sorted[1-p_a]); }
else if (r_n != -1 && p_p != -1) { exc = "np"; x = get_mesa_iso_name(react_sorted[1-r_n]); y = get_mesa_iso_name(prod_sorted[1-p_p]); }
else if (r_p != -1 && p_n != -1) { exc = "pn"; x = get_mesa_iso_name(react_sorted[1-r_p]); y = get_mesa_iso_name(prod_sorted[1-p_n]); }
else if (r_n != -1 && p_a != -1) { exc = "na"; x = get_mesa_iso_name(react_sorted[1-r_n]); y = get_mesa_iso_name(prod_sorted[1-p_a]); }
else if (r_a != -1 && p_n != -1) { exc = "an"; x = get_mesa_iso_name(react_sorted[1-r_a]); y = get_mesa_iso_name(prod_sorted[1-p_n]); }
if (!exc.empty()) return "r_" + x + "_" + exc + "_" + y;
}
std::string fallback = "r";
for (const auto& s : react_sorted) fallback += "_" + get_mesa_iso_name(s);
fallback += "_to";
for (const auto& s : prod_sorted) fallback += "_" + get_mesa_iso_name(s);
return fallback;
}
std::string export_engine_to_mesa_net(const engine::DynamicEngine& engine, engine::scratch::StateBlob& ctx, bool skip_weak) {
std::stringstream ss;
ss << "! Auto-generated MESA .net file from GridFire\n";
ss << "! Generated by GridFire version: " << version().toString() << "\n";
ss << "! Generated on " << std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) << "\n\n";
ss << "add_isos(\n";
for (const auto& species : engine.getNetworkSpecies(ctx)) {
ss << " " << get_mesa_iso_name(species) << "\n";
}
ss << ")\n\n";
ss << "add_reactions(\n";
const auto& reactions = engine.getNetworkReactions(ctx);
for (const auto& reaction_ptr : reactions) {
if (skip_weak && (reaction_ptr->type() == reaction::ReactionType::WEAK ||
reaction_ptr->type() == reaction::ReactionType::REACLIB_WEAK ||
reaction_ptr->type() == reaction::ReactionType::LOGICAL_REACLIB_WEAK)) {
continue;
}
ss << " " << get_mesa_reaction_name(*reaction_ptr) << "\n";
}
ss << ")\n";
return ss.str();
}
}

View File

@@ -61,14 +61,7 @@ namespace gridfire::policy {
std::make_unique<engine::GraphEngine>(m_initializing_composition, *m_partition_function, engine::NetworkBuildDepth::ThirdOrder, engine::NetworkConstructionFlags::DEFAULT)
);
m_network_stack.emplace_back(
std::make_unique<engine::MultiscalePartitioningEngineView>(*m_network_stack.back().get())
);
m_network_stack.emplace_back(
std::make_unique<engine::AdaptiveEngineView>(*m_network_stack.back().get())
);
std::unique_ptr<engine::scratch::StateBlob> scratch_blob = get_stack_scratch_blob();
std::unique_ptr<engine::scratch::StateBlob> scratch_blob = m_network_stack.back()->constructStateBlob(nullptr);
m_status = NetworkPolicyStatus::INITIALIZED_UNVERIFIED;
m_status = check_status(*scratch_blob);
@@ -110,8 +103,6 @@ namespace gridfire::policy {
std::vector<engine::EngineTypes> MainSequencePolicy::get_engine_types_stack() const {
return {
engine::EngineTypes::GRAPH_ENGINE,
engine::EngineTypes::MULTISCALE_PARTITIONING_ENGINE_VIEW,
engine::EngineTypes::ADAPTIVE_ENGINE_VIEW
};
}
@@ -125,32 +116,14 @@ namespace gridfire::policy {
}
auto blob = std::make_unique<engine::scratch::StateBlob>();
blob->enroll<engine::scratch::GraphEngineScratchPad>();
blob->enroll<engine::scratch::AdaptiveEngineViewScratchPad>();
blob->enroll<engine::scratch::MultiscalePartitioningEngineViewScratchPad>();
const engine::GraphEngine* graph_engine = dynamic_cast<engine::GraphEngine*>(m_network_stack.front().get());
if (!graph_engine) {
throw exceptions::PolicyError("Cannot get stack scratch blob from MainSequencePolicy: The base engine is not a GraphEngine. This indicates a serious internal inconsistency and should be reported to the GridFire developers, thank you.");
}
const engine::MultiscalePartitioningEngineView* multiscale_engine = dynamic_cast<engine::MultiscalePartitioningEngineView*>(m_network_stack[1].get());
if (!multiscale_engine) {
throw exceptions::PolicyError("Cannot get stack scratch blob from MainSequencePolicy: The middle engine is not a MultiscalePartitioningEngineView. This indicates a serious internal inconsistency and should be reported to the GridFire developers, thank you.");
}
const engine::AdaptiveEngineView* adaptive_engine = dynamic_cast<engine::AdaptiveEngineView*>(m_network_stack.back().get());
if (!adaptive_engine) {
throw exceptions::PolicyError("Cannot get stack scratch blob from MainSequencePolicy: The top engine is not an AdaptiveEngineView. This indicates a serious internal inconsistency and should be reported to the GridFire developers, thank you.");
}
auto* graph_engine_state = engine::scratch::get_state<engine::scratch::GraphEngineScratchPad, false>(*blob);
graph_engine_state->initialize(*graph_engine);
auto* multiscale_engine_state = engine::scratch::get_state<engine::scratch::MultiscalePartitioningEngineViewScratchPad, false>(*blob);
multiscale_engine_state->initialize();
auto* adaptive_engine_state = engine::scratch::get_state<engine::scratch::AdaptiveEngineViewScratchPad, false>(*blob);
adaptive_engine_state->initialize(*adaptive_engine);
return blob;
}

View File

@@ -17,6 +17,7 @@ gridfire_sources = files(
'lib/reaction/weak/weak_interpolator.cpp',
'lib/io/network_file.cpp',
'lib/io/generative/python.cpp',
'lib/io/generative/mesa.cpp',
'lib/solver/strategies/PointSolver.cpp',
'lib/solver/strategies/GridSolver.cpp',
'lib/solver/strategies/triggers/engine_partitioning_trigger.cpp',

View File

@@ -0,0 +1,99 @@
// ReSharper disable CppUnusedIncludeDirective
#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>
#include <format>
#include <cppad/utility/thread_alloc.hpp> // Required for parallel_setup
#include "fourdst/logging/logging.h"
#include "fourdst/atomic/species.h"
#include "fourdst/composition/utils.h"
#include "quill/Logger.h"
#include "quill/Backend.h"
#include "CLI/CLI.hpp"
#include <clocale>
#include "gridfire/gridfire.h"
#include "fourdst/composition/composition.h"
#include "gridfire/utils/gf_omp.h"
#include <atomic>
#include <new>
#include <cstdlib>
static std::terminate_handler g_previousHandler = nullptr;
void quill_terminate_handler();
gridfire::NetIn init(const double temp, const double rho, const double tMax) {
std::setlocale(LC_ALL, "");
g_previousHandler = std::set_terminate(quill_terminate_handler);
quill::Logger* logger = fourdst::logging::LogManager::getInstance().getLogger("log");
logger->set_log_level(quill::LogLevel::Info);
using namespace gridfire;
const std::vector<double> X = {0.7081145999999999, 2.94e-5, 0.276, 0.003, 0.0011, 9.62e-3, 1.62e-3, 5.16e-4};
const std::vector<std::string> symbols = {"H-1", "He-3", "He-4", "C-12", "N-14", "O-16", "Ne-20", "Mg-24"};
const fourdst::composition::Composition composition = fourdst::composition::buildCompositionFromMassFractions(symbols, X);
NetIn netIn;
netIn.composition = composition;
netIn.temperature = temp;
netIn.density = rho;
netIn.energy = 0;
netIn.tMax = tMax;
netIn.dt0 = 1e-12;
return netIn;
}
void quill_terminate_handler()
{
quill::Backend::stop();
if (g_previousHandler)
g_previousHandler();
else
std::abort();
}
int main(int argc, char* argv[]) {
using namespace gridfire;
double temp = 1.5e7;
double rho = 1.6e2;
double tMax = 3e17;
double coupling_ratio = 5.0;
std::string output_filename = "coupling.dat";
CLI::App app("GridFire Test Coupling");
app.add_option("--temperature", temp, "Temperature in degrees")->default_val(std::format("{:5.2E}", temp));
app.add_option("--density", rho, "Density in Kg")->default_val(std::format("{:5.2E}", rho));
app.add_option("--tmax", tMax, "Maximum time in seconds")->default_val(std::format("{:5.2E}", tMax));
app.add_option("--coupling_ratio", coupling_ratio, "Coupling ratio for multiscale partitioning")->default_val(std::format("{:.2f}", coupling_ratio));
app.add_option("--output", output_filename, "Output filename for intermediate results")->default_val("coupling.dat");
CLI11_PARSE(app, argc, argv);
const NetIn netIn = init(temp, rho, tMax);
auto base_engine = std::make_unique<engine::GraphEngine>(netIn.composition, 3);
auto base_blob = base_engine->constructStateBlob();
auto qse_engine = std::make_unique<engine::MultiscalePartitioningEngineView>(*base_engine);
auto blob = qse_engine->constructStateBlob(base_blob.get());
auto* state = engine::scratch::get_state<engine::scratch::MultiscalePartitioningEngineViewScratchPad, true>(*blob);
const solver::PointSolver localSolver(*base_engine);
solver::PointSolverContext solverCtx(*base_blob);
auto result = localSolver.evaluate(solverCtx, netIn, false, false);
}

View File

@@ -5,4 +5,5 @@
# Subdirectories for unit and integration tests
subdir('graphnet_sandbox')
subdir('flux_coupling')
subdir('extern')

View File