Scientific Computing

CMake, Python and Pytest

Python can easily be used from CMake, perhaps to simplify test scripts for continuous integration. Python scripts are managed in CMakeLists.txt. First, find Python interpreter:

find_package(Python COMPONENTS Interpreter REQUIRED)

Then to run a simple Python script in a CMake test:

add_test(NAME MatmulPython
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/matmul.py
)

To use PyTest:

add_test(NAME MatmulPython
COMMAND ${Python_EXECUTABLE} -m pytest ${CMAKE_CURRENT_SOURCE_DIR}
)

That will look under tests/test*.py by default.

From the project directory:

cmake -B build -S .
-S .
relative path to CMakeLists.txt e.g. -S src

This generates the makefile, but does not compile anything, since CMake is a build script generator.

Compile:

cmake --build build --parallel
--parallel
Compile code in parallel, if possible.

This uses the appropriate compilers and linkers to generate the executables and libraries.

Execute self-tests you’ve defined with add_test() in CMakeLists.txt:

ctest --test-dir build -V

If that’s too verbose, try:

ctest --test-dir build --output-on-failure

For faster testing by defaulting to running multiple tests in parallel, set CTEST_PARALLEL_LEVEL environment variable.

Suggested .octaverc for GNU Octave

Octave uses startup.m persistent user settings like Matlab. To keep Matlab compatibility, put Octave-specific startup commands and plotting defaults into ~/.octaverc, which sets default parameters for all GNU Octave sessions.

suppress_verbose_help_message(1)
page_output_immediately(1)
page_screen_output(0)
crash_dumps_octave_core(0)
sigterm_dumps_octave_core(0)
sighup_dumps_octave_core(0)

if isfile('~/Documents/MATLAB/startup.m')
  source('~/Documents/MATLAB/startup.m')
endif
*_dumps_*
eliminate nuisance octave-workspace files that appear when Octave is Ctrl+c exited or crashes.
page_output_immediately(1)
make Octave print immediately like Matlab.
if exist
use startup.m file like Matlab.

Set plot defaults: useful for HiDPI systems, control Octave default plot text size of axes and titles, useful for HiDPI systems by adding to “~/.octaverc”:

set(0, "defaultaxesfontsize", 16)
set(0, "defaultlinelinewidth", 2)

adjust 16 to produce the most appealing text labels in:

  • axes tick labels
  • legend key
  • title text

defaultline is the root category for lines, so defaultlinelinewidth is not a typo.

Speedup Octave start (without GUI): don’t autoload Octave packages

matplotlibrc Matplotlib defaults file

Tell current matplotlibrc location:

python -c "import matplotlib; print(matplotlib.matplotlib_fname())"

matplotlibrc location priority

Help end users have the same plotting experience by putting a matplotlibrc file in the Python project directory that users run your script from.

ctest_empty_binary_directory usage

CTest CDash scripts can use the function ctest_empty_binary_directory to recursively remove a CMake build directory to avoid hidden state between test runs in an overall build-test cycle. However, this function will cause the overall CTest run to return a non-zero error code if CMakeCache.txt isn’t present in the build directory. This is confusing in a CI system particularly. While we do appreciate this safety feature over simply using file(REMOVE_RECURSE), it’s necessary to enclose in if() statements like below to avoid false errors.

if(EXISTS ${CTEST_BINARY_DIRECTORY}/CMakeCache.txt)
  ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
endif()

HPC compiler modules

Linux modules are often used by HPC systems allow using more current versions of software than are in the default system repositories. A typical approach is to create an .sh file for a particular job type. To avoid unexpected conflicts or hidden state, load only the modules necessary for a particular project.

Many modules are typically available, listed by:

module avail

The currently loaded modules are listed by:

module list

Load recent GCC:

source gcc.sh

with gcc.sh like:

module load gcc

export CC=gcc CXX=g++ FC=gfortran

Determine available GCC versions:

module avail gcc

Load Intel oneAPI:

source oneapi.sh

with oneapi.sh like:

module load intel-oneapi

export CC=icx CXX=icpx FC=ifx
export MPI_ROOT=$I_MPI_ROOT

Determine available Intel oneAPI versions:

module avail intel-oneapi

Some HPC systems have a too-old GCC version defaulting for Intel oneAPI, which can be fixed by setting GCC toolchain.

CMake detect Cray PE

Cray PE Programming Environment allows easy switching of compilers for developing and running applications on Cray supercomputers. Use the Cray compiler wrappers cc, CC, and ftn instead of directly referencing the compiler backends gcc, g++, and gfortran.

In CMake script, detect if Cray PE is being used, regardless of compiler backend by detecting if environment variable CRAYPE_VERSION is set.

if(DEFINED ENV{CRAYPE_VERSION})
  message("Cray PE detected")
endif()

CMake Find MPIEXEC without FindMPI

CMake superprojects may have one project that builds binary executables with MPI and another project that runs those targets. To avoid the overhead of FindMPI, one can use the following find_program() command to use “mpiexec”.

find_program(MPIEXEC_EXECUTABLE
NAMES mpiexec
HINTS ${MPI_ROOT} ENV MPI_ROOT ENV I_MPI_ROOT
PATHS /usr/lib64 ENV MINGWROOT ENV MSMPI_BIN
PATH_SUFFIXES bin openmpi/bin mpich/bin
DOC "Runs an MPI program"
REQUIRED
)

CTest update command

To ensure expected behavior in a CTest script for ctest_update() when Git is used, set these variables in the CTest script:

set(CTEST_UPDATE_TYPE git)
set(CTEST_UPDATE_COMMAND git)

CMake TARGET_FILE generator expression

The CMake generator expression TARGET_FILE yields the full path to a binary target file. This is useful with add_custom_command() and add_test() when a script is used as part of those commands. For CMake native commands, it’s usually not necessary to use TARGET_FILE.

Example:

add_executable(my_exe main.c)

find_package(Python COMPONENTS Interpreter REQUIRED)

add_test(NAME scripted
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example.py $<TARGET_FILE:my_exe>
)

When using scripts it’s a good practice to also specify the full path as above.

Prohibited Git branch names

Git doesn’t have any general letter-specific restrictions on branch names. Organizations can implement push restrictions with services like GitHub to restrict branch names–for example bad words or regex patterns.

The Windows operating system has general filename restrictions that restrict several keywords from Git branch names. A list of case-insensitive prohibited branch names includes Windows devices names.

For example on Windows:

> git switch -c con

fatal: cannot lock ref 'refs/heads/con': Unable to create '.git/refs/heads/con.lock': Invalid argument

Note that one can work with those branch names using Windows Subsystem for Linux (WSL), but even if you create such a branch, it will not work from native Windows:

warning: ignoring broken ref refs/heads/con