From 75a88b97207e31ec8695e619f5392ccbcc6af3ab Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Fri, 28 Nov 2025 11:56:41 -0500 Subject: [PATCH 01/14] fix(unity): GridFire supports unity builds GridFire can now support unity builds. This is useful for python bindings. The main limitation is that openssl (a dependency of libplugin) does not support unity builds. Therefore when building in a unity build libplugin must be disabled. This is done with the -Dunity-safe=ture flag at setup time --- Doxyfile | 2 +- build-config/fourdst/meson.build | 19 ++++++++++++++++--- meson.build | 2 +- meson_options.txt | 1 + pyproject.toml | 2 +- src/meson.build | 5 ++++- src/python/gridfire/__init__.py | 2 +- subprojects/fourdst.wrap | 2 +- 8 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Doxyfile b/Doxyfile index 89e00480..4be53f7c 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = GridFire # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = v0.7.3_rc2 +PROJECT_NUMBER = v0.7.4_rc2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a diff --git a/build-config/fourdst/meson.build b/build-config/fourdst/meson.build index 124ecdcf..04a160c2 100644 --- a/build-config/fourdst/meson.build +++ b/build-config/fourdst/meson.build @@ -1,9 +1,17 @@ # bring in all of the fourdst utility repositories +fourdst_build_lib_all = true +if get_option('unity-safe') + fourdst_build_lib_all=false +endif + fourdst_sp = subproject('fourdst', default_options: ['build-tests=' + get_option('build-tests').to_string(), - 'build-python=' + get_option('build-python').to_string() + 'build-python=' + get_option('build-python').to_string(), + 'build-lib-all=' + fourdst_build_lib_all.to_string(), + 'pkg-config=' + get_option('pkg-config').to_string(), + 'build-lib-log=true' ] ) @@ -11,10 +19,15 @@ composition_dep = fourdst_sp.get_variable('composition_dep') log_dep = fourdst_sp.get_variable('log_dep') const_dep = fourdst_sp.get_variable('const_dep') config_dep = fourdst_sp.get_variable('config_dep') -plugin_dep = fourdst_sp.get_variable('plugin_dep') +if not get_option('unity-safe') + plugin_dep = fourdst_sp.get_variable('plugin_dep') +endif libcomposition = fourdst_sp.get_variable('libcomposition') libconst = fourdst_sp.get_variable('libconst') libconfig = fourdst_sp.get_variable('libconfig') liblogging = fourdst_sp.get_variable('liblogging') -libplugin = fourdst_sp.get_variable('libplugin') \ No newline at end of file + +if not get_option('unity-safe') + libplugin = fourdst_sp.get_variable('libplugin') +endif \ No newline at end of file diff --git a/meson.build b/meson.build index 7dd5d4cc..5097959f 100644 --- a/meson.build +++ b/meson.build @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # *********************************************************************** # -project('GridFire', ['c', 'cpp', 'fortran'], version: 'v0.7.3_rc2', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') +project('GridFire', ['c', 'cpp', 'fortran'], version: 'v0.7.4_rc2', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') # Add default visibility for all C++ targets add_project_arguments('-fvisibility=default', language: 'cpp') diff --git a/meson_options.txt b/meson_options.txt index 63a752d5..ec80a866 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,3 +4,4 @@ option('build-python', type: 'boolean', value: false, description: 'build the py option('build-tests', type: 'boolean', value: true, description: 'build the test suite') option('build-fortran', type: 'boolean', value: false, description: 'build fortran module support') option('unsafe-fortran', type: 'boolean', value: false, description: 'Allow untested fortran compilers (compilers other than gfortran)') +option('unity-safe', type: 'boolean', value: false, description: 'Enable safe unity builds for better compatibility across different compilers and platforms') diff --git a/pyproject.toml b/pyproject.toml index 6a7a6a2e..bedbcdc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "mesonpy" [project] name = "gridfire" # Choose your Python package name -version = "0.7.3_rc2" # Your project's version +version = "0.7.4_rc2" # Your project's version description = "Python interface to the GridFire nuclear network code" readme = "README.md" license = { file = "LICENSE.txt" } # Reference your license file [cite: 2] diff --git a/src/meson.build b/src/meson.build index 84495c35..a34ad24c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -38,11 +38,14 @@ gridfire_build_dependencies = [ log_dep, xxhash_dep, eigen_dep, - plugin_dep, sundials_dep, json_dep, ] +if not get_option('unity-safe') + gridfire_build_dependencies += [plugin_dep] +endif + # Define the libnetwork library so it can be linked against by other parts of the build system libgridfire = library('gridfire', gridfire_sources, diff --git a/src/python/gridfire/__init__.py b/src/python/gridfire/__init__.py index 709419c1..65d6e5e5 100644 --- a/src/python/gridfire/__init__.py +++ b/src/python/gridfire/__init__.py @@ -16,5 +16,5 @@ sys.modules['gridfire.io'] = io __all__ = ['type', 'utils', 'engine', 'solver', 'exceptions', 'partition', 'reaction', 'screening', 'io', 'policy'] -__version__ = "v0.7.3_rc2" +__version__ = "v0.7.4_rc2" diff --git a/subprojects/fourdst.wrap b/subprojects/fourdst.wrap index e718833b..140d8639 100644 --- a/subprojects/fourdst.wrap +++ b/subprojects/fourdst.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://github.com/4D-STAR/fourdst -revision = v0.9.10 +revision = v0.9.11 depth = 1 From a0dcfe43326464b2dadae0f3935be2769d421001 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sat, 29 Nov 2025 07:15:06 -0500 Subject: [PATCH 02/14] build(python): added fvisibility=hidden for python builds In order to avoid massive binaries uploaded to pypi we have worked to reduce the number of symbols included in binaries --- meson.build | 7 +++++-- src/meson.build | 14 +++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/meson.build b/meson.build index 5097959f..6ed49fea 100644 --- a/meson.build +++ b/meson.build @@ -20,8 +20,11 @@ # *********************************************************************** # project('GridFire', ['c', 'cpp', 'fortran'], version: 'v0.7.4_rc2', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') -# Add default visibility for all C++ targets -add_project_arguments('-fvisibility=default', language: 'cpp') +if get_option('build-python') + add_project_arguments('-fvisibility=hidden', language: 'cpp') +else + add_project_arguments('-fvisibility=default', language: 'cpp') +endif message('Found CXX compiler: ' + meson.get_compiler('cpp').get_id()) message('Found FORTRAN compiler: ' + meson.get_compiler('fortran').get_id()) diff --git a/src/meson.build b/src/meson.build index a34ad24c..aad8ffa2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -64,10 +64,10 @@ install_subdir('include/gridfire', install_dir: get_option('includedir')) message('Configuring C API...') subdir('extern') - -if get_option('build-python') - message('Configuring Python bindings...') - subdir('python') -else - message('Skipping Python bindings...') -endif +# +#if get_option('build-python') +# message('Configuring Python bindings...') +# subdir('python') +#else +# message('Skipping Python bindings...') +#endif From 4fe242a5efee23982e78ec8d9e87bbd376b6e111 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 30 Nov 2025 10:16:44 -0500 Subject: [PATCH 03/14] build(meson): added checks for header compatibility --- meson.build | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/meson.build b/meson.build index 6ed49fea..0ec200b3 100644 --- a/meson.build +++ b/meson.build @@ -31,20 +31,20 @@ message('Found FORTRAN compiler: ' + meson.get_compiler('fortran').get_id()) message('C++ standard set to: ' + get_option('cpp_std')) message('Fortran standard set to: ' + get_option('fortran_std')) +cppc = meson.get_compiler('cpp') -if meson.get_compiler('cpp').get_id() == 'clang' - # We disable these because of CppAD + +if cppc.get_id() == 'clang' message('disabling bitwise-instead-of-logical warnings for clang') add_project_arguments('-Wno-bitwise-instead-of-logical', language: 'cpp') endif -if meson.get_compiler('cpp').get_id() == 'gcc' - # We disable these because of boost notes about abi changes from C++10 -> C++17 make the build too noisey +if cppc.get_id() == 'gcc' message('disabling psabi warnings for gcc') add_project_arguments('-Wno-psabi', language: 'cpp') - if (meson.get_compiler('cpp').version().version_compare('<14.0')) - error('g++ version must be at least 14.0, found ' + meson.get_compiler('cpp').version()) + if (cppc.version().version_compare('<14.0')) + error('g++ version must be at least 14.0, found ' + cppc.version()) endif endif @@ -58,12 +58,20 @@ if (build_fortran) error('The only supported fortran compiler for GridFire is gfortran (version >= 14.0), found ' + fc + '. GridFire has not been tested with any other compilers. You can disable this check with the -Dunsafe-fortran=true flag to try other compilers') endif endif - if (meson.get_compiler('fortran').version().version_compare('<14.0')) - error('gfortran version must be at least 14.0, found ' + meson.get_compiler('fortran').version()) + if (fc.version().version_compare('<14.0')) + error('gfortran version must be at least 14.0, found ' + fc.version()) endif endif +if not cppc.has_header('print') + error('C++ standard library header not found. Please ensure your compiler and standard library supports C++23. We have already validated your compiler version so this is likely an issue with your standard library installation.') +endif +if not cppc.has_header('format') + error('C++ standard library header not found. Please ensure your compiler and standard library supports C++23. We have already validated your compiler version so this is likely an issue with your standard library installation.') +endif + + # For Eigen From dac09ae24ce6ef58b809ddf74b84bd4df655d822 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 30 Nov 2025 10:19:56 -0500 Subject: [PATCH 04/14] build(fortran): added check for fortran fortran tests only build when fortran build option is enabled --- tests/extern/meson.build | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/extern/meson.build b/tests/extern/meson.build index 4904143f..9ec12a00 100644 --- a/tests/extern/meson.build +++ b/tests/extern/meson.build @@ -1,2 +1,5 @@ subdir('C') -subdir('fortran') \ No newline at end of file + +if get_option('build-fortran') + subdir('fortran') +endif From b06b622c0671b8495e85e624f33798cf040e94ff Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Mon, 1 Dec 2025 09:59:22 -0500 Subject: [PATCH 05/14] build(cross): working on cross compilation We want to be able to build all targets on one machine, began implimenting cross compilation --- build-config/sundials/cvode/meson.build | 17 ++++++++++------- build-config/sundials/kinsol/meson.build | 9 ++++++--- cross/macos_arm64.ini | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 cross/macos_arm64.ini diff --git a/build-config/sundials/cvode/meson.build b/build-config/sundials/cvode/meson.build index cc382cb8..b46397f6 100644 --- a/build-config/sundials/cvode/meson.build +++ b/build-config/sundials/cvode/meson.build @@ -4,8 +4,11 @@ cvode_cmake_options = cmake.subproject_options() cvode_cmake_options.add_cmake_defines({ 'CMAKE_CXX_FLAGS' : '-Wno-deprecated-declarations', 'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations', - 'BUILD_SHARED_LIBS' : 'ON', - 'BUILD_STATIC_LIBS' : 'OFF', + 'BUILD_SHARED_LIBS' : 'OFF', + 'BUILD_STATIC_LIBS' : 'ON', + 'CMAKE_BUILD_WITH_INSTALL_RPATH': 'ON', + 'EXAMPLES_ENABLE_C': 'OFF', + 'CMAKE_POSITION_INDEPENDENT_CODE': 'ON' }) @@ -20,19 +23,19 @@ cvode_sp = cmake.subproject( ) # For the core SUNDIALS library (SUNContext, etc.) -sundials_core_dep = cvode_sp.dependency('sundials_core_shared') +sundials_core_dep = cvode_sp.dependency('sundials_core_static') # For the CVODE integrator library -sundials_cvode_dep = cvode_sp.dependency('sundials_cvode_shared') +sundials_cvode_dep = cvode_sp.dependency('sundials_cvode_static') # For the serial NVector library -sundials_nvecserial_dep = cvode_sp.dependency('sundials_nvecserial_shared') +sundials_nvecserial_dep = cvode_sp.dependency('sundials_nvecserial_static') # For the dense matrix library -sundials_sunmatrixdense_dep = cvode_sp.dependency('sundials_sunmatrixdense_shared') +sundials_sunmatrixdense_dep = cvode_sp.dependency('sundials_sunmatrixdense_static') # For the dense linear solver library -sundials_sunlinsoldense_dep = cvode_sp.dependency('sundials_sunlinsoldense_shared') +sundials_sunlinsoldense_dep = cvode_sp.dependency('sundials_sunlinsoldense_static') cvode_dep = declare_dependency( dependencies: [ diff --git a/build-config/sundials/kinsol/meson.build b/build-config/sundials/kinsol/meson.build index 3c780841..9014a5f6 100644 --- a/build-config/sundials/kinsol/meson.build +++ b/build-config/sundials/kinsol/meson.build @@ -5,8 +5,11 @@ kinsol_cmake_options = cmake.subproject_options() kinsol_cmake_options.add_cmake_defines({ 'CMAKE_CXX_FLAGS' : '-Wno-deprecated-declarations', 'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations', - 'BUILD_SHARED_LIBS' : 'ON', - 'BUILD_STATIC_LIBS' : 'OFF', + 'BUILD_SHARED_LIBS' : 'OFF', + 'BUILD_STATIC_LIBS' : 'ON', + 'CMAKE_BUILD_WITH_INSTALL_RPATH': 'ON', + 'EXAMPLES_ENABLE_C' : 'OFF', + 'CMAKE_POSITION_INDEPENDENT_CODE': 'ON' }) kinsol_cmake_options.add_cmake_defines({ @@ -19,7 +22,7 @@ kinsol_sp = cmake.subproject( options: kinsol_cmake_options, ) -sundials_kinsol_shared = kinsol_sp.dependency('sundials_kinsol_shared') +sundials_kinsol_shared = kinsol_sp.dependency('sundials_kinsol_static') kinsol_dep = declare_dependency( dependencies: [ diff --git a/cross/macos_arm64.ini b/cross/macos_arm64.ini new file mode 100644 index 00000000..33ea5f99 --- /dev/null +++ b/cross/macos_arm64.ini @@ -0,0 +1,16 @@ +[binaries] +c = 'arm64-apple-darwin25-clang' +cpp = 'arm64-apple-darwin25-clang++' +ar = 'arm64-apple-darwin25-ar' +strip = 'arm64-apple-darwin25-strip' +pkg-config = 'pkg-config' + +[host-machine] +system = 'darwin' +cpu_family = 'aarch64' +cpi = 'arm64' +endian = 'little' + +[built-in options] +c_args = ['-mmacosx-version-min=15.0'] +cpp_args = ['-mmacos-version-min=15.0'] From e260c7b02cde51dce0cc497f1703bae839a339ce Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Mon, 1 Dec 2025 09:59:43 -0500 Subject: [PATCH 06/14] fix(build): fortran Fortran language only added if build-fortran enabled --- meson.build | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 0ec200b3..f68c40c5 100644 --- a/meson.build +++ b/meson.build @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # *********************************************************************** # -project('GridFire', ['c', 'cpp', 'fortran'], version: 'v0.7.4_rc2', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') +project('GridFire', ['c', 'cpp'], version: 'v0.7.4_rc2', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') if get_option('build-python') add_project_arguments('-fvisibility=hidden', language: 'cpp') @@ -27,9 +27,7 @@ else endif message('Found CXX compiler: ' + meson.get_compiler('cpp').get_id()) -message('Found FORTRAN compiler: ' + meson.get_compiler('fortran').get_id()) message('C++ standard set to: ' + get_option('cpp_std')) -message('Fortran standard set to: ' + get_option('fortran_std')) cppc = meson.get_compiler('cpp') @@ -51,6 +49,8 @@ endif build_fortran = get_option('build-fortran') if (build_fortran) add_languages('fortran', native: true) + message('Found FORTRAN compiler: ' + meson.get_compiler('fortran').get_id()) + message('Fortran standard set to: ' + get_option('fortran_std')) message('Building fortran module (gridfire_mod.mod)') fc = meson.get_compiler('fortran') if not get_option('unsafe-fortran') From e0a05bbd1a8cb93942e6c0d2507dba8b18885ab2 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Mon, 1 Dec 2025 13:28:25 -0500 Subject: [PATCH 07/14] build(cross): macOS cross compilation macos cross compilation now works. macos binaries can be compiled on linux with osxcross installed and built --- .gitignore | 2 + build-config/fourdst/meson.build | 2 +- build-config/meson.build | 3 +- build-config/python/meson.build | 16 +++ build-config/sundials/cvode/meson.build | 66 ++++++++---- build-config/sundials/kinsol/meson.build | 33 ++++-- build-config/sundials/meson.build | 2 +- build-python/meson.build | 34 ++++-- cross/macos_arm64.ini | 7 +- meson.build | 6 ++ meson_options.txt | 1 + src/meson.build | 1 + tests/graphnet_sandbox/main.cpp | 1 - tests/graphnet_sandbox/meson.build | 2 +- utils/python/fetch_header_files.sh | 132 +++++++++++++++++++++++ 15 files changed, 264 insertions(+), 44 deletions(-) create mode 100644 build-config/python/meson.build create mode 100755 utils/python/fetch_header_files.sh diff --git a/.gitignore b/.gitignore index 1027882b..c20d7ec2 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,5 @@ meson-boost-test/ *.json *.xml *_pynucastro_network.py + +cross/python_includes diff --git a/build-config/fourdst/meson.build b/build-config/fourdst/meson.build index 04a160c2..c6696210 100644 --- a/build-config/fourdst/meson.build +++ b/build-config/fourdst/meson.build @@ -30,4 +30,4 @@ liblogging = fourdst_sp.get_variable('liblogging') if not get_option('unity-safe') libplugin = fourdst_sp.get_variable('libplugin') -endif \ No newline at end of file +endif diff --git a/build-config/meson.build b/build-config/meson.build index 8c41081a..bbc36e13 100644 --- a/build-config/meson.build +++ b/build-config/meson.build @@ -1,7 +1,8 @@ cmake = import('cmake') -subdir('fourdst') +subdir('python') +subdir('fourdst') subdir('sundials') subdir('cppad') diff --git a/build-config/python/meson.build b/build-config/python/meson.build new file mode 100644 index 00000000..b1afd178 --- /dev/null +++ b/build-config/python/meson.build @@ -0,0 +1,16 @@ +py_installation = import('python').find_installation('python3', pure: false) + + +if meson.is_cross_build() and host_machine.system() == 'darwin' + py_ver = get_option('python-target-version') + message('Cross build on Darwin, using python version ' + py_ver) + py_inc_dir = include_directories('../../cross/python_includes/python-' + py_ver + '/include/python' + py_ver) + py_dep = declare_dependency(include_directories: py_inc_dir) + py_module_prefix = '' + py_module_suffic = 'so' + meson.override_dependency('python3', py_dep) +else + py_dep = py_installation.dependency() + py_module_prefix = '' + py_module_suffic = 'so' +endif diff --git a/build-config/sundials/cvode/meson.build b/build-config/sundials/cvode/meson.build index b46397f6..1d976b94 100644 --- a/build-config/sundials/cvode/meson.build +++ b/build-config/sundials/cvode/meson.build @@ -6,9 +6,8 @@ cvode_cmake_options.add_cmake_defines({ 'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations', 'BUILD_SHARED_LIBS' : 'OFF', 'BUILD_STATIC_LIBS' : 'ON', - 'CMAKE_BUILD_WITH_INSTALL_RPATH': 'ON', 'EXAMPLES_ENABLE_C': 'OFF', - 'CMAKE_POSITION_INDEPENDENT_CODE': 'ON' + 'CMAKE_POSITION_INDEPENDENT_CODE': true }) @@ -22,29 +21,56 @@ cvode_sp = cmake.subproject( options: cvode_cmake_options, ) -# For the core SUNDIALS library (SUNContext, etc.) -sundials_core_dep = cvode_sp.dependency('sundials_core_static') +sundials_core_tgt = cvode_sp.target('sundials_core_static') +sundials_cvode_tgt = cvode_sp.target('sundials_cvode_static') +sundials_nvecserial_tgt = cvode_sp.target('sundials_nvecserial_static') +sundials_sunmatrixdense_tgt = cvode_sp.target('sundials_sunmatrixdense_static') +sundials_sunlinsoldense_tgt = cvode_sp.target('sundials_sunlinsoldense_static') -# For the CVODE integrator library -sundials_cvode_dep = cvode_sp.dependency('sundials_cvode_static') +cvode_objs = [ + sundials_core_tgt.extract_all_objects(recursive: true), + sundials_cvode_tgt.extract_all_objects(recursive: true), + sundials_nvecserial_tgt.extract_all_objects(recursive: true), + sundials_sunmatrixdense_tgt.extract_all_objects(recursive: true), + sundials_sunlinsoldense_tgt.extract_all_objects(recursive: true), +] -# For the serial NVector library -sundials_nvecserial_dep = cvode_sp.dependency('sundials_nvecserial_static') +sundials_core_includes = cvode_sp.include_directories('sundials_core_static') +sundials_cvode_includes = cvode_sp.include_directories('sundials_cvode_static') +sundials_nvecserial_includes = cvode_sp.include_directories('sundials_nvecserial_static') +sundials_sunmatrixdense_includes = cvode_sp.include_directories('sundials_sunmatrixdense_static') +sundials_sunlinsoldense_includes = cvode_sp.include_directories('sundials_sunlinsoldense_static') -# For the dense matrix library -sundials_sunmatrixdense_dep = cvode_sp.dependency('sundials_sunmatrixdense_static') +cvode_includes = [ + sundials_core_includes, + sundials_cvode_includes, + sundials_nvecserial_includes, + sundials_sunmatrixdense_includes, + sundials_sunlinsoldense_includes +] -# For the dense linear solver library -sundials_sunlinsoldense_dep = cvode_sp.dependency('sundials_sunlinsoldense_static') -cvode_dep = declare_dependency( - dependencies: [ - sundials_core_dep, - sundials_cvode_dep, - sundials_nvecserial_dep, - sundials_sunmatrixdense_dep, - sundials_sunlinsoldense_dep, - ], +empty_cvode_file = configure_file( + output: 'cvode_dummy_ar.cpp', + command: ['echo'], + capture: true + ) + + + +libcvode_static = static_library( + 'cvode-static', + empty_cvode_file, + objects: cvode_objs, + include_directories: cvode_includes, + pic: true, + install: false +) + + +cvode_dep = declare_dependency( + link_with: libcvode_static, + include_directories: cvode_includes, ) diff --git a/build-config/sundials/kinsol/meson.build b/build-config/sundials/kinsol/meson.build index 9014a5f6..b14cbe74 100644 --- a/build-config/sundials/kinsol/meson.build +++ b/build-config/sundials/kinsol/meson.build @@ -7,9 +7,8 @@ kinsol_cmake_options.add_cmake_defines({ 'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations', 'BUILD_SHARED_LIBS' : 'OFF', 'BUILD_STATIC_LIBS' : 'ON', - 'CMAKE_BUILD_WITH_INSTALL_RPATH': 'ON', 'EXAMPLES_ENABLE_C' : 'OFF', - 'CMAKE_POSITION_INDEPENDENT_CODE': 'ON' + 'CMAKE_POSITION_INDEPENDENT_CODE': true }) kinsol_cmake_options.add_cmake_defines({ @@ -22,11 +21,31 @@ kinsol_sp = cmake.subproject( options: kinsol_cmake_options, ) -sundials_kinsol_shared = kinsol_sp.dependency('sundials_kinsol_static') +sundials_kinsol_static_tgt = kinsol_sp.target('sundials_kinsol_obj_static') +kinsol_includes = kinsol_sp.include_directories('sundials_kinsol_obj_static') -kinsol_dep = declare_dependency( - dependencies: [ - sundials_kinsol_shared, - ] +kinsol_objs = [sundials_kinsol_static_tgt.extract_all_objects(recursive: false)] + +empty_kinsol_file = configure_file( + output: 'kinsol_dummy_ar.cpp', + command: ['echo'], + capture: true + ) + + +libkinsol_static = static_library( + 'kinsol_static', + empty_kinsol_file, + objects: kinsol_objs, + include_directories: kinsol_includes, + pic: true, + install: false ) + +kinsol_dep = declare_dependency( + link_with: libkinsol_static, + include_directories: kinsol_includes +) + + diff --git a/build-config/sundials/meson.build b/build-config/sundials/meson.build index e5cb6298..bf738c13 100644 --- a/build-config/sundials/meson.build +++ b/build-config/sundials/meson.build @@ -6,4 +6,4 @@ sundials_dep = declare_dependency( cvode_dep, kinsol_dep, ], -) \ No newline at end of file +) diff --git a/build-python/meson.build b/build-python/meson.build index d28b7862..88d67746 100644 --- a/build-python/meson.build +++ b/build-python/meson.build @@ -1,7 +1,6 @@ -# --- Python Extension Setup --- -py_installation = import('python').find_installation('python3', pure: false) gridfire_py_deps = [ + py_dep, pybind11_dep, const_dep, config_dep, @@ -9,9 +8,7 @@ gridfire_py_deps = [ gridfire_dep ] -py_mod = py_installation.extension_module( - '_gridfire', # Name of the generated .so/.pyd file (without extension) - sources: [ +py_sources = [ meson.project_source_root() + '/src/python/bindings.cpp', meson.project_source_root() + '/src/python/types/bindings.cpp', meson.project_source_root() + '/src/python/partition/bindings.cpp', @@ -29,11 +26,28 @@ py_mod = py_installation.extension_module( meson.project_source_root() + '/src/python/policy/bindings.cpp', meson.project_source_root() + '/src/python/policy/trampoline/py_policy.cpp', meson.project_source_root() + '/src/python/utils/bindings.cpp', - ], - dependencies : gridfire_py_deps, - install : true, - subdir: 'gridfire', -) + ] + + +if meson.is_cross_build() and host_machine.system() == 'darwin' + py_mod = shared_module( + '_gridfire', + sources: py_sources, + dependencies: gridfire_py_deps, + name_prefix: '', + name_suffix: 'so', + install: true, + install_dir: py_installation.get_install_dir() + '/gridfire' + ) +else + py_mod = py_installation.extension_module( + '_gridfire', # Name of the generated .so/.pyd file (without extension) + sources: py_sources, + dependencies : gridfire_py_deps, + install : true, + subdir: 'gridfire', + ) +endif py_installation.install_sources( diff --git a/cross/macos_arm64.ini b/cross/macos_arm64.ini index 33ea5f99..74dd72af 100644 --- a/cross/macos_arm64.ini +++ b/cross/macos_arm64.ini @@ -4,13 +4,16 @@ cpp = 'arm64-apple-darwin25-clang++' ar = 'arm64-apple-darwin25-ar' strip = 'arm64-apple-darwin25-strip' pkg-config = 'pkg-config' +ranlib = '/usr/bin/true' -[host-machine] +[host_machine] system = 'darwin' cpu_family = 'aarch64' -cpi = 'arm64' +cpu = 'arm64' endian = 'little' [built-in options] c_args = ['-mmacosx-version-min=15.0'] cpp_args = ['-mmacos-version-min=15.0'] +c_link_args = ['-mmacosx-version-min=15.0'] +cpp_link_args = ['-mmacos-version-min=15.0'] diff --git a/meson.build b/meson.build index f68c40c5..37ccfa3b 100644 --- a/meson.build +++ b/meson.build @@ -30,6 +30,7 @@ message('Found CXX compiler: ' + meson.get_compiler('cpp').get_id()) message('C++ standard set to: ' + get_option('cpp_std')) cppc = meson.get_compiler('cpp') +cc = meson.get_compiler('c') if cppc.get_id() == 'clang' @@ -72,6 +73,11 @@ if not cppc.has_header('format') endif +ignore_unused_args = '-Wno-unused-command-line-argument' + +add_global_arguments(ignore_unused_args, language: 'cpp') +add_global_arguments(ignore_unused_args, language: 'c') + # For Eigen diff --git a/meson_options.txt b/meson_options.txt index ec80a866..77cff5db 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,3 +5,4 @@ option('build-tests', type: 'boolean', value: true, description: 'build the test option('build-fortran', type: 'boolean', value: false, description: 'build fortran module support') option('unsafe-fortran', type: 'boolean', value: false, description: 'Allow untested fortran compilers (compilers other than gfortran)') option('unity-safe', type: 'boolean', value: false, description: 'Enable safe unity builds for better compatibility across different compilers and platforms') +option('python-target-version', type: 'string', value: '3.13', description: 'Target version for python compilation, only used for cross compilation') diff --git a/src/meson.build b/src/meson.build index aad8ffa2..ed52f2dc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -51,6 +51,7 @@ libgridfire = library('gridfire', gridfire_sources, include_directories: include_directories('include'), dependencies: gridfire_build_dependencies, + objects: [cvode_objs, kinsol_objs], install : true) gridfire_dep = declare_dependency( diff --git a/tests/graphnet_sandbox/main.cpp b/tests/graphnet_sandbox/main.cpp index 217cfe38..f42ffa67 100644 --- a/tests/graphnet_sandbox/main.cpp +++ b/tests/graphnet_sandbox/main.cpp @@ -4,7 +4,6 @@ #include "gridfire/gridfire.h" #include "fourdst/composition/composition.h" -#include "fourdst/plugin/bundle/bundle.h" #include "fourdst/logging/logging.h" #include "fourdst/atomic/species.h" #include "fourdst/composition/utils.h" diff --git a/tests/graphnet_sandbox/meson.build b/tests/graphnet_sandbox/meson.build index 935eb66a..41e4300c 100644 --- a/tests/graphnet_sandbox/meson.build +++ b/tests/graphnet_sandbox/meson.build @@ -1,5 +1,5 @@ executable( 'graphnet_sandbox', 'main.cpp', - dependencies: [gridfire_dep, composition_dep, plugin_dep, cli11_dep], + dependencies: [gridfire_dep, composition_dep, cli11_dep], ) diff --git a/utils/python/fetch_header_files.sh b/utils/python/fetch_header_files.sh new file mode 100755 index 00000000..62aa5c69 --- /dev/null +++ b/utils/python/fetch_header_files.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +# --- Configuration --- +PYTHON_VERSIONS=("3.8.10" "3.9.13" "3.10.11" "3.11.9" "3.12.3" "3.13.0" "3.14.0") + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_OUTPUT_DIR="$SCRIPT_DIR/../../cross/python_includes" + +# --- OS Detection --- +OS="$(uname -s)" +echo "Detected OS: $OS" + +# --- Dependency Check --- +check_dependencies() { + if [ "$OS" == "Linux" ]; then + if ! command -v 7z &> /dev/null; then + echo "Error: '7z' (p7zip-full) is required on Linux." + exit 1 + fi + if ! command -v cpio &> /dev/null; then + echo "Error: 'cpio' is required." + exit 1 + fi + fi +} + +# --- Extraction Logic (OS Specific) --- +extract_pkg() { + local pkg_file="$1" + local extract_root="$2" + local major_ver="$3" # e.g., 3.11 + + echo " -> Extracting..." + + if [ "$OS" == "Darwin" ]; then + pkgutil --expand "$pkg_file" "$extract_root/expanded" + local payload_path="$extract_root/expanded/Python_Framework.pkg/Payload" + + if [ ! -f "$payload_path" ]; then + echo " -> Error: Could not find Payload in package." + return 1 + fi + mkdir -p "$extract_root/root" + pushd "$extract_root/root" > /dev/null + cat "$payload_path" | gunzip | cpio -id "*include/python${major_ver}/*" 2>/dev/null + popd > /dev/null + + else + 7z x "$pkg_file" -o"$extract_root/expanded" -y > /dev/null + local payload_path="$extract_root/expanded/Python_Framework.pkg/Payload" + + if [ ! -f "$payload_path" ]; then + echo " -> Error: Could not find Payload in package." + return 1 + fi + + mkdir -p "$extract_root/root" + pushd "$extract_root/root" > /dev/null + cat "$payload_path" | gunzip | cpio -id "*include/python${major_ver}/*" 2>/dev/null + popd > /dev/null + fi +} + +check_dependencies + +mkdir -p "$BASE_OUTPUT_DIR" + +for FULL_VER in "${PYTHON_VERSIONS[@]}"; do + MAJOR_VER=$(echo "$FULL_VER" | cut -d. -f1,2) + + TARGET_DIR="$BASE_OUTPUT_DIR/python-$MAJOR_VER" + TEMP_DIR="$BASE_OUTPUT_DIR/tmp_$FULL_VER" + PKG_NAME="python-${FULL_VER}-macos11.pkg" + + if [[ "$MAJOR_VER" == "3.8" ]]; then + PKG_NAME="python-${FULL_VER}-macosx10.9.pkg" + fi + + DOWNLOAD_URL="https://www.python.org/ftp/python/${FULL_VER}/$PKG_NAME" + + echo "Processing Python $FULL_VER..." + + if [ -d "$TARGET_DIR" ] && [ "$(ls -A $TARGET_DIR)" ]; then + echo " -> Headers already exist in $TARGET_DIR. Skipping." + continue + fi + + mkdir -p "$TEMP_DIR" + + echo " -> Downloading from $DOWNLOAD_URL" + curl -L -s -o "$TEMP_DIR/python.pkg" "$DOWNLOAD_URL" + + if [ $? -ne 0 ]; then + echo " -> Download failed! Check version number or internet connection." + rm -rf "$TEMP_DIR" + continue + fi + + # 2. Extract + extract_pkg "$TEMP_DIR/python.pkg" "$TEMP_DIR" "$MAJOR_VER" + + # 3. Move Headers to Final Location + # The cpio extraction usually results in: ./Versions/X.Y/include/pythonX.Y + # We want to move that specific include folder to our target dir + + FOUND_HEADERS=$(find "$TEMP_DIR/root" -type d -path "*/include/python${MAJOR_VER}" | head -n 1) + + if [ -n "$FOUND_HEADERS" ]; then + echo " -> Found headers at: $FOUND_HEADERS" + + # Move the content to the final destination + # We want the folder to be .../python-3.11/include/python3.11 + mkdir -p "$TARGET_DIR/include" + mv "$FOUND_HEADERS" "$TARGET_DIR/include/" + + # Verify pyconfig.h exists (sanity check) + if [ -f "$TARGET_DIR/include/python${MAJOR_VER}/pyconfig.h" ]; then + echo " -> Success: Headers installed to $TARGET_DIR" + else + echo " -> Warning: Header move seemed successful, but pyconfig.h is missing." + fi + else + echo " -> Error: Could not locate header files after extraction." + fi + + # 4. Cleanup + rm -rf "$TEMP_DIR" + echo "---------------------------------------------------" +done + +echo "Done. All headers stored in $BASE_OUTPUT_DIR" + From 8a22496398b45b8817e579ff467d5bf93e301169 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Tue, 2 Dec 2025 10:04:00 -0500 Subject: [PATCH 08/14] fix(wheels): Repair wheel macos Script to repair RPATH issues in wheels on macos --- build-python/fix_rpaths.py | 2 +- utils/wheels/build-wheels-macos_aarch64.sh | 71 +++++++++-------- utils/wheels/repair_wheel_macos.sh | 90 ++++++++++++++++++++++ 3 files changed, 130 insertions(+), 33 deletions(-) create mode 100755 utils/wheels/repair_wheel_macos.sh diff --git a/build-python/fix_rpaths.py b/build-python/fix_rpaths.py index 34b4f3a8..9f9aa16d 100644 --- a/build-python/fix_rpaths.py +++ b/build-python/fix_rpaths.py @@ -78,7 +78,7 @@ def fix_rpaths(binary_path): def main(): if len(sys.argv) != 2: - print(f"--- Error: Expected one argument (path to .so file), got {sys.argv}", file=sys.stderr) + print(f"--- Error: Expected one argument (path to .dylib/.so file), got {sys.argv}", file=sys.stderr) sys.exit(1) # Get the file path directly from the command line argument diff --git a/utils/wheels/build-wheels-macos_aarch64.sh b/utils/wheels/build-wheels-macos_aarch64.sh index e8e67291..a4c8c442 100755 --- a/utils/wheels/build-wheels-macos_aarch64.sh +++ b/utils/wheels/build-wheels-macos_aarch64.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -euo pipefail +# 1. Validation if [[ $(uname -m) != "arm64" ]]; then echo "Error: This script is intended to run on an Apple Silicon (arm64) Mac." exit 1 @@ -11,11 +12,12 @@ if [[ $# -ne 1 ]]; then exit 1 fi -# --- Initial Setup --- +# 2. Setup Directories REPO_URL="$1" WORK_DIR="$(pwd)" WHEEL_DIR="${WORK_DIR}/wheels_macos_aarch64_tmp" FINAL_WHEEL_DIR="${WORK_DIR}/wheels_macos_aarch64" +RPATH_SCRIPT="${WORK_DIR}/../../build-python/fix_rpaths.py" # Assumes script is in this location relative to execution echo "➤ Creating wheel output directories" mkdir -p "${WHEEL_DIR}" @@ -26,10 +28,22 @@ echo "➤ Cloning ${REPO_URL} → ${TMPDIR}/project" git clone --depth 1 "${REPO_URL}" "${TMPDIR}/project" cd "${TMPDIR}/project" -# --- macOS Build Configuration --- +# 3. Build Configuration export MACOSX_DEPLOYMENT_TARGET=15.0 +# Meson options passed to pip via config-settings +# Note: We use an array to keep the command clean +MESON_ARGS=( + "-Csetup-args=-Dunity=off" + "-Csetup-args=-Dbuild-python=true" + "-Csetup-args=-Dbuild-fortran=false" + "-Csetup-args=-Dbuild-tests=false" + "-Csetup-args=-Dpkg-config=false" + "-Csetup-args=-Dunity-safe=true" +) + PYTHON_VERSIONS=("3.8.20" "3.9.23" "3.10.18" "3.11.13" "3.12.11" "3.13.5" "3.13.5t" "3.14.0rc1" "3.14.0rc1t" 'pypy3.10-7.3.19' "pypy3.11-7.3.20") +PYTHON_VERSIONS=("3.9.23" "3.10.18" "3.11.13" "3.12.11" "3.13.5" "3.13.5t" "3.14.0rc1" "3.14.0rc1t" 'pypy3.10-7.3.19' "pypy3.11-7.3.20") if ! command -v pyenv &> /dev/null; then echo "Error: pyenv not found. Please install it to manage Python versions." @@ -37,55 +51,48 @@ if ! command -v pyenv &> /dev/null; then fi eval "$(pyenv init -)" +# 4. Build Loop for PY_VERSION in "${PYTHON_VERSIONS[@]}"; do ( set -e - if ! pyenv versions --bare --filter="${PY_VERSION}." &>/dev/null; then - echo "⚠️ Python version matching '${PY_VERSION}.*' not found by pyenv. Skipping." + # Check if version exists in pyenv + if ! pyenv versions --bare --filter="${PY_VERSION}" &>/dev/null; then + echo "⚠️ Python version matching '${PY_VERSION}' not found by pyenv. Skipping." continue fi pyenv shell "${PY_VERSION}" - PY="$(pyenv which python)" - echo "➤ Building for $($PY --version) on macOS arm64 (target: ${MACOSX_DEPLOYMENT_TARGET})" + + echo "----------------------------------------------------------------" + echo "➤ Building for $($PY --version) on macOS arm64" + echo "----------------------------------------------------------------" + # Install build deps explicitly so we can skip build isolation "$PY" -m pip install --upgrade pip setuptools wheel meson meson-python delocate - CC=clang CXX=clang++ "$PY" -m pip wheel . \ - -w "${WHEEL_DIR}" -vv + # PERF: --no-build-isolation prevents creating a fresh venv and reinstalling meson/ninja + # for every single build, saving significant I/O and network time. + CC="ccache clang" CXX="ccache clang++" "$PY" -m pip wheel . \ + --no-build-isolation \ + "${MESON_ARGS[@]}" \ + -w "${WHEEL_DIR}" -vv - echo "➤ Sanitizing RPATHs before delocation..." - + # We expect exactly one new wheel in the tmp dir per iteration CURRENT_WHEEL=$(find "${WHEEL_DIR}" -name "*.whl" | head -n 1) - - if [ -f "$CURRENT_WHEEL" ]; then - "$PY" -m wheel unpack "$CURRENT_WHEEL" -d "${WHEEL_DIR}/unpacked" - - UNPACKED_ROOT=$(find "${WHEEL_DIR}/unpacked" -mindepth 1 -maxdepth 1 -type d) - find "$UNPACKED_ROOT" -name "*.so" | while read -r SO_FILE; do - echo " Processing: $SO_FILE" - "$PY" "../../build-python/fix_rpaths.py" "$SO_FILE" - done - - "$PY" -m wheel pack "$UNPACKED_ROOT" -d "${WHEEL_DIR}" - - rm -rf "${WHEEL_DIR}/unpacked" - else - echo "Error: No wheel found to sanitize!" - exit 1 - fi - - echo "➤ Repairing wheel(s) with delocate" - delocate-wheel -w "${FINAL_WHEEL_DIR}" "${WHEEL_DIR}"/*.whl - - rm "${WHEEL_DIR}"/*.whl + echo "➤ Repairing wheel with delocate" + # Delocate moves the repaired wheel to FINAL_WHEEL_DIR + delocate-wheel -w "${FINAL_WHEEL_DIR}" "$CURRENT_WHEEL" + # Clean up the intermediate wheel from this iteration so it doesn't confuse the next + rm "$CURRENT_WHEEL" ) done +# Cleanup rm -rf "${TMPDIR}" rm -rf "${WHEEL_DIR}" +echo "✅ All builds complete. Artifacts in ${FINAL_WHEEL_DIR}" diff --git a/utils/wheels/repair_wheel_macos.sh b/utils/wheels/repair_wheel_macos.sh new file mode 100755 index 00000000..5ff23997 --- /dev/null +++ b/utils/wheels/repair_wheel_macos.sh @@ -0,0 +1,90 @@ +#!/bin/zsh + +set -e +# Color codes for output +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +function fix_file_rpaths() { + local file_path="$1" + echo -e "${YELLOW}Fixing RPATHs in file: $file_path...${NC}" + python3 "$FIX_RPATH_SCRIPT" "$file_path" + if [ $? -ne 0 ]; then + echo -e "${RED}Error: RPATH fix script failed for file: $file_path${NC}" + exit 1 + fi + echo -e "${GREEN}RPATHs fixed for file: $file_path${NC}" +} + +export -f fix_file_rpaths + +echo -e "${YELLOW}" +echo "=========================================================================" +echo " TEMPORARY WHEEL REPAIR WORKAROUND" +echo "=========================================================================" +echo -e "${NC}" +echo "" +echo -e "${YELLOW}WARNING:${NC} This script applies a temporary patch to fix" +echo "a known issue with meson-python that causes duplicate RPATH entries in" +echo "built Python wheels on macOS, preventing module imports." +echo "" +echo "This workaround will:" +echo " 1. Unzip the wheel file" +echo " 2. Locate the extension modules" +echo " 3. Remove duplicate RPATH entries using install_name_tool" +echo " 4. Resign the wheel if necessary" +echo " 5. Repackage the wheel file" +echo "" + +FIX_RPATH_SCRIPT="../../build-python/fix_rpaths.py" + +# get the wheel directory to scan through +WHEEL_DIR="$1" +if [ -z "$WHEEL_DIR" ]; then + echo -e "${RED}Error: No wheel directory specified.${NC}" + echo "Usage: $0 /path/to/wheel_directory" + exit 1 +fi + +REPAIRED_WHEELS_DIR="repaired_wheels" +mkdir -p "$REPAIRED_WHEELS_DIR" + +REPAIRED_DELOCATED_WHEELS_DIR="${REPAIRED_WHEELS_DIR}/delocated" + +# Scal all files ending in .whl and not starting with a dot +for WHEEL_PATH in "$WHEEL_DIR"/*.whl; do + if [ ! -f "$WHEEL_PATH" ]; then + echo -e "${YELLOW}No wheel files found in directory: $WHEEL_DIR${NC}" + exit 0 + fi + echo "" + echo -e "${GREEN}Processing wheel: $WHEEL_PATH${NC}" + + WHEEL_NAME=$(basename "$WHEEL_PATH") + TEMP_DIR=$(mktemp -d) + + echo -e "${GREEN}Step 1: Unzipping wheel...${NC}" + python -m wheel unpack "$WHEEL_PATH" -d "$TEMP_DIR" + + echo -e "${GREEN}Step 2: Locating extension modules...${NC}" + while IFS= read -r -d '' so_file; do + echo "Found library: $so_file" + fix_file_rpaths "$so_file" + done < <(find "$TEMP_DIR" -name "*.so" -print0) + + echo -e "${GREEN}Step 4: Repackaging wheel...${NC}" + python -m wheel pack "$TEMP_DIR/gridfire-0.7.4rc2" -d "$REPAIRED_WHEELS_DIR" + + REPAIRED_WHEEL_PATH="${REPAIRED_WHEELS_DIR}/${WHEEL_NAME}" + + echo -e "${GREEN}Step 5: Delocating wheel...${NC}" + # Ensure delocate is installed + pip install delocate + delocate-wheel -w "$REPAIRED_DELOCATED_WHEELS_DIR" "$REPAIRED_WHEEL_PATH" + + echo -e "${GREEN}Repaired wheel saved to: ${REPAIRED_DELOCATED_WHEELS_DIR}/${WHEEL_NAME}${NC}" + # Clean up temporary directory + rm -rf "$TEMP_DIR" +done \ No newline at end of file From ed2c1d58163ab943df0479e8dfcb84862378ae76 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Tue, 2 Dec 2025 10:04:42 -0500 Subject: [PATCH 09/14] build(.gitignore): .whl added .whl files added to gitignore so large precompiled wheel folders are not accidently commited --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c20d7ec2..3e455cda 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,5 @@ meson-boost-test/ *_pynucastro_network.py cross/python_includes + +*.whl From d852ee43fee107b9e97b0fa7a426785e58bce254 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Tue, 2 Dec 2025 13:09:19 -0500 Subject: [PATCH 10/14] perf(precomputation): cleaned up allocations recovered about 5% execution time --- src/include/gridfire/engine/engine_abstract.h | 2 +- src/include/gridfire/engine/engine_graph.h | 14 +++++++++++++- .../solver/strategies/CVODE_solver_strategy.h | 2 +- src/lib/engine/engine_graph.cpp | 19 +++++++++++-------- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/include/gridfire/engine/engine_abstract.h b/src/include/gridfire/engine/engine_abstract.h index 82d06fcd..e372afd9 100644 --- a/src/include/gridfire/engine/engine_abstract.h +++ b/src/include/gridfire/engine/engine_abstract.h @@ -53,7 +53,7 @@ namespace gridfire::engine { struct StepDerivatives { std::map dydt{}; ///< Derivatives of abundances (dY/dt for each species). T nuclearEnergyGenerationRate = T(0.0); ///< Specific energy generation rate (e.g., erg/g/s). - std::map> reactionContributions{}; + std::optional>> reactionContributions = std::nullopt; T neutrinoEnergyLossRate = T(0.0); // (erg/g/s) T totalNeutrinoFlux = T(0.0); // (neutrinos/g/s) diff --git a/src/include/gridfire/engine/engine_graph.h b/src/include/gridfire/engine/engine_graph.h index 96b2d083..17652b1c 100644 --- a/src/include/gridfire/engine/engine_graph.h +++ b/src/include/gridfire/engine/engine_graph.h @@ -753,6 +753,14 @@ namespace gridfire::engine { [[nodiscard]] SpeciesStatus getSpeciesStatus(const fourdst::atomic::Species &species) const override; + [[nodiscard]] bool get_store_intermediate_reaction_contributions() const { + return m_store_intermediate_reaction_contributions; + } + + void set_store_intermediate_reaction_contributions(const bool value) { + m_store_intermediate_reaction_contributions = value; + } + private: struct PrecomputedReaction { @@ -879,6 +887,7 @@ namespace gridfire::engine { bool m_usePrecomputation = true; ///< Flag to enable or disable using precomputed reactions for efficiency. Mathematically, this should not change the results. Generally end users should not need to change this. bool m_useReverseReactions = true; ///< Flag to enable or disable reverse reactions. If false, only forward reactions are considered. + bool m_store_intermediate_reaction_contributions = false; ///< Flag to enable or disable storing intermediate reaction contributions for debugging. BuildDepthType m_depth; @@ -1207,7 +1216,10 @@ namespace gridfire::engine { const T nu_ij = static_cast(reaction.stoichiometry(species)); const T dydt_increment = threshold_flag * molarReactionFlow * nu_ij; dydt_vec[speciesIdx] += dydt_increment; - result.reactionContributions[species][std::string(reaction.id())] = dydt_increment; + + if (m_store_intermediate_reaction_contributions) { + result.reactionContributions.value()[species][std::string(reaction.id())] = dydt_increment; + } } } diff --git a/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h b/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h index dc656329..1cc4f454 100644 --- a/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h +++ b/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h @@ -237,7 +237,7 @@ namespace gridfire::solver { }; struct CVODERHSOutputData { - std::map> reaction_contribution_map; + std::optional>> reaction_contribution_map; double neutrino_energy_loss_rate; double total_neutrino_flux; }; diff --git a/src/lib/engine/engine_graph.cpp b/src/lib/engine/engine_graph.cpp index c6e2545a..7ba0c75b 100644 --- a/src/lib/engine/engine_graph.cpp +++ b/src/lib/engine/engine_graph.cpp @@ -684,7 +684,7 @@ namespace gridfire::engine { // --- Efficient lookup of only the active reactions --- uint64_t reactionHash = utils::hash_reaction(*reaction); const size_t reactionIndex = m_precomputedReactionIndexMap.at(reactionHash); - PrecomputedReaction precomputedReaction = m_precomputedReactions[reactionIndex]; + const PrecomputedReaction& precomputedReaction = m_precomputedReactions[reactionIndex]; // --- Forward abundance product --- double forwardAbundanceProduct = 1.0; @@ -697,12 +697,12 @@ namespace gridfire::engine { forwardAbundanceProduct = 0.0; break; // No need to continue if one of the reactants has zero abundance } - double factor = std::pow(comp.getMolarAbundance(reactant), power); + const double factor = std::pow(comp.getMolarAbundance(reactant), power); if (!std::isfinite(factor)) { LOG_CRITICAL(m_logger, "Non-finite factor encountered in forward abundance product for reaction '{}'. Check input abundances for validity.", reaction->id()); throw exceptions::BadRHSEngineError("Non-finite factor encountered in forward abundance product."); } - forwardAbundanceProduct *= std::pow(comp.getMolarAbundance(reactant), power); + forwardAbundanceProduct *= factor; } const double bare_rate = bare_rates.at(reactionCounter); @@ -764,8 +764,8 @@ namespace gridfire::engine { default: ; } - double local_neutrino_loss = molarReactionFlows.back() * q_abs * neutrino_loss_fraction * m_constants.Na * m_constants.MeV_to_erg; - double local_neutrino_flux = molarReactionFlows.back() * m_constants.Na; + const double local_neutrino_loss = molarReactionFlows.back() * q_abs * neutrino_loss_fraction * m_constants.Na * m_constants.MeV_to_erg; + const double local_neutrino_flux = molarReactionFlows.back() * m_constants.Na; result.totalNeutrinoFlux += local_neutrino_flux; result.neutrinoEnergyLossRate += local_neutrino_loss; @@ -782,7 +782,7 @@ namespace gridfire::engine { reactionCounter = 0; for (const auto& reaction: activeReactions) { - size_t j = m_precomputedReactionIndexMap.at(utils::hash_reaction(*reaction)); + const size_t j = m_precomputedReactionIndexMap.at(utils::hash_reaction(*reaction)); const auto& precomp = m_precomputedReactions[j]; const double R_j = molarReactionFlows[reactionCounter]; @@ -793,9 +793,12 @@ namespace gridfire::engine { const int stoichiometricCoefficient = precomp.stoichiometric_coefficients[i]; // Update the derivative for this species - double dydt_increment = static_cast(stoichiometricCoefficient) * R_j; + const double dydt_increment = static_cast(stoichiometricCoefficient) * R_j; result.dydt.at(species) += dydt_increment; - result.reactionContributions[species][std::string(reaction->id())] = dydt_increment; + + if (m_store_intermediate_reaction_contributions) { + result.reactionContributions.value()[species][std::string(reaction->id())] = dydt_increment; + } } reactionCounter++; } From 7242c765f3f6a370878d19cdef5767ea7d123ef2 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Wed, 3 Dec 2025 11:38:08 -0500 Subject: [PATCH 11/14] build(wasm): major progress on gridfire compiling to wasm --- build-check/CC/meson.build | 0 build-check/CPPC/meson.build | 33 +++++ build-check/FC/meson.build | 15 +++ build-check/meson.build | 15 +++ build-config/fourdst/meson.build | 22 +-- build-config/meson.build | 7 +- build-config/pybind/meson.build | 2 +- build-config/sundials/cvode/meson.build | 11 +- build-config/xxHash/meson.build | 2 +- build-extra/log-level/meson.build | 32 +++++ build-extra/pkg-config/meson.build | 19 +++ build-python/meson.build | 169 ++++++++++++------------ cross/wasm.ini | 23 ++++ meson.build | 142 ++------------------ meson_options.txt | 18 +-- src/extern/meson.build | 2 +- src/meson.build | 19 ++- subprojects/fourdst.wrap | 2 +- tests/extern/meson.build | 6 +- tests/meson.build | 6 +- 20 files changed, 295 insertions(+), 250 deletions(-) create mode 100644 build-check/CC/meson.build create mode 100644 build-check/CPPC/meson.build create mode 100644 build-check/FC/meson.build create mode 100644 build-check/meson.build create mode 100644 build-extra/log-level/meson.build create mode 100644 build-extra/pkg-config/meson.build create mode 100644 cross/wasm.ini diff --git a/build-check/CC/meson.build b/build-check/CC/meson.build new file mode 100644 index 00000000..e69de29b diff --git a/build-check/CPPC/meson.build b/build-check/CPPC/meson.build new file mode 100644 index 00000000..12a33c54 --- /dev/null +++ b/build-check/CPPC/meson.build @@ -0,0 +1,33 @@ +cppc = meson.get_compiler('cpp') + +if cppc.get_id() == 'clang' + message('disabling bitwise-instead-of-logical warnings for clang') + add_project_arguments('-Wno-bitwise-instead-of-logical', language: 'cpp') +endif + +if cppc.get_id() == 'gcc' + message('disabling psabi warnings for gcc') + add_project_arguments('-Wno-psabi', language: 'cpp') + + if (cppc.version().version_compare('<14.0')) + error('g++ version must be at least 14.0, found ' + cppc.version()) + endif +endif + +if not cppc.has_header('print') + error('C++ standard library header not found. Please ensure your compiler and standard library supports C++23. We have already validated your compiler version so this is likely an issue with your standard library installation.') +endif +if not cppc.has_header('format') + error('C++ standard library header not found. Please ensure your compiler and standard library supports C++23. We have already validated your compiler version so this is likely an issue with your standard library installation.') +endif + +# For Eigen +add_project_arguments('-Wno-deprecated-declarations', language: 'cpp') + +if get_option('build_python') + message('enabling hidden visibility for C++ symbols when building Python extension. This reduces the size of the resulting shared library.') + add_project_arguments('-fvisibility=hidden', language: 'cpp') +else + message('enabling default visibility for C++ symbols') + add_project_arguments('-fvisibility=default', language: 'cpp') +endif diff --git a/build-check/FC/meson.build b/build-check/FC/meson.build new file mode 100644 index 00000000..5d1cc40e --- /dev/null +++ b/build-check/FC/meson.build @@ -0,0 +1,15 @@ +if get_option('build_fortran') + add_languages('fortran', native: true) + message('Found FORTRAN compiler: ' + meson.get_compiler('fortran').get_id()) + message('Fortran standard set to: ' + get_option('fortran_std')) + message('Building fortran module (gridfire_mod.mod)') + fc = meson.get_compiler('fortran') + if not get_option('unsafe_fortran') + if fc.get_id() != 'gcc' + error('The only supported fortran compiler for GridFire is gfortran (version >= 14.0), found ' + fc + '. GridFire has not been tested with any other compilers. You can disable this check with the -Dunsafe-fortran=true flag to try other compilers') + endif + endif + if (fc.version().version_compare('<14.0')) + error('gfortran version must be at least 14.0, found ' + fc.version()) + endif +endif diff --git a/build-check/meson.build b/build-check/meson.build new file mode 100644 index 00000000..9ace8c4e --- /dev/null +++ b/build-check/meson.build @@ -0,0 +1,15 @@ +message('Found CXX compiler: ' + meson.get_compiler('cpp').get_id()) +message('C++ standard set to: ' + get_option('cpp_std')) + +cc = meson.get_compiler('c') + +ignore_unused_args = '-Wno-unused-command-line-argument' + +add_global_arguments(ignore_unused_args, language: 'cpp') +add_global_arguments(ignore_unused_args, language: 'c') + + +subdir('CPPC') +subdir('FC') + + diff --git a/build-config/fourdst/meson.build b/build-config/fourdst/meson.build index c6696210..98a2c9a5 100644 --- a/build-config/fourdst/meson.build +++ b/build-config/fourdst/meson.build @@ -1,17 +1,21 @@ # bring in all of the fourdst utility repositories fourdst_build_lib_all = true -if get_option('unity-safe') +if not get_option('plugin_support') fourdst_build_lib_all=false + message('Disabling fourdst plugin support as per user request.') endif fourdst_sp = subproject('fourdst', default_options: - ['build-tests=' + get_option('build-tests').to_string(), - 'build-python=' + get_option('build-python').to_string(), - 'build-lib-all=' + fourdst_build_lib_all.to_string(), - 'pkg-config=' + get_option('pkg-config').to_string(), - 'build-lib-log=true' + ['build_tests=' + get_option('build_tests').to_string(), + 'build_python=' + get_option('build_python').to_string(), + 'build_lib_all=' + fourdst_build_lib_all.to_string(), + 'build_lib_comp=true', + 'build_lib_config=true', + 'build_lib_log=true', + 'build_lib_const=true', + 'pkg_config=' + get_option('pkg_config').to_string(), ] ) @@ -19,7 +23,8 @@ composition_dep = fourdst_sp.get_variable('composition_dep') log_dep = fourdst_sp.get_variable('log_dep') const_dep = fourdst_sp.get_variable('const_dep') config_dep = fourdst_sp.get_variable('config_dep') -if not get_option('unity-safe') +if get_option('plugin_support') + warning('Including plugin library from fourdst. Note this will bring in minizip-ng and openssl, which can cause build issues with cross compilation due to their complexity.') plugin_dep = fourdst_sp.get_variable('plugin_dep') endif @@ -28,6 +33,7 @@ libconst = fourdst_sp.get_variable('libconst') libconfig = fourdst_sp.get_variable('libconfig') liblogging = fourdst_sp.get_variable('liblogging') -if not get_option('unity-safe') +if get_option('plugin_support') + warning('Including plugin library from fourdst. Note this will bring in minizip-ng and openssl, which can cause build issues with cross compilation due to their complexity.') libplugin = fourdst_sp.get_variable('libplugin') endif diff --git a/build-config/meson.build b/build-config/meson.build index bbc36e13..a433637d 100644 --- a/build-config/meson.build +++ b/build-config/meson.build @@ -1,7 +1,9 @@ cmake = import('cmake') - -subdir('python') +if get_option('build_python') + subdir('python') + subdir('pybind') +endif subdir('fourdst') subdir('sundials') @@ -11,6 +13,5 @@ subdir('eigen') subdir('json') -subdir('pybind') subdir('CLI11') diff --git a/build-config/pybind/meson.build b/build-config/pybind/meson.build index 49297116..5b46096c 100644 --- a/build-config/pybind/meson.build +++ b/build-config/pybind/meson.build @@ -1,3 +1,3 @@ pybind11_proj = subproject('pybind11') pybind11_dep = pybind11_proj.get_variable('pybind11_dep') -python3_dep = dependency('python3') \ No newline at end of file +python3_dep = dependency('python3') diff --git a/build-config/sundials/cvode/meson.build b/build-config/sundials/cvode/meson.build index 1d976b94..4b19f6ad 100644 --- a/build-config/sundials/cvode/meson.build +++ b/build-config/sundials/cvode/meson.build @@ -6,7 +6,7 @@ cvode_cmake_options.add_cmake_defines({ 'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations', 'BUILD_SHARED_LIBS' : 'OFF', 'BUILD_STATIC_LIBS' : 'ON', - 'EXAMPLES_ENABLE_C': 'OFF', + 'EXAMPLES_ENABLE_C' : 'OFF', 'CMAKE_POSITION_INDEPENDENT_CODE': true }) @@ -16,6 +16,15 @@ cvode_cmake_options.add_cmake_defines({ 'CMAKE_INSTALL_INCLUDEDIR': get_option('includedir') }) +if meson.is_cross_build() and host_machine.system() == 'emscripten' + cvode_cmake_options.add_cmake_defines({ + 'CMAKE_C_FLAGS': '-s MEMORY64=1 -s ALLOW_MEMORY_GROWTH=1', + 'CMAKE_CXX_FLAGS': '-s MEMORY64=1 -s ALLOW_MEMORY_GROWTH=1', + 'CMAKE_SHARED_LINKER_FLAGS': '-s MEMORY64=1 -s ALLOW_MEMORY_GROWTH=1', + 'CMAKE_EXE_LINKER_FLAGS': '-s MEMORY64=1 -s ALLOW_MEMORY_GROWTH=1' + }) +endif + cvode_sp = cmake.subproject( 'cvode', options: cvode_cmake_options, diff --git a/build-config/xxHash/meson.build b/build-config/xxHash/meson.build index a8551b51..3f98172c 100644 --- a/build-config/xxHash/meson.build +++ b/build-config/xxHash/meson.build @@ -1,4 +1,4 @@ xxhash_dep = declare_dependency( include_directories: include_directories('include') -) \ No newline at end of file +) diff --git a/build-extra/log-level/meson.build b/build-extra/log-level/meson.build new file mode 100644 index 00000000..18169529 --- /dev/null +++ b/build-extra/log-level/meson.build @@ -0,0 +1,32 @@ +llevel = get_option('log_level') + +logbase='QUILL_COMPILE_ACTIVE_LOG_LEVEL_' + +if (llevel == 'traceL3') + message('Setting log level to TRACE_L3') + log_argument = logbase + 'TRACE_L3' +elif (llevel == 'traceL2') + message('Setting log level to TRACE_L2') + log_argument = logbase + 'TRACE_L2' +elif (llevel == 'traceL1') + message('Setting log level to TRACE_L1') + log_argument = logbase + 'TRACE_L1' +elif (llevel == 'debug') + message('Setting log level to DEBUG') + log_argument = logbase + 'DEBUG' +elif (llevel == 'info') + message('Setting log level to INFO') + log_argument = logbase + 'INFO' +elif (llevel == 'warning') + message('Setting log level to WARNING') + log_argument = logbase + 'WARNING' +elif (llevel == 'error') + message('Setting log level to ERROR') + log_argument = logbase + 'ERROR' +elif (llevel == 'critical') + message('Setting log level to CRITICAL') + log_argument = logbase + 'CRITICAL' +endif + +log_argument = '-DQUILL_COMPILE_ACTIVE_LOG_LEVEL=' + log_argument +add_project_arguments(log_argument, language: 'cpp') diff --git a/build-extra/pkg-config/meson.build b/build-extra/pkg-config/meson.build new file mode 100644 index 00000000..56f2f016 --- /dev/null +++ b/build-extra/pkg-config/meson.build @@ -0,0 +1,19 @@ +if get_option('pkg_config') + message('Generating pkg-config file for GridFire...') + pkg = import('pkgconfig') + pkg.generate( + name: 'gridfire', + description: 'GridFire nuclear reaction network solver', + version: meson.project_version(), + libraries: [ + libgridfire, + libcomposition, + libconfig, + libconst, + liblogging + ], + subdirs: ['gridfire'], + filebase: 'gridfire', + install_dir: join_paths(get_option('libdir'), 'pkgconfig') + ) +endif diff --git a/build-python/meson.build b/build-python/meson.build index 88d67746..87234f3b 100644 --- a/build-python/meson.build +++ b/build-python/meson.build @@ -1,90 +1,95 @@ +if get_option('build_python') + message('Building Python bindings...') -gridfire_py_deps = [ - py_dep, - pybind11_dep, - const_dep, - config_dep, - composition_dep, - gridfire_dep -] + gridfire_py_deps = [ + py_dep, + pybind11_dep, + const_dep, + config_dep, + composition_dep, + gridfire_dep + ] -py_sources = [ - meson.project_source_root() + '/src/python/bindings.cpp', - meson.project_source_root() + '/src/python/types/bindings.cpp', - meson.project_source_root() + '/src/python/partition/bindings.cpp', - meson.project_source_root() + '/src/python/partition/trampoline/py_partition.cpp', - meson.project_source_root() + '/src/python/reaction/bindings.cpp', - meson.project_source_root() + '/src/python/screening/bindings.cpp', - meson.project_source_root() + '/src/python/screening/trampoline/py_screening.cpp', - meson.project_source_root() + '/src/python/io/bindings.cpp', - meson.project_source_root() + '/src/python/io/trampoline/py_io.cpp', - meson.project_source_root() + '/src/python/exceptions/bindings.cpp', - meson.project_source_root() + '/src/python/engine/bindings.cpp', - meson.project_source_root() + '/src/python/engine/trampoline/py_engine.cpp', - meson.project_source_root() + '/src/python/solver/bindings.cpp', - meson.project_source_root() + '/src/python/solver/trampoline/py_solver.cpp', - meson.project_source_root() + '/src/python/policy/bindings.cpp', - meson.project_source_root() + '/src/python/policy/trampoline/py_policy.cpp', - meson.project_source_root() + '/src/python/utils/bindings.cpp', - ] + py_sources = [ + meson.project_source_root() + '/src/python/bindings.cpp', + meson.project_source_root() + '/src/python/types/bindings.cpp', + meson.project_source_root() + '/src/python/partition/bindings.cpp', + meson.project_source_root() + '/src/python/partition/trampoline/py_partition.cpp', + meson.project_source_root() + '/src/python/reaction/bindings.cpp', + meson.project_source_root() + '/src/python/screening/bindings.cpp', + meson.project_source_root() + '/src/python/screening/trampoline/py_screening.cpp', + meson.project_source_root() + '/src/python/io/bindings.cpp', + meson.project_source_root() + '/src/python/io/trampoline/py_io.cpp', + meson.project_source_root() + '/src/python/exceptions/bindings.cpp', + meson.project_source_root() + '/src/python/engine/bindings.cpp', + meson.project_source_root() + '/src/python/engine/trampoline/py_engine.cpp', + meson.project_source_root() + '/src/python/solver/bindings.cpp', + meson.project_source_root() + '/src/python/solver/trampoline/py_solver.cpp', + meson.project_source_root() + '/src/python/policy/bindings.cpp', + meson.project_source_root() + '/src/python/policy/trampoline/py_policy.cpp', + meson.project_source_root() + '/src/python/utils/bindings.cpp', + ] -if meson.is_cross_build() and host_machine.system() == 'darwin' - py_mod = shared_module( - '_gridfire', - sources: py_sources, - dependencies: gridfire_py_deps, - name_prefix: '', - name_suffix: 'so', - install: true, - install_dir: py_installation.get_install_dir() + '/gridfire' - ) -else - py_mod = py_installation.extension_module( - '_gridfire', # Name of the generated .so/.pyd file (without extension) + if meson.is_cross_build() and host_machine.system() == 'darwin' + py_mod = shared_module( + '_gridfire', sources: py_sources, - dependencies : gridfire_py_deps, - install : true, + dependencies: gridfire_py_deps, + name_prefix: '', + name_suffix: 'so', + install: true, + install_dir: py_installation.get_install_dir() + '/gridfire' + ) + else + py_mod = py_installation.extension_module( + '_gridfire', # Name of the generated .so/.pyd file (without extension) + sources: py_sources, + dependencies : gridfire_py_deps, + install : true, + subdir: 'gridfire', + ) + endif + + + py_installation.install_sources( + files( + meson.project_source_root() + '/src/python/gridfire/__init__.py', + meson.project_source_root() + '/stubs/gridfire/_gridfire/__init__.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/exceptions.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/partition.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/reaction.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/screening.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/io.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/solver.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/policy.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/type.pyi' + ), subdir: 'gridfire', ) + + py_installation.install_sources( + files( + meson.project_source_root() + '/stubs/gridfire/_gridfire/engine/__init__.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/engine/diagnostics.pyi', + ), + subdir: 'gridfire/engine', + ) + + py_installation.install_sources( + files( + meson.project_source_root() + '/stubs/gridfire/_gridfire/utils/__init__.pyi', + ), + subdir: 'gridfire/utils', + ) + + py_installation.install_sources( + files( + meson.project_source_root() + '/stubs/gridfire/_gridfire/utils/hashing/__init__.pyi', + meson.project_source_root() + '/stubs/gridfire/_gridfire/utils/hashing/reaction.pyi', + ), + subdir: 'gridfire/utils/hashing', + ) +else + message('Python bindings disabled') endif - - -py_installation.install_sources( - files( - meson.project_source_root() + '/src/python/gridfire/__init__.py', - meson.project_source_root() + '/stubs/gridfire/_gridfire/__init__.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/exceptions.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/partition.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/reaction.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/screening.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/io.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/solver.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/policy.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/type.pyi' - ), - subdir: 'gridfire', -) - -py_installation.install_sources( - files( - meson.project_source_root() + '/stubs/gridfire/_gridfire/engine/__init__.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/engine/diagnostics.pyi', - ), - subdir: 'gridfire/engine', -) - -py_installation.install_sources( - files( - meson.project_source_root() + '/stubs/gridfire/_gridfire/utils/__init__.pyi', - ), - subdir: 'gridfire/utils', -) - -py_installation.install_sources( - files( - meson.project_source_root() + '/stubs/gridfire/_gridfire/utils/hashing/__init__.pyi', - meson.project_source_root() + '/stubs/gridfire/_gridfire/utils/hashing/reaction.pyi', - ), - subdir: 'gridfire/utils/hashing', -) diff --git a/cross/wasm.ini b/cross/wasm.ini new file mode 100644 index 00000000..49fa18a5 --- /dev/null +++ b/cross/wasm.ini @@ -0,0 +1,23 @@ +[binaries] +c = 'emcc' +cpp = 'em++' +ar = 'emar' +strip = 'emstrip' + +exec_wrapper = 'node' + +[built-in options] +c_args = ['-Dpkg_config=false', '-Dbuild_tests=false', '-Dbuild_examples=true', '-Dbuild_fortran=falase', '-Dplugin_support=false', '-s', 'MEMORY64=1', '-pthread', '-DQUILL_NO_THREAD_NAME_SUPPORT', '-DQUILL_IMMEDIATE_FLUSH'] +cpp_args = ['-Dpkg_config=false', '-Dbuild_tests=false', '-Dbuild_examples=true', '-Dbuild_fortran=falase', '-Dplugin_support=false', '-s', 'MEMORY64=1', '-pthread', '-DQUILL_NO_THREAD_NAME_SUPPORT', '-DQUILL_IMMEDIATE_FLUSH'] +c_link_args = ['-s', 'WASM=1', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'MEMORY64=1', '-fwasm-exceptions', '-pthread', '-s', 'EXPORTED_RUNTIME_METHODS=["FS", "callMain"]', '-s', 'STACK_SIZE=10485760'] +cpp_link_args = ['-s', 'WASM=1', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'MEMORY64=1', '-fwasm-exceptions', '-pthread', '-s', 'EXPORTED_RUNTIME_METHODS=["FS", "callMain"]', '-s', 'STACK_SIZE=10485760'] + +[host_machine] +system = 'emscripten' +cpu_family = 'wasm64' +cpu = 'wasm64' +endian = 'little' + +[properties] +cmake_toolchain_file = '/home/tboudreaux/Programming/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake' + diff --git a/meson.build b/meson.build index 37ccfa3b..59b34f7d 100644 --- a/meson.build +++ b/meson.build @@ -20,141 +20,25 @@ # *********************************************************************** # project('GridFire', ['c', 'cpp'], version: 'v0.7.4_rc2', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0') -if get_option('build-python') - add_project_arguments('-fvisibility=hidden', language: 'cpp') -else - add_project_arguments('-fvisibility=default', language: 'cpp') -endif +# Start by running the code which validates the build environment +subdir('build-check') -message('Found CXX compiler: ' + meson.get_compiler('cpp').get_id()) -message('C++ standard set to: ' + get_option('cpp_std')) - -cppc = meson.get_compiler('cpp') -cc = meson.get_compiler('c') - - -if cppc.get_id() == 'clang' - message('disabling bitwise-instead-of-logical warnings for clang') - add_project_arguments('-Wno-bitwise-instead-of-logical', language: 'cpp') -endif - -if cppc.get_id() == 'gcc' - message('disabling psabi warnings for gcc') - add_project_arguments('-Wno-psabi', language: 'cpp') - - if (cppc.version().version_compare('<14.0')) - error('g++ version must be at least 14.0, found ' + cppc.version()) - endif -endif - -build_fortran = get_option('build-fortran') -if (build_fortran) - add_languages('fortran', native: true) - message('Found FORTRAN compiler: ' + meson.get_compiler('fortran').get_id()) - message('Fortran standard set to: ' + get_option('fortran_std')) - message('Building fortran module (gridfire_mod.mod)') - fc = meson.get_compiler('fortran') - if not get_option('unsafe-fortran') - if fc.get_id() != 'gcc' - error('The only supported fortran compiler for GridFire is gfortran (version >= 14.0), found ' + fc + '. GridFire has not been tested with any other compilers. You can disable this check with the -Dunsafe-fortran=true flag to try other compilers') - endif - endif - if (fc.version().version_compare('<14.0')) - error('gfortran version must be at least 14.0, found ' + fc.version()) - endif -endif - - -if not cppc.has_header('print') - error('C++ standard library header not found. Please ensure your compiler and standard library supports C++23. We have already validated your compiler version so this is likely an issue with your standard library installation.') -endif -if not cppc.has_header('format') - error('C++ standard library header not found. Please ensure your compiler and standard library supports C++23. We have already validated your compiler version so this is likely an issue with your standard library installation.') -endif - - -ignore_unused_args = '-Wno-unused-command-line-argument' - -add_global_arguments(ignore_unused_args, language: 'cpp') -add_global_arguments(ignore_unused_args, language: 'c') - - - -# For Eigen -add_project_arguments('-Wno-deprecated-declarations', language: 'cpp') - -llevel = get_option('log-level') - -logbase='QUILL_COMPILE_ACTIVE_LOG_LEVEL_' - -if (llevel == 'traceL3') - message('Setting log level to TRACE_L3') - log_argument = logbase + 'TRACE_L3' -elif (llevel == 'traceL2') - message('Setting log level to TRACE_L2') - log_argument = logbase + 'TRACE_L2' -elif (llevel == 'traceL1') - message('Setting log level to TRACE_L1') - log_argument = logbase + 'TRACE_L1' -elif (llevel == 'debug') - message('Setting log level to DEBUG') - log_argument = logbase + 'DEBUG' -elif (llevel == 'info') - message('Setting log level to INFO') - log_argument = logbase + 'INFO' -elif (llevel == 'warning') - message('Setting log level to WARNING') - log_argument = logbase + 'WARNING' -elif (llevel == 'error') - message('Setting log level to ERROR') - log_argument = logbase + 'ERROR' -elif (llevel == 'critical') - message('Setting log level to CRITICAL') - log_argument = logbase + 'CRITICAL' -endif - -log_argument = '-DQUILL_COMPILE_ACTIVE_LOG_LEVEL=' + log_argument -add_project_arguments(log_argument, language: 'cpp') - -cpp = meson.get_compiler('cpp') +# Configure the logging level +subdir('build-extra/log-level') +# Then build the external dependencies subdir('build-config') +# Build the main source code subdir('src') -if get_option('build-python') - message('Configuring Python bindings...') - subdir('build-python') -else - message('Skipping Python bindings...') -endif +# Build the Python bindings +subdir('build-python') -if get_option('build-tests') - message('Setting up tests for GridFire...') - subdir('tests') -else - message('Skipping tests for GridFire...') -endif - - -if get_option('pkg-config') - message('Generating pkg-config file for GridFire...') - pkg = import('pkgconfig') - pkg.generate( - name: 'gridfire', - description: 'GridFire nuclear reaction network solver', - version: meson.project_version(), - libraries: [ - libgridfire, - libcomposition, - libconfig, - libconst, - liblogging - ], - subdirs: ['gridfire'], - filebase: 'gridfire', - install_dir: join_paths(get_option('libdir'), 'pkgconfig') - ) -endif +# Buil the test suite +subdir('tests') + +# Build the pkg-config file +subdir('build-extra/pkg-config') diff --git a/meson_options.txt b/meson_options.txt index 77cff5db..f22e89ed 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,8 +1,10 @@ -option('log-level', type: 'combo', choices: ['traceL3', 'traceL2', 'traceL1', 'debug', 'info', 'warning', 'error', 'critial'], value: 'info', description: 'Set the log level for the GridFire library') -option('pkg-config', type: 'boolean', value: true, description: 'generate pkg-config file for GridFire (gridfire.pc)') -option('build-python', type: 'boolean', value: false, description: 'build the python bindings so you can use GridFire from python') -option('build-tests', type: 'boolean', value: true, description: 'build the test suite') -option('build-fortran', type: 'boolean', value: false, description: 'build fortran module support') -option('unsafe-fortran', type: 'boolean', value: false, description: 'Allow untested fortran compilers (compilers other than gfortran)') -option('unity-safe', type: 'boolean', value: false, description: 'Enable safe unity builds for better compatibility across different compilers and platforms') -option('python-target-version', type: 'string', value: '3.13', description: 'Target version for python compilation, only used for cross compilation') +option('log_level', type: 'combo', choices: ['traceL3', 'traceL2', 'traceL1', 'debug', 'info', 'warning', 'error', 'critial'], value: 'info', description: 'Set the log level for the GridFire library') +option('pkg_config', type: 'boolean', value: true, description: 'generate pkg-config file for GridFire (gridfire.pc)') +option('build_python', type: 'boolean', value: false, description: 'build the python bindings so you can use GridFire from python') +option('build_tests', type: 'boolean', value: true, description: 'build the test suite') +option('build_examples', type: 'boolean', value: true, description: 'build example code') +option('build_fortran', type: 'boolean', value: false, description: 'build fortran module support') +option('unsafe_fortran', type: 'boolean', value: false, description: 'Allow untested fortran compilers (compilers other than gfortran)') +option('plugin_support', type: 'boolean', value: false, description: 'Enable support for libplugin plugins') +option('python_target_version', type: 'string', value: '3.13', description: 'Target version for python compilation, only used for cross compilation') +option('build_c_api', type: 'boolean', value: true, description: 'compile the C API') diff --git a/src/extern/meson.build b/src/extern/meson.build index d9900ff1..70a4f68b 100644 --- a/src/extern/meson.build +++ b/src/extern/meson.build @@ -23,7 +23,7 @@ gridfire_extern_dep = declare_dependency( install_subdir('include/gridfire', install_dir: get_option('includedir')) -if get_option('build-fortran') +if get_option('build_fortran') message('Configuring Fortran bindings...') subdir('fortran') endif diff --git a/src/meson.build b/src/meson.build index ed52f2dc..3f08190e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,7 +42,7 @@ gridfire_build_dependencies = [ json_dep, ] -if not get_option('unity-safe') +if get_option('plugin_support') gridfire_build_dependencies += [plugin_dep] endif @@ -63,12 +63,11 @@ gridfire_dep = declare_dependency( install_subdir('include/gridfire', install_dir: get_option('includedir')) -message('Configuring C API...') -subdir('extern') -# -#if get_option('build-python') -# message('Configuring Python bindings...') -# subdir('python') -#else -# message('Skipping Python bindings...') -#endif + +if not get_option('build_c_api') and get_option('build_fortran') + error('Cannot build fortran without C API. Set -Dbuild-c-api=true and -Dbuild-fortran=true') +endif +if get_option('build_c_api') + message('Configuring C API...') + subdir('extern') +endif diff --git a/subprojects/fourdst.wrap b/subprojects/fourdst.wrap index 140d8639..3466edab 100644 --- a/subprojects/fourdst.wrap +++ b/subprojects/fourdst.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://github.com/4D-STAR/fourdst -revision = v0.9.11 +revision = v0.9.13 depth = 1 diff --git a/tests/extern/meson.build b/tests/extern/meson.build index 9ec12a00..ebc1edde 100644 --- a/tests/extern/meson.build +++ b/tests/extern/meson.build @@ -1,5 +1,7 @@ -subdir('C') +if get_option('build_c_api') + subdir('C') +endif -if get_option('build-fortran') +if get_option('build_fortran') subdir('fortran') endif diff --git a/tests/meson.build b/tests/meson.build index a2b8027c..5f17b7ab 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,7 +1,7 @@ # Google Test dependency -gtest_dep = dependency('gtest', main: true, required : true) -gtest_main = dependency('gtest_main', required: true) -gtest_nomain_dep = dependency('gtest', main: false, required : true) +#gtest_dep = dependency('gtest', main: true, required : true) +#gtest_main = dependency('gtest_main', required: true) +#gtest_nomain_dep = dependency('gtest', main: false, required : true) # Subdirectories for unit and integration tests subdir('graphnet_sandbox') From b6f452e74cfcc97aca196ddfec2c56bd9a5db363 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sat, 6 Dec 2025 11:41:57 -0500 Subject: [PATCH 12/14] feat(libconfig): new version of libconfig --- .gitignore | 3 ++ build-config/fourdst/meson.build | 1 - build-extra/pkg-config/meson.build | 1 - meson.build | 2 + meson_options.txt | 1 + src/include/gridfire/config/config.h | 35 ++++++++++++++ src/include/gridfire/engine/engine_graph.h | 3 +- .../gridfire/engine/views/engine_adaptive.h | 7 +-- .../gridfire/engine/views/engine_defined.h | 8 ++-- src/include/gridfire/io/network_file.h | 7 ++- .../solver/strategies/CVODE_solver_strategy.h | 3 +- src/lib/engine/views/engine_adaptive.cpp | 4 +- .../strategies/CVODE_solver_strategy.cpp | 8 ++-- subprojects/fourdst.wrap | 2 +- tests/graphnet_sandbox/meson.build | 2 +- tools/config/generate_config_files.cpp | 48 +++++++++++++++++++ tools/config/meson.build | 1 + tools/meson.build | 3 ++ 18 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 src/include/gridfire/config/config.h create mode 100644 tools/config/generate_config_files.cpp create mode 100644 tools/config/meson.build create mode 100644 tools/meson.build diff --git a/.gitignore b/.gitignore index 3e455cda..ba8f29b2 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ subprojects/cvode-*/ subprojects/kinsol-*/ subprojects/CLI11-*/ subprojects/openssl-*/ +subprojects/tomlplusplus-*/ *.fbundle *.wraplock @@ -98,6 +99,8 @@ liblogging.wrap libplugin.wrap minizip-ng.wrap openssl.wrap +glaze.wrap +tomlplusplus.wrap .vscode/ diff --git a/build-config/fourdst/meson.build b/build-config/fourdst/meson.build index 98a2c9a5..d2b092a0 100644 --- a/build-config/fourdst/meson.build +++ b/build-config/fourdst/meson.build @@ -30,7 +30,6 @@ endif libcomposition = fourdst_sp.get_variable('libcomposition') libconst = fourdst_sp.get_variable('libconst') -libconfig = fourdst_sp.get_variable('libconfig') liblogging = fourdst_sp.get_variable('liblogging') if get_option('plugin_support') diff --git a/build-extra/pkg-config/meson.build b/build-extra/pkg-config/meson.build index 56f2f016..b95f8bd3 100644 --- a/build-extra/pkg-config/meson.build +++ b/build-extra/pkg-config/meson.build @@ -8,7 +8,6 @@ if get_option('pkg_config') libraries: [ libgridfire, libcomposition, - libconfig, libconst, liblogging ], diff --git a/meson.build b/meson.build index 59b34f7d..b7e88d36 100644 --- a/meson.build +++ b/meson.build @@ -38,6 +38,8 @@ subdir('build-python') # Buil the test suite subdir('tests') +subdir('tools') + # Build the pkg-config file subdir('build-extra/pkg-config') diff --git a/meson_options.txt b/meson_options.txt index f22e89ed..ffaaf308 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -8,3 +8,4 @@ option('unsafe_fortran', type: 'boolean', value: false, description: 'Allow unte option('plugin_support', type: 'boolean', value: false, description: 'Enable support for libplugin plugins') option('python_target_version', type: 'string', value: '3.13', description: 'Target version for python compilation, only used for cross compilation') option('build_c_api', type: 'boolean', value: true, description: 'compile the C API') +option('build_tools', type: 'boolean', value: true, description: 'build the GridFire command line tools') diff --git a/src/include/gridfire/config/config.h b/src/include/gridfire/config/config.h new file mode 100644 index 00000000..9c807503 --- /dev/null +++ b/src/include/gridfire/config/config.h @@ -0,0 +1,35 @@ +#pragma once + +#include "fourdst/config/config.h" + +namespace gridfire::config { + struct CVODESolverConfig { + double absTol = 1.0e-8; + double relTol = 1.0e-5; + }; + + struct SolverConfig { + CVODESolverConfig cvode; + }; + + struct AdaptiveEngineViewConfig { + double relativeCullingThreshold = 1.0e-75; + }; + + struct EngineViewConfig { + AdaptiveEngineViewConfig adaptiveEngineView; + }; + + struct EngineConfig { + EngineViewConfig views; + + }; + + struct GridFireConfig { + SolverConfig solver; + EngineConfig engine; + }; + + + +} \ No newline at end of file diff --git a/src/include/gridfire/engine/engine_graph.h b/src/include/gridfire/engine/engine_graph.h index 17652b1c..0e044f7d 100644 --- a/src/include/gridfire/engine/engine_graph.h +++ b/src/include/gridfire/engine/engine_graph.h @@ -12,6 +12,7 @@ #include "gridfire/screening/screening_types.h" #include "gridfire/partition/partition_abstract.h" #include "gridfire/engine/procedures/construction.h" +#include "gridfire/config/config.h" #include #include @@ -855,7 +856,7 @@ namespace gridfire::engine { const GraphEngine& m_engine; }; private: - Config& m_config = Config::getInstance(); + Config m_config; quill::Logger* m_logger = LogManager::getInstance().getLogger("log"); constants m_constants; diff --git a/src/include/gridfire/engine/views/engine_adaptive.h b/src/include/gridfire/engine/views/engine_adaptive.h index e0f14c16..59d461cc 100644 --- a/src/include/gridfire/engine/views/engine_adaptive.h +++ b/src/include/gridfire/engine/views/engine_adaptive.h @@ -4,6 +4,7 @@ #include "gridfire/screening/screening_abstract.h" #include "gridfire/screening/screening_types.h" #include "gridfire/types/types.h" +#include "gridfire/config/config.h" #include "fourdst/atomic/atomicSpecies.h" #include "fourdst/config/config.h" @@ -386,10 +387,10 @@ namespace gridfire::engine { */ [[nodiscard]] SpeciesStatus getSpeciesStatus(const fourdst::atomic::Species &species) const override; private: - using Config = fourdst::config::Config; using LogManager = fourdst::logging::LogManager; - /** @brief A reference to the singleton Config instance, used for retrieving configuration parameters. */ - Config& m_config = Config::getInstance(); + + fourdst::config::Config m_config; + /** @brief A pointer to the logger instance, used for logging messages. */ quill::Logger* m_logger = LogManager::getInstance().getLogger("log"); diff --git a/src/include/gridfire/engine/views/engine_defined.h b/src/include/gridfire/engine/views/engine_defined.h index c0f2f33b..16da8efd 100644 --- a/src/include/gridfire/engine/views/engine_defined.h +++ b/src/include/gridfire/engine/views/engine_defined.h @@ -6,6 +6,8 @@ #include "gridfire/io/network_file.h" #include "gridfire/types/types.h" +#include "gridfire/config/config.h" + #include "fourdst/config/config.h" #include "fourdst/logging/logging.h" @@ -365,9 +367,9 @@ namespace gridfire::engine { [[nodiscard]] std::string getNetworkFile() const { return m_fileName; } [[nodiscard]] const io::NetworkFileParser& getParser() const { return m_parser; } private: - using Config = fourdst::config::Config; - using LogManager = fourdst::logging::LogManager; - Config& m_config = Config::getInstance(); + using LogManager = LogManager; + Config m_config; + quill::Logger* m_logger = LogManager::getInstance().getLogger("log"); std::string m_fileName; ///< Parser for the network file. diff --git a/src/include/gridfire/io/network_file.h b/src/include/gridfire/io/network_file.h index 6f25e312..54a7f5b0 100644 --- a/src/include/gridfire/io/network_file.h +++ b/src/include/gridfire/io/network_file.h @@ -2,6 +2,7 @@ #include "fourdst/config/config.h" #include "fourdst/logging/logging.h" +#include "gridfire/config/config.h" #include "quill/Logger.h" @@ -101,9 +102,8 @@ namespace gridfire::io { */ [[nodiscard]] ParsedNetworkData parse(const std::string& filename) const override; private: - using Config = fourdst::config::Config; using LogManager = fourdst::logging::LogManager; - Config& m_config = Config::getInstance(); + fourdst::config::Config m_config; quill::Logger* m_logger = LogManager::getInstance().getLogger("log"); }; @@ -141,9 +141,8 @@ namespace gridfire::io { */ [[nodiscard]] ParsedNetworkData parse(const std::string& filename) const override; private: - using Config = fourdst::config::Config; using LogManager = fourdst::logging::LogManager; - Config& m_config = Config::getInstance(); + fourdst::config::Config m_config; quill::Logger* m_logger = LogManager::getInstance().getLogger("log"); std::string m_filename; diff --git a/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h b/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h index 1cc4f454..42c41580 100644 --- a/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h +++ b/src/include/gridfire/solver/strategies/CVODE_solver_strategy.h @@ -4,6 +4,7 @@ #include "gridfire/engine/engine_abstract.h" #include "gridfire/types/types.h" #include "gridfire/exceptions/exceptions.h" +#include "gridfire/config/config.h" #include "fourdst/atomic/atomicSpecies.h" #include "fourdst/config/config.h" @@ -243,7 +244,7 @@ namespace gridfire::solver { }; private: - fourdst::config::Config& m_config = fourdst::config::Config::getInstance(); + fourdst::config::Config m_config; quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log"); /** * @brief CVODE RHS C-wrapper that delegates to calculate_rhs and captures exceptions. diff --git a/src/lib/engine/views/engine_adaptive.cpp b/src/lib/engine/views/engine_adaptive.cpp index 9feee9da..a1956d3e 100644 --- a/src/lib/engine/views/engine_adaptive.cpp +++ b/src/lib/engine/views/engine_adaptive.cpp @@ -394,7 +394,9 @@ namespace gridfire::engine { const double maxFlow ) const { LOG_TRACE_L1(m_logger, "Culling reactions based on flow rates..."); - const auto relative_culling_threshold = m_config.get("gridfire:AdaptiveEngineView:RelativeCullingThreshold", 1e-75); + + const auto relative_culling_threshold = m_config->engine.views.adaptiveEngineView.relativeCullingThreshold; + double absoluteCullingThreshold = relative_culling_threshold * maxFlow; LOG_DEBUG(m_logger, "Relative culling threshold: {:7.3E} ({:7.3E})", relative_culling_threshold, absoluteCullingThreshold); std::vector culledReactions; diff --git a/src/lib/solver/strategies/CVODE_solver_strategy.cpp b/src/lib/solver/strategies/CVODE_solver_strategy.cpp index 7d430481..01e2727e 100644 --- a/src/lib/solver/strategies/CVODE_solver_strategy.cpp +++ b/src/lib/solver/strategies/CVODE_solver_strategy.cpp @@ -112,8 +112,8 @@ namespace gridfire::solver { // 2. If the user has set tolerances in code, those override the config // 3. If the user has not set tolerances in code and the config does not have them, use hardcoded defaults - auto absTol = m_config.get("gridfire:solver:CVODESolverStrategy:absTol", 1.0e-8); - auto relTol = m_config.get("gridfire:solver:CVODESolverStrategy:relTol", 1.0e-5); + auto absTol = m_config->solver.cvode.absTol; + auto relTol = m_config->solver.cvode.relTol; if (m_absTol) { absTol = *m_absTol; @@ -935,8 +935,8 @@ namespace gridfire::solver { sunrealtype *y_data = N_VGetArrayPointer(m_Y); sunrealtype *y_err_data = N_VGetArrayPointer(m_YErr); - const auto absTol = m_config.get("gridfire:solver:CVODESolverStrategy:absTol", 1.0e-8); - const auto relTol = m_config.get("gridfire:solver:CVODESolverStrategy:relTol", 1.0e-8); + const auto absTol = m_config->solver.cvode.absTol; + const auto relTol = m_config->solver.cvode.relTol; std::vector err_ratios; const size_t num_components = N_VGetLength(m_Y); diff --git a/subprojects/fourdst.wrap b/subprojects/fourdst.wrap index 3466edab..a805e371 100644 --- a/subprojects/fourdst.wrap +++ b/subprojects/fourdst.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://github.com/4D-STAR/fourdst -revision = v0.9.13 +revision = v0.9.14 depth = 1 diff --git a/tests/graphnet_sandbox/meson.build b/tests/graphnet_sandbox/meson.build index 41e4300c..ef538396 100644 --- a/tests/graphnet_sandbox/meson.build +++ b/tests/graphnet_sandbox/meson.build @@ -1,5 +1,5 @@ executable( 'graphnet_sandbox', 'main.cpp', - dependencies: [gridfire_dep, composition_dep, cli11_dep], + dependencies: [gridfire_dep, cli11_dep], ) diff --git a/tools/config/generate_config_files.cpp b/tools/config/generate_config_files.cpp new file mode 100644 index 00000000..122faa64 --- /dev/null +++ b/tools/config/generate_config_files.cpp @@ -0,0 +1,48 @@ +#include "fourdst/config/config.h" +#include "gridfire/config/config.h" +#include +#include + +#include "CLI/CLI.hpp" + +consteval std::string_view strip_namespaces(const std::string_view fullName) { + const size_t pos = fullName.rfind("::"); + if (pos == std::string_view::npos) { + return fullName; + } + return fullName.substr(pos + 2); +} + +template +consteval std::string_view get_type_name() { + constexpr std::string_view name = std::source_location::current().function_name(); + const auto pos = name.find("T = "); + if (pos == std::string_view::npos) return name; + const auto start = pos + 4; + const auto end = name.rfind(']'); + return name.substr(start, end - start); +} + +int main(int argc, char** argv) { + CLI::App app{"GridFire Sandbox Application."}; + + std::string outputPath = "."; + + app.add_option("-p,--path", outputPath, "path to save generated config files (default: current directory)"); + + CLI11_PARSE(app, argc, argv); + + const std::filesystem::path outPath(outputPath); + if (!std::filesystem::exists(outPath)) { + std::cerr << "Error: The specified path does not exist: " << outputPath << std::endl; + return 1; + } + + fourdst::config::Config configConfig; + const std::string_view name = strip_namespaces(get_type_name()); + + const std::string defaultConfigFilePath = (outPath / (std::string(name) + ".toml")).string(); + const std::string schemaFilePath = (outPath / (std::string(name) + ".schema.json")).string(); + configConfig.save(defaultConfigFilePath); + configConfig.save_schema(schemaFilePath); +} \ No newline at end of file diff --git a/tools/config/meson.build b/tools/config/meson.build new file mode 100644 index 00000000..f19f277b --- /dev/null +++ b/tools/config/meson.build @@ -0,0 +1 @@ +executable('gf_generate_config_file', 'generate_config_files.cpp', dependencies: [gridfire_dep, cli11_dep], install: true) \ No newline at end of file diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 00000000..75f2745b --- /dev/null +++ b/tools/meson.build @@ -0,0 +1,3 @@ +if get_option('build_tools') + subdir('config') +endif \ No newline at end of file From 4e2b3cb11f3d6d88880c795e21aa106404b71731 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sat, 6 Dec 2025 12:10:43 -0500 Subject: [PATCH 13/14] perf(GraphEngine): using caching clawed back ~10% performance --- src/include/gridfire/engine/engine_graph.h | 1 + src/include/gridfire/reaction/reaction.h | 2 ++ src/lib/engine/engine_graph.cpp | 27 +++++++++++++++++----- src/lib/reaction/reaction.cpp | 7 +++++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/include/gridfire/engine/engine_graph.h b/src/include/gridfire/engine/engine_graph.h index 0e044f7d..4ae79ba2 100644 --- a/src/include/gridfire/engine/engine_graph.h +++ b/src/include/gridfire/engine/engine_graph.h @@ -875,6 +875,7 @@ namespace gridfire::engine { mutable CppAD::ADFun m_rhsADFun; ///< CppAD function for the right-hand side of the ODE. mutable CppAD::ADFun m_epsADFun; ///< CppAD function for the energy generation rate. mutable CppAD::sparse_jac_work m_jac_work; ///< Work object for sparse Jacobian calculations. + mutable std::vector m_local_abundance_cache; bool m_has_been_primed = false; ///< Flag indicating if the engine has been primed. diff --git a/src/include/gridfire/reaction/reaction.h b/src/include/gridfire/reaction/reaction.h index d60d0281..8f1e8ab3 100644 --- a/src/include/gridfire/reaction/reaction.h +++ b/src/include/gridfire/reaction/reaction.h @@ -809,6 +809,8 @@ namespace gridfire::reaction { std::vector m_rates; ///< List of rate coefficient sets from each source. bool m_weak = false; + mutable std::unordered_map m_cached_rates; + private: /** * @brief Template implementation for calculating the total reaction rate. diff --git a/src/lib/engine/engine_graph.cpp b/src/lib/engine/engine_graph.cpp index 7ba0c75b..70151b9d 100644 --- a/src/lib/engine/engine_graph.cpp +++ b/src/lib/engine/engine_graph.cpp @@ -655,6 +655,7 @@ namespace gridfire::engine { } + StepDerivatives GraphEngine::calculateAllDerivativesUsingPrecomputation( const fourdst::composition::CompositionAbstract &comp, const std::vector &bare_rates, @@ -672,8 +673,13 @@ namespace gridfire::engine { T9, rho ); + m_local_abundance_cache.clear(); + for (const auto& species: m_networkSpecies) { + m_local_abundance_cache.push_back(comp.contains(species) ? comp.getMolarAbundance(species) : 0.0); + } StepDerivatives result; + std::vector dydt_scratch(m_networkSpecies.size(), 0.0); // --- Optimized loop --- std::vector molarReactionFlows; @@ -693,15 +699,18 @@ namespace gridfire::engine { const fourdst::atomic::Species& reactant = m_networkSpecies[reactantIndex]; const int power = precomputedReaction.reactant_powers[i]; - if (!comp.contains(reactant)) { - forwardAbundanceProduct = 0.0; - break; // No need to continue if one of the reactants has zero abundance - } - const double factor = std::pow(comp.getMolarAbundance(reactant), power); + const double abundance = m_local_abundance_cache[reactantIndex]; + + double factor; + if (power == 1) { factor = abundance; } + else if (power == 2) { factor = abundance * abundance; } + else { factor = std::pow(abundance, power); } + if (!std::isfinite(factor)) { LOG_CRITICAL(m_logger, "Non-finite factor encountered in forward abundance product for reaction '{}'. Check input abundances for validity.", reaction->id()); throw exceptions::BadRHSEngineError("Non-finite factor encountered in forward abundance product."); } + forwardAbundanceProduct *= factor; } @@ -794,7 +803,8 @@ namespace gridfire::engine { // Update the derivative for this species const double dydt_increment = static_cast(stoichiometricCoefficient) * R_j; - result.dydt.at(species) += dydt_increment; + // result.dydt.at(species) += dydt_increment; + dydt_scratch[speciesIndex] += dydt_increment; if (m_store_intermediate_reaction_contributions) { result.reactionContributions.value()[species][std::string(reaction->id())] = dydt_increment; @@ -803,6 +813,11 @@ namespace gridfire::engine { reactionCounter++; } + // load scratch into result.dydt + for (size_t i = 0; i < m_networkSpecies.size(); ++i) { + result.dydt[m_networkSpecies[i]] = dydt_scratch[i]; + } + // --- Calculate the nuclear energy generation rate --- double massProductionRate = 0.0; // [mol][s^-1] for (const auto & species : m_networkSpecies) { diff --git a/src/lib/reaction/reaction.cpp b/src/lib/reaction/reaction.cpp index debee6f6..310f4a91 100644 --- a/src/lib/reaction/reaction.cpp +++ b/src/lib/reaction/reaction.cpp @@ -278,7 +278,12 @@ namespace gridfire::reaction { double Ye, double mue, const std::vector &Y, const std::unordered_map& index_to_species_map ) const { - return calculate_rate(T9); + if (m_cached_rates.contains(T9)) { + return m_cached_rates.at(T9); + } + const double rate = calculate_rate(T9); + m_cached_rates[T9] = rate; + return rate; } double LogicalReaclibReaction::calculate_log_rate_partial_deriv_wrt_T9( From 67dde830af4dc6c07e1f5deacf8a3be3adc00035 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sat, 6 Dec 2025 13:48:12 -0500 Subject: [PATCH 14/14] perf(openMP): added openMP support Note that currently this actually slows the code down. Spinning up the threads and tearing them down is expensive --- build-check/CPPC/meson.build | 4 + meson_options.txt | 1 + src/include/gridfire/engine/engine_graph.h | 44 ++- src/include/gridfire/reaction/reaction.h | 2 + src/lib/engine/engine_graph.cpp | 386 ++++++++++++++------- src/lib/reaction/reaction.cpp | 4 + src/meson.build | 5 + 7 files changed, 314 insertions(+), 132 deletions(-) diff --git a/build-check/CPPC/meson.build b/build-check/CPPC/meson.build index 12a33c54..070401de 100644 --- a/build-check/CPPC/meson.build +++ b/build-check/CPPC/meson.build @@ -31,3 +31,7 @@ else message('enabling default visibility for C++ symbols') add_project_arguments('-fvisibility=default', language: 'cpp') endif + +if get_option('openmp_support') + add_project_arguments('-DGRIDFIRE_USE_OPENMP', language: 'cpp') +endif diff --git a/meson_options.txt b/meson_options.txt index ffaaf308..cbee156c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -9,3 +9,4 @@ option('plugin_support', type: 'boolean', value: false, description: 'Enable sup option('python_target_version', type: 'string', value: '3.13', description: 'Target version for python compilation, only used for cross compilation') option('build_c_api', type: 'boolean', value: true, description: 'compile the C API') option('build_tools', type: 'boolean', value: true, description: 'build the GridFire command line tools') +option('openmp_support', type: 'boolean', value: false, description: 'Enable OpenMP support for parallelization') \ No newline at end of file diff --git a/src/include/gridfire/engine/engine_graph.h b/src/include/gridfire/engine/engine_graph.h index 4ae79ba2..5698cd47 100644 --- a/src/include/gridfire/engine/engine_graph.h +++ b/src/include/gridfire/engine/engine_graph.h @@ -97,7 +97,7 @@ namespace gridfire::engine { * * @see engine_abstract.h */ - class GraphEngine final : public DynamicEngine{ + class GraphEngine final : public DynamicEngine { public: /** * @brief Constructs a GraphEngine from a composition. @@ -855,6 +855,12 @@ namespace gridfire::engine { const reaction::Reaction& m_reaction; const GraphEngine& m_engine; }; + + struct PrecomputationKernelResults { + std::vector dydt_vector; + double total_neutrino_energy_loss_rate{0.0}; + double total_neutrino_flux{0.0}; + }; private: Config m_config; quill::Logger* m_logger = LogManager::getInstance().getLogger("log"); @@ -959,6 +965,42 @@ namespace gridfire::engine { */ [[nodiscard]] bool validateConservation() const; + double compute_reaction_flow( + const std::vector &local_abundances, + const std::vector &screening_factors, + const std::vector &bare_rates, + const std::vector &bare_reverse_rates, + double rho, + size_t reactionCounter, + const reaction::Reaction &reaction, + size_t reactionIndex, + const PrecomputedReaction &precomputedReaction + ) const; + + std::pair compute_neutrino_fluxes( + double netFlow, + const reaction::Reaction &reaction) const; + + PrecomputationKernelResults accumulate_flows_serial( + const std::vector& local_abundances, + const std::vector& screening_factors, + const std::vector& bare_rates, + const std::vector& bare_reverse_rates, + double rho, + const reaction::ReactionSet& activeReactions + ) const; + +#ifdef GRIDFIRE_USE_OPENMP + PrecomputationKernelResults accumulate_flows_parallel( + const std::vector& local_abundances, + const std::vector& screening_factors, + const std::vector& bare_rates, + const std::vector& bare_reverse_rates, + double rho, + const reaction::ReactionSet& activeReactions + ) const; +#endif + [[nodiscard]] StepDerivatives calculateAllDerivativesUsingPrecomputation( const fourdst::composition::CompositionAbstract &comp, diff --git a/src/include/gridfire/reaction/reaction.h b/src/include/gridfire/reaction/reaction.h index 8f1e8ab3..b6ce7345 100644 --- a/src/include/gridfire/reaction/reaction.h +++ b/src/include/gridfire/reaction/reaction.h @@ -878,6 +878,8 @@ namespace gridfire::reaction { [[nodiscard]] std::optional> get(const std::string_view& id) const; + [[nodiscard]] std::unique_ptr get(size_t index) const; + /** * @brief Removes a reaction from the set. * @param reaction The Reaction to remove. diff --git a/src/lib/engine/engine_graph.cpp b/src/lib/engine/engine_graph.cpp index 70151b9d..e1999e87 100644 --- a/src/lib/engine/engine_graph.cpp +++ b/src/lib/engine/engine_graph.cpp @@ -28,6 +28,10 @@ #include "cppad/utility/sparse_rc.hpp" #include "cppad/utility/sparse_rcv.hpp" +#ifdef GRIDFIRE_USE_OPENMP + #include +#endif + namespace { enum class REACLIB_WEAK_TYPES { @@ -403,6 +407,167 @@ namespace gridfire::engine { return true; // All reactions passed the conservation check } + double GraphEngine::compute_reaction_flow( + const std::vector &local_abundances, + const std::vector &screening_factors, + const std::vector &bare_rates, + const std::vector &bare_reverse_rates, + const double rho, + const size_t reactionCounter, + const reaction::Reaction &reaction, + const size_t reactionIndex, + const PrecomputedReaction &precomputedReaction + ) const { + double forwardAbundanceProduct = 1.0; + for (size_t i = 0; i < precomputedReaction.unique_reactant_indices.size(); ++i) { + const size_t reactantIndex = precomputedReaction.unique_reactant_indices[i]; + const int power = precomputedReaction.reactant_powers[i]; + + const double abundance = local_abundances[reactantIndex]; + + double factor; + if (power == 1) { factor = abundance; } + else if (power == 2) { factor = abundance * abundance; } + else { factor = std::pow(abundance, power); } + + if (!std::isfinite(factor)) { + LOG_CRITICAL(m_logger, "Non-finite factor encountered in forward abundance product for reaction '{}'. Check input abundances for validity.", reaction.id()); + throw exceptions::BadRHSEngineError("Non-finite factor encountered in forward abundance product."); + } + + forwardAbundanceProduct *= factor; + } + + const double bare_rate = bare_rates.at(reactionCounter); + + const double screeningFactor = screening_factors[reactionCounter]; + const size_t numReactants = m_reactions[reactionIndex].reactants().size(); + const size_t numProducts = m_reactions[reactionIndex].products().size(); + + const double forwardMolarReactionFlow = screeningFactor * + bare_rate * + precomputedReaction.symmetry_factor * + forwardAbundanceProduct * + std::pow(rho, numReactants > 1 ? static_cast(numReactants) - 1 : 0.0); + if (!std::isfinite(forwardMolarReactionFlow)) { + LOG_CRITICAL(m_logger, "Non-finite forward molar reaction flow computed for reaction '{}'. Check input abundances and rates for validity.", reaction.id()); + throw exceptions::BadRHSEngineError("Non-finite forward molar reaction flow computed."); + } + + + double reverseMolarReactionFlow = 0.0; + if (precomputedReaction.reverse_symmetry_factor != 0.0 and m_useReverseReactions) { + const double bare_reverse_rate = bare_reverse_rates.at(reactionCounter); + + double reverseAbundanceProduct = 1.0; + for (size_t i = 0; i < precomputedReaction.unique_product_indices.size(); ++i) { + const size_t productIndex = precomputedReaction.unique_product_indices[i]; + reverseAbundanceProduct *= std::pow(local_abundances[productIndex], precomputedReaction.product_powers[i]); + } + + reverseMolarReactionFlow = screeningFactor * + bare_reverse_rate * + precomputedReaction.reverse_symmetry_factor * + reverseAbundanceProduct * + std::pow(rho, numProducts > 1 ? static_cast(numProducts) - 1 : 0.0); + } + + return forwardMolarReactionFlow - reverseMolarReactionFlow; + } + + std::pair GraphEngine::compute_neutrino_fluxes( + const double netFlow, + const reaction::Reaction &reaction + ) const { + if (reaction.type() == reaction::ReactionType::REACLIB_WEAK) { + const double q_abs = std::abs(reaction.qValue()); + const REACLIB_WEAK_TYPES weakType = get_weak_reaclib_reaction_type(reaction); + double neutrino_loss_fraction = 0.0; + switch (weakType) { + case REACLIB_WEAK_TYPES::BETA_PLUS_DECAY: + [[fallthrough]]; + case REACLIB_WEAK_TYPES::BETA_MINUS_DECAY: + neutrino_loss_fraction = 0.5; // Approximate 50% energy loss to neutrinos for beta decays + break; + case REACLIB_WEAK_TYPES::ELECTRON_CAPTURE: + [[fallthrough]]; + case REACLIB_WEAK_TYPES::POSITRON_CAPTURE: + neutrino_loss_fraction = 1.0; + break; + default: ; + } + + const double local_neutrino_loss = netFlow * q_abs * neutrino_loss_fraction * m_constants.Na * m_constants.MeV_to_erg; + const double local_neutrino_flux = netFlow * m_constants.Na; + + return {local_neutrino_loss, local_neutrino_flux}; + } + return {0.0, 0.0}; + } + + GraphEngine::PrecomputationKernelResults GraphEngine::accumulate_flows_serial( + const std::vector &local_abundances, + const std::vector &screening_factors, + const std::vector &bare_rates, + const std::vector &bare_reverse_rates, + const double rho, + const reaction::ReactionSet &activeReactions + ) const { + PrecomputationKernelResults results; + results.dydt_vector.resize(m_networkSpecies.size(), 0.0); + + std::vector molarReactionFlows; + molarReactionFlows.reserve(m_precomputedReactions.size()); + + size_t reactionCounter = 0; + + for (const auto& reaction : activeReactions) { + uint64_t reactionHash = utils::hash_reaction(*reaction); + const size_t reactionIndex = m_precomputedReactionIndexMap.at(reactionHash); + const PrecomputedReaction& precomputedReaction = m_precomputedReactions[reactionIndex]; + + double netFlow = compute_reaction_flow( + local_abundances, + screening_factors, + bare_rates, + bare_reverse_rates, + rho, + reactionCounter, + *reaction, + reactionIndex, + precomputedReaction); + + molarReactionFlows.push_back(netFlow); + + auto [local_neutrino_loss, local_neutrino_flux] = compute_neutrino_fluxes(netFlow, *reaction); + results.total_neutrino_energy_loss_rate += local_neutrino_loss; + results.total_neutrino_flux += local_neutrino_flux; + + reactionCounter++; + } + + LOG_TRACE_L3(m_logger, "Computed {} molar reaction flows for active reactions. Assembling these into RHS", molarReactionFlows.size()); + + reactionCounter = 0; + for (const auto& reaction: activeReactions) { + const size_t j = m_precomputedReactionIndexMap.at(utils::hash_reaction(*reaction)); + const auto& precomp = m_precomputedReactions[j]; + const double R_j = molarReactionFlows[reactionCounter]; + + for (size_t i = 0; i < precomp.affected_species_indices.size(); ++i) { + const size_t speciesIndex = precomp.affected_species_indices[i]; + + const int stoichiometricCoefficient = precomp.stoichiometric_coefficients[i]; + + const double dydt_increment = static_cast(stoichiometricCoefficient) * R_j; + results.dydt_vector[speciesIndex] += dydt_increment; + } + reactionCounter++; + } + + return results; + } + double GraphEngine::calculateReverseRate( const reaction::Reaction &reaction, const double T9, @@ -681,137 +846,31 @@ namespace gridfire::engine { StepDerivatives result; std::vector dydt_scratch(m_networkSpecies.size(), 0.0); - // --- Optimized loop --- - std::vector molarReactionFlows; - molarReactionFlows.reserve(m_precomputedReactions.size()); - - size_t reactionCounter = 0; - for (const auto& reaction : activeReactions) { - // --- Efficient lookup of only the active reactions --- - uint64_t reactionHash = utils::hash_reaction(*reaction); - const size_t reactionIndex = m_precomputedReactionIndexMap.at(reactionHash); - const PrecomputedReaction& precomputedReaction = m_precomputedReactions[reactionIndex]; - - // --- Forward abundance product --- - double forwardAbundanceProduct = 1.0; - for (size_t i = 0; i < precomputedReaction.unique_reactant_indices.size(); ++i) { - const size_t reactantIndex = precomputedReaction.unique_reactant_indices[i]; - const fourdst::atomic::Species& reactant = m_networkSpecies[reactantIndex]; - const int power = precomputedReaction.reactant_powers[i]; - - const double abundance = m_local_abundance_cache[reactantIndex]; - - double factor; - if (power == 1) { factor = abundance; } - else if (power == 2) { factor = abundance * abundance; } - else { factor = std::pow(abundance, power); } - - if (!std::isfinite(factor)) { - LOG_CRITICAL(m_logger, "Non-finite factor encountered in forward abundance product for reaction '{}'. Check input abundances for validity.", reaction->id()); - throw exceptions::BadRHSEngineError("Non-finite factor encountered in forward abundance product."); - } - - forwardAbundanceProduct *= factor; - } - - const double bare_rate = bare_rates.at(reactionCounter); - - const double screeningFactor = screeningFactors[reactionCounter]; - const size_t numReactants = m_reactions[reactionIndex].reactants().size(); - const size_t numProducts = m_reactions[reactionIndex].products().size(); - - // --- Forward reaction flow --- - const double forwardMolarReactionFlow = - screeningFactor * - bare_rate * - precomputedReaction.symmetry_factor * - forwardAbundanceProduct * - std::pow(rho, numReactants > 1 ? static_cast(numReactants) - 1 : 0.0); - if (!std::isfinite(forwardMolarReactionFlow)) { - LOG_CRITICAL(m_logger, "Non-finite forward molar reaction flow computed for reaction '{}'. Check input abundances and rates for validity.", reaction->id()); - throw exceptions::BadRHSEngineError("Non-finite forward molar reaction flow computed."); - } - - - // --- Reverse reaction flow --- - // Only do this is the reaction has a non-zero reverse symmetry factor (i.e. is reversible) - double reverseMolarReactionFlow = 0.0; - if (precomputedReaction.reverse_symmetry_factor != 0.0 and m_useReverseReactions) { - const double bare_reverse_rate = bare_reverse_rates.at(reactionCounter); - - double reverseAbundanceProduct = 1.0; - for (size_t i = 0; i < precomputedReaction.unique_product_indices.size(); ++i) { - const size_t productIndex = precomputedReaction.unique_product_indices[i]; - const fourdst::atomic::Species& product = m_networkSpecies[productIndex]; - reverseAbundanceProduct *= std::pow(comp.getMolarAbundance(product), precomputedReaction.product_powers[i]); - } - - reverseMolarReactionFlow = screeningFactor * - bare_reverse_rate * - precomputedReaction.reverse_symmetry_factor * - reverseAbundanceProduct * - std::pow(rho, numProducts > 1 ? static_cast(numProducts) - 1 : 0.0); - } - - molarReactionFlows.push_back(forwardMolarReactionFlow - reverseMolarReactionFlow); - - if (reaction->type() == reaction::ReactionType::REACLIB_WEAK) { - double q_abs = std::abs(reaction->qValue()); - REACLIB_WEAK_TYPES weakType = get_weak_reaclib_reaction_type(*reaction); - double neutrino_loss_fraction = 0.0; - switch (weakType) { - case REACLIB_WEAK_TYPES::BETA_PLUS_DECAY: - [[fallthrough]]; - case REACLIB_WEAK_TYPES::BETA_MINUS_DECAY: - neutrino_loss_fraction = 0.5; // Approximate 50% energy loss to neutrinos for beta decays - break; - case REACLIB_WEAK_TYPES::ELECTRON_CAPTURE: - [[fallthrough]]; - case REACLIB_WEAK_TYPES::POSITRON_CAPTURE: - neutrino_loss_fraction = 1.0; - break; - default: ; - } - - const double local_neutrino_loss = molarReactionFlows.back() * q_abs * neutrino_loss_fraction * m_constants.Na * m_constants.MeV_to_erg; - const double local_neutrino_flux = molarReactionFlows.back() * m_constants.Na; - - result.totalNeutrinoFlux += local_neutrino_flux; - result.neutrinoEnergyLossRate += local_neutrino_loss; - } - reactionCounter++; - } - - LOG_TRACE_L3(m_logger, "Computed {} molar reaction flows for active reactions. Assembling these into RHS", molarReactionFlows.size()); - - // --- Assemble molar abundance derivatives --- - for (const auto& species: m_networkSpecies) { - result.dydt[species] = 0.0; // Initialize the change in abundance for each network species to 0 - } - - reactionCounter = 0; - for (const auto& reaction: activeReactions) { - const size_t j = m_precomputedReactionIndexMap.at(utils::hash_reaction(*reaction)); - const auto& precomp = m_precomputedReactions[j]; - const double R_j = molarReactionFlows[reactionCounter]; - - for (size_t i = 0; i < precomp.affected_species_indices.size(); ++i) { - const size_t speciesIndex = precomp.affected_species_indices[i]; - const fourdst::atomic::Species& species = m_networkSpecies[speciesIndex]; - - const int stoichiometricCoefficient = precomp.stoichiometric_coefficients[i]; - - // Update the derivative for this species - const double dydt_increment = static_cast(stoichiometricCoefficient) * R_j; - // result.dydt.at(species) += dydt_increment; - dydt_scratch[speciesIndex] += dydt_increment; - - if (m_store_intermediate_reaction_contributions) { - result.reactionContributions.value()[species][std::string(reaction->id())] = dydt_increment; - } - } - reactionCounter++; - } +#ifndef GRIDFIRE_USE_OPENMP + const auto [dydt_vector, total_neutrino_energy_loss_rate, total_neutrino_flux] = accumulate_flows_serial( + m_local_abundance_cache, + screeningFactors, + bare_rates, + bare_reverse_rates, + rho, + activeReactions + ); + dydt_scratch = dydt_vector; + result.neutrinoEnergyLossRate = total_neutrino_energy_loss_rate; + result.totalNeutrinoFlux = total_neutrino_flux; +#else + const auto [dydt_vector, total_neutrino_energy_loss_rate, total_neutrino_flux] = accumulate_flows_parallel( + m_local_abundance_cache, + screeningFactors, + bare_rates, + bare_reverse_rates, + rho, + activeReactions + ); + dydt_scratch = dydt_vector; + result.neutrinoEnergyLossRate = total_neutrino_energy_loss_rate; + result.totalNeutrinoFlux = total_neutrino_flux; +#endif // load scratch into result.dydt for (size_t i = 0; i < m_networkSpecies.size(); ++i) { @@ -1520,4 +1579,69 @@ namespace gridfire::engine { return true; } + +#ifdef GRIDFIRE_USE_OPENMP + GraphEngine::PrecomputationKernelResults GraphEngine::accumulate_flows_parallel( + const std::vector &local_abundances, + const std::vector &screening_factors, + const std::vector &bare_rates, + const std::vector &bare_reverse_rates, + const double rho, + const reaction::ReactionSet &activeReactions + ) const { + int n_threads = omp_get_max_threads(); + std::vector> thread_local_dydt(n_threads, std::vector(m_networkSpecies.size(), 0.0)); + + double total_neutrino_energy_loss_rate = 0.0; + double total_neutrino_flux = 0.0; + + #pragma omp parallel for schedule(static) reduction(+:total_neutrino_energy_loss_rate, total_neutrino_flux) + for (size_t k = 0; k < activeReactions.size(); ++k) { + int t_id = omp_get_thread_num(); + const auto& reaction = activeReactions[k]; + const size_t reactionIndex = m_precomputedReactionIndexMap.at(utils::hash_reaction(reaction)); + const PrecomputedReaction& precomputedReaction = m_precomputedReactions[reactionIndex]; + + double netFlow = compute_reaction_flow( + local_abundances, + screening_factors, + bare_rates, + bare_reverse_rates, + rho, + reactionIndex, + reaction, + reactionIndex, + precomputedReaction + ); + + auto [neutrinoEnergyLossRate, neutrinoFlux] = compute_neutrino_fluxes( + netFlow, + reaction + ); + + total_neutrino_energy_loss_rate += neutrinoEnergyLossRate; + total_neutrino_flux += neutrinoFlux; + + for (size_t i = 0; i < precomputedReaction.affected_species_indices.size(); ++i) { + thread_local_dydt[t_id][precomputedReaction.affected_species_indices[i]] += + netFlow * precomputedReaction.stoichiometric_coefficients[i]; + } + + } + PrecomputationKernelResults results; + results.total_neutrino_energy_loss_rate = total_neutrino_energy_loss_rate; + results.total_neutrino_flux = total_neutrino_flux; + + results.dydt_vector.resize(m_networkSpecies.size(), 0.0); + #pragma omp parallel for schedule(static) + for (size_t i = 0; i < m_networkSpecies.size(); ++i) { + double sum = 0.0; + for (int t = 0; t < n_threads; ++t) sum += thread_local_dydt[t][i]; + results.dydt_vector[i] = sum; + } + + return results; + } +#endif + } diff --git a/src/lib/reaction/reaction.cpp b/src/lib/reaction/reaction.cpp index 310f4a91..2cb9ef2e 100644 --- a/src/lib/reaction/reaction.cpp +++ b/src/lib/reaction/reaction.cpp @@ -460,6 +460,10 @@ namespace gridfire::reaction { return std::make_optional(m_reactions[m_reactionNameMap.at(std::string(id))]->clone()); } + std::unique_ptr ReactionSet::get(size_t index) const { + return m_reactions.at(index)->clone(); + } + void ReactionSet::remove_reaction(const Reaction& reaction) { const size_t rh = reaction.hash(0); if (!m_reactionHashes.contains(rh)) { diff --git a/src/meson.build b/src/meson.build index 3f08190e..4fa4fcea 100644 --- a/src/meson.build +++ b/src/meson.build @@ -46,6 +46,11 @@ if get_option('plugin_support') gridfire_build_dependencies += [plugin_dep] endif +if get_option('openmp_support') + openmp_dep = dependency('openmp', required: true) + gridfire_build_dependencies += [openmp_dep] +endif + # Define the libnetwork library so it can be linked against by other parts of the build system libgridfire = library('gridfire', gridfire_sources,