Linear Algebra
Vector Sum
The ndarray crate supports a number of ways to create arrays -- this recipe
focuses on creating ndarray::Array
s 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
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
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
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
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); }