Generic programming§

nalgebra allows several kind of generic programming, either through traits of the alga crate, or by keeping the various type parameters of the Matrix<N, R, C, S> structure and the likes. In the end, this allows for:

Genericity wrt. scalars§

This allows to write code that works with algebraic entities constructed on top of a generic scalar type, e.g., floating-point numbers like f32 or f64 and in some more restrictive cases integers like i32 or u64. This can be achieved by keeping generic the first type parameter N of any structure of nalgebra (including aliases). The mandatory trait bound of N is na::Scalar which imposes some simple non-mathematical properties, i.e., N must copyable, printable (using the {:?} format string), and comparable using the equality operator. Other commonly used trait bounds for N are general::RingCommutative or general::Real from the alga crate. This enables operator overloading and useful mathematical functions for signed integers and floating point numbers respectively. Note that the Scalar trait bound does not have to be specified if Real already is.

use alga::general::{RingCommutative, Real};
use na::{Vector3, Scalar};

fn print_vector<N: Scalar>(m: &Vector3<N>) {
  println!("{:?}", m)
}

fn print_squared_norm<N: Scalar + RingCommutative>(v: &Vector3<N>) {
  // NOTE: alternatively, nalgebra already defines `v.squared_norm()`.
  let sqnorm = v.dot(v);
  println!("{:?}", sqnorm);
}

fn print_norm<N: Real>(v: &Vector3<N>) {
  // NOTE: alternatively, nalgebra already defines `v.norm()`.
  let norm = v.dot(v).sqrt();

  // The Real bound implies that N is Display so we can
  // use "{}" instead of "{:?}" for the format string.
  println!("{}", norm)
}

fn main() {
  let v1 = Vector3::new(1, 2, 3);
  let v2 = Vector3::new(1.0, 2.0, 3.0);

  print_vector(&v1);
  print_squared_norm(&v1);
  print_norm(&v2);
}

Dimensional-genericity§

Many algorithms work for an arbitrary number of dimensions. In those cases, there is often no point in writing a specific version for each dimension of interest because given the proper abstract mechanisms they all are equivalent (even performance-wise). Of course some algorithms (like convex hull computation) still require dedicated implementations for 2D and 3D to be efficient in which case the user may still retrieve the arguments’ dimension number with na::dimension::<V>() (where V is a vector type) to select specific implementations at runtime. Because both V and the result of na::dimension are known at compile-time, testing and the corresponding branching will usually be optimized away by the compiler. There are two ways of obtain this kind of abstraction in nalgebra:

  1. Algebraic genericity: the simplest and recommended method is to parametrize the generic algorithms with regard to the algebraic data type, i.e., the vector, matrix, transformation type, etc. This is based on the traits of the linear module of the alga crate. This method can be used only with algebraic entities with dimensions known at compile-time. This also gives you access to a rich set of features like vector space basis elements indexing, Gram-Schmidt orthogonalization, generic transformations, etc.
  2. Structural genericity: the less flexible way is to parametrize the algebraic structures themselves by the dimension number, for example, by keeping generic the number of rows R and columns C of the Matrix<N, R, C, S> type. Doing so is feasible but cumbersome as the data storage parameter must be generic as well because its type usually depends on the number of elements it has to contain (especially for stack-allocated algebraic structures). In some cases, because of some limitations of the rust compiler, even the data storage allocator associated type S::Alloc has to be constrained as well. See the section about implementation internals for more details about the specification of data storage structures and their allocators.
/// Reflects a vector wrt. the hyperplane with normal `plane_normal`.
fn reflect_wrt_hyperplane<V>(plane_normal: &Unit<V>, vector: &V) -> V
    where V: FiniteDimInnerSpace + Copy {
    let n = plane_normal.as_ref(); // Get the underlying vector of type `V`.
    *vector - *n * (n.dot(vector) * na::convert(2.0))
}

fn main() {
    let plane2 = Vector2::y_axis(); // 2D plane normal.
    let plane3 = Vector3::y_axis(); // 3D plane normal.

    let v2 = Vector2::new(1.0, 2.0);      // 2D vector to be reflected.
    let v3 = Vector3::new(1.0, 2.0, 3.0); // 3D vector to be reflected.

    // Call the generic function twice.
    let refl_v2 = reflect_wrt_hyperplane(&plane2, &v2);
    let refl_v3 = reflect_wrt_hyperplane(&plane3, &v3);

    assert!(refl_v2.y == -2.0);
    assert!(refl_v3.y == -2.0);
}
/// Reflects a vector wrt. the hyperplane with normal `plane_normal`.
fn reflect_wrt_hyperplane<N, D, S>(plane_normal: &Unit<ColumnVector<N, D, S>>,
                                   vector:       &ColumnVector<N, D, S>)
                                   -> OwnedColumnVector<N, D, S::Alloc>
    where N: Real,
          D: DimName,
          S: Storage<N, D, U1> {
    let n = plane_normal.as_ref(); // Get the underlying V.
    vector - n * (n.dot(vector) * na::convert(2.0))
}

fn main() {
    let plane2 = Vector2::y_axis(); // 2D plane normal.
    let plane3 = Vector3::y_axis(); // 3D plane normal.

    let v2 = Vector2::new(1.0, 2.0);      // 2D vector to be reflected.
    let v3 = Vector3::new(1.0, 2.0, 3.0); // 3D vector to be reflected.

    // Call the generic function twice.
    let refl_v2 = reflect_wrt_hyperplane(&plane2, &v2);
    let refl_v3 = reflect_wrt_hyperplane(&plane3, &v3);

    assert!(refl_v2.y == -2.0);
    assert!(refl_v3.y == -2.0);
}
/// Reflects a 2D vector wrt. the 2D line with normal `plane_normal`.
fn reflect_wrt_hyperplane2<N>(plane_normal: &Unit<Vector2<N>>,
                              vector:       &Vector2<N>)
                              -> Vector2<N>
    where N: Real {
    let n = plane_normal.as_ref(); // Get the underlying Vector2
    vector - n * (n.dot(vector) * na::convert(2.0))
}

/// Reflects a 3D vector wrt. the 3D plane with normal `plane_normal`.
/// /!\ This is an exact replicate of `reflect_wrt_hyperplane2, but for 3D.
fn reflect_wrt_hyperplane3<N>(plane_normal: &Unit<Vector3<N>>,
                              vector:       &Vector3<N>)
                              -> Vector3<N>
    where N: Real {
    let n = plane_normal.as_ref(); // Get the underlying Vector3
    vector - n * (n.dot(vector) * na::convert(2.0))
}

fn main() {
    let plane2 = Vector2::y_axis(); // 2D plane normal.
    let plane3 = Vector3::y_axis(); // 3D plane normal.

    let v2 = Vector2::new(1.0, 2.0);      // 2D vector to be reflected.
    let v3 = Vector3::new(1.0, 2.0, 3.0); // 3D vector to be reflected.

    // Call each specific implementation depending on the dimension.
    let refl_v2 = reflect_wrt_hyperplane2(&plane2, &v2);
    let refl_v3 = reflect_wrt_hyperplane3(&plane3, &v3);

    assert!(refl_v2.y == -2.0);
    assert!(refl_v3.y == -2.0);
}

Transformation-genericity§

If an algorithm works with elements of a given algebraic structure, it is usually expected to work with any element of its sub-structures as well. In particular, an algorithm that can be given as input elements of the group , where is the group operator, will still work if we feed it with elements of , where has the subgroup structure. For transformations types, this “subgroup” relationship is encoded by trait inheritance in alga (the subgroups inherits from the larger group).

For example, it makes sense for an algorithm that takes an Isometry3 in input to keep working if it is given a Rotation3 instead: rotations are just special cases of isometries with a translation equal to zero. However, the converse is not true: if an algorithm is designed to work only with pure rotations, it has little chances to keep working if we add translations as well.

The following figure shows the transformation trait hierarchy defined on the linear module of the alga crate. The corresponding data structures that implement them on nalgebra are shown in red. Note that despite the fact that alga designates an isometry as the composition of a reflexion, rotation, and a translation, the Isometry2/3 type of nalgebra does not include the reflexion part, i.e., it is actually a direct isometry.

transformation traits

Finally, note that the special data structure Id (not shown in the previous figure for clarity) is defined as a subgroup (with contains the identity element only) of every transformation group in alga. That’s why the Identity type performance trick actually works and is mathematically sound.

Implementation internals§


This section is still Work-in-Progress.


Perfomance tricks FAQ