Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c717c08
Update
mmelnich May 30, 2026
e91f253
Add fun_nystrom_pp MEX: float+double dispatch, SASO sketch, timing st…
mmelnich Jun 13, 2026
c73d709
fun_nystrom_pp MEX: bulk memcpy marshal instead of per-element copy
mmelnich Jun 15, 2026
2925287
fun_nystrom_pp: seed-driven sketch API (scalar k/s sampled in-MEX; ex…
mmelnich Jun 16, 2026
a8d1697
benchmark: FetchContent external baselines (Epperly MIT, Persson lice…
mmelnich Jun 16, 2026
5e324a9
CI: GitHub Actions (Linux+macOS MATLAB matrix, Python skeleton); Fetc…
mmelnich Jun 17, 2026
ddcc14b
fun_nystrom_pp: sync to kernel-internal SASO API (scalar k only, expl…
mmelnich Jul 9, 2026
40fdb31
fun_nystrom_pp MEX: raw-buffer marshal (no std::vector zero-init doub…
mmelnich Jul 9, 2026
e717725
fun_nystrom_pp MEX: block_qfa LFAType (Lanczos-QFA) + internal normal…
mmelnich Jul 9, 2026
6e4ab2e
fun_nystrom_pp: Adaptive/AdaptiveTol options for block_qfa (online ad…
mmelnich Jul 10, 2026
1abb1f9
fun_nystrom_pp MEX: expose oracle depth d_used in the times output st…
mmelnich Jul 10, 2026
909e33e
CI: fix macOS MEX build (guard GCC-only link flags) and Linux MATLAB …
mmelnich Jul 10, 2026
7168fba
CI: build blaspp as MKL ILP64 on Linux (match MATLAB's runtime BLAS) …
mmelnich Jul 10, 2026
b94252f
fun_nystrom_pp MEX: export lfa_us oracle timing for block/block_qfa (…
mmelnich Jul 10, 2026
c393bde
fun_nystrom_pp MEX: expose AdaptiveDelay/AdaptiveMin (inputs 15/16, 0…
mmelnich Jul 15, 2026
b113ccd
fun_nystrom_pp MEX: 'auto' LFAType (budget/auto_eps inputs 17-18, tim…
mmelnich Jul 16, 2026
64c50b6
fun_nystrom_pp MEX: scalar_qfa LFAType (certified plus fixed-depth); …
mmelnich Jul 24, 2026
70f89f8
fun_nystrom_pp MEX: assign the block-QFA adaptive window unconditiona…
mmelnich Jul 28, 2026
b04eb2c
MEX targets: Windows support (runtime-DLL staging + single OpenMP run…
mmelnich Aug 19, 2026
9fc4ed5
README: Windows build requires -G Ninja plus RANDLAPACK_RUNTIME_DLL_DIRS
mmelnich Aug 19, 2026
c128868
README: always pass CMAKE_BUILD_TYPE=Release for the MEX build
mmelnich Aug 19, 2026
8fe5cfd
funNystromPP MEX: block Gauss-Radau semantics, auto-tier exports, RAI…
mmelnich Aug 30, 2026
837034b
CMake: accept a project-root RandLAPACK_DIR and diagnose a wrong one
mmelnich Sep 1, 2026
ece1bc9
MEX: adaptive LFAType, symmetry validation, UsingDefaults knob warnings
mmelnich Sep 2, 2026
e519635
MEX vector-as-diagonal mode
mmelnich Sep 15, 2026
fa76336
MEX: entropy admitted to the certified tiers, scalar bracket exported…
mmelnich Sep 22, 2026
40fdb86
MEX: SpendCap (full-spend switch) for the self-tuning tiers
mmelnich Sep 23, 2026
2fa9639
fun_nystrom_pp: Sketch 'gaussian', sparse CSC input, threaded input c…
mmelnich Sep 26, 2026
bffc2f3
fun_nystrom_pp: read MATLAB's input in place (one copy of A); NUMA pl…
mmelnich Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions .github/actions/build-randlapack-stack/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: build-randlapack-stack
description: >
Build and install the RandLAPACK stack (BLAS++, Random123, LAPACK++, RandLAPACK)
as siblings of the checked-out bindings workspace, so the bindings can
find_package(RandLAPACK). Linux + macOS. Mirrors the dependency bring-up in
RandLAPACK's own core-linux.yaml / core-macos.yaml.

inputs:
randlapack-ref:
description: RandLAPACK git ref to build the bindings against.
# Bump to main once FunNystromPP lands there; for now the branch that has it.
default: funnystrompp-v2-baseline

runs:
using: composite
steps:
- name: base toolchain + BLAS
shell: bash
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
export DEBIAN_FRONTEND=noninteractive
# Intel MKL (ILP64) is the BLAS on Linux, NOT OpenBLAS: the bindings run
# inside MATLAB, whose runtime BLAS is MKL ILP64 (64-bit ints). blaspp must
# match that ABI or MATLAB's MKL services the MEX's BLAS calls with the wrong
# integer width (garbage leading dimension -> "Parameter N incorrect on entry
# to DGEMM" -> segfault). This mirrors the working local dev setup.
sudo apt-get update -qq
sudo apt-get install -qq -y git-core gcc g++ gfortran cmake m4 \
libgtest-dev wget gpg
wget -qO- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \
| sudo gpg --dearmor -o /usr/share/keyrings/oneapi-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \
| sudo tee /etc/apt/sources.list.d/oneAPI.list > /dev/null
sudo apt-get update -qq
sudo apt-get install -qq -y intel-oneapi-mkl-devel
# Source Intel's own MKL env script (robust across versions and the
# 'latest' symlink) so MKLROOT / LIBRARY_PATH / CPATH / LD_LIBRARY_PATH are
# set correctly, then hand them to later steps AND the calling job (build/
# run MEX) via $GITHUB_ENV. blaspp's link test needs the MKL libs on
# LIBRARY_PATH and its headers on CPATH; the MEX needs the ILP64 MKL on
# LD_LIBRARY_PATH at runtime. mkl vars.sh is MKL-only (no Intel iomp5): we
# use mkl_gnu_thread + system libgomp, so MATLAB's runtime stays unpolluted.
MKL_ENV="$(ls /opt/intel/oneapi/mkl/*/env/vars.sh | sort -V | tail -1)"
source "$MKL_ENV"
MKL_LIB="$(dirname "$(find "$MKLROOT" -name 'libmkl_core.so*' | head -1)")"
{
echo "MKLROOT=$MKLROOT"
echo "MKL_LIB=$MKL_LIB"
echo "CPATH=$CPATH"
echo "LIBRARY_PATH=$LIBRARY_PATH"
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"
} >> "$GITHUB_ENV"
elif [ "$RUNNER_OS" = "macOS" ]; then
# OpenBLAS, NOT Apple Accelerate: RandLAPACK refuses to run BQRRP when
# blaspp is linked against Accelerate (rl guard), and MKL has no Apple
# Silicon build. macOS two-level namespaces keep the MEX bound to its own
# OpenBLAS at runtime (no MATLAB-BLAS interposition), so LP64 is fine here.
brew install googletest openblas
OB="$(brew --prefix openblas)"
{
echo "OPENBLAS_LIB=$OB/lib"
echo "LIBRARY_PATH=$OB/lib"
echo "LD_LIBRARY_PATH=$OB/lib"
echo "DYLD_LIBRARY_PATH=$OB/lib"
echo "CPATH=$OB/include"
} >> "$GITHUB_ENV"
fi

- name: clone RandLAPACK (+ RandBLAS submodule)
shell: bash
run: |
cd "${{ github.workspace }}/.."
git clone https://github.com/BallisticLA/RandLAPACK.git
cd RandLAPACK
git checkout "${{ inputs.randlapack-ref }}"
git submodule update --init --recursive

- name: build + install BLAS++
shell: bash
run: |
cd "${{ github.workspace }}/.."
git clone https://github.com/icl-utk-edu/blaspp.git
# Linux: force ILP64 so blaspp calls MKL with 64-bit ints, matching MATLAB's
# runtime MKL (MKLROOT is set, so blaspp auto-detects MKL). macOS: force
# OpenBLAS (LP64) so blaspp does NOT pick Accelerate, which RandLAPACK's BQRRP
# guard rejects; two-level namespaces keep the MEX on its own OpenBLAS.
BLASPP_OPTS=""
if [ "$RUNNER_OS" = "Linux" ]; then
BLASPP_OPTS="-Dblas_int=int64"
elif [ "$RUNNER_OS" = "macOS" ]; then
BLASPP_OPTS="-Dblas=OpenBLAS"
fi
cmake -S blaspp -B blaspp-build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$PWD/blaspp-install" -Dbuild_tests=OFF $BLASPP_OPTS
cmake --build blaspp-build -j2 --target install

- name: install Random123
shell: bash
run: |
cd "${{ github.workspace }}/.."
git clone https://github.com/DEShawResearch/Random123.git
if [ "$RUNNER_OS" = "Linux" ]; then
make -C Random123 prefix="$PWD/Random123-install" install-include
else
mkdir -p Random123-install/include
cp -rp Random123/include/Random123 Random123-install/include/
fi

- name: build + install LAPACK++
shell: bash
run: |
cd "${{ github.workspace }}/.."
git clone https://github.com/icl-utk-edu/lapackpp.git
cmake -S lapackpp -B lapackpp-build -DCMAKE_BUILD_TYPE=Release \
-Dblaspp_DIR="$PWD/blaspp-install/lib/cmake/blaspp" \
-DCMAKE_INSTALL_PREFIX="$PWD/lapackpp-install" -Dbuild_tests=OFF
cmake --build lapackpp-build -j2 --target install

- name: build + install RandLAPACK (headers + cmake config; no tests built)
shell: bash
run: |
cd "${{ github.workspace }}/.."
cmake -S RandLAPACK -B RandLAPACK-build -DCMAKE_BUILD_TYPE=Release \
-Dblaspp_DIR="$PWD/blaspp-install/lib/cmake/blaspp" \
-Dlapackpp_DIR="$PWD/lapackpp-install/lib/cmake/lapackpp" \
-DRandom123_DIR="$PWD/Random123-install/include/" \
-DCMAKE_INSTALL_PREFIX="$PWD/RandLAPACK-install"
cmake --build RandLAPACK-build -j2 --target install
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: ci

on:
push:
pull_request:
workflow_dispatch:

# Tests the BINDING layer (the glue), not RandLAPACK's numerics — RandLAPACK has
# its own CI. We build the RandLAPACK stack (composite action, mirroring
# RandLAPACK's core-linux/core-macos), then build + exercise the bindings.
#
# MATLAB: matlab-actions/setup-matlab runs license-free on PUBLIC GitHub repos
# (this repo is public). Linux + macOS via the job matrix.

jobs:
matlab:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- name: build RandLAPACK stack
uses: ./.github/actions/build-randlapack-stack

- name: set up MATLAB
uses: matlab-actions/setup-matlab@v2

- name: build the MEX bindings
shell: bash
run: |
DEPS="$(cd "${{ github.workspace }}/.." && pwd)"
MATLAB_ROOT="$(dirname "$(dirname "$(command -v matlab)")")"
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DRandLAPACK_DIR="$DEPS/RandLAPACK-install/lib/cmake/RandLAPACK" \
-Dblaspp_DIR="$DEPS/blaspp-install/lib/cmake/blaspp" \
-Dlapackpp_DIR="$DEPS/lapackpp-install/lib/cmake/lapackpp" \
-DMatlab_ROOT_DIR="$MATLAB_ROOT"
cmake --build build -j2

- name: run MATLAB binding tests
uses: matlab-actions/run-command@v2
with:
command: "addpath('matlab'); addpath('matlab/tests'); test_bqrrp; test_fun_nystrom_pp; disp('matlab binding tests passed');"
env:
# the MEX dynamically links blaspp/lapackpp; set BOTH search-path vars
# (Linux uses LD_LIBRARY_PATH, macOS uses DYLD_LIBRARY_PATH). The MEX also
# needs its vendor BLAS: Linux DT_NEEDEDs the ILP64 MKL (MKL_LIB), macOS
# loads OpenBLAS (OPENBLAS_LIB); both are exported by the stack action and
# prepended. The unused one is empty, collapsing to a leading ':' the
# loader ignores.
LD_LIBRARY_PATH: "${{ env.MKL_LIB }}:${{ github.workspace }}/../blaspp-install/lib:${{ github.workspace }}/../lapackpp-install/lib"
DYLD_LIBRARY_PATH: "${{ env.OPENBLAS_LIB }}:${{ github.workspace }}/../blaspp-install/lib:${{ github.workspace }}/../lapackpp-install/lib"

python:
# Python bindings are a BQRRP-only skeleton for now; keep CI green while it
# matures (the job still reports build/test status).
continue-on-error: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: build RandLAPACK stack
uses: ./.github/actions/build-randlapack-stack

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: build + test the Python bindings
shell: bash
run: |
DEPS="$(cd "${{ github.workspace }}/.." && pwd)"
export RandLAPACK_DIR="$DEPS/RandLAPACK-install/lib/cmake/RandLAPACK"
export blaspp_DIR="$DEPS/blaspp-install/lib/cmake/blaspp"
export lapackpp_DIR="$DEPS/lapackpp-install/lib/cmake/lapackpp"
export LD_LIBRARY_PATH="$DEPS/blaspp-install/lib:$DEPS/lapackpp-install/lib:${LD_LIBRARY_PATH:-}"
python -m pip install --upgrade pip pytest numpy pybind11
python -m pip install ./python -v
pytest python/tests -q
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ __pycache__/
*.egg-info/
.pytest_cache/
*.so

# generated by CMake FetchContent (machine-specific paths)
matlab/benchmark/funnystrompp/external_paths.m
42 changes: 42 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,37 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Primary: find_package against an installed RandLAPACK. The user is expected
# to set -DRandLAPACK_DIR=/path/to/install/lib/cmake/RandLAPACK when invoking
# cmake. CPM fallback is a future addition.
#
# RandLAPACK_DIR must be the directory holding RandLAPACKConfig.cmake, but a
# common mistake is passing the RandNLA-project root or the install prefix
# instead. Accept those by searching the known install layouts beneath the
# given directory; anything else gets an actionable error instead of
# find_package's generic one.
# ---------------------------------------------------------------------------
if(DEFINED RandLAPACK_DIR AND NOT EXISTS "${RandLAPACK_DIR}/RandLAPACKConfig.cmake")
file(GLOB _rl_configs
"${RandLAPACK_DIR}/lib/cmake/RandLAPACK/RandLAPACKConfig.cmake"
"${RandLAPACK_DIR}/lib64/cmake/RandLAPACK/RandLAPACKConfig.cmake"
"${RandLAPACK_DIR}/RandLAPACK-install/lib/cmake/RandLAPACK/RandLAPACKConfig.cmake"
"${RandLAPACK_DIR}/RandLAPACK-install/lib64/cmake/RandLAPACK/RandLAPACKConfig.cmake"
"${RandLAPACK_DIR}/install/RandLAPACK-install/lib/cmake/RandLAPACK/RandLAPACKConfig.cmake"
"${RandLAPACK_DIR}/install/RandLAPACK-install/lib64/cmake/RandLAPACK/RandLAPACKConfig.cmake")
if(_rl_configs)
list(GET _rl_configs 0 _rl_config)
get_filename_component(_rl_dir "${_rl_config}" DIRECTORY)
message(STATUS "RandLAPACK_DIR does not itself contain RandLAPACKConfig.cmake; "
"using ${_rl_dir} found beneath it")
set(RandLAPACK_DIR "${_rl_dir}" CACHE PATH "Directory containing RandLAPACKConfig.cmake" FORCE)
else()
message(FATAL_ERROR
"RandLAPACK_DIR=${RandLAPACK_DIR} does not contain RandLAPACKConfig.cmake, "
"and none was found in the usual install layouts beneath it. "
"If RandLAPACK's install.sh has not completed successfully yet, run it first -- "
"it prints the exact -DRandLAPACK_DIR to use. Otherwise locate the file with\n"
" find <project-dir>/install -name RandLAPACKConfig.cmake\n"
"and pass its directory.")
endif()
endif()
find_package(RandLAPACK REQUIRED)
message(STATUS "RandLAPACK found at ${RandLAPACK_DIR}")

Expand All @@ -34,3 +64,15 @@ else()
"${CMAKE_CURRENT_SOURCE_DIR}/matlab to the MATLAB path, "
"but calls into bqrrp_mex will fail until the MEX is built.")
endif()

# ---------------------------------------------------------------------------
# Benchmark external baselines (off by default). When ON, FetchContent pulls
# the external reference implementations (Epperly, Persson) used by the
# funNystrom++ benchmark at configure time — NOT vendored in this repo.
# See matlab/benchmark/funnystrompp/CMakeLists.txt and THIRD_PARTY_LICENSES.md.
# ---------------------------------------------------------------------------
option(RANDLAPACK_BINDINGS_BENCHMARKS
"Fetch external benchmark baselines (Epperly, Persson) via FetchContent" OFF)
if(RANDLAPACK_BINDINGS_BENCHMARKS)
add_subdirectory(matlab/benchmark/funnystrompp)
endif()
52 changes: 43 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

MATLAB, and eventually Python, bindings for [RandLAPACK](https://github.com/BallisticLA/RandLAPACK).

## Scope (v0)
## Scope

Proof-of-concept binding for a single driver, `BQRRP` (randomized blocked QR with column pivoting), in single and double precision, real matrices, column-major. More drivers as their C++ APIs stabilize. Python next.
Bindings for selected RandLAPACK drivers, single and double precision, real matrices, column-major:

* **`BQRRP`** (randomized blocked QR with column pivoting) — MATLAB + Python.
* **`FunNystromPP`** (matrix-function trace estimation, `tr f(A)`) — MATLAB. The
Python binding is deferred.

More drivers as their C++ APIs stabilize.

## Requirements

Expand All @@ -16,12 +22,34 @@ Proof-of-concept binding for a single driver, `BQRRP` (randomized blocked QR wit

```sh
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DRandLAPACK_DIR=/path/to/RandLAPACK-install/lib/cmake/RandLAPACK \
-DMatlab_ROOT_DIR=/path/to/MATLAB
cmake --build build -j
```

If MATLAB is not found, MEX targets are skipped with a clear warning; the `.m` files still ship and can be used once the MEX is built. On success, `matlab/+randlapack/private/bqrrp_mex.<ext>` is in place.
Always pass `-DCMAKE_BUILD_TYPE=Release`: with no build type the MEX compiles
without optimization (and Ninja+MSVC even defaults to Debug, measured
2026-08-19), which is functionally correct but useless for timing.

On Windows, run from an x64 developer shell and pass two extra things:
`-G Ninja` and `-DRANDLAPACK_RUNTIME_DLL_DIRS=<BLAS backend bin dir>` (the
RandLAPACK installer prints that directory). `-G Ninja` is REQUIRED, not a
preference: without it CMake selects the multi-config Visual Studio generator,
which appends a `Debug\`/`Release\` subdirectory to the MEX output path, so the
MEX lands in `matlab/+randlapack/private/Debug/` where MATLAB's package loader
never looks (verified 2026-08-19; Ninja ships with the VS "C++ CMake tools"
component). The DLL-dirs flag stages the BLAS runtime next to the MEX; without
it MATLAB fails to load the MEX with "The specified module could not be found".

If MATLAB is not found, MEX targets are skipped with a clear warning; the `.m` files still ship and can be used once the MEX is built. On success, the per-driver MEX (`matlab/+randlapack/private/{bqrrp,fun_nystrom_pp}_mex.<ext>`) is in place.

### Prebuilt release (no compiler, no CMake)

To avoid building RandLAPACK and the MEX yourself, a release archive ships the
`.m` files plus a prebuilt `matlab/+randlapack/private/*.mexa64`. Unzip,
`addpath('.../matlab')`, and call the functions directly. Prebuilt binaries are
currently **Linux x86-64 only**; on macOS/Windows, build from source as above.

## Usage

Expand All @@ -42,15 +70,21 @@ Two output modes, chosen by a trailing string (qr-style):

Run `help randlapack.bqrrp` for the full signature, including the optional `b_sz`, `d_factor`, and RNG `state` arguments.

## Runtime notes

On **Linux MATLAB R2026a + Ubuntu 24.04** (and similar new-glibc hosts), MATLAB ships an older `libstdc++.so.6` than your compiler's. MEX files built against the system `libstdc++` fail at load with `version GLIBCXX_3.4.32 not found`. Workaround:
For matrix-function trace estimation:

```sh
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 matlab
```matlab
n = 1000;
G = randn(n, 200); A = G * G.' / 200; % symmetric PSD test matrix
est = randlapack.fun_nystrom_pp(A, 100, 50, 'Func', 'sqrt'); % k=100, s=50
```

Alias it in your shell profile to make it transparent. Not needed on macOS or older Ubuntu hosts.
Run `help randlapack.fun_nystrom_pp` for the full name-value signature (`Func`, `Q`, `LFAType`, `Depth`, `Sketch`, `Reorth`, …).

## Runtime notes

The MEX targets statically link `libstdc++`/`libgcc` (`-static-libstdc++ -static-libgcc`), so they load in any MATLAB regardless of the `GLIBCXX` version MATLAB ships — **no `LD_PRELOAD` is needed**. (Earlier versions required preloading the system `libstdc++.so.6`; that is no longer the case.)

The MEX does dynamically link BLAS/LAPACK (via blaspp/lapackpp). When running outside the build environment, point `LD_LIBRARY_PATH` at the blaspp/lapackpp install `lib`/`lib64` dirs, and set `MKL_INTERFACE_LAYER=ILP64`, `MKL_THREADING_LAYER=GNU` if using MKL.

## Conventions

Expand Down
Loading
Loading