ndarray

repository·master·Indexed 26 days ago

https://github.com/rust-ndarray/ndarray

An n-dimensional container for general elements and numerics in Rust. It provides efficient array manipulation, slicing, and matrix operations, supporting 1D rows/columns, 2D matrices, and higher-dimensional arrays. Key features include the array! macro for creation, performant element-wise arithmetic, axis-specific reductions, and the .dot() method for matrix multiplication. The ecosystem includes ndarray-gen for array generation and ndarray-rand for constructing arrays from probability distributions.

Tokens
16.4K
Snippets
44
Records
92
Agent score
86%

What's inside ndarray

  1. Overview of ndarray n-dimensional containers

    master
    ndarray provides an n-dimensional container for general elements and numerics. It supports 1-dimensional rows/columns, 2-dimensional matrices, and higher-dimensional arrays. Elements are accessed using indices corresponding to the number of dimensions (axes).
  2. Key features of ndarray

    master

    The ndarray crate provides several core capabilities for multidimensional data manipulation:

    • Generic n-dimensional arrays: Support for any element type.
    • Slicing: Supports arbitrary step sizes and negative indices (to access elements from the end of an axis).
    • Views and Subviews: Ability to create views and subviews of arrays, including iterators that yield subviews.
    • Performant Arithmetic: High-performance higher-order operations and arithmetic.
    • Slice/Mutate Raw Data: Use ArrayView::from and ArrayViewMut::from to treat any [T] data as an array view.
    • Lock-step Operations: Use the Zip tool for applying functions across two or more arrays or other NdProducer trait implementations.
  3. Calculate matrix products with dot()

    master

    Use the .dot() method for matrix multiplication. Note that for matrix multiplication to be valid, the shapes must be compatible (e.g., [1, 4] dot [4, 1]). You can use .into_shape_with_order() to reshape arrays and .t() to transpose them.

    use ndarray::prelude::*;
    use ndarray::Array;
    
    fn main() {
        let a = array![[10.,20.,30., 40.,]];
        let b = Array::range(0., 4., 1.);
        
        let b = b.into_shape_with_order((4,1)).unwrap();
        
        println!("{}", a.dot(&b));            // [1, 4] x [4, 1] -> [1, 1] 
        println!("{}", a.t().dot(&b.t()));    // [4, 1] x [1, 4] -> [4, 4]
    }
  4. Perform deep copies of arrays

    master

    To create an independently owned copy of an array where changes to the original do not affect the copy, use the .clone() method.

    Note on Ownership:

    • Array::clone(): Performs a deep copy, duplicating both the array structure and the underlying elements.
    • ArrayView::clone(): Performs a shallow copy, only cloning the view reference (the pointer and metadata), not the underlying data.
    use ndarray::prelude::*;
    use ndarray::Array;
    
    fn main() {
        let mut a = Array::range(0., 4., 1.).into_shape_with_order([2 ,2]).unwrap();
        let b = a.clone();
        
        println!("a = \n{}", a);
        println!("b clone of a = \n{}", b);
        
        a.slice_mut(s![1, 1]).fill(1234.);
        
        println!("a updated...");
        println!("a = \n{}", a);
        println!("b clone of a = \n{}", b);
    }
  5. Manage `rand` and `rand_distr` dependencies in `ndarray-rand`

    master

    To ensure version compatibility and avoid trait implementation errors, use the re-exported sub-modules provided by ndarray-rand instead of importing rand or rand_distr directly from their own crates:

    • Use ndarray_rand::rand for random number generation.
    • Use ndarray_rand::rand_distr for probability distributions.

    If you use a third-party crate for distributions, ensure it uses the same version of rand as ndarray-rand to prevent type incompatibility errors.

  6. Enable BLAS integration for matrix multiplication

    master

    To use BLAS for improved floating-point matrix multiplication, you must enable the blas feature in ndarray and manually select a provider by depending on blas-src.

    Note: Only end-user projects (not libraries) should select a provider.

    Option 1: Using system OpenBLAS

    [dependencies]
    ndarray = { version = "0.x.y", features = ["blas"] }
    blas-src = { version = "0.10", features = ["openblas"] }
    openblas-src = { version = "0.10", features = ["cblas", "system"] }

    Option 2: Using compiled Netlib

    [dependencies]
    ndarray = { version = "0.x.y", features = ["blas"] }
    blas-src = { version = "0.10.0", default-features = false, features = ["netlib"] }

    Required Code Integration

    After configuring your dependencies, you must link to blas_src in your code:

    extern crate blas_src;
  7. Index, slice, and iterate over arrays

    master

    Arrays support indexing, slicing, and iteration similar to NumPy.

    • Indexing: Use a[[i]] for 1D or a[[i, j]] for 2D.
    • Slicing: Use the s![] macro. For example, a.slice(s![2..5]) selects elements from index 2 to 4. a.slice_mut(s![..6;2]) allows mutable slicing with steps.
    • Iteration:
      • .iter(): Iterates over every element in the array (flat).
      • .outer_iter(): Iterates over the first dimension (e.g., rows in a 2D array).
    • Mapping: Use .mapv(|x| ...) to apply a function to every element and return a new array.
    use ndarray::prelude::*;
    use ndarray::Array;
    
    fn main() {
        let a = Array::range(0., 10., 1.);
        let mut a = a.mapv(|a: f64| a.powi(3));
    
        println!("{}", a[[2]]);
        println!("{:?}", a.slice(s![2..5]));
    
        for i in a.iter() {
            print!("{}, ", i)
        }
    }
  8. Perform element-wise arithmetic operations

    master

    Basic arithmetic operations (+, -, *, /) are performed element-wise. To avoid consuming the arrays, use references (&) in the operation.

    Ownership Rules for Binary Operators (@):

    • &A @ &A: Produces a new Array (allocates).
    • B @ A: Consumes B, updates it with the result, and returns it.
    • B @ &A: Consumes B, updates it with the result, and returns it.
    • C @= &A: Performs an arithmetic operation in place.
    use ndarray::prelude::*;
    use ndarray::Array;
    use std::f64::INFINITY as inf;
    
    fn main() {
        let a = array![[10.,20.,30., 40.,]];
        let b = Array::range(0., 4., 1.);
    
        assert_eq!(&a + &b, array![[10., 21., 32., 43.,]]);
        assert_eq!(&a - &b, array![[10., 19., 28., 37.,]]);
        assert_eq!(&a * &b, array![[0., 20., 60., 120.,]]);
        assert_eq!(&a / &b, array![[inf, 20., 15., 13.333333333333334,]]);
    }
  9. Initialize arrays with zeros, ones, or specific elements

    master

    You can initialize arrays with specific values using several methods. Note that for methods like Array::zeros, you may need to use turbofish syntax (::<Type, _>) to help the compiler infer the element type if it cannot be determined from context.

    • Array::zeros(shape): Creates an array filled with zeros.
    • Array::from_elem(shape, value): Creates an array filled with a specific value.
    • Array::ones(shape): Creates an array filled with ones.
    • Array::eye(n): Creates an identity matrix.
    • Array::linspace(range, n): Creates a 1-D array with n elements spaced linearly over a range.
    • Array::range(start, end, step): Creates a 1-D array with a specific range and step.
    • Array::logspace(...): Creates an array with logarithmically spaced elements.
    use ndarray::prelude::*;
    use ndarray::Array;
    
    // Using zeros with turbofish to specify element type f64
    fn main() {
      let a = Array::<f64, _>::zeros((3, 2, 4).f());
      println!("{:?}", a);
    }
  10. Use broadcasting for arithmetic operations

    master

    Broadcasting allows arithmetic operations between arrays of different but compatible shapes. The smaller array's dimensions are conceptually repeated to match the larger array's shape.

    You can also explicitly broadcast an array to a new shape using the .broadcast() method.

    use ndarray::prelude::*;
    
    fn main() {
        // Implicit broadcasting in arithmetic
        let a = array![
            [1., 1.], 
            [1., 2.], 
            [0., 3.], 
            [0., 4.]];
        let b = array![[0., 1.]];
        let c = array![
            [1., 2.], 
            [1., 3.], 
            [0., 4.], 
            [0., 5.]];
        assert!(c == a + b);
    
        // Explicit broadcasting
        let a_base = array![
            [1., 2.],
            [3., 4.],
        ];
        let b_broad = a_base.broadcast((3, 2, 2)).unwrap();
        println!("shape of a is {:?}", a_base.shape());
        println!("a is broadcasted to 3x2x2 = \n{}", b_broad);
    }