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: the most common kind of abstraction. This allows you to write code that will work on any scalar group like
i32
and fields likef32
orf64
. 
Dimensional genericity: allows you to write code that will work generically for 2D, 3D, and higher dimensions.

Transformation genericity: Some transformations may have several representations. For example, 3D rotations can either be expressed as 3x3 matrices with
Rotation3
or as unit quaternions withUnitQuaternion
. Both have their advantages and downsides depending on the applications. Thus it can be useful to write algorithms that are independent from the user’s choice of representation for the same transformation. Moreover, this abstraction allows you to use simpler transformation (e.g. a pure rotation) where a more complex one is accepted (e.g. an isometry). This can be allowed because some transformation groups are subgroups of others. For example, the group of rotations is a subgroup of the direct isometry group .
Genericity wrt. scalars§
This allows to write code that works with algebraic entities constructed on top
of a generic scalar type, e.g., floatingpoint 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 nonmathematical 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);
}
Dimensionalgenericity§
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 performancewise). 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 compiletime,
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:
 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 compiletime. This also gives you access to a rich set of features like vector space basis elements indexing, GramSchmidt orthogonalization, generic transformations, etc.  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 columnsC
of theMatrix<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 stackallocated algebraic structures). In some cases, because of some limitations of the rust compiler, even the data storage allocator associated typeS::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);
}
Transformationgenericity§
If an algorithm works with elements of a given algebraic structure, it is usually expected to work with any element of its substructures 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.
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§