Sling Academy
Home/Rust/Creating a Rust Library for Basic Linear Algebra Routines

Creating a Rust Library for Basic Linear Algebra Routines

Last updated: January 03, 2025

Creating a Rust library for basic linear algebra routines is a wonderful project for anyone looking to deepen their understanding of both Rust and the essential mathematical calculations at the heart of machine learning and computational physics. In this article, I'll walk you through how to create a simple Rust library to perform some basic linear algebra operations such as vector and matrix addition, subtraction, and multiplication.

Getting Started

Before we start with coding, make sure you have Rust installed on your machine. You can download it from the official Rust website. Once you have Rust installed, you can set up a new library by running:

cargo new lin_alg --lib

This command creates a new Rust library called lin_alg. The --lib flag is essential as it indicates that we are building a library rather than an application.

Defining a Vector Structure

To perform vector operations, we first need to define a Vector struct. Add the following code to src/lib.rs:

pub struct Vector {
    pub data: Vec,
}

impl Vector {
    pub fn new(data: Vec) -> Self {
        Vector { data }
    }
}

This struct holds a Vec of f64 numbers, which represent the elements of the vector. We also provide a constructor method new to create vectors easily.

Implementing Vector Addition

Now let's implement vector addition. We'll define a method add which combines two vectors:

impl Vector {
    pub fn add(&self, other: &Vector) -> Result<Vector, &str> {
        if self.data.len() != other.data.len() {
            return Err("Vectors must be of the same length");
        }

        let result_data: Vec<f64> = self.data
            .iter()
            .zip(&other.data)
            .map(|(a, b)| a + b)
            .collect();

        Ok(Vector::new(result_data))
    }
}

This method checks if the vectors are of the same length, and if they are, performs element-wise addition, returning a new Vector.

Matrix Operations

Let's dive deeper with matrices and create a Matrix struct useful for subsequent operations:

pub struct Matrix {
    pub data: Vec<Vec<f64>>,
}

impl Matrix {
    pub fn new(data: Vec<Vec<f64>>) -> Self {
        Matrix { data }
    }
}

Matrix Multiplication

Matrix multiplication is slightly more complex than vector addition. Here is how we can implement it:

impl Matrix {
    pub fn multiply(&self, other: &Matrix) -> Result<Matrix, &str> {
        let m = self.data.len();
        let n = self.data[0].len();
        let p = other.data[0].len();

        if n != other.data.len() {
            return Err("Inner matrix dimensions must agree");
        }

        let mut result: Vec<Vec<f64>> = vec![vec![0.0; p]; m];
        for i in 0..m {
            for j in 0..p {
                for k in 0..n {
                    result[i][j] += self.data[i][k] * other.data[k][j];
                }
            }
        }

        Ok(Matrix::new(result))
    }
}

This method first checks if the matrix dimensions agree for multiplication. If so, it computes the resulting matrix using the standard triple loop matrix multiplication algorithm.

Testing the Library

Finally, ensure that your implementation works correctly. You can add some tests in src/lib.rs as follows:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_vector_addition() {
        let v1 = Vector::new(vec![1.0, 2.0, 3.0]);
        let v2 = Vector::new(vec![4.0, 5.0, 6.0]);
        let expected = Vector::new(vec![5.0, 7.0, 9.0]);
        assert_eq!(v1.add(&v2).unwrap().data, expected.data);
    }

    #[test]
    fn test_matrix_multiplication() {
        let m1 = Matrix::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);
        let m2 = Matrix::new(vec![vec![5.0, 6.0], vec![7.0, 8.0]]);
        let expected = Matrix::new(vec![vec![19.0, 22.0], vec![43.0, 50.0]]);
        assert_eq!(m1.multiply(&m2).unwrap().data, expected.data);
    }
}

Run these tests using cargo test to verify the correctness of your library. Ensuring comprehensive tests will help maintain the accuracy and reliability of your library as you expand it with more features.

This simple library provides a foundation for understanding linear algebra operations in Rust, preparing you to tackle more advanced computational projects.

Next Article: Utilizing Approximate Equality with the `approx` Crate in Rust

Previous Article: Handling Underflow and Denormalized Floats in Rust

Series: Math and Numbers in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior