SVD Jacobi method two side cpp code

运行:

./a.out   7

编译:

g++   ./hello_svd_jacobi.cpp

#include <iostream>
#include <cmath>
#include <cassert>
#include <iomanip>
#include <cstdlib>
#include <chrono>
#include <random>
#include <algorithm>
#include <vector>
#include <array>

using std::cout;
using std::cerr;
using std::endl;
using std::setprecision;
using std::vector;
using std::array;



#include<cassert>


namespace matrix_alloc_jpd {

/// @brief  Allocate a 2-dimensional array.  (Uses row-major order.)
template<typename Entry>
void Alloc2D(size_t nrows,          //!< size of the array (number of rows)
             size_t ncols,          //!< size of the array (number of columns)
             Entry ***paaX          //!< pointer to a 2D C-style array
             );

/// @brief  Deallocate arrays that were created using Alloc2D().
template<typename Entry>
void Dealloc2D(Entry ***paaX        //!< pointer to a 2D C-style array
               );



// ---- IMPLEMENTATION ----


template<typename Entry>
void Alloc2D(size_t nrows,          // size of the array (number of rows)
             size_t ncols,          // size of the array (number of columns)
             Entry ***paaX)         // pointer to a 2D C-style array
{
  assert(paaX);
  *paaX = new Entry* [nrows];  //conventional 2D C array (pointer-to-pointer)
  (*paaX)[0] = new Entry [nrows * ncols];  // 1D C array (contiguous memory)
  for(size_t iy=0; iy<nrows; iy++)
    (*paaX)[iy] = (*paaX)[0] + iy*ncols;
  // The caller can access the contents using (*paaX)[i][j]
}

template<typename Entry>
void Dealloc2D(Entry ***paaX)       // pointer to a 2D C-style array
{
  if (paaX && *paaX) {
    delete [] (*paaX)[0];
    delete [] (*paaX);
    *paaX = nullptr;
  }
}

} // namespace matrix_alloc_jpd

//


#include <algorithm>
#include <cmath>
//#include <cassert>

namespace jacobi_pd {

using namespace matrix_alloc_jpd;

/// @class Jacobi
/// @brief Calculate the eigenvalues and eigevectors of a symmetric matrix
///        using the Jacobi eigenvalue algorithm.
/// @note  The "Vector" and "Matrix" type arguments can be any 
///        C or C++ object that support indexing, including pointers or vectors.

template<typename Scalar,
         typename Vector,
         typename Matrix,
         typename ConstMatrix=Matrix>
class Jacobi
{
  int n;            //!< the size of the matrix
  Scalar **M;       //!< local copy of the matrix being analyzed
  // Precomputed cosine, sine, and tangent of the most recent rotation angle:
  Scalar c;         //!< = cos(θ)
  Scalar s;         //!< = sin(θ)
  Scalar t;         //!< = tan(θ),  (note |t|<=1)
  int *max_idx_row; //!< for row i, the index j of the maximum element where j>i

public:

  /// @brief  Specify the size of the matrices you want to diagonalize later.
  /// @param n  the size (ie. number of rows) of the square matrix
  void SetSize(int n);

  Jacobi(int n = 0) {
    Init();
    SetSize(n);
  }

  ~Jacobi() {
    Dealloc();
  }

  // @typedef choose the criteria for sorting eigenvalues and eigenvectors
  typedef enum eSortCriteria {
    DO_NOT_SORT,
    SORT_DECREASING_EVALS,
    SORT_INCREASING_EVALS,
    SORT_DECREASING_ABS_EVALS,
    SORT_INCREASING_ABS_EVALS
  } SortCriteria;

  /// @brief Calculate all the eigenvalues and eigevectors of a symmetric matrix
  ///        using the Jacobi eigenvalue algorithm:
  ///        https://en.wikipedia.org/wiki/Jacobi_eigenvalue_algorithm
  /// @returns The number of Jacobi iterations attempted, which should be > 0.
  ///          If the return value is not strictly > 0 then convergence failed.
  /// @note  To reduce the computation time further, set calc_evecs=false.
  int
  Diagonalize(ConstMatrix mat, //!< the matrix you wish to diagonalize (size n)
              Vector eval,   //!< store the eigenvalues here
              Matrix evec,   //!< store the eigenvectors here (in rows)
              SortCriteria sort_criteria=SORT_DECREASING_EVALS,//!<sort results?
              bool calc_evecs=true,      //!< calculate the eigenvectors?
              int max_num_sweeps = 50);  //!< limit the number of iterations

private:

  /// @brief Calculate the components of a rotation matrix which performs a
  ///        rotation in the i,j plane by an angle (θ) causing M[i][j]=0.
  ///        The resulting parameters will be stored in this->c, this->s, and
  ///        this->t (which store cos(θ), sin(θ), and tan(θ), respectively).
  void CalcRot(Scalar const *const *M,   //!< matrix
               int i,      //!< row index
               int j);     //!< column index

  /// @brief Apply the (previously calculated) rotation matrix to matrix M
  ///        by multiplying it on both sides (a "similarity transform").
  ///        (To save time, only the elements in the upper-right-triangular
  ///         region of the matrix are updated.  It is assumed that i < j.)
  void ApplyRot(Scalar **M,  //!< matrix
                int i,     //!< row index
                int j);    //!< column index

  /// @brief Multiply matrix E on the left by the (previously calculated)
  ///        rotation matrix.
  void ApplyRotLeft(Matrix E,  //!< matrix
                    int i,     //!< row index
                    int j);    //!< column index

  ///@brief Find the off-diagonal index in row i whose absolute value is largest
  int MaxEntryRow(Scalar const *const *M, int i) const;

  /// @brief Find the indices (i_max, j_max) marking the location of the
  ///        entry in the matrix with the largest absolute value.  This
  ///        uses the max_idx_row[] array to find the answer in O(n) time.
  /// @returns This function does not return a value.  However after it is
  ///          invoked, the location of the largest matrix element will be
  ///          stored in the i_max and j_max arguments.
  void MaxEntry(Scalar const *const *M, int& i_max, int& j_max) const;

  // @brief Sort the rows in M according to the numbers in v (also sorted)
  void SortRows(Vector v, //!< vector containing the keys used for sorting
                Matrix M, //!< matrix whose rows will be sorted according to v
                int n,    //!< size of the vector and matrix
                SortCriteria s=SORT_DECREASING_EVALS //!< sort decreasing order?
                ) const;

  // memory management:
  void Alloc(int N);
  void Init();
  void Dealloc();

public:
  // memory management: copy and move constructor, swap, and assignment operator
  Jacobi(const Jacobi<Scalar, Vector, Matrix, ConstMatrix>& source);
  Jacobi(Jacobi<Scalar, Vector, Matrix, ConstMatrix>&& other);
  void swap(Jacobi<Scalar, Vector, Matrix, ConstMatrix> &other) noexcept;
  Jacobi<Scalar, Vector, Matrix, ConstMatrix>& operator = (Jacobi<Scalar, Vector, Matrix, ConstMatrix> source);

}; // class Jacobi





// -------------- IMPLEMENTATION --------------



template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
int Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Diagonalize(ConstMatrix mat,    // the matrix you wish to diagonalize (size n)
            Vector eval,        // store the eigenvalues here
            Matrix evec,        // store the eigenvectors here (in rows)
            SortCriteria sort_criteria, // sort results?
            bool calc_evec,     // calculate the eigenvectors?
            int max_num_sweeps) // limit the number of iterations ("sweeps")
{
  // -- Initialization --
  for (int i = 0; i < n; i++)
    for (int j = i; j < n; j++)          //copy mat[][] into M[][]
      M[i][j] = mat[i][j];               //(M[][] is a local copy we can modify)

  if (calc_evec)
    for (int i = 0; i < n; i++)
      for (int j = 0; j < n; j++)
        evec[i][j] = (i==j) ? 1.0 : 0.0; //Set evec equal to the identity matrix

  for (int i = 0; i < n-1; i++)          //Initialize the "max_idx_row[]" array 
    max_idx_row[i] = MaxEntryRow(M, i);  //(which is needed by MaxEntry())

  // -- Iteration --
  int n_iters;
  int max_num_iters = max_num_sweeps*n*(n-1)/2; //"sweep" = n*(n-1)/2 iters
  for (n_iters=1; n_iters <= max_num_iters; n_iters++) {
    int i,j;
    MaxEntry(M, i, j); // Find the maximum entry in the matrix. Store in i,j

    // If M[i][j] is small compared to M[i][i] and M[j][j], set it to 0.
    if ((M[i][i] + M[i][j] == M[i][i]) && (M[j][j] + M[i][j] == M[j][j])) {
      M[i][j] = 0.0;
      max_idx_row[i] = MaxEntryRow(M,i); //update max_idx_row[i]
    }

    if (M[i][j] == 0.0)
      break;

    // Otherwise, apply a rotation to make M[i][j] = 0
    CalcRot(M, i, j);  // Calculate the parameters of the rotation matrix.
    ApplyRot(M, i, j); // Apply this rotation to the M matrix.
    if (calc_evec)     // Optional: If the caller wants the eigenvectors, then
      ApplyRotLeft(evec,i,j); // apply the rotation to the eigenvector matrix

  } //for (int n_iters=0; n_iters < max_num_iters; n_iters++)

  // -- Post-processing --
  for (int i = 0; i < n; i++)
    eval[i] = M[i][i];

  // Optional: Sort results by eigenvalue.
  SortRows(eval, evec, n, sort_criteria);

  if ((n_iters > max_num_iters) && (n>1))   // If we exceeded max_num_iters,
    return 0;                               // indicate an error occured.

  //assert(n_iters > 0);
  return n_iters;
}


/// @brief Calculate the components of a rotation matrix which performs a
///        rotation in the i,j plane by an angle (θ) that (when multiplied on
///        both sides) will zero the ij'th element of M, so that afterwards
///        M[i][j] = 0.  The results will be stored in c, s, and t
///        (which store cos(θ), sin(θ), and tan(θ), respectively).

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
CalcRot(Scalar const *const *M,    // matrix
        int i,       // row index
        int j)       // column index
{
  t = 1.0; // = tan(θ)
  Scalar M_jj_ii = (M[j][j] - M[i][i]);
  if (M_jj_ii != 0.0) {
    // kappa = (M[j][j] - M[i][i]) / (2*M[i][j])
    Scalar kappa = M_jj_ii;
    t = 0.0;
    Scalar M_ij = M[i][j];
    if (M_ij != 0.0) {
      kappa /= (2.0*M_ij);
      // t satisfies: t^2 + 2*t*kappa - 1 = 0
      // (choose the root which has the smaller absolute value)
      t = 1.0 / (std::sqrt(1 + kappa*kappa) + std::abs(kappa));
      if (kappa < 0.0)
        t = -t;
    }
  }
  //assert(std::abs(t) <= 1.0);
  c = 1.0 / std::sqrt(1 + t*t);
  s = c*t;
}

/// brief  Perform a similarity transformation by multiplying matrix M on both
///         sides by a rotation matrix (and its transpose) to eliminate M[i][j].
/// details This rotation matrix performs a rotation in the i,j plane by
///         angle θ.  This function assumes that c=cos(θ). s=sin(θ), t=tan(θ)
///         have been calculated in advance (using the CalcRot() function).
///         It also assumes that i<j.  The max_idx_row[] array is also updated.
///         To save time, since the matrix is symmetric, the elements
///         below the diagonal (ie. M[u][v] where u>v) are not computed.
/// verbatim
///   M' = R^T * M * R
/// where R the rotation in the i,j plane and ^T denotes the transpose.
///                 i         j
///       _                             _
///      |  1                            | 
///      |    .                          |
///      |      .                        |
///      |        1                      |
///      |          c   ...   s          |
///      |          .  .      .          |
/// R  = |          .    1    .          |
///      |          .      .  .          |
///      |          -s  ...   c          |
///      |                      1        |
///      |                        .      |
///      |                          .    |
///      |_                           1 _|
/// endverbatim
///
/// Let M' denote the matrix M after multiplication by R^T and R.
/// The components of M' are:
///
/// verbatim
///   M'_uv =  Σ_w  Σ_z   R_wu * M_wz * R_zv
/// endverbatim
///
/// Note that a the rotation at location i,j will modify all of the matrix
/// elements containing at least one index which is either i or j
/// such as: M[w][i], M[i][w], M[w][j], M[j][w].
/// Check and see whether these modified matrix elements exceed the 
/// corresponding values in max_idx_row[] array for that row.
/// If so, then update max_idx_row for that row.
/// This is somewhat complicated by the fact that we must only consider
/// matrix elements in the upper-right triangle strictly above the diagonal.
/// (ie. matrix elements whose second index is > the first index).
/// The modified elements we must consider are marked with an "X" below:
///
/// @verbatim
///                 i         j
///       _                             _
///      |  .       X         X          | 
///      |    .     X         X          |
///      |      .   X         X          |
///      |        . X         X          |
///      |          X X X X X 0 X X X X  |  i
///      |            .       X          |
///      |              .     X          |
/// M  = |                .   X          |
///      |                  . X          |
///      |                    X X X X X  |  j
///      |                      .        |
///      |                        .      |
///      |                          .    |
///      |_                           . _|
/// @endverbatim

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
ApplyRot(Scalar **M,  // matrix
         int i,     // row index
         int j)     // column index
{
  // Recall that:
  // c = cos(θ)
  // s = sin(θ)
  // t = tan(θ) (which should be <= 1.0)

  // Compute the diagonal elements of M which have changed:
  M[i][i] -= t * M[i][j];
  M[j][j] += t * M[i][j];
  // Note: This is algebraically equivalent to:
  // M[i][i] = c*c*M[i][i] + s*s*M[j][j] - 2*s*c*M[i][j]
  // M[j][j] = s*s*M[i][i] + c*c*M[j][j] + 2*s*c*M[i][j]

  //Update the off-diagonal elements of M which will change (above the diagonal)

  //assert(i < j);

  M[i][j] = 0.0;

  //compute M[w][i] and M[i][w] for all w!=i,considering above-diagonal elements
  for (int w=0; w < i; w++) {        // 0 <= w <  i  <  j < n
    M[i][w] = M[w][i]; //backup the previous value. store below diagonal (i>w)
    M[w][i] = c*M[w][i] - s*M[w][j]; //M[w][i], M[w][j] from previous iteration
    if (i == max_idx_row[w]) max_idx_row[w] = MaxEntryRow(M, w);
    else if (std::abs(M[w][i])>std::abs(M[w][max_idx_row[w]])) max_idx_row[w]=i;
    //assert(max_idx_row[w] == MaxEntryRow(M, w));
  }
  for (int w=i+1; w < j; w++) {      // 0 <= i <  w  <  j < n
    M[w][i] = M[i][w]; //backup the previous value. store below diagonal (w>i)
    M[i][w] = c*M[i][w] - s*M[w][j]; //M[i][w], M[w][j] from previous iteration
  }
  for (int w=j+1; w < n; w++) {      // 0 <= i < j+1 <= w < n
    M[w][i] = M[i][w]; //backup the previous value. store below diagonal (w>i)
    M[i][w] = c*M[i][w] - s*M[j][w]; //M[i][w], M[j][w] from previous iteration
  }

  // now that we're done modifying row i, we can update max_idx_row[i]
  max_idx_row[i] = MaxEntryRow(M, i);

  //compute M[w][j] and M[j][w] for all w!=j,considering above-diagonal elements
  for (int w=0; w < i; w++) {        // 0 <=  w  <  i <  j < n
    M[w][j] = s*M[i][w] + c*M[w][j]; //M[i][w], M[w][j] from previous iteration
    if (j == max_idx_row[w]) max_idx_row[w] = MaxEntryRow(M, w);
    else if (std::abs(M[w][j])>std::abs(M[w][max_idx_row[w]])) max_idx_row[w]=j;
    //assert(max_idx_row[w] == MaxEntryRow(M, w));
  }
  for (int w=i+1; w < j; w++) {      // 0 <= i+1 <= w <  j < n
    M[w][j] = s*M[w][i] + c*M[w][j]; //M[w][i], M[w][j] from previous iteration
    if (j == max_idx_row[w]) max_idx_row[w] = MaxEntryRow(M, w);
    else if (std::abs(M[w][j])>std::abs(M[w][max_idx_row[w]])) max_idx_row[w]=j;
    //assert(max_idx_row[w] == MaxEntryRow(M, w));
  }
  for (int w=j+1; w < n; w++) {      // 0 <=  i  <  j <  w < n
    M[j][w] = s*M[w][i] + c*M[j][w]; //M[w][i], M[j][w] from previous iteration
  }
  // now that we're done modifying row j, we can update max_idx_row[j]
  max_idx_row[j] = MaxEntryRow(M, j);

} //Jacobi::ApplyRot()




///@brief Multiply matrix M on the LEFT side by a transposed rotation matrix R^T
///       This matrix performs a rotation in the i,j plane by angle θ  (where
///       the arguments "s" and "c" refer to cos(θ) and sin(θ), respectively).
/// @verbatim
///   E'_uv = Σ_w  R_wu * E_wv
/// @endverbatim

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
ApplyRotLeft(Matrix E,  // matrix
             int i,     // row index
             int j)     // column index
{
  // Recall that c = cos(θ) and s = sin(θ)
  for (int v = 0; v < n; v++) {
    Scalar Eiv = E[i][v]; //backup E[i][v]
    E[i][v] = c*E[i][v] - s*E[j][v];
    E[j][v] = s*Eiv     + c*E[j][v];
  }
}



template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
int Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
MaxEntryRow(Scalar const *const *M, int i) const {
  int j_max = i+1;
  for(int j = i+2; j < n; j++)
    if (std::abs(M[i][j]) > std::abs(M[i][j_max]))
      j_max = j;
  return j_max;
}



template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
MaxEntry(Scalar const *const *M, int& i_max, int& j_max) const {
  // find the maximum entry in the matrix M in O(n) time
  i_max = 0;
  j_max = max_idx_row[i_max];
  Scalar max_entry = std::abs(M[i_max][j_max]);
  int nm1 = n-1;
  for (int i=1; i < nm1; i++) {
    int j = max_idx_row[i];
    if (std::abs(M[i][j]) > max_entry) {
      max_entry = std::abs(M[i][j]);
      i_max = i;
      j_max = j;
    }
  }
  //#ifndef NDEBUG
   make sure that the maximum element really is stored at i_max, j_max
     (comment out the next loop before using. it slows down the code)
  //for (int i = 0; i < nm1; i++)
  //  for (int j = i+1; j < n; j++)
  //    assert(std::abs(M[i][j]) <= max_entry);
  //#endif
}



//Sort the rows of a matrix "evec" by the numbers contained in "eval"
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
SortRows(Vector eval, Matrix evec, int n, SortCriteria sort_criteria) const
{
  for (int i = 0; i < n-1; i++) {
    int i_max = i;
    for (int j = i+1; j < n; j++) {
      // find the "maximum" element in the array starting at position i+1
      switch (sort_criteria) {
      case SORT_DECREASING_EVALS:
        if (eval[j] > eval[i_max])
          i_max = j;
        break;
      case SORT_INCREASING_EVALS:
        if (eval[j] < eval[i_max])
          i_max = j;
        break;
      case SORT_DECREASING_ABS_EVALS:
        if (std::abs(eval[j]) > std::abs(eval[i_max]))
          i_max = j;
        break;
      case SORT_INCREASING_ABS_EVALS:
        if (std::abs(eval[j]) < std::abs(eval[i_max]))
          i_max = j;
        break;
      default:
        break;
      }
    }
    std::swap(eval[i], eval[i_max]); // sort "eval"
    for (int k = 0; k < n; k++)
      std::swap(evec[i][k], evec[i_max][k]); // sort "evec"
  }
}



template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Init() {
  n = 0;
  M = nullptr;
  max_idx_row = nullptr;
}

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
SetSize(int n) {
  Dealloc();
  Alloc(n);
}

// memory management:

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Alloc(int N) {
  n = N;
  if (n > 0) {
    max_idx_row = new int[n];
    Alloc2D(n, n, &M);
  }
}

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Dealloc() {
  if (max_idx_row) {
    delete [] max_idx_row;
    max_idx_row = nullptr;
  }
  Dealloc2D(&M);
  Init();
}

// memory management: copy and move constructor, swap, and assignment operator:

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Jacobi(const Jacobi<Scalar, Vector, Matrix, ConstMatrix>& source)
{
  Init();
  SetSize(source.n);
  //assert(n == source.n);
  // The following lines aren't really necessary, because the contents
  // of source.M and source.max_idx_row are not needed (since they are
  // overwritten every time Jacobi::Diagonalize() is invoked).
  std::copy(source.max_idx_row,
            source.max_idx_row + n,
            max_idx_row);
  for (int i = 0; i < n; i++)
    std::copy(source.M[i],
              source.M[i] + n,
              M[i]);
}

template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
swap(Jacobi<Scalar, Vector, Matrix, ConstMatrix> &other) noexcept {
  std::swap(n, other.n);
  std::swap(max_idx_row, other.max_idx_row);
  std::swap(M, other.M);
}

// Move constructor (C++11)
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Jacobi(Jacobi<Scalar, Vector, Matrix, ConstMatrix>&& other)
  : Jacobi()  // delegate to the default constructor (which invokes Init())
{
  this->swap(other);
}

// Using the "copy-swap" idiom for the assignment operator
// The assignment operator below implements both copy and move assignments.
// It accepts its argument by value, which invokes the appropriate
// (copy or move) constructor.
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
Jacobi<Scalar, Vector, Matrix, ConstMatrix>&
Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
operator = (Jacobi<Scalar, Vector, Matrix, ConstMatrix> source) {
  this->swap(source);
  return *this;
}

} // namespace jacobi





// THIS FILE USED TO BE EASY TO READ until I added "#if defined" statements.
// (They were added to test for many different kinds of array formats.)


using namespace matrix_alloc_jpd;
using namespace jacobi_pd;


// This code works with various types of C++ matrices (for example,
// double **, vector<vector<double>> array<array<double,5>,5>).
// I use "#if defined" statements to test different matrix types.
// For some of these (eg. array<array<double,5>,5>), the size of the matrix
// must be known at compile time.  I specify that size now.
#if defined USE_ARRAY_OF_ARRAYS
const int NF=5;  //(the array size must be known at compile time)
#elif defined USE_C_FIXED_SIZE_ARRAYS
const int NF=5;  //(the array size must be known at compile time)
#endif


// @brief  Are two numbers "similar"?
template<typename Scalar>
inline static bool Similar(Scalar a, Scalar b,
                           Scalar eps=1.0e-06,
                           Scalar ratio=1.0e-06,
                           Scalar ratio_denom=1.0)
{
  return ((std::abs(a-b)<=std::abs(eps))
          ||
          (std::abs(ratio_denom)*std::abs(a-b)
           <=
           std::abs(ratio)*0.5*(std::abs(a)+std::abs(b))));
}

/// @brief  Are two vectors (containing n numbers) similar?
template<typename Scalar, typename Vector>
inline static bool SimilarVec(Vector a, Vector b, int n,
                              Scalar eps=1.0e-06,
                              Scalar ratio=1.0e-06,
                              Scalar ratio_denom=1.0)
{
  for (int i = 0; i < n; i++)
    if (not Similar(a[i], b[i], eps, ratio, ratio_denom))
      return false;
  return true;
}

/// @brief  Are two vectors (or their reflections) similar?
template<typename Scalar, typename Vector>
inline static bool SimilarVecUnsigned(Vector a, Vector b, int n,
                                      Scalar eps=1.0e-06,
                                      Scalar ratio=1.0e-06,
                                      Scalar ratio_denom=1.0)
{
  if (SimilarVec(a, b, n, eps))
    return true;
  else {
    for (int i = 0; i < n; i++)
      if (not Similar(a[i], -b[i], eps, ratio, ratio_denom))
        return false;
    return true;
  }
}


/// @brief  Multiply two matrices A and B, store the result in C. (C = AB).

template<typename Matrix, typename ConstMatrix>
void mmult(ConstMatrix A, //<! input array
           ConstMatrix B, //<! input array
           Matrix C,      //<! store result here
           int m,      //<! number of rows of A
           int n=0,    //<! optional: number of columns of B (=m by default)
           int K=0     //<! optional: number of columns of A = num rows of B (=m by default)
           )
{
  if (n == 0) n = m; // if not specified, then assume the matrices are square
  if (K == 0) K = m; // if not specified, then assume the matrices are square

  for (int i = 0; i < m; i++)
    for (int j = 0; j < n; j++)
      C[i][j] = 0.0;

  // perform matrix multiplication
  for (int i = 0; i < m; i++)
    for (int j = 0; j < n; j++)
      for (int k = 0; k < K; k++)
        C[i][j] += A[i][k] * B[k][j];
}



/// @brief
///Sort the rows of a matrix "evec" by the numbers contained in "eval"
///(This is a simple O(n^2) sorting method, but O(n^2) is a lower bound anyway.)
///This is the same as the Jacobi::SortRows(), but that function is private.
template<typename Scalar, typename Vector, typename Matrix>
void
SortRows(Vector eval,
         Matrix evec,
         int n,
         bool sort_decreasing=true,
         bool sort_abs=false)
{
  for (int i = 0; i < n-1; i++) {
    int i_max = i;
    for (int j = i+1; j < n; j++) {
      if (sort_decreasing) {
        if (sort_abs) { //sort by absolute value?
          if (std::abs(eval[j]) > std::abs(eval[i_max]))
            i_max = j;
        }
        else if (eval[j] > eval[i_max])
          i_max = j;
      }
      else {
        if (sort_abs) { //sort by absolute value?
          if (std::abs(eval[j]) < std::abs(eval[i_max]))
            i_max = j;
        }
        else if (eval[j] < eval[i_max])
          i_max = j;
      }
    }
    std::swap(eval[i], eval[i_max]); // sort "eval"
    for (int k = 0; k < n; k++)
      std::swap(evec[i][k], evec[i_max][k]); // sort "evec"
  }
}



/// @brief  Generate a random orthonormal n x n matrix

template<typename Scalar, typename Matrix>
void GenRandOrth(Matrix R,
                 int n,
                 std::default_random_engine &rand_generator)
{
  std::normal_distribution<Scalar> gaussian_distribution(0,1);
  std::vector<Scalar> v(n);

  for (int i = 0; i < n; i++) {
    // Generate a vector, "v", in a random direction subject to the constraint
    // that it is orthogonal to the first i-1 rows-vectors of the R matrix.
    Scalar rsq = 0.0;
    while (rsq == 0.0) {
      // Generate a vector in a random direction
      // (This works because we are using a normal (Gaussian) distribution)
      for (int j = 0; j < n; j++)
        v[j] = gaussian_distribution(rand_generator);

      //Now subtract from v, the projection of v onto the first i-1 rows of R.
      //This will produce a vector which is orthogonal to these i-1 row-vectors.
      //(They are already normalized and orthogonal to each other.)
      for (int k = 0; k < i; k++) {
        Scalar v_dot_Rk = 0.0;
          for (int j = 0; j < n; j++)
            v_dot_Rk += v[j] * R[k][j];
        for (int j = 0; j < n; j++)
          v[j] -= v_dot_Rk * R[k][j];
      }
      // check if it is linearly independent of the other vectors and non-zero
      rsq = 0.0;
      for (int j = 0; j < n; j++)
        rsq += v[j]*v[j];
    }
    // Now normalize the vector
    Scalar r_inv = 1.0 / std::sqrt(rsq);
    for (int j = 0; j < n; j++)
      v[j] *= r_inv;
    // Now copy this vector to the i'th row of R
    for (int j = 0; j < n; j++)
      R[i][j] = v[j];
  } //for (int i = 0; i < n; i++)
} //void GenRandOrth()



/// @brief  Generate a random symmetric n x n matrix, M.
/// This function generates random numbers for the eigenvalues ("evals_known")
/// as well as the eigenvectors ("evecs_known"), and uses them to generate M.
/// The "eval_magnitude_range" argument specifies the the base-10 logarithm
/// of the range of eigenvalues desired.  The "n_degeneracy" argument specifies
/// the number of repeated eigenvalues desired (if any).
/// @returns  This function does not return a value.  However after it is
///           invoked, the M matrix will be filled with random numbers.
///           Additionally, the "evals" and "evecs" arguments will contain
///           the eigenvalues and eigenvectors (one eigenvector per row)
///           of the matrix.  Later, they can be compared with the eigenvalues
///           and eigenvectors calculated by Jacobi::Diagonalize()

template <typename Scalar, typename Vector, typename Matrix>
void GenRandSymm(Matrix M,       //<! store the matrix here
                 int n,          //<! matrix size
                 Vector evals,   //<! store the eigenvalues of here
                 Matrix evecs,   //<! store the eigenvectors here
                 std::default_random_engine &rand_generator,//<! makes random numbers
                 Scalar min_eval_size=0.1, //<! minimum possible eigenvalue size
                 Scalar max_eval_size=10.0,//<! maximum possible eigenvalue size
                 int n_degeneracy=1//<!number of repeated eigevalues(1disables)
                 )
{
  assert(n_degeneracy <= n);
  std::uniform_real_distribution<Scalar> random_real01;
  std::normal_distribution<Scalar> gaussian_distribution(0, max_eval_size);
  bool use_log_uniform_distribution = false;
  if (min_eval_size > 0.0)
    use_log_uniform_distribution = true;
  #if defined USE_VECTOR_OF_VECTORS
  vector<vector<Scalar> > D(n, vector<Scalar>(n));
  vector<vector<Scalar> > tmp(n, vector<Scalar>(n));
  #elif defined USE_ARRAY_OF_ARRAYS
  array<array<Scalar, NF>, NF> D;
  array<array<Scalar, NF>, NF> tmp;
  #elif defined USE_C_FIXED_SIZE_ARRAYS
  Scalar D[NF][NF], tmp[NF][NF];
  #else
  #define USE_C_POINTER_TO_POINTERS
  Scalar  **D, **tmp;
  Alloc2D(n, n, &D);
  Alloc2D(n, n, &tmp);
  #endif

  // Randomly generate the eigenvalues
  for (int i = 0; i < n; i++) {
    if (use_log_uniform_distribution) {
      // Use a "log-uniform distribution" (a.k.a. "reciprocal distribution")
      // (This is a way to specify numbers with a precise range of magnitudes.)
      assert((min_eval_size > 0.0) && (max_eval_size > 0.0));
      Scalar log_min = std::log(std::abs(min_eval_size));
      Scalar log_max = std::log(std::abs(max_eval_size));
      Scalar log_eval = (log_min + random_real01(rand_generator)*(log_max-log_min));
      evals[i] = std::exp(log_eval);
      // also consider both positive and negative eigenvalues:
      if (random_real01(rand_generator) < 0.5)
        evals[i] = -evals[i];
    }
    else {
      evals[i] = gaussian_distribution(rand_generator);
    }
  }

  // Does the user want us to force some of the eigenvalues to be the same?
  if (n_degeneracy > 1) {
    int *permutation = new int[n]; //a random permutation from 0...n-1
    for (int i = 0; i < n; i++)
      permutation[i] = i;
    std::shuffle(permutation, permutation+n, rand_generator);
    for (int i = 1; i < n_degeneracy; i++) //set the first n_degeneracy to same
      evals[permutation[i]] = evals[permutation[0]];
    delete [] permutation;
  }

  // D is a diagonal matrix whose diagonal elements are the eigenvalues
  for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++)
      D[i][j] = ((i == j) ? evals[i] : 0.0);

  // Now randomly generate the (transpose of) the "evecs" matrix
  GenRandOrth<Scalar, Matrix>(evecs, n, rand_generator); //(will transpose it later)

  // Construct the test matrix, M, where M = Rt * D * R

  // Original code:
  //mmult(evecs, D, tmp, n);  // <--> tmp = Rt * D
  // Unfortunately, C++ guesses the types incorrectly.  Must manually specify:
  // #ifdefs making the code ugly again:
  #if defined USE_VECTOR_OF_VECTORS
  mmult<vector<vector<Scalar> >&, const vector<vector<Scalar> >&>
  #elif defined USE_ARRAY_OF_ARRAYS
  mmult<array<array<Scalar,NF>,NF>&, const array<array<Scalar,NF>,NF>&>
  #elif defined USE_C_FIXED_SIZE_ARRAYS
  mmult<Scalar (*)[NF], Scalar (*)[NF]>
  #else
  mmult<Scalar**, Scalar const *const *>
  #endif
       (evecs, D, tmp, n);

  for (int i = 0; i < n-1; i++)
    for (int j = i+1; j < n; j++)
      std::swap(evecs[i][j], evecs[j][i]); //transpose "evecs"

  // Original code:
  //mmult(tmp, evecs, M, n);
  // Unfortunately, C++ guesses the types incorrectly.  Must manually specify:
  // #ifdefs making the code ugly again:
  #if defined USE_VECTOR_OF_VECTORS
  mmult<vector<vector<Scalar> >&, const vector<vector<Scalar> >&>
  #elif defined USE_ARRAY_OF_ARRAYS
  mmult<array<array<Scalar,NF>,NF>&, const array<array<Scalar,NF>,NF>&>
  #elif defined USE_C_FIXED_SIZE_ARRAYS
  mmult<Scalar (*)[NF], Scalar (*)[NF]>
  #else
  mmult<Scalar**, Scalar const *const *>
  #endif
       (tmp, evecs, M, n);
  //at this point M = Rt*D*R (where "R"="evecs")

  #if defined USE_C_POINTER_TO_POINTERS
  Dealloc2D(&D);
  Dealloc2D(&tmp);
  #endif
} // GenRandSymm()



template <typename Scalar>
void TestJacobi(int n, //<! matrix size
                int n_matrices=100, //<! number of matrices to test
                Scalar min_eval_size=0.1,  //<! minimum possible eigenvalue sizw
                Scalar max_eval_size=10.0, //<! maximum possible eigenvalue size
                int n_tests_per_matrix=1, //<! repeat test for benchmarking?

                int n_degeneracy=1, //<! repeated eigenvalues?
                unsigned seed=0, //<! random seed (if 0 then use the clock)
                Scalar eps=1.0e-06
                )
{
  bool test_code_coverage = false;
  if (n_tests_per_matrix < 1) {
    cout << "-- Testing code-coverage --" << endl;
    test_code_coverage = true;
    n_tests_per_matrix = 1;
  }
  cout << endl << "-- Diagonalization test (real symmetric)  --" << endl;

  // construct a random generator engine using a time-based seed:

  if (seed == 0) // if the caller did not specify a seed, use the system clock
    seed = std::chrono::system_clock::now().time_since_epoch().count();
  std::default_random_engine rand_generator(seed);



  // Create an instance of the Jacobi diagonalizer, and allocate the matrix
  // we will test it on, as well as the arrays that will store the resulting
  // eigenvalues and eigenvectors.
  // The way we do this depends on what version of the code we are using.
  // This is controlled by "#if defined" statements.
  
  #if defined USE_VECTOR_OF_VECTORS

  Jacobi<Scalar,
         vector<Scalar>&,
         vector<vector<Scalar> >&,
         const vector<vector<Scalar> >& > ecalc(n);

  // allocate the matrix, eigenvalues, eigenvectors
  vector<vector<Scalar> > M(n, vector<Scalar>(n));
  vector<vector<Scalar> > evecs(n, vector<Scalar>(n));
  vector<vector<Scalar> > evecs_known(n, vector<Scalar>(n));
  vector<Scalar> evals(n);
  vector<Scalar> evals_known(n);
  vector<Scalar> test_evec(n);

  #elif defined USE_ARRAY_OF_ARRAYS

  n = NF;
  cout << "Testing std::array (fixed size).\n"
    "(Ignoring first argument, and setting matrix size to " << n << ")" << endl;

  Jacobi<Scalar,
         array<Scalar, NF>&,
         array<array<Scalar, NF>, NF>&,
         const array<array<Scalar, NF>, NF>&> ecalc(n);

  // allocate the matrix, eigenvalues, eigenvectors
  array<array<Scalar, NF>, NF> M;
  array<array<Scalar, NF>, NF> evecs;
  array<array<Scalar, NF>, NF> evecs_known;
  array<Scalar, NF> evals;
  array<Scalar, NF> evals_known;
  array<Scalar, NF> test_evec;

  #elif defined USE_C_FIXED_SIZE_ARRAYS

  n = NF;
  cout << "Testing C fixed size arrays.\n"
    "(Ignoring first argument, and setting matrix size to " << n << ")" << endl;
  Jacobi<Scalar,
         Scalar*,
         Scalar (*)[NF],
         Scalar const (*)[NF]> ecalc(n);

  // allocate the matrix, eigenvalues, eigenvectors
  Scalar M[NF][NF];
  Scalar evecs[NF][NF];
  Scalar evecs_known[NF][NF];
  Scalar evals[NF];
  Scalar evals_known[NF];
  Scalar test_evec[NF];

  #else
 
  #define USE_C_POINTER_TO_POINTERS

  // Note: Normally, you would just use this to instantiate Jacobi:
  // Jacobi<Scalar, Scalar*, Scalar**, Scalar const*const*> ecalc(n);
  // -------------------------
  // ..but since Jacobi manages its own memory using new and delete, I also want
  // to test that the copy constructors, copy operators, and destructors work.
  // The following lines do this:
  Jacobi<Scalar, Scalar*, Scalar**, Scalar const*const*> ecalc_test_mem1;
  ecalc_test_mem1.SetSize(n);
  Jacobi<Scalar, Scalar*, Scalar**, Scalar const*const*> ecalc_test_mem2(2);
  // test the = operator
  ecalc_test_mem2 = ecalc_test_mem1;
  // test the copy constructor
  Jacobi<Scalar, Scalar*, Scalar**, Scalar const*const*> ecalc(ecalc_test_mem2);
  // allocate the matrix, eigenvalues, eigenvectors
  Scalar **M, **evecs, **evecs_known;
  Alloc2D(n, n, &M);
  Alloc2D(n, n, &evecs);
  Alloc2D(n, n, &evecs_known);
  Scalar *evals = new Scalar[n];
  Scalar *evals_known = new Scalar[n];
  Scalar *test_evec = new Scalar[n];

  #endif


  // --------------------------------------------------------------------
  // Now, generate random matrices and test Jacobi::Diagonalize() on them.
  // --------------------------------------------------------------------

  for(int imat = 0; imat < n_matrices; imat++) {

    // Create a randomly generated symmetric matrix.
    //This function generates random numbers for the eigenvalues ("evals_known")
    //as well as the eigenvectors ("evecs_known"), and uses them to generate M.

    #if defined USE_VECTOR_OF_VECTORS
    GenRandSymm<Scalar, vector<Scalar>&, vector<vector<Scalar> >&>
    #elif defined USE_ARRAY_OF_ARRAYS
    GenRandSymm<Scalar, array<Scalar,NF>&, array<array<Scalar,NF>,NF>&>
    #elif defined USE_C_FIXED_SIZE_ARRAYS
    GenRandSymm<Scalar, Scalar*, Scalar (*)[NF]>
    #else
    GenRandSymm<Scalar, Scalar*, Scalar**>
    #endif
               (M,
                n,
                evals_known,
                evecs_known,
                rand_generator,
                min_eval_size,
                max_eval_size,
                n_degeneracy);

    // Sort the matrix evals and eigenvector rows:
    // Original code:
    //SortRows<Scalar>(evals_known, evecs_known, n);
    // Unfortunately, C++ guesses the types incorrectly. Must use #ifdefs again:
    #if defined USE_VECTOR_OF_VECTORS
    SortRows<Scalar, vector<Scalar>&, vector<vector<Scalar> >&>
    #elif defined USE_ARRAY_OF_ARRAYS
    SortRows<Scalar, array<Scalar,NF>&, array<array<Scalar,NF>,NF>&>
    #elif defined USE_C_FIXED_SIZE_ARRAYS
    SortRows<Scalar, Scalar*, Scalar (*)[NF]>
    #else
    SortRows<Scalar, Scalar*, Scalar**>
    #endif
            (evals_known, evecs_known, n);


    if (n_matrices == 1) {
      cout << "Matrix:\n";
      for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
          cout << M[i][j] << " ";
        cout << "\n";
      }
      cout << "Eigenvalues (after sorting):\n";
      for (int i = 0; i < n; i++)
        cout << evals_known[i] << " ";
      cout << "\n";
      cout << "Eigenvectors (rows) which are known in advance:\n";
      for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
          cout << evecs_known[i][j] << " ";
        cout << "\n";
      }
      cout << "  (The eigenvectors calculated by Jacobi::Diagonalize() should match these.)\n";
    }

    for (int i_test = 0; i_test < n_tests_per_matrix; i_test++) {

      if (test_code_coverage) {

        // test SORT_INCREASING_ABS_EVALS:
        #if defined USE_VECTOR_OF_VECTORS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 vector<Scalar>&,
                                 vector<vector<Scalar> >&,
                                 const vector<vector<Scalar> >& >::SORT_INCREASING_ABS_EVALS);
        #elif defined USE_ARRAY_OF_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 array<Scalar,NF>&,
                                 array<array<Scalar,NF>,NF>&,
                                 const array<array<Scalar,NF>,NF>&>::SORT_INCREASING_ABS_EVALS);
        #elif defined USE_C_FIXED_SIZE_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar (*)[NF],
                                 Scalar const (*)[NF]>::SORT_INCREASING_ABS_EVALS);
        #else
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar**,
                                 Scalar const*const*>::SORT_INCREASING_ABS_EVALS);
        // Also make sure the code considers a scenario where convergence fails:
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar**,
                                 Scalar const*const*>::SORT_INCREASING_ABS_EVALS,
                          true,
                          0);   //<-- set the maximum allowed iterations to 0
        #endif

        for (int i = 1; i < n; i++)
          assert(std::abs(evals[i-1])<=std::abs(evals[i]));

        // test SORT_DECREASING_ABS_EVALS:
        #if defined USE_VECTOR_OF_VECTORS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 vector<Scalar>&,
                                 vector<vector<Scalar> >&,
                                 const vector<vector<Scalar> >& >::SORT_DECREASING_ABS_EVALS);
        #elif defined USE_ARRAY_OF_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 array<Scalar,NF>&,
                                 array<array<Scalar,NF>,NF>&,
                                 const array<array<Scalar,NF>,NF>&>::SORT_DECREASING_ABS_EVALS);
        #elif defined USE_C_FIXED_SIZE_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar (*)[NF],
                                 Scalar const (*)[NF]>::SORT_DECREASING_ABS_EVALS);
        #else
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar**,
                                 Scalar const*const*>::SORT_DECREASING_ABS_EVALS);
        #endif

        for (int i = 1; i < n; i++)
          assert(std::abs(evals[i-1])>=std::abs(evals[i]));

        // test SORT_INCREASING_EVALS:
        #if defined USE_VECTOR_OF_VECTORS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 vector<Scalar>&,
                                 vector<vector<Scalar> >&,
                                 const vector<vector<Scalar> >& >::SORT_INCREASING_EVALS);
        #elif defined USE_ARRAY_OF_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 array<Scalar,NF>&,
                                 array<array<Scalar,NF>,NF>&,
                                 const array<array<Scalar,NF>,NF>&>::SORT_INCREASING_EVALS);
        #elif defined USE_C_FIXED_SIZE_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar (*)[NF],
                                 Scalar const (*)[NF]>::SORT_INCREASING_EVALS);
        #else
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar**,
                                 Scalar const*const*>::SORT_INCREASING_EVALS);
        #endif
        for (int i = 1; i < n; i++)
          assert(evals[i-1] <= evals[i]);

        // test DO_NOT_SORT
        #if defined USE_VECTOR_OF_VECTORS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 vector<Scalar>&,
                                 vector<vector<Scalar> >&,
                                 const vector<vector<Scalar> >& >::DO_NOT_SORT);
        #elif defined USE_ARRAY_OF_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 array<Scalar,NF>&,
                                 array<array<Scalar,NF>,NF>&,
                                 const array<array<Scalar,NF>,NF>&>::DO_NOT_SORT);
        #elif defined USE_C_FIXED_SIZE_ARRAYS
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar (*)[NF],
                                 Scalar const (*)[NF]>::DO_NOT_SORT);
        #else
        ecalc.Diagonalize(M,
                          evals,
                          evecs,
                          Jacobi<Scalar,
                                 Scalar*,
                                 Scalar**,
                                 Scalar const*const*>::DO_NOT_SORT);
        #endif

      } //if (test_code_coverage)


      // Now (finally) calculate the eigenvalues and eigenvectors
      int n_sweeps = ecalc.Diagonalize(M, evals, evecs);

      if ((n_matrices == 1) && (i_test == 0)) {
        cout <<"Jacobi::Diagonalize() ran for "<<n_sweeps<<" iters (sweeps).\n";
        cout << "Eigenvalues calculated by Jacobi::Diagonalize()\n";
        for (int i = 0; i < n; i++)
          cout << evals[i] << " ";
        cout << "\n";
        cout << "Eigenvectors (rows) calculated by Jacobi::Diagonalize()\n";
        for (int i = 0; i < n; i++) {
          for (int j = 0; j < n; j++)
            cout << evecs[i][j] << " ";
          cout << "\n";
        }
      }

      assert(SimilarVec(evals, evals_known, n, eps*max_eval_size, eps));
      //Check that each eigenvector satisfies Mv = λv
      // <-->  Σ_b  M[a][b]*evecs[i][b] = evals[i]*evecs[i][b]   (for all a)
      for (int i = 0; i < n; i++) {
        for (int a = 0; a < n; a++) {
          test_evec[a] = 0.0;
          for (int b = 0; b < n; b++)
            test_evec[a] += M[a][b] * evecs[i][b];
          assert(Similar(test_evec[a],
                         evals[i] * evecs[i][a],
                         eps,  // tolerance (absolute difference)
                         eps*max_eval_size, // tolerance ratio (numerator)
                         evals_known[i] // tolerance ration (denominator)
                         ));
        }
      }

    } //for (int i_test = 0; i_test < n_tests_per_matrix; i++)

  } //for(int imat = 0; imat < n_matrices; imat++) {

  #if defined USE_C_POINTER_TO_POINTERS
  Dealloc2D(&M);
  Dealloc2D(&evecs);
  Dealloc2D(&evecs_known);
  delete [] evals;
  delete [] evals_known;
  delete [] test_evec;
  #endif

} //TestJacobi()


int main(int argc, char **argv) {
  int n_size = 2;
  int n_matr = 1;
  double emin = 0.0;
  double emax = 1.0;
  int n_tests = 1;
  int n_degeneracy = 1;
  unsigned seed = 0;

  if (argc <= 1) {
    cerr <<
      "Error: This program requires at least 1 argument.\n"
      "\n"
      "Description: Run Jacobi::Diagonalize() on randomly generated matrices.\n"
      "\n"
      "Arguments: n_size [n_matr emin emax n_degeneracy n_tests seed eps]\n"
      "        n_size = the size of the matrices\n"
      "    (NOTE: The remaining arguments are optional.)\n"
      "        n_matr = the number of randomly generated matrices to test\n"
      "          emin = the smallest possible eigenvalue magnitude (eg. 1e-05)\n"
      "          emax = the largest possible eigenvalue magnitude (>0 eg. 1e+05)\n"
      "    (NOTE: If emin=0, a normal distribution is used centered at 0.\n"
      "           Otherwise a log-uniform distribution is used from emin to emax.)\n"
      "  n_degeneracy = the number of repeated eigenvalues (1 disables, default)\n"
      "       n_tests = the number of times the eigenvalues and eigenvectors\n"
      "                 are calculated for EACH matrix.  By default this is 1.\n"
      "                 (Increase this to at least 20 if you plan to use this\n"
      "                 program for benchmarking (speed testing), because the time\n"
      "                 needed for generating a random matrix is not negligible.)\n"
      "                 (IF THIS NUMBER IS 0, it will test CODE-COVERAGE instead.)\n"
      "          seed = the seed used by the random number \"rand_generator\".\n"
      "                 (If this number is 0, which is the default, the system\n"
      "                  clock is used to choose a random seed.)\n"
      "           eps = the tolerance.  The difference between eigenvalues and their\n"
      "                 true value, cannot exceed this (multiplied by the eigenvalue\n"
      "                 of maximum magnitude).  Similarly, the difference between\n"
      "                 the eigenvectors after multiplication by the matrix and by\n"
      "                 and after multiplication by the eigenvalue, cannot exceed\n"
      "                 eps*maximum_eigenvalue/eigenvalue.  The default value is\n"
      "                 1.0e-06 (which works well for double precision numbers).\n"
         << endl;
    return 1;
  }

  n_size = std::stoi(argv[1]);
  if (argc > 2)
    n_matr = std::stoi(argv[2]);
  if (argc > 3)
    emin = std::stof(argv[3]);
  if (argc > 4)
    emax = std::stof(argv[4]);
  if (argc > 5)
    n_degeneracy = std::stoi(argv[5]);
  if (argc > 6)
    n_tests = std::stoi(argv[6]);
  if (argc > 7)
    seed = std::stoi(argv[7]);
  double eps = 1.0e-06;
  if (argc > 8)
    eps = std::stof(argv[8]);

  TestJacobi(n_size, n_matr, emin, emax, n_tests, n_degeneracy, seed, eps);

  cout << "test passed\n" << endl;
  return EXIT_SUCCESS;
}






















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值