Skip to content
Draft
2 changes: 1 addition & 1 deletion .github/workflows/fesom2_intel_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ jobs:
echo "Intel build OK (integration tests skipped)"

# TODO issues with netcdf: cd build
- # ctest -R "integration_*" --output-on-failure --verbose
# ctest -R "integration_*" --output-on-failure --verbose

148 changes: 148 additions & 0 deletions .github/workflows/fesom2_sp_ctest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: FESOM2 SP CTests (single precision)

# Single-precision (USE_SINGLE_PRECISION=ON) counterpart to the DP CTest runner.
# Precision is a property of the BUILD, not the test: this job builds fesom.x (and
# the mesh tools) in single precision with the SAME full config as the DP build, so
# the whole local test suite -- integration (incl. the CVMix schemes) plus the local
# meshpartitioner/meshdiag tests -- registers and runs in SP. Each fesom.x test
# additionally self-asserts the "SINGLE PRECISION MODE" startup banner.
#
# It is OPTIONAL / non-blocking initially (continue-on-error + not a required check):
# DP is already covered by fesom2_ctest_runner.yml and the `default` build preset, so
# this only adds the previously-missing SP coverage. Promote to blocking later by
# removing `continue-on-error`. Widening to SP meshpart/meshdiag/remote needs no build
# change -- only relaxing the ctest selection below.

on:
pull_request:
types:
- opened
- synchronize
- reopened
branches: [ main ]

workflow_dispatch:
inputs:
test_timeout:
description: 'Test timeout in seconds'
required: false
default: '600'
type: string

jobs:
sp_ctests:
runs-on: ubuntu-latest

# Optional / non-blocking while SP support is being stabilised. Remove this line
# (and add the job to branch-protection required checks) to make SP gate merges.
continue-on-error: true

env:
CC: gcc
CXX: g++
FC: gfortran
OMPI_MCA_rmaps_base_oversubscribe: yes

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install system dependencies
run: |
echo "Installing FESOM2 dependencies..."
sudo apt-get update
sudo apt-get install -y \
gcc \
gfortran \
g++ \
cmake \
make \
openmpi-bin \
libopenmpi-dev \
libnetcdf-dev \
libnetcdff-dev \
pkg-config \
git \
wget \
ca-certificates \
gnupg \
lsb-release

echo "Installing CMake 3.25+ from Kitware repository..."
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null
echo "deb https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null
sudo apt-get update
sudo apt-get install -y cmake

echo "Verifying installations..."
gcc --version
gfortran --version
mpirun --version
cmake --version
pkg-config --modversion netcdf
pkg-config --modversion netcdf-fortran

- name: Set up environment variables
run: |
echo "NETCDF_ROOT=/usr" >> $GITHUB_ENV
echo "NETCDF_Fortran_ROOT=/usr" >> $GITHUB_ENV
echo "FCFLAGS=-fallow-argument-mismatch" >> $GITHUB_ENV
echo "FFLAGS=-fallow-argument-mismatch" >> $GITHUB_ENV
echo "OMPI_MCA_btl_vader_single_copy_mechanism=none" >> $GITHUB_ENV
echo "OMPI_MCA_rmaps_base_oversubscribe=yes" >> $GITHUB_ENV
echo "OMPI_MCA_btl_base_warn_component_unused=0" >> $GITHUB_ENV

- name: Configure FESOM2 (single precision, full config = mirrors DP)
run: |
echo "Configuring FESOM2 single-precision build..."
mkdir -p build
cd build
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DUSE_SINGLE_PRECISION=ON \
-DBUILD_TESTING=ON \
-DENABLE_MPI_TESTS=ON \
-DBUILD_MESHDIAG=ON \
-DBUILD_MESHPARTITIONER=ON \
-DTEST_TIMEOUT=${{ github.event.inputs.test_timeout || '600' }} \
-DCMAKE_C_COMPILER=gcc \
-DCMAKE_CXX_COMPILER=g++ \
-DCMAKE_Fortran_COMPILER=gfortran \
-DMPI_C_COMPILER=mpicc \
-DMPI_CXX_COMPILER=mpicxx \
-DMPI_Fortran_COMPILER=mpifort \
-DNetCDF_ROOT=/usr \
-DNetCDF_Fortran_ROOT=/usr \
-DCMAKE_Fortran_FLAGS="-fallow-argument-mismatch" \
..

- name: Build FESOM2
run: |
cd build
make -j$(nproc) VERBOSE=1
if [ -f "bin/fesom.x" ]; then
echo "✅ single-precision fesom.x created"
ls -la bin/fesom.x
else
echo "❌ fesom.x not found"
exit 1
fi

- name: List available tests
run: |
cd build
echo "=== Available Tests ==="
ctest -N
echo "=== Labels ==="
ctest --print-labels

- name: Run CTest (local suite, single precision)
run: |
cd build
echo "Running local tests in single precision (excluding remote + ecbundle)..."
# Local set only: integration_pi_* (incl. cvmix) + local meshpartitioner/meshdiag.
# Remote (network downloads) and ecbundle are excluded. The -E exclusion auto-
# includes any new local test without editing this line.
ctest -E '^(remote_|ecbundle_)' \
--output-on-failure \
--timeout ${{ github.event.inputs.test_timeout || '600' }}
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ set(VERBOSE OFF CACHE BOOL "toggle debug output")
set(FESOM_PROFILING OFF CACHE BOOL "Enable enhanced profiling system")
set(CVMIX ON CACHE BOOL "Download/Compile/Install/Link CVMix libray")

# CVMix is a fixed double-precision library (its kind is cvmix_r8 = real64). It is
# linked in BOTH double- and single-precision FESOM builds: the FESOM wrappers in
# src/cvmix_driver/ convert WP<->cvmix_r8 at the CVMix call boundary, so CVMix always
# runs in double precision. This keeps the available mixing schemes identical in SP and
# DP (so SP and DP configurations can be compared like-for-like) and leaves the default
# DP build numerically unchanged. CVMIX can still be turned off explicitly (-DCVMIX=OFF).

# Testing options
option(BUILD_TESTING "Build tests" OFF)

Expand Down
72 changes: 59 additions & 13 deletions cmake/FesomTesting.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -366,17 +366,36 @@ endfunction()
# Function to add a FESOM integration test with custom options
function(add_fesom_test_with_options TEST_NAME MESH_NAME STEP_PER_DAY RUN_LENGTH RUN_LENGTH_UNIT RESTART_LENGTH RESTART_LENGTH_UNIT LOGFILE_OUTFREQ FORCE_ROTATION USE_CAVITY)
set(options MPI_TEST)
set(oneValueArgs NP TIMEOUT)
set(multiValueArgs COMMAND_ARGS)
set(oneValueArgs NP TIMEOUT LABEL MIX_SCHEME)
set(multiValueArgs COMMAND_ARGS EXTRA_SUCCESS_MARKERS)
cmake_parse_arguments(FESOM_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

# Set defaults
if(NOT DEFINED FESOM_TEST_NP)
set(FESOM_TEST_NP 1)
endif()
if(NOT DEFINED FESOM_TEST_TIMEOUT)
set(FESOM_TEST_TIMEOUT 300) # 5 minutes default
endif()

# Assemble the success-marker list: the mandatory clean-exit marker plus any
# caller-supplied EXTRA_SUCCESS_MARKERS (e.g. the single-precision banner).
# Each is quoted so check_fesom_run() receives them as distinct list items.
set(_success_markers "\"fesom should stop with exit status = 0\"")
foreach(_m IN LISTS FESOM_TEST_EXTRA_SUCCESS_MARKERS)
string(APPEND _success_markers " \"${_m}\"")
endforeach()

# Self-certify the build precision: every fesom.x run prints a working-precision
# banner at startup. Appending the build's banner as a mandatory marker makes each
# integration test verify it actually ran at the intended precision -- so a
# mis-configured build (DP when SP was wanted, or vice versa) fails loudly on every
# test rather than silently running the wrong precision. Silent no-op in DP.
if(USE_SINGLE_PRECISION)
string(APPEND _success_markers " \"SINGLE PRECISION MODE\"")
else()
string(APPEND _success_markers " \"DOUBLE PRECISION MODE\"")
endif()

# Create test run directory
set(TEST_RUN_DIR "${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}")
Expand Down Expand Up @@ -409,9 +428,13 @@ function(add_fesom_test_with_options TEST_NAME MESH_NAME STEP_PER_DAY RUN_LENGTH
set(ENV{OMPI_MCA_rmaps_base_oversubscribe} \"1\")
set(ENV{PRTE_MCA_rmaps_default_mapping_policy} \":oversubscribe\")

# Run FESOM with MPI
# Run FESOM with MPI.
# Wrap the launch in a shell that raises the stack limit: Intel-compiled
# fesom.x puts large per-column automatic arrays on the stack and SIGSEGVs
# on global meshes (e.g. core2) under the default 8 MB limit. ulimit is
# inherited by mpiexec and the ranks it spawns locally. Harmless for GNU/CI.
execute_process(
COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${FESOM_TEST_NP} ${CMAKE_BINARY_DIR}/bin/fesom.x
COMMAND /bin/sh -c \"ulimit -s unlimited 2>/dev/null || ulimit -s 1048576 2>/dev/null; exec '${MPIEXEC_EXECUTABLE}' ${MPIEXEC_NUMPROC_FLAG} ${FESOM_TEST_NP} '${CMAKE_BINARY_DIR}/bin/fesom.x'\"
WORKING_DIRECTORY \"${TEST_RUN_DIR}\"
RESULT_VARIABLE test_result
OUTPUT_VARIABLE test_output
Expand All @@ -430,7 +453,7 @@ function(add_fesom_test_with_options TEST_NAME MESH_NAME STEP_PER_DAY RUN_LENGTH
RESULT \"\${test_result}\"
OUTPUT_LOG \"${TEST_RUN_DIR}/test_output.log\"
ERROR_LOG \"${TEST_RUN_DIR}/test_error.log\"
SUCCESS_MARKERS \"fesom should stop with exit status = 0\"
SUCCESS_MARKERS ${_success_markers}
REQUIRED_ARTIFACTS \"${RESULT_DIR}/sst.fesom.1948.nc\"
)
")
Expand All @@ -441,9 +464,10 @@ function(add_fesom_test_with_options TEST_NAME MESH_NAME STEP_PER_DAY RUN_LENGTH
file(MAKE_DIRECTORY \"${TEST_RUN_DIR}\")
file(MAKE_DIRECTORY \"${RESULT_DIR}\")

# Run FESOM
# Run FESOM (serial). Raise the stack limit as for the MPI case above
# (Intel fesom.x overflows the default 8 MB stack on large meshes).
execute_process(
COMMAND ${CMAKE_BINARY_DIR}/bin/fesom.x
COMMAND /bin/sh -c \"ulimit -s unlimited 2>/dev/null || ulimit -s 1048576 2>/dev/null; exec '${CMAKE_BINARY_DIR}/bin/fesom.x'\"
WORKING_DIRECTORY \"${TEST_RUN_DIR}\"
RESULT_VARIABLE test_result
OUTPUT_VARIABLE test_output
Expand All @@ -462,15 +486,26 @@ function(add_fesom_test_with_options TEST_NAME MESH_NAME STEP_PER_DAY RUN_LENGTH
RESULT \"\${test_result}\"
OUTPUT_LOG \"${TEST_RUN_DIR}/test_output.log\"
ERROR_LOG \"${TEST_RUN_DIR}/test_error.log\"
SUCCESS_MARKERS \"fesom should stop with exit status = 0\"
SUCCESS_MARKERS ${_success_markers}
REQUIRED_ARTIFACTS \"${RESULT_DIR}/sst.fesom.1948.nc\"
)
")
endif()

# Configure namelists for this test with custom options
configure_fesom_namelists_with_options("${TEST_RUN_DIR}" "${TEST_DATA_DIR}" "${RESULT_DIR}" "${MESH_NAME}" "${STEP_PER_DAY}" "${RUN_LENGTH}" "${RUN_LENGTH_UNIT}" "${RESTART_LENGTH}" "${RESTART_LENGTH_UNIT}" "${LOGFILE_OUTFREQ}" "${FORCE_ROTATION}" "${USE_CAVITY}")


# Optional: override the vertical mixing scheme in namelist.oce (e.g. to
# exercise a CVMix scheme). Whitespace-tolerant and anchored on the char
# before the key, matching the harness's other namelist rewrites; the
# trailing comment is preserved.
if(DEFINED FESOM_TEST_MIX_SCHEME)
file(READ "${TEST_RUN_DIR}/namelist.oce" _oce_content)
string(REGEX REPLACE "([^A-Za-z0-9_]mix_scheme[ \t]*=[ \t]*)'[^']*'"
"\\1'${FESOM_TEST_MIX_SCHEME}'" _oce_content "${_oce_content}")
file(WRITE "${TEST_RUN_DIR}/namelist.oce" "${_oce_content}")
endif()

# Add the test
add_test(
NAME ${TEST_NAME}
Expand All @@ -482,7 +517,17 @@ function(add_fesom_test_with_options TEST_NAME MESH_NAME STEP_PER_DAY RUN_LENGTH
TIMEOUT ${FESOM_TEST_TIMEOUT}
WORKING_DIRECTORY ${TEST_RUN_DIR}
)


# Every fesom.x integration test carries the base label 'integration', plus any
# extra label passed via LABEL (e.g. cvmix). CTest LABELS is a list, so this yields
# e.g. "integration" for the base pi tests and "integration;cvmix" for CVMix tests:
# `ctest -L integration` runs the whole suite, `ctest -L cvmix` isolates CVMix.
set(_labels "integration")
if(DEFINED FESOM_TEST_LABEL)
list(APPEND _labels "${FESOM_TEST_LABEL}")
endif()
set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${_labels}")

# For MPI tests, set required properties
if(FESOM_TEST_MPI_TEST AND FESOM_TEST_NP GREATER 1)
set_tests_properties(${TEST_NAME} PROPERTIES
Expand Down Expand Up @@ -584,9 +629,10 @@ function(add_fesom_meshdiag_test_with_options TEST_NAME MESH_NAME RUNID)
set(ENV{OMPI_MCA_rmaps_base_oversubscribe} \"1\")
set(ENV{PRTE_MCA_rmaps_default_mapping_policy} \":oversubscribe\")

# Run fesom_meshdiag with MPI
# Run fesom_meshdiag with MPI. Raise the stack limit (Intel builds overflow
# the default 8 MB stack on large meshes); inherited by mpiexec + ranks.
execute_process(
COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${FESOM_TEST_NP} ${CMAKE_BINARY_DIR}/bin/fesom_meshdiag
COMMAND /bin/sh -c \"ulimit -s unlimited 2>/dev/null || ulimit -s 1048576 2>/dev/null; exec '${MPIEXEC_EXECUTABLE}' ${MPIEXEC_NUMPROC_FLAG} ${FESOM_TEST_NP} '${CMAKE_BINARY_DIR}/bin/fesom_meshdiag'\"
WORKING_DIRECTORY \"${TEST_RUN_DIR}\"
RESULT_VARIABLE test_result
OUTPUT_VARIABLE test_output
Expand Down
4 changes: 4 additions & 0 deletions config/namelist.io
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ compression_level = 1 ! compression level for netCDF output (1=fastest, 9
! frequency = output frequency (integer)
! unit = 'y' (yearly), 'm' (monthly), 'd' (daily), 'h' (hourly), 's' (steps)
! precision = 4 (single precision) or 8 (double precision)
! Note: in a single-precision build (WP=4) a field requesting precision 8 is
! still honoured (written as NF_DOUBLE; means accumulated in real64), but its
! samples are single-precision-sourced. FESOM prints one summary line at
! startup listing such fields.
! ============================================================================
&nml_list
io_list = 'sst ',1, 'm', 4,
Expand Down
Loading
Loading