/// <summary> /// Drops entries from a sparse matrix /// </summary> private static int RowPrune(SymbolicColumnStorage A, int n, int[] rr) { int i, j, nz = 0; var ai = A.RowIndices; var ap = A.ColumnPointers; for (j = 0; j < n; j++) { i = ap[j]; // Record new location of col j. ap[j] = nz; for (; i < ap[j + 1]; i++) { if (ai[i] >= rr[1] && ai[i] < rr[2]) { // Keep A(i,j). ai[nz] = ai[i]; nz++; } } } // Record new nonzero count. ap[n] = nz; // Remove extra space. Array.Resize <int>(ref A.RowIndices, nz); return(nz); }
/// <summary> /// Permutes a sparse matrix, C = PAQ. /// </summary> /// <param name="A">m-by-n, column-compressed matrix</param> /// <param name="pinv">a permutation vector of length m</param> /// <param name="q">a permutation vector of length n</param> /// <returns>C = PAQ, null on error</returns> private SymbolicColumnStorage Permute(CompressedColumnStorage <T> A, int[] pinv, int[] q) { int t, j, k, nz = 0; int m = A.RowCount; int n = A.ColumnCount; var ap = A.ColumnPointers; var ai = A.RowIndices; var result = SymbolicColumnStorage.Create(A); var cp = result.ColumnPointers; var ci = result.RowIndices; for (k = 0; k < n; k++) { // Column k of C is column q[k] of A cp[k] = nz; j = q != null ? (q[k]) : k; for (t = ap[j]; t < ap[j + 1]; t++) { // Row i of A is row pinv[i] of C ci[nz++] = pinv != null ? (pinv[ai[t]]) : ai[t]; } } // Finalize the last column of C cp[n] = nz; return(result); }
/// <summary> /// Symbolic ordering and analysis for QR. /// </summary> /// <param name="A">Matrix to factorize.</param> /// <param name="p">Permutation.</param> protected void SymbolicAnalysis(CompressedColumnStorage <T> A, int[] p, bool natural) { int m = A.RowCount; int n = A.ColumnCount; var sym = this.S = new SymbolicFactorization(); // Fill-reducing ordering sym.q = p; var C = natural ? SymbolicColumnStorage.Create(A) : Permute(A, null, sym.q); // etree of C'*C, where C=A(:,q) sym.parent = GraphHelper.EliminationTree(m, n, C.ColumnPointers, C.RowIndices, true); int[] post = GraphHelper.TreePostorder(sym.parent, n); sym.cp = GraphHelper.ColumnCounts(C, sym.parent, post, true); // col counts chol(C'*C) bool ok = C != null && sym.parent != null && sym.cp != null && CountV(C, sym); if (ok) { sym.unz = 0; for (int k = 0; k < n; k++) { sym.unz += sym.cp[k]; } } }
/// <summary> /// Symbolic ordering and analysis for QR. /// </summary> private void SymbolicAnalysis(ColumnOrdering order, CompressedColumnStorage <double> A) { int m = A.RowCount; int n = A.ColumnCount; var sym = this.symFactor = new SymbolicFactorization(); // Fill-reducing ordering sym.q = AMD.Generate(A, order); var C = order > 0 ? Permute(A, null, sym.q) : SymbolicColumnStorage.Create(A); // etree of C'*C, where C=A(:,q) sym.parent = GraphHelper.EliminationTree(m, n, C.ColumnPointers, C.RowIndices, true); int[] post = GraphHelper.TreePostorder(sym.parent, n); sym.cp = GraphHelper.ColumnCounts(C, sym.parent, post, true); // col counts chol(C'*C) bool ok = C != null && sym.parent != null && sym.cp != null && CountV(C); if (ok) { sym.unz = 0; for (int k = 0; k < n; k++) { sym.unz += sym.cp[k]; } } }
/// <summary> /// Ordering and symbolic analysis for a Cholesky factorization. /// </summary> /// <param name="A">Matrix to factorize.</param> /// <param name="p">Permutation.</param> private void SymbolicAnalysis(CompressedColumnStorage <Complex> A, int[] p) { int n = A.ColumnCount; var sym = this.S = new SymbolicFactorization(); // Find inverse permutation. sym.pinv = Permutation.Invert(p); // C = spones(triu(A(P,P))) var C = PermuteSym(A, sym.pinv, false); // Find etree of C. sym.parent = GraphHelper.EliminationTree(n, n, C.ColumnPointers, C.RowIndices, false); // Postorder the etree. var post = GraphHelper.TreePostorder(sym.parent, n); // Find column counts of chol(C) var c = GraphHelper.ColumnCounts(SymbolicColumnStorage.Create(C, false), sym.parent, post, false); sym.cp = new int[n + 1]; // Find column pointers for L sym.unz = sym.lnz = Helper.CumulativeSum(sym.cp, c, n); }
public void TestEmptyTranspose(int rows, int columns) { var A = new SymbolicColumnStorage(rows, columns, 0, true); var B = A.Transpose(); Assert.IsNotNull(B); }
public void TestEmptyAdd(int rows, int columns) { var A = new SymbolicColumnStorage(rows, columns, 0, true); var B = new SymbolicColumnStorage(rows, columns, 0, true); var C = A.Add(B); Assert.IsNotNull(C); }
// breadth-first search for coarse decomposition (C0,C1,R1 or R0,R3,C3) private bool BreadthFirstSearch(SymbolicColumnStorage A, int n, int[] wi, int[] wj, int[] queue, int[] jimatch, int imatch_offset, int jmatch_offset, int mark) { // cs_bfs int[] Ap, Ai; int head = 0, tail = 0, j, i, p, j2; for (j = 0; j < n; j++) // place all unmatched nodes in queue { if (jimatch[imatch_offset + j] >= 0) { continue; // skip j if matched } wj[j] = 0; // j in set C0 (R0 if transpose) queue[tail++] = j; // place unmatched col j in queue } if (tail == 0) { return(true); // quick return if no unmatched nodes } // Transpose if requested SymbolicColumnStorage C = (mark == 1) ? A.Clone() : A.Transpose(); if (C == null) { return(false); // bfs of C=A' to find R3,C3 from R0 } Ap = C.ColumnPointers; Ai = C.RowIndices; while (head < tail) // while queue is not empty { j = queue[head++]; // get the head of the queue for (p = Ap[j]; p < Ap[j + 1]; p++) { i = Ai[p]; if (wi[i] >= 0) { continue; // skip if i is marked } wi[i] = mark; // i in set R1 (C3 if transpose) j2 = jimatch[jmatch_offset + i]; // traverse alternating path to j2 if (wj[j2] >= 0) { continue; // skip j2 if it is marked } wj[j2] = mark; // j2 in set C1 (R3 if transpose) queue[tail++] = j2; // add j2 to queue } } //if (mark != 1) SparseMatrix.spfree(C); // free A' if it was created return(true); }
/// <summary> /// Find nonzero pattern of Cholesky L(k,1:k-1) using etree and triu(A(:,k)) /// </summary> public static int EtreeReach(SymbolicColumnStorage A, int k, int[] parent, int[] s, int[] w) { int i, p, n, len; if (parent == null || s == null || w == null) { return(-1); // check inputs } int top = n = A.ColumnCount; int[] Ap = A.ColumnPointers; int[] Ai = A.RowIndices; //CS_MARK(w, k); w[k] = -w[k] - 2; // mark node k as visited for (p = Ap[k]; p < Ap[k + 1]; p++) { i = Ai[p]; // A(i,k) is nonzero if (i > k) { continue; // only use upper triangular part of A } for (len = 0; !(w[i] < 0); i = parent[i]) // traverse up etree { s[len++] = i; // L(k,i) is nonzero //CS_MARK(w, i); w[i] = -w[i] - 2; // mark i as visited } while (len > 0) { s[--top] = s[--len]; // push path onto stack } } for (p = top; p < n; p++) { //CS_MARK(w, s[p]); w[s[p]] = -w[s[p]] - 2; // unmark all nodes } //CS_MARK(w, k); w[k] = -w[k] - 2; // unmark node k return(top); // s [top..n-1] contains pattern of L(k,:) }
/// <summary> /// Compute the Numeric Cholesky factorization, L = chol (A, [pinv parent cp]). /// </summary> /// <returns>Numeric Cholesky factorization</returns> private void Factorize(CompressedColumnStorage <Complex> A, IProgress <double> progress) { Complex d, lki; int top, i, p, k, cci; int n = A.ColumnCount; // Allocate workspace. var c = new int[n]; var s = new int[n]; var x = this.temp; var colp = S.cp; var pinv = S.pinv; var parent = S.parent; var C = pinv != null?PermuteSym(A, pinv, true) : A; var cp = C.ColumnPointers; var ci = C.RowIndices; var cx = C.Values; this.L = CompressedColumnStorage <Complex> .Create(n, n, colp[n]); var lp = L.ColumnPointers; var li = L.RowIndices; var lx = L.Values; for (k = 0; k < n; k++) { lp[k] = c[k] = colp[k]; } double current = 0.0; double step = n / 100.0; for (k = 0; k < n; k++) // compute L(k,:) for L*L' = C { // Progress reporting. if (k >= current) { current += step; if (progress != null) { progress.Report(k / (double)n); } } // Find nonzero pattern of L(k,:) top = GraphHelper.EtreeReach(SymbolicColumnStorage.Create(C, false), k, parent, s, c); x[k] = 0; // x (0:k) is now zero for (p = cp[k]; p < cp[k + 1]; p++) // x = full(triu(C(:,k))) { if (ci[p] <= k) { x[ci[p]] = cx[p]; } } d = x[k]; // d = C(k,k) x[k] = 0; // clear x for k+1st iteration // Triangular solve for (; top < n; top++) // solve L(0:k-1,0:k-1) * x = C(:,k) { i = s[top]; // s [top..n-1] is pattern of L(k,:) lki = x[i] / lx[lp[i]]; // L(k,i) = x (i) / L(i,i) x[i] = 0; // clear x for k+1st iteration cci = c[i]; for (p = lp[i] + 1; p < cci; p++) { x[li[p]] -= lx[p] * lki; } d -= lki * Complex.Conjugate(lki); // d = d - L(k,i)*L(k,i) p = c[i]++; li[p] = k; // store L(k,i) in column i lx[p] = Complex.Conjugate(lki); } // Compute L(k,k) if (d.Real <= 0 || d.Imaginary != 0) { throw new Exception(Resources.MatrixSymmetricPositiveDefinite); } p = c[k]++; li[p] = k; // store L(k,k) = sqrt (d) in column k lx[p] = Complex.Sqrt(d); } lp[n] = colp[n]; // finalize L }
/// <summary> /// Compute the Numeric Cholesky factorization, L = chol (A, [pinv parent cp]). /// </summary> /// <returns>Numeric Cholesky factorization</returns> private void Factorize(CompressedColumnStorage <double> A) { double d, lki; int top, i, p, k; int n = A.ColumnCount; // Allocate workspace. var c = new int[n]; var s = new int[n]; var x = new double[n]; var colp = symFactor.cp; var pinv = symFactor.pinv; var parent = symFactor.parent; var C = pinv != null?PermuteSym(A, pinv, true) : A; var cp = C.ColumnPointers; var ci = C.RowIndices; var cx = C.Values; this.L = CompressedColumnStorage <double> .Create(n, n, colp[n]); var lp = L.ColumnPointers; var li = L.RowIndices; var lx = L.Values; //var lst = new List<int>(); var percent = 0; for (k = 0; k < n; k++) { lp[k] = c[k] = colp[k]; } for (k = 0; k < n; k++) // compute L(k,:) for L*L' = C { if (100 * k / n != percent) { Progress = percent = (100 * k) / n; //Console.WriteLine("{0}% solve", percent); } // Find nonzero pattern of L(k,:) top = GraphHelper.EtreeReach(SymbolicColumnStorage.Create(C, false), k, parent, s, c); x[k] = 0; // x (0:k) is now zero var tmp = cp[k + 1]; for (p = cp[k]; p < tmp; p++) // x = full(triu(C(:,k))) { if (ci[p] <= k) { x[ci[p]] = cx[p]; } } d = x[k]; // d = C(k,k) x[k] = 0; // clear x for k+1st iteration // Triangular solve for (; top < n; top++) // solve L(0:k-1,0:k-1) * x = C(:,k) { i = s[top]; // s [top..n-1] is pattern of L(k,:) lki = x[i] / lx[lp[i]]; // L(k,i) = x (i) / L(i,i) x[i] = 0; // clear x for k+1st iteration p = lp[i] + 1; var cci = c[i]; for (p = lp[i] + 1; p < cci; p++) { x[li[p]] -= lx[p] * lki; } d -= lki * lki; // d = d - L(k,i)*L(k,i) p = c[i]++; li[p] = k; // store L(k,i) in column i lx[p] = lki; } // Compute L(k,k) if (d <= 0) { throw new NotPositiveDefiniteException("not pos def"); // TODO: ex } p = c[k]++; li[p] = k; // store L(k,k) = sqrt (d) in column k lx[p] = Math.Sqrt(d); } lp[n] = colp[n]; // finalize L }
/// <summary> /// Column counts for Cholesky (LL'=A or LL'=A'A) and QR, given parent and post ordering. /// </summary> public static int[] ColumnCounts(SymbolicColumnStorage A, int[] parent, int[] post, bool ata) { int i, j, k, J, p, q, jleaf = 0; int[] ATp, ATi, colcount, delta, head = null, next = null; if (parent == null || post == null) { return(null); // check inputs } int m = A.RowCount; int n = A.ColumnCount; delta = colcount = new int[n]; // allocate result var AT = A.Transpose(); // AT = A' // w is ancestor int[] w = new int[n]; // get workspace int[] maxfirst = new int[n]; int[] prevleaf = new int[n]; int[] first = new int[n]; for (k = 0; k < n; k++) { w[k] = -1; // clear workspace w [0..s-1] } Array.Copy(w, maxfirst, n); Array.Copy(w, prevleaf, n); Array.Copy(w, first, n); for (k = 0; k < n; k++) // find first [j] { j = post[k]; delta[j] = (first[j] == -1) ? 1 : 0; // delta[j]=1 if j is a leaf for (; j != -1 && first[j] == -1; j = parent[j]) { first[j] = k; } } ATp = AT.ColumnPointers; ATi = AT.RowIndices; if (ata) // Init ata { head = new int[n + 1]; next = new int[m]; Array.Copy(w, head, n); head[n] = -1; for (k = 0; k < n; k++) { w[post[k]] = k; // invert post } for (i = 0; i < m; i++) { for (k = n, p = ATp[i]; p < ATp[i + 1]; p++) { k = Math.Min(k, w[ATi[p]]); } next[i] = head[k]; // place row i in linked list k head[k] = i; } } for (i = 0; i < n; i++) { w[i] = i; // each node in its own set } for (k = 0; k < n; k++) { j = post[k]; // j is the kth node in postordered etree if (parent[j] != -1) { delta[parent[j]]--; // j is not a root } //int HEAD(k,j) (ata ? head [k] : j) //int NEXT(J) (ata ? next [J] : -1) for (J = (ata ? head[k] : j); J != -1; J = (ata ? next[J] : -1)) // J=j for LL'=A case { for (p = ATp[J]; p < ATp[J + 1]; p++) { i = ATi[p]; q = IsLeaf(i, j, first, maxfirst, prevleaf, w, ref jleaf); if (jleaf >= 1) { delta[j]++; // A(i,j) is in skeleton } if (jleaf == 2) { delta[q]--; // account for overlap in q } } } if (parent[j] != -1) { w[j] = parent[j]; } } for (j = 0; j < n; j++) // sum up delta's of each child { if (parent[j] != -1) { colcount[parent[j]] += colcount[j]; } } return(colcount); // success: free workspace }
/// <summary> /// Finds the strongly connected components of a square matrix. /// </summary> /// <returns>strongly connected components, null on error</returns> private static DulmageMendelsohn FindScc(SymbolicColumnStorage A, int n) { // matrix A temporarily modified, then restored int i, k, b, nb = 0, top; int[] xi, p, r, Ap, ATp; var AT = A.Transpose(); // AT = A' Ap = A.ColumnPointers; ATp = AT.ColumnPointers; xi = new int[2 * n + 1]; // get workspace var D = new DulmageMendelsohn(n, 0); // allocate result p = D.p; r = D.r; top = n; for (i = 0; i < n; i++) // first dfs(A) to find finish times (xi) { if (!(Ap[i] < 0)) { top = GraphHelper.DepthFirstSearch(i, A.ColumnPointers, A.RowIndices, top, xi, xi, n, null); } } for (i = 0; i < n; i++) { //CS_MARK(Ap, i); Ap[i] = -(Ap[i]) - 2; // restore A; unmark all nodes } top = n; nb = n; for (k = 0; k < n; k++) // dfs(A') to find strongly connnected comp { i = xi[k]; // get i in reverse order of finish times if (ATp[i] < 0) { continue; // skip node i if already ordered } r[nb--] = top; // node i is the start of a component in p top = GraphHelper.DepthFirstSearch(i, AT.ColumnPointers, AT.RowIndices, top, p, xi, n, null); } r[nb] = 0; // first block starts at zero; shift r up for (k = nb; k <= n; k++) { r[k - nb] = r[k]; } D.nb = nb = n - nb; // nb = # of strongly connected components for (b = 0; b < nb; b++) // sort each block in natural order { for (k = r[b]; k < r[b + 1]; k++) { xi[p[k]] = b; } } for (b = 0; b <= nb; b++) { xi[n + b] = r[b]; } for (i = 0; i < n; i++) { p[xi[n + xi[i]]++] = i; } return(D); }
// Construct matrix C private static SymbolicColumnStorage ConstructMatrix(SymbolicColumnStorage A, ColumnOrdering order) { SymbolicColumnStorage result = null; // Compute A' var AT = A.Transpose(); int m = A.RowCount; int n = A.ColumnCount; if (order == ColumnOrdering.MinimumDegreeAtPlusA) { if (n != m) { throw new ArgumentException(Resources.MatrixSquare, "A"); } // Return A+A' result = A.Add(AT); } else if (order == ColumnOrdering.MinimumDegreeStS) { // Drop dense columns from AT int dense, p, p2 = 0; // Find dense threshold dense = Math.Max(16, 10 * (int)Math.Sqrt(n)); dense = Math.Min(n - 2, dense); var colptr = AT.ColumnPointers; var rowind = AT.RowIndices; for (int j = 0; j < m; j++) { // Column j of AT starts here. p = colptr[j]; // New column j starts here. colptr[j] = p2; if (colptr[j + 1] - p > dense) { // Skip dense column j continue; } for (; p < colptr[j + 1]; p++) { rowind[p2++] = rowind[p]; } } colptr[m] = p2; // Return A'*A with no dense rows result = AT.Multiply(AT.Transpose()); } else { // Return A'*A result = AT.Multiply(A); } // Drop diagonal entries. result.Keep(KeepOffDiag); return(result); }
/// <summary> /// Generate minimum degree ordering of A+A' (if A is symmetric) or A'A. /// </summary> /// <param name="A">Column-compressed matrix</param> /// <param name="order">Column ordering method</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> public static int[] Generate <T>(CompressedColumnStorage <T> A, ColumnOrdering order) where T : struct, IEquatable <T>, IFormattable { int[] Cp, Ci, P, W, nv, next, head, elen, degree, w, hhead; int d, dk, dext, lemax = 0, e, elenk, eln, i, j, k, k1, k2, k3, jlast, ln, dense, nzmax, mindeg = 0, nvi, nvj, nvk, mark, wnvi, cnz, nel = 0, p, p1, p2, p3, p4, pj, pk, pk1, pk2, pn, q, n; bool ok; int h; n = A.ColumnCount; if (order == ColumnOrdering.Natural) { // TODO: return null here? return(Permutation.Create(n)); } var C = ConstructMatrix(SymbolicColumnStorage.Create(A), order); Cp = C.ColumnPointers; cnz = Cp[n]; // Find dense threshold 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); } P = new int[n + 1]; // allocate result W = new int[n + 1]; // get workspace w = new int[n + 1]; degree = new int[n + 1]; 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; nzmax = C.RowIndices.Length; Ci = C.RowIndices; for (i = 0; i <= n; i++) { P[i] = -1; w[i] = 1; // node i is alive degree[i] = W[i]; // degree of node i } next = new int[n + 1]; hhead = new int[n + 1]; head = new int[n + 1]; 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 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 (i = 0; i < n; i++) { 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 elenk = elen[k]; // elenk = |Ek| nvk = nv[k]; // # of nodes k represents nel += nvk; // nv[k] nodes of A eliminated // Garbage collection 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) } } 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 for (k3 = 0; k3 < W[j] - 1; k3++) { Ci[q++] = Ci[p++]; } } } cnz = q; // Ci [cnz...nzmax-1] now free } // Construct new element dk = 0; nv[k] = -nvk; // flag k as in Lk p = Cp[k]; pk1 = (elenk == 0) ? p : cnz; // do in place if elen[k] == 0 pk2 = pk1; for (k1 = 1; k1 <= elenk + 1; k1++) { 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 } for (k2 = 1; k2 <= ln; k2++) { 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 for (pk = pk1; pk < pk2; pk++) // scan 1: find |Le\Lk| { i = Ci[pk]; if ((eln = elen[i]) <= 0) { continue; // skip if elen[i] empty } nvi = -nv[i]; // nv [i] was negated wnvi = mark - nvi; for (p = Cp[i]; p <= Cp[i] + eln - 1; p++) // scan Ei { 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 for (pk = pk1; pk < pk2; pk++) // scan2: degree update { i = Ci[pk]; // consider node i in Lk p1 = Cp[i]; p2 = p1 + elen[i] - 1; pn = p1; for (h = 0, d = 0, p = p1; p <= p2; p++) // scan Ei { e = Ci[p]; if (w[e] != 0) // e is an unabsorbed element { dext = w[e] - mark; // dext = |Le\Lk| 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| p3 = pn; p4 = p1 + W[i]; for (p = p2 + 1; p < p4; p++) // prune edges in Ai { j = Ci[p]; 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++) { 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; } jlast = i; for (j = next[i]; j != -1;) // compare i with all j { 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 { i = Ci[pk]; if ((nvi = -nv[i]) <= 0) { continue; // skip if i is dead } nv[i] = nvi; // restore nv[i] 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 (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 (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; } } for (k = 0, i = 0; i <= n; i++) // postorder the assembly tree { if (Cp[i] == -1) { k = GraphHelper.TreeDepthFirstSearch(i, k, head, next, P, w); } } return(P); }
/// <summary> /// Compute strongly connected components of matrix. /// </summary> /// <param name="matrix">column-compressed matrix</param> /// <returns>Strongly connected components</returns> public static StronglyConnectedComponents Generate <T>(CompressedColumnStorage <T> matrix) where T : struct, IEquatable <T>, IFormattable { return(Generate(SymbolicColumnStorage.Create(matrix), matrix.ColumnCount)); }
/// <summary> /// Find a maximum transveral (zero-free diagonal). Seed optionally selects a /// randomized algorithm. /// </summary> /// <param name="A">column-compressed matrix</param> /// <param name="seed">0: natural, -1: reverse, randomized otherwise</param> /// <returns>row and column matching, size m+n</returns> public static int[] Generate(SymbolicColumnStorage A, int seed) { int i, j, k, p, n2 = 0, m2 = 0; int[] jimatch, w, cheap, js, iss, ps, Cp, q; int n = A.ColumnCount; int m = A.RowCount; int[] Ap = A.ColumnPointers; int[] Ai = A.RowIndices; //[jmatch [0..m-1]; imatch [0..n-1]] w = jimatch = new int[m + n]; // allocate result for (k = 0, j = 0; j < n; j++) // count nonempty rows and columns { n2 += (Ap[j] < Ap[j + 1]) ? 1 : 0; for (p = Ap[j]; p < Ap[j + 1]; p++) { w[Ai[p]] = 1; k += (j == Ai[p]) ? 1 : 0; // count entries already on diagonal } } if (k == Math.Min(m, n)) // quick return if diagonal zero-free { for (i = 0; i < k; i++) { jimatch[i] = i; } for (; i < m; i++) { jimatch[i] = -1; } for (j = 0; j < k; j++) { jimatch[m + j] = j; } for (; j < n; j++) { jimatch[m + j] = -1; } return(jimatch); } for (i = 0; i < m; i++) { m2 += w[i]; } // Transpose if needed SymbolicColumnStorage C = (m2 < n2) ? A.Transpose() : A.Clone(); if (C == null) { return(jimatch); } n = C.ColumnCount; m = C.RowCount; Cp = C.ColumnPointers; int jmatch_offset = (m2 < n2) ? n : 0; int imatch_offset = (m2 < n2) ? 0 : m; w = new int[n]; // get workspace cheap = new int[n]; js = new int[n]; iss = new int[n]; ps = new int[n]; for (j = 0; j < n; j++) { cheap[j] = Cp[j]; // for cheap assignment } for (j = 0; j < n; j++) { w[j] = -1; // all columns unflagged } for (i = 0; i < m; i++) { jimatch[jmatch_offset + i] = -1; // nothing matched yet } q = Permutation.Create(n, seed); // q = random permutation for (k = 0; k < n; k++) // augment, starting at column q[k] { Augment(q[k], C.ColumnPointers, C.RowIndices, jimatch, jmatch_offset, cheap, w, js, iss, ps); } for (j = 0; j < n; j++) { jimatch[imatch_offset + j] = -1; // find row match } for (i = 0; i < m; i++) { if (jimatch[jmatch_offset + i] >= 0) { jimatch[imatch_offset + jimatch[jmatch_offset + i]] = i; } } return(jimatch); }
/// <summary> /// Compute nnz(V) = S.lnz, S.pinv, S.leftmost, S.m2 from A and S.parent /// </summary> private bool CountV(SymbolicColumnStorage A, SymbolicFactorization S) { int i, k, p, pa; int[] pinv, leftmost, parent = S.parent; int m = A.RowCount; int n = A.ColumnCount; int[] ap = A.ColumnPointers; int[] ai = A.RowIndices; S.pinv = pinv = new int[m + n]; // allocate pinv, S.leftmost = leftmost = new int[m]; // and leftmost var w = new int[m]; // get workspace var head = new int[n]; var tail = new int[n]; var nque = new int[n]; // Initialized to 0's for (k = 0; k < n; k++) { head[k] = -1; // queue k is empty } for (k = 0; k < n; k++) { tail[k] = -1; } for (i = 0; i < m; i++) { leftmost[i] = -1; } for (k = n - 1; k >= 0; k--) { for (p = ap[k]; p < ap[k + 1]; p++) { leftmost[ai[p]] = k; // leftmost[i] = min(find(A(i,:))) } } for (i = m - 1; i >= 0; i--) // scan rows in reverse order { pinv[i] = -1; // row i is not yet ordered k = leftmost[i]; if (k == -1) { continue; // row i is empty } if (nque[k]++ == 0) { tail[k] = i; // first row in queue k } w[i] = head[k]; // put i at head of queue k head[k] = i; } S.lnz = 0; S.m2 = m; for (k = 0; k < n; k++) // find row permutation and nnz(V) { i = head[k]; // remove row i from queue k S.lnz++; // count V(k,k) as nonzero if (i < 0) { i = S.m2++; // add a fictitious row } pinv[i] = k; // associate row i with V(:,k) if (--nque[k] <= 0) { continue; // skip if V(k+1:m,k) is empty } S.lnz += nque[k]; // nque [k] is nnz (V(k+1:m,k)) if ((pa = parent[k]) != -1) // move all rows to parent of k { if (nque[pa] == 0) { tail[pa] = tail[k]; } w[tail[k]] = head[pa]; head[pa] = w[i]; nque[pa] += nque[k]; } } for (i = 0; i < m; i++) { if (pinv[i] < 0) { pinv[i] = k++; } } return(true); }
public void TestConstructor(int rows, int columns) { var A = new SymbolicColumnStorage(rows, columns, 0, true); Assert.IsNotNull(A); }
/// <summary> /// Compute coarse and then fine Dulmage-Mendelsohn decomposition. seed /// optionally selects a randomized algorithm. /// </summary> /// <param name="matrix">column-compressed matrix</param> /// <param name="seed">0: natural, -1: reverse, random order otherwise</param> /// <returns>Dulmage-Mendelsohn analysis</returns> public static DulmageMendelsohn Generate <T>(CompressedColumnStorage <T> matrix, int seed = 0) where T : struct, IEquatable <T>, IFormattable { int i, j, k, cnz, nc, nb1, nb2; int[] Cp, ps, rs; bool ok; // We are not interested in the actual matrix values. var A = SymbolicColumnStorage.Create(matrix); // Maximum matching int m = A.RowCount; int n = A.ColumnCount; var result = new DulmageMendelsohn(m, n); // allocate result int[] p = result.p; int[] q = result.q; int[] r = result.r; int[] s = result.s; int[] cc = result.cc; int[] rr = result.rr; int[] jimatch = MaximumMatching.Generate(A, seed); // max transversal if (jimatch == null) { return(null); } // Coarse decomposition for (j = 0; j < n; j++) { s[j] = -1; // unmark all cols for bfs } for (i = 0; i < m; i++) { r[i] = -1; // unmark all rows for bfs } result.BreadthFirstSearch(A, n, r, s, q, jimatch, m, 0, 1); // find C1, R1 from C0*/ ok = result.BreadthFirstSearch(A, m, s, r, p, jimatch, 0, m, 3); // find R3, C3 from R0*/ if (!ok) { return(null); } result.Unmatched(n, s, q, cc, 0); // unmatched set C0 result.Matched(n, s, jimatch, m, p, q, cc, rr, 1, 1); // set R1 and C1 result.Matched(n, s, jimatch, m, p, q, cc, rr, 2, -1); // set R2 and C2 result.Matched(n, s, jimatch, m, p, q, cc, rr, 3, 3); // set R3 and C3 result.Unmatched(m, r, p, rr, 3); // unmatched set R0 // Fine decomposition int[] pinv = Permutation.Invert(p); // pinv=p' var C = SymbolicColumnStorage.Create(matrix); A.Permute(pinv, q, C); // C=A(p,q) (it will hold A(R2,C2)) Cp = C.ColumnPointers; nc = cc[3] - cc[2]; // delete cols C0, C1, and C3 from C if (cc[2] > 0) { for (j = cc[2]; j <= cc[3]; j++) { Cp[j - cc[2]] = Cp[j]; } } C.Reshape(-1, nc); if (rr[2] - rr[1] < m) // delete rows R0, R1, and R3 from C { RowPrune(C, nc, rr); cnz = Cp[nc]; int[] Ci = C.RowIndices; if (rr[1] > 0) { for (k = 0; k < cnz; k++) { Ci[k] -= rr[1]; } } } C.Reshape(nc, -1); var scc = FindScc(C, nc); // find strongly connected components of C*/ // Combine coarse and fine decompositions ps = scc.p; // C(ps,ps) is the permuted matrix rs = scc.r; // kth block is rs[k]..rs[k+1]-1 nb1 = scc.nb; // # of blocks of A(R2,C2) for (k = 0; k < nc; k++) { s[k] = q[ps[k] + cc[2]]; } for (k = 0; k < nc; k++) { q[k + cc[2]] = s[k]; } for (k = 0; k < nc; k++) { r[k] = p[ps[k] + rr[1]]; } for (k = 0; k < nc; k++) { p[k + rr[1]] = r[k]; } nb2 = 0; // create the fine block partitions r[0] = s[0] = 0; if (cc[2] > 0) { nb2++; // leading coarse block A (R1, [C0 C1]) } for (k = 0; k < nb1; k++) // coarse block A (R2,C2) { r[nb2] = rs[k] + rr[1]; // A (R2,C2) splits into nb1 fine blocks s[nb2] = rs[k] + cc[2]; nb2++; } if (rr[2] < m) { r[nb2] = rr[2]; // trailing coarse block A ([R3 R0], C3) s[nb2] = cc[3]; nb2++; } r[nb2] = m; s[nb2] = n; result.nb = nb2; // Remove unused space Array.Resize(ref result.r, nb2 + 1); Array.Resize(ref result.s, nb2 + 1); return(result); }