Linear Algebra

Vector Sum

ndarray-badge

The ndarray crate supports a number of ways to create arrays -- this recipe focuses on creating ndarray::Arrays from std::Vec via from_vec. Adding two arrays together is no different than adding two numbers together. Using the & operand on the arrays within an arithmetic operation prevents the operation from consuming the arrays. Without &, the arrays are consumed.

In the first example, arrays a and b are moved in the let-statement z = a + b. In the second example, the arrays c and d are not moved and instead, a new array is created for w. Updating either of c or d after the vector sum has no effect the value of w. Additionally, while printing c works as expected, it would be an error to print b due to the move. See Binary Operators With Two Arrays for additional detail.

extern crate ndarray;
use ndarray::Array;

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

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

  let epsilon = 1e-8;
  for elem in z.iter() {
    let diff: f32 = *elem - 6.;
    assert!(diff.abs() < epsilon);
  }

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

  for elem in w.iter() {
    let diff: f32 = *elem - 6.;
    assert!(diff.abs() < epsilon);
  }

}

Vector Norm

ndarray-badge

This recipe demonstrates use of the Array1 type, ArrayView1 type, fold method, and dot method in computing the l1 and l2 norms of a given vector. The l2 norm calculation is the simpler of the two, as it is the square root of the dot product of a vector with itself, shown in the function l2_norm. The l1 norm, shown in the function l1_norm, is computed by a 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 ArrayView1 type. This recipe considers vector norms, so the norm functions only need to accept one dimensional views (hence 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). The most convenient 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 your public API, that may be a better choice for the benefit of your users, but for internal functions, the more concise ArrayView1<f64> may be preferable.

#[macro_use(array)]
extern crate ndarray;

use ndarray::{Array1, 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));
}

Adding matrices

ndarray-badge cat-science-badge

Creates two matrices with ndarray::arr2 and adds them together.

extern crate ndarray;

use ndarray::arr2;

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

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

    println!("Sum: {}", a + b);
}

Multiplying matrices

ndarray-badge cat-science-badge

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

extern crate ndarray;

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 with a matrix

ndarray-badge cat-science-badge

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. (dot performs matrix multiplication, 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 dot handles it as a column vector.

extern crate ndarray;

use ndarray::{arr1, arr2, 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);
}