complexEigs.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. import { clone } from '../../../utils/object.js';
  2. export function createComplexEigs(_ref) {
  3. var {
  4. addScalar,
  5. subtract,
  6. flatten,
  7. multiply,
  8. multiplyScalar,
  9. divideScalar,
  10. sqrt,
  11. abs,
  12. bignumber,
  13. diag,
  14. inv,
  15. qr,
  16. usolve,
  17. usolveAll,
  18. equal,
  19. complex,
  20. larger,
  21. smaller,
  22. matrixFromColumns,
  23. dot
  24. } = _ref;
  25. /**
  26. * @param {number[][]} arr the matrix to find eigenvalues of
  27. * @param {number} N size of the matrix
  28. * @param {number|BigNumber} prec precision, anything lower will be considered zero
  29. * @param {'number'|'BigNumber'|'Complex'} type
  30. * @param {boolean} findVectors should we find eigenvectors?
  31. *
  32. * @returns {{ values: number[], vectors: number[][] }}
  33. */
  34. function complexEigs(arr, N, prec, type, findVectors) {
  35. if (findVectors === undefined) {
  36. findVectors = true;
  37. }
  38. // TODO check if any row/col are zero except the diagonal
  39. // make sure corresponding rows and columns have similar magnitude
  40. // important because of numerical stability
  41. // MODIFIES arr by side effect!
  42. var R = balance(arr, N, prec, type, findVectors);
  43. // R is the row transformation matrix
  44. // arr = A' = R A R⁻¹, A is the original matrix
  45. // (if findVectors is false, R is undefined)
  46. // (And so to return to original matrix: A = R⁻¹ arr R)
  47. // TODO if magnitudes of elements vary over many orders,
  48. // move greatest elements to the top left corner
  49. // using similarity transformations, reduce the matrix
  50. // to Hessenberg form (upper triangular plus one subdiagonal row)
  51. // updates the transformation matrix R with new row operationsq
  52. // MODIFIES arr by side effect!
  53. reduceToHessenberg(arr, N, prec, type, findVectors, R);
  54. // still true that original A = R⁻¹ arr R)
  55. // find eigenvalues
  56. var {
  57. values,
  58. C
  59. } = iterateUntilTriangular(arr, N, prec, type, findVectors);
  60. // values is the list of eigenvalues, C is the column
  61. // transformation matrix that transforms arr, the hessenberg
  62. // matrix, to upper triangular
  63. // (So U = C⁻¹ arr C and the relationship between current arr
  64. // and original A is unchanged.)
  65. var vectors;
  66. if (findVectors) {
  67. vectors = findEigenvectors(arr, N, C, R, values, prec, type);
  68. vectors = matrixFromColumns(...vectors);
  69. }
  70. return {
  71. values,
  72. vectors
  73. };
  74. }
  75. /**
  76. * @param {number[][]} arr
  77. * @param {number} N
  78. * @param {number} prec
  79. * @param {'number'|'BigNumber'|'Complex'} type
  80. * @returns {number[][]}
  81. */
  82. function balance(arr, N, prec, type, findVectors) {
  83. var big = type === 'BigNumber';
  84. var cplx = type === 'Complex';
  85. var realzero = big ? bignumber(0) : 0;
  86. var one = big ? bignumber(1) : cplx ? complex(1) : 1;
  87. var realone = big ? bignumber(1) : 1;
  88. // base of the floating-point arithmetic
  89. var radix = big ? bignumber(10) : 2;
  90. var radixSq = multiplyScalar(radix, radix);
  91. // the diagonal transformation matrix R
  92. var Rdiag;
  93. if (findVectors) {
  94. Rdiag = Array(N).fill(one);
  95. }
  96. // this isn't the only time we loop thru the matrix...
  97. var last = false;
  98. while (!last) {
  99. // ...haha I'm joking! unless...
  100. last = true;
  101. for (var i = 0; i < N; i++) {
  102. // compute the taxicab norm of i-th column and row
  103. // TODO optimize for complex numbers
  104. var colNorm = realzero;
  105. var rowNorm = realzero;
  106. for (var j = 0; j < N; j++) {
  107. if (i === j) continue;
  108. var c = abs(arr[i][j]); // should be real
  109. colNorm = addScalar(colNorm, c);
  110. rowNorm = addScalar(rowNorm, c);
  111. }
  112. if (!equal(colNorm, 0) && !equal(rowNorm, 0)) {
  113. // find integer power closest to balancing the matrix
  114. // (we want to scale only by integer powers of radix,
  115. // so that we don't lose any precision due to round-off)
  116. var f = realone;
  117. var _c = colNorm;
  118. var rowDivRadix = divideScalar(rowNorm, radix);
  119. var rowMulRadix = multiplyScalar(rowNorm, radix);
  120. while (smaller(_c, rowDivRadix)) {
  121. _c = multiplyScalar(_c, radixSq);
  122. f = multiplyScalar(f, radix);
  123. }
  124. while (larger(_c, rowMulRadix)) {
  125. _c = divideScalar(_c, radixSq);
  126. f = divideScalar(f, radix);
  127. }
  128. // check whether balancing is needed
  129. // condition = (c + rowNorm) / f < 0.95 * (colNorm + rowNorm)
  130. var condition = smaller(divideScalar(addScalar(_c, rowNorm), f), multiplyScalar(addScalar(colNorm, rowNorm), 0.95));
  131. // apply balancing similarity transformation
  132. if (condition) {
  133. // we should loop once again to check whether
  134. // another rebalancing is needed
  135. last = false;
  136. var g = divideScalar(1, f);
  137. for (var _j = 0; _j < N; _j++) {
  138. if (i === _j) {
  139. continue;
  140. }
  141. arr[i][_j] = multiplyScalar(arr[i][_j], f);
  142. arr[_j][i] = multiplyScalar(arr[_j][i], g);
  143. }
  144. // keep track of transformations
  145. if (findVectors) {
  146. Rdiag[i] = multiplyScalar(Rdiag[i], f);
  147. }
  148. }
  149. }
  150. }
  151. }
  152. // return the diagonal row transformation matrix
  153. return diag(Rdiag);
  154. }
  155. /**
  156. * @param {number[][]} arr
  157. * @param {number} N
  158. * @param {number} prec
  159. * @param {'number'|'BigNumber'|'Complex'} type
  160. * @param {boolean} findVectors
  161. * @param {number[][]} R the row transformation matrix that will be modified
  162. */
  163. function reduceToHessenberg(arr, N, prec, type, findVectors, R) {
  164. var big = type === 'BigNumber';
  165. var cplx = type === 'Complex';
  166. var zero = big ? bignumber(0) : cplx ? complex(0) : 0;
  167. if (big) {
  168. prec = bignumber(prec);
  169. }
  170. for (var i = 0; i < N - 2; i++) {
  171. // Find the largest subdiag element in the i-th col
  172. var maxIndex = 0;
  173. var max = zero;
  174. for (var j = i + 1; j < N; j++) {
  175. var el = arr[j][i];
  176. if (smaller(abs(max), abs(el))) {
  177. max = el;
  178. maxIndex = j;
  179. }
  180. }
  181. // This col is pivoted, no need to do anything
  182. if (smaller(abs(max), prec)) {
  183. continue;
  184. }
  185. if (maxIndex !== i + 1) {
  186. // Interchange maxIndex-th and (i+1)-th row
  187. var tmp1 = arr[maxIndex];
  188. arr[maxIndex] = arr[i + 1];
  189. arr[i + 1] = tmp1;
  190. // Interchange maxIndex-th and (i+1)-th column
  191. for (var _j2 = 0; _j2 < N; _j2++) {
  192. var tmp2 = arr[_j2][maxIndex];
  193. arr[_j2][maxIndex] = arr[_j2][i + 1];
  194. arr[_j2][i + 1] = tmp2;
  195. }
  196. // keep track of transformations
  197. if (findVectors) {
  198. var tmp3 = R[maxIndex];
  199. R[maxIndex] = R[i + 1];
  200. R[i + 1] = tmp3;
  201. }
  202. }
  203. // Reduce following rows and columns
  204. for (var _j3 = i + 2; _j3 < N; _j3++) {
  205. var n = divideScalar(arr[_j3][i], max);
  206. if (n === 0) {
  207. continue;
  208. }
  209. // from j-th row subtract n-times (i+1)th row
  210. for (var k = 0; k < N; k++) {
  211. arr[_j3][k] = subtract(arr[_j3][k], multiplyScalar(n, arr[i + 1][k]));
  212. }
  213. // to (i+1)th column add n-times j-th column
  214. for (var _k = 0; _k < N; _k++) {
  215. arr[_k][i + 1] = addScalar(arr[_k][i + 1], multiplyScalar(n, arr[_k][_j3]));
  216. }
  217. // keep track of transformations
  218. if (findVectors) {
  219. for (var _k2 = 0; _k2 < N; _k2++) {
  220. R[_j3][_k2] = subtract(R[_j3][_k2], multiplyScalar(n, R[i + 1][_k2]));
  221. }
  222. }
  223. }
  224. }
  225. return R;
  226. }
  227. /**
  228. * @returns {{values: values, C: Matrix}}
  229. * @see Press, Wiliams: Numerical recipes in Fortran 77
  230. * @see https://en.wikipedia.org/wiki/QR_algorithm
  231. */
  232. function iterateUntilTriangular(A, N, prec, type, findVectors) {
  233. var big = type === 'BigNumber';
  234. var cplx = type === 'Complex';
  235. var one = big ? bignumber(1) : cplx ? complex(1) : 1;
  236. if (big) {
  237. prec = bignumber(prec);
  238. }
  239. // The Francis Algorithm
  240. // The core idea of this algorithm is that doing successive
  241. // A' = Q⁺AQ transformations will eventually converge to block-
  242. // upper-triangular with diagonal blocks either 1x1 or 2x2.
  243. // The Q here is the one from the QR decomposition, A = QR.
  244. // Since the eigenvalues of a block-upper-triangular matrix are
  245. // the eigenvalues of its diagonal blocks and we know how to find
  246. // eigenvalues of a 2x2 matrix, we know the eigenvalues of A.
  247. var arr = clone(A);
  248. // the list of converged eigenvalues
  249. var lambdas = [];
  250. // size of arr, which will get smaller as eigenvalues converge
  251. var n = N;
  252. // the diagonal of the block-diagonal matrix that turns
  253. // converged 2x2 matrices into upper triangular matrices
  254. var Sdiag = [];
  255. // N×N matrix describing the overall transformation done during the QR algorithm
  256. var Qtotal = findVectors ? diag(Array(N).fill(one)) : undefined;
  257. // n×n matrix describing the QR transformations done since last convergence
  258. var Qpartial = findVectors ? diag(Array(n).fill(one)) : undefined;
  259. // last eigenvalue converged before this many steps
  260. var lastConvergenceBefore = 0;
  261. while (lastConvergenceBefore <= 100) {
  262. lastConvergenceBefore += 1;
  263. // TODO if the convergence is slow, do something clever
  264. // Perform the factorization
  265. var k = 0; // TODO set close to an eigenvalue
  266. for (var i = 0; i < n; i++) {
  267. arr[i][i] = subtract(arr[i][i], k);
  268. }
  269. // TODO do an implicit QR transformation
  270. var {
  271. Q,
  272. R
  273. } = qr(arr);
  274. arr = multiply(R, Q);
  275. for (var _i = 0; _i < n; _i++) {
  276. arr[_i][_i] = addScalar(arr[_i][_i], k);
  277. }
  278. // keep track of transformations
  279. if (findVectors) {
  280. Qpartial = multiply(Qpartial, Q);
  281. }
  282. // The rightmost diagonal element converged to an eigenvalue
  283. if (n === 1 || smaller(abs(arr[n - 1][n - 2]), prec)) {
  284. lastConvergenceBefore = 0;
  285. lambdas.push(arr[n - 1][n - 1]);
  286. // keep track of transformations
  287. if (findVectors) {
  288. Sdiag.unshift([[1]]);
  289. inflateMatrix(Qpartial, N);
  290. Qtotal = multiply(Qtotal, Qpartial);
  291. if (n > 1) {
  292. Qpartial = diag(Array(n - 1).fill(one));
  293. }
  294. }
  295. // reduce the matrix size
  296. n -= 1;
  297. arr.pop();
  298. for (var _i2 = 0; _i2 < n; _i2++) {
  299. arr[_i2].pop();
  300. }
  301. // The rightmost diagonal 2x2 block converged
  302. } else if (n === 2 || smaller(abs(arr[n - 2][n - 3]), prec)) {
  303. lastConvergenceBefore = 0;
  304. var ll = eigenvalues2x2(arr[n - 2][n - 2], arr[n - 2][n - 1], arr[n - 1][n - 2], arr[n - 1][n - 1]);
  305. lambdas.push(...ll);
  306. // keep track of transformations
  307. if (findVectors) {
  308. Sdiag.unshift(jordanBase2x2(arr[n - 2][n - 2], arr[n - 2][n - 1], arr[n - 1][n - 2], arr[n - 1][n - 1], ll[0], ll[1], prec, type));
  309. inflateMatrix(Qpartial, N);
  310. Qtotal = multiply(Qtotal, Qpartial);
  311. if (n > 2) {
  312. Qpartial = diag(Array(n - 2).fill(one));
  313. }
  314. }
  315. // reduce the matrix size
  316. n -= 2;
  317. arr.pop();
  318. arr.pop();
  319. for (var _i3 = 0; _i3 < n; _i3++) {
  320. arr[_i3].pop();
  321. arr[_i3].pop();
  322. }
  323. }
  324. if (n === 0) {
  325. break;
  326. }
  327. }
  328. // standard sorting
  329. lambdas.sort((a, b) => +subtract(abs(a), abs(b)));
  330. // the algorithm didn't converge
  331. if (lastConvergenceBefore > 100) {
  332. var err = Error('The eigenvalues failed to converge. Only found these eigenvalues: ' + lambdas.join(', '));
  333. err.values = lambdas;
  334. err.vectors = [];
  335. throw err;
  336. }
  337. // combine the overall QR transformation Qtotal with the subsequent
  338. // transformation S that turns the diagonal 2x2 blocks to upper triangular
  339. var C = findVectors ? multiply(Qtotal, blockDiag(Sdiag, N)) : undefined;
  340. return {
  341. values: lambdas,
  342. C
  343. };
  344. }
  345. /**
  346. * @param {Matrix} A hessenberg-form matrix
  347. * @param {number} N size of A
  348. * @param {Matrix} C column transformation matrix that turns A into upper triangular
  349. * @param {Matrix} R similarity that turns original matrix into A
  350. * @param {number[]} values array of eigenvalues of A
  351. * @param {'number'|'BigNumber'|'Complex'} type
  352. * @returns {number[][]} eigenvalues
  353. */
  354. function findEigenvectors(A, N, C, R, values, prec, type) {
  355. var Cinv = inv(C);
  356. var U = multiply(Cinv, A, C);
  357. var big = type === 'BigNumber';
  358. var cplx = type === 'Complex';
  359. var zero = big ? bignumber(0) : cplx ? complex(0) : 0;
  360. var one = big ? bignumber(1) : cplx ? complex(1) : 1;
  361. // turn values into a kind of "multiset"
  362. // this way it is easier to find eigenvectors
  363. var uniqueValues = [];
  364. var multiplicities = [];
  365. for (var λ of values) {
  366. var i = indexOf(uniqueValues, λ, equal);
  367. if (i === -1) {
  368. uniqueValues.push(λ);
  369. multiplicities.push(1);
  370. } else {
  371. multiplicities[i] += 1;
  372. }
  373. }
  374. // find eigenvectors by solving U − λE = 0
  375. // TODO replace with an iterative eigenvector algorithm
  376. // (this one might fail for imprecise eigenvalues)
  377. var vectors = [];
  378. var len = uniqueValues.length;
  379. var b = Array(N).fill(zero);
  380. var E = diag(Array(N).fill(one));
  381. // eigenvalues for which usolve failed (due to numerical error)
  382. var failedLambdas = [];
  383. var _loop = function _loop(_i4) {
  384. var λ = uniqueValues[_i4];
  385. var S = subtract(U, multiply(λ, E)); // the characteristic matrix
  386. var solutions = usolveAll(S, b);
  387. solutions.shift(); // ignore the null vector
  388. // looks like we missed something, try inverse iteration
  389. while (solutions.length < multiplicities[_i4]) {
  390. var approxVec = inverseIterate(S, N, solutions, prec, type);
  391. if (approxVec == null) {
  392. // no more vectors were found
  393. failedLambdas.push(λ);
  394. break;
  395. }
  396. solutions.push(approxVec);
  397. }
  398. // Transform back into original array coordinates
  399. var correction = multiply(inv(R), C);
  400. solutions = solutions.map(v => multiply(correction, v));
  401. vectors.push(...solutions.map(v => flatten(v)));
  402. };
  403. for (var _i4 = 0; _i4 < len; _i4++) {
  404. _loop(_i4);
  405. }
  406. if (failedLambdas.length !== 0) {
  407. var err = new Error('Failed to find eigenvectors for the following eigenvalues: ' + failedLambdas.join(', '));
  408. err.values = values;
  409. err.vectors = vectors;
  410. throw err;
  411. }
  412. return vectors;
  413. }
  414. /**
  415. * Compute the eigenvalues of an 2x2 matrix
  416. * @return {[number,number]}
  417. */
  418. function eigenvalues2x2(a, b, c, d) {
  419. // λ± = ½ trA ± ½ √( tr²A - 4 detA )
  420. var trA = addScalar(a, d);
  421. var detA = subtract(multiplyScalar(a, d), multiplyScalar(b, c));
  422. var x = multiplyScalar(trA, 0.5);
  423. var y = multiplyScalar(sqrt(subtract(multiplyScalar(trA, trA), multiplyScalar(4, detA))), 0.5);
  424. return [addScalar(x, y), subtract(x, y)];
  425. }
  426. /**
  427. * For an 2x2 matrix compute the transformation matrix S,
  428. * so that SAS⁻¹ is an upper triangular matrix
  429. * @return {[[number,number],[number,number]]}
  430. * @see https://math.berkeley.edu/~ogus/old/Math_54-05/webfoils/jordan.pdf
  431. * @see http://people.math.harvard.edu/~knill/teaching/math21b2004/exhibits/2dmatrices/index.html
  432. */
  433. function jordanBase2x2(a, b, c, d, l1, l2, prec, type) {
  434. var big = type === 'BigNumber';
  435. var cplx = type === 'Complex';
  436. var zero = big ? bignumber(0) : cplx ? complex(0) : 0;
  437. var one = big ? bignumber(1) : cplx ? complex(1) : 1;
  438. // matrix is already upper triangular
  439. // return an identity matrix
  440. if (smaller(abs(c), prec)) {
  441. return [[one, zero], [zero, one]];
  442. }
  443. // matrix is diagonalizable
  444. // return its eigenvectors as columns
  445. if (larger(abs(subtract(l1, l2)), prec)) {
  446. return [[subtract(l1, d), subtract(l2, d)], [c, c]];
  447. }
  448. // matrix is not diagonalizable
  449. // compute off-diagonal elements of N = A - λI
  450. // N₁₂ = 0 ⇒ S = ( N⃗₁, I⃗₁ )
  451. // N₁₂ ≠ 0 ⇒ S = ( N⃗₂, I⃗₂ )
  452. var na = subtract(a, l1);
  453. var nb = subtract(b, l1);
  454. var nc = subtract(c, l1);
  455. var nd = subtract(d, l1);
  456. if (smaller(abs(nb), prec)) {
  457. return [[na, one], [nc, zero]];
  458. } else {
  459. return [[nb, zero], [nd, one]];
  460. }
  461. }
  462. /**
  463. * Enlarge the matrix from n×n to N×N, setting the new
  464. * elements to 1 on diagonal and 0 elsewhere
  465. */
  466. function inflateMatrix(arr, N) {
  467. // add columns
  468. for (var i = 0; i < arr.length; i++) {
  469. arr[i].push(...Array(N - arr[i].length).fill(0));
  470. }
  471. // add rows
  472. for (var _i5 = arr.length; _i5 < N; _i5++) {
  473. arr.push(Array(N).fill(0));
  474. arr[_i5][_i5] = 1;
  475. }
  476. return arr;
  477. }
  478. /**
  479. * Create a block-diagonal matrix with the given square matrices on the diagonal
  480. * @param {Matrix[] | number[][][]} arr array of matrices to be placed on the diagonal
  481. * @param {number} N the size of the resulting matrix
  482. */
  483. function blockDiag(arr, N) {
  484. var M = [];
  485. for (var i = 0; i < N; i++) {
  486. M[i] = Array(N).fill(0);
  487. }
  488. var I = 0;
  489. for (var sub of arr) {
  490. var n = sub.length;
  491. for (var _i6 = 0; _i6 < n; _i6++) {
  492. for (var j = 0; j < n; j++) {
  493. M[I + _i6][I + j] = sub[_i6][j];
  494. }
  495. }
  496. I += n;
  497. }
  498. return M;
  499. }
  500. /**
  501. * Finds the index of an element in an array using a custom equality function
  502. * @template T
  503. * @param {Array<T>} arr array in which to search
  504. * @param {T} el the element to find
  505. * @param {function(T, T): boolean} fn the equality function, first argument is an element of `arr`, the second is always `el`
  506. * @returns {number} the index of `el`, or -1 when it's not in `arr`
  507. */
  508. function indexOf(arr, el, fn) {
  509. for (var i = 0; i < arr.length; i++) {
  510. if (fn(arr[i], el)) {
  511. return i;
  512. }
  513. }
  514. return -1;
  515. }
  516. /**
  517. * Provided a near-singular upper-triangular matrix A and a list of vectors,
  518. * finds an eigenvector of A with the smallest eigenvalue, which is orthogonal
  519. * to each vector in the list
  520. * @template T
  521. * @param {T[][]} A near-singular square matrix
  522. * @param {number} N dimension
  523. * @param {T[][]} orthog list of vectors
  524. * @param {number} prec epsilon
  525. * @param {'number'|'BigNumber'|'Complex'} type
  526. * @return {T[] | null} eigenvector
  527. *
  528. * @see Numerical Recipes for Fortran 77 – 11.7 Eigenvalues or Eigenvectors by Inverse Iteration
  529. */
  530. function inverseIterate(A, N, orthog, prec, type) {
  531. var largeNum = type === 'BigNumber' ? bignumber(1000) : 1000;
  532. var b; // the vector
  533. // you better choose a random vector before I count to five
  534. var i = 0;
  535. while (true) {
  536. b = randomOrthogonalVector(N, orthog, type);
  537. b = usolve(A, b);
  538. if (larger(norm(b), largeNum)) {
  539. break;
  540. }
  541. if (++i >= 5) {
  542. return null;
  543. }
  544. }
  545. // you better converge before I count to ten
  546. i = 0;
  547. while (true) {
  548. var c = usolve(A, b);
  549. if (smaller(norm(orthogonalComplement(b, [c])), prec)) {
  550. break;
  551. }
  552. if (++i >= 10) {
  553. return null;
  554. }
  555. b = normalize(c);
  556. }
  557. return b;
  558. }
  559. /**
  560. * Generates a random unit vector of dimension N, orthogonal to each vector in the list
  561. * @template T
  562. * @param {number} N dimension
  563. * @param {T[][]} orthog list of vectors
  564. * @param {'number'|'BigNumber'|'Complex'} type
  565. * @returns {T[]} random vector
  566. */
  567. function randomOrthogonalVector(N, orthog, type) {
  568. var big = type === 'BigNumber';
  569. var cplx = type === 'Complex';
  570. // generate random vector with the correct type
  571. var v = Array(N).fill(0).map(_ => 2 * Math.random() - 1);
  572. if (big) {
  573. v = v.map(n => bignumber(n));
  574. }
  575. if (cplx) {
  576. v = v.map(n => complex(n));
  577. }
  578. // project to orthogonal complement
  579. v = orthogonalComplement(v, orthog);
  580. // normalize
  581. return normalize(v, type);
  582. }
  583. /**
  584. * Project vector v to the orthogonal complement of an array of vectors
  585. */
  586. function orthogonalComplement(v, orthog) {
  587. for (var w of orthog) {
  588. // v := v − (w, v)/∥w∥² w
  589. v = subtract(v, multiply(divideScalar(dot(w, v), dot(w, w)), w));
  590. }
  591. return v;
  592. }
  593. /**
  594. * Calculate the norm of a vector.
  595. * We can't use math.norm because factory can't handle circular dependency.
  596. * Seriously, I'm really fed up with factory.
  597. */
  598. function norm(v) {
  599. return abs(sqrt(dot(v, v)));
  600. }
  601. /**
  602. * Normalize a vector
  603. * @template T
  604. * @param {T[]} v
  605. * @param {'number'|'BigNumber'|'Complex'} type
  606. * @returns {T[]} normalized vec
  607. */
  608. function normalize(v, type) {
  609. var big = type === 'BigNumber';
  610. var cplx = type === 'Complex';
  611. var one = big ? bignumber(1) : cplx ? complex(1) : 1;
  612. return multiply(divideScalar(one, norm(v)), v);
  613. }
  614. return complexEigs;
  615. }