Eigen.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /* Eigen.cpp
  2. *
  3. * Copyright (C) 1993-2018 David Weenink
  4. *
  5. * This code is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or (at
  8. * your option) any later version.
  9. *
  10. * This code is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /*
  19. djmw 20010719
  20. djmw 20020402 GPL header.
  21. djmw 20030701 Modified sorting in Eigen_initFromSquareRootPair.
  22. djmw 20030703 Added Eigens_alignEigenvectors.
  23. djmw 20030708 Corrected syntax error in Eigens_alignEigenvectors.
  24. djmw 20030812 Corrected memory bug in Eigen_initFromSymmetricMatrix.
  25. djmw 20030825 Removed praat_USE_LAPACK external variable.
  26. djmw 20031101 Documentation
  27. djmw 20031107 Moved NUMdmatrix_transpose to NUM2.c
  28. djmw 20031210 Added rowLabels to Eigen_drawEigenvector and Eigen_Strings_drawEigenvector
  29. djmw 20030322 Extra test in Eigen_initFromSquareRootPair.
  30. djmw 20040329 Added fractionOfTotal and cumulative parameters in Eigen_drawEigenvalues_scree.
  31. djmw 20040622 Less horizontal labels in Eigen_drawEigenvector.
  32. djmw 20050706 Shortened horizontal offsets in Eigen_drawEigenvalues from 1 to 0.5
  33. djmw 20051204 Eigen_initFromSquareRoot adapted for nrows < ncols
  34. djmw 20071012 Added: oo_CAN_WRITE_AS_ENCODING.h
  35. djmw 20110304 Thing_new
  36. */
  37. #include "Eigen.h"
  38. #include "MAT_numerics.h"
  39. #include "NUMmachar.h"
  40. #include "NUMclapack.h"
  41. #include "NUM2.h"
  42. #include "SVD.h"
  43. #include "oo_DESTROY.h"
  44. #include "Eigen_def.h"
  45. #include "oo_COPY.h"
  46. #include "Eigen_def.h"
  47. #include "oo_EQUAL.h"
  48. #include "Eigen_def.h"
  49. #include "oo_CAN_WRITE_AS_ENCODING.h"
  50. #include "Eigen_def.h"
  51. #include "oo_WRITE_TEXT.h"
  52. #include "Eigen_def.h"
  53. #include "oo_READ_TEXT.h"
  54. #include "Eigen_def.h"
  55. #include "oo_WRITE_BINARY.h"
  56. #include "Eigen_def.h"
  57. #include "oo_READ_BINARY.h"
  58. #include "Eigen_def.h"
  59. #include "oo_DESCRIPTION.h"
  60. #include "Eigen_def.h"
  61. Thing_implement (Eigen, Daata, 0);
  62. static void Graphics_ticks (Graphics g, double min, double max, bool hasNumber, bool hasTick, bool hasDottedLine, bool integers) {
  63. double range = max - min, scale = 1.0, tick = min, dtick = 1.0;
  64. if (range == 0.0) {
  65. return;
  66. } else if (range > 1.0) {
  67. while (range / scale > 10.0) {
  68. scale *= 10.0;
  69. }
  70. range /= scale;
  71. } else {
  72. while (range / scale < 10.0) {
  73. scale /= 10.0;
  74. }
  75. range *= scale;
  76. }
  77. if (range < 3.0) {
  78. dtick = 0.5;
  79. }
  80. dtick *= scale;
  81. tick = dtick * Melder_roundDown (min / dtick);
  82. if (tick < min) {
  83. tick += dtick;
  84. }
  85. while (tick <= max) {
  86. double num = integers ? round (tick) : tick;
  87. Graphics_markBottom (g, num, hasNumber, hasTick, hasDottedLine, nullptr);
  88. tick += dtick;
  89. }
  90. }
  91. void Eigen_init (Eigen me, integer numberOfEigenvalues, integer dimension) {
  92. my numberOfEigenvalues = numberOfEigenvalues;
  93. my dimension = dimension;
  94. my eigenvalues = VECzero (numberOfEigenvalues);
  95. my eigenvectors = MATzero (numberOfEigenvalues, dimension);
  96. }
  97. /*
  98. Solve: (A'A - lambda)x = 0 for eigenvalues lambda and eigenvectors x.
  99. svd(A) = UDV' => A'A = (UDV')'(UDV') = VD^2V'
  100. (VD^2V'-lambda)x = 0 => (D^2 - lambda)V'x = 0 => solution V'x = I => x = V
  101. Eigenvectors: the columns of the matrix V
  102. Eigenvalues: D_i^2
  103. */
  104. void Eigen_initFromSquareRoot (Eigen me, constMAT a) {
  105. Melder_assert (a.nrow >= 1);
  106. integer numberOfZeroed, numberOfEigenvalues;
  107. integer nsv = std::min (a.nrow, a.ncol);
  108. my dimension = a.ncol;
  109. autoSVD svd = SVD_createFromGeneralMatrix (a);
  110. /*
  111. Make sv's that are too small zero. These values occur automatically
  112. when the rank of A'A < a.ncol. This could occur when for
  113. example numberOfRows <= a.ncol.
  114. (n points in an n-dimensional space define maximally an n-1
  115. dimensional surface for which we maximally need an n-1 dimensional
  116. basis.)
  117. */
  118. numberOfZeroed = SVD_zeroSmallSingularValues (svd.get(), 0.0);
  119. numberOfEigenvalues = nsv - numberOfZeroed;
  120. Eigen_init (me, numberOfEigenvalues, a.ncol);
  121. integer k = 0;
  122. for (integer i = 1; i <= nsv; i ++) {
  123. double t = svd -> d [i];
  124. if (t > 0.0) {
  125. my eigenvalues [++ k] = t * t;
  126. for (integer j = 1; j <= a.ncol; j ++) {
  127. my eigenvectors [k] [j] = svd -> v [j] [i];
  128. }
  129. }
  130. }
  131. Eigen_sort (me);
  132. }
  133. void Eigen_initFromSquareRootPair (Eigen me, constMAT a, constMAT b) {
  134. //Melder_assert (a.nrow >= a.ncol && b.nrow >= b.ncol); // pggb: this cannot be an assert, and seems too strict anyway
  135. Melder_require (a.ncol == b.ncol,
  136. U"The numbers of columns should be equal, not ", a.ncol, U" and ", b.ncol, U".");
  137. // Eigen has not been inited yet.
  138. double *u = nullptr, *v = nullptr, maxsv2 = -10.0;
  139. char jobu = 'N', jobv = 'N', jobq = 'Q';
  140. integer k, ll, m = a.nrow, n = a.ncol, p = b.nrow;
  141. integer lda = m, ldb = p, ldu = lda, ldv = ldb, ldq = n;
  142. integer lwork = std::max (std::max (3 * n, m), p) + n, info;
  143. /* Melder_assert (numberOfRows >= numberOfColumns || numberOfRows_b >= numberOfColumns);*/
  144. my dimension = a.ncol;
  145. autoVEC alpha = VECraw (n);
  146. autoVEC beta = VECraw (n);
  147. autoVEC work = VECraw (lwork);
  148. autoINTVEC iwork = INTVECzero (n);
  149. autoMAT q = MATraw (n, n);
  150. autoMAT ac = MATtranspose (a);
  151. autoMAT bc = MATtranspose (b);
  152. (void) NUMlapack_dggsvd (& jobu, & jobv, & jobq, & m, & n, & p, & k, & ll,
  153. & ac [1][1], & lda, & bc [1][1], & ldb, alpha.begin(), beta.begin(), u, & ldu,
  154. v, & ldv, & q [1][1], & ldq, work.begin(), iwork.begin(), & info);
  155. Melder_require (info == 0, U"dggsvd fails with code ", info, U".");
  156. // Calculate the eigenvalues (alpha[i]/beta[i])^2 and store in alpha[i].
  157. maxsv2 = -1.0;
  158. for (integer i = k + 1; i <= k + ll; i ++) {
  159. double t = alpha [i] / beta [i];
  160. alpha [i] = t * t;
  161. if (alpha [i] > maxsv2) {
  162. maxsv2 = alpha [i];
  163. }
  164. }
  165. // Deselect the eigenvalues < eps * max_eigenvalue by making them -1.0
  166. integer numberOfDeselected = 0;
  167. for (integer i = k + 1; i <= k + ll; i ++) {
  168. if (alpha [i] < NUMfpp -> eps * maxsv2) {
  169. numberOfDeselected ++;
  170. alpha [i] = -1.0;
  171. }
  172. }
  173. Melder_require (ll - numberOfDeselected > 0, U"No eigenvectors can be found. Matrix too singular.");
  174. Eigen_init (me, ll - numberOfDeselected, a.ncol);
  175. integer numberOfEigenvalues = 0;
  176. for (integer i = k + 1; i <= k + ll; i ++) {
  177. if (alpha [i] == -1.0) continue;
  178. my eigenvalues [++ numberOfEigenvalues] = alpha [i];
  179. for (integer j = 1; j <= a.ncol; j ++)
  180. my eigenvectors [numberOfEigenvalues] [j] = q [i] [j];
  181. }
  182. Eigen_sort (me);
  183. MATnormalizeRows_inplace (my eigenvectors.get(), 2.0, 1.0);
  184. }
  185. void Eigen_initFromSymmetricMatrix (Eigen me, constMAT a) {
  186. Melder_assert (a.ncol == a.nrow);
  187. if (! my eigenvectors.at) {
  188. Eigen_init (me, a.ncol, a.ncol);
  189. }
  190. MATtranspose_preallocated (my eigenvectors.get(), a);
  191. MAT_getEigenSystemFromSymmetricMatrix_inplace (my eigenvectors.get(), true, my eigenvalues.get(), false);
  192. }
  193. autoEigen Eigen_create (integer numberOfEigenvalues, integer dimension) {
  194. try {
  195. autoEigen me = Thing_new (Eigen);
  196. Eigen_init (me.get(), numberOfEigenvalues, dimension);
  197. return me;
  198. } catch (MelderError) {
  199. Melder_throw (U"Eigen not created.");
  200. }
  201. }
  202. integer Eigen_getNumberOfEigenvectors (Eigen me) {
  203. return my numberOfEigenvalues;
  204. }
  205. double Eigen_getEigenvectorElement (Eigen me, integer ivec, integer element) {
  206. if (ivec > my numberOfEigenvalues || element < 1 || element > my dimension) {
  207. return undefined;
  208. }
  209. return my eigenvectors[ivec][element];
  210. }
  211. integer Eigen_getDimensionOfComponents (Eigen me) {
  212. return my dimension;
  213. }
  214. double Eigen_getSumOfEigenvalues (Eigen me, integer from, integer to) {
  215. if (from < 1) from = 1;
  216. if (to < 1) to = my numberOfEigenvalues;
  217. if (to > my numberOfEigenvalues || from > to)
  218. return undefined;
  219. return NUMsum (my eigenvalues.subview (from, to));
  220. }
  221. double Eigen_getCumulativeContributionOfComponents (Eigen me, integer from, integer to) {
  222. longdouble partial = 0.0, sum = 0.0;
  223. if (to == 0) {
  224. to = my numberOfEigenvalues;
  225. }
  226. if (from > 0 && to <= my numberOfEigenvalues && from <= to) {
  227. for (integer i = 1; i <= my numberOfEigenvalues; i ++) {
  228. sum += my eigenvalues [i];
  229. if (i >= from && i <= to) {
  230. partial += my eigenvalues [i];
  231. }
  232. }
  233. }
  234. return sum > 0.0 ? partial / sum : 0.0;
  235. }
  236. integer Eigen_getDimensionOfFraction (Eigen me, double fraction) {
  237. double sum = Eigen_getSumOfEigenvalues (me, 0, 0);
  238. if (sum == 0.0) {
  239. return 1;
  240. }
  241. integer n = 1;
  242. longdouble p = my eigenvalues [1];
  243. while (p / sum < fraction && n < my numberOfEigenvalues) {
  244. p += my eigenvalues [++ n];
  245. }
  246. return n;
  247. }
  248. void Eigen_sort (Eigen me) {
  249. double *e = my eigenvalues.at, **v = my eigenvectors.at;
  250. for (integer i = 1; i < my numberOfEigenvalues; i ++) {
  251. integer k;
  252. double emax = e [k = i];
  253. for (integer j = i + 1; j <= my numberOfEigenvalues; j ++) {
  254. if (e [j] > emax) {
  255. emax = e [k = j];
  256. }
  257. }
  258. if (k != i) {
  259. // Swap eigenvalues and eigenvectors
  260. std::swap (e [i], e [k]);
  261. for (integer j = 1; j <= my dimension; j ++)
  262. std::swap (v [i] [j], v [k] [j]);
  263. }
  264. }
  265. }
  266. void Eigen_invertEigenvector (Eigen me, integer ivec) {
  267. if (ivec < 1 || ivec > my numberOfEigenvalues) {
  268. return;
  269. }
  270. for (integer j = 1; j <= my dimension; j ++) {
  271. my eigenvectors [ivec] [j] = - my eigenvectors [ivec] [j];
  272. }
  273. }
  274. void Eigen_drawEigenvalues (Eigen me, Graphics g, integer first, integer last, double ymin, double ymax, bool fractionOfTotal, bool cumulative, double size_mm, conststring32 mark, bool garnish) {
  275. double xmin = first, xmax = last, scale = 1.0, sumOfEigenvalues = 0.0;
  276. if (first < 1) first = 1;
  277. if (last < 1 || last > my numberOfEigenvalues) last = my numberOfEigenvalues;
  278. if (last <= first) {
  279. first = 1;
  280. last = my numberOfEigenvalues;
  281. }
  282. xmin = first - 0.5; xmax = last + 0.5;
  283. if (fractionOfTotal || cumulative) {
  284. sumOfEigenvalues = Eigen_getSumOfEigenvalues (me, 0, 0);
  285. if (sumOfEigenvalues <= 0.0)
  286. sumOfEigenvalues = 1.0;
  287. scale = sumOfEigenvalues;
  288. }
  289. if (ymax <= ymin) {
  290. ymax = Eigen_getSumOfEigenvalues (me, ( cumulative ? 1 : first ), first) / scale;
  291. ymin = Eigen_getSumOfEigenvalues (me, ( cumulative ? 1 : last ), last) / scale;
  292. if (ymin > ymax) {
  293. double tmp = ymin;
  294. ymin = ymax; ymax = tmp;
  295. }
  296. if (ymin == ymax) { // only one eigenvalue
  297. ymin -= 0.1 * ymin;
  298. ymax += 0.1 * ymax;
  299. }
  300. }
  301. Graphics_setInner (g);
  302. Graphics_setWindow (g, xmin, xmax, ymin, ymax);
  303. for (integer i = first; i <= last; i ++) {
  304. double accu = Eigen_getSumOfEigenvalues (me, (cumulative ? 1 : i), i);
  305. Graphics_mark (g, i, accu / scale, size_mm, mark);
  306. }
  307. Graphics_unsetInner (g);
  308. if (garnish) {
  309. Graphics_drawInnerBox (g);
  310. Graphics_textLeft (g, true, (fractionOfTotal ? (cumulative ? U"Cumulative fractional eigenvalue" : U"Fractional eigenvalue" ) :
  311. ( cumulative ? U"Cumulative eigenvalue" : U"Eigenvalue" ) ));
  312. Graphics_ticks (g, first, last, true, true, false, true);
  313. Graphics_marksLeft (g, 2, true, true, false);
  314. Graphics_textBottom (g, true, U"Index");
  315. }
  316. }
  317. void Eigen_drawEigenvector (Eigen me, Graphics g, integer ivec, integer first, integer last,
  318. double ymin, double ymax, bool weigh, double size_mm, conststring32 mark, bool connect, char32 **rowLabels, bool garnish)
  319. {
  320. double xmin = first, xmax = last;
  321. if (ivec < 1 || ivec > my numberOfEigenvalues) return;
  322. if (last <= first) {
  323. first = 1;
  324. last = my dimension;
  325. xmin = 0.5;
  326. xmax = last + 0.5;
  327. }
  328. double *vec = my eigenvectors [ivec];
  329. double w = weigh ? sqrt (my eigenvalues [ivec]) : 1.0;
  330. // If ymax < ymin the eigenvector will automatically be drawn inverted.
  331. if (ymax == ymin) {
  332. NUMvector_extrema (vec, first, last, & ymin, & ymax);
  333. ymax *= w; ymin *= w;
  334. }
  335. Graphics_setInner (g);
  336. Graphics_setWindow (g, xmin, xmax, ymin, ymax);
  337. for (integer i = first; i <= last; i ++) {
  338. Graphics_mark (g, i, w * vec [i], size_mm, mark);
  339. if (connect && i > first)
  340. Graphics_line (g, i - 1.0, w * vec [i - 1], i, w * vec [i]);
  341. }
  342. Graphics_unsetInner (g);
  343. if (garnish) {
  344. Graphics_markBottom (g, first, false, true, false, rowLabels ? rowLabels [first] : Melder_integer (first));
  345. Graphics_markBottom (g, last, false, true, false, rowLabels ? rowLabels [last] : Melder_integer (last));
  346. Graphics_drawInnerBox (g);
  347. if (ymin * ymax < 0.0)
  348. Graphics_markLeft (g, 0.0, true, true, true, nullptr);
  349. Graphics_marksLeft (g, 2, true, true, false);
  350. if (! rowLabels)
  351. Graphics_textBottom (g, true, U"Element number");
  352. }
  353. }
  354. void Eigens_alignEigenvectors (OrderedOf<structEigen>* me) {
  355. if (my size < 2) return;
  356. Eigen e1 = my at [1];
  357. integer nev1 = e1 -> numberOfEigenvalues;
  358. integer dimension = e1 -> dimension;
  359. for (integer i = 2; i <= my size; i ++)
  360. Melder_require (my at [i] -> dimension == dimension,
  361. U"The dimension of the eigenvectors should be equal (offending object is ", i, U").");
  362. /*
  363. Correlate eigenvectors.
  364. If r < 0 then mirror the eigenvector.
  365. */
  366. for (integer i = 2; i <= my size; i ++) {
  367. Eigen e2 = my at [i];
  368. for (integer j = 1; j <= std::min (nev1, e2 -> numberOfEigenvalues); j ++) {
  369. double ip = NUMinner (e1 -> eigenvectors.row(j), e2 -> eigenvectors.row(j));
  370. if (ip < 0.0)
  371. for (integer k = 1; k <= dimension; k ++)
  372. e2 -> eigenvectors [j] [k] = - e2 -> eigenvectors [j] [k];
  373. }
  374. }
  375. }
  376. static autoVEC Eigens_getAnglesBetweenSubspaces (Eigen me, Eigen thee, integer ivec_from, integer ivec_to) {
  377. integer nvectors = ivec_to - ivec_from + 1;
  378. integer nmin = my numberOfEigenvalues < thy numberOfEigenvalues ? my numberOfEigenvalues : thy numberOfEigenvalues;
  379. Melder_require (my dimension == thy dimension, U"The eigenvectors should have equal dimensions.");
  380. Melder_require (ivec_from > 0 && ivec_from <= ivec_to && ivec_to <= nmin, U"Eigenvector range too large.");
  381. /*
  382. Algorithm 12.4.3 Golub & van Loan
  383. Because we deal with eigenvectors we don't have to do the QR decomposition,
  384. the columns in the Q's are the eigenvectors.
  385. Compute C.
  386. */
  387. autoVEC angles_degrees = VECraw (nvectors);
  388. autoMAT c = MATmul_nt (my eigenvectors.horizontalBand (ivec_from, ivec_to), thy eigenvectors.horizontalBand (ivec_from, ivec_to));
  389. autoSVD svd = SVD_createFromGeneralMatrix (c.get());
  390. for (integer i = 1; i <= nvectors; i ++) {
  391. angles_degrees [i] = acos (svd -> d [i]) * 180.0 / NUMpi;
  392. }
  393. return angles_degrees;
  394. }
  395. double Eigens_getAngleBetweenEigenplanes_degrees (Eigen me, Eigen thee) {
  396. autoVEC angles_degrees = Eigens_getAnglesBetweenSubspaces (me, thee, 1, 2);
  397. return angles_degrees [2];
  398. }
  399. /* End of file Eigen.cpp */