csAmd.js 18 KB

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