oxiblas-ndarray 0.2.3

ndarray integration for OxiBLAS
Documentation

oxiblas-ndarray

ndarray integration for OxiBLAS

Crates.io Documentation

Overview

oxiblas-ndarray provides integration between OxiBLAS and the ndarray crate (0.17), enabling you to use OxiBLAS BLAS/LAPACK operations on ndarray arrays. The API is a set of free functions, re-exported through oxiblas_ndarray::prelude, that take and return Array1 / Array2 values.

Features

  • Conversions between ndarray::Array2 and oxiblas_matrix::Mat: the owned conversions always copy, while the view conversions (ArrayView2 to MatRef) borrow the array's buffer without copying
  • BLAS operations on ndarray arrays: Level 1 (dot, nrm2, asum, axpy, scal), Level 2 (gemv), Level 3 (gemm), matrix norms and helpers, with Complex32 / Complex64 variants
  • LAPACK decompositions (LU, QR, SVD, Cholesky, symmetric, Hermitian and general eigenvalue decompositions, Schur), solvers, inverse, pseudo-inverse, determinant, rank, condition number and randomized SVD
  • Parallel GEMM with Rayon (parallel feature) and sparse CSR/CSC conversions, SpMV and a CG solver (sparse feature)
  • Type-safe API: result structs (LuResult, QrResult, SvdResult, ...) and a LapackError type for the LAPACK wrappers
  • Compatible with existing ndarray code

Installation

[dependencies]
oxiblas-ndarray = "0.2"

# With parallel GEMM (Rayon)
oxiblas-ndarray = { version = "0.2", features = ["parallel"] }

# With sparse (CSR/CSC) integration
oxiblas-ndarray = { version = "0.2", features = ["sparse"] }

Usage

BLAS Operations

use ndarray::{array, Array1, Array2};
use oxiblas_ndarray::prelude::*;

let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
let b = array![[7.0, 8.0], [9.0, 10.0], [11.0, 12.0]];

// Matrix multiplication using OxiBLAS
let c = matmul(&a, &b);
assert_eq!(c, array![[58.0, 64.0], [139.0, 154.0]]);

// C = alpha * A * B + beta * C
let mut c2 = Array2::<f64>::zeros((2, 2));
gemm_ndarray(1.0, &a, &b, 0.0, &mut c2);
assert_eq!(c2, c);

// Matrix-vector product and dot product
let x = array![1.0, 0.0, 1.0];
let y = matvec(&a, &x);                 // [4.0, 10.0]
let d = dot_ndarray(&x, &Array1::from_vec(vec![1.0, 2.0, 3.0]));  // 4.0

Decompositions

use ndarray::{array, Array1};
use oxiblas_ndarray::prelude::*;

fn main() -> LapackResult<()> {
    let a = array![
        [2.0, 1.0, 1.0],
        [4.0, 3.0, 3.0],
        [8.0, 7.0, 9.0],
    ];

    // LU decomposition (A = P * L * U)
    let lu = lu_ndarray(&a)?;
    let det = lu.det();                              // 4.0
    let b = Array1::from_vec(vec![4.0, 10.0, 24.0]);
    let x = lu.solve(&b);                            // solves A x = b: [1.0, 1.0, 1.0]

    // QR decomposition
    let qr = qr_ndarray(&a)?;
    let (q, r) = (&qr.q, &qr.r);

    // SVD (singular values in descending order)
    let svd = svd_ndarray(&a)?;
    let (u, s, vt) = (&svd.u, &svd.s, &svd.vt);

    // Cholesky (symmetric positive definite matrices)
    let spd = array![[4.0, 1.0], [1.0, 3.0]];
    let chol = cholesky_ndarray(&spd)?;
    let l = &chol.l;

    // Symmetric eigenvalue decomposition (eigenvalues in ascending order)
    let evd = eig_symmetric(&spd)?;
    let eigenvalues = &evd.eigenvalues;
    let eigenvectors = &evd.eigenvectors;

    Ok(())
}

Linear Solvers

use ndarray::array;
use oxiblas_ndarray::prelude::*;

fn main() -> LapackResult<()> {
    let a = array![[2.0, 1.0], [1.0, 3.0]];
    let b = array![5.0, 8.0];

    // Solve linear system
    let x = solve_ndarray(&a, &b)?;
    // x = [1.4, 2.2]

    // Multiple right-hand sides
    let b_multi = array![[5.0, 1.0], [8.0, 2.0]];
    let x_multi = solve_multiple_ndarray(&a, &b_multi)?;

    // Least squares, inverse, determinant, rank
    let x_ls = lstsq_ndarray(&a, &b)?;
    let a_inv = inv_ndarray(&a)?;
    let det = det_ndarray(&a)?;                      // 5.0
    let rank = rank_ndarray(&a)?;                    // 2

    Ok(())
}

Conversions

use ndarray::{array, Array2};
use oxiblas_ndarray::prelude::*;

// ndarray -> oxiblas (copies into a new Mat)
let nd = array![[1.0, 2.0], [3.0, 4.0]];
let ox = array2_to_mat(&nd);

// oxiblas -> ndarray (copies)
let back: Array2<f64> = mat_to_array2(&ox);
assert_eq!(back, nd);

// Zero-copy view of a column-major array (None for layouts that cannot be borrowed)
let col_major: Array2<f64> = zeros_f(3, 3);
let view = col_major.view();
let mat_ref = array_view_to_mat_ref(&view);
assert!(mat_ref.is_some());

// A row-major array is borrowed as a transposed MatRef; the flag says so
let row_major = Array2::<f64>::zeros((3, 3));
let view = row_major.view();
let (mat_ref, transposed) = array_view_to_mat_ref_or_transposed(&view).expect("contiguous");
assert!(transposed);

Parallel and Sparse

// Requires the `parallel` and `sparse` features
use ndarray::array;
use oxiblas_ndarray::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let a = array![[1.0, 2.0], [3.0, 4.0]];
    let b = array![[5.0, 6.0], [7.0, 8.0]];
    let c = matmul_par(&a, &b);                      // Rayon-parallel GEMM

    // Dense -> CSR (keeps every entry that is not exactly zero)
    let dense = array![[4.0, 1.0, 0.0], [1.0, 4.0, 1.0], [0.0, 1.0, 4.0]];
    let csr = array2_to_csr(&dense);
    assert_eq!(csr.nnz(), 7);

    // y = A * x
    let x = array![1.0, 2.0, 3.0];
    let y = spmv_ndarray(&csr, &x);                  // [6.0, 12.0, 14.0]

    // Conjugate gradient solve (A must be symmetric positive definite)
    let solution = sparse_solve_ndarray(&csr, &y)?;  // ~ [1.0, 2.0, 3.0]
    Ok(())
}

Performance

The BLAS and LAPACK wrappers delegate to OxiBLAS's optimized SIMD kernels:

  • GEMM: runs oxiblas-blas GEMM; see Performance in the root README for measured figures
  • Copying wrappers - The BLAS/LAPACK convenience functions copy their inputs into Mat; only the view conversions are zero-copy
  • Column-major - matmul and matmul_par return column-major arrays (matmul_c returns row-major); input layout is handled by the copy

API Reference

All of the items below are available through oxiblas_ndarray::prelude::* unless noted.

BLAS (blas)

Level Functions
Level 1 dot_ndarray, dot_view, nrm2_ndarray, asum_ndarray, axpy_ndarray, scal_ndarray; complex: dotc_c64_ndarray, dotu_c64_ndarray, nrm2_c64_ndarray, asum_c64_ndarray, axpy_c64_ndarray, scal_c64_ndarray and the _c32_ counterparts
Level 2 gemv_ndarray (takes a Transpose), matvec, matvec_t
Level 3 gemm_ndarray(alpha, a, b, beta, c), matmul, matmul_c, matmul_into; with parallel: gemm_par_ndarray, matmul_par
Norms frobenius_norm, norm_1, norm_inf, norm_max (the last three for f64), complex *_c32 / *_c64 versions
Helpers eye, eye_f, trace, transpose, conj_transpose_c32, conj_transpose_c64, eye_c32, eye_c64, trace_c32, trace_c64

LAPACK (lapack)

Area Functions and results
Decompositions lu_ndarray (LuResult { l, u, perm } with solve, det), qr_ndarray (QrResult { q, r }), svd_ndarray / svd_truncated (SvdResult { u, s, vt }), cholesky_ndarray (CholeskyResult { l } with solve, det), schur_ndarray (SchurResult)
Eigenvalues eig_symmetric / eigvals_symmetric (SymEvdResult { eigenvalues, eigenvectors }), eig_ndarray / eigvals_ndarray (general matrices, Eigenvalue values)
Randomized rsvd_ndarray, rsvd_power_ndarray (RandomizedSvdResult { u, s, v }), low_rank_approx_ndarray
Solvers solve_ndarray, solve_multiple_ndarray, lstsq_ndarray, tridiag_solve_ndarray, tridiag_solve_spd_ndarray, tridiag_solve_multiple_ndarray
Matrix functions inv_ndarray, pinv_ndarray, det_ndarray, cond_ndarray, rank_ndarray
Complex (lapack module only) svd_complex_ndarray, qr_complex_ndarray, cholesky_hermitian_ndarray, eig_hermitian_ndarray
Errors LapackError (Singular, NotPositiveDefinite, DimensionMismatch, NotConverged, Other) and LapackResult<T>

Conversions (conversions)

Kind Functions
Copying array2_to_mat, array2_into_mat, mat_to_array2, mat_to_array2_c, mat_ref_to_array2, and ArrayD versions (arrayd_to_mat, mat_to_arrayd, arrayd_to_array2, array2_to_arrayd, ...)
Zero-copy views array_view_to_mat_ref, array_view_to_mat_ref_or_transposed, array_view_mut_to_mat_mut, and ArrayViewD versions; they return None for layouts that cannot be borrowed (not contiguous along one axis, or a negative stride)
Layout helpers zeros_f, filled_f, is_column_major, is_row_major, to_column_major, array1_to_vec, slice_to_array1

Sparse (sparse, feature sparse)

array2_to_csr, array2_to_csc (and *_with_tolerance), csr_to_array2, csc_to_array2, spmv_ndarray, spmv_full_ndarray, sparse_solve_ndarray, sparse_solve_ndarray_with_options and SparseNdarrayError.

Feature Flags

Feature Description Default
parallel Rayon-parallel GEMM (gemm_par_ndarray, matmul_par) via oxiblas-blas/parallel and oxiblas-core/parallel
sparse CSR/CSC conversions, SpMV and a CG solver via oxiblas-sparse

The default features enable std support in oxiblas-core and oxiblas-matrix.

Benchmarks

cargo bench --package oxiblas-ndarray --bench ndarray_blas
cargo bench --package oxiblas-ndarray --bench faer_comparison

Comparison with ndarray-linalg

Feature oxiblas-ndarray ndarray-linalg
Pure Rust ✓
No C dependencies (library and its normal dependencies) ✓
BLAS/LAPACK ✓ ✓
OpenBLAS backend ✓
Intel MKL backend ✓
Custom backend ✓ (OxiBLAS)
Performance See Performance Depends on the backend
Cross-compilation Easy Difficult

Related Crates

License

Licensed under the Apache License, Version 2.0. See LICENSE for details.