Exemple #1
0
        /// <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];
                }
            }
        }
Exemple #2
0
        /// <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);
        }
Exemple #3
0
        /// <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];
                }
            }
        }
Exemple #4
0
        /// <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);
        }
Exemple #5
0
        /// <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
        }
Exemple #6
0
        /// <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
        }
Exemple #7
0
        /// <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);
        }
Exemple #8
0
        /// <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));
 }