Compare Eigen matrices in Google Test or Google Mock
Asked Answered
C

3

14

I was wondering if there is a good way to test two Eigen matrices for approximate equality using Google Test, or Google Mock.

Take the following test-case as a simplified example: I am multiplying two complex valued matrices A, and B, and expect a certain result C_expect. I calculate the numerical result C_actual = A * B, using Eigen. Now, I want to compare C_expect, and C_actual. Right now, the corresponding code looks like this:

#include <complex>
#include <Eigen/Dense>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

typedef std::complex<double> Complex;
typedef Eigen::Matrix2cd Matrix;

TEST(Eigen, MatrixMultiplication) {
    Matrix A, B, C_expect, C_actual;

    A << Complex(1, 1), Complex(2, 3),
         Complex(3, 2), Complex(4, 4);
    B << Complex(4, 4), Complex(3, 2),
         Complex(2, 3), Complex(1, 1);
    C_expect << Complex(-5, 20), Complex(0, 10),
                Complex(0, 40), Complex(5, 20);

    C_actual = A * B;

    // !!! These are the lines that bother me.
    for (int j = 0; j < C_actual.cols(); ++j) {
        for (int i = 0; i < C_actual.rows(); ++i) {
            EXPECT_NEAR(C_expect(i, j).real(), C_actual(i, j).real(), 1e-7)
                << "Re(" << i << "," << j << ")";
            EXPECT_NEAR(C_expect(i, j).imag(), C_actual(i, j).imag(), 1e-7)
                << "Im(" << i << "," << j << ")";
        }
    }
}

What's wrong with this? Well, I have to manually iterate through all indices of the matrix, and then compare the real-part and imaginary-part individually. I would much prefer something along the lines of Google Mock's ElementsAreArray matcher. E.g.

EXPECT_THAT(C_actual, ElementsAreArray(C_expect));
// or
EXPECT_THAT(C_actual, Pointwise(MyComplexNear(1e-7), C_expect));

Unfortunately, the built-in capabilities of Google Mock only seem to work on 1-dimensional C-style, or STL-type containers. Furthermore, I need an approximate comparison for the complex values of my matrix.

My question: Do you know if (and how) it is possible to teach Google Mock to iterate over multiple dimensions, and compare complex floating point numbers to approximate equality?

Please note, that I cannot just handle the data-pointers as C-style arrays, because the storage layout might differ between C_expect, and C_actual. Also, in reality, the matrices are larger than just 2x2 matrices. I.e. some sort of loop is definitely necessary.

Chavis answered 2/8, 2014 at 12:22 Comment(0)
E
22

Why not use the isApprox or isMuchSmallerThan member functions of Eigen Matrix types?

The documentation of these above functions are available here

So for most cases ASSERT_TRUE(C_actual.isApprox(C_expect)); is what you need. You can also provide a precision parameter as the second arguement to isApprox.

Endoparasite answered 4/8, 2014 at 20:44 Comment(3)
Thanks for your answer. Yes, in some cases this would be a good alternative. Unfortunately, in some cases, I would need to know which component is the offending one. But, for whenever this is not the case the above is a good solution.Chavis
Using MATCHER_P2 it is actually possible to turn isApprox into a GMock matcher. The printing of the Eigen objects poses a problem, because the Google people decided it would be a good idea to default to a hex-dump if there is no explicit overload to operator<< or PrintTo. So, the templated operator<< inside Eigen doesn't count. Hence, you will have to provide an overload for every single Eigen type. But that is a different issue...Chavis
I posted another question about the PrintTo issue here.Chavis
D
9

EXPECT_PRED2 from GoogleTest can be used for this.

Under C++11 using a lambda works fine but looks unseemly:

  ASSERT_PRED2([](const MatrixXf &lhs, const MatrixXf &rhs) {
                  return lhs.isApprox(rhs, 1e-4);
               },
               C_expect, C_actual);

If that fails, you get a print-out of the input arguments.

Instead of using a lambda, a normal predicate function can be defined like this:

bool MatrixEquality(const MatrixXf &lhs, const MatrixXf &rhs) {
  return lhs.isApprox(rhs, 1e-4);
}

TEST(Eigen, MatrixMultiplication) {
  ...

  ASSERT_PRED2(MatrixEquality, C_expected, C_actual);
}

The later version also works on pre-C++11.

Distributary answered 30/8, 2016 at 23:57 Comment(0)
C
4

A simplified solution would be to compare the norm of the difference with some epsilon, i.e.

(C_expect - C_actual).norm() < 1e-6 

In a vector space || X - Y || == 0 if and only if X == Y, and the norm is always non-negative (real). This way, you won't have to manually do the loop and compare element-wise (of course the norm will perform more calculations in the background than simple element-wise comparisons)

PS: the Matrix::norm() implemented in Eigen is the Frobenius norm, which is computationally very fast to evaluate, see http://mathworld.wolfram.com/FrobeniusNorm.html

Cheapen answered 2/8, 2014 at 13:4 Comment(5)
Thanks for your answer. Yes, I forgot to mention this one. In my case it would be fairly important to know which component is the offending one. That's why I'm printing the i, j indices in my example code.Chavis
You can get a raw pointer to the data, MatrixXcd::data(), then use it to iterate up to MatrixXcd::size(), I cannot think of any other way. In this way you will compare 2 complex arrays.Cheapen
As mentioned in my question. That approach will get you into trouble if the storage order differs.Chavis
But you can test for the storage order, I think there is something like isRowMajor... Of course, for a general storage order you are out of luck and have to probably define your own mapping between indexes in the array and corresponding ones in the matrix. Maybe use Map to change the storage order at runtime, then get the data()? eigen.tuxfamily.org/dox/group__TutorialMapClass.htmlCheapen
Hmm, I don't think that Map can be used that way. it would simply force a different storage order on the matrix, which would be like taking the transpose in my case. But, I think I could enforce a certain storage order by copying into a matrix with the expected storage order.Chavis

© 2022 - 2024 — McMap. All rights reserved.