From 3b119537bcf1041521c93f5bb68d5caeb231f33d Mon Sep 17 00:00:00 2001 From: carrae Date: Mon, 13 Jul 2026 17:03:26 +0200 Subject: [PATCH 01/10] First cmake version (Pitagora) --- .gitignore | 4 + CMakeLists.txt | 167 +++++++++++++ CMakePresets.json | 72 ++++++ cmake/GenerateVersionHeader.cmake | 81 ++++++ cmake/JorekCompilerFlags.cmake | 77 ++++++ cmake/JorekDependencies.cmake | 254 +++++++++++++++++++ cmake/JorekModelConfig.cmake | 152 ++++++++++++ cmake/JorekOptions.cmake | 94 +++++++ cmake/JorekSources.cmake | 396 ++++++++++++++++++++++++++++++ cmake/JorekSummary.cmake | 32 +++ cmake/JorekVersion.cmake | 47 ++++ 11 files changed, 1376 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 cmake/GenerateVersionHeader.cmake create mode 100644 cmake/JorekCompilerFlags.cmake create mode 100644 cmake/JorekDependencies.cmake create mode 100644 cmake/JorekModelConfig.cmake create mode 100644 cmake/JorekOptions.cmake create mode 100644 cmake/JorekSources.cmake create mode 100644 cmake/JorekSummary.cmake create mode 100644 cmake/JorekVersion.cmake diff --git a/.gitignore b/.gitignore index aec1bddfbb..b9cb7e3c51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# CMake out-of-source builds (cmake --preset puts them under build/) +/build/ +CMakeUserPresets.json + *.bck *.o *.mod diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..3ba7ceba91 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,167 @@ +cmake_minimum_required(VERSION 3.20) + +# The version header embeds the compile flags, so languages must be enabled +# before we can generate it. Fortran is the primary language; C and C++ are +# needed for tools/*.c, the r-tree, the sparse-solver shims and Catalyst. +project(JOREK + LANGUAGES Fortran C CXX + DESCRIPTION "Non-linear extended MHD code for toroidal X-point geometries") + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR + "In-source builds are not supported. Configure into a separate directory, " + "e.g. cmake --preset pitagora-intel") +endif() + +set(CMAKE_Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/modules") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +include(JorekOptions) # cache options, replaces Makefile.inc switches +include(JorekModelConfig) # derive -D flags from the model settings files +include(JorekCompilerFlags) # per-compiler flag sets, replaces defaults.mk +include(JorekDependencies) # MPI, HDF5, FFTW, BLAS/LAPACK, sparse solvers +include(JorekVersion) # git-derived version.h +include(JorekSources) # source discovery, program/library split + +# --- Usage requirements shared by every JOREK target ------------------------ +add_library(jorek_common INTERFACE) +target_include_directories(jorek_common INTERFACE + "${CMAKE_BINARY_DIR}/generated" # version.h + "${JOREK_GENERATED_INCLUDE_DIR}" # rhs/amat/aux_automatic.h + "${CMAKE_CURRENT_SOURCE_DIR}/tools" # r3_info.h + "${CMAKE_CURRENT_SOURCE_DIR}/models" + ${JOREK_INCLUDE_DIRS}) +target_compile_definitions(jorek_common INTERFACE ${JOREK_DEFINES}) +target_link_libraries(jorek_common INTERFACE ${JOREK_LIBRARIES}) + +# --- The code generator (semianalytical models only) ------------------------ +# +# Built before, and independently of, the core library -- see JorekSources for +# why it cannot simply link against it. Its objects are compiled a second time +# into jorek_core; that is a dozen files, and it keeps the graph acyclic. +if(JOREK_SEMIANALYTICAL) + add_library(jorek_codegen STATIC ${JOREK_CODEGEN_SOURCES}) + add_dependencies(jorek_codegen jorek_version_header) + target_link_libraries(jorek_codegen PRIVATE jorek_common) + # Its own module directory: these sources are also compiled into jorek_core, + # and the two must not fight over the same .mod files. + set_target_properties(jorek_codegen PROPERTIES + Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/modules_codegen") + + # algexpr2fort evaluates the model's symbolic equations and writes the + # RHS/AMAT/AUX Fortran that mod_elt_matrix{,_fft}.f90 #include. It hard-codes + # its output path as "models/model/.h" relative to the working + # directory, so run it from the build tree to keep the source tree clean. + add_executable(algexpr2fort "${CMAKE_CURRENT_SOURCE_DIR}/models/algexpr2fort.f90") + target_link_libraries(algexpr2fort PRIVATE jorek_codegen jorek_common) + set_target_properties(algexpr2fort PROPERTIES + Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/modules_codegen" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + LINKER_LANGUAGE Fortran) + + set(JOREK_GENERATED_HEADERS + "${JOREK_GENERATED_INCLUDE_DIR}/rhs_automatic.h" + "${JOREK_GENERATED_INCLUDE_DIR}/amat_automatic.h" + "${JOREK_GENERATED_INCLUDE_DIR}/aux_automatic.h") + + add_custom_command( + OUTPUT ${JOREK_GENERATED_HEADERS} + COMMAND "$" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/generated" + DEPENDS algexpr2fort + COMMENT "Generating element-matrix code for ${JOREK_MODEL}" + VERBATIM) + add_custom_target(jorek_generated_code DEPENDS ${JOREK_GENERATED_HEADERS}) +endif() + +# --- The core library ------------------------------------------------------- +# +# The Make build discovered each executable's objects by scanning for `call` +# statements and linking whatever file was named after the callee. Here every +# non-program source instead goes into one static archive, and the linker +# extracts what each executable actually references. Keeping it a single archive +# matters: JOREK is full of bare external subroutines, so any split into two +# libraries would leave them referencing each other across the link line. +add_library(jorek_core STATIC ${JOREK_LIB_SOURCES}) +add_dependencies(jorek_core jorek_version_header) +target_link_libraries(jorek_core PUBLIC jorek_common) +target_include_directories(jorek_core PUBLIC "${CMAKE_Fortran_MODULE_DIRECTORY}") + +if(JOREK_SEMIANALYTICAL) + add_dependencies(jorek_core jorek_generated_code) +endif() + +# --- Executables ------------------------------------------------------------ +# +# Only the main solver is built by default, mirroring `make`. Every other +# program is a named target; `make most` builds the regression-tested subset. +foreach(_prog_src IN LISTS JOREK_PROGRAM_SOURCES) + get_filename_component(_prog "${_prog_src}" NAME_WE) + + if(_prog STREQUAL "jorek2_main") + add_executable(jorek2_main "${_prog_src}") + set_target_properties(jorek2_main PROPERTIES + OUTPUT_NAME "jorek_model${JOREK_MODEL_NUMBER}") + else() + add_executable(${_prog} EXCLUDE_FROM_ALL "${_prog_src}") + endif() + + target_link_libraries(${_prog} PRIVATE jorek_core) + set_target_properties(${_prog} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + # The core library mixes Fortran, C and C++ objects, and CMake would + # otherwise pick the C++ driver to link -- which does not supply the + # Fortran runtime's main(). Every JOREK program is a Fortran program. + LINKER_LANGUAGE Fortran) +endforeach() + +if(TARGET eqdsk2jorek AND JOREK_DIERCKX_LIBRARIES) + target_link_libraries(eqdsk2jorek PRIVATE ${JOREK_DIERCKX_LIBRARIES}) +endif() + +# The set of diagnostics that must keep compiling for regression testing to +# pass. Kept in sync with the `most:` target of the legacy Makefile. +set(JOREK_MOST_TARGETS + jorek2_connection2 + jorek2_connection_stan + jorek2_connection_flux_aligned + jorek2_diagno + jorek2_diagno_spi + jorek2_fieldlines_vtk + jorek2_four + CARIDDI_wall_curr + jorek2_poincare + jorek2_powers + jorek2_target2vtk + jorek2vtk_3d + jorek2vtk + jorek2vtk_GaussVortTerms + test_gvec2jorek_import + jorek_to_helena + new_diag_demo + jorek2_postproc + rst_bin2hdf5 + rst_hdf52bin + jorek2_main) +if(JOREK_USE_IMAS) + list(APPEND JOREK_MOST_TARGETS jorek2_IDS) +endif() + +set(_most_existing "") +foreach(_t IN LISTS JOREK_MOST_TARGETS) + if(TARGET ${_t}) + list(APPEND _most_existing ${_t}) + else() + message(WARNING "'most' target references unknown program '${_t}'") + endif() +endforeach() +add_custom_target(most DEPENDS ${_most_existing}) + +include(JorekSummary) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000000..27e54aa7ea --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,72 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "JOREK_MODEL": "model600" + } + }, + { + "name": "pitagora-intel", + "inherits": "base", + "displayName": "Pitagora CPU - Intel oneAPI + MKL + STRUMPACK", + "description": "Requires: module load intel-oneapi-mpi intel-oneapi-compilers intel-oneapi-mkl/2024.0.0--intel-oneapi-mpi--2021.12.1 hdf5/1.14.3--intel-oneapi-mpi--2021.12.1--oneapi--2024.1.0 cmake", + "cacheVariables": { + "CMAKE_Fortran_COMPILER": "mpiifx", + "CMAKE_C_COMPILER": "mpiicx", + "CMAKE_CXX_COMPILER": "mpiicpx", + "CMAKE_Fortran_FLAGS": "-mcmodel=medium -march=skylake-avx512 -axCORE-AVX512", + + "JOREK_USE_MKL": "ON", + "JOREK_USE_STRUMPACK": "ON", + "JOREK_USE_MUMPS": "OFF", + "JOREK_USE_PASTIX": "OFF", + "JOREK_USE_HDF5": "ON", + "JOREK_USE_FFTW": "ON", + "JOREK_USE_BLOCK": "ON", + + "JOREK_LIB_DIR": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd", + "STRUMPACK_ROOT": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd/STRUMPACK/install", + "METIS_ROOT": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd/METIS/install", + "PARMETIS_ROOT": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd/ParMETIS/install", + "GKLIB_ROOT": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd/GKlib/install" + } + }, + { + "name": "pitagora-intel-debug", + "inherits": "pitagora-intel", + "displayName": "Pitagora CPU - Intel oneAPI, debug build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + } + ], + "buildPresets": [ + { + "name": "pitagora-intel", + "configurePreset": "pitagora-intel", + "jobs": 16 + }, + { + "name": "pitagora-intel-most", + "configurePreset": "pitagora-intel", + "displayName": "Main solver plus the regression-tested diagnostics", + "targets": ["most"], + "jobs": 16 + }, + { + "name": "pitagora-intel-debug", + "configurePreset": "pitagora-intel-debug", + "jobs": 16 + } + ] +} diff --git a/cmake/GenerateVersionHeader.cmake b/cmake/GenerateVersionHeader.cmake new file mode 100644 index 0000000000..2e8e9e38c7 --- /dev/null +++ b/cmake/GenerateVersionHeader.cmake @@ -0,0 +1,81 @@ +# Writes version.h. Run as a script (cmake -P) both at configure time and on +# every build, so the recorded git revision follows the working tree. +# +# The values are Fortran character literals, so any single quote inside them +# must be doubled or the #include will not compile. + +function(_git out_var) + execute_process( + COMMAND git ${ARGN} + WORKING_DIRECTORY "${SOURCE_DIR}" + OUTPUT_VARIABLE _out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + set(_out "unknown") + endif() + set(${out_var} "${_out}" PARENT_SCOPE) +endfunction() + +function(_quote out_var value) + string(REPLACE "'" "''" value "${value}") + string(REPLACE "\n" " " value "${value}") + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +_git(RCS_VERSION describe --always --dirty --abbrev) +_git(LATEST_TAG_REV rev-list --tags --max-count=1) +if(LATEST_TAG_REV STREQUAL "unknown" OR LATEST_TAG_REV STREQUAL "") + set(JOREK_VERSION "unknown") +else() + _git(JOREK_VERSION describe --tags "${LATEST_TAG_REV}") +endif() +_git(RCS_LABEL log -1 "--format=%s (%D)") +_git(RCS_TIME log -1 "--format=%ad") + +cmake_host_system_information(RESULT COMPILE_MACHINE QUERY HOSTNAME) +if(DEFINED ENV{USER}) + set(COMPILE_USER "$ENV{USER}") +else() + set(COMPILE_USER "unknown") +endif() +# Recording the loaded environment modules makes a build reproducible on an HPC +# system, where the module set determines which libraries were linked. +if(DEFINED ENV{LOADEDMODULES}) + set(COMPILE_MODULES "$ENV{LOADEDMODULES}") +else() + set(COMPILE_MODULES "") +endif() + +foreach(_v RCS_VERSION JOREK_VERSION RCS_LABEL RCS_TIME COMPILE_COMMAND + COMPILE_FLAGS COMPILE_INCLUDES COMPILE_DEFINES COMPILE_LIBS + COMPILE_USER COMPILE_MACHINE COMPILE_MODULES) + _quote(${_v} "${${_v}}") +endforeach() + +set(_content +"#define RCS_VERSION '${RCS_VERSION}' +#define JOREK_VERSION '${JOREK_VERSION}' +#define RCS_LABEL '${RCS_LABEL}' +#define RCS_TIME '${RCS_TIME}' +#define compile_command '${COMPILE_COMMAND}' +#define compile_flags '${COMPILE_FLAGS}' +#define compile_includes '${COMPILE_INCLUDES}' +#define compile_defines '${COMPILE_DEFINES}' +#define compile_libs '${COMPILE_LIBS}' +#define compile_dir '${BINARY_DIR}' +#define compile_user '${COMPILE_USER}' +#define compile_machine '${COMPILE_MACHINE}' +#define compile_modules '${COMPILE_MODULES}' +") + +# Only touch the file when it changes, so rebuilding does not invalidate every +# object that includes it. +set(_existing "") +if(EXISTS "${OUTPUT_FILE}") + file(READ "${OUTPUT_FILE}" _existing) +endif() +if(NOT _existing STREQUAL _content) + file(WRITE "${OUTPUT_FILE}" "${_content}") +endif() diff --git a/cmake/JorekCompilerFlags.cmake b/cmake/JorekCompilerFlags.cmake new file mode 100644 index 0000000000..676f5c1038 --- /dev/null +++ b/cmake/JorekCompilerFlags.cmake @@ -0,0 +1,77 @@ +# Per-compiler flags, replacing the COMPILER_FAMILY blocks of defaults.mk. +# +# Two things are non-negotiable for JOREK and are set here rather than left to +# the site config: +# * Fortran sources must be run through the C preprocessor (-cpp / -fpp). +# The sources are named .f90, so no compiler does this automatically. +# * Default reals must be promoted to 8 bytes (-fdefault-real-8 / -r8). +# The code assumes it; a build without it is silently wrong. + +if(NOT CMAKE_Fortran_COMPILER_ID STREQUAL CMAKE_C_COMPILER_ID) + message(FATAL_ERROR + "Fortran (${CMAKE_Fortran_COMPILER_ID}) and C (${CMAKE_C_COMPILER_ID}) compilers " + "must come from the same vendor") +endif() + +set(_common "") # applied to every Fortran source +set(_debug "") # applied to Fortran sources in Debug builds only + +if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + + list(APPEND _common + -cpp -ffree-line-length-none + -fdefault-real-8 -fdefault-double-8 + # JOREK passes arrays where scalars are declared in several MPI calls; + # gfortran >= 10 rejects that by default. + -fallow-argument-mismatch + -Wall -Wextra -Wno-unused-variable -Wno-tabs + -Wcharacter-truncation -Wsurprising) + + list(APPEND _debug + -fcheck=all + -ffpe-trap=invalid,zero,overflow + -finit-real=snan -finit-integer=12345678 + -fimplicit-none) + +elseif(CMAKE_Fortran_COMPILER_ID MATCHES "^Intel") + + # Use the single-token `-opt=value` forms throughout. CMake de-duplicates + # repeated entries in COMPILE_OPTIONS, so a `-warn all -warn nointerfaces` + # spelling collapses to `-warn all nointerfaces` and the compiler then treats + # the orphaned keywords as input filenames. + list(APPEND _common + -fpp -r8 -align + -warn=all,nointerfaces,nounused,noexternal) + + list(APPEND _debug + -traceback + -check=all,noarg_temp_created + -ftrapuv -fpe0 -init=snan + -implicitnone) + + # The semianalytical models build very large automatic arrays in the element + # matrix routines and overflow the stack without this. + if(JOREK_SEMIANALYTICAL) + list(APPEND _common -heap-arrays) + endif() + +else() + message(WARNING + "Untested Fortran compiler '${CMAKE_Fortran_COMPILER_ID}'. You must supply " + "preprocessing and 8-byte-default-real flags yourself via CMAKE_Fortran_FLAGS.") +endif() + +add_compile_options("$<$:${_common}>") +add_compile_options( + "$<$,$>:${_debug}>") + +# A plain-text record of the flags for version.h. Generator expressions cannot +# be evaluated at configure time, so keep the flags as a string here rather than +# trying to recover them from the COMPILE_OPTIONS directory property. +set(_record ${_common}) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND _record ${_debug}) +endif() +string(REPLACE ";" " " JOREK_Fortran_FLAGS_RECORD "${_record}") + +find_package(OpenMP REQUIRED COMPONENTS Fortran C CXX) diff --git a/cmake/JorekDependencies.cmake b/cmake/JorekDependencies.cmake new file mode 100644 index 0000000000..957e428220 --- /dev/null +++ b/cmake/JorekDependencies.cmake @@ -0,0 +1,254 @@ +# External libraries. Everything discovered here is appended to +# JOREK_LIBRARIES and JOREK_DEFINES, which the core library exposes to all +# executables. + +set(JOREK_LIBRARIES "") + +# --- MPI -------------------------------------------------------------------- +find_package(MPI REQUIRED COMPONENTS Fortran C) +list(APPEND JOREK_LIBRARIES MPI::MPI_Fortran MPI::MPI_C) +list(APPEND JOREK_LIBRARIES OpenMP::OpenMP_Fortran) + +# JOREK compiles differently against MPI-2 and MPI-3. Fortran defines +# MPI_VERSION as an integer parameter, which cannot be used by the +# preprocessor, so the value has to be pushed in from outside. The Make build +# compiled and ran a probe program; FindMPI already reports it. +if(MPI_Fortran_VERSION MATCHES "^([0-9]+)") + list(APPEND JOREK_DEFINES "MPI_VERSION=${CMAKE_MATCH_1}") +else() + message(WARNING "Could not determine the MPI version; assuming MPI_VERSION=0") + list(APPEND JOREK_DEFINES "MPI_VERSION=0") +endif() + +# --- Dense linear algebra --------------------------------------------------- +# MKL provides BLAS, LAPACK, ScaLAPACK, BLACS and an FFTW3 interface in one +# package; otherwise the three are found separately. +if(JOREK_USE_MKL) + if(NOT DEFINED ENV{MKLROOT}) + message(FATAL_ERROR "JOREK_USE_MKL=ON but MKLROOT is not set; load the MKL module") + endif() + set(JOREK_MKL_ROOT "$ENV{MKLROOT}") + + if(JOREK_USE_INTSIZE64) + set(_mkl_int ilp64) + else() + set(_mkl_int lp64) + endif() + + # Link the MKL interface layer as a group: the libraries are mutually + # recursive and a plain sequential link line does not resolve. + set(_mkl_libs + -L${JOREK_MKL_ROOT}/lib/intel64 + -Wl,--start-group + -lmkl_scalapack_${_mkl_int} + -lmkl_intel_${_mkl_int} + -lmkl_intel_thread + -lmkl_core + -lmkl_blacs_intelmpi_${_mkl_int} + -Wl,--end-group + -liomp5 -lpthread -lm -ldl) + list(APPEND JOREK_LIBRARIES ${_mkl_libs}) + set(JOREK_BLAS_LAPACK_SUMMARY "Intel MKL (${JOREK_MKL_ROOT})") +else() + if(JOREK_BLAS_LAPACK_LIBRARIES) + list(APPEND JOREK_LIBRARIES ${JOREK_BLAS_LAPACK_LIBRARIES}) + set(JOREK_BLAS_LAPACK_SUMMARY "${JOREK_BLAS_LAPACK_LIBRARIES} (user-specified)") + else() + find_package(LAPACK REQUIRED) + find_package(BLAS REQUIRED) + list(APPEND JOREK_LIBRARIES LAPACK::LAPACK BLAS::BLAS) + set(JOREK_BLAS_LAPACK_SUMMARY "${LAPACK_LIBRARIES}") + endif() + + if(JOREK_SCALAPACK_LIBRARIES) + list(APPEND JOREK_LIBRARIES ${JOREK_SCALAPACK_LIBRARIES}) + endif() +endif() + +# --- HDF5 ------------------------------------------------------------------- +if(JOREK_USE_HDF5) + # The site modules export HDF5_HOME; FindHDF5 looks at HDF5_ROOT. + if(NOT HDF5_ROOT AND DEFINED ENV{HDF5_HOME}) + set(HDF5_ROOT "$ENV{HDF5_HOME}") + endif() + set(HDF5_PREFER_PARALLEL TRUE) + find_package(HDF5 REQUIRED COMPONENTS Fortran Fortran_HL) + list(APPEND JOREK_LIBRARIES ${HDF5_Fortran_HL_LIBRARIES} ${HDF5_Fortran_LIBRARIES}) + list(APPEND JOREK_DEFINES "USE_HDF5") + set(JOREK_HDF5_SUMMARY "${HDF5_VERSION} (${HDF5_Fortran_INCLUDE_DIRS})") + + if(NOT HDF5_IS_PARALLEL) + message(WARNING "The HDF5 found is serial; JOREK expects a parallel (MPI) HDF5") + endif() +endif() + +# --- FFTW ------------------------------------------------------------------- +if(JOREK_USE_FFTW) + list(APPEND JOREK_DEFINES "USE_FFTW") + if(JOREK_USE_MKL) + # MKL ships the FFTW3 interface, including the fftw3.f03 module file that + # jorek2_main.f90 includes. No extra library to link. + set(JOREK_FFTW_INCLUDE_DIR "${JOREK_MKL_ROOT}/include/fftw") + if(NOT EXISTS "${JOREK_FFTW_INCLUDE_DIR}/fftw3.f03") + message(FATAL_ERROR + "MKL FFTW3 interface not found at ${JOREK_FFTW_INCLUDE_DIR}") + endif() + set(JOREK_FFTW_SUMMARY "MKL FFTW3 interface") + else() + find_path(JOREK_FFTW_INCLUDE_DIR fftw3.f03 + HINTS $ENV{FFTW_INCLUDE} $ENV{FFTW_HOME}/include + DOC "Directory containing fftw3.f03") + find_library(JOREK_FFTW_LIBRARY fftw3 + HINTS $ENV{FFTW_LIB} $ENV{FFTW_HOME}/lib + DOC "The FFTW3 library") + if(NOT JOREK_FFTW_INCLUDE_DIR OR NOT JOREK_FFTW_LIBRARY) + message(FATAL_ERROR "JOREK_USE_FFTW=ON but FFTW was not found; load the fftw module") + endif() + list(APPEND JOREK_LIBRARIES ${JOREK_FFTW_LIBRARY}) + set(JOREK_FFTW_SUMMARY "${JOREK_FFTW_LIBRARY}") + endif() +endif() + +# --- Sparse direct solvers -------------------------------------------------- +# Small helper: locate a library under a required install prefix and fail with +# an actionable message rather than a link error 20 minutes later. +function(_jorek_require_library out_var name) + cmake_parse_arguments(ARG "" "" "HINTS" ${ARGN}) + find_library(${out_var} ${name} + HINTS ${ARG_HINTS} + PATH_SUFFIXES lib lib64 + NO_DEFAULT_PATH) + find_library(${out_var} ${name} HINTS ${ARG_HINTS} PATH_SUFFIXES lib lib64) + if(NOT ${out_var}) + message(FATAL_ERROR "Could not find lib${name}; searched: ${ARG_HINTS}") + endif() + set(${out_var} "${${out_var}}" PARENT_SCOPE) +endfunction() + +if(JOREK_USE_STRUMPACK) + if(NOT STRUMPACK_ROOT) + message(FATAL_ERROR "JOREK_USE_STRUMPACK=ON but STRUMPACK_ROOT is not set") + endif() + _jorek_require_library(JOREK_STRUMPACK_LIBRARY strumpack HINTS "${STRUMPACK_ROOT}") + find_path(JOREK_STRUMPACK_INCLUDE_DIR StrumpackSparseSolver.h + HINTS "${STRUMPACK_ROOT}" PATH_SUFFIXES include) + + list(APPEND JOREK_LIBRARIES ${JOREK_STRUMPACK_LIBRARY}) + list(APPEND JOREK_DEFINES "USE_STRUMPACK" "NEWSPK") + set(JOREK_STRUMPACK_SUMMARY "${JOREK_STRUMPACK_LIBRARY}") + + # STRUMPACK's sparse reordering pulls in the METIS family. + foreach(_m METIS PARMETIS GKLIB) + if(${_m}_ROOT) + string(TOLOWER "${_m}" _lib) + if(_m STREQUAL "GKLIB") + set(_lib GKlib) + endif() + _jorek_require_library(JOREK_${_m}_LIBRARY ${_lib} HINTS "${${_m}_ROOT}") + list(APPEND JOREK_LIBRARIES ${JOREK_${_m}_LIBRARY}) + find_path(JOREK_${_m}_INCLUDE_DIR NAMES metis.h parmetis.h GKlib.h + HINTS "${${_m}_ROOT}" PATH_SUFFIXES include) + if(JOREK_${_m}_INCLUDE_DIR) + list(APPEND JOREK_SOLVER_INCLUDE_DIRS "${JOREK_${_m}_INCLUDE_DIR}") + endif() + endif() + endforeach() +endif() + +if(JOREK_USE_MUMPS) + if(NOT MUMPS_ROOT) + message(FATAL_ERROR "JOREK_USE_MUMPS=ON but MUMPS_ROOT is not set") + endif() + _jorek_require_library(JOREK_DMUMPS_LIBRARY dmumps HINTS "${MUMPS_ROOT}") + _jorek_require_library(JOREK_MUMPS_COMMON_LIBRARY mumps_common HINTS "${MUMPS_ROOT}") + find_library(JOREK_PORD_LIBRARY pord + HINTS "${MUMPS_ROOT}" "${MUMPS_ROOT}/PORD" PATH_SUFFIXES lib lib64) + find_path(JOREK_MUMPS_INCLUDE_DIR dmumps_struc.h + HINTS "${MUMPS_ROOT}" PATH_SUFFIXES include) + + list(APPEND JOREK_LIBRARIES + ${JOREK_DMUMPS_LIBRARY} ${JOREK_MUMPS_COMMON_LIBRARY} ${JOREK_PORD_LIBRARY}) + list(APPEND JOREK_SOLVER_INCLUDE_DIRS "${JOREK_MUMPS_INCLUDE_DIR}") + list(APPEND JOREK_DEFINES "USE_MUMPS") + set(JOREK_MUMPS_SUMMARY "${JOREK_DMUMPS_LIBRARY}") +endif() + +if(JOREK_USE_PASTIX OR JOREK_USE_PASTIX6) + if(NOT PASTIX_ROOT) + message(FATAL_ERROR "PaStiX requested but PASTIX_ROOT is not set") + endif() + _jorek_require_library(JOREK_PASTIX_LIBRARY pastix HINTS "${PASTIX_ROOT}") + find_path(JOREK_PASTIX_INCLUDE_DIR NAMES pastix.h pastix_fortran.h + HINTS "${PASTIX_ROOT}" PATH_SUFFIXES include) + list(APPEND JOREK_LIBRARIES ${JOREK_PASTIX_LIBRARY}) + list(APPEND JOREK_SOLVER_INCLUDE_DIRS "${JOREK_PASTIX_INCLUDE_DIR}") + + if(JOREK_USE_PASTIX) + list(APPEND JOREK_DEFINES "USE_PASTIX" "MEMORY_USAGE") + endif() + if(JOREK_USE_PASTIX6) + list(APPEND JOREK_DEFINES "USE_PASTIX6") + endif() + if(JOREK_USE_PASTIX_MURGE) + list(APPEND JOREK_DEFINES "USE_MURGE") + endif() + + if(SCOTCH_ROOT) + _jorek_require_library(JOREK_SCOTCH_LIBRARY scotch HINTS "${SCOTCH_ROOT}") + _jorek_require_library(JOREK_SCOTCHERR_LIBRARY scotcherr HINTS "${SCOTCH_ROOT}") + list(APPEND JOREK_LIBRARIES ${JOREK_SCOTCH_LIBRARY} ${JOREK_SCOTCHERR_LIBRARY}) + endif() +else() + # mod_pastix.f90 keeps its interface declarations even when PaStiX is absent. + # Renaming the entry point avoids unresolved symbols at link time; this is + # the same trick defaults.mk used. + list(APPEND JOREK_DEFINES "pastix_fortran=fake_pastix_fortran") +endif() + +if(JOREK_USE_WSMP) + _jorek_require_library(JOREK_WSMP_LIBRARY pwsmp64 HINTS "$ENV{WSMP_HOME}") + list(APPEND JOREK_LIBRARIES ${JOREK_WSMP_LIBRARY}) + list(APPEND JOREK_DEFINES "USE_WSMP") +endif() + +if(JOREK_USE_HIPS) + _jorek_require_library(JOREK_HIPS_LIBRARY hips HINTS "$ENV{HIPS_HOME}") + list(APPEND JOREK_LIBRARIES ${JOREK_HIPS_LIBRARY}) + list(APPEND JOREK_DEFINES "USE_HIPS") +endif() + +# --- Optional integrations -------------------------------------------------- +if(JOREK_USE_BOOST) + find_package(Boost REQUIRED) + list(APPEND JOREK_LIBRARIES Boost::boost) +endif() + +if(JOREK_USE_IMAS) + find_path(JOREK_IMAS_INCLUDE_DIR ids_schemas.mod HINTS $ENV{IMAS_PREFIX}/include) + find_library(JOREK_IMAS_LIBRARY imas HINTS $ENV{IMAS_PREFIX}/lib) + if(NOT JOREK_IMAS_INCLUDE_DIR OR NOT JOREK_IMAS_LIBRARY) + message(FATAL_ERROR "JOREK_USE_IMAS=ON but the IMAS Access Layer was not found") + endif() + list(APPEND JOREK_LIBRARIES ${JOREK_IMAS_LIBRARY}) + list(APPEND JOREK_SOLVER_INCLUDE_DIRS "${JOREK_IMAS_INCLUDE_DIR}") + list(APPEND JOREK_DEFINES "USE_IMAS") +endif() + +if(JOREK_USE_CATALYST) + find_package(catalyst REQUIRED) + list(APPEND JOREK_LIBRARIES catalyst::catalyst) + list(APPEND JOREK_DEFINES "USE_CATALYST") +endif() + +# JOREK links C++ translation units (the r-tree, the STRUMPACK/PaStiX shims) +# into a Fortran executable, so the C++ runtime must be pulled in explicitly. +list(APPEND JOREK_LIBRARIES stdc++) + +# --- Collected include directories ----------------------------------------- +set(JOREK_INCLUDE_DIRS + ${JOREK_SOLVER_INCLUDE_DIRS} + ${JOREK_STRUMPACK_INCLUDE_DIR} + ${JOREK_FFTW_INCLUDE_DIR} + ${HDF5_Fortran_INCLUDE_DIRS}) +list(REMOVE_ITEM JOREK_INCLUDE_DIRS "") +list(REMOVE_DUPLICATES JOREK_INCLUDE_DIRS) diff --git a/cmake/JorekModelConfig.cmake b/cmake/JorekModelConfig.cmake new file mode 100644 index 0000000000..10607bed07 --- /dev/null +++ b/cmake/JorekModelConfig.cmake @@ -0,0 +1,152 @@ +# Derives preprocessor symbols from the physics model, reproducing what +# `util/config.sh -p ` did for defaults.mk. The model settings files are +# the single source of truth: we read them rather than duplicating their values. + +set(JOREK_MODEL_DIR "${CMAKE_SOURCE_DIR}/models/${JOREK_MODEL}") +set(JOREK_MODEL_SETTINGS "${JOREK_MODEL_DIR}/mod_model_settings.f90") +set(JOREK_GLOBAL_SETTINGS "${CMAKE_SOURCE_DIR}/models/mod_settings.f90") + +if(NOT IS_DIRECTORY "${JOREK_MODEL_DIR}") + file(GLOB _available RELATIVE "${CMAKE_SOURCE_DIR}/models" "${CMAKE_SOURCE_DIR}/models/model*") + message(FATAL_ERROR "Unknown model '${JOREK_MODEL}'. Available: ${_available}") +endif() +foreach(_f "${JOREK_MODEL_SETTINGS}" "${JOREK_GLOBAL_SETTINGS}") + if(NOT EXISTS "${_f}") + message(FATAL_ERROR "Missing model settings file: ${_f}") + endif() +endforeach() + +# Re-run CMake when the settings files change: they decide the compile flags. +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${JOREK_MODEL_SETTINGS}" "${JOREK_GLOBAL_SETTINGS}") + +if(NOT JOREK_MODEL MATCHES "^model([0-9]+)$") + message(FATAL_ERROR "JOREK_MODEL must look like 'model', got '${JOREK_MODEL}'") +endif() +set(JOREK_MODEL_NUMBER "${CMAKE_MATCH_1}") + +# Read a `, parameter :: = ` declaration out of a settings +# file. +# +# Fortran is case-insensitive and the model settings files are not consistent: +# model600 writes `with_vpar` while model303 writes `with_Vpar`. util/config.sh +# greps with -i, so match case-insensitively here too -- a case-sensitive match +# silently drops the flag and builds the wrong physics. +function(_jorek_read_setting out_var file key) + string(TOLOWER "${key}" _key) + file(STRINGS "${file}" _lines) + set(${out_var} "" PARENT_SCOPE) + foreach(_line IN LISTS _lines) + string(TOLOWER "${_line}" _line) + # Strip trailing comments so a value mentioned in the documentation column + # cannot be picked up instead of the real one. + string(REGEX REPLACE "!.*$" "" _line "${_line}") + if(_line MATCHES "::[ \t]*${_key}[ \t]*=[ \t]*([^ \t]+)") + set(${out_var} "${CMAKE_MATCH_1}" PARENT_SCOPE) + return() + endif() + endforeach() +endfunction() + +# Map a `logical, parameter :: = .true.` setting onto a -D. +function(_jorek_logical_define key define) + _jorek_read_setting(_value "${JOREK_MODEL_SETTINGS}" "${key}") + string(TOLOWER "${_value}" _value) + if(_value STREQUAL ".true.") + list(APPEND JOREK_DEFINES "${define}") + set(JOREK_DEFINES "${JOREK_DEFINES}" PARENT_SCOPE) + elseif(NOT _value STREQUAL ".false." AND NOT _value STREQUAL "") + message(FATAL_ERROR "Could not parse '${key}' in ${JOREK_MODEL_SETTINGS} (got '${_value}')") + endif() +endfunction() + +set(JOREK_DEFINES "") + +list(APPEND JOREK_DEFINES "JOREK_MODEL=${JOREK_MODEL_NUMBER}" "USE_MPI") + +_jorek_logical_define(with_Vpar WITH_Vpar) +_jorek_logical_define(with_TiTe WITH_TiTe) +_jorek_logical_define(with_neutrals WITH_Neutrals) +_jorek_logical_define(with_impurities WITH_Impurities) +_jorek_logical_define(with_refluid WITH_REFluid) + +# A non-zero n_mod_ext means the model is the base of a model family. +_jorek_read_setting(JOREK_N_MOD_EXT "${JOREK_MODEL_SETTINGS}" "n_mod_ext") +if(JOREK_N_MOD_EXT AND NOT JOREK_N_MOD_EXT STREQUAL "0") + list(APPEND JOREK_DEFINES "MODEL_FAMILY") +endif() + +# Polynomial orders above 3 need more Gauss points than the compiled-in default. +_jorek_read_setting(JOREK_N_ORDER "${JOREK_GLOBAL_SETTINGS}" "n_order") +if(NOT JOREK_N_ORDER) + message(FATAL_ERROR "Could not read n_order from ${JOREK_GLOBAL_SETTINGS}") +endif() +if(NOT JOREK_N_ORDER STREQUAL "3") + list(APPEND JOREK_DEFINES "GAUSS_ORDER=8") +endif() + +# Full-MHD models take a different equation set. +set(_fullmhd_models 710 711 712 750) +if(JOREK_MODEL_NUMBER IN_LIST _fullmhd_models) + list(APPEND JOREK_DEFINES "fullmhd") +endif() + +# The stellarator models build their element matrices from symbolic expressions +# that algexpr2fort turns into Fortran at build time. STELLARATOR_MODEL also +# changes type_element (it gains an allocatable `chi`), so the two flags are +# tracked separately even though the same models set both today. +set(_semianalytical_models 180 183) +if(JOREK_MODEL_NUMBER IN_LIST _semianalytical_models) + set(JOREK_SEMIANALYTICAL TRUE) + set(JOREK_STELLARATOR TRUE) + list(APPEND JOREK_DEFINES "SEMIANALYTICAL" "STELLARATOR_MODEL") +else() + set(JOREK_SEMIANALYTICAL FALSE) + set(JOREK_STELLARATOR FALSE) +endif() + +# --- Feature switches (JorekOptions) --------------------------------------- +if(JOREK_USE_DOMM) + list(APPEND JOREK_DEFINES "USE_DOMM") +endif() +if(JOREK_USE_EXT_FIELD) + list(APPEND JOREK_DEFINES "USE_EXT_FIELD") +endif() +if(JOREK_USE_BLOCK) + list(APPEND JOREK_DEFINES "USE_BLOCK") +endif() +if(JOREK_USE_DIRECT_CONSTRUCTION) + list(APPEND JOREK_DEFINES "DIRECT_CONSTRUCTION") +endif() +if(JOREK_USE_COMPLEX_PRECOND) + list(APPEND JOREK_DEFINES "USE_COMPLEX_PRECOND") +endif() +if(JOREK_USE_INTSIZE64) + list(APPEND JOREK_DEFINES "INTSIZE64") +endif() +if(JOREK_USE_TASKLOOP) + list(APPEND JOREK_DEFINES "USE_TASKLOOP") +endif() +if(JOREK_USE_QUADTREE) + list(APPEND JOREK_DEFINES "USE_QUADTREE") +endif() +if(JOREK_USE_NO_TREE) + list(APPEND JOREK_DEFINES "USE_NO_TREE") +endif() +if(JOREK_USE_R3_INFO) + list(APPEND JOREK_DEFINES "USE_R3_INFO" "USE_R3_INFO_MPI") +endif() + +# GMRES is the default; BiCGSTAB replaces it. +if(JOREK_USE_BICGSTAB) + list(APPEND JOREK_DEFINES "USE_BICGSTAB") +else() + list(APPEND JOREK_DEFINES "USE_GMRES") +endif() + +if(JOREK_USE_STD_BESSELK) + list(APPEND JOREK_DEFINES "USE_STD_BESSELK") +endif() +if(JOREK_USE_BOOST) + list(APPEND JOREK_DEFINES "USE_BOOST") +endif() diff --git a/cmake/JorekOptions.cmake b/cmake/JorekOptions.cmake new file mode 100644 index 0000000000..d9ef142e0a --- /dev/null +++ b/cmake/JorekOptions.cmake @@ -0,0 +1,94 @@ +# Cache options replacing the switches that used to live in Makefile.inc. +# Every JOREK_USE_* here maps onto exactly one -DUSE_* preprocessor symbol, +# with the same meaning it had in defaults.mk. + +set(JOREK_MODEL "model600" CACHE STRING + "Physics model to build (directory name under models/, e.g. model600)") + +# --- Physics / numerics switches ------------------------------------------- +option(JOREK_USE_DOMM "Use Dommaschk potentials (no FE correction of n.B on boundary)" ON) +option(JOREK_USE_EXT_FIELD "Use external magnetic field from a gvec2jorek file (requires JOREK_USE_DOMM=OFF)" OFF) +option(JOREK_USE_BLOCK "Enable blocked matrix assembly" ON) +option(JOREK_USE_DIRECT_CONSTRUCTION "Construct the matrix directly" OFF) +option(JOREK_USE_BICGSTAB "Use BiCGSTAB instead of GMRES as the iterative solver" OFF) +option(JOREK_USE_COMPLEX_PRECOND "Use a complex-valued preconditioner" OFF) +option(JOREK_USE_INTSIZE64 "Link against the 64-bit-integer builds of the solver libraries" OFF) +option(JOREK_USE_TASKLOOP "Use OpenMP taskloop constructs" OFF) +option(JOREK_USE_R3_INFO "Enable the r3_info timing instrumentation" OFF) + +# Element localisation strategy: the r-tree is the default, the other two are +# mutually exclusive alternatives selected by preprocessor symbol. +option(JOREK_USE_QUADTREE "Use a quadtree instead of an r-tree for element localisation" OFF) +option(JOREK_USE_NO_TREE "Use brute-force element localisation (no spatial tree)" OFF) + +# --- Sparse solvers --------------------------------------------------------- +option(JOREK_USE_STRUMPACK "Build with the STRUMPACK sparse direct solver" ON) +option(JOREK_USE_MUMPS "Build with the MUMPS sparse direct solver" OFF) +option(JOREK_USE_PASTIX "Build with the PaStiX 5 sparse direct solver" OFF) +option(JOREK_USE_PASTIX_MURGE "Use the PaStiX MURGE interface" OFF) +option(JOREK_USE_PASTIX6 "Build with the PaStiX 6 sparse direct solver" OFF) +option(JOREK_USE_WSMP "Build with the WSMP sparse direct solver" OFF) +option(JOREK_USE_HIPS "Build with the HIPS sparse direct solver" OFF) + +# --- I/O and optional integrations ----------------------------------------- +option(JOREK_USE_HDF5 "Build with HDF5 input/output (strongly recommended)" ON) +option(JOREK_USE_FFTW "Build with FFTW (or the MKL FFTW3 interface)" ON) +option(JOREK_USE_MKL "Use Intel MKL for BLAS/LAPACK/ScaLAPACK/FFTW" OFF) +option(JOREK_USE_IMAS "Build the IMAS/IDS coupling (communication/IMAS)" OFF) +option(JOREK_USE_CATALYST "Build the ParaView Catalyst in-situ adaptor" OFF) + +# --- Bessel function backend (mutually exclusive) --------------------------- +option(JOREK_USE_STD_BESSELK "Use the C++ standard library for modified Bessel functions" OFF) +option(JOREK_USE_BOOST "Use Boost for modified Bessel functions" OFF) + +# --- What to build ---------------------------------------------------------- +option(JOREK_BUILD_TESTS "Compile the FRUIT unit-test modules into the core library" OFF) +option(JOREK_BUILD_EXAMPLES "Compile the particle examples and benchmarks" OFF) + +# --- External library locations --------------------------------------------- +# Presets fill these in; they are plain paths so a site can point them anywhere. +set(JOREK_LIB_DIR "" CACHE PATH "Root of the pre-built JOREK library collection") +set(STRUMPACK_ROOT "" CACHE PATH "STRUMPACK install prefix") +set(METIS_ROOT "" CACHE PATH "METIS install prefix") +set(PARMETIS_ROOT "" CACHE PATH "ParMETIS install prefix") +set(GKLIB_ROOT "" CACHE PATH "GKlib install prefix") +set(MUMPS_ROOT "" CACHE PATH "MUMPS install prefix") +set(PASTIX_ROOT "" CACHE PATH "PaStiX install prefix") +set(SCOTCH_ROOT "" CACHE PATH "Scotch install prefix") +set(JOREK_DIERCKX_LIBRARIES "" CACHE STRING "DIERCKX libraries (only needed by eqdsk2jorek)") + +# Escape hatch for sources that do not compile in a given configuration. +# CMake compiles every source in the tree into the core library, whereas the +# Make build only compiled what the requested program transitively referenced -- +# so CMake also compiles files that are unreachable, and therefore unmaintained. +set(JOREK_EXCLUDE_SOURCES "" CACHE STRING + "Regexes (matched against absolute source paths) to exclude from the build") +set(JOREK_BLAS_LAPACK_LIBRARIES "" CACHE STRING "Override the detected BLAS/LAPACK libraries") +set(JOREK_SCALAPACK_LIBRARIES "" CACHE STRING "Override the detected ScaLAPACK/BLACS libraries") + +# --- Consistency checks ----------------------------------------------------- +if(JOREK_USE_QUADTREE AND JOREK_USE_NO_TREE) + message(FATAL_ERROR "JOREK_USE_QUADTREE and JOREK_USE_NO_TREE are mutually exclusive") +endif() + +if(JOREK_USE_STD_BESSELK AND JOREK_USE_BOOST) + message(FATAL_ERROR "JOREK_USE_STD_BESSELK and JOREK_USE_BOOST are mutually exclusive") +endif() + +# defaults.mk only honoured USE_EXT_FIELD when USE_DOMM was off; make that +# an error rather than silently dropping the flag. +if(JOREK_USE_EXT_FIELD AND JOREK_USE_DOMM) + message(FATAL_ERROR + "JOREK_USE_EXT_FIELD requires JOREK_USE_DOMM=OFF (the external field replaces " + "the Dommaschk potentials)") +endif() + +if(NOT JOREK_USE_HDF5) + message(WARNING "JOREK_USE_HDF5=OFF: restart and diagnostic I/O will be limited") +endif() + +if(NOT (JOREK_USE_STRUMPACK OR JOREK_USE_MUMPS OR JOREK_USE_PASTIX + OR JOREK_USE_PASTIX6 OR JOREK_USE_WSMP OR JOREK_USE_HIPS)) + message(FATAL_ERROR "No sparse direct solver selected; enable at least one of " + "JOREK_USE_STRUMPACK / JOREK_USE_MUMPS / JOREK_USE_PASTIX / JOREK_USE_PASTIX6") +endif() diff --git a/cmake/JorekSources.cmake b/cmake/JorekSources.cmake new file mode 100644 index 0000000000..7905ca3c81 --- /dev/null +++ b/cmake/JorekSources.cmake @@ -0,0 +1,396 @@ +# Source discovery. +# +# The Make build listed source directories in DIRS and then used +# util/makedepend + util/obj_deps to work out, per executable, which objects to +# link -- by scanning for `call foo(...)` and pulling in a file named foo.f90. +# CMake needs none of that: Fortran module dependencies are tracked natively, +# and the link closure falls out of putting every non-program source into a +# static archive and letting the linker extract what it references. + +# Directories searched for sources, non-recursively (as in the Makefile's DIRS). +set(JOREK_SOURCE_DIRS + . + benchmarks + communication + core + datatypes + diagnostics + diagnostics/new_diag + diagnostics/postproc + elements + grids + grids/grid_utils + matrix + models + models/${JOREK_MODEL} + particles + particles/diagnostics + particles/postprocessors + particles/postprocessors/camera + particles/postprocessors/filters + particles/postprocessors/lens + particles/postprocessors/lights + particles/postprocessors/spectra + particles/postprocessors/utils + particles/projection_functions + particles/pushers + # Not optional despite the name: the synthetic-light postprocessors in + # particles/postprocessors/lights `use mod_particle_common_test_tools`, which + # lives here. The Make build always had this directory in DIRS, so the + # coupling went unnoticed. + particles/tests + plots + refinement + solvers + tools + tools/fruit + vacuum) + +# The IMAS coupling only compiles against the ITER Access Layer. +if(JOREK_USE_IMAS) + list(APPEND JOREK_SOURCE_DIRS communication/IMAS) +endif() + +# The FRUIT test suites are not part of a normal build; the Make build only +# compiled them on demand too. The suites in particles/tests `use` modules from +# particles/benchmarks, so enabling one requires the other. +if(JOREK_BUILD_TESTS) + list(APPEND JOREK_SOURCE_DIRS + communication/tests + core/tests + diagnostics/tests + elements/tests + grids/tests + particles/postprocessors/tests + tools/tests + particles/benchmarks/projection + particles/benchmarks/pusher + particles/benchmarks/pusher_cartesian) +endif() + +if(JOREK_BUILD_EXAMPLES) + list(APPEND JOREK_SOURCE_DIRS + particles/examples + particles/postprocessors/examples) +endif() + +list(REMOVE_DUPLICATES JOREK_SOURCE_DIRS) + +set(_all_sources "") +foreach(_dir IN LISTS JOREK_SOURCE_DIRS) + file(GLOB _found CONFIGURE_DEPENDS + "${CMAKE_SOURCE_DIR}/${_dir}/*.f90" + "${CMAKE_SOURCE_DIR}/${_dir}/*.f" + "${CMAKE_SOURCE_DIR}/${_dir}/*.c" + "${CMAKE_SOURCE_DIR}/${_dir}/*.cpp") + list(APPEND _all_sources ${_found}) +endforeach() + +# The Catalyst adaptor is guarded by USE_CATALYST but its .cpp does not compile +# at all without the Catalyst headers, so drop it rather than rely on #ifdef. +if(NOT JOREK_USE_CATALYST) + list(FILTER _all_sources EXCLUDE REGEX "/(mod_)?catalyst_adaptor\\.(f90|cpp)$") +endif() + +# communication/export_grid.f90 and communication/import_equil.f90 write and +# read type_element with unformatted derived-type I/O. For the stellarator +# models type_element gains an allocatable `chi` component, which makes that +# I/O invalid Fortran, so neither file compiles. Nothing in the tree calls +# either of them -- they are dead code, and the Make build never compiled them +# because it only built what the target program transitively referenced. +if(JOREK_STELLARATOR) + list(APPEND JOREK_EXCLUDE_SOURCES + "/communication/export_grid\\.f90$" + "/communication/import_equil\\.f90$") +endif() + +foreach(_pattern IN LISTS JOREK_EXCLUDE_SOURCES) + list(FILTER _all_sources EXCLUDE REGEX "${_pattern}") +endforeach() + +# algexpr2fort is a build-time code generator, not part of the solver; it is +# given its own target below. +list(FILTER _all_sources EXCLUDE REGEX "/algexpr2fort\\.f90$") + +# --- Test-directory helpers required by production code --------------------- +# +# particles/tests holds two different kinds of file: the FRUIT test suites, and +# shared helper modules that non-test code genuinely depends on (the synthetic +# light postprocessors `use mod_particle_common_test_tools`). The Make build +# kept every tests/ directory in DIRS and compiled lazily, so it never had to +# tell them apart. +# +# Selecting by filename would be wrong: mod_projection_helpers_test_tools has an +# unconditional `include 'dmumps_struc.h'` and only compiles against MUMPS, yet +# nothing outside the benchmarks needs it. So pull in a tests/ file only when +# something outside tests/ actually uses a module it defines, transitively. +if(NOT JOREK_BUILD_TESTS) + set(_pool ${_all_sources}) + set(_core ${_all_sources}) + list(FILTER _pool INCLUDE REGEX "/tests/") + list(FILTER _core EXCLUDE REGEX "/tests/") + + # Which tests/ file provides which module? + foreach(_src IN LISTS _pool) + file(STRINGS "${_src}" _lines + REGEX "^[ \t]*[Mm][Oo][Dd][Uu][Ll][Ee][ \t]+[A-Za-z]") + foreach(_line IN LISTS _lines) + if(_line MATCHES "^[ \t]*[Mm][Oo][Dd][Uu][Ll][Ee][ \t]+([A-Za-z][A-Za-z0-9_]*)") + string(TOLOWER "${CMAKE_MATCH_1}" _m) + if(NOT _m STREQUAL "procedure") + set(_provider_${_m} "${_src}") + endif() + endif() + endforeach() + endforeach() + + # Seed the worklist with every module used outside tests/, then close over the + # helpers that are pulled in. + set(_wanted "") + foreach(_src IN LISTS _core) + if(NOT _src MATCHES "\\.f90$") + continue() + endif() + file(STRINGS "${_src}" _lines REGEX "^[ \t]*[Uu][Ss][Ee][ \t]+[A-Za-z]") + foreach(_line IN LISTS _lines) + if(_line MATCHES "^[ \t]*[Uu][Ss][Ee][ \t]+([A-Za-z][A-Za-z0-9_]*)") + string(TOLOWER "${CMAKE_MATCH_1}" _m) + list(APPEND _wanted "${_m}") + endif() + endforeach() + endforeach() + list(REMOVE_DUPLICATES _wanted) + + set(_kept_helpers "") + set(_i 0) + list(LENGTH _wanted _n_wanted) + while(_i LESS _n_wanted) + list(GET _wanted ${_i} _m) + math(EXPR _i "${_i} + 1") + if(NOT DEFINED _provider_${_m}) + continue() + endif() + set(_src "${_provider_${_m}}") + if(_src IN_LIST _kept_helpers) + continue() + endif() + list(APPEND _kept_helpers "${_src}") + + file(STRINGS "${_src}" _lines REGEX "^[ \t]*[Uu][Ss][Ee][ \t]+[A-Za-z]") + foreach(_line IN LISTS _lines) + if(_line MATCHES "^[ \t]*[Uu][Ss][Ee][ \t]+([A-Za-z][A-Za-z0-9_]*)") + string(TOLOWER "${CMAKE_MATCH_1}" _dep) + if(NOT _dep IN_LIST _wanted) + list(APPEND _wanted "${_dep}") + math(EXPR _n_wanted "${_n_wanted} + 1") + endif() + endif() + endforeach() + endwhile() + + set(_all_sources ${_core} ${_kept_helpers}) + + list(LENGTH _kept_helpers _n_helpers) + if(_n_helpers) + message(STATUS + "JOREK: pulling in ${_n_helpers} helper module(s) from tests/ that non-test code depends on") + endif() +endif() + +# --- Split programs from library sources ------------------------------------ +# A Fortran file with a `program` statement becomes its own executable; +# everything else goes into the core library. +set(JOREK_PROGRAM_SOURCES "") +set(JOREK_LIB_SOURCES "") +foreach(_src IN LISTS _all_sources) + set(_is_program FALSE) + if(_src MATCHES "\\.f90$") + file(STRINGS "${_src}" _program_stmt + REGEX "^[ \t]*[Pp][Rr][Oo][Gg][Rr][Aa][Mm][ \t]+[A-Za-z]" + LIMIT_COUNT 1) + if(_program_stmt) + set(_is_program TRUE) + endif() + endif() + + if(_is_program) + list(APPEND JOREK_PROGRAM_SOURCES "${_src}") + else() + list(APPEND JOREK_LIB_SOURCES "${_src}") + endif() +endforeach() + +if(NOT JOREK_PROGRAM_SOURCES MATCHES "jorek2_main\\.f90") + message(FATAL_ERROR "jorek2_main.f90 was not found among the discovered programs") +endif() + +# Module and object files are keyed by the source's basename, so two sources +# with the same name in different directories collide. The Makefile had a +# `duplicates` target to report this; resolve it at configure time instead. +# +# The Make build let the first directory in DIRS win, silently shadowing the +# others. JOREK_SOURCE_DIRS is ordered to give the same winner, so this keeps +# the two builds producing the same binary -- but say so loudly, because a +# shadowed file is almost always a mistake in the source tree. +function(_jorek_drop_shadowed list_var) + set(_kept "") + set(_seen "") + foreach(_src IN LISTS ${list_var}) + get_filename_component(_name "${_src}" NAME_WE) + if(_name IN_LIST _seen) + list(APPEND JOREK_SHADOWED_SOURCES "${_src}") + else() + list(APPEND _seen "${_name}") + list(APPEND _kept "${_src}") + endif() + endforeach() + set(${list_var} "${_kept}" PARENT_SCOPE) + set(JOREK_SHADOWED_SOURCES "${JOREK_SHADOWED_SOURCES}" PARENT_SCOPE) +endfunction() + +set(JOREK_SHADOWED_SOURCES "") +set(_combined ${JOREK_LIB_SOURCES} ${JOREK_PROGRAM_SOURCES}) +_jorek_drop_shadowed(_combined) +foreach(_src IN LISTS JOREK_SHADOWED_SOURCES) + list(REMOVE_ITEM JOREK_LIB_SOURCES "${_src}") + list(REMOVE_ITEM JOREK_PROGRAM_SOURCES "${_src}") + get_filename_component(_name "${_src}" NAME_WE) + file(RELATIVE_PATH _rel "${CMAKE_SOURCE_DIR}" "${_src}") + message(WARNING + "Duplicate source basename '${_name}': ignoring ${_rel}, which is shadowed by " + "another file of the same name earlier in JOREK_SOURCE_DIRS. This matches what " + "the Make build linked, but the duplicate should be removed from the repository.") +endforeach() + +# --- Sources for the semianalytical code generator --------------------------- +# +# Models 180/183 build their element matrices from symbolic expressions that the +# algexpr2fort program turns into Fortran at build time. algexpr2fort is itself +# built from JOREK sources, so it cannot link the core library: that library +# contains mod_elt_matrix, which #includes the very headers algexpr2fort has not +# produced yet. +# +# Give it instead its own small library holding exactly what it needs -- the +# transitive closure of the modules it `use`s, which is about a dozen files and +# reaches neither mod_elt_matrix nor the generated headers. That keeps the +# dependency graph acyclic and leaves every executable linking a single archive. +# The names a Fortran source depends on: the modules it `use`s, plus the bare +# subroutines it `call`s. JOREK resolves the latter by filename -- a `call foo` +# is satisfied by whichever file is named foo.f90 -- which is what +# util/makedepend did and what the linker relies on. +function(_jorek_source_deps out_var file) + set(_names "") + if(NOT file MATCHES "\\.(f90|f)$") + set(${out_var} "" PARENT_SCOPE) # C/C++ pull in nothing by these rules + return() + endif() + + file(STRINGS "${file}" _lines) + foreach(_line IN LISTS _lines) + string(REGEX REPLACE "!.*$" "" _line "${_line}") # drop comments + + if(_line MATCHES "^[ \t]*[Uu][Ss][Ee][ \t]+([A-Za-z][A-Za-z0-9_]*)") + string(TOLOWER "${CMAKE_MATCH_1}" _m) + list(APPEND _names "${_m}") + endif() + + string(REGEX MATCHALL "[Cc][Aa][Ll][Ll][ \t]+[A-Za-z][A-Za-z0-9_]*" _hits "${_line}") + foreach(_hit IN LISTS _hits) + string(REGEX REPLACE "^[Cc][Aa][Ll][Ll][ \t]+" "" _hit "${_hit}") + string(TOLOWER "${_hit}" _hit) + list(APPEND _names "${_hit}") + endforeach() + endforeach() + + if(_names) + list(REMOVE_DUPLICATES _names) + endif() + set(${out_var} "${_names}" PARENT_SCOPE) +endfunction() + +function(_jorek_forward_closure out_var seed sources) + # Index the pool twice: module name -> providing file, and basename -> file. + foreach(_src IN LISTS sources) + get_filename_component(_base "${_src}" NAME_WE) + string(TOLOWER "${_base}" _base) + if(NOT DEFINED _basefile_${_base}) + set(_basefile_${_base} "${_src}") + endif() + + if(NOT _src MATCHES "\\.f90$") + continue() + endif() + file(STRINGS "${_src}" _lines + REGEX "^[ \t]*[Mm][Oo][Dd][Uu][Ll][Ee][ \t]+[A-Za-z]") + foreach(_line IN LISTS _lines) + if(_line MATCHES "^[ \t]*[Mm][Oo][Dd][Uu][Ll][Ee][ \t]+([A-Za-z][A-Za-z0-9_]*)") + string(TOLOWER "${CMAKE_MATCH_1}" _m) + # `module procedure` in an interface block is not a module definition. + if(NOT _m STREQUAL "procedure") + set(_provider_${_m} "${_src}") + endif() + endif() + endforeach() + endforeach() + + _jorek_source_deps(_pending "${seed}") + set(_closure "") + set(_i 0) + list(LENGTH _pending _n) + while(_i LESS _n) + list(GET _pending ${_i} _name) + math(EXPR _i "${_i} + 1") + + # A name may resolve as a module, as a filename, or both. + set(_hits "") + if(DEFINED _provider_${_name}) + list(APPEND _hits "${_provider_${_name}}") + endif() + if(DEFINED _basefile_${_name}) + list(APPEND _hits "${_basefile_${_name}}") + endif() + + foreach(_src IN LISTS _hits) + if(_src IN_LIST _closure) + continue() + endif() + list(APPEND _closure "${_src}") + + _jorek_source_deps(_deps "${_src}") + foreach(_dep IN LISTS _deps) + if(NOT _dep IN_LIST _pending) + list(APPEND _pending "${_dep}") + math(EXPR _n "${_n} + 1") + endif() + endforeach() + endforeach() + endwhile() + + set(${out_var} "${_closure}" PARENT_SCOPE) +endfunction() + +set(JOREK_GENERATED_INCLUDE_DIR "${CMAKE_BINARY_DIR}/generated/models/${JOREK_MODEL}") +file(MAKE_DIRECTORY "${JOREK_GENERATED_INCLUDE_DIR}") + +set(JOREK_CODEGEN_SOURCES "") +if(JOREK_SEMIANALYTICAL) + _jorek_forward_closure(JOREK_CODEGEN_SOURCES + "${CMAKE_SOURCE_DIR}/models/algexpr2fort.f90" "${JOREK_LIB_SOURCES}") + + # If this ever trips, the equations module has grown a dependency on the code + # it is supposed to generate, and the generator can no longer be built. + foreach(_src IN LISTS JOREK_CODEGEN_SOURCES) + if(_src MATCHES "/mod_elt_matrix(_fft)?\\.f90$") + message(FATAL_ERROR + "algexpr2fort transitively depends on ${_src}, which #includes the headers " + "algexpr2fort generates. This dependency cycle must be broken in the sources.") + endif() + endforeach() + + list(LENGTH JOREK_CODEGEN_SOURCES _n_codegen) + message(STATUS "JOREK: algexpr2fort code generator needs ${_n_codegen} source(s)") +endif() + +list(LENGTH JOREK_LIB_SOURCES _n_lib) +list(LENGTH JOREK_PROGRAM_SOURCES _n_prog) +message(STATUS "JOREK sources: ${_n_lib} library, ${_n_prog} program(s)") diff --git a/cmake/JorekSummary.cmake b/cmake/JorekSummary.cmake new file mode 100644 index 0000000000..4c3b5bc9e1 --- /dev/null +++ b/cmake/JorekSummary.cmake @@ -0,0 +1,32 @@ +# Print what was actually configured. The Make build had `make printsettings` +# for this; here it comes for free at the end of every configure. + +string(REPLACE ";" " " _defines_pretty "${JOREK_DEFINES}") + +message(STATUS "") +message(STATUS "==================== JOREK configuration ====================") +message(STATUS " Model : ${JOREK_MODEL} (number ${JOREK_MODEL_NUMBER})") +message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") +message(STATUS " Executable : jorek_model${JOREK_MODEL_NUMBER}") +message(STATUS " Fortran compiler : ${CMAKE_Fortran_COMPILER} (${CMAKE_Fortran_COMPILER_ID})") +message(STATUS " C / C++ compiler : ${CMAKE_C_COMPILER_ID} / ${CMAKE_CXX_COMPILER_ID}") +message(STATUS " MPI : ${MPI_Fortran_VERSION}") +message(STATUS " BLAS/LAPACK : ${JOREK_BLAS_LAPACK_SUMMARY}") +if(JOREK_USE_HDF5) + message(STATUS " HDF5 : ${JOREK_HDF5_SUMMARY}") +endif() +if(JOREK_USE_FFTW) + message(STATUS " FFTW : ${JOREK_FFTW_SUMMARY}") +endif() +if(JOREK_USE_STRUMPACK) + message(STATUS " STRUMPACK : ${JOREK_STRUMPACK_LIBRARY}") +endif() +if(JOREK_USE_MUMPS) + message(STATUS " MUMPS : ${JOREK_DMUMPS_LIBRARY}") +endif() +if(JOREK_USE_PASTIX OR JOREK_USE_PASTIX6) + message(STATUS " PaStiX : ${JOREK_PASTIX_LIBRARY}") +endif() +message(STATUS " Defines : ${_defines_pretty}") +message(STATUS "============================================================") +message(STATUS "") diff --git a/cmake/JorekVersion.cmake b/cmake/JorekVersion.cmake new file mode 100644 index 0000000000..0fc87c83c1 --- /dev/null +++ b/cmake/JorekVersion.cmake @@ -0,0 +1,47 @@ +# version.h records the git revision, the compile flags and who built the code; +# constants.f90, the restart I/O and jorek2help all #include it and print it +# into the log so a run can be traced back to a build. +# +# It must be regenerated on every build, not just at configure time, because +# the git description changes as you commit. GenerateVersionHeader.cmake only +# rewrites the file when its contents actually change, so this does not force a +# rebuild of the world on every `make`. + +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/generated") +set(JOREK_VERSION_HEADER "${CMAKE_BINARY_DIR}/generated/version.h") + +# The header embeds the exact flags used: the site flags for this build type +# plus the JOREK ones recorded by JorekCompilerFlags. +string(TOUPPER "${CMAKE_BUILD_TYPE}" _cfg) +set(_flags "${CMAKE_Fortran_FLAGS} ${CMAKE_Fortran_FLAGS_${_cfg}} ${JOREK_Fortran_FLAGS_RECORD}") + +string(REPLACE ";" " " _defines_str "${JOREK_DEFINES}") +string(REPLACE ";" " " _includes_str "${JOREK_INCLUDE_DIRS}") +string(REPLACE ";" " " _libs_str "${JOREK_LIBRARIES}") + +add_custom_target(jorek_version_header + BYPRODUCTS "${JOREK_VERSION_HEADER}" + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_FILE=${JOREK_VERSION_HEADER} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DBINARY_DIR=${CMAKE_BINARY_DIR} + -DCOMPILE_COMMAND=${CMAKE_Fortran_COMPILER} + "-DCOMPILE_FLAGS=${_flags}" + "-DCOMPILE_DEFINES=${_defines_str}" + "-DCOMPILE_INCLUDES=${_includes_str}" + "-DCOMPILE_LIBS=${_libs_str}" + -P "${CMAKE_SOURCE_DIR}/cmake/GenerateVersionHeader.cmake" + COMMENT "Checking JOREK version header" + VERBATIM) + +# Run it once now so the header exists before the first compile. +execute_process(COMMAND ${CMAKE_COMMAND} + -DOUTPUT_FILE=${JOREK_VERSION_HEADER} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DBINARY_DIR=${CMAKE_BINARY_DIR} + -DCOMPILE_COMMAND=${CMAKE_Fortran_COMPILER} + "-DCOMPILE_FLAGS=${_flags}" + "-DCOMPILE_DEFINES=${_defines_str}" + "-DCOMPILE_INCLUDES=${_includes_str}" + "-DCOMPILE_LIBS=${_libs_str}" + -P "${CMAKE_SOURCE_DIR}/cmake/GenerateVersionHeader.cmake") From 119f6e51f69ceff8b8f7b29421e4b3c687b3826b Mon Sep 17 00:00:00 2001 From: carrae Date: Wed, 15 Jul 2026 10:07:02 +0200 Subject: [PATCH 02/10] Add viper-intel CMake presets --- CMakePresets.json | 51 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 27e54aa7ea..b69ad3a7d0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -25,7 +25,6 @@ "CMAKE_C_COMPILER": "mpiicx", "CMAKE_CXX_COMPILER": "mpiicpx", "CMAKE_Fortran_FLAGS": "-mcmodel=medium -march=skylake-avx512 -axCORE-AVX512", - "JOREK_USE_MKL": "ON", "JOREK_USE_STRUMPACK": "ON", "JOREK_USE_MUMPS": "OFF", @@ -33,7 +32,6 @@ "JOREK_USE_HDF5": "ON", "JOREK_USE_FFTW": "ON", "JOREK_USE_BLOCK": "ON", - "JOREK_LIB_DIR": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd", "STRUMPACK_ROOT": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd/STRUMPACK/install", "METIS_ROOT": "/pitagora_work/FUPA1_MHD/JOREK_LIBRARIES/libraries_recompile_amd/METIS/install", @@ -48,6 +46,38 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "viper-intel", + "inherits": "base", + "displayName": "Viper CPU - Intel oneAPI + MKL + STRUMPACK (AMD Genoa/Zen4)", + "description": "Requires: module purge && module load intel/2025.3 impi/2021.17 mkl/2025.3 hdf5-mpi/2.0.0 fftw-mpi/3.3.10 cmake", + "cacheVariables": { + "CMAKE_Fortran_COMPILER": "mpiifx", + "CMAKE_C_COMPILER": "mpiicx", + "CMAKE_CXX_COMPILER": "mpiicpx", + "CMAKE_Fortran_FLAGS": "-mcmodel=medium -march=znver4", + "JOREK_USE_MKL": "ON", + "JOREK_USE_STRUMPACK": "ON", + "JOREK_USE_MUMPS": "OFF", + "JOREK_USE_PASTIX": "OFF", + "JOREK_USE_HDF5": "ON", + "JOREK_USE_FFTW": "ON", + "JOREK_USE_BLOCK": "ON", + "JOREK_LIB_DIR": "/u/edocarr/libs/intel_25_03/", + "STRUMPACK_ROOT": "/u/edocarr/libs/intel_25_03/STRUMPACK/install", + "METIS_ROOT": "/u/edocarr/libs/intel_25_03/METIS/install", + "PARMETIS_ROOT": "/u/edocarr/libs/intel_25_03/ParMETIS/install", + "GKLIB_ROOT": "/u/edocarr/libs/intel_25_03/GKlib/install" + } + }, + { + "name": "viper-intel-debug", + "inherits": "viper-intel", + "displayName": "Viper CPU - Intel oneAPI, debug build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } } ], "buildPresets": [ @@ -67,6 +97,23 @@ "name": "pitagora-intel-debug", "configurePreset": "pitagora-intel-debug", "jobs": 16 + }, + { + "name": "viper-intel", + "configurePreset": "viper-intel", + "jobs": 16 + }, + { + "name": "viper-intel-most", + "configurePreset": "viper-intel", + "displayName": "Main solver plus the regression-tested diagnostics", + "targets": ["most"], + "jobs": 16 + }, + { + "name": "viper-intel-debug", + "configurePreset": "viper-intel-debug", + "jobs": 16 } ] } From b4f03e3b66b0acbbefcc85a03bda79fc145a6256 Mon Sep 17 00:00:00 2001 From: carrae Date: Wed, 15 Jul 2026 10:20:12 +0200 Subject: [PATCH 03/10] Change README.txt --- README.txt | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.txt b/README.txt index f802ae61ac..12f28ff72e 100644 --- a/README.txt +++ b/README.txt @@ -6,6 +6,40 @@ For a documentation about the code, see jorek.eu/wiki + +======================================================= + Building with CMake (recommended) +======================================================= + +Each supported machine is a CMake preset in CMakePresets.json. To list them: + + cmake --list-presets + +Pick the preset for your machine (e.g. viper-intel, pitagora-intel; add +-debug for a debug build). Each preset's "description" field states the exact +`module load` line to run first. + + # 1. load the modules listed in the preset's description + # 2. configure once + cmake --preset + + # 3. build (build tree is build//) + cmake --build build/ -j 16 # default: main solver + cmake --build build/ --target jorek2_diagno -j 16 # a specific program + cmake --build build/ --target help # list all targets + +Every .f90 file with a `program` statement becomes a target named after the +file; the built executables land in build//. Set the model and options +per preset in CMakePresets.json (JOREK_MODEL, JOREK_USE_*), not in Makefile.inc. + + +======================================================= + Building with Makefile.inc (DEPRECATED) +======================================================= + +NOTE: the Makefile.inc / `make` build below is deprecated. Prefer the CMake +build above. The instructions are kept for reference only. + to build jorek2: - edit the Makefile.inc file to match your environment From 086ebe835486cbc57952c59dcaf6bfe40b0d3872 Mon Sep 17 00:00:00 2001 From: carrae Date: Wed, 15 Jul 2026 10:33:19 +0200 Subject: [PATCH 04/10] adapt sources discovery for kinetic_develop --- cmake/JorekSources.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/JorekSources.cmake b/cmake/JorekSources.cmake index 7905ca3c81..234cf81061 100644 --- a/cmake/JorekSources.cmake +++ b/cmake/JorekSources.cmake @@ -32,6 +32,7 @@ set(JOREK_SOURCE_DIRS particles/postprocessors/lights particles/postprocessors/spectra particles/postprocessors/utils + particles/initialisers particles/projection_functions particles/pushers # Not optional despite the name: the synthetic-light postprocessors in From 8e6bcc8d66c73f59290c2596092aecc8f9d7a54b Mon Sep 17 00:00:00 2001 From: carrae Date: Wed, 15 Jul 2026 10:34:36 +0200 Subject: [PATCH 05/10] adapt util/config.sh to CMake --- util/config.sh | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/util/config.sh b/util/config.sh index 22d67d68bf..394c8ee6b8 100755 --- a/util/config.sh +++ b/util/config.sh @@ -26,8 +26,8 @@ SCRIPTDIR=`dirname $0`; SCRIPTDIR=`readlink -f $SCRIPTDIR` if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then usage && exit -elif [ -z "Makefile.inc" ]; then - echo "Could not find a ./Makefile.inc. Are you in the JOREK trunk?" >&2 +elif [ ! -f Makefile.inc ] && [ ! -f CMakePresets.json ]; then + echo "Could not find ./Makefile.inc or ./CMakePresets.json. Are you in the JOREK trunk?" >&2 exit 1 fi @@ -48,14 +48,34 @@ function setmodel() { echo "ERROR: Illegal model specified: '$model'." >&2 exit 1 fi - # --- Set model in makefile configuration files - sed -i -e "s/\(^ *MODEL *= *\)[^ ]*\(.*$\)/\1$model\2/" Makefile.inc - # --- Clean up .d/.o/.mod files because of the model change - make cleanall + if [ -f Makefile.inc ]; then + # --- Legacy make build: set model in Makefile.inc, clean stale .d/.o/.mod + sed -i -e "s/\(^ *MODEL *= *\)[^ ]*\(.*$\)/\1$model\2/" Makefile.inc + make cleanall + elif [ -f CMakePresets.json ]; then + # --- CMake build: the model is JOREK_MODEL in CMakePresets.json + sed -i -E "s/(\"JOREK_MODEL\"[[:space:]]*:[[:space:]]*\")model[0-9]+(\")/\1$model\2/" CMakePresets.json + echo "Set JOREK_MODEL=$model in CMakePresets.json." >&2 + echo "Reconfigure to apply, e.g.: cmake --preset " >&2 + else + echo "ERROR: neither Makefile.inc nor CMakePresets.json found; cannot set model." >&2 + exit 1 + fi } +# Determine the active physics model. Priority: +# 1. JOREK_MODEL environment variable (explicit override) +# 2. Makefile.inc (legacy make build) +# 3. JOREK_MODEL in CMakePresets.json (CMake build default) function getmodel() { - egrep "MODEL *= *model[0-9]*" Makefile.inc | sed -e "s/^ *MODEL *= *\(model[0-9]*\).*$/\1/" + if [ -n "$JOREK_MODEL" ]; then + echo "model${JOREK_MODEL#model}" + elif [ -f Makefile.inc ]; then + egrep "MODEL *= *model[0-9]*" Makefile.inc | sed -e "s/^ *MODEL *= *\(model[0-9]*\).*$/\1/" + elif [ -f CMakePresets.json ]; then + grep -oE '"JOREK_MODEL"[[:space:]]*:[[:space:]]*"model[0-9]+"' CMakePresets.json \ + | head -1 | grep -oE 'model[0-9]+' + fi } function setparam() { From 56b0f448ede935452d0437f0541894d253ded14e Mon Sep 17 00:00:00 2001 From: carrae Date: Wed, 15 Jul 2026 10:35:32 +0200 Subject: [PATCH 06/10] adapt .gitignore to cmake --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b9cb7e3c51..976cfd6eb8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ -# CMake out-of-source builds (cmake --preset puts them under build/) +# CMake /build/ CMakeUserPresets.json +/CMakeCache.txt +/CMakeFiles/ *.bck *.o From ec11bdae93ebb4bd344ff293bb7402f8e1612121 Mon Sep 17 00:00:00 2001 From: carrae Date: Wed, 15 Jul 2026 15:35:50 +0200 Subject: [PATCH 07/10] add tok configs --- CMakePresets.json | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/CMakePresets.json b/CMakePresets.json index b69ad3a7d0..96a9385336 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -78,6 +78,38 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "tok-intel", + "inherits": "base", + "displayName": "TOK CPU - Intel oneAPI + MKL + STRUMPACK (IPP Garching)", + "description": "Requires: module purge && module load intel/2025.1 impi/2021.15 mkl fftw-mpi hdf5-mpi cmake", + "cacheVariables": { + "CMAKE_Fortran_COMPILER": "mpiifx", + "CMAKE_C_COMPILER": "mpiicx", + "CMAKE_CXX_COMPILER": "mpiicpx", + "CMAKE_Fortran_FLAGS": "-mcmodel=medium -march=native", + "JOREK_USE_MKL": "ON", + "JOREK_USE_STRUMPACK": "ON", + "JOREK_USE_MUMPS": "OFF", + "JOREK_USE_PASTIX": "OFF", + "JOREK_USE_HDF5": "ON", + "JOREK_USE_FFTW": "ON", + "JOREK_USE_BLOCK": "ON", + "JOREK_LIB_DIR": "/tokp/work/mhoelzl/bin-of-ihol/intel", + "STRUMPACK_ROOT": "/tokp/work/mhoelzl/bin-of-ihol/intel/STRUMPACK-7.1.3/install", + "METIS_ROOT": "/tokp/work/mhoelzl/bin-of-ihol/intel/metis_git/install", + "PARMETIS_ROOT": "/tokp/work/mhoelzl/bin-of-ihol/intel/ParMETIS_git/install", + "GKLIB_ROOT": "/tokp/work/mhoelzl/bin-of-ihol/intel/GKlib_git/install" + } + }, + { + "name": "tok-intel-debug", + "inherits": "tok-intel", + "displayName": "TOK CPU - Intel oneAPI, debug build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } } ], "buildPresets": [ @@ -114,6 +146,23 @@ "name": "viper-intel-debug", "configurePreset": "viper-intel-debug", "jobs": 16 + }, + { + "name": "tok-intel", + "configurePreset": "tok-intel", + "jobs": 16 + }, + { + "name": "tok-intel-most", + "configurePreset": "tok-intel", + "displayName": "Main solver plus the regression-tested diagnostics", + "targets": ["most"], + "jobs": 16 + }, + { + "name": "tok-intel-debug", + "configurePreset": "tok-intel-debug", + "jobs": 16 } ] } From 3c489fa330efb274122eacf3ed8fb7cec8d61b1c Mon Sep 17 00:00:00 2001 From: carrae Date: Thu, 6 Aug 2026 10:16:08 +0200 Subject: [PATCH 08/10] Adding numerics in source dirs --- cmake/JorekSources.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/JorekSources.cmake b/cmake/JorekSources.cmake index 234cf81061..e3a5b72edb 100644 --- a/cmake/JorekSources.cmake +++ b/cmake/JorekSources.cmake @@ -23,6 +23,7 @@ set(JOREK_SOURCE_DIRS matrix models models/${JOREK_MODEL} + numerics particles particles/diagnostics particles/postprocessors From acf35d431c5327ec2c211484cabcf9a65f888864 Mon Sep 17 00:00:00 2001 From: carrae Date: Tue, 28 Jul 2026 11:17:08 +0200 Subject: [PATCH 09/10] Fix warnings in CMake --- cmake/JorekCompilerFlags.cmake | 32 ++++++++++++++++++++++++++------ cmake/JorekSources.cmake | 13 +++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/cmake/JorekCompilerFlags.cmake b/cmake/JorekCompilerFlags.cmake index 676f5c1038..7d7284cd87 100644 --- a/cmake/JorekCompilerFlags.cmake +++ b/cmake/JorekCompilerFlags.cmake @@ -16,6 +16,11 @@ endif() set(_common "") # applied to every Fortran source set(_debug "") # applied to Fortran sources in Debug builds only +# Relaxes the implicit-typing diagnostics for the vendored fixed-form .f sources, +# which declare nothing explicitly; the Make build had the same carve-out in +# F77FLAGS. Applied per source file by JorekSources. +set(JOREK_Fortran_FIXED_FORM_OPTIONS "") + if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") list(APPEND _common @@ -33,22 +38,35 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") -finit-real=snan -finit-integer=12345678 -fimplicit-none) + # gfortran makes -fimplicit-none an error, so without this the fixed-form + # sources do not compile at all in a Debug build. + set(JOREK_Fortran_FIXED_FORM_OPTIONS -fno-implicit-none) + elseif(CMAKE_Fortran_COMPILER_ID MATCHES "^Intel") - # Use the single-token `-opt=value` forms throughout. CMake de-duplicates - # repeated entries in COMPILE_OPTIONS, so a `-warn all -warn nointerfaces` - # spelling collapses to `-warn all nointerfaces` and the compiler then treats - # the orphaned keywords as input filenames. + # -warn and -check take their keyword list as a separate argument -- ifx warns + # (#10158) and drops the option for a `-warn=...` spelling -- so keep the two + # tokens together in a `SHELL:` group, which is also what stops CMake from + # de-duplicating them apart. -init is the opposite case and wants `-init=`. list(APPEND _common -fpp -r8 -align - -warn=all,nointerfaces,nounused,noexternal) + "SHELL:-warn all,nointerfaces,nounused,noexternal" + # #5462 "global name too long" fires all over the tree: ifx truncates mangled + # symbols, keeping the distinguishing tail, and the only fix would be + # renaming modules. No -warn keyword covers it short of -warn nogeneral. + -diag-disable=5462) list(APPEND _debug -traceback - -check=all,noarg_temp_created + "SHELL:-check all,noarg_temp_created" -ftrapuv -fpe0 -init=snan -implicitnone) + # Kills #6717 from both -warn all and -implicitnone. Two plain list elements, + # not a `SHELL:` group: source-file COMPILE_OPTIONS pass `SHELL:` through to + # the compiler verbatim, and are not de-duplicated anyway. + set(JOREK_Fortran_FIXED_FORM_OPTIONS -warn nodeclarations) + # The semianalytical models build very large automatic arrays in the element # matrix routines and overflow the stack without this. if(JOREK_SEMIANALYTICAL) @@ -73,5 +91,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") list(APPEND _record ${_debug}) endif() string(REPLACE ";" " " JOREK_Fortran_FLAGS_RECORD "${_record}") +# `SHELL:` is a CMake marker, not something the compiler ever sees. +string(REPLACE "SHELL:" "" JOREK_Fortran_FLAGS_RECORD "${JOREK_Fortran_FLAGS_RECORD}") find_package(OpenMP REQUIRED COMPONENTS Fortran C CXX) diff --git a/cmake/JorekSources.cmake b/cmake/JorekSources.cmake index e3a5b72edb..934acec507 100644 --- a/cmake/JorekSources.cmake +++ b/cmake/JorekSources.cmake @@ -393,6 +393,19 @@ if(JOREK_SEMIANALYTICAL) message(STATUS "JOREK: algexpr2fort code generator needs ${_n_codegen} source(s)") endif() +# The .f sources take the relaxed diagnostics from JorekCompilerFlags. Source +# properties are scoped to the directory rather than to a target, so this one +# pass covers every target -- they are all defined in the top-level scope. +if(JOREK_Fortran_FIXED_FORM_OPTIONS) + set(_fixed_form ${JOREK_LIB_SOURCES} ${JOREK_PROGRAM_SOURCES} ${JOREK_CODEGEN_SOURCES}) + list(FILTER _fixed_form INCLUDE REGEX "\\.f$") + if(_fixed_form) + list(REMOVE_DUPLICATES _fixed_form) + set_source_files_properties(${_fixed_form} PROPERTIES + COMPILE_OPTIONS "${JOREK_Fortran_FIXED_FORM_OPTIONS}") + endif() +endif() + list(LENGTH JOREK_LIB_SOURCES _n_lib) list(LENGTH JOREK_PROGRAM_SOURCES _n_prog) message(STATUS "JOREK sources: ${_n_lib} library, ${_n_prog} program(s)") From 432690d8b715a4bd7d984a094c8cc7e433b542ab Mon Sep 17 00:00:00 2001 From: carrae Date: Wed, 12 Aug 2026 14:30:41 +0200 Subject: [PATCH 10/10] mirror the fortran flags for the C/C++ compiler --- CMakePresets.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakePresets.json b/CMakePresets.json index 96a9385336..b410fbd4fd 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -25,6 +25,8 @@ "CMAKE_C_COMPILER": "mpiicx", "CMAKE_CXX_COMPILER": "mpiicpx", "CMAKE_Fortran_FLAGS": "-mcmodel=medium -march=skylake-avx512 -axCORE-AVX512", + "CMAKE_C_FLAGS": "-mcmodel=medium -march=skylake-avx512 -axCORE-AVX512", + "CMAKE_CXX_FLAGS": "-mcmodel=medium -march=skylake-avx512 -axCORE-AVX512", "JOREK_USE_MKL": "ON", "JOREK_USE_STRUMPACK": "ON", "JOREK_USE_MUMPS": "OFF", @@ -57,6 +59,8 @@ "CMAKE_C_COMPILER": "mpiicx", "CMAKE_CXX_COMPILER": "mpiicpx", "CMAKE_Fortran_FLAGS": "-mcmodel=medium -march=znver4", + "CMAKE_C_FLAGS": "-mcmodel=medium -march=znver4", + "CMAKE_CXX_FLAGS": "-mcmodel=medium -march=znver4", "JOREK_USE_MKL": "ON", "JOREK_USE_STRUMPACK": "ON", "JOREK_USE_MUMPS": "OFF", @@ -89,6 +93,8 @@ "CMAKE_C_COMPILER": "mpiicx", "CMAKE_CXX_COMPILER": "mpiicpx", "CMAKE_Fortran_FLAGS": "-mcmodel=medium -march=native", + "CMAKE_C_FLAGS": "-mcmodel=medium -march=native", + "CMAKE_CXX_FLAGS": "-mcmodel=medium -march=native", "JOREK_USE_MKL": "ON", "JOREK_USE_STRUMPACK": "ON", "JOREK_USE_MUMPS": "OFF",