bug fix fix unit tests, improve doc readability, and to prevent accidentally allocating memory on the heap. (Note: All of these changes are related to "jacobi3()". That function instantiates Jacobi without allocating memory on the heap, and this created some headaches. The original code at https://github/jewettaij/jacobi_pd does not have this feature, and the unit tests there do not test for these kinds of errors. Hopefully this commit fixes everything.)

This commit is contained in:
Andrew Jewett
2020-09-06 20:05:47 -07:00
parent fabf762fa8
commit a57a1404f3
3 changed files with 64 additions and 24 deletions

View File

@ -1286,6 +1286,3 @@ elements of a sparse matrix.)
:project: progguide :project: progguide
:members: :members:
.. doxygenstruct:: MathEigen::realTypeMap
:project: progguide
:members:

View File

@ -90,16 +90,18 @@ jacobi3(double const mat[3][3], // the 3x3 matrix you wish to diagonalize
{mat[2][0], mat[2][1], mat[2][2]} }; {mat[2][0], mat[2][1], mat[2][2]} };
double *M[3] = { &(mat_cpy[0][0]), &(mat_cpy[1][0]), &(mat_cpy[2][0]) }; double *M[3] = { &(mat_cpy[0][0]), &(mat_cpy[1][0]), &(mat_cpy[2][0]) };
int midx[3]; // (another array which is preallocated to avoid using the heap)
// variable "ecalc3" does all the work: // variable "ecalc3" does all the work:
Jacobi<double,double*,double(*)[3],double const(*)[3]> ecalc3(3,M); Jacobi<double,double*,double(*)[3],double const(*)[3]> ecalc3(3, M, midx);
int ierror = ecalc3.Diagonalize(mat, eval, evec, sort_criteria); int ierror = ecalc3.Diagonalize(mat, eval, evec, sort_criteria);
// This will store the eigenvectors in the rows of "evec". // This will store the eigenvectors in the rows of "evec".
// Do we want to return the eigenvectors in columns instead of rows? // Do we want to return the eigenvectors in columns instead of rows?
if (evec_as_columns) if (evec_as_columns)
for (int i=0; i<3; i++) for (int i=0; i<3; i++)
for (int j=0; j<3; j++) for (int j=i+1; j<3; j++)
std::swap(evec[i][j], evec[j][i]); std::swap(evec[i][j], evec[j][i]); // transpose the evec matrix
return ierror; return ierror;
} }
@ -124,16 +126,18 @@ jacobi3(double const* const* mat, // the 3x3 matrix you wish to diagonalize
{mat[2][0], mat[2][1], mat[2][2]} }; {mat[2][0], mat[2][1], mat[2][2]} };
double *M[3] = { &(mat_cpy[0][0]), &(mat_cpy[1][0]), &(mat_cpy[2][0]) }; double *M[3] = { &(mat_cpy[0][0]), &(mat_cpy[1][0]), &(mat_cpy[2][0]) };
int midx[3]; // (another array which is preallocated to avoid using the heap)
// variable "ecalc3" does all the work: // variable "ecalc3" does all the work:
Jacobi<double,double*,double**,double const*const*> ecalc3(3,M); Jacobi<double,double*,double**,double const*const*> ecalc3(3, M, midx);
int ierror = ecalc3.Diagonalize(mat, eval, evec, sort_criteria); int ierror = ecalc3.Diagonalize(mat, eval, evec, sort_criteria);
// This will store the eigenvectors in the rows of "evec". // This will store the eigenvectors in the rows of "evec".
// Do we want to return the eigenvectors in columns instead of rows? // Do we want to return the eigenvectors in columns instead of rows?
if (evec_as_columns) if (evec_as_columns)
for (int i=0; i<3; i++) for (int i=0; i<3; i++)
for (int j=0; j<3; j++) for (int j=i+1; j<3; j++)
std::swap(evec[i][j], evec[j][i]); std::swap(evec[i][j], evec[j][i]); // transpose the evec matrix
return ierror; return ierror;
} }

View File

@ -150,9 +150,7 @@ namespace MathEigen {
public: public:
/// @brief Specify the size of the matrices you want to diagonalize later. /// @brief Specify the size of the matrices you want to diagonalize later.
/// @param n the size (ie. number of rows) of the (square) matrix. /// @param n the size (ie. number of rows) of the (square) matrix.
/// @param workspace optional array for storing intermediate calculations Jacobi(int n);
/// (The vast majority of users can ignore this argument.)
Jacobi(int n = 0, Scalar **workspace=nullptr);
~Jacobi(); ~Jacobi();
@ -185,9 +183,25 @@ namespace MathEigen {
int max_num_sweeps=50 //!< limit the number of iterations int max_num_sweeps=50 //!< limit the number of iterations
); );
// An alternative constructor is provided, if, for some reason,
// you want to avoid allocating memory on the heap during
// initialization. In that case, you can allocate space for the "M"
// and "max_idx_row" arrays on the stack in advance, and pass them
// to the constructor. (The vast majority of users probably
// do not need this feature. Unless you do, I encourage you
// to use the regular constructor instead.)
// n the size (ie. number of rows) of the (square) matrix.
// M optional preallocated n x n array
// max_idx_row optional preallocated array of size n
// note If either the "M" or "max_idx_row" arguments are specified,
// they both must be specified.
Jacobi(int n, Scalar **M, int *max_idx_row);
private: private:
bool is_M_preallocated; bool is_preallocated;
// (Descriptions of private functions can be found in their implementation.) // (Descriptions of private functions can be found in their implementation.)
void _Jacobi(int n, Scalar **M, int *max_idx_row);
void CalcRot(Scalar const *const *M, int i, int j); void CalcRot(Scalar const *const *M, int i, int j);
void ApplyRot(Scalar **M, int i, int j); void ApplyRot(Scalar **M, int i, int j);
void ApplyRotLeft(Matrix E, int i, int j); void ApplyRotLeft(Matrix E, int i, int j);
@ -240,9 +254,9 @@ namespace MathEigen {
double *eval, double *eval,
double evec[3][3], double evec[3][3],
// optional arguments: // optional arguments:
bool evec_as_columns=true, bool evec_as_columns = true,
Jacobi<double,double*,double(*)[3],double const(*)[3]>:: Jacobi<double,double*,double(*)[3],double const(*)[3]>::
SortCriteria sort_criteria= SortCriteria sort_criteria =
Jacobi<double,double*,double(*)[3],double const(*)[3]>:: Jacobi<double,double*,double(*)[3],double const(*)[3]>::
SORT_DECREASING_EVALS SORT_DECREASING_EVALS
); );
@ -505,18 +519,40 @@ inline real_t<T> l1_norm(const std::vector<T>& vec) {
// --- Implementation: Eigendecomposition of small dense matrices --- // --- Implementation: Eigendecomposition of small dense matrices ---
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix> template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
Jacobi<Scalar, Vector, Matrix, ConstMatrix>:: Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Jacobi(int n, Scalar **workspace) { Jacobi(int n) {
_Jacobi(n, nullptr, nullptr);
}
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Jacobi(int n, Scalar **M, int *max_idx_row) {
_Jacobi(n, M, max_idx_row);
}
// _Jacobi() is a function which is invoked by the two constructors and it
// does all of the work. (Splitting the constructor into multiple functions
// was technically unnecessary, but it makes documenting the code easier.)
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void
Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
_Jacobi(int n, Scalar **M, int *max_idx_row) {
Init(); Init();
if (workspace) { if (M) { // if caller supplies their own "M" array, don't allocate M
is_M_preallocated = true; is_preallocated = true;
M = workspace;
this->n = n; this->n = n;
this->M = M;
this->max_idx_row = max_idx_row;
//assert(this->max_idx_row);
} }
else { else {
is_M_preallocated = false; is_preallocated = false;
SetSize(n); SetSize(n); // allocate the "M" and "max_int_row" arrays
} }
} }
@ -524,7 +560,7 @@ Jacobi(int n, Scalar **workspace) {
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix> template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
Jacobi<Scalar, Vector, Matrix, ConstMatrix>:: Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
~Jacobi() { ~Jacobi() {
if (! is_M_preallocated) if (! is_preallocated)
Dealloc(); Dealloc();
} }
@ -829,6 +865,7 @@ Init() {
n = 0; n = 0;
M = nullptr; M = nullptr;
max_idx_row = nullptr; max_idx_row = nullptr;
is_preallocated = false;
} }
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix> template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
@ -843,6 +880,7 @@ SetSize(int n) {
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix> template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>:: void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Alloc(int n) { Alloc(int n) {
//assert(! is_preallocated);
this->n = n; this->n = n;
if (n > 0) { if (n > 0) {
max_idx_row = new int[n]; max_idx_row = new int[n];
@ -853,8 +891,7 @@ Alloc(int n) {
template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix> template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>:: void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
Dealloc() { Dealloc() {
if (max_idx_row) //assert(! is_preallocated);
delete [] max_idx_row;
Dealloc2D(&M); Dealloc2D(&M);
Init(); Init();
} }
@ -867,6 +904,7 @@ Jacobi(const Jacobi<Scalar, Vector, Matrix, ConstMatrix>& source)
{ {
Init(); Init();
SetSize(source.n); SetSize(source.n);
//assert(n == source.n); //assert(n == source.n);
// The following lines aren't really necessary, because the contents // The following lines aren't really necessary, because the contents
// of source.M and source.max_idx_row are not needed (since they are // of source.M and source.max_idx_row are not needed (since they are
@ -884,6 +922,7 @@ template<typename Scalar,typename Vector,typename Matrix,typename ConstMatrix>
void Jacobi<Scalar, Vector, Matrix, ConstMatrix>:: void Jacobi<Scalar, Vector, Matrix, ConstMatrix>::
swap(Jacobi<Scalar, Vector, Matrix, ConstMatrix> &other) { swap(Jacobi<Scalar, Vector, Matrix, ConstMatrix> &other) {
std::swap(n, other.n); std::swap(n, other.n);
std::swap(is_preallocated, other.is_preallocated);
std::swap(max_idx_row, other.max_idx_row); std::swap(max_idx_row, other.max_idx_row);
std::swap(M, other.M); std::swap(M, other.M);
} }