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