csAmd.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. import { factory } from '../../../utils/factory.js';
  2. import { csFkeep } from './csFkeep.js';
  3. import { csFlip } from './csFlip.js';
  4. import { csTdfs } from './csTdfs.js';
  5. var name = 'csAmd';
  6. var dependencies = ['add', 'multiply', 'transpose'];
  7. export var createCsAmd = /* #__PURE__ */factory(name, dependencies, _ref => {
  8. var {
  9. add,
  10. multiply,
  11. transpose
  12. } = _ref;
  13. /**
  14. * Approximate minimum degree ordering. The minimum degree algorithm is a widely used
  15. * heuristic for finding a permutation P so that P*A*P' has fewer nonzeros in its factorization
  16. * than A. It is a gready method that selects the sparsest pivot row and column during the course
  17. * of a right looking sparse Cholesky factorization.
  18. *
  19. * Reference: http://faculty.cse.tamu.edu/davis/publications.html
  20. *
  21. * @param {Number} order 0: Natural, 1: Cholesky, 2: LU, 3: QR
  22. * @param {Matrix} m Sparse Matrix
  23. */
  24. return function csAmd(order, a) {
  25. // check input parameters
  26. if (!a || order <= 0 || order > 3) {
  27. return null;
  28. }
  29. // a matrix arrays
  30. var asize = a._size;
  31. // rows and columns
  32. var m = asize[0];
  33. var n = asize[1];
  34. // initialize vars
  35. var lemax = 0;
  36. // dense threshold
  37. var dense = Math.max(16, 10 * Math.sqrt(n));
  38. dense = Math.min(n - 2, dense);
  39. // create target matrix C
  40. var cm = _createTargetMatrix(order, a, m, n, dense);
  41. // drop diagonal entries
  42. csFkeep(cm, _diag, null);
  43. // C matrix arrays
  44. var cindex = cm._index;
  45. var cptr = cm._ptr;
  46. // number of nonzero elements in C
  47. var cnz = cptr[n];
  48. // allocate result (n+1)
  49. var P = [];
  50. // create workspace (8 * (n + 1))
  51. var W = [];
  52. var len = 0; // first n + 1 entries
  53. var nv = n + 1; // next n + 1 entries
  54. var next = 2 * (n + 1); // next n + 1 entries
  55. var head = 3 * (n + 1); // next n + 1 entries
  56. var elen = 4 * (n + 1); // next n + 1 entries
  57. var degree = 5 * (n + 1); // next n + 1 entries
  58. var w = 6 * (n + 1); // next n + 1 entries
  59. var hhead = 7 * (n + 1); // last n + 1 entries
  60. // use P as workspace for last
  61. var last = P;
  62. // initialize quotient graph
  63. var mark = _initializeQuotientGraph(n, cptr, W, len, head, last, next, hhead, nv, w, elen, degree);
  64. // initialize degree lists
  65. var nel = _initializeDegreeLists(n, cptr, W, degree, elen, w, dense, nv, head, last, next);
  66. // minimum degree node
  67. var mindeg = 0;
  68. // vars
  69. var i, j, k, k1, k2, e, pj, ln, nvi, pk, eln, p1, p2, pn, h, d;
  70. // while (selecting pivots) do
  71. while (nel < n) {
  72. // select node of minimum approximate degree. amd() is now ready to start eliminating the graph. It first
  73. // finds a node k of minimum degree and removes it from its degree list. The variable nel keeps track of thow
  74. // many nodes have been eliminated.
  75. for (k = -1; mindeg < n && (k = W[head + mindeg]) === -1; mindeg++) {
  76. ;
  77. }
  78. if (W[next + k] !== -1) {
  79. last[W[next + k]] = -1;
  80. }
  81. // remove k from degree list
  82. W[head + mindeg] = W[next + k];
  83. // elenk = |Ek|
  84. var elenk = W[elen + k];
  85. // # of nodes k represents
  86. var nvk = W[nv + k];
  87. // W[nv + k] nodes of A eliminated
  88. nel += nvk;
  89. // Construct a new element. The new element Lk is constructed in place if |Ek| = 0. nv[i] is
  90. // negated for all nodes i in Lk to flag them as members of this set. Each node i is removed from the
  91. // degree lists. All elements e in Ek are absorved into element k.
  92. var dk = 0;
  93. // flag k as in Lk
  94. W[nv + k] = -nvk;
  95. var p = cptr[k];
  96. // do in place if W[elen + k] === 0
  97. var pk1 = elenk === 0 ? p : cnz;
  98. var pk2 = pk1;
  99. for (k1 = 1; k1 <= elenk + 1; k1++) {
  100. if (k1 > elenk) {
  101. // search the nodes in k
  102. e = k;
  103. // list of nodes starts at cindex[pj]
  104. pj = p;
  105. // length of list of nodes in k
  106. ln = W[len + k] - elenk;
  107. } else {
  108. // search the nodes in e
  109. e = cindex[p++];
  110. pj = cptr[e];
  111. // length of list of nodes in e
  112. ln = W[len + e];
  113. }
  114. for (k2 = 1; k2 <= ln; k2++) {
  115. i = cindex[pj++];
  116. // check node i dead, or seen
  117. if ((nvi = W[nv + i]) <= 0) {
  118. continue;
  119. }
  120. // W[degree + Lk] += size of node i
  121. dk += nvi;
  122. // negate W[nv + i] to denote i in Lk
  123. W[nv + i] = -nvi;
  124. // place i in Lk
  125. cindex[pk2++] = i;
  126. if (W[next + i] !== -1) {
  127. last[W[next + i]] = last[i];
  128. }
  129. // check we need to remove i from degree list
  130. if (last[i] !== -1) {
  131. W[next + last[i]] = W[next + i];
  132. } else {
  133. W[head + W[degree + i]] = W[next + i];
  134. }
  135. }
  136. if (e !== k) {
  137. // absorb e into k
  138. cptr[e] = csFlip(k);
  139. // e is now a dead element
  140. W[w + e] = 0;
  141. }
  142. }
  143. // cindex[cnz...nzmax] is free
  144. if (elenk !== 0) {
  145. cnz = pk2;
  146. }
  147. // external degree of k - |Lk\i|
  148. W[degree + k] = dk;
  149. // element k is in cindex[pk1..pk2-1]
  150. cptr[k] = pk1;
  151. W[len + k] = pk2 - pk1;
  152. // k is now an element
  153. W[elen + k] = -2;
  154. // Find set differences. The scan1 function now computes the set differences |Le \ Lk| for all elements e. At the start of the
  155. // scan, no entry in the w array is greater than or equal to mark.
  156. // clear w if necessary
  157. mark = _wclear(mark, lemax, W, w, n);
  158. // scan 1: find |Le\Lk|
  159. for (pk = pk1; pk < pk2; pk++) {
  160. i = cindex[pk];
  161. // check if W[elen + i] empty, skip it
  162. if ((eln = W[elen + i]) <= 0) {
  163. continue;
  164. }
  165. // W[nv + i] was negated
  166. nvi = -W[nv + i];
  167. var wnvi = mark - nvi;
  168. // scan Ei
  169. for (p = cptr[i], p1 = cptr[i] + eln - 1; p <= p1; p++) {
  170. e = cindex[p];
  171. if (W[w + e] >= mark) {
  172. // decrement |Le\Lk|
  173. W[w + e] -= nvi;
  174. } else if (W[w + e] !== 0) {
  175. // ensure e is a live element, 1st time e seen in scan 1
  176. W[w + e] = W[degree + e] + wnvi;
  177. }
  178. }
  179. }
  180. // degree update
  181. // The second pass computes the approximate degree di, prunes the sets Ei and Ai, and computes a hash
  182. // function h(i) for all nodes in Lk.
  183. // scan2: degree update
  184. for (pk = pk1; pk < pk2; pk++) {
  185. // consider node i in Lk
  186. i = cindex[pk];
  187. p1 = cptr[i];
  188. p2 = p1 + W[elen + i] - 1;
  189. pn = p1;
  190. // scan Ei
  191. for (h = 0, d = 0, p = p1; p <= p2; p++) {
  192. e = cindex[p];
  193. // check e is an unabsorbed element
  194. if (W[w + e] !== 0) {
  195. // dext = |Le\Lk|
  196. var dext = W[w + e] - mark;
  197. if (dext > 0) {
  198. // sum up the set differences
  199. d += dext;
  200. // keep e in Ei
  201. cindex[pn++] = e;
  202. // compute the hash of node i
  203. h += e;
  204. } else {
  205. // aggressive absorb. e->k
  206. cptr[e] = csFlip(k);
  207. // e is a dead element
  208. W[w + e] = 0;
  209. }
  210. }
  211. }
  212. // W[elen + i] = |Ei|
  213. W[elen + i] = pn - p1 + 1;
  214. var p3 = pn;
  215. var p4 = p1 + W[len + i];
  216. // prune edges in Ai
  217. for (p = p2 + 1; p < p4; p++) {
  218. j = cindex[p];
  219. // check node j dead or in Lk
  220. var nvj = W[nv + j];
  221. if (nvj <= 0) {
  222. continue;
  223. }
  224. // degree(i) += |j|
  225. d += nvj;
  226. // place j in node list of i
  227. cindex[pn++] = j;
  228. // compute hash for node i
  229. h += j;
  230. }
  231. // check for mass elimination
  232. if (d === 0) {
  233. // absorb i into k
  234. cptr[i] = csFlip(k);
  235. nvi = -W[nv + i];
  236. // |Lk| -= |i|
  237. dk -= nvi;
  238. // |k| += W[nv + i]
  239. nvk += nvi;
  240. nel += nvi;
  241. W[nv + i] = 0;
  242. // node i is dead
  243. W[elen + i] = -1;
  244. } else {
  245. // update degree(i)
  246. W[degree + i] = Math.min(W[degree + i], d);
  247. // move first node to end
  248. cindex[pn] = cindex[p3];
  249. // move 1st el. to end of Ei
  250. cindex[p3] = cindex[p1];
  251. // add k as 1st element in of Ei
  252. cindex[p1] = k;
  253. // new len of adj. list of node i
  254. W[len + i] = pn - p1 + 1;
  255. // finalize hash of i
  256. h = (h < 0 ? -h : h) % n;
  257. // place i in hash bucket
  258. W[next + i] = W[hhead + h];
  259. W[hhead + h] = i;
  260. // save hash of i in last[i]
  261. last[i] = h;
  262. }
  263. }
  264. // finalize |Lk|
  265. W[degree + k] = dk;
  266. lemax = Math.max(lemax, dk);
  267. // clear w
  268. mark = _wclear(mark + lemax, lemax, W, w, n);
  269. // Supernode detection. Supernode detection relies on the hash function h(i) computed for each node i.
  270. // If two nodes have identical adjacency lists, their hash functions wil be identical.
  271. for (pk = pk1; pk < pk2; pk++) {
  272. i = cindex[pk];
  273. // check i is dead, skip it
  274. if (W[nv + i] >= 0) {
  275. continue;
  276. }
  277. // scan hash bucket of node i
  278. h = last[i];
  279. i = W[hhead + h];
  280. // hash bucket will be empty
  281. W[hhead + h] = -1;
  282. for (; i !== -1 && W[next + i] !== -1; i = W[next + i], mark++) {
  283. ln = W[len + i];
  284. eln = W[elen + i];
  285. for (p = cptr[i] + 1; p <= cptr[i] + ln - 1; p++) {
  286. W[w + cindex[p]] = mark;
  287. }
  288. var jlast = i;
  289. // compare i with all j
  290. for (j = W[next + i]; j !== -1;) {
  291. var ok = W[len + j] === ln && W[elen + j] === eln;
  292. for (p = cptr[j] + 1; ok && p <= cptr[j] + ln - 1; p++) {
  293. // compare i and j
  294. if (W[w + cindex[p]] !== mark) {
  295. ok = 0;
  296. }
  297. }
  298. // check i and j are identical
  299. if (ok) {
  300. // absorb j into i
  301. cptr[j] = csFlip(i);
  302. W[nv + i] += W[nv + j];
  303. W[nv + j] = 0;
  304. // node j is dead
  305. W[elen + j] = -1;
  306. // delete j from hash bucket
  307. j = W[next + j];
  308. W[next + jlast] = j;
  309. } else {
  310. // j and i are different
  311. jlast = j;
  312. j = W[next + j];
  313. }
  314. }
  315. }
  316. }
  317. // Finalize new element. The elimination of node k is nearly complete. All nodes i in Lk are scanned one last time.
  318. // Node i is removed from Lk if it is dead. The flagged status of nv[i] is cleared.
  319. for (p = pk1, pk = pk1; pk < pk2; pk++) {
  320. i = cindex[pk];
  321. // check i is dead, skip it
  322. if ((nvi = -W[nv + i]) <= 0) {
  323. continue;
  324. }
  325. // restore W[nv + i]
  326. W[nv + i] = nvi;
  327. // compute external degree(i)
  328. d = W[degree + i] + dk - nvi;
  329. d = Math.min(d, n - nel - nvi);
  330. if (W[head + d] !== -1) {
  331. last[W[head + d]] = i;
  332. }
  333. // put i back in degree list
  334. W[next + i] = W[head + d];
  335. last[i] = -1;
  336. W[head + d] = i;
  337. // find new minimum degree
  338. mindeg = Math.min(mindeg, d);
  339. W[degree + i] = d;
  340. // place i in Lk
  341. cindex[p++] = i;
  342. }
  343. // # nodes absorbed into k
  344. W[nv + k] = nvk;
  345. // length of adj list of element k
  346. if ((W[len + k] = p - pk1) === 0) {
  347. // k is a root of the tree
  348. cptr[k] = -1;
  349. // k is now a dead element
  350. W[w + k] = 0;
  351. }
  352. if (elenk !== 0) {
  353. // free unused space in Lk
  354. cnz = p;
  355. }
  356. }
  357. // Postordering. The elimination is complete, but no permutation has been computed. All that is left
  358. // of the graph is the assembly tree (ptr) and a set of dead nodes and elements (i is a dead node if
  359. // nv[i] is zero and a dead element if nv[i] > 0). It is from this information only that the final permutation
  360. // is computed. The tree is restored by unflipping all of ptr.
  361. // fix assembly tree
  362. for (i = 0; i < n; i++) {
  363. cptr[i] = csFlip(cptr[i]);
  364. }
  365. for (j = 0; j <= n; j++) {
  366. W[head + j] = -1;
  367. }
  368. // place unordered nodes in lists
  369. for (j = n; j >= 0; j--) {
  370. // skip if j is an element
  371. if (W[nv + j] > 0) {
  372. continue;
  373. }
  374. // place j in list of its parent
  375. W[next + j] = W[head + cptr[j]];
  376. W[head + cptr[j]] = j;
  377. }
  378. // place elements in lists
  379. for (e = n; e >= 0; e--) {
  380. // skip unless e is an element
  381. if (W[nv + e] <= 0) {
  382. continue;
  383. }
  384. if (cptr[e] !== -1) {
  385. // place e in list of its parent
  386. W[next + e] = W[head + cptr[e]];
  387. W[head + cptr[e]] = e;
  388. }
  389. }
  390. // postorder the assembly tree
  391. for (k = 0, i = 0; i <= n; i++) {
  392. if (cptr[i] === -1) {
  393. k = csTdfs(i, k, W, head, next, P, w);
  394. }
  395. }
  396. // remove last item in array
  397. P.splice(P.length - 1, 1);
  398. // return P
  399. return P;
  400. };
  401. /**
  402. * Creates the matrix that will be used by the approximate minimum degree ordering algorithm. The function accepts the matrix M as input and returns a permutation
  403. * vector P. The amd algorithm operates on a symmetrix matrix, so one of three symmetric matrices is formed.
  404. *
  405. * Order: 0
  406. * A natural ordering P=null matrix is returned.
  407. *
  408. * Order: 1
  409. * Matrix must be square. This is appropriate for a Cholesky or LU factorization.
  410. * P = M + M'
  411. *
  412. * Order: 2
  413. * Dense columns from M' are dropped, M recreated from M'. This is appropriatefor LU factorization of unsymmetric matrices.
  414. * P = M' * M
  415. *
  416. * Order: 3
  417. * This is best used for QR factorization or LU factorization is matrix M has no dense rows. A dense row is a row with more than 10*sqr(columns) entries.
  418. * P = M' * M
  419. */
  420. function _createTargetMatrix(order, a, m, n, dense) {
  421. // compute A'
  422. var at = transpose(a);
  423. // check order = 1, matrix must be square
  424. if (order === 1 && n === m) {
  425. // C = A + A'
  426. return add(a, at);
  427. }
  428. // check order = 2, drop dense columns from M'
  429. if (order === 2) {
  430. // transpose arrays
  431. var tindex = at._index;
  432. var tptr = at._ptr;
  433. // new column index
  434. var p2 = 0;
  435. // loop A' columns (rows)
  436. for (var j = 0; j < m; j++) {
  437. // column j of AT starts here
  438. var p = tptr[j];
  439. // new column j starts here
  440. tptr[j] = p2;
  441. // skip dense col j
  442. if (tptr[j + 1] - p > dense) {
  443. continue;
  444. }
  445. // map rows in column j of A
  446. for (var p1 = tptr[j + 1]; p < p1; p++) {
  447. tindex[p2++] = tindex[p];
  448. }
  449. }
  450. // finalize AT
  451. tptr[m] = p2;
  452. // recreate A from new transpose matrix
  453. a = transpose(at);
  454. // use A' * A
  455. return multiply(at, a);
  456. }
  457. // use A' * A, square or rectangular matrix
  458. return multiply(at, a);
  459. }
  460. /**
  461. * Initialize quotient graph. There are four kind of nodes and elements that must be represented:
  462. *
  463. * - A live node is a node i (or a supernode) that has not been selected as a pivot nad has not been merged into another supernode.
  464. * - A dead node i is one that has been removed from the graph, having been absorved into r = flip(ptr[i]).
  465. * - A live element e is one that is in the graph, having been formed when node e was selected as the pivot.
  466. * - A dead element e is one that has benn absorved into a subsequent element s = flip(ptr[e]).
  467. */
  468. function _initializeQuotientGraph(n, cptr, W, len, head, last, next, hhead, nv, w, elen, degree) {
  469. // Initialize quotient graph
  470. for (var k = 0; k < n; k++) {
  471. W[len + k] = cptr[k + 1] - cptr[k];
  472. }
  473. W[len + n] = 0;
  474. // initialize workspace
  475. for (var i = 0; i <= n; i++) {
  476. // degree list i is empty
  477. W[head + i] = -1;
  478. last[i] = -1;
  479. W[next + i] = -1;
  480. // hash list i is empty
  481. W[hhead + i] = -1;
  482. // node i is just one node
  483. W[nv + i] = 1;
  484. // node i is alive
  485. W[w + i] = 1;
  486. // Ek of node i is empty
  487. W[elen + i] = 0;
  488. // degree of node i
  489. W[degree + i] = W[len + i];
  490. }
  491. // clear w
  492. var mark = _wclear(0, 0, W, w, n);
  493. // n is a dead element
  494. W[elen + n] = -2;
  495. // n is a root of assembly tree
  496. cptr[n] = -1;
  497. // n is a dead element
  498. W[w + n] = 0;
  499. // return mark
  500. return mark;
  501. }
  502. /**
  503. * Initialize degree lists. Each node is placed in its degree lists. Nodes of zero degree are eliminated immediately. Nodes with
  504. * degree >= dense are alsol eliminated and merged into a placeholder node n, a dead element. Thes nodes will appera last in the
  505. * output permutation p.
  506. */
  507. function _initializeDegreeLists(n, cptr, W, degree, elen, w, dense, nv, head, last, next) {
  508. // result
  509. var nel = 0;
  510. // loop columns
  511. for (var i = 0; i < n; i++) {
  512. // degree @ i
  513. var d = W[degree + i];
  514. // check node i is empty
  515. if (d === 0) {
  516. // element i is dead
  517. W[elen + i] = -2;
  518. nel++;
  519. // i is a root of assembly tree
  520. cptr[i] = -1;
  521. W[w + i] = 0;
  522. } else if (d > dense) {
  523. // absorb i into element n
  524. W[nv + i] = 0;
  525. // node i is dead
  526. W[elen + i] = -1;
  527. nel++;
  528. cptr[i] = csFlip(n);
  529. W[nv + n]++;
  530. } else {
  531. var h = W[head + d];
  532. if (h !== -1) {
  533. last[h] = i;
  534. }
  535. // put node i in degree list d
  536. W[next + i] = W[head + d];
  537. W[head + d] = i;
  538. }
  539. }
  540. return nel;
  541. }
  542. function _wclear(mark, lemax, W, w, n) {
  543. if (mark < 2 || mark + lemax < 0) {
  544. for (var k = 0; k < n; k++) {
  545. if (W[w + k] !== 0) {
  546. W[w + k] = 1;
  547. }
  548. }
  549. mark = 2;
  550. }
  551. // at this point, W [0..n-1] < mark holds
  552. return mark;
  553. }
  554. function _diag(i, j) {
  555. return i !== j;
  556. }
  557. });