Matrices, vectors, arrays and Linear Algebra: MRPT and Eigen classes
Contents
1. Classes for linear Algebra: Eigen
MRPT uses the powerful Eigen C++ library (version 3 of the API) for managing numerical matrices and vectors, so if you are familiar with Eigen, you already understand a large part of MRPT classes, interfaces and methods!
If you don’t know Eigen, these are good starting points:
The main difference with the standard Eigen API is that MRPT extends Eigen::Matrix<>
with many new typedefs and methods that extend the number of operations readily available for any vector or matrix, and by the way, provides a smooth transition for code relying on the former non-Eigen MRPT matrix APIs.
You can check out the documentation for Eigen::MatrixBase, which contains both the original Eigen methods plus those provided by MRPT (look for the sections named “MRPT plugin: …”) via a plugin mechanism.
2. MRPT-specific classes
For new code, it’s probably a good idea to directly use Eigen::Matrix<> (or any Eigen typedef: MatrixXd, MatrixXf, etc.) instead of MRPT types, since the latter allare Eigen::Matrix<>
instances (in the Object Programming sense of “being derived from”) with different template arguments, as summarized in this table:
MRPT type | Inherits from… | Memory layout |
mrpt::dynamicsize_vector < T > Base class for:
|
Eigen::Matrix<>< T,Dynamic,1 > |
Auto (→ ColMajor) |
mrpt::math::CArrayNumeric < T,LEN > |
Eigen::Matrix<>< T,N,1 > |
Auto (→ ColMajor) |
mrpt::math::CMatrixTemplateNumeric < T > With typedef ‘s:
And with derived classes capable of binary serialization:
|
Eigen::Matrix<>< T,Dynamic,Dynamic > |
RowMajor |
mrpt::math::CMatrixFixedNumeric < T, NROWS, NCOLS > |
Eigen::Matrix<>< T,NROWS, NCOLS > |
ColMajor only if NCOLS=1, RowMajor otherwise. |
|
These classes are not based on Eigen, but inherit from STL std::vector<> |
Elements contiguous in memory, as in any std::vector<> |
mrpt::math::CArray<T,SIZE> |
Not based on Eigen, designed to hold numeric or non-numeric elements. |
Elements contiguous in memory. |
MRPT classes inherit from Eigen::Matrix, but there a few more differences, which are enumerated below.
3. Differences between MRPT matrix classes and Eigen classes
- Note in the table above that memory layout is significative since former MRPT classes all were RowMajor, while Eigen’s default is ColMajor. In general, users can’t notice the difference, except for some special situations, for example, the following code expect the matrix constructor to read the numbers from the array in RowMajor order (so “2” ends up in the first row, second column, etc.):
123const double nums[] = { 1,2,3, 4,5,6 };mrpt::math::CMatrixFixedNumeric M(nums);std::cout << M;
- MRPT matrices’ default constructors set all entries to zero, while in Eigen the default values are undefined. Both approaches are useful in different cases.
- Dumping an MRPT matrix to
std::cout
will append a final line feed, while with Eigen classes the cursor will remain at the end of the last printed line. So, these two print commands will produce exactly the same output:
1234mrpt::math::CMatrixTemplateNumeric M1 = ...Eigen::MatrixXd M2 = M1;std::cout << M1; // MRPT matrix: automatically append new line char.std::cout << M2 << std::endl; // Eigen matrix: need to add new line char manually
4. Common errors (and their solutions!)
4.1. Alignment compile-time issues
Fixed-size vectorizable Eigen objects have special memory alignment requirements for efficiency. To refer to those classes plus any other class or struct that contains such matrices (at any depth down in the hierarchy!), I’ll use the name “problematic classes” (“problematic” standing for “needing special care for their alignment”).
There are plenty of affected classes in MRPT (CMatrixFixedNumeric
, CPose3D
, etc.), and there are good chances that your own classes are affected too, since just a single fixed-size matrix anywhere within a class marks it as “problematic”.
Normally the compiler handles the alignment requirements automatically, but there are some cases where it doesn’t, and you’ll find the following errors.
4.1.2. STL containers
If you have errors like:
1 |
error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned |
around a STL container, then it means that you must use Eigen’s special aligned memory allocator for the container. This Eigen’s webpage explains this problem in detail.
But, in short these are the required changes:
1 2 3 4 5 6 7 8 9 10 11 12 |
Let "TYPE" be any "problematic" class or struct. // vector std::vector<TYPE> v; // ERROR. std::vector<TYPE, Eigen::aligned_allocator<TYPE> > v; // OK, or alternatively mrpt::aligned_containers<TYPE>::vector_t v; // this version is a short-cut. // map std::map<KEY,VALUE> m; // ERROR. std::map<KEY,VALUE, std::less<KEY>, Eigen::aligned_allocator<std::pair<const KEY,VALUE> > > m; // OK, or alternatively mrpt::aligned_containers<KEY,VALUE>::map_t m; // this version is a short-cut. |
Notice that MRPT provides mrpt::aligned_containers to ease the declaration of such STL containers.
4.1.3. The “parameter won’t be aligned” error:
If you have an error like:
1 |
<span style="font-family: Consolas, Monaco, monospace; font-size: 12px; line-height: 18px;">error C2719: 'NAME': formal parameter with __declspec(align('16')) won't be aligned</span> |
and it wasn’t around an STL container, then you are trying to pass the “problematic” type as an argument by value. Read this page in Eigen’s website explaining the situation.
In short, the solution is to convert it to passing by reference:
1 2 3 4 5 6 7 8 9 |
// CPose3D: Is a problematic type since it contains Eigen matrixes of fixed-size. void myFunc(CPose3D p) // ERROR! {...} void myFunc(const CPose3D p) // ERROR! {...} void myFunc(CPose3D &p) // OK {...} void myFunc(const CPose3D &p) // OK {...} |
4.2. Alignment run-time issues
If you find runtime errors like:
1 2 3 |
Assertion failed: (reinterpret_cast<size_t>(array) & 0xf) == 0 && "this assertio n is explained here: " "http://eigen.tuxfamily.org/dox/UnalignedArrayAssert.html " " **** READ THIS WEB PAGE !!! ****", file ... |
while accessing data members in a class that contains “problematic” types, and the instantiation of that class was created dynamically (with new
), that is:
1 2 3 4 5 6 |
class CoolStuff { MyProblematicType m_damnit; ... }; CoolStuff c1; // OK CoolStuff c2 = new CoolStuff; // ERROR! |
then, (first read the page cited in the error!) the problem will be most likely solved adding the EIGEN_MAKE_ALIGNED_OPERATOR_NEW
macro to your class declaration:
1 2 3 4 5 6 7 8 |
class CoolStuff { MyProblematicType m_damnit; ... public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW // Add this, in the "public" scope }; CoolStuff c1; // OK CoolStuff c2 = new CoolStuff; // Now, it's OK too. |
When you create an instance of your class with new CoolStuff();
a custom memory allocator will be invoked that always returns aligned memory.
Important note: All MRPT classes inheriting from mrpt::utils::CObject
already have this specialized new operators, so if your custom classes also inhirit fromCObject
or CSerializable
, you don’t have to use that macro.
4.3. Mixing numeric types: explicit casting
In order to avoid unintended conversions from matrices/vectors of one type to the other (e.g. double <->float), which cost time, Eigen raises the following error at runtime:
1 2 3 |
Eigen/src/Core/Assign.h:504: Derived& Eigen::DenseBase::lazyAssign(const Eigen::DenseBase&) [with OtherDerived = Eigen::Matrix<float,...>, Derived = Eigen::Matrix<double, ...="">]: Assertion `(SameType) && "YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY"' failed. |
if you try something as:
1 2 3 |
Eigen::Matrix<double,3,3> Md; Eigen::Matrix<float,3,3> Mf; Md = Mf; // This causes the error! |
The solution is to use the cast() explicit converter, that is:
1 2 3 |
Eigen::Matrix<double,3,3> Md; Eigen::Matrix<float,3,3> Mf; Md = Mf.cast(); // Now this is OK |
4.4. resize()
vs. setSize()
Notice that resize()
is a native Eigen method that doesn’t preserve the old contents of the matrix, while setSize()
is an MRPT method that pads with zerosthe new elements and keep the old values unchanged.
Refer to the documentation of Eigen::MatrixBase
(which includes the plugin methods from MRPT).
5. Issues porting MRPT code from ≤ 0.9.2
Apart from finding the build or runtime errors mentioned above (which apply to either new or old Eigen and MRPT-based code), there are some important points to keep in mind if your code intensively used old (non-Eigen) MRPT vectors and matrices:
5.1. Issues with mrpt::vector_float
and mrpt::vector_double
The types mrpt::vector_float and std::vector<float> (or the double
versions) are not interchangeable any more.
5.2. Matrices constructors from poses (TPose2D
,…) are now explicit
Example: Previous code “CMatrixDouble31 m = myPose2D;” won’t build now, should be: “CMatrixDouble31 m = CMatrixDouble31(myPose2D);”
5.3. Iterators to matrix elements
Iterators to vectors and matrices, which were implemented with a uniform interface with former MRPT classes, are not available anymore, since they are not provided by Eigen. Read about their reasons in this thread.
5.4. CVectorFloat
and CVectorDouble
The use of these types is discouraged in new code. They are now synonymous with mrpt::vector_float
and mrpt::vector_double
.
5.5. The matrix method unit()
The method unit()
, which sets a matrix to an identity matrix, had an inconsistent signature between fixed and dynamic sized matrices. It’s now unified in Eigen::MatrixBase::unit(), although you can always directly invoke Eigen’s setIdentity()
.
6. Advanced topics
6.1. Including boh MRPT and Eigen headers in your code
- Include MRPT headers first (since it uses the plugin mechanism…).
- Including “mrpt-base” assures Eigen headers are always already included.
- Can’t mix version 2 & 3. Use either MRPT embedded version or other copy of Eigen version 3, but not both.
- CMake-based mechanism to extend Eigen::MatrixBase plugins already in MRPT.