Using a Linear Algebra Library¶
This guide shows you how to use a linear algebra library's vector and matrix types (e.g. Eigen, GLM, Blaze) directly as mp-units vector and tensor representation types. For most mainstream libraries you do not need to write any adapter code at all. mp-units ships opt-in integration plugins that wire up the required customization points for you.
For the design rationale behind representation types and the full customization-point reference, see Representation Types in the User's Guide. To author a representation type of your own from scratch, see Using Custom Representation Types.
Shipped Integration Plugins¶
mp-units ships opt-in integration plugins, each available as a header (header mode) and as a named module (module mode). The currently available plugins adapt linear algebra libraries, so their vector and matrix types can be used directly as vector/tensor representations:
| Header | Module | CMake target (module mode) | Library |
|---|---|---|---|
<mp-units/integrations/eigen.h> |
mp_units.integrations.eigen |
mp-units::integrations-eigen |
Eigen |
<mp-units/integrations/glm.h> |
mp_units.integrations.glm |
mp-units::integrations-glm |
GLM |
<mp-units/integrations/blaze.h> |
mp_units.integrations.blaze |
mp-units::integrations-blaze |
Blaze |
Include the plugin header next to the library's own headers and the library types become usable as representations immediately:
#include <Eigen/Core>
#include <mp-units/integrations/eigen.h> // header mode; or: import mp_units.integrations.eigen;
#include <mp-units/systems/si.h>
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
quantity v = Eigen::Vector3d{30, 40, 0} * isq::velocity[km / h];
quantity speed = magnitude(v); // 50 km/h (a vector quantity supports magnitude() directly)
Each header is guarded with __has_include, so it is a harmless no-op when its library is
not available, and it is always safe to include. The matching module is built only when C++
modules and the library are both available. See the
linear algebra example for the same scenario compiled
against all three libraries, in both header and module mode, alongside the built-in
cartesian_vector.
Enabling in CMake¶
In header mode the integration headers ship with the library itself, so your existing
mp-units::mp-units link already makes them available. You only need to give the compiler
access to the linear algebra library's own headers. The guarded plugin header stays a no-op
until they are visible:
find_package(mp-units REQUIRED)
find_package(Eigen3 REQUIRED)
target_link_libraries(my_app PRIVATE mp-units::mp-units Eigen3::Eigen)
In module mode each plugin is a separate, optionally-built package. Discover it with
find_package (which also pulls in its mp-units and third-party dependencies) and link
its per-library target. That target deliberately links only mp-units::core and the linear
algebra library, not the systems, so keep your usual mp-units::mp-units (or
mp-units::systems) link for the si/isq definitions your code uses:
find_package(mp-units-integrations-eigen REQUIRED) # also finds its mp-units and Eigen3 deps
target_link_libraries(my_app PRIVATE mp-units::mp-units mp-units::integrations-eigen)
The header-only mp-units::integrations target, the per-library targets
(mp-units::integrations-eigen, -glm, -blaze), and their separate packaging are
documented in Project Structure.
Expression templates
Eigen and Blaze evaluate lazily: their arithmetic operators return proxy expression types
that reference their operands. The plugin headers map each such proxy to its evaluated
concrete type (via
representation_canonical_type)
so a quantity never stores a dangling proxy.
V2 limitation: vector-operation result types
A vector quantity supports magnitude() directly, but the result drops the precise
quantity spec down to the unit's kind. V2 cannot yet express a dedicated
scalar-magnitude quantity spec. As a result the magnitude keeps vector character
whenever the unit is tied to a vector quantity spec (e.g. N is kind_of<isq::force>),
and only collapses to scalar character for units built from scalar base units (e.g.
km/h). For the same reason quantity-level scalar_product() / vector_product()
(dot/cross) are not provided in V2. They must return a different quantity kind,
which V2 references cannot name. Compute those on the raw representation and re-attach the
reference yourself.
Adapting a Library That Has No Plugin¶
Any weakly-regular vector type (copyable, with bool-returning equality) that exposes a
Euclidean norm can serve as a representation. The bundled plugins wire up exactly the
customization points described here, and they are the template for adapting any other library.
Five things vary between libraries:
- Underlying type. Eigen and GLM expose a
value_typemember, sorepresentation_underlying_typedetects it automatically. Blaze names itElementTypeinstead, so its plugin specializesrepresentation_underlying_typeexplicitly. - Magnitude. Eigen and Blaze provide
norm(), which themagnitude()CPO uses directly. GLM spells itlength(), so its plugin adds a one-linemagnitude()overload (found by ADL) that forwards to it. - Order. A library whose structural shape is ambiguous declares its order explicitly.
An Eigen column vector is an
NĂ—1matrix, so it exposes both single-indexoperator[]and two-indexoperator(), making its order ambiguous, sotensor_orderhas no default for it and its plugin specializes it from the compile-time shape. GLM and Blaze, whose vectors and matrices are structurally distinct types, need no such override. - Field. No override is needed:
numeric_fieldreads the field off a scalar element, so a real matrix ofdoubleis real even though Eigen and Blaze also exposereal()/imag()on it, and a complex matrix is complex. The one requirement is that a complex-element container also exposereal()/imag()on its surface (Eigen and Blaze do); a complex container missing that API is left unclassified rather than guessed, so it is not a representation until it provides the API or specializesnumeric_field. -
Materializing expression templates. Eigen and Blaze return lazy proxy types from their arithmetic operators. A proxy holds references to its operands, so storing one inside a
quantitywould leave dangling references once those operands expire. Their plugins specializerepresentation_canonical_typeto map each proxy to its evaluated concrete type (Eigen'sPlainObject, Blaze'sResultType), so aquantityalways stores a materialized value. GLM evaluates eagerly, so it needs no such specialization.
Two further requirements are not customization points but constraints on how the library's own operations are written. They cost nothing for a library whose scalar side is pinned to its own element type (as Eigen, GLM, and Blaze all are), and they bite a library that templates it generically.
-
Scaling operators must be constrained on element validity, and must not accept a quantity or a reference (which includes a bare unit). A generic
operator*(const your_vector&, const auto&)swallows types it cannot handle.First, require that the element multiplication is valid, with
requires(const T& t, const U& u) { t * u; }. This is basic hygiene, since without it the operator is selected for arguments its own body cannot compile. It also settles unit magnitudes for free, becauseelement * magnitudeis ill-formed, so a constrained operator is never mistaken for one providing magnitude-aware scaling. An unconstrained one is, and the type must then answer whatelement * magnitudeproduces and is restricted to truncating conversions, when the built-in floating-point path would have handled it.Second, exclude
ReferenceandQuantityexplicitly. Element validity cannot catch these, becauseelement * unitandelement * quantityare both valid and simply yield a quantity. A number-vector multiplied by a unit or by a scalar quantity has to become a vector quantity (quantity<unit, your_vector>), never a vector of quantities, so the framework's ownRep * Referenceandquantity * Valueoperators must win.cartesian_vectorshows the guard: -
magnitude()must state its requirements as constraints, never as an internalstatic_assert. The library asks "ismagnitude(v)valid?" whenever it classifies a representation, and with a deduced return type that question instantiates the body. An assertion inside then fires as a hard error instead of a clean "no", which takes down an unrelated probe and points at the wrong place. Anything that decides whether the operation applies (a rank or shape requirement, for instance) belongs in the signature:
Element-wise operator== is disqualifying
Armadillo is the notable type that does not qualify: its
operator== returns an element-wise mask rather than a bool, so it is not
std::equality_comparable and cannot satisfy the representation requirements.
See Also¶
User's Guide:
- Representation Types - Complete design and requirements reference, including the customization points the plugins specialize
- Character of a Quantity - Understanding vector and tensor characters
How-to Guides:
- Using Custom Representation Types - Authoring a representation type of your own from scratch
- Decompose a Vector Quantity into Components - Splitting a vector quantity into named component quantities
Examples:
- Using a Linear Algebra Library as the Representation - Eigen, GLM, Blaze, and the built-in
cartesian_vectordriven through the same scenario