csAmd.js 18 KB

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