feature: trust region support in DIOM #67
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |