Computational Science Cookbook II: C++ and you
[ code , comp-sci ]

Introduction

C++, along with vanilla C and Fortran, are the dominant languages of high-performance computing. Unlike C/Fortran though, it remains popular in the software engineering community, leading to many headaches when searching the internet for advice. A line should be drawn between the requirements of the tech industry and the requirements of science: many behaviours that would be unthinkable in production code are completely fine for scientific code that runs once.

The non-negotiable requirements are

  1. Write code that is verifiably correct.
  2. Write code that is fast, or at least not unduly inefficient.
  3. Finish the project in a reasonable amount of time.

The nice-to-haves are

  1. Make the code easy to read for another person.
  2. Make the code easy to use.
  3. Make the code easy to install.

Build systems

Just writing a shell script

A fair number of science codes are simple enough that a file called comile.sh, reading g++ -o best_science src1.cc src2.cc src3.cc -I/path/to/library is good enough. The price you pay here is that a)

GNU make

This is a tried-and-tested tool for building projects. Like much in the GNU ecosystem, it’s an… eccentric piece of software with a learning curve strongly resembling a cliff. I will not attempt to explain it, as there are already several attempts on the internet.

I will leave here my own makefile (partially copied from Atilla Szabó’s makefile, and partially plagiarised from stackoverflow).

# Created on 01/08/2018
# Copyright (C) 2018 Attila Szabó <as2372@cam.ac.uk>
# Modified 2022 Alaric Sanders <als217@cam.ac.uk>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the Creative Commons Attribution License (CC-BY),
# version 4.0 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Please find a copy of the Creative Commons CC-BY License on
# <https://creativecommons.org/licenses/by/4.0/>.


# ---------- Compiler and linker directives ----------
CXXFLAGS = -std=c++2a -pedantic -Wall
CXXOPTS = -O1 -g

src_path := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

# Output directory after linking
SOURCE_DIR = .
BIN_DIR = ../bin
BUILD_DIR = ../build

# ------ Establishing objects and sources ----

# Search subdirectories as well

STRUCT_DIR = struct
STRUCT_CC = $(wildcard $(SOURCE_DIR)/$(STRUCT_DIR)/*.cc)
STRUCT_O = $(patsubst $(SOURCE_DIR)/%.cc,$(BUILD_DIR)/%.o,$(STRUCT_CC))

# Change ~/include as necessary for your architecture
INC_PATH = -I/opt/homebrew/include -I$(STRUCT_DIR)

# ---------- Libraries needed for linking ----------

# Change or remove ~/lib as necessary for your architecture
LIB_PATH = -L /opt/homebrew/lib

LIBS = -lstdc++ -lm -lstdc++fs

# ---------- Compilation rules ----------

# Nontrivial header dependences
DEPENDS := $(patsubst %.cc,%.d,$(SOURCES))

# Generic .cc -> .o rule
$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.cc
	mkdir -p $(@D)
	$(CXX) $(CXXFLAGS) $(CXXOPTS) $(INC_PATH) -MMD -MP $< -c -o $@

# ---------- Linking rules ----------

# Generic .o -> exec rule: uses all prerequisites (meant to be .o files)
%: $(BUILD_DIR)/%.o $(COMMON_O) $(MANAGER_O) $(STRUCT_O)
	$(CXX) $(CXXFLAGS) $(INC_PATH) $(LDFLAGS) -MMD $^ \
	$(LIB_PATH) $(LIBS) \
	-o $(BIN_DIR)/$@

# Don't allow direct compilation 
%: %.cc
# Don't allow removal of object files in chained implicit linking
.SECONDARY:

# Cleanup:
.PHONY: clean
clean:
	-rm -r $(BUILD_DIR)/**

This file expects your directory structure to look something like

myProject/src + Makefile
              + source1.cc
			  + source2.cc
			  + struct ----- + struct1.hh
							 + struct1.cc
			                 + struct2.hh
			                 + struct2.cc

Note that this makefile traverses all levels of subfolders, which in principle could lead to some nasty behaviour if you have done some funny business with symlinks.

cmake, meson

are probably not worth your time to learn unless you plan to become a software engineer / distribute your code widely.

Documentation

By default, the C/C++ extension to VS code recognises “javadoc style” function annotations:

/**
* @brief Runs a full Monte Carlo sweep
* 
* Performs 1 round of gauge shuffling, then n Ising MC steps
* 
* @param T Tmeperature
* @param sweep Number of Ising MC steps
* @param method_Sz Bit flag for Ising flip strategy. may be
*                  - MC_NONE for no flips
*                  - MC_SMALL for Metropolis by plaquette
*                  - MC_FREEZE to flip only if energy is strictly smaller
* @param method_angle Bit flag for angular relaxation strategy.
*                  - MC_NONE for no relaxation
*                  - MC_LARGE for von Mises sampled gradient descent
*                  - MC_SMALL for uniform MC gradient descent
*                  - MC_FREEZE for MC at zero temperature
* @param sampling  Bit flag for order to do MC steps in
*                  - MC_SEQ to go through all sites in order
*                  - MC_RANDOM to visit random sites
*/
void MC(double T, unsigned sweep, unsigned method_Sz = MC_SMALL,
            unsigned method_angle = MC_LARGE, unsigned sampling = MC_SEQ);

In an IDE, this gives you hover-tips:

tooltips

Further, the tool Doxygen can read these annotations and produce a searchable HTML webpage documenting your code. In practice, this does not tend to be the most readable thing on the planet, but it’s better than no documentation.