csAmd.js 18 KB

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