Search code examples
c++tensorfloweigenblastensor

How to multiply an Eigen Tensor by the scalar sum of another Eigen Tensor in C++?


I am using the Tensor aspect of the Eigen library in C++ and would like to compute the element-wise product of one Eigen Tensor multiplied by the scalar sum of the elements in a second Eigen Tensor. Something like:

#include <Eigen/Dense>
Eigen::Tensor<float,2>u(5,5);
Eigen::Tensor<float,2>v(5,5);

u.setConstant(1.f);
v.setConstant(2.f);

Eigen::Tensor<float,2>ans(5,5);
ans = u*v.sum();

However the * operator is not supported in this way.

The README suggest to use the .constant() method associated with Tensor objects. And while

 ans = u*u.constant(5.);
 auto ans2 = u.sum(); 

compiles and functions properly

ans = u*u.constant(v.sum());

does not.

error: no matching function for call to ‘Eigen::Tensor<float, 2>::constant(const Eigen::TensorReductionOp<Eigen::internal::SumReducer<float>, const Eigen::DimensionList<long int, 2ul>, const Eigen::Tensor<float, 2>, Eigen::MakePointer>)’
       ans = u*u.constant(v.sum());
                                 ^

From further reading it appears that this is because u.constant() expects a Scalar value to be passed to it, whereas v.sum() returns a "non-evaluated expression" (see Tensor Operations and C++ "auto" in README). There is further suggestion that evaluation of v.sum() can be forced using .eval(), though this appears to return another "non-evaluated expression" type, albeit with a ForcedEvalOp tagged on the end.

error: no matching function for call to ‘Eigen::Tensor<float, 2>::constant(const Eigen::TensorForcedEvalOp<const Eigen::TensorReductionOp<Eigen::internal::SumReducer<float>, const Eigen::DimensionList<long int, 2ul>, const Eigen::Tensor<float, 2>, Eigen::MakePointer> >)’
   ans = u*u.constant(v.sum().eval());
                                    ^

The TODO section of README mentions:

"Representation of scalar values: Scalar values are often represented by tensors of size 1 and rank 1. It would be more logical and user friendly to use tensors of rank 0 instead. For example Tensor::maximum() currently returns a Tensor. Similarly, the inner product of 2 1d tensors (through contractions) returns a 1d tensor. In the future these operations might be updated to return 0d tensors instead."

Which implies that v.sum() should return a rank 1 Tensor of length 1. But the () operator, typically used for indexing seem unable to access its value in a usable form for u.constant() and:

ans = u*u.constant(v.sum()(0))

also fails to compile.

 error: no match for call to ‘(const Eigen::TensorReductionOp<Eigen::internal::SumReducer<float>, const Eigen::DimensionList<long int, 2ul>, const Eigen::Tensor<float, 2>, Eigen::MakePointer>) (int)’
   ans = u*u.constant(v.sum()(0));
                               ^

as does

ans = u*u.constant(v.sum().eval()(0))

.

error: no match for call to ‘(const Eigen::TensorForcedEvalOp<const Eigen::TensorReductionOp<Eigen::internal::SumReducer<float>, const Eigen::DimensionList<long int, 2ul>, const Eigen::Tensor<float, 2>, Eigen::MakePointer> >) (int)’
   ans = u*u.constant(v.sum().eval()(0));

Solution

  • According to this exchange, you should be able to force the evaluation by assigning the result of the reduction:

    Eigen::Tensor<float, 0> vSum = v.sum();
    

    That should work with your line:

    ans = u * u.constant(vSum);
    

    The cause of this is that if you try to call the template method constant directly with v.sum() it will try to use the declared returning type as Scalar, which does not work. Eigen uses a lot of complex, essentially opaque types to minimize unnecessary computation and copying, but also a lot of templating too, so it is not that uncommon having to explicitly force type conversions like in this case.