oscirs_linalg

crates.io

A linear algebra crate for Rust

Description

This crate allows for simple and easy linear algebra-related calculations and supports GPU parallelization through OpenCL 3.0.

This crate relies upon the opencl3 crate which provides a rust implementation of OpenCL 3.0 macros.

Use

oscirs_linalg primarily relies upon the Matrix struct. You can create one using a vector of 32-bit floats in addition to a number of rows and columns whose product adds up to the length of the vector. The constructor new_matrix checks to make sure this is the case before successfully returning a Matrix. Data vectors are row-major. Example code can be seen below.

```rust use oscirslinalg::matrix::{ newmatrix, Matrix };

let data: Vec = vec![2.0; 6]; let nrows: usize = 2; let ncols: usize = 3;

let mat: Matrix = newmatrix(data, nrows, n_cols) .expect("Failed to create mat"); ```

Matrices can be added together using typical arithmetic operators just like ints or floats. Matrices can also be negated or added/subtracted with floats. When an operation requires dimension checks, you should use the ? operator or expect() to handle the error.

```rust let data2: Vec = vec![3.0; 6]; let nrows2: usize = 2; let ncols_2: usize = 3;

let mat2: Matrix = newmatrix(data2, nrows2, ncols2) .expect("Failed to create mat2");

let result: Matrix = (mat + mat2) .expect("Failed to add mat and mat2");

asserteq!(result.getdata(), vec![5.0; 6]); ```

Matrices can be indexed using nested square brackets, and individual rows/cols can be indexed using the row() and col() methods respectively.

rust assert_eq!(result[[1, 1]], 5.0);

GPU Acceleration

While matrices can be multiplied through A * B syntax, this is a single-threaded CPU-based operation. For large matrices, this quickly becomes inefficient. oscirs_linalg supports GPU-based parallelized matrix multiplication through OpenCL.

To start with parallization, first initialize the Calculator struct through the init() function. Make sure to declare it as mutable, as it will be dynamically storing matrices internally.

```rust use oscirs_linalg::calculator::{ self, Calculator };

let mut calc: Calculator = calculator::init() .expect("Failed to initialize Calculator"); ```

Matrices must be stored in the calculator's memory before any operations can be performed. The store_matrix() method returns a memory index that will be used to reference that matrix for the multiplication operation.

```rust let avec: Vec = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let bvec: Vec = vec![2.0, 1.0, 2.0, 3.0, 2.0, 1.0];

let amat: Matrix = newmatrix(avec, 2, 3) .expect("Failed to create Matrix A"); let bmat: Matrix = newmatrix(bvec, 3, 2) .expect("Failed to create Matrix B");

let aidx: usize = calc.storematrix(amat) .expect("Failed to store Matrix A in calculator memory"); let bidx: usize = calc.storematrix(bmat) .expect("Failed to store Matrix B in calculator memory"); ```

Once both matrices are stored in the calculator's memory, you can call the mat_mul() method on Calculator to multiply the two matrices. The arguments to mat_mul() are the memory indices of the matrices to mulitply.

```rust let (cmat, _cidx) = calc.matmul(aidx, b_idx) .expect("Failed to mulitply Matrix A and Matrix B");

asserteq!(cmat.getdata(), vec![12.0, 10.0, 30.0, 25.0], "Matrix C data not as expected"); asserteq!(cmat.getrows(), 2, "Matrix C row dimension not as expected"); asserteq!(cmat.get_cols(), 2, "Matrix C col dimension not as expected"); ```

mat_mul() returns a tuple of the resultant matrix and its index in memory. The resultant matrix is always stored in the calculator's memory so that subsequent calculations can be performed faster and with less memory shuffling.

Custom OpenCL Kernels

oscirslinalg also supports using your own OpenCL kernels with the memory management tools provided by Calculator. This gets a bit complicated and involves some unsafe functions, but an example is given in the tests folder under linalgtests.rs. It requires the creation of a custom closure that calculates the output matrix dimensions and work sizes from the input matrices, but once you do that it is easy to execute your custom kernel as many times as you want.