Skip to content

Eigen Interoperation

Au supports using Eigen vector and matrix types as the underlying storage type — the “rep” — of a Quantity. This gives you a vector or matrix where every element carries the same unit, with all of Au’s usual safety and conversion features, and with the same runtime performance as raw Eigen.

Once you’re finished with this guide, you’ll be able to create Eigen-backed quantities, do unit-aware arithmetic and conversions with them, read and write individual elements, and avoid the lifetime pitfalls that Eigen is famous for.

Warning

Eigen carries a seriously elevated object lifetime risk, compared to most other libraries. We strongly recommend reading and understanding our safety guide so that you can recognize and avoid the pitfalls.

Creating Eigen-backed quantities

Pass an Eigen object to any quantity maker, just as you would a raw number:

auto displacement = meters(Eigen::Vector3d{1.0, 2.0, 3.0});

This produces a Quantity<Meters, Eigen::Vector3d>: a single quantity whose value is a vector, and whose every element is a length in meters.

auto and safety

The above snippet uses auto with Eigen, which means we should carefully check it for object lifetime risk. Using the principles in our safety guide, we can see that this instance is safe, because there are no operations — and, therefore, no expression templates with references that could dangle.

Arithmetic and conversions

The usual Quantity operations work as expected, including mixed-unit operations:

auto a = meters(Eigen::Vector3d{1.0, 2.0, 3.0});
auto b = centi(meters)(Eigen::Vector3d{400.0, 500.0, 600.0});

a.in<Eigen::Vector3d>(centi(meters));   // Eigen::Vector3d{100.0, 200.0, 300.0}

eval((a + b).in(centi(meters)));        // Eigen::Vector3d{500.0, 700.0, 900.0}

Note that the above examples carefully avoid lifetime issues by following the principles in our safety guide, which also explains how to spot these issues in the first place.

Element access

Reading an element produces an ordinary Quantity whose rep is the element type:

auto displacement = meters(Eigen::Vector3d{1.0, 2.0, 3.0});

displacement[0];     // meters(1.0)
displacement(2);     // meters(3.0)

Writing an element requires an explicit call to .mutable_view():

displacement.mutable_view()[0] = meters(10.0);
displacement.mutable_view()[1] = centi(meters)(2000.0);  // Unit-safe: stores 20.0

displacement[0] = meters(10.0) will not compile, on purpose: displacement[0] is a temporary, so that assignment would be a silent no-op. See the element access reference docs for more detail.

Eigen member functions

Eigen provides much of its API as member functions — v.norm(), v.transpose(), v.dot(w), and so on.

Au cannot provide these as member functions for an Eigen-backed Quantity, because it would be inappropriately invasive, and would massively increase the scope of the Quantity API. Instead, there is a simple rule: every Eigen member function becomes a free function with the same name for an Eigen-backed Quantity. These are all available in Au’s Eigen compatibility header:

#include "au/compatibility/eigen.hh"

This header has no physical dependency on Eigen, so including it doesn’t add Eigen to your project, and include order doesn’t matter. Here are some usage examples.

auto v = meters(Eigen::Vector3d{3.0, 4.0, 0.0});

norm(v);           // meters(5.0)
squaredNorm(v);    // squared(meters)(25.0)
dot(v, v);         // squared(meters)(25.0)
eval(transpose(v));  // Transposed vector, still in meters.

See the Eigen compatibility reference for the full list of functions.

Run your tests under sanitizers

Lifetime bugs with expression templates are easy to write and hard to spot in review — again, exactly as with raw Eigen. We strongly recommend making sure that every line of Eigen code is covered by a unit test, and that those unit tests are run under the address sanitizer. This “defense in depth” approach complements human review.

Outcome and limitations

You can now use Eigen types as first-class quantity reps, with unit safety on top and no performance penalty underneath. Here are the main known limitations.

  1. Every element of a vector or matrix quantity has the same unit. “Heterogeneous” matrices, with distinct units per row or column, are planned future work (see #707).

  2. prod() and determinant() require fixed-size operands, because the result’s unit depends on the operand’s size, which must therefore be known at compile time.

  3. mutable_view() is more verbose than raw Eigen’s v[i] = .... This is a deliberate safety tradeoff; the safety guide explains why.