The R Project SVN R-packages

Rev

Rev 4428 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/* ========================================================================== */
/* === Partition/cholmod_nesdis ============================================= */
/* ========================================================================== */

/* -----------------------------------------------------------------------------
 * CHOLMOD/Partition Module.
 * Copyright (C) 2005-2006, Univ. of Florida.  Author: Timothy A. Davis
 * The CHOLMOD/Partition Module is licensed under Version 2.1 of the GNU
 * Lesser General Public License.  See lesser.txt for a text of the license.
 * CHOLMOD is also available under other licenses; contact authors for details.
 * http://www.cise.ufl.edu/research/sparse
 * -------------------------------------------------------------------------- */

/* CHOLMOD nested dissection and graph partitioning.
 *
 * cholmod_bisect:
 *
 *  Finds a set of nodes that partitions the graph into two parts.
 *  Compresses the graph first.  Requires METIS.
 *
 * cholmod_nested_dissection:
 *
 *  Nested dissection, using its own compression and connected-commponents
 *  algorithms, an external graph partitioner (METIS), and a constrained
 *  minimum degree ordering algorithm (CCOLAMD or CSYMAMD).  Typically
 *  gives better orderings than METIS_NodeND (about 5% to 10% fewer
 *  nonzeros in L).
 *
 * cholmod_collapse_septree:
 *
 *  Prune the separator tree returned by cholmod_nested_dissection.
 *
 * This file contains several routines private to this file:
 *
 *  partition   compress and partition a graph
 *  clear_flag  clear Common->Flag, but do not modify negative entries
 *  find_components find the connected components of a graph
 *
 * Supports any xtype (pattern, real, complex, or zomplex).
 */

#ifndef NPARTITION

#include "cholmod_internal.h"
#include "cholmod_partition.h"
#include "cholmod_cholesky.h"

/* ========================================================================== */
/* === partition ============================================================ */
/* ========================================================================== */

/* Find a set of nodes that partition a graph.  The graph must be symmetric
 * with no diagonal entries.  To compress the graph first, compress is TRUE
 * and on input Hash [j] holds the hash key for node j, which must be in the
 * range 0 to csize-1. The input graph (Cp, Ci) is destroyed.  Cew is all 1's
 * on input and output.  Cnw [j] > 0 is the initial weight of node j.  On
 * output, Cnw [i] = 0 if node i is absorbed into j and the original weight
 * Cnw [i] is added to Cnw [j].  If compress is FALSE, the graph is not
 * compressed and Cnw and Hash are unmodified.  The partition itself is held in
 * the output array Part of size n.  Part [j] is 0, 1, or 2, depending on
 * whether node j is in the left part of the graph, the right part, or the
 * separator, respectively.  Note that the input graph need not be connected,
 * and the output subgraphs (the three parts) may also be unconnected.
 *
 * Returns the size of the separator, in terms of the sum of the weights of
 * the nodes.  It is guaranteed to be between 1 and the total weight of all
 * the nodes.  If it is of size less than the total weight, then both the left
 * and right parts are guaranteed to be non-empty (this guarantee depends on
 * cholmod_metis_bisector).
 */

static UF_long partition    /* size of separator or -1 if failure */
(
    /* inputs, not modified on output */
#ifndef NDEBUG
    Int csize,      /* upper bound on # of edges in the graph;
             * csize >= MAX (n, nnz(C)) must hold. */
#endif
    int compress,   /* if TRUE the compress the graph first */

    /* input/output */
    Int Hash [ ],   /* Hash [i] = hash >= 0 is the hash function for node
             * i on input.  On output, Hash [i] = FLIP (j) if node
             * i is absorbed into j.  Hash [i] >= 0 if i has not
             * been absorbed. */

    /* input graph, compressed graph of cn nodes on output */
    cholmod_sparse *C,

    /* input/output */
    Int Cnw [ ],    /* size n.  Cnw [j] > 0 is the weight of node j on
             * input.  On output, if node i is absorbed into
             * node j, then Cnw [i] = 0 and the original weight of
             * node i is added to Cnw [j].  The sum of Cnw [0..n-1]
             * is not modified. */

    /* workspace */
    Int Cew [ ],    /* size csize, all 1's on input and output */

    /* more workspace, undefined on input and output */
    Int Cmap [ ],   /* size n (i/i/l) */

    /* output */
    Int Part [ ],   /* size n, Part [j] = 0, 1, or 2. */

    cholmod_common *Common
)
{
    Int n, hash, head, i, j, k, p, pend, ilen, ilast, pi, piend,
    jlen, ok, cn, csep, pdest, nodes_pruned, nz, total_weight, jscattered ;
    Int *Cp, *Ci, *Next, *Hhead ;

#ifndef NDEBUG
    Int cnt, pruned ;
    double work = 0, goodwork = 0 ;
#endif

    /* ---------------------------------------------------------------------- */
    /* quick return for small or empty graphs */
    /* ---------------------------------------------------------------------- */

    n = C->nrow ;
    Cp = C->p ;
    Ci = C->i ;
    nz = Cp [n] ;

    PRINT2 (("Partition start, n "ID" nz "ID"\n", n, nz)) ;

    total_weight = 0 ;
    for (j = 0 ; j < n ; j++)
    {
    ASSERT (Cnw [j] > 0) ;
    total_weight += Cnw [j] ;
    }

    if (n <= 2)
    {
    /* very small graph */
    for (j = 0 ; j < n ; j++)
    {
        Part [j] = 2 ;
    }
    return (total_weight) ;
    }
    else if (nz <= 0)
    {
    /* no edges, this is easy */
    PRINT2 (("diagonal matrix\n")) ;
    k = n/2 ;
    for (j = 0 ; j < k ; j++)
    {
        Part [j] = 0 ;
    }
    for ( ; j < n ; j++)
    {
        Part [j] = 1 ;
    }
    /* ensure the separator is not empty (required by nested dissection) */
    Part [n-1] = 2 ;
    return (Cnw [n-1]) ;
    }

#ifndef NDEBUG
    ASSERT (n > 1 && nz > 0) ;
    PRINT2 (("original graph:\n")) ;
    for (j = 0 ; j < n ; j++)
    {
    PRINT2 ((""ID": ", j)) ;
    for (p = Cp [j] ; p < Cp [j+1] ; p++)
    {
        i = Ci [p] ;
        PRINT3 ((""ID" ", i)) ;
        ASSERT (i >= 0 && i < n && i != j) ;
    }
    PRINT2 (("hash: "ID"\n", Hash [j])) ;
    }
    DEBUG (for (p = 0 ; p < csize ; p++) ASSERT (Cew [p] == 1)) ;
#endif

    nodes_pruned = 0 ;

    if (compress)
    {

    /* ------------------------------------------------------------------ */
    /* get workspace */
    /* ------------------------------------------------------------------ */

    Next = Part ;   /* use Part as workspace for Next [ */
    Hhead = Cew ;   /* use Cew as workspace for Hhead [ */

    /* ------------------------------------------------------------------ */
    /* create the hash buckets */
    /* ------------------------------------------------------------------ */

    for (j = 0 ; j < n ; j++)
    {
        /* get the hash key for node j */
        hash = Hash [j] ;
        ASSERT (hash >= 0 && hash < csize) ;
        head = Hhead [hash] ;
        if (head > EMPTY)
        {
        /* hash bucket for this hash key is empty. */
        head = EMPTY ;
        }
        else
        {
        /* hash bucket for this hash key is not empty.  get old head */
        head = FLIP (head) ;
        ASSERT (head >= 0 && head < n) ;
        }
        /* node j becomes the new head of the hash bucket.  FLIP it so that
         * we can tell the difference between an empty or non-empty hash
         * bucket. */
        Hhead [hash] = FLIP (j) ;
        Next [j] = head ;
        ASSERT (head >= EMPTY && head < n) ;
    }

#ifndef NDEBUG
    for (cnt = 0, k = 0 ; k < n ; k++)
    {
        ASSERT (Hash [k] >= 0 && Hash [k] < csize) ;    /* k is alive */
        hash = Hash [k] ;
        ASSERT (hash >= 0 && hash < csize) ;
        head = Hhead [hash] ;
        ASSERT (head < EMPTY) ; /* hash bucket not empty */
        j = FLIP (head) ;
        ASSERT (j >= 0 && j < n) ;
        if (j == k)
        {
        PRINT2 (("hash "ID": ", hash)) ;
        for ( ; j != EMPTY ; j = Next [j])
        {
            PRINT3 ((" "ID"", j)) ;
            ASSERT (j >= 0 && j < n) ;
            ASSERT (Hash [j] == hash) ;
            cnt++ ;
            ASSERT (cnt <= n) ;
        }
        PRINT2 (("\n")) ;
        }
    }
    ASSERT (cnt == n) ;
#endif

    /* ------------------------------------------------------------------ */
    /* scan the non-empty hash buckets for indistinguishable nodes */
    /* ------------------------------------------------------------------ */

    /* If there are no hash collisions and no compression occurs, this takes
     * O(n) time.  If no hash collisions, but some nodes are removed, this
     * takes time O(n+e) where e is the sum of the degress of the nodes
     * that are removed.  Even with many hash collisions (a rare case),
     * this algorithm has never been observed to perform more than nnz(A)
     * useless work.
     *
     * Cmap is used as workspace to mark nodes of the graph, [
     * for comparing the nonzero patterns of two nodes i and j.
     */

#define Cmap_MARK(i)   Cmap [i] = j
#define Cmap_MARKED(i) (Cmap [i] == j)

    for (i = 0 ; i < n ; i++)
    {
        Cmap [i] = EMPTY ;
    }

    for (k = 0 ; k < n ; k++)
    {
        hash = Hash [k] ;
        ASSERT (hash >= FLIP (n-1) && hash < csize) ;
        if (hash < 0)
        {
        /* node k has already been absorbed into some other node */
        ASSERT (FLIP (Hash [k]) >= 0 && FLIP (Hash [k] < n)) ;
        continue ;
        }
        head = Hhead [hash] ;
        ASSERT (head < EMPTY || head == 1) ;
        if (head == 1)
        {
        /* hash bucket is already empty */
        continue ;
        }
        PRINT2 (("\n--------------------hash "ID":\n", hash)) ;
        for (j = FLIP (head) ; j != EMPTY && Next[j] > EMPTY ; j = Next [j])
        {
        /* compare j with all nodes i following it in hash bucket */
        ASSERT (j >= 0 && j < n && Hash [j] == hash) ;
        p = Cp [j] ;
        pend = Cp [j+1] ;
        jlen = pend - p ;
        jscattered = FALSE ;
        DEBUG (for (i = 0 ; i < n ; i++) ASSERT (!Cmap_MARKED (i))) ;
        DEBUG (pruned = FALSE) ;
        ilast = j ;
        for (i = Next [j] ; i != EMPTY ; i = Next [i])
        {
            ASSERT (i >= 0 && i < n && Hash [i] == hash && i != j) ;
            pi = Cp [i] ;
            piend = Cp [i+1] ;
            ilen = piend - pi ;
            DEBUG (work++) ;
            if (ilen != jlen)
            {
            /* i and j have different degrees */
            ilast = i ;
            continue ;
            }
            /* scatter the pattern of node j, if not already */
            if (!jscattered)
            {
            Cmap_MARK (j) ;
            for ( ; p < pend ; p++)
            {
                Cmap_MARK (Ci [p]) ;
            }
            jscattered = TRUE ;
            DEBUG (work += jlen) ;
            }
            for (ok = Cmap_MARKED (i) ; ok && pi < piend ; pi++)
            {
            ok = Cmap_MARKED (Ci [pi]) ;
            DEBUG (work++) ;
            }
            if (ok)
            {
            /* found it.  kill node i and merge it into j */
            PRINT2 (("found "ID" absorbed into "ID"\n", i, j)) ;
            Hash [i] = FLIP (j) ;
            Cnw [j] += Cnw [i] ;
            Cnw [i] = 0 ;
            ASSERT (ilast != i && ilast >= 0 && ilast < n) ;
            Next [ilast] = Next [i] ; /* delete i from bucket */
            nodes_pruned++ ;
            DEBUG (goodwork += (ilen+1)) ;
            DEBUG (pruned = TRUE) ;
            }
            else
            {
            /* i and j are different */
            ilast = i ;
            }
        }
        DEBUG (if (pruned) goodwork += jlen) ;
        }
        /* empty the hash bucket, restoring Cew */
        Hhead [hash] = 1 ;
    }

    DEBUG (if (((work - goodwork) / (double) nz) > 0.20) PRINT0 ((
        "work %12g good %12g nz %12g (wasted work/nz: %6.2f )\n",
        work, goodwork, (double) nz, (work - goodwork) / ((double) nz)))) ;

    /* All hash buckets now empty.  Cmap no longer needed as workspace. ]
     * Cew no longer needed as Hhead; Cew is now restored to all ones. ]
     * Part no longer needed as workspace for Next. ] */
    }

    /* Edge weights are all one, node weights reflect node absorption */
    DEBUG (for (p = 0 ; p < csize ; p++) ASSERT (Cew [p] == 1)) ;
    DEBUG (for (cnt = 0, j = 0 ; j < n ; j++) cnt += Cnw [j]) ;
    ASSERT (cnt == total_weight) ;

    /* ---------------------------------------------------------------------- */
    /* compress and partition the graph */
    /* ---------------------------------------------------------------------- */

    if (nodes_pruned == 0)
    {

    /* ------------------------------------------------------------------ */
    /* no pruning done at all.  Do not create the compressed graph */
    /* ------------------------------------------------------------------ */

    /* FUTURE WORK: could call CHACO, SCOTCH, ... here too */
    csep = CHOLMOD(metis_bisector) (C, Cnw, Cew, Part, Common) ;

    }
    else if (nodes_pruned == n-1)
    {

    /* ------------------------------------------------------------------ */
    /* only one node left.  This is a dense graph */
    /* ------------------------------------------------------------------ */

    PRINT2 (("completely dense graph\n")) ;
    csep = total_weight ;
    for (j = 0 ; j < n ; j++)
    {
        Part [j] = 2 ;
    }

    }
    else
    {

    /* ------------------------------------------------------------------ */
    /* compress the graph and partition the compressed graph */
    /* ------------------------------------------------------------------ */

    /* ------------------------------------------------------------------ */
    /* create the map from the uncompressed graph to the compressed graph */
    /* ------------------------------------------------------------------ */

    /* Cmap [j] = k if node j is alive and the kth node of compressed graph.
     * The mapping is done monotonically (that is, k <= j) to simplify the
     * uncompression later on.  Cmap [j] = EMPTY if node j is dead. */

    for (j = 0 ; j < n ; j++)
    {
        Cmap [j] = EMPTY ;
    }
    k = 0 ;
    for (j = 0 ; j < n ; j++)
    {
        if (Cnw [j] > 0)
        {
        ASSERT (k <= j) ;
        Cmap [j] = k++ ;
        }
    }
    cn = k ;        /* # of nodes in compressed graph */
    PRINT2 (("compressed graph from "ID" to "ID" nodes\n", n, cn)) ;
    ASSERT (cn > 1 && cn == n - nodes_pruned) ;

    /* ------------------------------------------------------------------ */
    /* create the compressed graph */
    /* ------------------------------------------------------------------ */

    k = 0 ;
    pdest = 0 ;
    for (j = 0 ; j < n ; j++)
    {
        if (Cnw [j] > 0)
        {
        /* node j in the full graph is node k in the compressed graph */
        ASSERT (k <= j && Cmap [j] == k) ;
        p = Cp [j] ;
        pend = Cp [j+1] ;
        Cp [k] = pdest ;
        Cnw [k] = Cnw [j] ;
        for ( ; p < pend ; p++)
        {
            /* prune dead nodes, and remap to new node numbering */
            i = Ci [p] ;
            ASSERT (i >= 0 && i < n && i != j) ;
            i = Cmap [i] ;
            ASSERT (i >= EMPTY && i < cn && i != k) ;
            if (i > EMPTY)
            {
            ASSERT (pdest <= p) ;
            Ci [pdest++] = i ;
            }
        }
        k++ ;
        }
    }
    Cp [cn] = pdest ;
    C->nrow = cn ;
    C->ncol = cn ;  /* affects mem stats unless restored when C free'd */

#ifndef NDEBUG
    PRINT2 (("pruned graph ("ID"/"ID") nodes, ("ID"/"ID") edges\n",
            cn, n, pdest, nz)) ;
    PRINT2 (("compressed graph:\n")) ;
    for (cnt = 0, j = 0 ; j < cn ; j++)
    {
        PRINT2 ((""ID": ", j)) ;
        for (p = Cp [j] ; p < Cp [j+1] ; p++)
        {
        i = Ci [p] ;
        PRINT3 ((""ID" ", i)) ;
        ASSERT (i >= 0 && i < cn && i != j) ;
        }
        PRINT2 (("weight: "ID"\n", Cnw [j])) ;
        ASSERT (Cnw [j] > 0) ;
        cnt += Cnw [j] ;
    }
    ASSERT (cnt == total_weight) ;
    for (j = 0 ; j < n ; j++) PRINT2 (("Cmap ["ID"] = "ID"\n", j, Cmap[j]));
    ASSERT (k == cn) ;
#endif

    /* ------------------------------------------------------------------ */
    /* find the separator of the compressed graph */
    /* ------------------------------------------------------------------ */

    /* FUTURE WORK: could call CHACO, SCOTCH, ... here too */
    csep = CHOLMOD(metis_bisector) (C, Cnw, Cew, Part, Common) ;

    if (csep < 0)
    {
        /* failed */
        return (-1) ;
    }

    PRINT2 (("Part: ")) ;
    DEBUG (for (j = 0 ; j < cn ; j++) PRINT2 ((""ID" ", Part [j]))) ;
    PRINT2 (("\n")) ;

    /* Cp and Ci no longer needed */

    /* ------------------------------------------------------------------ */
    /* find the separator of the uncompressed graph */
    /* ------------------------------------------------------------------ */

    /* expand the separator to live nodes in the uncompressed graph */
    for (j = n-1 ; j >= 0 ; j--)
    {
        /* do this in reverse order so that Cnw can be expanded in place */
        k = Cmap [j] ;
        ASSERT (k >= EMPTY && k < n) ;
        if (k > EMPTY)
        {
        /* node k in compressed graph and is node j in full graph */
        ASSERT (k <= j) ;
        ASSERT (Hash [j] >= EMPTY) ;
        Part [j] = Part [k] ;
        Cnw [j] = Cnw [k] ;
        }
        else
        {
        /* node j is a dead node */
        Cnw [j] = 0 ;
        DEBUG (Part [j] = EMPTY) ;
        ASSERT (Hash [j] < EMPTY) ;
        }
    }

    /* find the components for the dead nodes */
    for (i = 0 ; i < n ; i++)
    {
        if (Hash [i] < EMPTY)
        {
        /* node i has been absorbed into node j */
        j = FLIP (Hash [i]) ;
        ASSERT (Part [i] == EMPTY && j >= 0 && j < n && Cnw [i] == 0) ;
        Part [i] = Part [j] ;
        }
        ASSERT (Part [i] >= 0 && Part [i] <= 2) ;
    }

#ifndef NDEBUG
    PRINT2 (("Part: ")) ;
    for (cnt = 0, j = 0 ; j < n ; j++)
    {
        ASSERT (Part [j] != EMPTY) ;
        PRINT2 ((""ID" ", Part [j])) ;
        if (Part [j] == 2) cnt += Cnw [j] ;
    }
    PRINT2 (("\n")) ;
    PRINT2 (("csep "ID" "ID"\n", cnt, csep)) ;
    ASSERT (cnt == csep) ;
    for (cnt = 0, j = 0 ; j < n ; j++) cnt += Cnw [j] ;
    ASSERT (cnt == total_weight) ;
#endif

    }

    /* ---------------------------------------------------------------------- */
    /* return the separator (or -1 if error) */
    /* ---------------------------------------------------------------------- */

    PRINT2 (("Partition done, n "ID" csep "ID"\n", n, csep)) ;
    return (csep) ;
}


/* ========================================================================== */
/* === clear_flag =========================================================== */
/* ========================================================================== */

/* A node j has been removed from the graph if Flag [j] < EMPTY.
 * If Flag [j] >= EMPTY && Flag [j] < mark, then node j is alive but unmarked.
 * Flag [j] == mark means that node j is alive and marked.  Incrementing mark
 * means that all nodes are either (still) dead, or live but unmarked.
 *
 * If Map is NULL, then on output, Common->mark < Common->Flag [i] for all i
 * from 0 to Common->nrow.  This is the same output condition as
 * cholmod_clear_flag, except that this routine maintains the Flag [i] < EMPTY
 * condition as well, if that condition was true on input.
 *
 * If Map is non-NULL, then on output, Common->mark < Common->Flag [i] for all
 * i in the set Map [0..cn-1].
 *
 * workspace: Flag (nrow)
 */

static UF_long clear_flag (Int *Map, Int cn, cholmod_common *Common)
{
    Int nrow, i ;
    Int *Flag ;
    PRINT2 (("old mark %ld\n", Common->mark)) ;
    Common->mark++ ;
    PRINT2 (("new mark %ld\n", Common->mark)) ;
    if (Common->mark <= 0)
    {
    nrow = Common->nrow ;
    Flag = Common->Flag ;
        if (Map != NULL)
        {
            for (i = 0 ; i < cn ; i++)
            {
                /* if Flag [Map [i]] < EMPTY, leave it alone */
                if (Flag [Map [i]] >= EMPTY)
                {
                    Flag [Map [i]] = EMPTY ;
                }
            }
            /* now Flag [Map [i]] <= EMPTY for all i */
        }
        else
        {
            for (i = 0 ; i < nrow ; i++)
            {
                /* if Flag [i] < EMPTY, leave it alone */
                if (Flag [i] >= EMPTY)
                {
                    Flag [i] = EMPTY ;
                }
            }
            /* now Flag [i] <= EMPTY for all i */
        }
    Common->mark = 0 ;
    }
    return (Common->mark) ;
}


/* ========================================================================== */
/* === find_components ====================================================== */
/* ========================================================================== */

/* Find all connected components of the current subgraph C.  The subgraph C
 * consists of the nodes of B that appear in the set Map [0..cn-1].  If Map
 * is NULL, then it is assumed to be the identity mapping
 * (Map [0..cn-1] = 0..cn-1).
 *
 * A node j does not appear in B if it has been ordered (Flag [j] < EMPTY,
 * which means that j has been ordered and is "deleted" from B).
 *
 * If the size of a component is large, it is placed on the component stack,
 * Cstack.  Otherwise, its nodes are ordered and it is not placed on the Cstack.
 *
 * A component S is defined by a "representative node" (repnode for short)
 * called the snode, which is one of the nodes in the subgraph.  Likewise, the
 * subgraph C is defined by its repnode, called cnode.
 * 
 * If Part is not NULL on input, then Part [i] determines how the components
 * are placed on the stack.  Components containing nodes i with Part [i] == 0
 * are placed first, followed by components with Part [i] == 1. 
 *
 * The first node placed in each of the two parts is flipped when placed in
 * the Cstack.  This allows the components of the two parts to be found simply
 * by traversing the Cstack.
 *
 * workspace: Flag (nrow)
 */

static void find_components
(
    /* inputs, not modified on output */
    cholmod_sparse *B,
    Int Map [ ],        /* size n, only Map [0..cn-1] used */
    Int cn,         /* # of nodes in C */
    Int cnode,          /* root node of component C, or EMPTY if C is the
                 * entire graph B */

    Int Part [ ],       /* size cn, optional */

    /* input/output */
    Int Bnz [ ],        /* size n.  Bnz [j] = # nonzeros in column j of B.
                 * Reduce since B is pruned of dead nodes. */

    Int CParent [ ],        /* CParent [i] = j if component with repnode j is
                 * the parent of the component with repnode i.
                 * CParent [i] = EMPTY if the component with
                 * repnode i is a root of the separator tree.
                 * CParent [i] is -2 if i is not a repnode. */
    Int Cstack [ ],     /* component stack for nested dissection */
    Int *top,           /* Cstack [0..top] contains root nodes of the
                 * the components currently in the stack */

    /* workspace, undefined on input and output: */
    Int Queue [ ],      /* size n, for breadth-first search */

    cholmod_common *Common
)
{
    Int n, mark, cj, j, sj, sn, p, i, snode, pstart, pdest, pend, nd_components,
    part, first, save_mark ;
    Int *Bp, *Bi, *Flag ;

    /* ---------------------------------------------------------------------- */
    /* get workspace */
    /* ---------------------------------------------------------------------- */

    PRINT2 (("find components: cn %d\n", cn)) ;
    Flag = Common->Flag ;       /* size n */

    /* force initialization of Flag [Map [0..cn-1]] */
    save_mark = Common->mark ;      /* save the current mark */
    Common->mark = EMPTY ;

    /* clear Flag; preserve Flag [Map [i]] if Flag [Map [i]] already < EMPTY */
    /* this takes O(cn) time */
    mark = clear_flag (Map, cn, Common) ;

    Bp = B->p ;
    Bi = B->i ;
    n = B->nrow ;
    ASSERT (cnode >= EMPTY && cnode < n) ;
    ASSERT (IMPLIES (cnode >= 0, Flag [cnode] < EMPTY)) ;

    /* get ordering parameters */
    nd_components = Common->method [Common->current].nd_components ;

    /* ---------------------------------------------------------------------- */
    /* find the connected components of C via a breadth-first search */
    /* ---------------------------------------------------------------------- */

    part = (Part == NULL) ? 0 : 1 ;

    /* examine each part (part 1 and then part 0) */
    for (part = (Part == NULL) ? 0 : 1 ; part >= 0 ; part--)
    {

    /* first is TRUE for the first connected component in each part */
    first = TRUE ;

    /* find all connected components in the current part */
    for (cj = 0 ; cj < cn ; cj++)
    {
        /* get node snode, which is node cj of C.  It might already be in
         * the separator of C (and thus ordered, with Flag [snode] < EMPTY)
         */
        snode = (Map == NULL) ? (cj) : (Map [cj]) ;
        ASSERT (snode >= 0 && snode < n) ;

        if (Flag [snode] >= EMPTY && Flag [snode] < mark
            && ((Part == NULL) || Part [cj] == part))
        {

        /* ---------------------------------------------------------- */
        /* find new connected component S */
        /* ---------------------------------------------------------- */

        /* node snode is the repnode of a connected component S, the
         * parent of which is cnode, the repnode of C.  If cnode is
         * EMPTY then C is the original graph B. */
        PRINT2 (("----------:::snode "ID" cnode "ID"\n", snode, cnode));

        ASSERT (CParent [snode] == -2) ;
        if (first || nd_components)
        {
            /* If this is the first node in this part, then it becomes
             * the repnode of all components in this part, and all
             * components in this part form a single node in the
             * separator tree.  If nd_components is TRUE, then all
             * connected components form their own node in the
             * separator tree.
             */
            CParent [snode] = cnode ;
        }

        /* place j in the queue and mark it */
        Queue [0] = snode ;
        Flag [snode] = mark ;
        sn = 1 ;

        /* breadth-first traversal, starting at node j */
        for (sj = 0 ; sj < sn ; sj++)
        {
            /* get node j from head of Queue and traverse its edges */
            j = Queue [sj] ;
            PRINT2 (("    j: "ID"\n", j)) ;
            ASSERT (j >= 0 && j < n) ;
            ASSERT (Flag [j] == mark) ;
            pstart = Bp [j] ;
            pdest = pstart ;
            pend = pstart + Bnz [j] ;
            for (p = pstart ; p < pend ; p++)
            {
            i = Bi [p] ;
            if (i != j && Flag [i] >= EMPTY)
            {
                /* node is still in the graph */
                Bi [pdest++] = i ;
                if (Flag [i] < mark)
                {
                /* node i is in this component S, and unflagged
                 * (first time node i has been seen in this BFS)
                 * place node i in the queue and mark it */
                Queue [sn++] = i ;
                Flag [i] = mark ;
                }
            }
            }
            /* edges to dead nodes have been removed */
            Bnz [j] = pdest - pstart ;
        }

        /* ---------------------------------------------------------- */
        /* order S if it is small; place it on Cstack otherwise */
        /* ---------------------------------------------------------- */

        PRINT2 (("sn "ID"\n", sn)) ;

        /* place the new component on the Cstack.  Flip the node if
         * is the first connected component of the current part,
         * or if all components are treated as their own node in
         * the separator tree. */
        Cstack [++(*top)] =
            (first || nd_components) ? FLIP (snode) : snode ;
        first = FALSE ;
        }
    }
    }

    /* restore the flag (normally taking O(1) time except for Int overflow) */
    Common->mark = save_mark++ ;
    clear_flag (NULL, 0, Common) ;
    DEBUG (for (i = 0 ; i < n ; i++) ASSERT (Flag [i] < Common->mark)) ;
}


/* ========================================================================== */
/* === cholmod_bisect ======================================================= */
/* ========================================================================== */

/* Finds a node bisector of A, A*A', A(:,f)*A(:,f)'.
 *
 * workspace: Flag (nrow),
 *  Iwork (nrow if symmetric, max (nrow,ncol) if unsymmetric).
 *  Allocates a temporary matrix B=A*A' or B=A,
 *  and O(nnz(A)) temporary memory space.
 */

UF_long CHOLMOD(bisect) /* returns # of nodes in separator */
(
    /* ---- input ---- */
    cholmod_sparse *A,  /* matrix to bisect */
    Int *fset,      /* subset of 0:(A->ncol)-1 */
    size_t fsize,   /* size of fset */
    int compress,   /* if TRUE, compress the graph first */
    /* ---- output --- */
    Int *Partition, /* size A->nrow.  Node i is in the left graph if
             * Partition [i] = 0, the right graph if 1, and in the
             * separator if 2. */
    /* --------------- */
    cholmod_common *Common
)
{
    Int *Bp, *Bi, *Hash, *Cmap, *Bnw, *Bew, *Iwork ;
    cholmod_sparse *B ;
    unsigned Int hash ;
    Int j, n, bnz, sepsize, p, pend ;
    size_t csize, s ;
    int ok = TRUE ;

    /* ---------------------------------------------------------------------- */
    /* check inputs */
    /* ---------------------------------------------------------------------- */

    RETURN_IF_NULL_COMMON (EMPTY) ;
    RETURN_IF_NULL (A, EMPTY) ;
    RETURN_IF_NULL (Partition, EMPTY) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ;
    Common->status = CHOLMOD_OK ;

    /* ---------------------------------------------------------------------- */
    /* quick return */
    /* ---------------------------------------------------------------------- */

    n = A->nrow ;
    if (n == 0)
    {
    return (0) ;
    }

    /* ---------------------------------------------------------------------- */
    /* allocate workspace */
    /* ---------------------------------------------------------------------- */

    /* s = n + MAX (n, A->ncol) */
    s = CHOLMOD(add_size_t) (A->nrow, MAX (A->nrow, A->ncol), &ok) ;
    if (!ok)
    {
    ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
    return (EMPTY) ;
    }

    CHOLMOD(allocate_work) (n, s, 0, Common) ;
    if (Common->status < CHOLMOD_OK)
    {
    return (EMPTY) ;
    }
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ;

    Iwork = Common->Iwork ;
    Hash = Iwork ;      /* size n, (i/l/l) */
    Cmap = Iwork + n ;      /* size n, (i/i/l) */

    /* ---------------------------------------------------------------------- */
    /* convert the matrix to adjacency list form */
    /* ---------------------------------------------------------------------- */

    /* The input graph to must be symmetric, with no diagonal entries
     * present.  The columns need not be sorted. */

    /* B = A, A*A', or A(:,f)*A(:,f)', upper and lower parts present */

    if (A->stype)
    {
    /* Add the upper/lower part to a symmetric lower/upper matrix by
     * converting to unsymmetric mode */
    /* workspace: Iwork (nrow) */
    B = CHOLMOD(copy) (A, 0, -1, Common) ;
    }
    else
    {
    /* B = A*A' or A(:,f)*A(:,f)', no diagonal */
    /* workspace: Flag (nrow), Iwork (max (nrow,ncol)) */
    B = CHOLMOD(aat) (A, fset, fsize, -1, Common) ;
    }

    if (Common->status < CHOLMOD_OK)
    {
    return (EMPTY) ;
    }
    Bp = B->p ;
    Bi = B->i ;
    bnz = Bp [n] ;
    ASSERT ((Int) (B->nrow) == n && (Int) (B->ncol) == n) ;

    /* B does not include the diagonal, and both upper and lower parts.
     * Common->anz includes the diagonal, and just the lower part of B */
    Common->anz = bnz / 2 + ((double) n) ;

    /* Bew should be at least size n for the hash function to work well */
    /* this cannot cause overflow, because the matrix is already created */
    csize = MAX (((size_t) n) + 1, (size_t) bnz) ;

    /* create the graph using Flag as workspace for node weights [ */
    Bnw = Common->Flag ;    /* size n workspace */

    /* compute hash for each node if compression requested */
    if (compress)
    {
    for (j = 0 ; j < n ; j++)
    {
        hash = j ;
        pend = Bp [j+1] ;
        for (p = Bp [j] ; p < pend ; p++)
        {
        hash += Bi [p] ;
        ASSERT (Bi [p] != j) ;
        }
        /* finalize the hash key for node j */
        hash %= csize ;
        Hash [j] = (Int) hash ;
        ASSERT (Hash [j] >= 0 && Hash [j] < csize) ;
    }
    }

    /* allocate edge weights */
    Bew = CHOLMOD(malloc) (csize, sizeof (Int), Common) ;
    if (Common->status < CHOLMOD_OK)
    {
    /* out of memory */
    CHOLMOD(free_sparse) (&B, Common) ;
    CHOLMOD(free) (csize, sizeof (Int), Bew, Common) ;
    return (EMPTY) ;
    }

    /* graph has unit node and edge weights */
    for (j = 0 ; j < n ; j++)
    {
    Bnw [j] = 1 ;
    }
    for (s = 0 ; s < csize ; s++)
    {
    Bew [s] = 1 ;
    }

    /* ---------------------------------------------------------------------- */
    /* compress and partition the graph */
    /* ---------------------------------------------------------------------- */

    sepsize = partition (
#ifndef NDEBUG
        csize,
#endif
        compress, Hash, B, Bnw, Bew, Cmap, Partition, Common) ;

    /* contents of Bp, Bi, Bnw, and Bew no longer needed ] */

    /* If partition fails, free the workspace below and return sepsize < 0 */

    /* ---------------------------------------------------------------------- */
    /* free workspace */
    /* ---------------------------------------------------------------------- */

    B->ncol = n ;   /* restore size for memory usage statistics */
    CHOLMOD(free_sparse) (&B, Common) ;
    Common->mark = EMPTY ;
    CHOLMOD_CLEAR_FLAG (Common) ;
    CHOLMOD(free) (csize, sizeof (Int), Bew, Common) ;
    return (sepsize) ;
}


/* ========================================================================== */
/* === cholmod_nested_dissection ============================================ */
/* ========================================================================== */

/* This method uses a node bisector, applied recursively (but using a
 * non-recursive algorithm).  Once the graph is partitioned, it calls a
 * constrained min degree code (CAMD or CSYMAMD for A+A', and CCOLAMD for A*A')
 * to order all the nodes in the graph - but obeying the constraints determined
 * by the separators.  This routine is similar to METIS_NodeND, except for how
 * it treats the leaf nodes.  METIS_NodeND orders the leaves of the separator
 * tree with MMD, ignoring the rest of the matrix when ordering a single leaf.
 * This routine orders the whole matrix with CSYMAMD or CCOLAMD, all at once,
 * when the graph partitioning is done.
 *
 * This function also returns a postorderd separator tree (CParent), and a
 * mapping of nodes in the graph to nodes in the separator tree (Cmember).
 *
 * workspace: Flag (nrow), Head (nrow+1), Iwork (4*nrow + (ncol if unsymmetric))
 *  Allocates a temporary matrix B=A*A' or B=A,
 *  and O(nnz(A)) temporary memory space.
 *  Allocates an additional 3*n*sizeof(Int) temporary workspace
 */

UF_long CHOLMOD(nested_dissection) /* returns # of components, or -1 if error */
(
    /* ---- input ---- */
    cholmod_sparse *A,  /* matrix to order */
    Int *fset,      /* subset of 0:(A->ncol)-1 */
    size_t fsize,   /* size of fset */
    /* ---- output --- */
    Int *Perm,      /* size A->nrow, output permutation */
    Int *CParent,   /* size A->nrow.  On output, CParent [c] is the parent
             * of component c, or EMPTY if c is a root, and where
             * c is in the range 0 to # of components minus 1 */
    Int *Cmember,   /* size A->nrow.  Cmember [j] = c if node j of A is
             * in component c */
    /* --------------- */
    cholmod_common *Common
)
{
    double prune_dense, nd_oksep ;
    Int *Bp, *Bi, *Bnz, *Cstack, *Imap, *Map, *Flag, *Head, *Next, *Bnw, *Iwork,
    *Ipost, *NewParent, *Hash, *Cmap, *Cp, *Ci, *Cew, *Cnw, *Part, *Post,
    *Work3n ;
    unsigned Int hash ;
    Int n, bnz, top, i, j, k, cnode, cdense, p, cj, cn, ci, cnz, mark, c, uncol,
    sepsize, parent, ncomponents, threshold, ndense, pstart, pdest, pend,
    nd_compress, nd_camd, csize, jnext, nd_small, total_weight,
    nchild, child = EMPTY ;
    cholmod_sparse *B, *C ;
    size_t s ;
    int ok = TRUE ;
    DEBUG (Int cnt) ;

    /* ---------------------------------------------------------------------- */
    /* get inputs */
    /* ---------------------------------------------------------------------- */

    RETURN_IF_NULL_COMMON (EMPTY) ;
    RETURN_IF_NULL (A, EMPTY) ;
    RETURN_IF_NULL (Perm, EMPTY) ;
    RETURN_IF_NULL (CParent, EMPTY) ;
    RETURN_IF_NULL (Cmember, EMPTY) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ;
    Common->status = CHOLMOD_OK ;

    /* ---------------------------------------------------------------------- */
    /* quick return */
    /* ---------------------------------------------------------------------- */

    n = A->nrow ;
    if (n == 0)
    {
    return (1) ;
    }

    /* ---------------------------------------------------------------------- */
    /* get inputs */
    /* ---------------------------------------------------------------------- */

    /* get ordering parameters */
    prune_dense = Common->method [Common->current].prune_dense ;
    nd_compress = Common->method [Common->current].nd_compress ;
    nd_oksep = Common->method [Common->current].nd_oksep ;
    nd_oksep = MAX (0, nd_oksep) ;
    nd_oksep = MIN (1, nd_oksep) ;
    nd_camd = Common->method [Common->current].nd_camd ;
    nd_small = Common->method [Common->current].nd_small ;
    nd_small = MAX (4, nd_small) ;

    PRINT0 (("nd_components %d nd_small %d nd_oksep %g\n", 
    Common->method [Common->current].nd_components,
    nd_small, nd_oksep)) ;

    /* ---------------------------------------------------------------------- */
    /* allocate workspace */
    /* ---------------------------------------------------------------------- */

    /* s = 4*n + uncol */
    uncol = (A->stype == 0) ? A->ncol : 0 ;
    s = CHOLMOD(mult_size_t) (n, 4, &ok) ;
    s = CHOLMOD(add_size_t) (s, uncol, &ok) ;
    if (!ok)
    {
    ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
    return (EMPTY) ;
    }

    CHOLMOD(allocate_work) (n, s, 0, Common) ;
    if (Common->status < CHOLMOD_OK)
    {
    return (EMPTY) ;
    }
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ;

    /* ---------------------------------------------------------------------- */
    /* get workspace */
    /* ---------------------------------------------------------------------- */

    Flag = Common->Flag ;   /* size n */
    Head = Common->Head ;   /* size n+1, all equal to -1 */

    Iwork = Common->Iwork ;
    Imap = Iwork ;      /* size n, same as Queue in find_components */
    Map  = Iwork + n ;      /* size n */
    Bnz  = Iwork + 2*((size_t) n) ; /* size n */
    Hash = Iwork + 3*((size_t) n) ; /* size n */

    Work3n = CHOLMOD(malloc) (n, 3*sizeof (Int), Common) ;
    Part = Work3n ;     /* size n */
    Bnw  = Part + n ;       /* size n */
    Cnw  = Bnw + n ;        /* size n */

    Cstack = Perm ;     /* size n, use Perm as workspace for Cstack [ */
    Cmap = Cmember ;        /* size n, use Cmember as workspace [ */

    if (Common->status < CHOLMOD_OK)
    {
    return (EMPTY) ;
    }

    /* ---------------------------------------------------------------------- */
    /* convert B to symmetric form with both upper/lower parts present */
    /* ---------------------------------------------------------------------- */

    /* B = A+A', A*A', or A(:,f)*A(:,f)', upper and lower parts present */

    if (A->stype)
    {
    /* Add the upper/lower part to a symmetric lower/upper matrix by
     * converting to unsymmetric mode */
    /* workspace: Iwork (nrow) */
    B = CHOLMOD(copy) (A, 0, -1, Common) ;
    }
    else
    {
    /* B = A*A' or A(:,f)*A(:,f)', no diagonal */
    /* workspace: Flag (nrow), Iwork (max (nrow,ncol)) */
    B = CHOLMOD(aat) (A, fset, fsize, -1, Common) ;
    }

    if (Common->status < CHOLMOD_OK)
    {
    CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ;
    return (EMPTY) ;
    }
    Bp = B->p ;
    Bi = B->i ;
    bnz = CHOLMOD(nnz) (B, Common) ;
    ASSERT ((Int) (B->nrow) == n && (Int) (B->ncol) == n) ;
    csize = MAX (n, bnz) ;
    ASSERT (CHOLMOD(dump_sparse) (B, "B for nd:", Common) >= 0) ;

    /* ---------------------------------------------------------------------- */
    /* initializations */
    /* ---------------------------------------------------------------------- */

    /* all nodes start out unmarked and unordered (Type 4, see below) */
    Common->mark = EMPTY ;
    CHOLMOD_CLEAR_FLAG (Common) ;
    ASSERT (Flag == Common->Flag) ;
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ;

    for (j = 0 ; j < n ; j++)
    {
    CParent [j] = -2 ;
    }

    /* prune dense nodes from B */
    if (IS_NAN (prune_dense) || prune_dense < 0)
    {
    /* only remove completely dense nodes */
    threshold = n-2 ;
    }
    else
    {
    /* remove nodes with degree more than threshold */
    threshold = (Int) (MAX (16, prune_dense * sqrt ((double) (n)))) ;
    threshold = MIN (n, threshold) ;
    }
    ndense = 0 ;
    cnode = EMPTY ;
    cdense = EMPTY ;

    for (j = 0 ; j < n ; j++)
    {
    Bnz [j] = Bp [j+1] - Bp [j] ;
    if (Bnz [j] > threshold)
    {
        /* node j is dense, prune it from B */
        PRINT2 (("j is dense %d\n", j)) ;
        ndense++ ;
        if (cnode == EMPTY)
        {
        /* first dense node found becomes root of this component,
         * which contains all of the dense nodes found here */
        cdense = j ;
        cnode = j ;
        CParent [cnode] = EMPTY ;
        }
        Flag [j] = FLIP (cnode) ;
    }
    }
    B->packed = FALSE ;
    ASSERT (B->nz == NULL) ;

    if (ndense == n)
    {
    /* all nodes removed: Perm is identity, all nodes in component zero,
     * and the separator tree has just one node. */
    PRINT2 (("all nodes are dense\n")) ;
    for (k = 0 ; k < n ; k++)
    {
        Perm [k] = k ;
        Cmember [k] = 0 ;
    }
    CParent [0] = EMPTY ;
    CHOLMOD(free_sparse) (&B, Common) ;
    CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ;
    Common->mark = EMPTY ;
    CHOLMOD_CLEAR_FLAG (Common) ;
    return (1) ;
    }

    /* Cp and Ci are workspace to construct the subgraphs to partition */
    C = CHOLMOD(allocate_sparse) (n, n, csize, FALSE, TRUE, 0, CHOLMOD_PATTERN,
        Common) ;
    Cew  = CHOLMOD(malloc) (csize, sizeof (Int), Common) ;

    if (Common->status < CHOLMOD_OK)
    {
    /* out of memory */
    CHOLMOD(free_sparse) (&C, Common) ;
    CHOLMOD(free_sparse) (&B, Common) ;
    CHOLMOD(free) (csize, sizeof (Int), Cew, Common) ;
    CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ;
    Common->mark = EMPTY ;
    CHOLMOD_CLEAR_FLAG (Common) ;
    PRINT2 (("out of memory for C, etc\n")) ;
    return (EMPTY) ;
    }

    Cp = C->p ;
    Ci = C->i ;

    /* create initial unit node and edge weights */
    for (j = 0 ; j < n ; j++)
    {
    Bnw [j] = 1 ;
    }
    for (p = 0 ; p < csize ; p++)
    {
    Cew [p] = 1 ;
    }

    /* push the initial connnected components of B onto the Cstack */
    top = EMPTY ;   /* Cstack is empty */
    /* workspace: Flag (nrow), Iwork (nrow); use Imap as workspace for Queue [*/
    find_components (B, NULL, n, cnode, NULL,
        Bnz, CParent, Cstack, &top, Imap, Common) ;
    /* done using Imap as workspace for Queue ] */

    /* Nodes can now be of Type 0, 1, 2, or 4 (see definition below) */

    /* ---------------------------------------------------------------------- */
    /* while Cstack is not empty, do: */
    /* ---------------------------------------------------------------------- */

    while (top >= 0)
    {

    /* clear the Flag array, but do not modify negative entries in Flag  */
    mark = clear_flag (NULL, 0, Common) ;

    DEBUG (for (i = 0 ; i < n ; i++) Imap [i] = EMPTY) ;

    /* ------------------------------------------------------------------ */
    /* get node(s) from the top of the Cstack */
    /* ------------------------------------------------------------------ */

    /* i is the repnode of its (unordered) connected component.  Get
     * all repnodes for all connected components of a single part.  If
     * each connected component is to be ordered separately (nd_components
     * is TRUE), then this while loop iterates just once. */

    cnode = EMPTY ;
    cn = 0 ;
    while (cnode == EMPTY)
    {
        i = Cstack [top--] ;

        if (i < 0)
        {
        /* this is the last node in this component */
        i = FLIP (i) ;
        cnode = i ;
        }

        ASSERT (i >= 0 && i < n && Flag [i] >= EMPTY) ;

        /* place i in the queue and mark it */
        Map [cn] = i ;
        Flag [i] = mark ;
        Imap [i] = cn ;
        cn++ ;
    }

    ASSERT (cnode != EMPTY) ;

    /* During ordering, there are five kinds of nodes in the graph of B,
     * based on Flag [j] and CParent [j] for nodes j = 0 to n-1:
     *
     * Type 0: If cnode is a repnode of an unordered component, then
     * CParent [cnode] is in the range EMPTY to n-1 and
     * Flag [cnode] >= EMPTY.  This is a "live" node.
     *
     * Type 1: If cnode is a repnode of an ordered separator component,
     * then Flag [cnode] < EMPTY and FLAG [cnode] = FLIP (cnode).
     * CParent [cnode] is in the range EMPTY to n-1.  cnode is a root of
     * the separator tree if CParent [cnode] == EMPTY.  This node is dead.
     *
     * Type 2: If node j isn't a repnode, has not been absorbed via
     * graph compression into another node, but is in an ordered separator
     * component, then cnode = FLIP (Flag [j]) gives the repnode of the
     * component that contains j and CParent [j]  is -2.  This node is dead.
     * Note that Flag [j] < EMPTY.
     *
     * Type 3: If node i has been absorbed via graph compression into some
     * other node j = FLIP (Flag [i]) where j is not a repnode.
     * CParent [j] is -2.  Node i may or may not be in an ordered
     * component.  This node is dead.  Note that Flag [j] < EMPTY.
     *
     * Type 4: If node j is "live" (not in an ordered component, and not
     * absorbed into any other node), then Flag [j] >= EMPTY.
     *
     * Only "live" nodes (of type 0 or 4) are placed in a subgraph to be
     * partitioned.  Node j is alive if Flag [j] >= EMPTY, and dead if
     * Flag [j] < EMPTY.
     */

    /* ------------------------------------------------------------------ */
    /* create the subgraph for this connected component C */
    /* ------------------------------------------------------------------ */

    /* Do a breadth-first search of the graph starting at cnode.
     * use Map [0..cn-1] for nodes in the component C [
     * use Cnw and Cew for node and edge weights of the resulting subgraph [
     * use Cp and Ci for the resulting subgraph [
     * use Imap [i] for all nodes i in B that are in the component C [
     */

    cnz = 0 ;
    total_weight = 0 ;
    for (cj = 0 ; cj < cn ; cj++)
    {
        /* get node j from the head of the queue; it is node cj of C */
        j = Map [cj] ;
        ASSERT (Flag [j] == mark) ;
        Cp [cj] = cnz ;
        Cnw [cj] = Bnw [j] ;
        ASSERT (Cnw [cj] >= 0) ;
        total_weight += Cnw [cj] ;
        pstart = Bp [j] ;
        pdest = pstart ;
        pend = pstart + Bnz [j] ;
        hash = cj ;
        for (p = pstart ; p < pend ; p++)
        {
        i = Bi [p] ;
        /* prune diagonal entries and dead edges from B */
        if (i != j && Flag [i] >= EMPTY)
        {
            /* live node i is in the current component */
            Bi [pdest++] = i ;
            if (Flag [i] != mark)
            {
            /* First time node i has been seen, it is a new node
             * of C.  place node i in the queue and mark it */
            Map [cn] = i ;
            Flag [i] = mark ;
            Imap [i] = cn ;
            cn++ ;
            }
            /* place the edge (cj,ci) in the adjacency list of cj */
            ci = Imap [i] ;
            ASSERT (ci >= 0 && ci < cn && ci != cj && cnz < csize) ;
            Ci [cnz++] = ci ;
            hash += ci ;
        }
        }
        /* edges to dead nodes have been removed */
        Bnz [j] = pdest - pstart ;
        /* finalize the hash key for column j */
        hash %= csize ;
        Hash [cj] = (Int) hash ;
        ASSERT (Hash [cj] >= 0 && Hash [cj] < csize) ;
    }
    Cp [cn] = cnz ;
    C->nrow = cn ;
    C->ncol = cn ;  /* affects mem stats unless restored when C free'd */

    /* contents of Imap no longer needed ] */

#ifndef NDEBUG
    for (cj = 0 ; cj < cn ; cj++)
    {
        j = Map [cj] ;
        PRINT2 (("----------------------------C column cj: "ID" j: "ID"\n",
        cj, j)) ;
        ASSERT (j >= 0 && j < n) ;
        ASSERT (Flag [j] >= EMPTY) ;
        for (p = Cp [cj] ; p < Cp [cj+1] ; p++)
        {
        ci = Ci [p] ;
        i = Map [ci] ;
        PRINT3 (("ci: "ID" i: "ID"\n", ci, i)) ;
        ASSERT (ci != cj && ci >= 0 && ci < cn) ;
        ASSERT (i != j && i >= 0 && i < n) ;
        ASSERT (Flag [i] >= EMPTY) ;
        }
    }
#endif

    PRINT0 (("consider cn %d nd_small %d ", cn, nd_small)) ;
    if (cn < nd_small)  /* could be 'total_weight < nd_small' instead */
    {
        /* place all nodes in the separator */
        PRINT0 ((" too small\n")) ;
        sepsize = total_weight ;
    }
    else
    {

        /* Cp and Ci now contain the component, with cn nodes and cnz
         * nonzeros.  The mapping of a node cj into node j the main graph
         * B is given by Map [cj] = j */
        PRINT0 ((" cut\n")) ;

        /* -------------------------------------------------------------- */
        /* compress and partition the graph C */
        /* -------------------------------------------------------------- */

        /* The edge weights Cew [0..csize-1] are all 1's on input to and
         * output from the partition routine. */

        sepsize = partition (
#ifndef NDEBUG
            csize,
#endif
            nd_compress, Hash, C, Cnw, Cew,
            Cmap, Part, Common) ;

        /* contents of Cp and Ci no longer needed ] */

        if (sepsize < 0)
        {
        /* failed */
        C->ncol = n ;   /* restore size for memory usage statistics */
        CHOLMOD(free_sparse) (&C, Common) ;
        CHOLMOD(free_sparse) (&B, Common) ;
        CHOLMOD(free) (csize, sizeof (Int), Cew, Common) ;
        CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ;
        Common->mark = EMPTY ;
        CHOLMOD_CLEAR_FLAG (Common) ;
        return (EMPTY) ;
        }

        /* -------------------------------------------------------------- */
        /* compress B based on how C was compressed */
        /* -------------------------------------------------------------- */

        for (ci = 0 ; ci < cn ; ci++)
        {
        if (Hash [ci] < EMPTY)
        {
            /* ci is dead in C, having been absorbed into cj */
            cj = FLIP (Hash [ci]) ;
            PRINT2 (("In C, "ID" absorbed into "ID" (wgt now "ID")\n",
                ci, cj, Cnw [cj])) ;
            /* i is dead in B, having been absorbed into j */
            i = Map [ci] ;
            j = Map [cj] ;
            PRINT2 (("In B, "ID" (wgt "ID") => "ID" (wgt "ID")\n",
                i, Bnw [i], j, Bnw [j], Cnw [cj])) ;
            /* more than one node may be absorbed into j.  This is
             * accounted for in Cnw [cj].  Assign it here rather
             * than += Bnw [i] */
            Bnw [i] = 0 ;
            Bnw [j] = Cnw [cj] ;
            Flag [i] = FLIP (j) ;
        }
        }

        DEBUG (for (cnt = 0, j = 0 ; j < n ; j++) cnt += Bnw [j]) ;
        ASSERT (cnt == n) ;
    }

    /* contents of Cnw [0..cn-1] no longer needed ] */

    /* ------------------------------------------------------------------ */
    /* order the separator, and stack the components when C is split */
    /* ------------------------------------------------------------------ */

    /* one more component has been found: either the separator of C,
     * or all of C */

    ASSERT (sepsize >= 0 && sepsize <= total_weight) ;

    PRINT0 (("sepsize %d tot %d : %8.4f ", sepsize, total_weight,
        ((double) sepsize) / ((double) total_weight))) ;

    if (sepsize == total_weight || sepsize == 0 ||
        sepsize > nd_oksep * total_weight)
    {
        /* Order the nodes in the component.  The separator is too large,
         * or empty.  Note that the partition routine cannot return a
         * sepsize of zero, but it can return a separator consisting of the
         * whole graph.  The "sepsize == 0" test is kept, above, in case the
         * partition routine changes.  In either case, this component
         * remains unsplit, and becomes a leaf of the separator tree. */
        PRINT2 (("cnode %d sepsize zero or all of graph: "ID"\n",
        cnode, sepsize)) ;
        for (cj = 0 ; cj < cn ; cj++)
        {
        j = Map [cj] ;
        Flag [j] = FLIP (cnode) ;
        PRINT2 (("      node cj: "ID" j: "ID" ordered\n", cj, j)) ;
        }
        ASSERT (Flag [cnode] == FLIP (cnode)) ;
        ASSERT (cnode != EMPTY && Flag [cnode] < EMPTY) ;
        PRINT0 (("discarded\n")) ;

    }
    else
    {

        /* Order the nodes in the separator of C and find a new repnode
         * cnode that is in the separator of C.  This requires the separator
         * to be non-empty. */
        PRINT0 (("sepsize not tiny: "ID"\n", sepsize)) ;
        parent = CParent [cnode] ;
        ASSERT (parent >= EMPTY && parent < n) ;
        CParent [cnode] = -2 ;
        cnode = EMPTY ;
        for (cj = 0 ; cj < cn ; cj++)
        {
        j = Map [cj] ;
        if (Part [cj] == 2)
        {
            /* All nodes in the separator become part of a component
             * whose repnode is cnode */
            PRINT2 (("node cj: "ID" j: "ID" ordered\n", cj, j)) ;
            if (cnode == EMPTY)
            {
            PRINT2(("------------new cnode: cj "ID" j "ID"\n",
                    cj, j)) ;
            cnode = j ;
            }
            Flag [j] = FLIP (cnode) ;
        }
        else
        {
            PRINT2 (("      node cj: "ID" j: "ID" not ordered\n",
                cj, j)) ;
        }
        }
        ASSERT (cnode != EMPTY && Flag [cnode] < EMPTY) ;
        ASSERT (CParent [cnode] == -2) ;
        CParent [cnode] = parent ;

        /* find the connected components when C is split, and push
         * them on the Cstack.  Use Imap as workspace for Queue. [ */
        /* workspace: Flag (nrow) */
        find_components (B, Map, cn, cnode, Part, Bnz,
            CParent, Cstack, &top, Imap, Common) ;
        /* done using Imap as workspace for Queue ] */
    }
    /* contents of Map [0..cn-1] no longer needed ] */
    }

    /* done using Cmember as workspace for Cmap ] */
    /* done using Perm as workspace for Cstack ] */

    /* ---------------------------------------------------------------------- */
    /* place nodes removed via compression into their proper component */
    /* ---------------------------------------------------------------------- */

    /* At this point, all nodes are of Type 1, 2, or 3, as defined above. */

    for (i = 0 ; i < n ; i++)
    {
    /* find the repnode cnode that contains node i */
    j = FLIP (Flag [i]) ;
    PRINT2 (("\nfind component for "ID", in: "ID"\n", i, j)) ;
    ASSERT (j >= 0 && j < n) ;
    DEBUG (cnt = 0) ;
    while (CParent [j] == -2)
    {
        j = FLIP (Flag [j]) ;
        PRINT2 (("    walk up to "ID" ", j)) ;
        ASSERT (j >= 0 && j < n) ;
        PRINT2 ((" CParent "ID"\n", CParent [j])) ;
        ASSERT (cnt < n) ;
        DEBUG (cnt++) ;
    }
    cnode = j ;
    ASSERT (cnode >= 0 && cnode < n) ;
    ASSERT (CParent [cnode] >= EMPTY && CParent [cnode] < n) ;
    PRINT2 (("i "ID" is in component with cnode "ID"\n", i, cnode)) ;
    ASSERT (Flag [cnode] == FLIP (cnode)) ;

    /* Mark all nodes along the path from i to cnode as being in the
     * component whos repnode is cnode.  Perform path compression.  */
    j = FLIP (Flag [i]) ;
    Flag [i] = FLIP (cnode) ;
    DEBUG (cnt = 0) ;
    while (CParent [j] == -2)
    {
        ASSERT (j >= 0 && j < n) ;
        jnext = FLIP (Flag [j]) ;
        PRINT2 (("    "ID" walk "ID" set cnode to "ID"\n", i, j, cnode)) ;
        ASSERT (cnt < n) ;
        DEBUG (cnt++) ;
        Flag [j] = FLIP (cnode) ;
        j = jnext ;
    }
    }

    /* At this point, all nodes fall into Types 1 or 2, as defined above. */

#ifndef NDEBUG
    for (j = 0 ; j < n ; j++)
    {
    PRINT2 (("j %d CParent %d  ", j, CParent [j])) ;
    if (CParent [j] >= EMPTY && CParent [j] < n)
    {
        /* case 1: j is a repnode of a component */
        cnode = j ;
        PRINT2 ((" a repnode\n")) ;
    }
    else
    {
        /* case 2: j is not a repnode of a component */
        cnode = FLIP (Flag [j]) ;
        PRINT2 ((" repnode is %d\n", cnode)) ;
        ASSERT (cnode >= 0 && cnode < n) ;
        ASSERT (CParent [cnode] >= EMPTY && CParent [cnode] < n) ;
    }
    ASSERT (Flag [cnode] == FLIP (cnode)) ;
    /* case 3 no longer holds */
    }
#endif

    /* ---------------------------------------------------------------------- */
    /* free workspace */
    /* ---------------------------------------------------------------------- */

    C->ncol = n ;   /* restore size for memory usage statistics */
    CHOLMOD(free_sparse) (&C, Common) ;
    CHOLMOD(free_sparse) (&B, Common) ;
    CHOLMOD(free) (csize, sizeof (Int), Cew, Common) ;
    CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ;

    /* ---------------------------------------------------------------------- */
    /* handle dense nodes */
    /* ---------------------------------------------------------------------- */

    /* The separator tree has nodes with either no children or two or more
     * children - with one exception.  There may exist a single root node with
     * exactly one child, which holds the dense rows/columns of the matrix.
     * Delete this node if it exists. */

    if (ndense > 0)
    {
    ASSERT (CParent [cdense] == EMPTY) ;    /* cdense has no parent */
    /* find the children of cdense */
    nchild = 0 ;
    for (j = 0 ; j < n ; j++)
    {
        if (CParent [j] == cdense)
        {
        nchild++ ;
        child = j ;
        }
    }
    if (nchild == 1)
    {
        /* the cdense node has just one child; merge the two nodes */
        PRINT1 (("root has one child\n")) ;
        CParent [cdense] = -2 ;     /* cdense is deleted */
        CParent [child] = EMPTY ;       /* child becomes a root */
        for (j = 0 ; j < n ; j++)
        {
        if (Flag [j] == FLIP (cdense))
        {
            /* j is a dense node */
            PRINT1 (("dense %d\n", j)) ;
            Flag [j] = FLIP (child) ;
        }
        }
    }
    }

    /* ---------------------------------------------------------------------- */
    /* postorder the components */
    /* ---------------------------------------------------------------------- */

    DEBUG (for (cnt = 0, j = 0 ; j < n ; j++) if (CParent [j] != -2) cnt++) ;

    /* use Cmember as workspace for Post [ */
    Post = Cmember ;

    /* cholmod_postorder uses Head and Iwork [0..2n].  It does not use Flag,
     * which here holds the mapping of nodes to repnodes.  It ignores all nodes
     * for which CParent [j] < -1, so it operates just on the repnodes. */
    /* workspace: Head (n), Iwork (2*n) */
    ncomponents = CHOLMOD(postorder) (CParent, n, NULL, Post, Common) ;
    ASSERT (cnt == ncomponents) ;

    /* use Iwork [0..n-1] as workspace for Ipost ( */
    Ipost = Iwork ;
    DEBUG (for (j = 0 ; j < n ; j++) Ipost [j] = EMPTY) ;

    /* compute inverse postorder */
    for (c = 0 ; c < ncomponents ; c++)
    {
    cnode = Post [c] ;
    ASSERT (cnode >= 0 && cnode < n) ;
    Ipost [cnode] = c ;
    ASSERT (Head [c] == EMPTY) ;
    }

    /* adjust the parent array */
    /* Iwork [n..2n-1] used for NewParent [ */
    NewParent = Iwork + n ;
    for (c = 0 ; c < ncomponents ; c++)
    {
    parent = CParent [Post [c]] ;
    NewParent [c] = (parent == EMPTY) ? EMPTY : (Ipost [parent]) ;
    }
    for (c = 0 ; c < ncomponents ; c++)
    {
    CParent [c] = NewParent [c] ;
    }
    ASSERT (CHOLMOD(dump_parent) (CParent, ncomponents, "CParent", Common)) ;

    /* Iwork [n..2n-1] no longer needed for NewParent ] */
    /* Cmember no longer needed for Post ] */

#ifndef NDEBUG
    /* count the number of children of each node */
    for (c = 0 ; c < ncomponents ; c++)
    {
    Cmember [c] = 0 ;
    }
    for (c = 0 ; c < ncomponents ; c++)
    {
    if (CParent [c] != EMPTY) Cmember [CParent [c]]++ ;
    }
    for (c = 0 ; c < ncomponents ; c++)
    {
    /* a node is either a leaf, or has 2 or more children */
    ASSERT (Cmember [c] == 0 || Cmember [c] >= 2) ;
    }
#endif

    /* ---------------------------------------------------------------------- */
    /* place each node in its component */
    /* ---------------------------------------------------------------------- */

    for (j = 0 ; j < n ; j++)
    {
    /* node j is in the cth component, whose repnode is cnode */
    cnode = FLIP (Flag [j]) ;
    PRINT2 (("j "ID"  flag "ID" cnode "ID"\n",
            j, Flag [j], FLIP (Flag [j]))) ;
    ASSERT (cnode >= 0 && cnode < n) ;
    c = Ipost [cnode] ;
    ASSERT (c >= 0 && c < ncomponents) ;
    Cmember [j] = c ;
    }

    /* Flag no longer needed for the node-to-component mapping */

    /* done using Iwork [0..n-1] as workspace for Ipost ) */

    /* ---------------------------------------------------------------------- */
    /* clear the Flag array */
    /* ---------------------------------------------------------------------- */

    Common->mark = EMPTY ;
    CHOLMOD_CLEAR_FLAG (Common) ;
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ;

    /* ---------------------------------------------------------------------- */
    /* find the permutation */
    /* ---------------------------------------------------------------------- */

    PRINT1 (("nd_camd: %d A->stype %d\n", nd_camd, A->stype)) ;

    if (nd_camd)
    {

    /* ------------------------------------------------------------------ */
    /* apply camd, csymamd, or ccolamd using the Cmember constraints */
    /* ------------------------------------------------------------------ */

    if (A->stype != 0)
    {
        /* ordering A+A', so fset and fsize are ignored.
         * Add the upper/lower part to a symmetric lower/upper matrix by
         * converting to unsymmetric mode
         * workspace: Iwork (nrow) */
        B = CHOLMOD(copy) (A, 0, -1, Common) ;
        if (Common->status < CHOLMOD_OK)
        {
        PRINT0 (("make symmetric failed\n")) ;
        return (EMPTY) ;
        }
        ASSERT ((Int) (B->nrow) == n && (Int) (B->ncol) == n) ;
        PRINT2 (("nested dissection (2)\n")) ;
        B->stype = -1 ;
        if (nd_camd == 2)
        {
        /* workspace:  Head (nrow+1), Iwork (nrow) if symmetric-upper */
        ok = CHOLMOD(csymamd) (B, Cmember, Perm, Common) ;
        }
        else
        {
        /* workspace: Head (nrow), Iwork (4*nrow) */
        ok = CHOLMOD(camd) (B, NULL, 0, Cmember, Perm, Common) ;
        }
        CHOLMOD(free_sparse) (&B, Common) ;
        if (!ok)
        {
        /* failed */
        PRINT0 (("camd/csymamd failed\n")) ;
        return (EMPTY) ;
        }
    }
    else
    {
        /* ordering A*A' or A(:,f)*A(:,f)' */
        /* workspace: Iwork (nrow if no fset; MAX(nrow,ncol) if fset) */
        if (!CHOLMOD(ccolamd) (A, fset, fsize, Cmember, Perm, Common))
        {
        /* ccolamd failed */
        PRINT2 (("ccolamd failed\n")) ;
        return (EMPTY) ;
        }
    }

    }
    else
    {

    /* ------------------------------------------------------------------ */
    /* natural ordering of each component */
    /* ------------------------------------------------------------------ */

    /* use Iwork [0..n-1] for Next [ */
    Next = Iwork  ;

    /* ------------------------------------------------------------------ */
    /* place the nodes in link lists, one list per component */
    /* ------------------------------------------------------------------ */

    /* do so in reverse order, to preserve original ordering */
    for (j = n-1 ; j >= 0 ; j--)
    {
        /* node j is in the cth component */
        c = Cmember [j] ;
        ASSERT (c >= 0 && c < ncomponents) ;
        /* place node j in link list for component c */
        Next [j] = Head [c] ;
        Head [c] = j ;
    }

    /* ------------------------------------------------------------------ */
    /* order each node in each component */
    /* ------------------------------------------------------------------ */

    k = 0 ;
    for (c = 0 ; c < ncomponents ; c++)
    {
        for (j = Head [c] ; j != EMPTY ; j = Next [j])
        {
        Perm [k++] = j ;
        }
        Head [c] = EMPTY ;
    }
    ASSERT (k == n) ;

    /* done using Iwork [0..n-1] for Next ] */
    }

    /* ---------------------------------------------------------------------- */
    /* clear workspace and return number of components */
    /* ---------------------------------------------------------------------- */

    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ;
    return (ncomponents) ;
}

/* ========================================================================== */
/* === cholmod_collapse_septree ============================================= */
/* ========================================================================== */

/* cholmod_nested_dissection returns the separator tree that was used in the
 * constrained minimum degree algorithm.  Parameter settings (nd_small,
 * nd_oksep, etc) that give a good fill-reducing ordering may give too fine of
 * a separator tree for other uses (parallelism, multi-level LPDASA, etc).  This
 * function takes as input the separator tree computed by
 * cholmod_nested_dissection, and collapses selected subtrees into single
 * nodes.  A subtree is collapsed if its root node (the separator) is large
 * compared to the total number of nodes in the subtree, or if the subtree is
 * small.  Note that the separator tree may actually be a forest.
 *
 * nd_oksep and nd_small act just like the ordering parameters in Common.
 * Returns the new number of nodes in the separator tree.
 */

UF_long CHOLMOD(collapse_septree)
(
    /* ---- input ---- */
    size_t n,       /* # of nodes in the graph */
    size_t ncomponents, /* # of nodes in the separator tree (must be <= n) */
    double nd_oksep,    /* collapse if #sep >= nd_oksep * #nodes in subtree */
    size_t nd_small,    /* collapse if #nodes in subtree < nd_small */
    /* ---- in/out --- */
    Int *CParent,   /* size ncomponents; from cholmod_nested_dissection */
    Int *Cmember,   /* size n; from cholmod_nested_dissection */
    /* --------------- */
    cholmod_common *Common
)
{
    Int *First, *Count, *Csubtree, *W, *Map ;
    Int c, j, k, nc, sepsize, total_weight, parent, nc_new, first ;
    int collapse = FALSE, ok = TRUE ;
    size_t s ;

    /* ---------------------------------------------------------------------- */
    /* get inputs */
    /* ---------------------------------------------------------------------- */

    RETURN_IF_NULL_COMMON (EMPTY) ;
    RETURN_IF_NULL (CParent, EMPTY) ;
    RETURN_IF_NULL (Cmember, EMPTY) ;
    if (n < ncomponents)
    {
    ERROR (CHOLMOD_INVALID, "invalid separator tree") ;
    return (EMPTY) ;
    }
    Common->status = CHOLMOD_OK ;
    nc = ncomponents ;
    if (n <= 1 || ncomponents <= 1)
    {
    /* no change; tree is one node already */
    return (nc) ;
    }

    nd_oksep = MAX (0, nd_oksep) ;
    nd_oksep = MIN (1, nd_oksep) ;
    nd_small = MAX (4, nd_small) ;

    /* ---------------------------------------------------------------------- */
    /* allocate workspace */
    /* ---------------------------------------------------------------------- */

    /* s = 3*ncomponents */
    s = CHOLMOD(mult_size_t) (ncomponents, 3, &ok) ;
    if (!ok)
    {
    ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
    return (EMPTY) ;
    }
    CHOLMOD(allocate_work) (0, s, 0, Common) ;
    if (Common->status < CHOLMOD_OK)
    {
    return (EMPTY) ;
    }
    W = Common->Iwork ;
    Count    = W ; W += ncomponents ;       /* size ncomponents */
    Csubtree = W ; W += ncomponents ;       /* size ncomponents */
    First    = W ; W += ncomponents ;       /* size ncomponents */

    /* ---------------------------------------------------------------------- */
    /* find the first descendant of each node of the separator tree */
    /* ---------------------------------------------------------------------- */

    for (c = 0 ; c < nc ; c++)
    {
    First [c] = EMPTY ;
    }
    for (k = 0 ; k < nc ; k++)
    {
    for (c = k ; c != EMPTY && First [c] == -1 ; c = CParent [c])
    {
        ASSERT (c >= 0 && c < nc) ;
        First [c] = k ;
    }
    }

    /* ---------------------------------------------------------------------- */
    /* find the number of nodes of the graph in each node of the tree */
    /* ---------------------------------------------------------------------- */

    for (c = 0 ; c < nc ; c++)
    {
    Count [c] = 0 ;
    }
    for (j = 0 ; j < (Int) n ; j++)
    {
    ASSERT (Cmember [j] >= 0 && Cmember [j] < nc) ;
    Count [Cmember [j]]++ ;
    }

    /* ---------------------------------------------------------------------- */
    /* find the number of nodes in each subtree */
    /* ---------------------------------------------------------------------- */

    for (c = 0 ; c < nc ; c++)
    {
    /* each subtree includes its root */
    Csubtree [c] = Count [c] ;
    PRINT1 ((ID" size "ID" parent "ID" first "ID"\n",
        c, Count [c], CParent [c], First [c])) ;
    }

    for (c = 0 ; c < nc ; c++)
    {
    /* add the subtree of the child, c, into the count of its parent */
    parent = CParent [c] ;
    ASSERT (parent >= EMPTY && parent < nc) ;
    if (parent != EMPTY)
    {
        Csubtree [parent] += Csubtree [c] ;
    }
    }

#ifndef NDEBUG
    /* the sum of the roots should be n */
    j = 0 ;
    for (c = 0 ; c < nc ; c++) if (CParent [c] == EMPTY) j += Csubtree [c] ;
    ASSERT (j == (Int) n) ;
#endif

    /* ---------------------------------------------------------------------- */
    /* find subtrees to collapse */
    /* ---------------------------------------------------------------------- */

    /* consider all nodes in reverse post-order */
    for (c = nc-1 ; c >= 0 ; c--)
    {
    /* consider the subtree rooted at node c */
    sepsize = Count [c] ;
    total_weight = Csubtree [c] ;
    PRINT1 (("Node "ID" sepsize "ID" subtree "ID" ratio %g\n", c, sepsize,
        total_weight, ((double) sepsize)/((double) total_weight))) ;
    first = First [c] ;
    if (first < c &&    /* c must not be a leaf */
       (sepsize > nd_oksep * total_weight || total_weight < (int) nd_small))
    {
        /* this separator is too large, or the subtree is too small.
         * collapse the tree, by converting the entire subtree rooted at
         * c into a single node.  The subtree consists of all nodes from
         * First[c] to the root c.  Flag all nodes from First[c] to c-1
         * as dead.
         */
        collapse = TRUE ;
        for (k = first ; k < c ; k++)
        {
        CParent [k] = -2 ;
        PRINT1 (("   collapse node "ID"\n", k)) ;
        }
        /* continue at the next node, first-1 */
        c = first ;
    }
    }

    PRINT1 (("collapse: %d\n", collapse)) ;

    /* ---------------------------------------------------------------------- */
    /* compress the tree */
    /* ---------------------------------------------------------------------- */

    Map = Count ;   /* Count no longer needed */

    nc_new = nc ;
    if (collapse)
    {
    nc_new = 0 ;
    for (c = 0 ; c < nc ; c++)
    {
        Map [c] = nc_new ;
        if (CParent [c] >= EMPTY)
        {
        /* node c is alive, and becomes node Map[c] in the new tree.
         * Increment nc_new for the next node c. */
        nc_new++ ;
        }
    }
    PRINT1 (("Collapse the tree from "ID" to "ID" nodes\n", nc, nc_new)) ;
    ASSERT (nc_new > 0) ;
    for (c = 0 ; c < nc ; c++)
    {
        parent = CParent [c] ;
        if (parent >= EMPTY)
        {
        /* node c is alive */
        CParent [Map [c]] = (parent == EMPTY) ? EMPTY : Map [parent] ;
        }
    }
    for (j = 0 ; j < (Int) n ; j++)
    {
        PRINT1 (("j "ID" Cmember[j] "ID" Map[Cmember[j]] "ID"\n",
        j, Cmember [j], Map [Cmember [j]])) ;
        Cmember [j] = Map [Cmember [j]] ;
    }
    }

    /* ---------------------------------------------------------------------- */
    /* return new size of separator tree */
    /* ---------------------------------------------------------------------- */

    return (nc_new) ;
}
#endif