From 07409edc368ef59995c8b981e6c06b52ad51f823 Mon Sep 17 00:00:00 2001 From: John Biddiscombe Date: Mon, 16 Feb 2026 23:34:55 +0100 Subject: [PATCH] Add a Validation check for LandauDamping, also fix merge queue dashboard labels --- alpine/CMakeLists.txt | 10 +- alpine/validation/CMakeLists.txt | 23 ++ .../validation/FieldLandau_valid_result.csv | 27 ++ .../validation/LandauDampingCorrectness.cpp | 333 ++++++++++++++++++ ci/cscs/common.yml | 4 +- ci/cscs/cuda/build_sm90.yml | 17 +- ci/cscs/dashboard-configure-build.cmake | 7 +- ci/cscs/openmp/build_openmp.yml | 18 +- ci/cscs/rocm/build_rocm-6.3.yml | 16 +- 9 files changed, 426 insertions(+), 29 deletions(-) create mode 100644 alpine/validation/CMakeLists.txt create mode 100644 alpine/validation/FieldLandau_valid_result.csv create mode 100644 alpine/validation/LandauDampingCorrectness.cpp diff --git a/alpine/CMakeLists.txt b/alpine/CMakeLists.txt index 4db043d4f..ff9856ba3 100644 --- a/alpine/CMakeLists.txt +++ b/alpine/CMakeLists.txt @@ -16,9 +16,13 @@ endfunction() if(IPPL_ENABLE_TESTS) # cmake-format: off + # Landau will write a CSV file to the data directory that we will validate later + make_directory("${PROJECT_BINARY_DIR}/alpine/data") + # Add the test add_ippl_integration_test(LandauDamping - ARGS 16 16 16 10000000 10 FFT 0.01 LeapFrog --overallocate 2.0 --info 10 - LABELS alpine integration) + ARGS "16" "16" "16" "10000000" "25" "FFT" "0.01" "LeapFrog" "--overallocate" "2.0" "--info" "10" + LABELS alpine integration + WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/alpine/") # cmake-format: on else() add_alpine_example(LandauDamping) @@ -26,3 +30,5 @@ endif() add_alpine_example(PenningTrap) add_alpine_example(BumponTailInstability) + +add_subdirectory(validation) diff --git a/alpine/validation/CMakeLists.txt b/alpine/validation/CMakeLists.txt new file mode 100644 index 000000000..5236a9ba0 --- /dev/null +++ b/alpine/validation/CMakeLists.txt @@ -0,0 +1,23 @@ +# ----------------------------------------------------------------------------- +# validation check for LandauDamping test +# ----------------------------------------------------------------------------- + +if(BUILD_TESTING) + # Build the C++ correctness validation test + add_executable(LandauDampingCorrectness LandauDampingCorrectness.cpp) + + message("Adding test: LandauDampingCorrectness") + + # command line params are : output, reference, tolerance + add_test( + NAME LandauDampingCorrectnessValidation + COMMAND LandauDampingCorrectness "${PROJECT_BINARY_DIR}/alpine/data/FieldLandau_2_manager.csv" + "${PROJECT_SOURCE_DIR}/alpine/validation/FieldLandau_valid_result.csv" "4E-1" + WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/alpine/") + + set_tests_properties( + LandauDampingCorrectnessValidation + PROPERTIES LABELS "validation;alpine" TIMEOUT 30 + # This test should only run if the LandauDamping integration test passed + DEPENDS LandauDamping) +endif() diff --git a/alpine/validation/FieldLandau_valid_result.csv b/alpine/validation/FieldLandau_valid_result.csv new file mode 100644 index 000000000..9eb047189 --- /dev/null +++ b/alpine/validation/FieldLandau_valid_result.csv @@ -0,0 +1,27 @@ +time, Ex_field_energy, Ex_max_norm +0.0000000000000000e+00 9.6742715140721582e+00 1.0825163128256524e-01 +5.0000000000000003e-02 9.6368948973459112e+00 1.0808994906148438e-01 +1.0000000000000001e-01 9.5415075224669970e+00 1.0740779743767520e-01 +1.5000000000000002e-01 9.3882773328014082e+00 1.0630178072727425e-01 +2.0000000000000001e-01 9.1791183371635334e+00 1.0482602738140372e-01 +2.5000000000000000e-01 8.9172134334449193e+00 1.0306560288960222e-01 +2.9999999999999999e-01 8.6061662103776140e+00 1.0113820775231776e-01 +3.4999999999999998e-01 8.2505504653792148e+00 9.8691447720593309e-02 +3.9999999999999997e-01 7.8550417931569703e+00 9.5598706257118735e-02 +4.4999999999999996e-01 7.4249700904468465e+00 9.2872937917417214e-02 +4.9999999999999994e-01 6.9653510910124385e+00 9.0033318617501135e-02 +5.4999999999999993e-01 6.4822476651919327e+00 8.7257335757243495e-02 +5.9999999999999998e-01 5.9816257151911403e+00 8.4644036660629890e-02 +6.5000000000000002e-01 5.4696405485153790e+00 8.2154906259950061e-02 +7.0000000000000007e-01 4.9535573676676501e+00 7.9019365369956629e-02 +7.5000000000000011e-01 4.4392121571874581e+00 7.5190519676643197e-02 +8.0000000000000016e-01 3.9321278953403862e+00 7.1054552970285123e-02 +8.5000000000000020e-01 3.4384135389096344e+00 6.6356647790906387e-02 +9.0000000000000024e-01 2.9638108812800437e+00 6.1694793644303261e-02 +9.5000000000000029e-01 2.5131142881540285e+00 5.7498634682739851e-02 +1.0000000000000002e+00 2.0909400590908556e+00 5.3113907328247389e-02 +1.0500000000000003e+00 1.7017166009804880e+00 4.8965793799131234e-02 +1.1000000000000003e+00 1.3486230947669464e+00 4.4410412852319857e-02 +1.1500000000000004e+00 1.0343505136217537e+00 3.9926242617971765e-02 +1.2000000000000004e+00 7.6105675926529781e-01 3.5819185614705762e-02 +1.2500000000000004e+00 5.3020078549605387e-01 3.1521337179656390e-02 diff --git a/alpine/validation/LandauDampingCorrectness.cpp b/alpine/validation/LandauDampingCorrectness.cpp new file mode 100644 index 000000000..065c6c477 --- /dev/null +++ b/alpine/validation/LandauDampingCorrectness.cpp @@ -0,0 +1,333 @@ +// LandauDamping Correctness Validation Test +// +// This test compares the CSV output generated by the LandauDamping simulation +// with a reference (valid) result file. All numerical values must be within +// a tolerance of 1E-7 for the test to pass. +// +// Usage: +// ./LandauDampingCorrectness [output_csv] [reference_csv] [tolerance] +// +// Where: +// output_csv : Path to the generated FieldLandau_*.csv file +// (default: data/FieldLandau_*_manager.csv - finds first match) +// reference_csv : Path to the reference file +// (default: ../../alpine/validation/FieldLandau_valid_result.csv) +// tolerance : Absolute tolerance for comparison (default: 1e-7) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// Structure to hold a row of CSV data +struct CSVRow { + double time; + double Ex_field_energy; + double Ex_max_norm; + + CSVRow() + : time(0.0) + , Ex_field_energy(0.0) + , Ex_max_norm(0.0) {} + + CSVRow(double t, double e, double n) + : time(t) + , Ex_field_energy(e) + , Ex_max_norm(n) {} +}; + +// Trim whitespace from string +std::string trim(const std::string& str) { + size_t first = str.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) + return ""; + size_t last = str.find_last_not_of(" \t\r\n"); + return str.substr(first, (last - first + 1)); +} + +// Find first CSV file matching pattern in directory +std::string findCSVFile(const std::string& pattern) { + // If pattern is a file that exists, return it + if (fs::exists(pattern) && fs::is_regular_file(pattern)) { + return pattern; + } + + // Try to find a matching file in data/ directory + std::string dataDir = "data"; + if (fs::exists(dataDir) && fs::is_directory(dataDir)) { + for (const auto& entry : fs::directory_iterator(dataDir)) { + if (entry.is_regular_file() && entry.path().extension() == ".csv") { + std::string filename = entry.path().filename().string(); + if (filename.find("FieldLandau_") == 0 + && filename.find("_manager.csv") != std::string::npos) { + return entry.path().string(); + } + } + } + } + + return pattern; // Return original pattern if not found +} + +// Load CSV file into vector of CSVRow +bool loadCSV(const std::string& filepath, std::vector& data, bool& hasMaxNorm, + std::string& errorMsg) { + std::ifstream file(filepath); + if (!file.is_open()) { + errorMsg = "Could not open file: " + filepath; + return false; + } + + std::string line; + bool isFirstLine = true; + int lineNum = 0; + hasMaxNorm = false; + + while (std::getline(file, line)) { + lineNum++; + line = trim(line); + + // Skip empty lines + if (line.empty()) + continue; + + // Check header line + if (isFirstLine) { + isFirstLine = false; + // Check if we have Ex_max_norm column + hasMaxNorm = (line.find("Ex_max_norm") != std::string::npos); + continue; + } + + // Parse data line + std::istringstream iss(line); + double time, energy, maxNorm = 0.0; + + if (!(iss >> time >> energy)) { + errorMsg = "Failed to parse line " + std::to_string(lineNum) + ": " + line; + return false; + } + + // Try to read third column if present + if (hasMaxNorm) { + if (!(iss >> maxNorm)) { + // If header says we have max_norm but can't read it, it's an error + errorMsg = "Expected Ex_max_norm column but failed to parse at line " + + std::to_string(lineNum); + return false; + } + } + + data.emplace_back(time, energy, maxNorm); + } + + file.close(); + + if (data.empty()) { + errorMsg = "No data rows found in file: " + filepath; + return false; + } + + return true; +} + +// Compare two CSV datasets +bool compareCSVData(const std::vector& output, const std::vector& reference, + bool outputHasMaxNorm, bool refHasMaxNorm, double tolerance, + std::vector& errors) { + // Check row count + if (output.size() != reference.size()) { + std::ostringstream oss; + oss << "Row count mismatch: output has " << output.size() << " rows, reference has " + << reference.size() << " rows"; + errors.push_back(oss.str()); + return false; + } + + // Compare each row + std::vector timeErrors, energyErrors, normErrors; + double maxTimeDiff = 0.0, maxEnergyDiff = 0.0, maxNormDiff = 0.0; + + for (size_t i = 0; i < output.size(); ++i) { + // Compare time + double timeDiff = std::abs(output[i].time - reference[i].time); + if (timeDiff > tolerance) { + timeErrors.push_back(static_cast(i)); + maxTimeDiff = std::max(maxTimeDiff, timeDiff); + } + + // Compare Ex_field_energy + double energyDiff = std::abs(output[i].Ex_field_energy - reference[i].Ex_field_energy); + if (energyDiff > tolerance) { + energyErrors.push_back(static_cast(i)); + maxEnergyDiff = std::max(maxEnergyDiff, energyDiff); + } + + // Compare Ex_max_norm if both have it + if (outputHasMaxNorm && refHasMaxNorm) { + double normDiff = std::abs(output[i].Ex_max_norm - reference[i].Ex_max_norm); + if (normDiff > tolerance) { + normErrors.push_back(static_cast(i)); + maxNormDiff = std::max(maxNormDiff, normDiff); + } + } + } + + // Report errors + bool hasErrors = false; + + if (!timeErrors.empty()) { + hasErrors = true; + std::ostringstream oss; + oss << "Column 'time': " << timeErrors.size() << " value(s) outside tolerance " + << "(tolerance=" << std::scientific << tolerance << ", max_diff=" << maxTimeDiff + << ") at rows ["; + for (size_t i = 0; i < std::min(timeErrors.size(), size_t(10)); ++i) { + if (i > 0) + oss << ", "; + oss << timeErrors[i]; + } + if (timeErrors.size() > 10) + oss << ", ..."; + oss << "]"; + errors.push_back(oss.str()); + } + + if (!energyErrors.empty()) { + hasErrors = true; + std::ostringstream oss; + oss << "Column 'Ex_field_energy': " << energyErrors.size() << " value(s) outside tolerance " + << "(tolerance=" << std::scientific << tolerance << ", max_diff=" << maxEnergyDiff + << ") at rows ["; + for (size_t i = 0; i < std::min(energyErrors.size(), size_t(10)); ++i) { + if (i > 0) + oss << ", "; + oss << energyErrors[i]; + } + if (energyErrors.size() > 10) + oss << ", ..."; + oss << "]"; + errors.push_back(oss.str()); + } + + if (!normErrors.empty()) { + hasErrors = true; + std::ostringstream oss; + oss << "Column 'Ex_max_norm': " << normErrors.size() << " value(s) outside tolerance " + << "(tolerance=" << std::scientific << tolerance << ", max_diff=" << maxNormDiff + << ") at rows ["; + for (size_t i = 0; i < std::min(normErrors.size(), size_t(10)); ++i) { + if (i > 0) + oss << ", "; + oss << normErrors[i]; + } + if (normErrors.size() > 10) + oss << ", ..."; + oss << "]"; + errors.push_back(oss.str()); + } + + return !hasErrors; +} + +int main(int argc, char* argv[]) { + // Default values + std::string outputCSV = "data/FieldLandau_*_manager.csv"; + std::string referenceCSV = "../../alpine/validation/FieldLandau_valid_result.csv"; + double tolerance = 1e-7; + + // Parse command line arguments + if (argc > 1) { + if (std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") { + std::cout << "LandauDamping Correctness Validation Test\n\n" + << "Usage: " << argv[0] << " [output_csv] [reference_csv] [tolerance]\n\n" + << "Arguments:\n" + << " output_csv : Path to generated CSV file (default: " + "data/FieldLandau_*_manager.csv)\n" + << " reference_csv : Path to reference CSV file (default: " + "../../alpine/validation/FieldLandau_valid_result.csv)\n" + << " tolerance : Absolute tolerance for comparison (default: 1e-7)\n\n" + << "Exit codes:\n" + << " 0 : PASS - Output matches reference within tolerance\n" + << " 1 : FAIL - Output does not match reference\n" + << " 2 : ERROR - File I/O or parsing error\n\n" + << "Examples:\n" + << " " << argv[0] << "\n" + << " " << argv[0] << " data/FieldLandau_2_manager.csv\n" + << " " << argv[0] << " output.csv reference.csv 1e-6\n"; + return 0; + } + outputCSV = argv[1]; + } + if (argc > 2) { + referenceCSV = argv[2]; + } + if (argc > 3) { + tolerance = std::atof(argv[3]); + } + + // Find output file if pattern given + outputCSV = findCSVFile(outputCSV); + + std::cout << "LandauDamping Correctness Validation\n"; + std::cout << "=====================================\n"; + std::cout << "Output file: " << outputCSV << "\n"; + std::cout << "Reference file: " << referenceCSV << "\n"; + std::cout << "Tolerance: " << std::scientific << tolerance << "\n"; + std::cout << std::endl; + + // Load output CSV + std::vector outputData; + bool outputHasMaxNorm = false; + std::string errorMsg; + + if (!loadCSV(outputCSV, outputData, outputHasMaxNorm, errorMsg)) { + std::cerr << "✗ ERROR: Failed to load output CSV\n"; + std::cerr << " " << errorMsg << std::endl; + return 2; + } + + std::cout << "Loaded output: " << outputData.size() << " rows"; + if (outputHasMaxNorm) + std::cout << " (with Ex_max_norm column)"; + std::cout << std::endl; + + // Load reference CSV + std::vector referenceData; + bool refHasMaxNorm = false; + + if (!loadCSV(referenceCSV, referenceData, refHasMaxNorm, errorMsg)) { + std::cerr << "✗ ERROR: Failed to load reference CSV\n"; + std::cerr << " " << errorMsg << std::endl; + return 2; + } + + std::cout << "Loaded reference: " << referenceData.size() << " rows"; + if (refHasMaxNorm) + std::cout << " (with Ex_max_norm column)"; + std::cout << std::endl; + std::cout << std::endl; + + // Compare data + std::vector errors; + bool passed = compareCSVData(outputData, referenceData, outputHasMaxNorm, refHasMaxNorm, + tolerance, errors); + + if (passed) { + std::cout << "✓ PASS: Output matches reference within tolerance" << std::endl; + return 0; + } else { + std::cout << "✗ FAIL: Output does not match reference" << std::endl; + for (const auto& error : errors) { + std::cout << " - " << error << std::endl; + } + return 1; + } +} diff --git a/ci/cscs/common.yml b/ci/cscs/common.yml index 8439b1afd..aab7f3bef 100644 --- a/ci/cscs/common.yml +++ b/ci/cscs/common.yml @@ -6,7 +6,9 @@ include: - echo "CI_PROJECT_URL=$CI_PROJECT_URL" - echo "CI_COMMIT_SHORT_SHA=$CI_COMMIT_SHORT_SHA" - | - if [[ "$CI_COMMIT_REF_NAME" =~ pr([0-9]+)$ ]]; then + if [[ "$CI_COMMIT_REF_NAME" =~ gh-readonly-queue.*-pr-([0-9]+)- ]]; then + export CDASH_LABEL="Merge-Queue-PR-${BASH_REMATCH[1]}" + elif [[ "$CI_COMMIT_REF_NAME" =~ pr([0-9]+)$ ]]; then export CDASH_LABEL="PR-${BASH_REMATCH[1]}" else export CDASH_LABEL="$CI_COMMIT_REF_SLUG" diff --git a/ci/cscs/cuda/build_sm90.yml b/ci/cscs/cuda/build_sm90.yml index b54ffdbb4..6175f92c5 100644 --- a/ci/cscs/cuda/build_sm90.yml +++ b/ci/cscs/cuda/build_sm90.yml @@ -27,17 +27,18 @@ variables: - env | sort | grep CI script: - >- - ctest -V -S $CI_PROJECT_DIR/ci/cscs/dashboard-configure-build.cmake + ctest -V -S $CI_PROJECT_DIR/ci/cscs/dashboard-configure-build.cmake -DCTEST_SITE="$CTEST_SITE" - -DPRESET=alps-gh200 - -DCDASH_LABEL=$CDASH_LABEL + -DPRESET=alps-gh200 + -DCDASH_LABEL=$CDASH_LABEL -DBUILD_TYPE=$BUILD_TYPE -DBUILD_DIR=$BUILD_PATH - -DBUILD_ARCH=$BUILD_ARCH - -DKokkos_VERSION=git.4.7.02 - -DKokkos_ARCH_FLAG=Kokkos_ARCH_HOPPER90 - -DMPIEXEC_EXECUTABLE=/usr/bin/srun - -DMPIEXEC_PREFLAGS="$SRUN_FLAGS" + -DBUILD_ARCH=$BUILD_ARCH + -DHeffte_VERSION=git.v2.4.1 + -DKokkos_VERSION=git.4.7.02 + -DKokkos_ARCH_FLAG=Kokkos_ARCH_HOPPER90 + -DMPIEXEC_EXECUTABLE=/usr/bin/srun + -DMPIEXEC_PREFLAGS="$SRUN_FLAGS" -DMPIEXEC_MAX_NUMPROCS=4 - echo "Build directory size (before cleanup):" $(du -sh $BUILD_PATH | cut -f1) - find $BUILD_PATH -name \*.o -delete diff --git a/ci/cscs/dashboard-configure-build.cmake b/ci/cscs/dashboard-configure-build.cmake index b12f67b12..ed460f165 100644 --- a/ci/cscs/dashboard-configure-build.cmake +++ b/ci/cscs/dashboard-configure-build.cmake @@ -50,7 +50,12 @@ set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DMPIEXEC_EXECUTABLE=${M set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}") set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DMPIEXEC_MAX_NUMPROCS=${MPIEXEC_MAX_NUMPROCS}") -set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DKokkos_VERSION=${Kokkos_VERSION}") +if(DEFINED Heffte_VERSION) + set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DHeffte_VERSION=${Heffte_VERSION}") +endif() +if(DEFINED Kokkos_VERSION) + set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DKokkos_VERSION=${Kokkos_VERSION}") +endif() set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -D${Kokkos_ARCH_FLAG}=ON") # --- configure & build --- diff --git a/ci/cscs/openmp/build_openmp.yml b/ci/cscs/openmp/build_openmp.yml index ff4896956..4387273f2 100644 --- a/ci/cscs/openmp/build_openmp.yml +++ b/ci/cscs/openmp/build_openmp.yml @@ -28,19 +28,19 @@ variables: - export CXX=g++ script: - >- - ctest -V -S $CI_PROJECT_DIR/ci/cscs/dashboard-configure-build.cmake + ctest -V -S $CI_PROJECT_DIR/ci/cscs/dashboard-configure-build.cmake -DCTEST_SITE="$CTEST_SITE" - -DPRESET=release-testing - -DCDASH_LABEL=$CDASH_LABEL - -DBUILD_TYPE=$BUILD_TYPE + -DPRESET=release-testing + -DCDASH_LABEL=$CDASH_LABEL + -DBUILD_TYPE=$BUILD_TYPE -DBUILD_DIR=$BUILD_PATH - -DBUILD_ARCH=$BUILD_ARCH + -DBUILD_ARCH=$BUILD_ARCH -DIPPL_PLATFORMS=OPENMP -DIPPL_OPENMP_THREADS=32 - -DKokkos_VERSION=git.4.7.02 - -DKokkos_ARCH_FLAG=Kokkos_ARCH_AMD_GFX942_APU - -DMPIEXEC_EXECUTABLE=/usr/bin/srun - -DMPIEXEC_PREFLAGS="$SRUN_FLAGS" + -DKokkos_VERSION=git.4.7.02 + -DKokkos_ARCH_FLAG=Kokkos_ARCH_AMD_GFX942_APU + -DMPIEXEC_EXECUTABLE=/usr/bin/srun + -DMPIEXEC_PREFLAGS="$SRUN_FLAGS" -DMPIEXEC_MAX_NUMPROCS=4 - echo "Build directory size (before cleanup):" $(du -sh $BUILD_PATH | cut -f1) - find $BUILD_PATH -name \*.o -delete diff --git a/ci/cscs/rocm/build_rocm-6.3.yml b/ci/cscs/rocm/build_rocm-6.3.yml index d09a7de6f..4735f8fa8 100644 --- a/ci/cscs/rocm/build_rocm-6.3.yml +++ b/ci/cscs/rocm/build_rocm-6.3.yml @@ -32,17 +32,17 @@ variables: - export CMAKE_PREFIX_PATH=$HSA_PATH:$CMAKE_PREFIX_PATH script: - >- - ctest -V -S $CI_PROJECT_DIR/ci/cscs/dashboard-configure-build.cmake + ctest -V -S $CI_PROJECT_DIR/ci/cscs/dashboard-configure-build.cmake -DCTEST_SITE="$CTEST_SITE" - -DPRESET=alps-mi300 - -DCDASH_LABEL=$CDASH_LABEL + -DPRESET=alps-mi300 + -DCDASH_LABEL=$CDASH_LABEL -DBUILD_TYPE=$BUILD_TYPE -DBUILD_DIR=$BUILD_PATH - -DBUILD_ARCH=$BUILD_ARCH - -DKokkos_VERSION=git.4.7.02 - -DKokkos_ARCH_FLAG=Kokkos_ARCH_AMD_GFX942_APU - -DMPIEXEC_EXECUTABLE=/usr/bin/srun - -DMPIEXEC_PREFLAGS="$SRUN_FLAGS" + -DBUILD_ARCH=$BUILD_ARCH + -DKokkos_VERSION=git.4.7.02 + -DKokkos_ARCH_FLAG=Kokkos_ARCH_AMD_GFX942_APU + -DMPIEXEC_EXECUTABLE=/usr/bin/srun + -DMPIEXEC_PREFLAGS="$SRUN_FLAGS" -DMPIEXEC_MAX_NUMPROCS=4 # -DHeffte_ENABLE_GPU_AWARE_MPI=OFF - echo "Build directory size (before cleanup):" $(du -sh $BUILD_PATH | cut -f1)