Skip to content

feature: trust region support in DIOM #67

feature: trust region support in DIOM

feature: trust region support in DIOM #67

Workflow file for this run

name: Test LibKrylov interfaces
# Run on every push/PR that touches the C interface or this workflow.
on:
push:
paths:
- 'interfaces/**'
- 'src/**'
- 'Project.toml'
- '.github/workflows/test-libkrylov.yml'
pull_request:
paths:
- 'interfaces/**'
- 'src/**'
- 'Project.toml'
- '.github/workflows/test-libkrylov.yml'
jobs:
test:
name: ${{ matrix.label }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- label: linux-x86_64
runner: ubuntu-latest
juliac: juliac
lib: libkrylov.so
libdir: lib
- label: linux-aarch64
runner: ubuntu-24.04-arm
juliac: juliac
lib: libkrylov.so
libdir: lib
- label: macos-arm64
runner: macos-latest
juliac: juliac
lib: libkrylov.dylib
libdir: lib
- label: macos-x86_64
runner: macos-15-intel
juliac: juliac
lib: libkrylov.dylib
libdir: lib
- label: windows-x86_64
runner: windows-latest
juliac: juliac.bat
lib: libkrylov.dll
libdir: bin
steps:
- uses: actions/checkout@v4
# -----------------------------------------------------------------------
# The C (krylov.h) and Fortran (krylov.f90) headers each expose the
# Krylov.jl version as three integers (MAJOR/MINOR/PATCH). Verify the
# committed headers match Project.toml so a version bump can never ship with
# a stale header. Runs before any build step so it validates exactly what is
# committed.
# -----------------------------------------------------------------------
- name: Check header versions match Project.toml
shell: bash
run: |
ver=$(grep -E '^version\s*=' Project.toml | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
IFS=. read -r major minor patch <<< "$ver"
echo "Project.toml version: $ver (major=$major minor=$minor patch=$patch)"
fail=0
check() { # check <file> <line-must-contain>
grep -Fq "$2" "$1" || { echo "::error file=$1::expected '$2' (Project.toml is $ver)"; fail=1; }
}
check interfaces/include/krylov.h "#define KRYLOV_VERSION_MAJOR $major"
check interfaces/include/krylov.h "#define KRYLOV_VERSION_MINOR $minor"
check interfaces/include/krylov.h "#define KRYLOV_VERSION_PATCH $patch"
check interfaces/include/krylov.f90 "KRYLOV_VERSION_MAJOR = $major"
check interfaces/include/krylov.f90 "KRYLOV_VERSION_MINOR = $minor"
check interfaces/include/krylov.f90 "KRYLOV_VERSION_PATCH = $patch"
[ "$fail" -eq 0 ] && echo "Header versions match Project.toml ($ver)."
exit $fail
- uses: julia-actions/setup-julia@v2
with:
version: '1.12'
# -----------------------------------------------------------------------
# Install gfortran on macOS (not pre-installed on GitHub runners)
# -----------------------------------------------------------------------
- name: Install gfortran (macOS)
if: runner.os == 'macOS'
uses: fortran-lang/setup-fortran@main
with:
compiler: 'gcc'
version: '14'
# -----------------------------------------------------------------------
# Install JuliaC.jl (provides the juliac CLI)
# -----------------------------------------------------------------------
- name: Install JuliaC.jl
shell: bash
run: |
julia --startup-file=no -e "
import Pkg
Pkg.Registry.add(\"General\")
Pkg.Apps.add(url=\"https://github.com/JuliaLang/JuliaC.jl\", rev=\"v0.3.8\")
"
- name: Add juliac to PATH
shell: bash
run: |
JULIAC_BIN=$(julia --startup-file=no -e "print(joinpath(DEPOT_PATH[1], \"bin\"))")
echo "$JULIAC_BIN" >> "$GITHUB_PATH"
# -----------------------------------------------------------------------
# Trim SparseArrays from the build, exactly like the release workflow, so
# the library exercised by the tests matches the one we ship. The C
# interface only exposes solvers, never the Krylov processes, so this drops
# SparseArrays (and therefore SuiteSparse) as dead weight.
# -----------------------------------------------------------------------
- name: Strip SparseArrays from the build
shell: bash
run: |
julia --startup-file=no interfaces/scripts/trim_sparsearrays.jl
git diff --stat
# -----------------------------------------------------------------------
# Instantiate the Krylov.jl project
# -----------------------------------------------------------------------
- name: Instantiate Julia project
shell: bash
run: julia --startup-file=no --project=. -e "import Pkg; Pkg.instantiate()"
# -----------------------------------------------------------------------
# Compile libkrylov with --bundle so the library is fully self-contained
# (Julia runtime bundled alongside) and dlopen works without any system
# Julia in PATH on all platforms.
# -----------------------------------------------------------------------
- name: Build libkrylov
shell: bash
run: |
OUTLIB="interfaces/build/${{ matrix.libdir }}/${{ matrix.lib }}"
mkdir -p "$(dirname "$OUTLIB")"
${{ matrix.juliac }} \
--project . \
--compile-ccallable \
--trim=safe \
--bundle interfaces/build \
--output-lib "$OUTLIB" \
interfaces/src/LibKrylov.jl
# -----------------------------------------------------------------------
# Generate the C header
# -----------------------------------------------------------------------
- name: Generate krylov.h
shell: bash
run: julia --startup-file=no --project=. interfaces/scripts/generate_header.jl
# -----------------------------------------------------------------------
# Verify the bundle is self-contained.
# Linux uses a dynamic check: run an example with ONLY the bundle on the
# library path (no system Julia), so a missing dlopen'd dependency makes it
# fail. macOS/Windows can't isolate reliably via env vars (SIP strips
# DYLD_*, Windows always searches system dirs), so they use a STATIC check
# that inspects the shipped binaries directly — see the steps below.
# -----------------------------------------------------------------------
- name: Verify bundle is self-contained (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gcc -O2 -o interfaces/build/selfcheck interfaces/examples/C/basic_cg.c \
-I interfaces/include "$LIBPATH/${{ matrix.lib }}"
unset LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$LIBPATH:$LIBPATH/julia"
out="$(interfaces/build/selfcheck)"
echo "$out"
echo "$out" | grep -q "Solved: yes"
# macOS: every dependency (otool -L) of the library and of each bundled
# dylib must resolve to something the bundle actually ships or to a genuine
# system lib. A loader-relative or bare dependency (@rpath/@loader_path/
# @executable_path/<name>, incl. a dylib's own install id) is fine only if a
# dylib of that name is present in the bundle; a /usr/lib or /System path is
# a system lib; any *other* absolute path (Homebrew, the runner's Julia
# depot) is a hardcoded external dependency and a leak. We do not inspect
# rpaths: a stray rpath is harmless unless a dependency needs it, which the
# resolution check below already catches.
- name: Verify bundle is self-contained (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
set -uo pipefail
BUNDLE="interfaces/build"
find "$BUNDLE" -name '*.dylib' -exec basename {} \; | sort -u > bundled.txt
: > leaks.txt
{ find "$BUNDLE" -name '*.dylib'
echo "$BUNDLE/${{ matrix.libdir }}/${{ matrix.lib }}"; } | while IFS= read -r lib; do
[ -f "$lib" ] || continue
# Dependencies (skip line 1: the file header "path:").
otool -L "$lib" | tail -n +2 | awk '{print $1}' | while IFS= read -r dep; do
case "$dep" in
""|/usr/lib/*|/System/*) continue ;; # system / empty
/*) echo "LEAK abspath: $(basename "$lib") -> $dep" | tee -a leaks.txt; continue ;;
esac
# loader-relative or bare name: must be shipped in the bundle
grep -Fqx "$(basename "$dep")" bundled.txt \
|| echo "LEAK unbundled: $(basename "$lib") -> $dep" | tee -a leaks.txt
done
done
if [ -s leaks.txt ]; then
echo "::error::macOS bundle is NOT self-contained"; sort -u leaks.txt; exit 1
fi
echo "macOS bundle is self-contained"
# Windows: every import (objdump -p) of every bundled DLL must resolve to
# another bundled DLL, a Windows API set (api-ms-win-*/ext-ms-*), or a real
# system DLL in System32. Anything else would be picked up from the runner's
# Julia install and is a leaked external dependency.
- name: Verify bundle is self-contained (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
set -uo pipefail
BUNDLE="interfaces/build"
SYS="/c/Windows/System32"
: > leaks.txt
find "$BUNDLE" -iname '*.dll' -printf '%f\n' | tr '[:upper:]' '[:lower:]' | sort -u > bundled.txt
find "$BUNDLE" -iname '*.dll' | while IFS= read -r dll; do
objdump -p "$dll" 2>/dev/null | grep 'DLL Name:' | sed 's/.*DLL Name: *//' | tr -d '\r' | while IFS= read -r dep; do
depl="$(echo "$dep" | tr '[:upper:]' '[:lower:]')"
case "$depl" in
api-ms-win-*|ext-ms-*) continue ;;
esac
grep -qx "$depl" bundled.txt && continue
{ [ -f "$SYS/$dep" ] || [ -f "$SYS/$depl" ]; } && continue
echo "LEAK: $(basename "$dll") -> $dep" | tee -a leaks.txt
done
done
if [ -s leaks.txt ]; then
echo "::error::Windows bundle is NOT self-contained"; sort -u leaks.txt; exit 1
fi
echo "Windows bundle is self-contained"
# -----------------------------------------------------------------------
# Run the Julia test suite (all solvers × 4 precisions)
# Loads LibKrylov.jl as a plain Julia module — no dlopen of libkrylov.so.
# Loading a juliac-compiled lib from within Julia would trigger a second
# runtime via ijl_adopt_thread and crash; the C/Fortran tests cover the
# compiled library from native processes.
# -----------------------------------------------------------------------
- name: Run Julia tests
shell: bash
run: |
julia --startup-file=no --project=. interfaces/test/test_libkrylov.jl
# -----------------------------------------------------------------------
# Build and run the C smoke test (basic_cg example)
# -----------------------------------------------------------------------
- name: Build C example
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gcc -o interfaces/build/basic_cg interfaces/examples/C/basic_cg.c \
-I interfaces/include \
"$LIBPATH/${{ matrix.lib }}"
- name: Run C example
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
actual=$(interfaces/build/basic_cg)
echo "$actual"
echo "$actual" | grep -q "Solved: yes"
echo "$actual" | grep -q "x = \[ 1.00 1.00 1.00 1.00 1.00 \]"
# -----------------------------------------------------------------------
# Build and run the C block example (block_gmres)
# -----------------------------------------------------------------------
- name: Build C example (block)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gcc -o interfaces/build/block_gmres interfaces/examples/C/block_gmres.c \
-I interfaces/include \
"$LIBPATH/${{ matrix.lib }}" -lm
- name: Run C example (block)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
actual=$(interfaces/build/block_gmres)
echo "$actual"
echo "$actual" | grep -q "Block solved: yes"
# -----------------------------------------------------------------------
# Build and run the C preconditioning example (Jacobi-preconditioned CG)
# -----------------------------------------------------------------------
- name: Build C example (preconditioning)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gcc -o interfaces/build/preconditioning interfaces/examples/C/preconditioning.c \
-I interfaces/include \
"$LIBPATH/${{ matrix.lib }}"
- name: Run C example (preconditioning)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
actual=$(interfaces/build/preconditioning)
echo "$actual"
echo "$actual" | grep -q "Solved: yes"
echo "$actual" | grep -q "x = \[ 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 \]"
# -----------------------------------------------------------------------
# Build and run the C least-squares example (rectangular LSQR with adjoint)
# -----------------------------------------------------------------------
- name: Build C example (least-squares)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gcc -o interfaces/build/least_squares interfaces/examples/C/least_squares.c \
-I interfaces/include \
"$LIBPATH/${{ matrix.lib }}"
- name: Run C example (least-squares)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
actual=$(interfaces/build/least_squares)
echo "$actual"
echo "$actual" | grep -q "Solved: yes"
echo "$actual" | grep -q "x = \[ 1.00 2.00 3.00 \]"
# -----------------------------------------------------------------------
# Build and run the C full test (all solvers)
# -----------------------------------------------------------------------
- name: Build C test (all solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
# -lm is implicit on Windows (CRT), harmless flag on Linux/macOS
gcc -O2 -o interfaces/build/test_all_solvers_c \
interfaces/test/C/test_all_solvers.c \
-I interfaces/include \
"$LIBPATH/${{ matrix.lib }}" \
-lm
- name: Run C test (all solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
interfaces/build/test_all_solvers_c
# -----------------------------------------------------------------------
# Build and run the C behavioural test (options, preconditioner,
# warm start, error codes, reuse, Float32)
# -----------------------------------------------------------------------
- name: Build C test (API behaviour)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gcc -O2 -o interfaces/build/test_api_c \
interfaces/test/C/test_api.c \
-I interfaces/include \
"$LIBPATH/${{ matrix.lib }}" \
-lm
- name: Run C test (API behaviour)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
interfaces/build/test_api_c
# -----------------------------------------------------------------------
# Build and run the C block test (block_gmres / block_minres)
# -----------------------------------------------------------------------
- name: Build C test (block solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gcc -O2 -o interfaces/build/test_block_c \
interfaces/test/C/test_block.c \
-I interfaces/include \
"$LIBPATH/${{ matrix.lib }}" \
-lm
- name: Run C test (block solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
interfaces/build/test_block_c
# -----------------------------------------------------------------------
# Build and run the Fortran smoke test (basic_cg example)
# -----------------------------------------------------------------------
- name: Build Fortran example
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gfortran -o interfaces/build/basic_cg_fortran \
interfaces/examples/Fortran/basic_cg.f90 \
"$LIBPATH/${{ matrix.lib }}"
- name: Run Fortran example
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
actual=$(interfaces/build/basic_cg_fortran)
echo "$actual"
echo "$actual" | grep -q "Solved: T"
echo "$actual" | grep -q "1.00"
# -----------------------------------------------------------------------
# Build and run the Fortran block example (block_gmres)
# -----------------------------------------------------------------------
- name: Build Fortran example (block)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gfortran -o interfaces/build/block_gmres_fortran \
interfaces/examples/Fortran/block_gmres.f90 \
"$LIBPATH/${{ matrix.lib }}"
- name: Run Fortran example (block)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
actual=$(interfaces/build/block_gmres_fortran)
echo "$actual"
echo "$actual" | grep -q "Block solved: T"
# -----------------------------------------------------------------------
# Build and run the Fortran full test (all solvers)
# -----------------------------------------------------------------------
- name: Build Fortran test (all solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gfortran -O2 -o interfaces/build/test_all_solvers_fortran \
interfaces/test/Fortran/test_all_solvers.f90 \
"$LIBPATH/${{ matrix.lib }}"
- name: Run Fortran test (all solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
interfaces/build/test_all_solvers_fortran
# -----------------------------------------------------------------------
# Build and run the Fortran block test (block_gmres / block_minres)
# -----------------------------------------------------------------------
- name: Build Fortran test (block solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
gfortran -O2 -o interfaces/build/test_block_fortran \
interfaces/test/Fortran/test_block.f90 \
"$LIBPATH/${{ matrix.lib }}"
- name: Run Fortran test (block solvers)
shell: bash
run: |
LIBPATH="$(pwd)/interfaces/build/${{ matrix.libdir }}"
export PATH="$LIBPATH:$PATH"
export LD_LIBRARY_PATH="$LIBPATH:${LD_LIBRARY_PATH:-}"
export DYLD_LIBRARY_PATH="$LIBPATH:${DYLD_LIBRARY_PATH:-}"
interfaces/build/test_block_fortran