Scientific Computing

Use libutil on macOS and Linux

Libutil gives abstractions for OS-specific TTY operations. When using these abstractions across macOS and Linux, use this preprocessing statement for the appropriate header:

#ifdef __APPLE__
#include <util.h>
#else
#include <pty.h>
#endif

If using CMake, ensure the library and header are found:

find_library(UTIL_LIBRARY NAMES util)

if(APPLE)
  find_path(UTIL_INCLUDE_DIR NAMES util.h)
elseif(UNIX)
  find_path(UTIL_INCLUDE_DIR NAMES pty.h)
endif()

Cross-compile for DOS from Windows or Linux

OpenWatcom is an open-source C/C++ compiler that can compile for a variety of systems, particularly legacy 16/32-bit Windows and DOS systems. This can be of interest for retro gamers and those using old devices that have DOS-based software, including industrial controllers and two-way radio programming.

CMake supports OpenWatcom, and is conveniently used with a toolchain file. GitHub Actions has easy scripting for OpenWatcom to test building of DOS programs from Linux.

The easiest way to show this is by example: see the Ascii Patrol game for how to build with OpenWatcom for DOS from Windows/Linux and GitHub Actions build CI.

To run the programs build for DOS, use DOSBox-X.

Force older language standard in CMake

CMake target_compile_features sets a transitive MINIMUM language standard necessary. If the compiler defaults to a newer language standard, target_compile_features() allows that default higher language standard.

target_compile_features(<target> PRIVATE cxx_std_98)

This can make issues for legacy code that requires an older language standard. For example, an old C++98 code may need to have the compiler in C++98 mode. This is accomplished with the target property CXX_STANDARD. Other languages may have a similar property e.g. C_STANDARD.

Example: if C++98 needed for old code:

set_property(TARGET old PROPERTY CXX_STANDARD 98)

CMake import interface link

CMake can add interface linking to imported libraries. For example, a imported library obtained by find_package() or otherwise. Normally, this would work like the following example to say link “stdc++fs” to example imported library “imported::lib” for GCC older than 9.1.

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.1.0)
  target_link_libraries(imported::lib INTERFACE stdc++fs)
endif()

Function target_link_libraries() should work, but does not always work for some projects with a configure time error. To workaround this issue if it arises, set target property INTERFACE_LINK_LIBRARIES directly like:

set_property(TARGET imported::lib PROPERTY INTERFACE_LINK_LIBRARIES stdc++fs)

CMake CTest single command build and test

Many CMake users have muscle memory for the three-stanza configure, build, test with CMake:

cmake -B build
# configure CMake and generate build files

cmake --build build
# compile and link binaries

ctest --test-dir build
# run program self-tests

This can be reduced to a single command for many programs:

ctest -S setup.cmake

where a single file setup.cmake is added to the project.

Command-line options like “-Dvar=yes” must be aggregated and passed along to ctest_configure(OPTIONS) in setup.cmake.

Get CPU count from Matlab

Capture the number of physical CPU cores available on a computer from Matlab:

function N = get_cpu_count()
%% get apparent number of physical CPU cores

N = maxNumCompThreads;
if N < 2  % happens on some HPC
  N = feature('NumCores');
end

end

Related: Python CPU count

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()

# ctest_start(), etc.