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.

273K
  • 29,503
  • 10
  • 41
  • 64
Lemming
  • 4,085
  • 3
  • 23
  • 36

3 Answers3

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.

iNFINITEi
  • 1,504
  • 16
  • 23
  • 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. – Lemming Aug 05 '14 at 07:40
  • Using [MATCHER_P2](https://code.google.com/p/googlemock/wiki/CookBook#Writing_New_Parameterized_Matchers_Quickly) 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... – Lemming Aug 05 '14 at 09:43
  • 1
    I posted another question about the `PrintTo` issue [here](http://stackoverflow.com/questions/25146997/teach-google-test-how-to-print-eigen-matrix). – Lemming Aug 05 '14 at 19:42
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.

Unapiedra
  • 15,037
  • 12
  • 64
  • 93
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

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 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. – Lemming Aug 02 '14 at 15:11
  • 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. – vsoftco Aug 02 '14 at 15:14
  • As mentioned in my question. That approach will get you into trouble if the storage order differs. – Lemming Aug 02 '14 at 15:41
  • 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()`? http://eigen.tuxfamily.org/dox/group__TutorialMapClass.html – vsoftco Aug 02 '14 at 15:44
  • 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. – Lemming Aug 02 '14 at 17:09