The nalgebra library in Rust

Main features

Vectors

Vectors in nalgebra can be either statically or dynamically allocated. Vectors with static sizes are known at compile time and are stored on the stack, while dynamic vectors are allocated on the heap.

Example of a static vector:

use nalgebra::Vector3;

fn main() {
    let v = Vector3::new(1.0, 2.0, 3.0);
    println!("Vector: {:?}", v);
}

Example of a dynamic vector:

use nalgebra::DVector;

fn main() {
    let v = DVector::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
    println!("Dynamic Vector: {:?}", v);
}

Matrices

Matrices can also be static or dynamic. Static matrices have sizes known at compile time, while dynamic matrices have sizes that can change at runtime.

Example of a static matrix:

use nalgebra::Matrix3;

fn main() {
    let m = Matrix3::new(1.0, 2.0, 3.0,
                         4.0, 5.0, 6.0,
                         7.0, 8.0, 9.0);
    println!("Matrix:\n{}", m);
}

Example of a dynamic matrix:

use nalgebra::DMatrix;

fn main() {
    let m = DMatrix::from_row_slice(3, 3, &[
        1.0, 2.0, 3.0,
        4.0, 5.0, 6.0,
        7.0, 8.0, 9.0
    ]);
    println!("Dynamic Matrix:\n{}", m);
}

Operations on vectors and matrices include addition, multiplication, transpose, inversion, and other standard linear operations.

Points and transformations

Dots

Points in nalgebra represent coordinates in space. They are used to indicate the positions of objects, as opposed to vectors, which indicate directions and distances.

Example:

use nalgebra::{Point2, Point3};

fn main() {
    let p2 = Point2::new(1.0, 2.0);
    let p3 = Point3::new(1.0, 2.0, 3.0);
    
    println!("Point2: {:?}", p2);
    println!("Point3: {:?}", p3);
}

Transformations

Transformations include various operations such as translations, rotations And scaling.

Example:

use nalgebra::{Translation3, Rotation3, Isometry3, Vector3};

fn main() {
    let translation = Translation3::new(1.0, 2.0, 3.0);
    let rotation = Rotation3::from_axis_angle(&Vector3::z_axis(), 0.785398163); // 45 degrees in radians

    let isometry = Isometry3::new(translation.vector, rotation.scaled_axis());
    
    println!("Isometry:\n{}", isometry);
}

Specific data structures

Quaternions

Quaternions are used to represent and calculate 3D rotations. They are sometimes a must-have for animations and graphics applications due to their compactness and lack of effect “gimballock“.

Example:

use nalgebra::{Quaternion, UnitQuaternion};

fn main() {
    let axis = Vector3::y_axis();
    let angle = 1.57; // 90 degrees in radians

    let quaternion = UnitQuaternion::from_axis_angle(&axis, angle);
    
    println!("Quaternion: {:?}", quaternion);
}

Isometrics

Isometry combines translation and rotation. This structure is needed to represent the positions and orientations of objects in space.

Example:

use nalgebra::{Isometry3, Vector3};

fn main() {
    let translation = Vector3::new(1.0, 2.0, 3.0);
    let rotation = Vector3::new(0.0, 1.0, 0.0); // Rotate around Y axis

    let iso = Isometry3::new(translation, rotation);
    
    println!("Isometry:\n{}", iso);
}

Homogeneous matrices

Homogeneous matrices are used to represent common linear transformations: translations, rotations, scalings, and shears.

Example:

use nalgebra::Matrix4;

fn main() {
    let mut m = Matrix4::identity();
    m[(0, 3)] = 1.0; // Translation along X axis
    m[(1, 1)] = 0.5; // Scaling along Y axis
    
    println!("Homogeneous Matrix:\n{}", m);
}

Other features

Matrix factorizations

Matrix factorization is the process of factoring a matrix into a product of several matrices, which is very often used to solve systems of linear equations, calculate inverse matrices and determinants.

LU decomposition

LU decomposition splits a matrix into a lower triangular matrix L and the upper triangular matrix U.

Code example:

use nalgebra::DMatrix;

fn main() {
    let matrix = DMatrix::from_row_slice(3, 3, &[2.0, 1.0, 1.0,
                                                 4.0, -6.0, 0.0,
                                                 -2.0, 7.0, 2.0]);
    let lu = matrix.lu();
    let l = lu.l();
    let u = lu.u();
    println!("L:\n{}", l);
    println!("U:\n{}", u);
}

QR decomposition

QR factorization factorizes a matrix into an orthogonal matrix Q and the upper triangular matrix R.

Code example:

use nalgebra::DMatrix;

fn main() {
    let matrix = DMatrix::from_row_slice(3, 3, &[12.0, -51.0, 4.0,
                                                 6.0, 167.0, -68.0,
                                                -4.0, 24.0, -41.0]);
    let qr = matrix.qr();
    let q = qr.q();
    let r = qr.r();
    println!("Q:\n{}", q);
    println!("R:\n{}", r);
}

SVD decomposition

The SVD decomposition represents a matrix as a product of three matrices: U, Σ diagonal matrix with singular values ​​and V^*.

Example of decomposition:

use nalgebra::DMatrix;

fn main() {
    let matrix = DMatrix::from_row_slice(3, 2, &[1.0, 0.0,
                                                 0.0, 1.0,
                                                 1.0, 1.0]);
    let svd = matrix.svd(true, true);
    let u = svd.u.unwrap();
    let s = svd.singular_values;
    let v_t = svd.v_t.unwrap();
    println!("U:\n{}", u);
    println!("Σ:\n{}", s);
    println!("V^T:\n{}", v_t);
}

Special types of matrices

Sparse matrices

Sparse matrices contain mostly zero elements, which often saves memory. Nalgebra supports various sparse matrix formats, such as compressed sparse column and compressed sparse row.

Example of creating a sparse matrix:

use nalgebra_sparse::csr::CsrMatrix;

fn main() {
    let num_rows = 4;
    let num_cols = 4;
    let row_offsets = vec![0, 2, 4, 7, 8];
    let col_indices = vec![0, 1, 1, 2, 0, 2, 3, 3];
    let values = vec![10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0];
    let csr = CsrMatrix::try_from_csr_data(num_rows, num_cols, row_offsets, col_indices, values).unwrap();
    println!("CSR Matrix:\n{}", csr);
}

Orthographic and perspective projections

Orthographic and perspective projections are used in computer graphics to display three-dimensional scenes on a two-dimensional screen.

Example of orthographic projection:

use nalgebra::Orthographic3;

fn main() {
    let orthographic = Orthographic3::new(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    println!("Orthographic Projection:\n{}", orthographic.as_matrix());
}

Example of perspective projection:

use nalgebra::Perspective3;

fn main() {
    let perspective = Perspective3::new(16.0 / 9.0, 3.14 / 4.0, 0.1, 100.0);
    println!("Perspective Projection:\n{}", perspective.as_matrix());
}

GPU Computing and Embedded Systems

Nalgebra supports compilation to WebAssembly and various embedded systems.

Example compilation for WebAssembly:

# В Cargo.toml добавьте поддержку wasm
[dependencies]
nalgebra = "0.27"
wasm-bindgen = "0.2"

# В main.rs
use nalgebra::Vector3;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add_vectors(a: &Vector3<f32>, b: &Vector3<f32>) -> Vector3<f32> {
    a + b
}

There is a command for compilation:

wasm-pack build --target web

Nalgebra can also be used with GPU computing libraries such as wgpu And ash.

The nalgebra library is a powerful and flexible tool for working with linear algebra in Rust.

nalgebra on GitHub


What to do if you have an inexperienced team and need to do a difficult task? You can write a letter of resignation and then tell about your previous jobs on the Internet with horror. Or you can try to build processes in such a way as to get the required result. In the open lesson, we will not discuss which of these two strategies is correct, but we will talk about how mathematics can help build an application architecture that will allow you to solve this difficult task.

The lesson will be held as part of the course “Mathematics for programmers” on July 26. You can register using the link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *