simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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;