Skip to content

Commit edff810

Browse files
committed
Add chemistry support to MFC with Spack compatibility
This commit enables thermochemistry in MFC via a new MFC_CHEMISTRY CMake option and adds full Spack package support for the chemistry variant. Changes: CMake (CMakeLists.txt): - Add MFC_CHEMISTRY option (default OFF) to enable chemistry - Add MFC_MECH_FILE cache variable for specifying Cantera mechanism files - Pass chemistry flag to Fypp preprocessor as -D chemistry=True/False - Add Python3 requirement when MFC_CHEMISTRY=ON - Generate m_thermochem.f90 via gen_thermochem.py during build Python (toolchain/scripts/gen_thermochem.py): - New script to generate Fortran chemistry module using Pyrometheus and Cantera - Called by CMake when MFC_CHEMISTRY=ON - Supports custom mechanism files via --mech argument Fortran (src/common/m_derived_types.fpp): - Guard m_thermochem import with #:if chemistry - Move fallback num_species declaration after implicit none - Fixes "IMPLICIT NONE after data declaration" error Build system (toolchain/mfc/build.py): - Auto-enable MFC_CHEMISTRY=ON when case requests chemistry - Pass MFC_MECH_FILE when cantera_file is specified in case Spack (packaging/spack/package.py): - Add chemistry variant (default False) - Add cantera+python dependency when +chemistry - Vendor Pyrometheus 1.0.5 as a resource - Pass MFC_CHEMISTRY to CMake via cmake_args - Add Pyrometheus to PYTHONPATH in setup_build_environment Documentation (packaging/spack/SPACK.md): - Document chemistry variant and usage - List chemistry dependencies This preserves backward compatibility: existing builds work unchanged (chemistry OFF by default), while enabling reproducible chemistry builds via Spack with "spack install mfc+chemistry".
1 parent 0846330 commit edff810

6 files changed

Lines changed: 133 additions & 3 deletions

File tree

CMakeLists.txt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ endif()
9595

9696
find_program(FYPP_EXE fypp REQUIRED)
9797

98+
# Python3 is required for chemistry code generation when MFC_CHEMISTRY is ON
99+
if (MFC_CHEMISTRY)
100+
find_package(Python3 REQUIRED COMPONENTS Interpreter)
101+
endif()
102+
98103

99104
# Miscellaneous Configuration:
100105
# * Explicitly link to -ldl (or system equivalent)
@@ -372,7 +377,7 @@ macro(HANDLE_SOURCES target useCommon)
372377
-D MFC_${${target}_UPPER}
373378
-D MFC_COMPILER="${CMAKE_Fortran_COMPILER_ID}"
374379
-D MFC_CASE_OPTIMIZATION=False
375-
-D chemistry=False
380+
-D chemistry=${_chem_str}
376381
--line-numbering
377382
--no-folding
378383
--line-length=999
@@ -385,6 +390,27 @@ macro(HANDLE_SOURCES target useCommon)
385390

386391
list(APPEND ${target}_SRCs ${f90})
387392
endforeach()
393+
394+
# Generate m_thermochem.f90 when chemistry is enabled
395+
if (MFC_CHEMISTRY)
396+
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/modules/${target}")
397+
398+
add_custom_command(
399+
OUTPUT "${CMAKE_BINARY_DIR}/modules/${target}/m_thermochem.f90"
400+
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/modules/${target}"
401+
COMMAND ${Python3_EXECUTABLE}
402+
"${CMAKE_SOURCE_DIR}/toolchain/scripts/gen_thermochem.py"
403+
--module m_thermochem
404+
--out "${CMAKE_BINARY_DIR}/modules/${target}/m_thermochem.f90"
405+
--mech "${MFC_MECH_FILE}"
406+
DEPENDS "${CMAKE_SOURCE_DIR}/toolchain/scripts/gen_thermochem.py"
407+
COMMENT "Generating m_thermochem.f90 via Pyrometheus/Cantera for ${target}"
408+
VERBATIM
409+
)
410+
411+
list(APPEND ${target}_SRCs
412+
"${CMAKE_BINARY_DIR}/modules/${target}/m_thermochem.f90")
413+
endif()
388414
endmacro()
389415

390416

packaging/spack/SPACK.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ spack install mfc~post_process # Skip post_process binary
8888
```
8989
Controls whether the post-processing tool is built.
9090

91+
#### Chemistry (default: disabled)
92+
```
93+
spack install mfc+chemistry
94+
```
95+
Enables thermochemistry support by generating the `m_thermochem.f90` module using Pyrometheus and Cantera during the build. When enabled, MFC can perform reactive flow simulations with detailed chemical kinetics. Requires `cantera+python` and vendors Pyrometheus automatically.
96+
9197
### Dependencies
9298

9399
Build-time dependencies (required during compilation):
@@ -103,6 +109,8 @@ Optional dependencies (variant-controlled):
103109
- mpi - Message Passing Interface (when +mpi)
104110
- silo - Silo data format with HDF5 support (when +post_process)
105111
- hdf5 - HDF5 data format (transitive dependency via Silo when +post_process)
112+
- cantera+python - Thermochemical kinetics library (when +chemistry)
113+
- pyrometheus - Fortran code generator for chemistry (vendored automatically when +chemistry)
106114
- cuda - NVIDIA CUDA toolkit (when +openacc or +openmp with NVHPC)
107115
- hip - AMD ROCm HIP (when +openacc or +openmp with Cray)
108116

packaging/spack/package.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,24 @@ class Mfc(CMakePackage):
3636
description="Floating point precision",
3737
)
3838
variant("post_process", default=True, description="Build post-processing tool")
39+
variant("chemistry", default=False, description="Enable thermochemistry via Pyrometheus/Cantera")
3940

4041
# Required dependencies
4142
depends_on("cmake@3.20:", type="build")
4243
depends_on("py-fypp", type="build")
4344
depends_on("python@3:", type="build")
44-
# Note: py-cantera is not yet available in Spack builtin repo
45-
# Will add when chemistry variant is implemented
45+
46+
# Chemistry dependencies
47+
depends_on("cantera+python", type="build", when="+chemistry")
48+
# Note: py-pyrometheus may not be in Spack yet; will be added to PYTHONPATH via resource
49+
resource(
50+
name="pyrometheus",
51+
url="https://files.pythonhosted.org/packages/21/77/1e48bef25dfef5d9e35c1ab3a3a2ea1c82adb59aceb82b18d13b3d6c8a2b/pyrometheus-1.0.5.tar.gz",
52+
sha256="a572ab6db954f4a850d1292bb1ef6d6055916784a894d149d657996fa98d0367",
53+
when="+chemistry",
54+
placement="pydeps/pyrometheus",
55+
expand=True
56+
)
4657

4758
# Runtime dependencies
4859
depends_on("fftw@3:", when="~mpi")
@@ -80,6 +91,7 @@ def cmake_args(self):
8091
self.define("MFC_PRE_PROCESS", True),
8192
self.define("MFC_SIMULATION", True),
8293
self.define_from_variant("MFC_POST_PROCESS", "post_process"),
94+
self.define_from_variant("MFC_CHEMISTRY", "chemistry"),
8395
]
8496

8597
if self.spec.variants["precision"].value == "single":
@@ -90,3 +102,7 @@ def cmake_args(self):
90102
def setup_build_environment(self, env):
91103
# Fypp is required for preprocessing
92104
env.prepend_path("PATH", self.spec["py-fypp"].prefix.bin)
105+
106+
# Make vendored Pyrometheus importable when chemistry is enabled
107+
if "+chemistry" in self.spec:
108+
env.prepend_path("PYTHONPATH", join_path(self.stage.source_path, "pydeps", "pyrometheus"))

src/common/m_derived_types.fpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ module m_derived_types
1111
use m_constants !< Constants
1212

1313
use m_precision_select
14+
#:if chemistry
1415
use m_thermochem, only: num_species
16+
#:endif
1517

1618
implicit none
1719

20+
#:if not chemistry
21+
integer, parameter :: num_species = 1
22+
#:endif
23+
1824
!> Derived type adding the field position (fp) as an attribute
1925
type field_position
2026
real(stp), allocatable, dimension(:, :, :) :: fp !< Field position

toolchain/mfc/build.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ def configure(self, case: Case):
154154
flags.append(f"-DMFC_Unified={'ON' if ARG('unified') else 'OFF'}")
155155
flags.append(f"-DMFC_Fastmath={'ON' if ARG('fastmath') else 'OFF'}")
156156

157+
# Enable chemistry flags when requested by the case
158+
if case.params.get('chemistry', 'F') == 'T':
159+
flags.append("-DMFC_CHEMISTRY=ON")
160+
# Pass mechanism if provided; otherwise Cantera defaults (e.g., h2o2.yaml)
161+
mech = case.params.get("cantera_file", "")
162+
if mech:
163+
flags.append(f"-DMFC_MECH_FILE={mech}")
164+
157165
command = ["cmake"] + flags + ["-S", cmake_dirpath, "-B", build_dirpath]
158166

159167
delete_directory(build_dirpath)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate m_thermochem.f90 module using Pyrometheus and Cantera.
4+
5+
This script is called by CMake when MFC_CHEMISTRY=ON to generate
6+
the Fortran thermochemistry module from a Cantera mechanism file.
7+
"""
8+
import argparse
9+
import sys
10+
11+
def main():
12+
parser = argparse.ArgumentParser(
13+
description="Generate Fortran thermochemistry module via Pyrometheus"
14+
)
15+
parser.add_argument("--module", required=True, help="Module name (e.g., m_thermochem)")
16+
parser.add_argument("--out", required=True, help="Output Fortran file path")
17+
parser.add_argument(
18+
"--mech",
19+
default="h2o2.yaml",
20+
help="Cantera mechanism file (YAML). Defaults to h2o2.yaml"
21+
)
22+
args = parser.parse_args()
23+
24+
try:
25+
import cantera as ct
26+
except ImportError:
27+
print("ERROR: cantera Python package is required for chemistry.", file=sys.stderr)
28+
print("Install it with: pip install cantera", file=sys.stderr)
29+
sys.exit(1)
30+
31+
try:
32+
import pyrometheus as pyro
33+
except ImportError:
34+
print("ERROR: pyrometheus Python package is required for chemistry.", file=sys.stderr)
35+
print("Install it with: pip install pyrometheus", file=sys.stderr)
36+
sys.exit(1)
37+
38+
# Load the Cantera solution
39+
# If no mechanism file is specified or it's empty, use Cantera's default h2o2.yaml
40+
mech_file = args.mech if args.mech else "h2o2.yaml"
41+
42+
try:
43+
sol = ct.Solution(mech_file)
44+
except Exception as e:
45+
print(f"ERROR: Failed to load Cantera mechanism '{mech_file}': {e}", file=sys.stderr)
46+
sys.exit(1)
47+
48+
# Generate Fortran code using Pyrometheus
49+
try:
50+
code = pyro.FortranCodeGenerator().generate(args.module, sol)
51+
except Exception as e:
52+
print(f"ERROR: Failed to generate Fortran code: {e}", file=sys.stderr)
53+
sys.exit(1)
54+
55+
# Write to output file
56+
try:
57+
with open(args.out, 'w') as f:
58+
f.write(code)
59+
print(f"Successfully generated {args.out} from {mech_file}")
60+
except Exception as e:
61+
print(f"ERROR: Failed to write output file '{args.out}': {e}", file=sys.stderr)
62+
sys.exit(1)
63+
64+
if __name__ == "__main__":
65+
main()
66+

0 commit comments

Comments
 (0)