Linear Algebra

Adding matrices

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

Creates two 2-D matrices with ndarray::arr2⮳ and sums them element-wise.

Note the sum is computed as let sum = &a + &b. The & operator is used to avoid consuming a and b, making them available later for display. A new array is created containing their sum.

use ndarray::arr2;

fn main() {
    let a = arr2(&[[1, 2, 3], [4, 5, 6]]);

    let b = arr2(&[[6, 5, 4], [3, 2, 1]]);

    let sum = &a + &b;

    println!("{}", a);
    println!("+");
    println!("{}", b);
    println!("=");
    println!("{}", sum);
}

Multiplying matrices

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

Creates two matrices with ndarray::arr2⮳ and performs matrix multiplication on them with ndarray::ArrayBase::dot⮳.

use ndarray::arr2;

fn main() {
    let a = arr2(&[[1, 2, 3], [4, 5, 6]]);

    let b = arr2(&[[6, 3], [5, 2], [4, 1]]);

    println!("{}", a.dot(&b));
}

Multiply a scalar with a vector and a matrix

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

Creates a 1-D array (vector) with ndarray::arr1⮳ and a 2-D array (matrix) with ndarray::arr2

First, a scalar is multiplied by the vector to get another vector. Then, the matrix is multiplied by the new vector with ndarray::Array2::dot⮳ (Matrix multiplication is performed using ndarray::Array2::dot⮳, while the * operator performs element-wise multiplication.)

In ndarray⮳, 1-D arrays can be interpreted as either row or column vectors depending on context. If representing the orientation of a vector is important, a 2-D array with one row or one column must be used instead. In this example, the vector is a 1-D array on the right-hand side, so ndarray::Array2::dot⮳ handles it as a column vector.

use ndarray::arr1;
use ndarray::arr2;
use ndarray::Array1;

fn main() {
    let scalar = 4;

    let vector = arr1(&[1, 2, 3]);

    let matrix = arr2(&[[4, 5, 6], [7, 8, 9]]);

    let new_vector: Array1<_> = scalar * vector;
    println!("{}", new_vector);

    let new_matrix = matrix.dot(&new_vector);
    println!("{}", new_matrix);
}

Vector comparison

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

The ndarray⮳ crate supports a number of ways to create arrays -- this recipe creates ndarray::Array⮳ from std::Vec using std::convert::From⮳. Then, it sums the arrays element-wise.

This recipe contains an example of comparing two floating-point vectors element-wise. Floating-point numbers are often stored inexactly, making exact comparisons difficult. However, the approx::assert_abs_diff_eq⮳ macro from the approx⮳ crate allows for convenient element-wise comparisons. To use the approx⮳ crate with ndarray⮳, the approx⮳ feature must be added to the ndarray⮳ dependency in Cargo.toml. For example, ndarray = { version = "0.13", features = [ "approx" ] }.

This recipe also contains additional ownership examples. Here, let z = a + b consumes a and b, updates a with the result, then moves ownership to z. Alternatively, let w = &c + &d creates a new vector without consuming c or d, allowing their modification later. See Binary Operators With Two Arrays⮳ for additional detail.

use approx::assert_abs_diff_eq;
use ndarray::Array;

fn main() {
    let a = Array::from(vec![1., 2., 3., 4., 5.]);
    let b = Array::from(vec![5., 4., 3., 2., 1.]);
    let mut c = Array::from(vec![1., 2., 3., 4., 5.]);
    let mut d = Array::from(vec![5., 4., 3., 2., 1.]);

    let z = a + b;
    let w = &c + &d;

    assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.]));

    println!("c = {}", c);
    c[0] = 10.;
    d[1] = 10.;

    assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.]));
}

Vector norm

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

This recipe demonstrates use of the ndarray::Array1⮳ type, ndarray::Array1⮳ type, ndarray::ArrayBase::fold method, and ndarray::ArrayBase::dot⮳ method in computing the l1⮳ and l2⮳ norms of a given vector.

  • The l2_norm⮳ function is the simpler of the two, as it computes the square root of the dot product of a vector with itself. + The l1_norm⮳ function is computed by a ndarray::ArrayBase::fold⮳ operation that sums the absolute values of the elements. (This could also be performed with x.mapv(f64::abs).scalar_sum(), but that would allocate a new array for the result of the mapv.)

Note that both l1_norm⮳ and l2_norm⮳ take the ndarray::ArrayView1⮳ type. This recipe considers vector norms, so the norm functions only need to accept one-dimensional views, hence ndarray::ArrayView1⮳. While the functions could take a parameter of type &Array1<f64> instead, that would require the caller to have a reference to an owned array, which is more restrictive than just having access to a view (since a view can be created from any array or view, not just an owned array).

ndarray::Array⮳ and ndarray::Array⮳ are both type aliases for ndarray::Array⮳. So, the most general argument type for the caller would be &ArrayBase<S, Ix1> where S: Data, because then the caller could use &array or &view instead of x.view(). If the function is part of a public API, that may be a better choice for the benefit of users. For internal functions, the more concise ArrayView1<f64> may be preferable.

use ndarray::array;
use ndarray::Array1;
use ndarray::ArrayView1;

fn l1_norm(x: ArrayView1<f64>) -> f64 {
    x.fold(0., |acc, elem| acc + elem.abs())
}

fn l2_norm(x: ArrayView1<f64>) -> f64 {
    x.dot(&x).sqrt()
}

fn normalize(mut x: Array1<f64>) -> Array1<f64> {
    let norm = l2_norm(x.view());
    x.mapv_inplace(|e| e / norm);
    x
}

fn main() {
    let x = array![1., 2., 3., 4., 5.];
    println!("||x||_2 = {}", l2_norm(x.view()));
    println!("||x||_1 = {}", l1_norm(x.view()));
    println!("Normalizing x yields {:?}", normalize(x));
}

Invert matrix

nalgebra nalgebra-crates.io nalgebra-github nalgebra-lib.rs cat-mathematics cat-no-std cat-science cat-wasm

Creates a 3x3 matrix with nalgebra::Matrix3⮳ and inverts it, if possible.

use nalgebra::Matrix3;

fn main() {
    let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
    println!("m1 = {}", m1);
    match m1.try_inverse() {
        Some(inv) => {
            println!("The inverse of m1 is: {}", inv);
        }
        None => {
            println!("m1 is not invertible!");
        }
    }
}

(De)-Serialize a Matrix

ndarray ndarray-crates.io ndarray-github ndarray-lib.rs cat-data-structures cat-science

Serialize and deserialize a matrix to and from JSON. Serialization is taken care of by serde_json::to_string⮳ and serde_json::to_string⮳ performs deserialization.

Note that serialization followed by deserialization gives back the original matrix.

use nalgebra::DMatrix;

fn main() -> Result<(), std::io::Error> {
    let row_slice: Vec<i32> = (1..5001).collect();
    let matrix = DMatrix::from_row_slice(50, 100, &row_slice);

    // serialize matrix
    let serialized_matrix = serde_json::to_string(&matrix)?;

    // deserialize matrix
    let deserialized_matrix: DMatrix<i32> =
        serde_json::from_str(&serialized_matrix)?;

    // verify that `deserialized_matrix` is equal to `matrix`
    assert!(deserialized_matrix == matrix);

    Ok(())
}