Eigen unsupported Tensor to Eigen matrix
Asked Answered
T

1

5

I get a Eigen::Tensor<std::complex, 2> after some operations on a Tensor with more dimensions. Is there an easy way to create a Eigen::MatrixXcf from this Tensor-object or do I have to copy the values manually?

Trillby answered 14/2, 2018 at 20:38 Comment(0)
W
12

Original answer. See the update further down.

I am currently using Eigen version 3.3.4, and there is no easy built-in function to cast between Eigen::Tensor types and the more familiar Matrix or Array types. It would have been handy to have something like the .matrix() or .array() methods.

There is also no easy way to add such methods via the plugin mechanism, because the Tensor module doesn't seem to support plugins. If anybody knows how please comment.

In the meantime it is possible to make make fairly efficient workarounds using the Map functions. The following works in C++14, for casting Tensors to and from Matrices

#include <unsupported/Eigen/CXX11/Tensor>
#include <iostream>
   
template<typename T>
using  MatrixType = Eigen::Matrix<T,Eigen::Dynamic, Eigen::Dynamic>;
   
template<typename Scalar,int rank, typename sizeType>
auto Tensor_to_Matrix(const Eigen::Tensor<Scalar,rank> &tensor,const sizeType rows,const sizeType cols)
{
    return Eigen::Map<const MatrixType<Scalar>> (tensor.data(), rows,cols);
}


template<typename Scalar, typename... Dims>
auto Matrix_to_Tensor(const MatrixType<Scalar> &matrix, Dims... dims)
{
    constexpr int rank = sizeof... (Dims);
    return Eigen::TensorMap<Eigen::Tensor<const Scalar, rank>>(matrix.data(), {dims...});
}


int main () {
    Eigen::Tensor<double,4> my_rank4 (2,2,2,2);
    my_rank4.setRandom();

    Eigen::MatrixXd         mymatrix =  Tensor_to_Matrix(my_rank4, 4,4);
    Eigen::Tensor<double,3> my_rank3 =  Matrix_to_Tensor(mymatrix, 2,2,4);

    std::cout << my_rank3 << std::endl;

    return 0;
}

This works with complex types just as well.

Unfortunately these functions only take tensors, not tensor operations. For instance, this doesn't work:

Eigen::MatrixXd mymatrix =  Tensor_to_Matrix(my_rank4.shuffle(Eigen::array<long,4>{1,0,3,2}), 4,4);

Update August 2021:

A memory leak was fixed, see the code below. In short, one must remember to call .cleanup() member function on the Eigen::TensorEvaluator object to deallocate internal temporary buffers.

Update October 2021:

Since I submitted the answer above back in 2018, I have learned how to map and cast between Eigen::Tensor and Eigen::Matrix (and expressions thereof) with a minimal amount of temporaries.

I have used the helper functions below since Eigen 3.3.7 using C++17

#include <unsupported/Eigen/CXX11/Tensor>
#include <iostream>
    
template<typename T>
using  MatrixType = Eigen::Matrix<T,Eigen::Dynamic, Eigen::Dynamic>;
template<typename T>
using  VectorType = Eigen::Matrix<T,Eigen::Dynamic, 1>;


/*
 *
 *  Convert Eigen::Tensor --> Eigen::Matrix
 *
 */


template<typename Derived, typename Device = Eigen::DefaultDevice>
class selfCleaningEvaluator {
    private:
    using Evaluator = Eigen::TensorEvaluator<const Eigen::TensorForcedEvalOp<const Derived>, Device>;
    Evaluator m_eval;

    public:
    selfCleaningEvaluator(const Evaluator &eval) : m_eval(eval) {}
    selfCleaningEvaluator(const Eigen::TensorBase<Derived, Eigen::ReadOnlyAccessors> &expr, const Device &device = Device())
        : m_eval(Evaluator(expr.eval(), device)) {
        m_eval.evalSubExprsIfNeeded(nullptr);
    }

    ~selfCleaningEvaluator() {
        // This whole point of this object is to call cleanup automatically on destruct.
        // If there are pending operations to evaluate, m_eval will allocate a buffer to hold a result,
        // which needs to be deallocated.
        m_eval.cleanup();
    }

    constexpr auto rank() {
        using DimType = typename decltype(m_eval)::Dimensions::Base;
        return DimType{}.size(); // Because Derived::Dimensions is sometimes wrong
    }
    const Evaluator *operator->() const { return &m_eval; }
    Evaluator       *operator->() { return &m_eval; }
    constexpr auto   map() {
        // We inspect m_eval to get the type, rank and dimensions, because it has the resulting tensor,
        // whereas Derived is the tensor type _before_ whatever operation is pending (if any).
        using DimType       = typename decltype(m_eval)::Dimensions::Base;
        constexpr auto rank = DimType{}.size();
        using Scalar        = typename Eigen::internal::remove_const<typename decltype(m_eval)::Scalar>::type;
        return Eigen::TensorMap<Eigen::Tensor<Scalar, rank>>(m_eval.data(), m_eval.dimensions());
    }
};

// Evaluates expressions if needed
template<typename T, typename Device = Eigen::DefaultDevice>
auto asEval(const Eigen::TensorBase<T, Eigen::ReadOnlyAccessors> &expr,  // An Eigen::TensorBase object (Tensor, TensorMap, TensorExpr... )
            const Device &device = Device()) {                           // Override to evaluate on another device, e.g. thread pool or gpu.
    return selfCleaningEvaluator(expr, device);
}

// Converts any Eigen::Tensor (or expression) to an Eigen::Matrix with shape rows/cols
template<typename T, typename sizeType, typename Device = Eigen::DefaultDevice>
auto MatrixCast(const Eigen::TensorBase<T, Eigen::ReadOnlyAccessors> &expr, const sizeType rows, const sizeType cols, const Device &device = Device()) {
    auto tensor    = asEval(expr, device);
    auto tensorMap = tensor.map();
    using Scalar   = typename decltype(tensorMap)::Scalar;
    return static_cast<MatrixType<Scalar>>(Eigen::Map<const MatrixType<Scalar>>(tensorMap.data(), rows, cols));
}

// Converts any Eigen::Tensor (or expression) to an Eigen::Matrix with shape rows/cols
template<typename T, typename Device = Eigen::DefaultDevice>
auto VectorCast(const Eigen::TensorBase<T, Eigen::ReadOnlyAccessors> &expr, const Device &device = Device()) {
    auto tensor    = asEval(expr, device);
    auto tensorMap = tensor.map();
    auto size      = Eigen::internal::array_prod(tensorMap.dimensions());
    using Scalar   = typename decltype(tensorMap)::Scalar;
    return static_cast<VectorType<Scalar>>(Eigen::Map<const VectorType<Scalar>>(tensorMap.data(), size));
}

// View an existing Eigen::Tensor as an Eigen::Map<Eigen::Matrix>
template<typename Scalar, auto rank, typename sizeType>
auto MatrixMap(const Eigen::Tensor<Scalar, rank> &tensor, const sizeType rows, const sizeType cols) {
    return Eigen::Map<const MatrixType<Scalar>>(tensor.data(), rows, cols);
}

// View an existing Eigen::Tensor of rank 2 as an Eigen::Map<Eigen::Matrix>
// Rows/Cols are determined from the matrix
template<typename Scalar>
auto MatrixMap(const Eigen::Tensor<Scalar, 2> &tensor) {
    return Eigen::Map<const MatrixType<Scalar>>(tensor.data(), tensor.dimension(0), tensor.dimension(1));
}

// View an existing Eigen::Tensor of rank 1 as an Eigen::Map<Eigen::Vector>
// Rows is the same as the size of the tensor. 
template<typename Scalar, auto rank>
auto VectorMap(const Eigen::Tensor<Scalar, rank> &tensor) {
    return Eigen::Map<const VectorType<Scalar>>(tensor.data(), tensor.size());
}


/*
 *
 *  Convert Eigen::Matrix --> Eigen::Tensor
 *
 */


// Converts an Eigen::Matrix (or expression) to Eigen::Tensor
// with dimensions specified in std::array
template<typename Derived, typename T, auto rank>
Eigen::Tensor<typename Derived::Scalar, rank>
TensorCast(const Eigen::EigenBase<Derived> &matrix, const std::array<T, rank> &dims) {
    return Eigen::TensorMap<const Eigen::Tensor<const typename Derived::Scalar, rank>>
                (matrix.derived().eval().data(), dims);
}

// Converts an Eigen::Matrix (or expression) to Eigen::Tensor
// with dimensions specified in Eigen::DSizes
template<typename Derived, typename T, auto rank>
Eigen::Tensor<typename Derived::Scalar, rank>
TensorCast(const Eigen::EigenBase<Derived> &matrix, const Eigen::DSizes<T, rank> &dims) {
    return Eigen::TensorMap<const Eigen::Tensor<const typename Derived::Scalar, rank>>
                (matrix.derived().eval().data(), dims);
}

// Converts an Eigen::Matrix (or expression) to Eigen::Tensor
// with dimensions as variadic arguments
template<typename Derived, typename... Dims>
auto TensorCast(const Eigen::EigenBase<Derived> &matrix, const Dims... dims) {
    static_assert(sizeof...(Dims) > 0, "TensorCast: sizeof... (Dims) must be larger than 0");
    return TensorCast(matrix, std::array<Eigen::Index, sizeof...(Dims)>{dims...});
}

// Converts an Eigen::Matrix (or expression) to Eigen::Tensor
// with dimensions directly as arguments in a variadic template
template<typename Derived>
auto TensorCast(const Eigen::EigenBase<Derived> &matrix) {
    if constexpr(Derived::ColsAtCompileTime == 1 or Derived::RowsAtCompileTime == 1) {
        return TensorCast(matrix, matrix.size());
    } else {
        return TensorCast(matrix, matrix.rows(), matrix.cols());
    }
}

// View an existing Eigen::Matrix as Eigen::TensorMap
// with dimensions specified in std::array
template<typename Derived, auto rank>
auto TensorMap(const Eigen::PlainObjectBase<Derived> &matrix, const std::array<long, rank> &dims) {
    return Eigen::TensorMap<const Eigen::Tensor<const typename Derived::Scalar, rank>>(matrix.derived().data(), dims);
}

// View an existing Eigen::Matrix as Eigen::TensorMap
// with dimensions as variadic arguments
template<typename Derived, typename... Dims>
auto TensorMap(const Eigen::PlainObjectBase<Derived> &matrix, const Dims... dims) {
    return TensorMap(matrix, std::array<long, static_cast<int>(sizeof...(Dims))>{dims...});
}

// View an existing Eigen::Matrix as Eigen::TensorMap
// with dimensions determined automatically from the given matrix
template<typename Derived>
auto TensorMap(const Eigen::PlainObjectBase<Derived> &matrix) {
    if constexpr(Derived::ColsAtCompileTime == 1 or Derived::RowsAtCompileTime == 1) {
        return TensorMap(matrix, matrix.size());
    } else {
        return TensorMap(matrix, matrix.rows(), matrix.cols());
    }
}



int main () {
    Eigen::Tensor<double,4> my_rank4 (2,2,2,2);
    my_rank4.setRandom();

    Eigen::MatrixXd         mymatrix =  MatrixCast(my_rank4, 4,4);   // Cast Eigen::Tensor --> Eigen::Matrix
    Eigen::Tensor<double,3> my_rank3 =  TensorCast(mymatrix, 2,2,4); // Cast Eigen::Matrix --> Eigen::Tensor

    std::cout << my_rank3 << std::endl;

    return 0;
}    

Try on compiler-explorer

Williswillison answered 24/2, 2018 at 14:2 Comment(2)
I haven't worked much with the Map-Types. When I create a Matrix with your Tensor_to_Matrix function and the Tensor used for that gets destroyed(and with that the data I assume), is the data inside the Matrix then pointing towards gargabe or is that handled by the Map?Trillby
Maps can be received directly into the appropriate matrix type (Like MatrixXd) or into another map (Eigen::Map<Eigen::MatrixXd>), or a matrix reference (Eigen::Ref<Eigen::MatrixXd>). I'm not 100% sure, but I believe in the first case the data is copied, and in the other cases you get a wrapper, or "view" for the raw data in your tensor. This should allow for lazy evaluation. If this is a concern you can copy data explicitly by replacing auto with MatrixType<Scalar> in the function declaration.Williswillison

© 2022 - 2024 — McMap. All rights reserved.