/// <summary> /// Constructs the matrix. /// </summary> /// <param name="a">a.</param> /// <returns>SymbolicColumnStorage.</returns> internal static SymbolicColumnStorage ConstructMatrix(CompressedColumnStorage a) { var A = new SymbolicColumnStorage(a); // Compute A' var AT = A.Transpose(); // Return A+A' var result = A.Add(AT); // Drop diagonal entries. result.Keep(); return(result); }
/// <summary> /// Symbolic sum C = A + B /// </summary> /// <param name="other">column-compressed matrix</param> /// <returns>Sum C = A + B</returns> private SymbolicColumnStorage Add(SymbolicColumnStorage other) { var workspace = new int[this.nrows]; // Allocate result: (anz + bnz) is an upper bound var columnPointers = new int[other.ColumnPointers.Length]; var rowIndices = new int[ColumnPointers[ncols] + other.ColumnPointers[other.ncols]]; int nz = 0; for (int j = 0; j < other.ncols; j++) { // Column j of result starts here columnPointers[j] = nz; nz = Scatter(j, workspace, j + 1, rowIndices, nz); // A(:,j) nz = other.Scatter(j, workspace, j + 1, rowIndices, nz); // B(:,j) } // Finalize the last column columnPointers[other.ncols] = nz; // Remove extra space Array.Resize(ref rowIndices, nz); return(new SymbolicColumnStorage(this.nrows, other.ncols, columnPointers, rowIndices)); }
/// <summary> /// Generates the specified c. /// </summary> /// <param name="C">The c.</param> /// <param name="n">The n.</param> /// <returns>System.Int32[].</returns> internal static int[] Generate(SymbolicColumnStorage C, int n) { // int e, i, j, k; int j, k; int lemax = 0; int mindeg = 0; int nel = 0; var Cp = C.ColumnPointers; var cnz = Cp[n]; // Find dense threshold var dense = Math.Max(16, 10 * (int)Math.Sqrt(n)); dense = Math.Min(n - 2, dense); // add elbow room to C if (!C.Resize(cnz + cnz / 5 + 2 * n)) { return(null); } var P = new int[n + 1]; var W = new int[n + 1]; // get workspace var w = new int[n + 1]; var degree = new int[n + 1]; var elen = new int[n + 1]; // Initialized to 0's // Initialize quotient graph for (k = 0; k < n; k++) { W[k] = Cp[k + 1] - Cp[k]; } W[n] = 0; var nzmax = C.RowIndices.Length; var Ci = C.RowIndices; for (int i = 0; i <= n; i++) { P[i] = -1; w[i] = 1; // node i is alive degree[i] = W[i]; // degree of node i } var next = new int[n + 1]; var hhead = new int[n + 1]; var head = new int[n + 1]; var nv = new int[n + 1]; Array.Copy(P, next, n + 1); Array.Copy(P, head, n + 1); // degree list i is empty Array.Copy(P, hhead, n + 1); // hash list i is empty Array.Copy(w, nv, n + 1); // node i is just one node var mark = Clear(0, 0, w, n); // clear w elen[n] = -2; // n is a dead element Cp[n] = -1; // n is a root of assembly tree w[n] = 0; // n is a dead element // Initialize degree lists for (int i = 0; i < n; i++) { int d = degree[i]; if (d == 0) // node i is empty { elen[i] = -2; // element i is dead nel++; Cp[i] = -1; // i is a root of assembly tree w[i] = 0; } else if (d > dense) // node i is dense { nv[i] = 0; // absorb i into element n elen[i] = -1; // node i is dead nel++; Cp[i] = -(n + 2); // FLIP(n) nv[n]++; } else { if (head[d] != -1) { P[head[d]] = i; } next[i] = head[d]; // put node i in degree list d head[d] = i; } } while (nel < n) // while (selecting pivots) do { // Select node of minimum approximate degree for (k = -1; mindeg < n && (k = head[mindeg]) == -1; mindeg++) { ; } if (next[k] != -1) { P[next[k]] = -1; } head[mindeg] = next[k]; // remove k from degree list var elenk = elen[k]; var nvk = nv[k]; nel += nvk; // nv[k] nodes of A eliminated // Garbage collection int p; if (elenk > 0 && cnz + mindeg >= nzmax) { for (j = 0; j < n; j++) { if ((p = Cp[j]) >= 0) // j is a live node or element { Cp[j] = Ci[p]; // save first entry of object Ci[p] = -(j + 2); // first entry is now CS_FLIP(j) } } int q; for (q = 0, p = 0; p < cnz;) // scan all of memory { if ((j = flip(Ci[p++])) >= 0) // found object j { Ci[q] = Cp[j]; // restore first entry of object Cp[j] = q++; // new pointer to object j int k3; for (k3 = 0; k3 < W[j] - 1; k3++) { Ci[q++] = Ci[p++]; } } } cnz = q; // Ci [cnz...nzmax-1] now free } // Construct new element var dk = 0; nv[k] = -nvk; // flag k as in Lk p = Cp[k]; var pk1 = elenk == 0 ? p : cnz; var pk2 = pk1; int k1; int ln; int nvi; for (k1 = 1; k1 <= elenk + 1; k1++) { int pj, e; if (k1 > elenk) { e = k; // search the nodes in k pj = p; // list of nodes starts at Ci[pj]*/ ln = W[k] - elenk; // length of list of nodes in k } else { e = Ci[p++]; // search the nodes in e pj = Cp[e]; ln = W[e]; // length of list of nodes in e } int k2; for (k2 = 1; k2 <= ln; k2++) { int i = Ci[pj++]; if ((nvi = nv[i]) <= 0) { continue; // node i dead, or seen } dk += nvi; // degree[Lk] += size of node i nv[i] = -nvi; // negate nv[i] to denote i in Lk Ci[pk2++] = i; // place i in Lk if (next[i] != -1) { P[next[i]] = P[i]; } if (P[i] != -1) // remove i from degree list { next[P[i]] = next[i]; } else { head[degree[i]] = next[i]; } } if (e != k) { Cp[e] = -(k + 2); // absorb e into k // FLIP(k) w[e] = 0; // e is now a dead element } } if (elenk != 0) { cnz = pk2; // Ci [cnz...nzmax] is free } degree[k] = dk; // external degree of k - |Lk\i| Cp[k] = pk1; // element k is in Ci[pk1..pk2-1] W[k] = pk2 - pk1; elen[k] = -2; // k is now an element // Find set differences mark = Clear(mark, lemax, w, n); // clear w if necessary int pk; int eln; for (pk = pk1; pk < pk2; pk++) // scan 1: find |Le\Lk| { int i = Ci[pk]; if ((eln = elen[i]) <= 0) { continue; // skip if elen[i] empty } nvi = -nv[i]; // nv [i] was negated var wnvi = mark - nvi; for (p = Cp[i]; p <= Cp[i] + eln - 1; p++) // scan Ei { int e = Ci[p]; if (w[e] >= mark) { w[e] -= nvi; // decrement |Le\Lk| } else if (w[e] != 0) // ensure e is a live element { w[e] = degree[e] + wnvi; // 1st time e seen in scan 1 } } } // Degree update int h; for (pk = pk1; pk < pk2; pk++) // scan2: degree update { int i = Ci[pk]; // consider node i in Lk var p1 = Cp[i]; var p2 = p1 + elen[i] - 1; var pn = p1; var d = 0; for (h = 0, d = 0, p = p1; p <= p2; p++) // scan Ei { int e = Ci[p]; if (w[e] != 0) // e is an unabsorbed element { var dext = w[e] - mark; if (dext > 0) { d += dext; // sum up the set differences Ci[pn++] = e; // keep e in Ei h += e; // compute the hash of node i } else { Cp[e] = -(k + 2); // aggressive absorb. e.k // FLIP(k) w[e] = 0; // e is a dead element } } } elen[i] = pn - p1 + 1; // elen[i] = |Ei| var p3 = pn; var p4 = p1 + W[i]; for (p = p2 + 1; p < p4; p++) // prune edges in Ai { j = Ci[p]; int nvj; if ((nvj = nv[j]) <= 0) { continue; // node j dead or in Lk } d += nvj; // degree(i) += |j| Ci[pn++] = j; // place j in node list of i h += j; // compute hash for node i } if (d == 0) // check for mass elimination { Cp[i] = -(k + 2); // absorb i into k // FLIP(k) nvi = -nv[i]; dk -= nvi; // |Lk| -= |i| nvk += nvi; // |k| += nv[i] nel += nvi; nv[i] = 0; elen[i] = -1; // node i is dead } else { degree[i] = Math.Min(degree[i], d); // update degree(i) Ci[pn] = Ci[p3]; // move first node to end Ci[p3] = Ci[p1]; // move 1st el. to end of Ei Ci[p1] = k; // add k as 1st element in of Ei W[i] = pn - p1 + 1; // new len of adj. list of node i h = (h < 0 ? -h : h) % n; // finalize hash of i next[i] = hhead[h]; // place i in hash bucket hhead[h] = i; P[i] = h; // save hash of i in last[i] } } // scan2 is done degree[k] = dk; // finalize |Lk| lemax = Math.Max(lemax, dk); mark = Clear(mark + lemax, lemax, w, n); // clear w // Supernode detection for (pk = pk1; pk < pk2; pk++) { int i = Ci[pk]; if (nv[i] >= 0) { continue; // skip if i is dead } h = P[i]; // scan hash bucket of node i i = hhead[h]; hhead[h] = -1; // hash bucket will be empty for (; i != -1 && next[i] != -1; i = next[i], mark++) { ln = W[i]; eln = elen[i]; for (p = Cp[i] + 1; p <= Cp[i] + ln - 1; p++) { w[Ci[p]] = mark; } var jlast = i; for (j = next[i]; j != -1;) // compare i with all j { var ok = (W[j] == ln) && (elen[j] == eln); for (p = Cp[j] + 1; ok && p <= Cp[j] + ln - 1; p++) { if (w[Ci[p]] != mark) { ok = false; // compare i and j } } if (ok) // i and j are identical { Cp[j] = -(i + 2); // absorb j into i // FLIP(i) nv[i] += nv[j]; nv[j] = 0; elen[j] = -1; // node j is dead j = next[j]; // delete j from hash bucket next[jlast] = j; } else { jlast = j; // j and i are different j = next[j]; } } } } // Finalize new element for (p = pk1, pk = pk1; pk < pk2; pk++) // finalize Lk { int i = Ci[pk]; if ((nvi = -nv[i]) <= 0) { continue; // skip if i is dead } nv[i] = nvi; // restore nv[i] int d = degree[i] + dk - nvi; // compute external degree(i) d = Math.Min(d, n - nel - nvi); if (head[d] != -1) { P[head[d]] = i; } next[i] = head[d]; // put i back in degree list P[i] = -1; head[d] = i; mindeg = Math.Min(mindeg, d); // find new minimum degree degree[i] = d; Ci[p++] = i; // place i in Lk } nv[k] = nvk; // # nodes absorbed into k if ((W[k] = p - pk1) == 0) // length of adj list of element k { Cp[k] = -1; // k is a root of the tree w[k] = 0; // k is now a dead element } if (elenk != 0) { cnz = p; // free unused space in Lk } } // Postordering for (int i = 0; i < n; i++) { Cp[i] = -(Cp[i] + 2); // fix assembly tree // FLIP(Cp[i]) } for (j = 0; j <= n; j++) { head[j] = -1; } for (j = n; j >= 0; j--) // place unordered nodes in lists { if (nv[j] > 0) { continue; // skip if j is an element } next[j] = head[Cp[j]]; // place j in list of its parent head[Cp[j]] = j; } for (int e = n; e >= 0; e--) // place elements in lists { if (nv[e] <= 0) { continue; // skip unless e is an element } if (Cp[e] != -1) { next[e] = head[Cp[e]]; // place e in list of its parent head[Cp[e]] = e; } } k = 0; for (int i = 0; i <= n; i++) // postorder the assembly tree { if (Cp[i] == -1) { k = TreeDepthFirstSearch(i, k, head, next, P, w); } } return(P); }
/// <summary> /// Generate minimum degree ordering of A+A' (if A is symmetric) or A'A. /// </summary> /// <param name="A">Column-compressed matrix</param> /// <returns>amd(A+A') if A is symmetric, or amd(A'A) otherwise, null on /// error or for natural ordering</returns> /// <remarks>See Chapter 7.1 (Fill-reducing orderings: Minimum degree ordering) in /// "Direct Methods for Sparse Linear Systems" by Tim Davis.</remarks> internal static int[] Generate(CompressedColumnStorage A) { return(Generate(SymbolicColumnStorage.ConstructMatrix(A), A.ncols)); }