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 --libThis 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.