Scientific Computing

Fortran include statement

The Fortran include statement inserts source code from the specified file into the Fortran source code file at the location of the include statement. The include file can contain any valid Fortran syntax, including procedures, modules, variable definitions, operations, etc. This concept is similar to the C/C++ #include preprocessor directive that can also be used for inlining code, but Fortran include does not require a preprocessor. include statements are frequently used to reuse code like defining constants or Fortran 77 common blocks. Generated code from build systems like CMake and Meson can be consumed with include statements. The file suffix “.inc” is often used, but is arbitrary.

One example of a Fortran-only project extensively using CMake-generated Fortran source with include is h5fortran to allow polymorphic (type and rank) HDF5 I/O in Fortran. The source code deduplication thus achieved is significant and the code is easier to maintain.

Build systems scan Fortran source files for dependencies to detect the include statements and track the included files. Makefiles with CMake uses the compiler itself or depend.make in each target build directory to track dependencies. Ninja (with CMake or other build system such as Meson) specifies include dependencies via depfiles per source file, which may be observed for debugging with option ninja -d keepdepfile

In the example below, the dependency of main.f90 on const.inc is tracked by:

  • Ninja: depfile “build/CMakeFiles/main.dir/main.f90-pp.f90.d”
  • Make: “build/CMakeFiles/main.dir/depend.make”

A minimal example of using the Fortran include statement is shown below.

main.f90:

program main

include 'const.inc'

print '(A, I0)', 'The value of my_const is: ', my_const

end program

const.inc:

integer, parameter :: my_const = 42

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)

project(include_example LANGUAGES Fortran)

add_executable(main main.f90)

Observe that CMake will rebuild main.f90 if const.inc changes.

cmake -Bbuild
cmake --build build

touch const.inc

cmake --build build

Python libstdc++ conflict LD_PRELOAD

If importing Python modules or trying to run an Anaconda Python program like Spyder gives CXXABI errors, it can be due to a conflict between the system libstdc++ and the Anaconda libstdc++. Assuming Anaconda / Miniconda Python on Linux, try specifying the libstdc++ library in the conda environment by LD_PRELOAD.

Find the system libstdc++:

find /usr -name libstdc++.so.6

Suppose “/usr/lib64/libstdc++.so.6”. Set LD_PRELOAD environment variable in the conda environment:

conda env config vars set LD_PRELOAD=/usr/lib64/libstdc++.so.6

conda activate

Then retry the command / program.

Overview of Free C++ and Fortran Compiler Families

These modern, currently-supported compiler families are free-to-use for C, C++ and Fortran.

Compiler C C++ Fortran
GNU gcc: C23 g++: C++26 gfortran: F2018
Intel oneAPI icx: C23 icpx: C++23 ifx: F2023
LLVM clang: C17 clang++: C++26 flang-new: F2018
AOCC clang: C17 clang++: C++17 flang: F2008
NVIDIA HPC SDK nvc: C11 nvc++: C++23 nvfortran: F2003
IBM OpenXL xlc: C17 xlc++: C++26 xlf: F2018

GCC

GCC has broad support of modern standards on a very wide range of computing platforms. GCC is competitive in build time and runtime with vendor-specialized compilers. These may offer vendor-specific capabilities not available in general compilers like GCC.

Intel

Intel oneAPI compilers are free to use for any user, supporting Linux and Windows computers with x86-64 CPU including from AMD, Intel, etc. Intel oneAPI components like MKL, IPP, and TBB are available at no cost. Intel MPI Library implements the MPICH specification for massively parallel computation across discrete computing nodes.

LLVM

LLVM Clang C and C++ compilers join with the Flang Fortran compiler using modern C++ internals and robust industry support. LLVM is known for performance, correctness, and prompt implementation of new language standards. LLVM has powerful associated tooling like clang-format, clang-tidy, and sanitizers. It is generally important to ensure that a project builds with both LLVM and GCC for better portability.

AMD

AMD AOCC LLVM compiler is tuned for AMD CPUs. AOCC works on non-AMD CPUs but is generally only useful for those with HPC/AI workloads on AMD CPUs, as it typically uses LLVM releases that are a few versions behind the latest.

AMD GPUs are the focus of the ROCm software stack, which includes the HIP C++ language and ROCm Fortran compiler. Currently the ROCm Fortran Next Gen compiler is in early development and is intended for advanced users who need to use AMD GPUs with Fortran or C/C++ code.

NVIDIA

NVIDIA HPC SDK is free to use and works on a variety of desktop CPUs including x86-64, OpenPOWER, and ARM. A key feature of the NVIDIA compilers is intrinsic support for CUDA Fortran, enabling offloading computationally intensive Fortran code to NVIDIA GPUs. NVIDIA HPC SDK includes specialized tools for NVIDIA Nsight profiling, debugging, and optimizing HPC applications on NVIDIA platforms.

IBM OpenXL

Unlike the other compilers mentioned above, IBM OpenXL LLVM-based compilers are specifically designed for POWER CPUs, such as ppc64le. Consequently, IBM OpenXL compilers do not work with typical x86-based computers.

The IBM OpenXL Fortran compiler features extensive optimization capabilities specifically for POWER CPU architecture. It supports OpenMP for parallel processing and can auto-vectorize code for POWER vector units (VSX, VMX). The compiler includes built-in support for IBM MASS (Mathematical Acceleration Subsystem) libraries and optimization reports to help developers tune code performance. OpenXL compilers include hardware-specific optimizations for POWER CPUs and support for IBM-specific operating systems like AIX.

Matlab dark mode

Matlab Online and locally-installed Matlab Desktop can use “dark mode” for the Matlab Desktop.

s = settings;

s.matlab.appearance.MATLABTheme.PersonalValue = "Dark";

Or from the GUI by searching for “Dark” in the Matlab settings.

Figures can be switched between light and dark mode with theme().

Related: Alternative Matlab code editors

References:

Matlab lint .m script recursive

Matlab codeIssues() command recursively lints Matlab .m code files. The output is a neat table.

The Matlab build system has a built-in CodeIssuesTask for use via buildtool to validate an entire Matlab project from a single command.

Used from CI, this is a quick first step check of a project to help ensure compatibility of code syntax across Matlab versions. Of course, the Matlab version checked is only the currently-running Matlab, so the CI system would need to fan out running across the desired Matlab versions.

Matlab Git operations

Matlab Git operations are a first-class part of the Matlab environment, without need for system() calls. The Matlab Desktop GUI or Matlab factory functions allow most common Git operations to be directly performed.

For example, Git clone is a plain Matlab function that can be called from the command line or script.

gitclone("https://github.invalid/username/repo.git")

The Matlab branching and merging GUI can be helpful for those not familiar with Git commands.

Some Git Matlab operations are object-oriented, for example Git pull

repo = gitrepo("https://github.invalid/username/repo.git");

pull(repo)

Create Docker image

For research reproducibility, a Docker image gives a known software state that should be usable for many years. It isn’t strictly necessary to make a custom image as described below, but it can be useful for a known software state.


Create a Docker image by using a Dockerfile. If desired to set environment variables, use ENV Dockerfile instruction.

FROM alpine:latest

# example for CMake project using Fortran with OpenMPI.
RUN apk add --no-cache ninja-build cmake gfortran openmpi-dev

ENV CMAKE_GENERATOR=Ninja

Create the file above named Dockerfile in the Git repo.

apk
Alpine package manager

Build the Docker image:

docker build -t openmpi-fortran .

from the same directory as Dockerfile

Check the image exists–it may be nearly 500 MB:

docker images

Before committing for upload, invoke the container.

docker run openmpi-fortran

This will almost immediately start and stop, as it didn’t have a command to run or persist.

Get the hexadecimal container ID by:

docker ps -a

The container will have an auto-assigned name. Note the hexadecimal “Container ID”.

upload Docker image

Once the configured Docker container is ready, share this container. This can be done with Docker Hub.

Once ready to upload the image, note the “container ID”, which is the hexadecimal number at the terminal prompt of the Docker container, under docker ps. The container hex ID must appear under docker ps, just being in docker images is not enough.

docker commit -m "Fortran Dockerfile setup" hex_id dockerhub_username/openmpi-fortran

The changes to the image the container made are gathered into a new image. It may take a minute or two for a large image. Ideally with a small image it will take only a couple seconds. The new image has not yet left the computer, it will show up under

docker images

Once uploaded, the Docker image is visible to the world by default.

Login to Docker Hub

docker login -u dockerhub_username

Push (upload) this image to Docker Hub. This image is world-visible by default!

docker push dockerhub_username/openmpi-fortran

If the image is very large > 1 GB, be aware it will take a while to upload, and for the host to download. This is a telltale that it’s important to keep Docker images small.

Docker Fortran image

Docker images are useful for reproducibility and ease of setup and for software binary distribution on platforms not natively available. For example, it may be desired to distribute a statically-linked binary that will run on any Linux system with compatible CPU architecture and kernel system calls.

To setup and maintain Docker images, it’s useful to have Docker Desktop available on the developer laptop to debug and test Dockerfiles. Read the install instructions particular to the laptop OS to understand OS-specific caveats and features. For example, on Linux laptops, to avoid the need for “sudo” in every command, follow the post-install.

Run a Docker container

Docker commands can be run from the system terminal if desired–all commands in this article assume this. Docker images by default will be downloaded to run locally.

Try the Hello World images, which should auto-download and print a welcome message

docker run hello-world

Search for a desired image using Docker Desktop or docker search. Consider the “Official” images first. Let’s use Alpine Linux.

docker search alpine

Get the desired image

docker pull alpine

Verify the images:

docker images

Start the Docker container:

docker run -it alpine
-it
interactive session

Verify the Alpine version running in the container. It will have a # superuser prompt.

cat /etc/os-release

Search for desired APK packages from within the container:

apk update
apk search gfortran

Verify the MUSL C library version like:

ldd

manage containers

These commands are issued NOT from a system Terminal, not the Docker container.

  • list images: docker images -a

  • list containers (running and stopped): docker ps -a

  • stop a Docker container: docker stop container_name

  • start a Docker container: docker start container_name

  • login to a running Docker container: docker exec -it container_name

  • get container environment variables: docker exec container_name env

  • cleanup unused containers docker system prune

Each docker exec command is a new shell instance. Changing directories in one docker exec has no effect on subsequent commands for example.

Docker on GitHub Actions

Docker images are useful for reproducibility and ease of setup and for software binary distribution on platforms not natively available on GitHub Actions runner images. While one can setup a custom Docker image, it’s often possible to simply use an existing official image from Docker Hub.

Example: Ubuntu 20.04

This example GitHub Actions workflow uses the Ubuntu 20.04 image to build a C++ binary with the GNU C++ compiler. For APT operations, the “-y” option is necessary. Environment variable DEBIAN_FRONTEND is set to “noninteractive” to avoid interactive prompts for certain operations despite “-y”. Don’t use “sudo” as the container user is root and the “sudo” package is not installed.

A special feature of this example is using Kitware’s CMake APT repo to install the latest version of CMake on an EOL Ubuntu distro.

name: ubuntu-20.04

on: [push]

# avoid wasted runs
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ubuntu-20.04:
    runs-on: ubuntu-latest
    container:
      image: ubuntu:20.04
      env:
        DEBIAN_FRONTEND: noninteractive

    strategy:
      fail-fast: false
      matrix:
        gcc-version: [7, 8]

    env:
      CC: gcc-${{ matrix.gcc-version }}
      CXX: g++-${{ matrix.gcc-version }}
      FC: gfortran-${{ matrix.gcc-version }}

    steps:

    - name: install compilers
      run: |
        apt update -y
        apt install -y --no-install-recommends ca-certificates gpg curl ninja-build ${{ env.CC }} ${{ env.CXX }} ${{ env.FC }}

    - name: install CMake
      run: |
        curl -s https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
        echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
        apt-get update
        test -f /usr/share/doc/kitware-archive-keyring/copyright || rm /usr/share/keyrings/kitware-archive-keyring.gpg
        apt-get install --no-install-recommends -y kitware-archive-keyring
        apt-get install --no-install-recommends -y cmake

    - uses: actions/checkout@v4

    - name: CMake configure
      run: cmake -B build

    - name: CMake build
      run: cmake --build build

    - name: CMake test
      run: ctest --test-dir build

Example: Alpine Linux

This example GitHub Actions workflow uses the Alpine Linux image with the MUSL C library to build a statically-linked binary.

name: alpine-musl

on: [push]

jobs:
  musl:
    runs-on: ubuntu-latest
    container:
      image: alpine

    steps:
    - uses: actions/checkout@v4

    - name: install build tools
      run: apk add --no-cache ninja-build cmake make gfortran openmpi-dev

    - name: print MUSL version
      continue-on-error: true
      run: ldd --version

    - name: CMake configure
      run: cmake -B build

    - name: CMake build
      run: cmake --build build

# Good idea to ensure self-tests pass before packaging
    - name: CMake test
      run: ctest --test-dir build

(Optional) If a CPack archive is desired add step:

    - name: CMake package
      if: success()
      run: cpack --config build/CPackConfig.cmake

The binary artifact or CPack archive can be uploaded by step upload-artifact:

    - name: .exe for release
      uses: actions/upload-artifact@v4
      if: success()
      with:
        name: my.exe
        path: build/my.exe