Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
MatrixTest.h 11.80 KiB
#ifndef MANTID_TESTMATIX__
#define MANTID_TESTMATIX__

#include <cxxtest/TestSuite.h>
#include <cmath>
#include <ostream>
#include <vector>
#include <algorithm>

#include "MantidKernel/Exception.h"
#include "MantidKernel/Matrix.h"
#include "MantidKernel/V3D.h"

#include <boost/lexical_cast.hpp>

using Mantid::Kernel::Matrix;
using Mantid::Kernel::DblMatrix;
using Mantid::Kernel::V3D;

class MatrixTest : public CxxTest::TestSuite {

public:
  void makeMatrix(Matrix<double> &A) const {
    A.setMem(3, 3);
    A[0][0] = 1.0;
    A[1][0] = 3.0;
    A[0][1] = 4.0;
    A[0][2] = 6.0;
    A[2][0] = 5.0;
    A[1][1] = 3.0;
    A[2][1] = 1.0;
    A[1][2] = 6.0;
    A[2][2] = -7.0;
    return;
  }

  /**
  Test that a matrix can be inverted
  */
  void testInvert() {
    Matrix<double> A(3, 3);

    A[0][0] = 1.0;
    A[1][0] = 3.0;
    A[0][1] = 4.0;
    A[0][2] = 6.0;
    A[2][0] = 5.0;
    A[1][1] = 3.0;
    A[2][1] = 1.0;
    A[1][2] = 6.0;
    A[2][2] = -7.0;

    TS_ASSERT_DELTA(A.Invert(), 105.0, 1e-5);
    Matrix<double> B(1, 1);
    B[0][0] = 2.;
    TS_ASSERT_DELTA(B.Invert(), 2, 1e-5);
    TS_ASSERT_DELTA(B[0][0], 0.5, 1e-5);
  }

  void testIdent() {
    Matrix<double> A(3, 3);

    A[0][0] = 1.0;
    A[1][0] = 0.0;
    A[0][1] = 0.0;
    A[0][2] = 0.0;
    A[2][0] = 0.0;
    A[1][1] = 1.0;
    A[2][1] = 0.0;
    A[1][2] = 0.0;
    A[2][2] = 1.0;

    Matrix<double> Ident(3, 3);
    TS_ASSERT_DIFFERS(Ident, A);
    Ident.identityMatrix();
    TS_ASSERT_EQUALS(Ident, A);
  }

  /** Test of equals with a user-specified tolerance */
  void test_equals() {
    Matrix<double> A(3, 3, true);
    Matrix<double> B(3, 3, true);
    B[1][1] = 1.1;
    TS_ASSERT(!A.equals(B, 0.05));
    TS_ASSERT(A.equals(B, 0.15));
  }

  void test_not_equal() {
    Matrix<double> A(3, 3, true);
    Matrix<double> B(3, 3, true);

    A[0][0] = -1.0;

    TS_ASSERT(A != B);
    TS_ASSERT(!(A == B));
  }

  /**
  Check that we can swap rows and columns
  */
  void testSwapRows() {
    Matrix<double> A(3, 3);
    makeMatrix(A);
    Matrix<double> B(A);
    A.swapRows(1, 2);
    A.swapCols(1, 2);
    TS_ASSERT_EQUALS(A[0][0], B[0][0]);
    TS_ASSERT_EQUALS(A[2][2], B[1][1]);
    // Plus all the others..
  }

  void testEigenvectors() {
    Matrix<double> Eval;
    Matrix<double> Diag;
    Matrix<double> A(3, 3); // NOTE: A must be symmetric
    A[0][0] = 1.0;
    A[1][0] = A[0][1] = 4.0;
    A[0][2] = A[2][0] = 5.0;
    A[1][1] = 3.0;
    A[2][1] = A[1][2] = 6.0;
    A[2][2] = -7.0;
    TS_ASSERT(A.Diagonalise(Eval, Diag));

    Matrix<double> MA = A * Eval;
    Matrix<double> MV = Eval * Diag;
    Eval.sortEigen(Diag);
    TS_ASSERT(Diag[0][0] < Diag[1][1]);
    TS_ASSERT(Diag[1][1] < Diag[2][2]);
    TS_ASSERT(MA == MV);

    std::vector<double> X(3);
    X[0] = Eval[0][1];
    X[1] = Eval[1][1];
    X[2] = Eval[2][1];

    std::vector<double> out = A * X;
    transform(X.begin(), X.end(), X.begin(),
              std::bind2nd(std::multiplies<double>(), Diag[1][1]));
    TS_ASSERT_DELTA(X[0], out[0], 0.0001);
    TS_ASSERT_DELTA(X[1], out[1], 0.0001);
    TS_ASSERT_DELTA(X[2], out[2], 0.0001);
  }

  /**
  Tests the diagonalisation  on a symmetric 2x2 matrix
  */
  void testDiagonalise() {
    Matrix<double> Eval;
    Matrix<double> Diag;
    Matrix<double> A(2, 2); // symmetric only
    A[0][0] = 1.0;
    A[1][0] = 3.0;
    A[0][1] = 3.0;
    A[1][1] = 4.0;
    TS_ASSERT(A.Diagonalise(Eval, Diag)); // returns 1 or 2

    Matrix<double> EvalT(Eval);
    EvalT.Transpose();
    Eval *= Diag;
    Eval *= EvalT;
    TS_ASSERT(Eval == A);
  }

  void testFromVectorThrows() {
    std::vector<double> data(5, 0);

    TSM_ASSERT_THROWS("building matrix by this construcor and data with wrong "
                      "number of elements should throw",
                      (Matrix<double>(data)), std::invalid_argument);
  }

  void test_Transpose_On_Square_Matrix_Matches_TPrime() {
    Matrix<double> A(2, 2);
    A[0][0] = 1.0;
    A[0][1] = 2.0;
    A[1][0] = 3.0;
    A[1][1] = 4.0;

    auto B = A.Tprime(); // new matrix
    TS_ASSERT_EQUALS(1.0, B[0][0]);
    TS_ASSERT_EQUALS(3.0, B[0][1]);
    TS_ASSERT_EQUALS(2.0, B[1][0]);
    TS_ASSERT_EQUALS(4.0, B[1][1]);

    A.Transpose(); // in place
    TS_ASSERT_EQUALS(1.0, A[0][0]);
    TS_ASSERT_EQUALS(3.0, A[0][1]);
    TS_ASSERT_EQUALS(2.0, A[1][0]);
    TS_ASSERT_EQUALS(4.0, A[1][1]);
  }

  void test_Transpose_On_Irregular_Matrix_Matches_TPrime() {
    Matrix<double> A(2, 3);
    A[0][0] = 1.0;
    A[0][1] = 2.0;
    A[0][2] = 3.0;
    A[1][0] = 4.0;
    A[1][1] = 5.0;
    A[1][2] = 6.0;

    auto B = A.Tprime(); // new matrix
    TS_ASSERT_EQUALS(2, B.numCols());
    TS_ASSERT_EQUALS(3, B.numRows());
    TS_ASSERT_EQUALS(1.0, B[0][0]);
    TS_ASSERT_EQUALS(4.0, B[0][1]);
    TS_ASSERT_EQUALS(2.0, B[1][0]);
    TS_ASSERT_EQUALS(5.0, B[1][1]);
    TS_ASSERT_EQUALS(3.0, B[2][0]);
    TS_ASSERT_EQUALS(6.0, B[2][1]);
  }

  void testFromVectorBuildCorrect() {
    std::vector<int> data(9, 0);
    for (int i = 0; i < 9; i++) {
      data[i] = i;
    }
    Matrix<int> myMat;
    TSM_ASSERT_THROWS_NOTHING("building matrix by this construcor and data "
                              "with correct number of elements should not "
                              "throw",
                              myMat = Matrix<int>(data));

    // and the range of the elements in the matrix is correct;
    V3D rez1 = myMat * V3D(1, 0, 0);
    V3D rez2 = myMat * V3D(0, 1, 0);
    V3D rez3 = myMat * V3D(0, 0, 1);
    TSM_ASSERT_EQUALS("The data in a matrix have to be located row-wise, so "
                      "multiplication by (1,0,0)^T selects 1-st column ",
                      true, V3D(0, 3, 6) == rez1);
    TSM_ASSERT_EQUALS("The data in a matrix have to be located row-wise, so "
                      "multiplication by (0,1,0)^T selects 2-nd column ",
                      true, V3D(1, 4, 7) == rez2);
    TSM_ASSERT_EQUALS("The data in a matrix have to be located row-wise, so "
                      "multiplication by (0,0,1)^T selects 3-rd column ",
                      true, V3D(2, 5, 8) == rez3);
  }

  void testIsRotation() {
    Matrix<double> d(3, 3, true);
    TS_ASSERT(d.isRotation());
    d[0][0] = -1;
    TS_ASSERT(!d.isRotation());
  }

  void testToRotation() {
    /*
    |1  0  0|
    |1  2  0|
    |0  0 -3|
    transforms to
    |-s-s  0|
    |-s s  0|
    |0  0 -1|
    with s=sqrt(0.5) and scaling (-sqrt(2),sqrt(2),3)
    */
    Matrix<double> d(3, 3, true);
    d[1][0] = 1.0;
    d[1][1] = 2.;
    d[2][2] = -3.;
    std::vector<double> v = d.toRotation();

    TS_ASSERT_DELTA(d[0][0], -sqrt(0.5), 1e-7);
    TS_ASSERT_DELTA(d[0][1], -sqrt(0.5), 1e-7);
    TS_ASSERT_DELTA(d[1][0], -sqrt(0.5), 1e-7);
    TS_ASSERT_DELTA(d[1][1], sqrt(0.5), 1e-7);
    TS_ASSERT_DELTA(d[2][2], -1., 1e-7);
    TS_ASSERT_DELTA(v[0], -sqrt(2.), 1e-7);
    TS_ASSERT_DELTA(v[1], sqrt(2.), 1e-7);
    TS_ASSERT_DELTA(v[2], 3., 1e-7);
  }

  void test_Input_Stream_Throws_On_Bad_Input() {
    DblMatrix rot;
    std::istringstream is;
    is.str("Matr(3,3)1,2,3,4,5,6,7,8,9");
    TS_ASSERT_THROWS(is >> rot, std::invalid_argument);
    is.str("Matrix3,3)1,2,3,4,5,6,7,8,9");
    TS_ASSERT_THROWS(is >> rot, std::invalid_argument);
    is.str("Matrix(3,31,2,3,4,5,6,7,8,9");
    TS_ASSERT_THROWS(is >> rot, std::invalid_argument);
  }

  void test_Input_Stream_On_Square_Matrix() {
    DblMatrix rot;
    std::istringstream is;
    is.str("Matrix(3,3)1,2,3,4,5,6,7,8,9");
    TS_ASSERT_THROWS_NOTHING(is >> rot);
    TS_ASSERT_EQUALS(rot.numRows(), 3);
    TS_ASSERT_EQUALS(rot.numCols(), 3);
    for (size_t i = 0; i < 3; ++i) {
      for (size_t j = 0; j < 3; ++j) {
        TS_ASSERT_EQUALS(rot[i][j],
                         static_cast<double>(i * rot.numRows() + j + 1));
      }
    }
  }

  void test_Input_Stream_On_Non_Square_Matrix() {
    DblMatrix rot;
    std::istringstream is;
    is.str("Matrix(2,4)0,1,2,3,10,11,12,13");
    TS_ASSERT_THROWS_NOTHING(is >> rot);
    TS_ASSERT_EQUALS(rot.numRows(), 2);
    TS_ASSERT_EQUALS(rot.numCols(), 4);
    for (size_t i = 0; i < 2; ++i) {
      for (size_t j = 0; j < 4; ++j) {
        if (i < 1) {
          TS_ASSERT_EQUALS(rot[i][j], static_cast<double>(i + j));
        } else {
          TS_ASSERT_EQUALS(rot[i][j], static_cast<double>(9 + i + j));
        }
      }
    }
  }

  void test_fillMatrix_With_Good_Input_Gives_Expected_Matrix() {
    DblMatrix rot;
    std::istringstream is;
    is.str("Matrix(3|3)1|2|3|4|5|6|7|8|9");
    TS_ASSERT_THROWS_NOTHING(Mantid::Kernel::fillFromStream(is, rot, '|'));

    checkMatrixHasExpectedValuesForSquareMatrixTest(rot);
  }

  void test_fillMatrix_Accepts_Any_Delimiter_Between_Number_Rows_And_Columns() {
    DblMatrix rot;
    std::istringstream is;
    is.str("Matrix(3@3)1|2|3|4|5|6|7|8|9");
    TS_ASSERT_THROWS_NOTHING(Mantid::Kernel::fillFromStream(is, rot, '|'));

    checkMatrixHasExpectedValuesForSquareMatrixTest(rot);
  }

  void test_fillMatrix_With_Mixed_Delimiters_In_Input_Values_Throws() {
    DblMatrix rot;
    std::istringstream is;
    is.str("Matrix(3|3)1|2,3|4|5|6|7|8|9");
    TS_ASSERT_THROWS(Mantid::Kernel::fillFromStream(is, rot, '|'),
                     std::invalid_argument);
  }

  void test_Construction_Non_Square_Matrix_From_Output_Stream() {
    DblMatrix ref(2, 3);
    ref[0][0] = 5;
    ref[0][1] = 10;
    ref[0][2] = 15;
    ref[1][0] = 105;
    ref[1][1] = 110;
    ref[1][2] = 115;
    std::ostringstream os;
    os << ref;
    TS_ASSERT_EQUALS(os.str(), "Matrix(2,3)5,10,15,105,110,115");
  }

  void test_Construction_Square_Matrix_From_Output_Stream() {
    DblMatrix square(2, 2);
    square[0][0] = 2;
    square[0][1] = 4;
    square[1][0] = 6;
    square[1][1] = 8;

    std::ostringstream os;
    os << square;
    TS_ASSERT_EQUALS(os.str(), "Matrix(2,2)2,4,6,8");
  }

  void test_Dump_Matrix_To_Output_Stream_With_Custom_Delimiter() {
    DblMatrix square(2, 2);
    square[0][0] = 2;
    square[0][1] = 4;
    square[1][0] = 6;
    square[1][1] = 8;

    std::ostringstream os;
    Mantid::Kernel::dumpToStream(os, square, '|');
    TS_ASSERT_EQUALS(os.str(), "Matrix(2|2)2|4|6|8");
  }

  void test_lexical_cast() {
    try {
      DblMatrix R = boost::lexical_cast<DblMatrix>("Matrix(2,2)2,4,6,8");
      TS_ASSERT_EQUALS(R.numRows(), 2);
      TS_ASSERT_EQUALS(R.numCols(), 2);
      TS_ASSERT_EQUALS(R[0][0], 2.0);
      TS_ASSERT_EQUALS(R[0][1], 4.0);
      TS_ASSERT_EQUALS(R[1][0], 6.0);
      TS_ASSERT_EQUALS(R[1][1], 8.0);
    } catch (boost::bad_lexical_cast &e) {
      TS_FAIL(e.what());
    }
  }

  void testMultiplicationWithVector() {
    DblMatrix M = boost::lexical_cast<DblMatrix>(
        "Matrix(3,3)-0.23,0.55,5.22,2.96,4.2,0.1,-1.453,3.112,-2.34");

    V3D v(2.3, 4.5, -0.45);

    V3D nv = M * v;

    // Results from octave.
    TS_ASSERT_DELTA(nv.X(), -0.403000000000000, 1e-15);
    TS_ASSERT_DELTA(nv.Y(), 25.663000000000000, 1e-15);
    TS_ASSERT_DELTA(nv.Z(), 11.715100000000003, 1e-15);

    DblMatrix M4(4, 4, true);
    TS_ASSERT_THROWS(M4.operator*(v),
                     Mantid::Kernel::Exception::MisMatch<size_t>);

    DblMatrix M23 = boost::lexical_cast<DblMatrix>(
        "Matrix(2,3)-0.23,0.55,5.22,2.96,4.2,0.1");
    TS_ASSERT_THROWS_NOTHING(M23.operator*(v));

    nv = M23 * v;

    TS_ASSERT_DELTA(nv.X(), -0.403000000000000, 1e-15);
    TS_ASSERT_DELTA(nv.Y(), 25.663000000000000, 1e-15);
    TS_ASSERT_EQUALS(nv.Z(), 0);
    DblMatrix M43 = boost::lexical_cast<DblMatrix>(
        "Matrix(4,3)-0.23,0.55,5.22,2.96,4.2,0.1,-0.23,0.55,5.22,2.96,4.2,0.1");
    TS_ASSERT_THROWS(M43.operator*(v),
                     Mantid::Kernel::Exception::MisMatch<size_t>);
  }

private:
  void checkMatrixHasExpectedValuesForSquareMatrixTest(const DblMatrix &mat) {
    TS_ASSERT_EQUALS(mat.numRows(), 3);
    TS_ASSERT_EQUALS(mat.numCols(), 3);
    for (size_t i = 0; i < 3; ++i) {
      for (size_t j = 0; j < 3; ++j) {
        TS_ASSERT_EQUALS(mat[i][j],
                         static_cast<double>(i * mat.numRows() + j + 1));
      }
    }
  }
};

#endif