ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
Jacobi.h
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #pragma once
9 
10 // Local
11 #include "SquareMatrix.h"
12 
13 #define ROTATE(a, i, j, k, l) \
14  { \
15  Scalar g = a[i][j]; \
16  h = a[k][l]; \
17  a[i][j] = g - s * (h + g * tau); \
18  a[k][l] = h + s * (g - h * tau); \
19  }
20 
22 template <typename Scalar>
23 class Jacobi {
24 public:
26  using EigenValues = std::vector<Scalar>;
27 
29 
45  static bool ComputeEigenValuesAndVectors2(const SquareMatrix& matrix,
46  SquareMatrix& eigenVectors,
47  EigenValues& eigenValues,
48  unsigned maxIterationCount = 50) {
49  if (!matrix.isValid()) {
50  return false;
51  }
52 
53  unsigned n = matrix.size();
54 
55  // duplicate the current matrix (as it may be modified afterwards!)
56  SquareMatrix a(matrix);
57 
58  // output eigen vectors matrix
59  eigenVectors = SquareMatrix(n);
60  if (!eigenVectors.isValid()) {
61  return false;
62  }
63  eigenVectors.toIdentity();
64 
65  std::vector<Scalar> bw, zw;
66  try {
67  eigenValues.resize(n);
68  zw.resize(n, 0);
69  bw.resize(n);
70 
71  // initialize bw and d to the diagonal of this matrix
72  for (unsigned i = 0; i < n; i++) {
73  bw[i] = eigenValues[i] = a.m_values[i][i];
74  }
75  } catch (const std::bad_alloc&) {
76  // not enough memory
77  return false;
78  }
79 
80  for (unsigned it = 0; it < maxIterationCount; ++it) {
81  // The convergence threshold is based on the size of the elements in
82  // the strict upper triangle of the matrix.
83  double sum = 0.0;
84  for (unsigned j = 0; j < n; ++j) {
85  for (unsigned i = 0; i < j; ++i) {
86  double val = a.m_values[i][j];
87  sum += val * val;
88  }
89  }
90 
91  Scalar thresh = static_cast<Scalar>(sqrt(sum) / (4 * n));
92  if (thresh == 0) {
93  break;
94  }
95 
96  for (unsigned p = 0; p < n; ++p) {
97  for (unsigned q = p + 1; q < n; ++q) {
98  Scalar gapq = 10 * std::abs(a.m_values[p][q]);
99  Scalar termp = gapq + std::abs(eigenValues[p]);
100  Scalar termq = gapq + std::abs(eigenValues[q]);
101 
102  // Annihilate tiny offdiagonal elements
103  if (it > 4 && termp == std::abs(eigenValues[p]) &&
104  termq == std::abs(eigenValues[q])) {
105  a.m_values[p][q] = 0;
106  }
107  // Otherwise, apply a rotation
108  else if (thresh <= std::abs(a.m_values[p][q])) {
109  Scalar h = eigenValues[q] - eigenValues[p];
110  Scalar term = std::abs(h) + gapq;
111 
112  Scalar t = 0;
113  if (term == std::abs(h)) {
114  t = a.m_values[p][q] / h;
115  } else {
116  Scalar theta = h / (2 * a.m_values[p][q]);
117  t = 1 / (std::abs(theta) + sqrt(1 + theta * theta));
118  if (theta < 0) {
119  t = -t;
120  }
121  }
122  Scalar c = 1 / sqrt(1 + t * t);
123  Scalar s = t * c;
124  Scalar tau = s / (1 + c);
125  h = t * a.m_values[q][p];
126 
127  // Accumulate corrections to diagonal elements.
128  zw[p] -= h;
129  zw[q] += h;
130  eigenValues[p] -= h;
131  eigenValues[q] += h;
132 
133  a.m_values[p][q] = 0;
134 
135  // Rotate, using information from the upper triangle of
136  // A only.
137  unsigned j;
138  for (j = 0; j < p; ++j) {
139  Scalar g = a.m_values[j][p];
140  Scalar h = a.m_values[j][q];
141  a.m_values[j][p] = g - s * (h + g * tau);
142  a.m_values[j][q] = h + s * (g - h * tau);
143  }
144 
145  for (j = p + 1; j < q; ++j) {
146  Scalar g = a.m_values[p][j];
147  Scalar h = a.m_values[j][q];
148  a.m_values[p][j] = g - s * (h + g * tau);
149  a.m_values[j][q] = h + s * (g - h * tau);
150  }
151 
152  for (j = q + 1; j < n; ++j) {
153  Scalar g = a.m_values[p][j];
154  Scalar h = a.m_values[q][j];
155  a.m_values[p][j] = g - s * (h + g * tau);
156  a.m_values[q][j] = h + s * (g - h * tau);
157  }
158 
159  // Accumulate information in the eigenvector matrix.
160  for (j = 0; j < n; ++j) {
161  Scalar g = eigenVectors.m_values[j][p];
162  Scalar h = eigenVectors.m_values[j][q];
163  eigenVectors.m_values[j][p] = g - s * (h + g * tau);
164  eigenVectors.m_values[j][q] = h + s * (g - h * tau);
165  }
166  }
167  }
168  }
169 
170  for (unsigned i = 0; i < n; ++i) {
171  bw[i] += zw[i];
172  eigenValues[i] = bw[i];
173  zw[i] = 0.0;
174  }
175  }
176 
177  return true;
178  }
179 
181 
183  static bool ComputeEigenValuesAndVectors(const SquareMatrix& matrix,
184  SquareMatrix& eigenVectors,
185  EigenValues& eigenValues,
186  bool absoluteValues = true,
187  unsigned maxIterationCount = 50) {
188  if (!matrix.isValid()) {
189  return false;
190  }
191 
192  unsigned n = matrix.size();
193  unsigned matrixSquareSize = n * n;
194 
195  // output eigen vectors matrix
196  eigenVectors = SquareMatrix(n);
197  if (!eigenVectors.isValid()) {
198  return false;
199  }
200  eigenVectors.toIdentity();
201 
202  std::vector<Scalar> b, z;
203  try {
204  eigenValues.resize(n);
205  b.resize(n);
206  z.resize(n);
207  } catch (const std::bad_alloc&) {
208  // not enough memory
209  return false;
210  }
211  Scalar* d = eigenValues.data();
212 
213  // init
214  {
215  for (unsigned ip = 0; ip < n; ip++) {
216  b[ip] = d[ip] =
217  matrix.m_values[ip][ip]; // Initialize b and d to the
218  // diagonal of a.
219  z[ip] = 0; // This vector will accumulate terms of the form
220  // tapq as in equation (11.1.14)
221  }
222  }
223 
224  for (unsigned i = 1; i <= maxIterationCount; i++) {
225  // Sum off-diagonal elements
226  Scalar sm = 0;
227  {
228  for (unsigned ip = 0; ip < n - 1; ip++) {
229  for (unsigned iq = ip + 1; iq < n; iq++)
230  sm += std::abs(matrix.m_values[ip][iq]);
231  }
232  }
233 
234  if (sm == 0) // The normal return, which relies on quadratic
235  // convergence to machine underflow.
236  {
237  if (absoluteValues) {
238  // we only need the absolute values of eigenvalues
239  for (unsigned ip = 0; ip < n; ip++) d[ip] = std::abs(d[ip]);
240  }
241 
242  return true;
243  }
244 
245  Scalar tresh = 0;
246  if (i < 4) {
247  tresh = sm / static_cast<Scalar>(
248  5 * matrixSquareSize); //...on the first
249  // three sweeps.
250  }
251 
252  for (unsigned ip = 0; ip < n - 1; ip++) {
253  for (unsigned iq = ip + 1; iq < n; iq++) {
254  Scalar g = std::abs(matrix.m_values[ip][iq]) * 100;
255  // After four sweeps, skip the rotation if the off-diagonal
256  // element is small.
257  if (i > 4 &&
258  static_cast<float>(std::abs(d[ip]) + g) ==
259  static_cast<float>(std::abs(d[ip])) &&
260  static_cast<float>(std::abs(d[iq]) + g) ==
261  static_cast<float>(std::abs(d[iq]))) {
262  matrix.m_values[ip][iq] = 0;
263  } else if (std::abs(matrix.m_values[ip][iq]) > tresh) {
264  Scalar h = d[iq] - d[ip];
265  Scalar t = 0;
266  if (static_cast<float>(std::abs(h) + g) ==
267  static_cast<float>(std::abs(h))) {
268  t = matrix.m_values[ip][iq] / h;
269  } else {
270  Scalar theta =
271  h /
272  (2 *
273  matrix.m_values[ip][iq]); // Equation
274  // (11.1.10).
275  t = 1 / (std::abs(theta) + sqrt(1 + theta * theta));
276  if (theta < 0) t = -t;
277  }
278 
279  Scalar c = 1 / sqrt(t * t + 1);
280  Scalar s = t * c;
281  Scalar tau = s / (1 + c);
282  h = t * matrix.m_values[ip][iq];
283  z[ip] -= h;
284  z[iq] += h;
285  d[ip] -= h;
286  d[iq] += h;
287  matrix.m_values[ip][iq] = 0;
288 
289  // Case of rotations 1 <= j < p
290  {
291  for (unsigned j = 0; j + 1 <= ip; j++)
292  ROTATE(matrix.m_values, j, ip, j, iq)
293  }
294  // Case of rotations p < j < q
295  {
296  for (unsigned j = ip + 1; j + 1 <= iq; j++)
297  ROTATE(matrix.m_values, ip, j, j, iq)
298  }
299  // Case of rotations q < j <= n
300  {
301  for (unsigned j = iq + 1; j < n; j++)
302  ROTATE(matrix.m_values, ip, j, iq, j)
303  }
304  // Last case
305  {
306  for (unsigned j = 0; j < n; j++)
307  ROTATE(eigenVectors.m_values, j, ip, j, iq)
308  }
309  }
310  }
311  }
312 
313  // update b, d and z
314  {
315  for (unsigned ip = 0; ip < n; ip++) {
316  b[ip] += z[ip];
317  d[ip] = b[ip];
318  z[ip] = 0;
319  }
320  }
321  }
322 
323  // Too many iterations!
324  return false;
325  }
326 
329 
333  static bool SortEigenValuesAndVectors(SquareMatrix& eigenVectors,
334  EigenValues& eigenValues) {
335  if (!eigenVectors.isValid() || eigenVectors.size() < 2 ||
336  eigenVectors.size() != eigenValues.size()) {
337  assert(false);
338  return false;
339  }
340 
341  unsigned n = eigenVectors.size();
342  for (unsigned i = 0; i < n - 1; i++) {
343  unsigned maxValIndex = i;
344  for (unsigned j = i + 1; j < n; j++)
345  if (eigenValues[j] > eigenValues[maxValIndex]) maxValIndex = j;
346 
347  if (maxValIndex != i) {
348  std::swap(eigenValues[i], eigenValues[maxValIndex]);
349  for (unsigned j = 0; j < n; ++j)
350  std::swap(eigenVectors.m_values[j][i],
351  eigenVectors.m_values[j][maxValIndex]);
352  }
353  }
354 
355  return true;
356  }
357 
359 
364  static bool GetEigenVector(const SquareMatrix& eigenVectors,
365  unsigned index,
366  Scalar eigenVector[]) {
367  if (eigenVector && index < eigenVectors.size()) {
368  for (unsigned i = 0; i < eigenVectors.size(); ++i) {
369  eigenVector[i] = eigenVectors.m_values[i][index];
370  }
371  return true;
372  } else {
373  assert(false);
374  return false;
375  }
376  }
377 
379 
385  static bool GetMaxEigenValueAndVector(const SquareMatrix& eigenVectors,
386  const EigenValues& eigenValues,
387  Scalar& maxEigenValue,
388  Scalar maxEigenVector[]) {
389  if (!eigenVectors.isValid() || eigenVectors.size() < 2 ||
390  eigenVectors.size() != eigenValues.size()) {
391  assert(false);
392  return false;
393  }
394 
395  unsigned maxIndex = 0;
396  for (unsigned i = 1; i < eigenVectors.size(); ++i)
397  if (eigenValues[i] > eigenValues[maxIndex]) maxIndex = i;
398 
399  maxEigenValue = eigenValues[maxIndex];
400  return GetEigenVector(eigenVectors, maxIndex, maxEigenVector);
401  }
402 
404 
410  static bool GetMinEigenValueAndVector(const SquareMatrix& eigenVectors,
411  const EigenValues& eigenValues,
412  Scalar& minEigenValue,
413  Scalar minEigenVector[]) {
414  if (!eigenVectors.isValid() || eigenVectors.size() < 2 ||
415  eigenVectors.size() != eigenValues.size()) {
416  assert(false);
417  return false;
418  }
419 
420  unsigned minIndex = 0;
421  for (unsigned i = 1; i < eigenVectors.size(); ++i)
422  if (eigenValues[i] < eigenValues[minIndex]) minIndex = i;
423 
424  minEigenValue = eigenValues[minIndex];
425  return GetEigenVector(eigenVectors, minIndex, minEigenVector);
426  }
427 };
#define ROTATE(a, i, j, k, l)
Definition: Jacobi.h:13
Jacobi eigen vectors/values decomposition.
Definition: Jacobi.h:23
static bool GetEigenVector(const SquareMatrix &eigenVectors, unsigned index, Scalar eigenVector[])
Returns the given eigenvector.
Definition: Jacobi.h:364
std::vector< Scalar > EigenValues
Definition: Jacobi.h:26
static bool ComputeEigenValuesAndVectors2(const SquareMatrix &matrix, SquareMatrix &eigenVectors, EigenValues &eigenValues, unsigned maxIterationCount=50)
Computes the eigenvalues and eigenvectors of a given square matrix.
Definition: Jacobi.h:45
cloudViewer::SquareMatrixTpl< Scalar > SquareMatrix
Definition: Jacobi.h:25
static bool GetMinEigenValueAndVector(const SquareMatrix &eigenVectors, const EigenValues &eigenValues, Scalar &minEigenValue, Scalar minEigenVector[])
Returns the smallest eigenvalue and its associated eigenvector.
Definition: Jacobi.h:410
static bool ComputeEigenValuesAndVectors(const SquareMatrix &matrix, SquareMatrix &eigenVectors, EigenValues &eigenValues, bool absoluteValues=true, unsigned maxIterationCount=50)
Computes eigen vectors (and values) with the Jacobian method.
Definition: Jacobi.h:183
static bool SortEigenValuesAndVectors(SquareMatrix &eigenVectors, EigenValues &eigenValues)
Definition: Jacobi.h:333
static bool GetMaxEigenValueAndVector(const SquareMatrix &eigenVectors, const EigenValues &eigenValues, Scalar &maxEigenValue, Scalar maxEigenVector[])
Returns the biggest eigenvalue and its associated eigenvector.
Definition: Jacobi.h:385
void toIdentity()
Sets matrix to identity.
Definition: SquareMatrix.h:452
Scalar ** m_values
The matrix rows.
Definition: SquareMatrix.h:157
bool isValid() const
Returns matrix validity.
Definition: SquareMatrix.h:138
unsigned size() const
Returns matrix size.
Definition: SquareMatrix.h:133
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370