Expand description
Russell - Rust Scientific Library
russell_lab
: Scientific laboratory for linear algebra and numerical mathematics
Important: This crate depends on external libraries (non-Rust). Thus, please check the Installation Instructions on the GitHub Repository.
§Introduction
This crate extends Rust with scientific computing capabilities, such as special mathematical functions (e.g., Bessel, beta, elliptic integral, erf, erfc, gamma), auxiliary mathematical functions (e.g., ramp, Heaviside, boxcar, logistic), numeric constants (e.g., SQRT_PI, EULER, NAPIER), numerical differentiation (e.g., to check analytical derivatives), functions to validate calculations, code performance measurement, least-square fitting, reading/writing table-formatted data, generation of grid coordinates for plotting, interpolation, and others. This crate also implements several functions to perform linear algebra computations (e.g., Matrix, Vector, Matrix-Vector, Eigen-decomposition, SVD, Inverse, and more).
The code shall be implemented in native Rust code as much as possible. However, thin interfaces (“wrappers”) are implemented for some of the best tools available in numerical mathematics, including OpenBLAS and Intel MKL.
The code is organized in modules:
- algo – Structs and algorithms that roughly depend on the other modules
- base – “Base” functionality to help other modules
- check – Functions to assist in unit and integration testing
- math (not re-exported) – Mathematical “special” functions and constants
- matrix – Matrix struct and associated functions
- matvec – Functions operating on matrices and vectors
- vector – Vector struct and associated functions
Other functionality may be found in external crates, for instance:
- lambert_w – Computes the Lambert W function
§Linear algebra
For linear algebra, the main structures are NumVector and NumMatrix, that are generic Vector and Matrix structures. The Matrix data is stored as column-major. The Vector and Matrix are f64
and Complex64
aliases of NumVector
and NumMatrix
, respectively.
The figure below illustrates the base structures (struct) for linear algebra computations. The vector and matrix structures are generic but require the type parameter to implement a set of traits. The essential trait is the Num
trait provided by the num-traits
crate. This trait allows vectors and matrices in Russell to hold any numerical type. The Copy
trait enables the possibility of cloning vectors and matrices directly; however, for efficiency, the BLAS functions can be called instead. DeserializedOwned
and Serialize
allow vectors and matrices to be serialized/deserialized, a feature that propagates for all other structures depending on NumVector
and NumMatrix
. This feature allows, for instance, writing and reading JSON files with simulation results. NumCast
is required by NumVector
because the linspace
member function needs to cast the number of grid points to the final type (e.g., f64
). NumMatrix
requires the AddAssign
and MulAssign
traits to implement the convenience member functions named add
and mul
.
NumVector
has a single data member named data
(a Vec<T>
) holding all elements. Thus, NumVector
wraps the native Rust vector struct. NumVector
implements various methods, including functions for constructing and transforming vectors. One helpful method is linspace
to generate a sequence of equally spaced numbers. NumVector
implements the Index
trait, allowing the access of elements using the [i]
notation where i
is the index.
NumMatrix
stores all entries in the column-major representation. The main reason for using the column-major representation is to make the code work directly with Fortran routines, such as the BLAS/LAPACK functions. It is worth noting that BLAS/LAPACK have functions that accept row-major matrices. Nonetheless, these functions add an overhead due to temporary memory allocation and copies, including transposing matrices.
Thus, column-major data storage is essential. However, this requirement yields an undesirable side effect. Unfortunately, it is no longer possible to use the Index trait to access matrix elements using a notation such as a[i][j]
as in other languages. Alternatively, russell_lab
implements member functions get(i, j)
and set(i, j, value)
to access and update matrix elements.
NumMatrix
also implements the AsArray2D
trait, allowing matrices to be constructed from fixed-size nested lists of elements (stack-allocated), growable nested arrays of type Vec<Vec<T>>
(heap-allocated) and slices that are views into an array. For example, matrices can be created as follows:
use russell_lab::{NumMatrix, StrError};
fn main() -> Result<(), StrError> {
let a = NumMatrix::<i32>::from(&[
[1, 2, 3], //
[4, 5, 6], //
[7, 8, 9], //
]);
println!("{}", a);
assert_eq!(
format!("{}", a),
"┌ ┐\n\
│ 1 2 3 │\n\
│ 4 5 6 │\n\
│ 7 8 9 │\n\
└ ┘"
);
assert_eq!(a.as_data(), &[1, 4, 7, 2, 5, 8, 3, 6, 9]);
Ok(())
}
Finally, NumVector
and NumMatrix
implement the Display
trait, allowing them to be pretty printed.
§Functionality
The linear algebra functions currently handle only (f64, i32)
pairs, i.e., accessing the (double, int)
C functions. We also consider (Complex64, i32)
pairs.
There are many functions for linear algebra, such as (for Real and Complex types):
- Vector addition (vec_add()), copy (vec_copy()), inner (vec_inner()) and outer (vec_outer()) products, norms (vec_norm()), and more
- Matrix addition (mat_add()), multiplication (mat_mat_mul(), mat_t_mat_mul()), copy (mat_copy()), singular-value decomposition (mat_svd()), eigenvalues (mat_eigen(), mat_eigen_sym()), pseudo-inverse (mat_pseudo_inverse()), inverse (mat_inverse()), norms (mat_norm()), and more
- Matrix-vector multiplication (mat_vec_mul())
- Solution of dense linear systems with symmetric (mat_cholesky()) or non-symmetric (solve_lin_sys()) coefficient matrices
The russell_lab
functions are higher-level than the BLAS/LAPACK counterparts, thus losing some of the generality of BLAS/LAPACK. Each BLAS/LAPACK function wrapped by russell_lab
is carefully documented and thoroughly tested.
russell_lab
implements functions organized in the vector
, matvec
, and matrix
directories. These directories correspond to the BLAS terminology as Level 1, Level 2, and Level 3.
All vector functions are prefixed with vec_
and complex_vec_
, whereas all matrix functions are prefixed with mat_
and complex_mat_
. The matvec
functions have varied names, albeit descriptive.
russell_lab
implements several other interfaces to BLAS/LAPACK. However, some functions are complemented with native Rust code to provide a higher-level interface or improve the functionality. For instance, after calling the DGEEV function, mat_eigen
post-processes the results via the dgeev_data
function to extract the results from LAPACK’s compact representation, generating a more convenient interface to the user.
An example of added functionality is the mat_pseudo_inverse
function, which computes the pseudo-inverse of a rectangular matrix using the singular-value decomposition. This function is based on the singular-value decomposition routine provided by LAPACK.
§Complex numbers
For convenience, this library re-exports:
num_complex::Complex64
– Needed for ComplexMatrix, ComplexVector and complex functionsnum_complex::ComplexFloat
– Needed for the intrinsic Complex64 operators such asabs()
an others
When using the crate::cpx! macro, the Complex64
type must be imported as well. For example:
use russell_lab::{cpx, Complex64};
println!("{}", cpx!(1.0, 2.0));
§Examples
§(matrix) Eigen-decomposition of a small matrix
use russell_lab::{mat_eigen, Matrix, Vector};
use russell_lab::StrError;
fn main() -> Result<(), StrError> {
let data = [
[2.0, 0.0, 0.0],
[0.0, 3.0, 4.0],
[0.0, 4.0, 9.0],
];
let mut a = Matrix::from(&data);
let m = a.nrow();
let mut l_real = Vector::new(m);
let mut l_imag = Vector::new(m);
let mut v_real = Matrix::new(m, m);
let mut v_imag = Matrix::new(m, m);
mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut a)?;
println!("eigenvalues =\n{}", l_real);
println!("eigenvectors =\n{}", v_real);
Ok(())
}
§(math) Bessel functions
use russell_lab::math;
use russell_lab::{Vector, StrError};
fn main() -> Result<(), StrError> {
let xx = Vector::linspace(0.0, 15.0, 101)?;
let j0 = xx.get_mapped(|x| math::bessel_j0(x));
let j1 = xx.get_mapped(|x| math::bessel_j1(x));
let j2 = xx.get_mapped(|x| math::bessel_jn(2, x));
Ok(())
}
Re-exports§
pub use algo::*;
pub use base::*;
pub use check::*;
pub use matrix::*;
pub use matvec::*;
pub use vector::*;
Modules§
- algo
- This module implements algorithms built from base, math, and vector-matrix routines
- base
- This module implements a “base” functionality to help other modules
- check
- This module contains functions to compare float numbers and arrays for unit testing
- math
- This module implements mathematical (specialized) functions and constants
- matrix
- This module contains functions for calculations with matrices
- matvec
- This module contains functions for calculations with matrices and vectors
- vector
- This module contains functions for calculations with vectors
Macros§
- cpx
- Allocates a new Complex64 number
Traits§
- Complex
Float - Generic trait for floating point complex numbers.