Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write arbitrary Eigen object to row-major plain storage

Tags:

c++

eigen

eigen3

I am writing a module to write data to a file which uses by convention only row-major storage. I would like my function to be able to allow both column-major and row-major Eigen objects as input.

Currently I first use Eigen to copy a column-major object to a row-major object, before I write. My code works well for most cases, but for Eigen::VectorXi compiling fails with an assertion that I don't understand. How do I solve this? Can I avoid creating many cases?

The code (writing is mimicked by outputting a std::vector):

#include <vector>
#include <iostream>
#include <Eigen/Eigen>

template <class T, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
std::vector<T> write(const Eigen::Matrix<T,Rows,Cols,Options,MaxRows,MaxCols>& matrix)
{
    std::vector<T> data(static_cast<size_t>(matrix.size()));

    if (matrix.IsRowMajor) {
        std::copy(matrix.data(), matrix.data()+matrix.size(), data.begin());
        return data;
    } else {
        Eigen::Matrix<T, Rows, Cols, Eigen::RowMajor, MaxRows, MaxCols> tmp = matrix;
        return write(tmp);
    }
}

int main()
{
    Eigen::VectorXi matrix = Eigen::VectorXi::LinSpaced(10, 0, 9);

    std::vector<int> output = write(matrix);
}

The compilation error:

In file included from test.cpp:3:
In file included from /usr/local/Cellar/eigen/3.3.7/include/eigen3/Eigen/Eigen:1:
In file included from /usr/local/Cellar/eigen/3.3.7/include/eigen3/Eigen/Dense:1:
In file included from /usr/local/Cellar/eigen/3.3.7/include/eigen3/Eigen/Core:457:
/usr/local/Cellar/eigen/3.3.7/include/eigen3/Eigen/src/Core/PlainObjectBase.h:903:7: error: static_assert failed "INVALID_MATRIX_TEMPLATE_PARAMETERS"
      EIGEN_STATIC_ASSERT((EIGEN_IMPLIES(MaxRowsAtCompileTime==1 && MaxColsAtCompileTime!=1, (Options&RowMajor)==RowMajor)
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/Cellar/eigen/3.3.7/include/eigen3/Eigen/src/Core/util/StaticAssert.h:33:40: note: expanded from macro 'EIGEN_STATIC_ASSERT'
    #define EIGEN_STATIC_ASSERT(X,MSG) static_assert(X,#MSG);
                                       ^             ~
/usr/local/Cellar/eigen/3.3.7/include/eigen3/Eigen/src/Core/PlainObjectBase.h:535:7: note: in instantiation of member function 'Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 1, 1, -1, 1>
      >::_check_template_params' requested here
      _check_template_params();
      ^
/usr/local/Cellar/eigen/3.3.7/include/eigen3/Eigen/src/Core/Matrix.h:377:9: note: in instantiation of function template specialization 'Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 1, 1, -1, 1>
      >::PlainObjectBase<Eigen::Matrix<int, -1, 1, 0, -1, 1> >' requested here
      : Base(other.derived())
        ^
test.cpp:14:79: note: in instantiation of function template specialization 'Eigen::Matrix<int, -1, 1, 1, -1, 1>::Matrix<Eigen::Matrix<int, -1, 1, 0, -1, 1> >' requested here
        Eigen::Matrix<T, Rows, Cols, Eigen::RowMajor, MaxRows, MaxCols> tmp = matrix;
                                                                              ^
test.cpp:23:31: note: in instantiation of function template specialization 'write<int, -1, 1, 0, -1, 1>' requested here
    std::vector<int> output = write(matrix);
                              ^
1 error generated.
like image 303
Tom de Geus Avatar asked Apr 17 '26 02:04

Tom de Geus


2 Answers

Understanding the static assertion

Unfortunately the assertion is really not self-explanatory and the only thing you can get from it is the hint, that something is wrong with your template parameters. If we look into Eigen's source code we find the following beginning on line 903:

EIGEN_STATIC_ASSERT((EIGEN_IMPLIES(MaxRowsAtCompileTime==1 && MaxColsAtCompileTime!=1, (Options&RowMajor)==RowMajor)
                        && EIGEN_IMPLIES(MaxColsAtCompileTime==1 && MaxRowsAtCompileTime!=1, (Options&RowMajor)==0)
                        && ((RowsAtCompileTime == Dynamic) || (RowsAtCompileTime >= 0))
                        && ((ColsAtCompileTime == Dynamic) || (ColsAtCompileTime >= 0))
                        && ((MaxRowsAtCompileTime == Dynamic) || (MaxRowsAtCompileTime >= 0))
                        && ((MaxColsAtCompileTime == Dynamic) || (MaxColsAtCompileTime >= 0))
                        && (MaxRowsAtCompileTime == RowsAtCompileTime || RowsAtCompileTime==Dynamic)
                        && (MaxColsAtCompileTime == ColsAtCompileTime || ColsAtCompileTime==Dynamic)
                        && (Options & (DontAlign|RowMajor)) == Options),
        INVALID_MATRIX_TEMPLATE_PARAMETERS)

Even though the compiler indicates that

EIGEN_IMPLIES(MaxRowsAtCompileTime==1 && MaxColsAtCompileTime!=1, (Options&RowMajor)==RowMajor)

causes the error, the following line really does:

EIGEN_IMPLIES(MaxColsAtCompileTime==1 && MaxRowsAtCompileTime!=1, (Options&RowMajor)==0)

Understanding what triggers the assertion

You provide Eigen::VectorXi as an input for write. Eigen::VectorXi is really just a typedef for

Eigen::Matrix<int, Eigen::Dynamic, 1, Eigen::ColMajor, Eigen::Dynamic, 1>

Therefore the line

Eigen::Matrix<T, Rows, Cols, Eigen::RowMajor, MaxRows, MaxCols> tmp = matrix;

in write expands to

Eigen::Matrix<int, Eigen::Dynamic, 1, Eigen::RowMajor, Eigen::Dynamic, 1> tmp = matrix;

which triggers the assertion, since a matrix with MaxColsAtCompileTime==1 and MaxRowsAtCompileTime!=1 must not be RowMajor.

Solve your problem

The problem now is that even though you can check if your input matrix is a vector, row-major or column-major, you cannot declare

Eigen::Matrix<T, Rows, Cols, Eigen::RowMajor, MaxRows, MaxCols>

if it is no legal to do so at compile-time (and it isn't due to the static assertion).

You have the following options to make your code work:

1. if constexpr (C++17)

C++17 offers a way for detecting at compile-time if a certain conditional branch will be taken or not. The downside of this approach (beside the requirement for a C++17 compiler) is that you can only test for constant expressions.

In the concrete example this looks like this:

template <class T, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
std::vector<T> write(const Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols>& matrix)
{
  typedef Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols> MatrixType;
  std::vector<T> data(static_cast<size_t>(matrix.size()));

  if constexpr (MatrixType::MaxRowsAtCompileTime == 1 || 
                MatrixType::MaxColsAtCompileTime ==1 ||
               (MatrixType::Options&Eigen::RowMajor) == Eigen::RowMajor) {
    std::copy(matrix.data(), matrix.data() + matrix.size(), data.begin());
    return data;
  } else {
    Eigen::Matrix<T, Rows, Cols, Eigen::RowMajor, MaxRows, MaxCols> tmp = matrix;
    return write(tmp);
  }
}

2. SFINAE

You can dispatch the call to write at compile-time using SFINAE by using std::enable_if. The following example uses a slightly modified version of your original code but everything should be clear from context:

// matrix is either a vector or in row-major
template <typename Derived>
std::vector<typename Derived::Scalar> write(const Eigen::MatrixBase<Derived>& matrix,
  typename std::enable_if<Derived::MaxRowsAtCompileTime == 1 ||
                          Derived::MaxColsAtCompileTime == 1 ||
                          (Derived::Options & Eigen::RowMajor) == Eigen::RowMajor,
                          Derived>::type* = 0)
{
  std::vector<typename Derived::Scalar> data(
      static_cast<size_t>(matrix.size()));
  std::copy(matrix.derived().data(), matrix.derived().data() + matrix.size(),
            data.begin());
  return data;
}

// matrix is neither a vector nor in row-major
template <typename Derived>
std::vector<typename Derived::Scalar> write(const Eigen::MatrixBase<Derived>& matrix,
  typename std::enable_if<Derived::MaxRowsAtCompileTime != 1 &&
                          Derived::MaxColsAtCompileTime != 1 &&
                          (Derived::Options & Eigen::RowMajor) == 0,
                          Derived>::type* = 0)
{
  Eigen::Matrix<typename Derived::Scalar, Derived::RowsAtCompileTime,
                Derived::ColsAtCompileTime, Eigen::RowMajor,
                Derived::MaxRowsAtCompileTime, Derived::MaxColsAtCompileTime> tmp = matrix;
  return write(tmp);
}

This works using a C++11 compiler.

Other options would be to specialise the template but it will get even more lengthy than the SFINAE approach.

Some test cases:

Eigen::Matrix<int, 3, 3, Eigen::RowMajor> m;
m << 1, 2, 3, 
     1, 2, 3,
     1, 2, 3;

std::vector<int> output = write(m);

for (const auto& element : output) {
  std::cout << element << " ";
}

Output: 1 2 3 1 2 3 1 2 3

Eigen::Matrix<int, 3, 3, Eigen::ColMajor> m;
m << 1, 2, 3, 
     1, 2, 3,
     1, 2, 3;

std::vector<int> output = write(m);

for (const auto& element : output) {
  std::cout << element << " ";
}

Output: 1 2 3 1 2 3 1 2 3

Eigen::VectorXi m = Eigen::VectorXi::LinSpaced(10, 0, 9);

std::vector<int> output = write(m);

for (const auto& element : output) {
  std::cout << element << " ";
}

Output: 0 1 2 3 4 5 6 7 8 9

Eigen::RowVectorXi m = Eigen::RowVectorXi::LinSpaced(10, 0, 9);

std::vector<int> output = write(m);

for (const auto& element : output) {
  std::cout << element << " ";
}

Output: 0 1 2 3 4 5 6 7 8 9

like image 87
Orzowei Avatar answered Apr 18 '26 17:04

Orzowei


A simpler solution is to let Eigen::Ref does all the job for you:

Ref<const Matrix<T,Rows,Cols,Cols==1?ColMajor:RowMajor,MaxRows,MaxCols>,0, InnerStride<1> > row_maj(matrix);

Then row_maj will be guaranteed to be sequentially stored in row-major order. If matrix is compatible, then no copy occurs. No branch, no SFINAE, etc.

Here matrix can be any expression, not only a Matrix<...> but also sub-matrices, Map, another Ref, etc.

To handle any expressions, just replace Rows and the likes with XprType::RowsAtCompileTime where XprType is the type of matrix.

template <class XprType>
std::vector<typename XprType::Scalar> write(const Eigen::MatrixBase<XprType>& matrix)
{...}
like image 20
ggael Avatar answered Apr 18 '26 17:04

ggael



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!