/* ========================================================================== */
/* === UMFPACK_qsymbolic ==================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 3.2 (Jan. 1, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis, University of Florida, davis@cise.ufl.edu.  All Rights Reserved.    */
/* See README, umfpack.h, or type "umfpack_details" in Matlab for License.    */
/* -------------------------------------------------------------------------- */

/*
    User-callable.  Performs a symbolic factorization.
    See umfpack_qsymbolic.h and UMFPACK_symbolic.h for details.

    Dynamic memory usage of UMFPACK_qsymbolic:

    1)  calls UMF_malloc 6 times, for a workspace of size
	(C + 5*n)*sizeof(Int) The value of C is determined by the macro
	UMF_COLAMD_RECOMMENDED.  It is roughly
	max (2*nz, 3*n) + 14*(n+1) + nz/5, or typically about 2.2*nz + 14*n,
	where nz is the number of entries in A.

    2)  two more calls to UMF_malloc are made, for a total space of
	(n+1) integers + sizeof (SymbolicType).  sizeof (SymbolicType) is a
	small constant.  This space is part of the Symbolic object and is not
	freed unless an error occurs.  The analysis (UMF_colamd and/or
	UMF_analyze) is then performed.

    3)  UMF_malloc is called 5 times, for a total workspace of
	(2*(nfr+1)+3*(nchains+1))*sizeof(Int), where nfr is the total
	number of frontal matrices and nchains is the total number
	of frontal matrix chains, and where nchains <= nfr <= n.
	This space is part of the Symbolic object and is not free'd
	unless an error occurs.

	Thus, the peak memory usage of UMFPACK_symbolic occurs at this point.
	It is roughly (2.2*nz + 19*n)*sizeof(Int) for temporary workspace,
	plus between n and 6*n times sizeof(Int) for the final Symbolic object.

    4)	The 6 workspace objects allocated in step (1) are free'd via
	UMF_free.  The final Symbolic object consists of 7 allocated objects.
	Its final total size is lies roughly between n and 6*n integers.
	If an error occurs, all 7 are free'd via UMF_free.

    Dynamic memory usage of UMFPACK_free_symbolic:

    1)  All 7 objects comprising the Symbolic object are free'd via UMF_free.

*/

/* ========================================================================== */

#include "umf_internal.h"
#include "umf_symbolic_usage.h"
#include "umf_colamd.h"
#include "umf_set_stats.h"
#include "umf_analyze.h"
#include "umf_transpose.h"
#include "umf_is_permutation.h"
#include "umf_kernel_init_usage.h"
#include "umf_malloc.h"
#include "umf_free.h"

typedef struct	/* SWorkType */
{
    Int *Front_npivots ;
    Int *Front_nrows ;
    Int *Front_ncols ;
    Int *Front_parent ;
    Int *Front_cols ;		/* in UMF_colamd only */
    Int *Ci ;

} SWorkType ;

PRIVATE void free_work
(
    SWorkType *SWork
) ;

PRIVATE void error
(
    SymbolicType **Symbolic,
    SWorkType *SWork
) ;

#define SYM_WORK_USAGE(n,Clen) (DUNITS (Int, Clen) + 5*DUNITS (Int, n))

#ifndef NDEBUG
PRIVATE Int init_count ;
#endif

/* ========================================================================== */

GLOBAL Int UMFPACK_qsymbolic
(
    Int n,
    const Int Ap [ ],
    const Int Ai [ ],
    const Int Q [ ],
    void **SymbolicHandle,
    const double Control [UMFPACK_CONTROL],
    double User_Info [UMFPACK_INFO]
)
{

    /* ---------------------------------------------------------------------- */
    /* local variables */
    /* ---------------------------------------------------------------------- */

    Int colamd_ok, i, nz, maxfrsize, s, j, newj, status, maxnpiv, chain_npiv,
	maxnrows, maxncols, nfr, col, nchains, maxrows, maxcols, p, nb,
	*Chain_start, *Chain_maxrows, *Chain_maxcols, *Front_npivots, *Cp, *Ci,
	Clen, colamd_stats [COLAMD_STATS], fpiv, frows, fcols,
	child, cr, cc, parent, *Link, *Row_degree, row, *Front_parent,
	head_usage, tail_usage, max_usage, analyze_compactions,
	lf, uf, init_tail_usage, k, do_colamd, do_UMF_analyze, too_large ;
    double knobs [COLAMD_KNOBS], flops, f, fr, fc, fb, tstart, tend,
	*Info, Info2 [UMFPACK_INFO], drow, dcol, dusage, dlf, duf, dmax_usage,
	dhead_usage, dh, dlnz, dunz, dmaxfrsize, dClen ;
    SymbolicType *Symbolic ;
    SWorkType SWorkSpace, *SWork ;

#ifndef NDEBUG
    double f2 ;
    Int lf2, uf2 ;
    UMF_dump_start ( ) ;
    init_count = UMF_malloc_count ;
#endif

    /* ---------------------------------------------------------------------- */
    /* get the amount of time used by the process so far */
    /* ---------------------------------------------------------------------- */

    tstart = umfpack_timer ( ) ;

    /* ---------------------------------------------------------------------- */
    /* check input parameters */
    /* ---------------------------------------------------------------------- */

    if (Control)
    {
	/* use the Control array passed to us by the caller. */
	drow = Control [UMFPACK_DENSE_ROW] ;
	dcol = Control [UMFPACK_DENSE_COL] ;
	nb = (Int) Control [UMFPACK_BLOCK_SIZE] ;
    }
    else
    {
	/* no Control passed - use defaults instead */
	drow = UMFPACK_DEFAULT_DENSE_ROW ;
	dcol = UMFPACK_DEFAULT_DENSE_COL ;
	nb = UMFPACK_DEFAULT_BLOCK_SIZE ;
    }

    nb = MAX (1, nb) ;
    DEBUG0 (("nb = %d\n", nb)) ;

    if (User_Info)
    {
	/* return Info in user's array */
	Info = User_Info ;
    }
    else
    {
	/* no Info array passed - use local one instead */
	Info = Info2 ;
    }
    for (i = 0 ; i < UMFPACK_INFO ; i++)
    {
	Info [i] = EMPTY ;
    }
    Info [UMFPACK_STATUS] = UMFPACK_OK ;
    Info [UMFPACK_N] = n ;
    Info [UMFPACK_SIZE_OF_UNIT] = (double) (sizeof (Unit)) ;
    Info [UMFPACK_SIZE_OF_INT] = (double) (sizeof (int)) ;
    Info [UMFPACK_SIZE_OF_LONG] = (double) (sizeof (long)) ;
    Info [UMFPACK_SIZE_OF_POINTER] = (double) (sizeof (void *)) ;
    Info [UMFPACK_SIZE_OF_ENTRY] = (double) (sizeof (double)) ;
    Info [UMFPACK_SYMBOLIC_DEFRAG] = 0 ;

    *SymbolicHandle = (void *) NULL ;

    if (!Ai || !Ap)		/* Ai and/or Ap are missing */
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_argument_missing ;
	return (UMFPACK_ERROR_argument_missing) ;
    }

    if (n <= 0)		/* n must be > 0 */
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_n_nonpositive ;
	return (UMFPACK_ERROR_n_nonpositive) ;
    }

    nz = Ap [n] ;
    DEBUG0 (("In UMFPACK_symbolic, n "ID" nz "ID"\n", n, nz)) ;
    Info [UMFPACK_NZ] = nz ;
    if (nz == 0)
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_singular_matrix ;
	return (UMFPACK_ERROR_singular_matrix) ;
    }
    if (nz < 0)
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_nz_negative ;
	return (UMFPACK_ERROR_nz_negative) ;
    }

    if (Q)
    {
	do_colamd = FALSE ;
    }
    else
    {
	do_colamd = TRUE ;
    }

    /* ---------------------------------------------------------------------- */
    /* determine amount of memory required for UMFPACK_symbolic */
    /* ---------------------------------------------------------------------- */

    dClen = UMF_COLAMD_RECOMMENDED ((double) nz, (double) n, (double) n) ;
    too_large = INT_OVERFLOW (dClen * sizeof (Int)) ;

    if (too_large)
    {
	/* Problem is too large for array indexing (Ci [i]) with an Int i. */
	/* Cannot even analyze the problem to determine upper bounds on */
	/* memory usage. Need to use the long integer version, umfpack_l_*. */
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_problem_too_large ;
	Info [UMFPACK_SYMBOLIC_PEAK_MEMORY] =
	    SYM_WORK_USAGE (n, dClen) + UMF_symbolic_usage (n, n, n) ;
	return (UMFPACK_ERROR_problem_too_large) ;
    }

    Clen = UMF_COLAMD_RECOMMENDED (nz, n, n) ;

    /* worst case total memory usage for UMFPACK_symbolic (revised below) */
    Info [UMFPACK_SYMBOLIC_PEAK_MEMORY] =
	SYM_WORK_USAGE (n, Clen) + UMF_symbolic_usage (n, n, n) ;

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

    Symbolic = (SymbolicType *) NULL ;
    SWork = &SWorkSpace ;	/* used for UMFPACK_symbolic only */

    SWork->Ci = (Int *) UMF_malloc (Clen, sizeof (Int)) ;
    SWork->Front_npivots = (Int *) UMF_malloc (n, sizeof (Int)) ;
    SWork->Front_nrows = (Int *) UMF_malloc (n, sizeof (Int)) ;
    SWork->Front_ncols = (Int *) UMF_malloc (n, sizeof (Int)) ;
    SWork->Front_parent = (Int *) UMF_malloc (n, sizeof (Int)) ;
    SWork->Front_cols = (Int *) UMF_malloc (n, sizeof (Int)) ;

    if (!SWork->Ci || !SWork->Front_npivots || !SWork->Front_nrows ||
	!SWork->Front_ncols || !SWork->Front_parent || !SWork->Front_cols)
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_out_of_memory ;
	error (&Symbolic, SWork) ;
	return (UMFPACK_ERROR_out_of_memory) ;
    }

    /* ---------------------------------------------------------------------- */
    /* allocate the first part of the Symbolic object (header and Cperm_init) */
    /* ---------------------------------------------------------------------- */

    Symbolic = (SymbolicType *) UMF_malloc (1, sizeof (SymbolicType)) ;

    if (!Symbolic)
    {
	/* If we fail here, Symbolic is NULL and thus it won't be */
	/* dereferenced by UMFPACK_free_symbolic, as called by error ( ). */
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_out_of_memory ;
	error (&Symbolic, SWork) ;
	return (UMFPACK_ERROR_out_of_memory) ;
    }

    /* We now know that Symbolic has been allocated */
    Symbolic->valid = 0 ;
    Symbolic->Chain_start = (Int *) NULL ;
    Symbolic->Chain_maxrows = (Int *) NULL ;
    Symbolic->Chain_maxcols = (Int *) NULL ;
    Symbolic->Front_npivots = (Int *) NULL ;
    Symbolic->Front_parent = (Int *) NULL ;

    Symbolic->Cperm_init = (Int *) UMF_malloc (n+1, sizeof (Int)) ;

    if (!Symbolic->Cperm_init)
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_out_of_memory ;
	error (&Symbolic, SWork) ;
	return (UMFPACK_ERROR_out_of_memory) ;
    }

    DEBUG0 (("(1)Symbolic UMF_malloc_count - init_count = "ID"\n",
	UMF_malloc_count - init_count)) ;
    ASSERT (UMF_malloc_count == init_count + 8) ;

    Symbolic->n = n ;
    Symbolic->nz = nz ;
    Symbolic->drow = drow ;
    Symbolic->dcol = dcol ;
    Symbolic->nb = nb ;

    /* ---------------------------------------------------------------------- */
    /* use colamd or input column ordering Q */
    /* ---------------------------------------------------------------------- */

    if (do_colamd)
    {

	/* ------------------------------------------------------------------ */
	/* copy the matrix Into colamd workspace (colamd destroys its input) */
	/* ------------------------------------------------------------------ */

	Ci = SWork->Ci ;
	for (i = 0 ; i < nz ; i++)
	{
	    DEBUG5 (("Ai  "ID": "ID"\n", i, Ai [i])) ;
	    Ci [i] = Ai [i] ;
	}

	/* load the column pointers into Cperm_init [0..n] */
	Cp = Symbolic->Cperm_init ;
	for (col = 0 ; col <= n ; col++)
	{
	    DEBUG5 (("Ap  "ID": "ID"\n", col, Ap [col])) ;
	    Cp [col] = Ap [col] ;
	}

	/* ------------------------------------------------------------------ */
	/* set UMF_colamd defaults */
	/* ------------------------------------------------------------------ */

	UMF_colamd_set_defaults (knobs) ;
	knobs [COLAMD_DENSE_ROW] = drow ;
	knobs [COLAMD_DENSE_COL] = dcol ;

	/* ------------------------------------------------------------------ */
	/* check input matrix and find the initial column pre-ordering */
	/* ------------------------------------------------------------------ */

	colamd_ok = UMF_colamd (n, n, Clen, SWork->Ci,
		Symbolic->Cperm_init, knobs, colamd_stats,
		SWork->Front_npivots,
		SWork->Front_nrows,
		SWork->Front_ncols,
		SWork->Front_parent,
		/* used in UMF_colamd only: */ SWork->Front_cols,
		&nfr) ;

	if (!colamd_ok)
	{
	    /* This will be modified below.  It is not (yet) an internal */
	    /* error.  It is only an internal error if is it not modified */
	    /* below (colamd_stats missing). */
	    Info [UMFPACK_STATUS] = UMFPACK_ERROR_internal_error ;
	}

	s = colamd_stats [COLAMD_STATUS] ;

	if (s == COLAMD_OK)
	{
	    status = UMFPACK_OK ;
	}
	else if (s == COLAMD_ERROR_singular_matrix)
	{
	    status = UMFPACK_ERROR_singular_matrix ;
	}
	else if (s == COLAMD_ERROR_jumbled_matrix)
	{
	    status = UMFPACK_ERROR_jumbled_matrix ;
	}
	else if (s == COLAMD_ERROR_p0_nonzero)
	{
	    status = UMFPACK_ERROR_Ap0_nonzero ;
	}
	else if (s == COLAMD_ERROR_row_index_out_of_bounds)
	{
	    status = UMFPACK_ERROR_row_index_out_of_bounds ;
	}
	else if (s == COLAMD_ERROR_col_length_negative)
	{
	    status = UMFPACK_ERROR_col_length_negative ;
	}
	else
	{
	    /* these errors "cannot" happen */
	    /* COLAMD_ERROR_A_too_small */
	    /* COLAMD_ERROR_A_not_present */
	    /* COLAMD_ERROR_p_not_present */
	    /* COLAMD_ERROR_out_of_memory */
	    /* COLAMD_ERROR_internal_error */
	    /* COLAMD_ERROR_nrow_negative */
	    /* COLAMD_ERROR_ncol_negative */
	    /* COLAMD_ERROR_nnz_negative */
	    status = UMFPACK_ERROR_internal_error ;
	}

	Info [UMFPACK_STATUS] = status ;
	if (status != UMFPACK_OK)
	{
	    error (&Symbolic, SWork) ;
	    return (status) ;
	}

	Info [UMFPACK_NDENSE_ROW] = colamd_stats [COLAMD_DENSE_ROW] ;
	Info [UMFPACK_NEMPTY_ROW] = colamd_stats [COLAMD_EMPTY_ROW] ;
	Info [UMFPACK_NDENSE_COL] = colamd_stats [COLAMD_DENSE_COL] ;
	Info [UMFPACK_NEMPTY_COL] = colamd_stats [COLAMD_EMPTY_COL] ;
	Info [UMFPACK_SYMBOLIC_DEFRAG] = colamd_stats [COLAMD_DEFRAG_COUNT];

	/* re-analyze if any "dense" rows or cols ignored by UMF_colamd */
	do_UMF_analyze = 
	    colamd_stats [COLAMD_DENSE_ROW] > 0 ||
	    colamd_stats [COLAMD_DENSE_COL] > 0 ;

#ifndef NDEBUG
	for (col = 0 ; col < n ; col++)
	{
	    DEBUG1 (("Cperm_init ["ID"] = "ID"\n",
		col, Symbolic->Cperm_init[col]));
	}
	/* make sure colamd returned a valid permutation */
	ASSERT (Symbolic->Cperm_init) ;
	ASSERT (UMF_is_permutation (Symbolic->Cperm_init, SWork->Ci,
	   n, n)) ;
#endif

    }
    else
    {

	/* ------------------------------------------------------------------ */
	/* do not call colamd - use input Q instead */
	/* ------------------------------------------------------------------ */

	Int length ;

	/* use Ci as workspace to check input permutation */
	if (!UMF_is_permutation (Q, SWork->Ci, n, n))
	{
	    Info [UMFPACK_STATUS] = UMFPACK_ERROR_invalid_permutation ;
	    error (&Symbolic, SWork) ;
	    return (UMFPACK_ERROR_invalid_permutation) ;
	}

	if (Ap [0] != 0)
	{
	    Info [UMFPACK_STATUS] = UMFPACK_ERROR_Ap0_nonzero ;
	    error (&Symbolic, SWork) ;
	    return (UMFPACK_ERROR_Ap0_nonzero) ;
	}

	/* check Ap input */
	for (j = 0 ; j < n ; j++)
	{
	    length = Ap [j+1] - Ap [j] ;
	    if (length == 0)
	    {
		Info [UMFPACK_STATUS] = UMFPACK_ERROR_singular_matrix ;
		error (&Symbolic, SWork) ;
		return (UMFPACK_ERROR_singular_matrix) ;
	    }
	    if (length < 0)
	    {
		Info [UMFPACK_STATUS] = UMFPACK_ERROR_col_length_negative ;
		error (&Symbolic, SWork) ;
		return (UMFPACK_ERROR_col_length_negative) ;
	    }
	}

	Info [UMFPACK_NDENSE_ROW] = 0 ;
	Info [UMFPACK_NEMPTY_ROW] = 0 ;
	Info [UMFPACK_NDENSE_COL] = 0 ;
	Info [UMFPACK_NEMPTY_COL] = 0 ;

	/* copy the user's input permutation */
	for (k = 0 ; k < n ; k++)
	{
	    Symbolic->Cperm_init [k] = Q [k] ;
	}

	do_UMF_analyze = TRUE ;

    }

    Symbolic->Cperm_init [n] = EMPTY ;	/* unused in Cperm_init */

    /* ---------------------------------------------------------------------- */
    /* analyze, using the given ordering, or using colamd's ordering */
    /* ---------------------------------------------------------------------- */

    if (do_UMF_analyze)
    {

	Int *W, *Bp, *Bi, *Cperm2, *Cperm_init, ok, ilast,
	    *P, Clen2, bnz, bsize, Clen0 ;

	/* ------------------------------------------------------------------ */
	/* Clen must be of size:
		Bp, Link, W, Cperm2:	4*n+1
		Bi:			MAX (bnz,n), where bnz <= nz
		Ci [0..Clen0-1]:	Clen0 >= nz+n

		Total:  5*n+1 + 2*nz, assuming bnz >= n
			6*n+1 + nz if bnz < n

		This is less than colamd_recommend (anz, n, n), so
		we are guaranteed enough space for UMF_transpose and
		UMF_analyze.
	*/
	/* ------------------------------------------------------------------ */

	Cperm_init = Symbolic->Cperm_init ;

	/* use Ci for workspace: Bp (n+1), W, Link, and Cperm2 (n each) */
	Ci = SWork->Ci ;
	Clen0 = Clen - (4*n+1) ;
	Bp = Ci + Clen0 ;
	Link = Bp + (n+1) ;
	W = Link + n ;
	Cperm2 = W + n ;
	ASSERT (Cperm2 + n == Ci + Clen) ;

	/* ------------------------------------------------------------------ */
	/* P = order that rows will be used in UMF_analyze */
	/* ------------------------------------------------------------------ */

	/* use W to mark rows, and use Link for row permutation P [ [ */
	for (i = 0 ; i < n ; i++)
	{
	    W [i] = FALSE ;
	}
	P = Link ;

	bnz = 0 ;
	k = 0 ;
	for (newj = 0 ; newj < n ; newj++)
	{
	    j = Cperm_init [newj] ;
	    bnz += Ap [j+1] - Ap [j] ;
	    ilast = -1 ;
	    for (p = Ap [j] ; p < Ap [j+1] ; p++)
	    {
		i = Ai [p] ;
		if (i < 0 || i >= n)
		{
		    Info [UMFPACK_STATUS] =
			UMFPACK_ERROR_row_index_out_of_bounds ;
		    error (&Symbolic, SWork) ;
		    return (UMFPACK_ERROR_row_index_out_of_bounds) ;
		}
		if (i <= ilast)
		{
		    Info [UMFPACK_STATUS] = UMFPACK_ERROR_jumbled_matrix ;
		    error (&Symbolic, SWork) ;
		    return (UMFPACK_ERROR_jumbled_matrix) ;
		}
		if (!W [i])
		{
		    /* this row has just been see for the first time */
		    W [i] = TRUE ;
		    P [k++] = i ;
		}
		ilast = i ;
	    }
	}

	/* If the whole matrix has truly empty rows, then P will not be */
	/* complete, and visa versa.  The matrix is structurally singular. */
	if (k < n)
	{
	    Info [UMFPACK_STATUS] = UMFPACK_ERROR_singular_matrix ;
	    error (&Symbolic, SWork) ;
	    return (UMFPACK_ERROR_singular_matrix) ;
	}

	/* contents of W no longer needed ] */

	ASSERT (k == n) ;
	ASSERT (UMF_is_permutation (P, W, n, n)) ;

	/* ------------------------------------------------------------------ */
	/* B = row-form of the pattern of A (P, Cperm_init (1:n)) */
	/* ------------------------------------------------------------------ */

	/* use tail end of Ci for Bi */
	Clen2 = Clen0 ;
	bsize = MAX (bnz, n) ;
	Clen2 -= bsize ;
	Bi = Ci + Clen2 ;

	/* skip error test (already done, above) */
	(void) UMF_transpose (n, Ap, Ai, (double *) NULL, P,
	    Cperm_init, n, Bp, Bi, (double *) NULL, W, FALSE) ;

	/* contents of P (same as Link) and W not needed */
	/* still need Link and W as work arrays, though ] */

	ASSERT (Bp [0] == 0) ;
	ASSERT (Bp [n] == bnz) ;

	/* increment Bp to point into Ci, not Bi */
	for (i = 0 ; i <= n ; i++)
	{
	    Bp [i] += Clen2 ;
	}
	ASSERT (Bp [0] == Clen0 - bsize) ;
	ASSERT (Bp [n] <= Clen0) ;

	/* ------------------------------------------------------------------ */
	/* analyze */
	/* ------------------------------------------------------------------ */

	ok = UMF_analyze (n, n, Ci, Bp, Cperm2, W, Link,
		SWork->Front_ncols, SWork->Front_nrows, SWork->Front_npivots,
		SWork->Front_parent, &nfr, &analyze_compactions) ;
	if (!ok)
	{
	    Info [UMFPACK_STATUS] = UMFPACK_ERROR_internal_error ;
	    error (&Symbolic, SWork) ;
	    return (UMFPACK_ERROR_internal_error) ;
	}
	Info [UMFPACK_SYMBOLIC_DEFRAG] += analyze_compactions ;

	/* ------------------------------------------------------------------ */
	/* combine the input permutation and UMF_analyze's permutation */
	/* ------------------------------------------------------------------ */

	ASSERT (UMF_is_permutation (Cperm2, W, n, n)) ;

	for (k = 0 ; k < n ; k++)
	{
	    W [k] = Cperm_init [Cperm2 [k]] ;
	}

	for (k = 0 ; k < n ; k++)
	{
	    Cperm_init [k] = W [k] ;
	}

	ASSERT (UMF_is_permutation (Cperm_init, W, n, n)) ;

    }

    /* ---------------------------------------------------------------------- */
    /* determine the size of the Symbolic object */
    /* ---------------------------------------------------------------------- */

    nchains = 0 ;
    for (i = 0 ; i < nfr ; i++)
    {
	if (SWork->Front_parent [i] != i+1)
	{
	    nchains++ ;
	}
    }

    Symbolic->nchains = nchains ;
    Symbolic->nfr = nfr ;

    /* final size of Symbolic object */
    Info [UMFPACK_SYMBOLIC_SIZE] = UMF_symbolic_usage (n, nchains, nfr) ;

    /* actual peak memory usage for UMFPACK_symbolic (actual nfr, nchains) */
    Info [UMFPACK_SYMBOLIC_PEAK_MEMORY] =
	SYM_WORK_USAGE (n, Clen) + Info [UMFPACK_SYMBOLIC_SIZE] ;
    Symbolic->peak_sym_usage = Info [UMFPACK_SYMBOLIC_PEAK_MEMORY] ;

    DEBUG0 (("Number of fronts: "ID"\n", nfr)) ;

    /* ---------------------------------------------------------------------- */
    /* allocate the second part of the Symbolic object (Front_*, Chain_*) */
    /* ---------------------------------------------------------------------- */

    Symbolic->Front_npivots = (Int *) UMF_malloc (nfr+1, sizeof (Int)) ;
    Symbolic->Front_parent = (Int *) UMF_malloc (nfr+1, sizeof (Int)) ;
    Symbolic->Chain_start = (Int *) UMF_malloc (nchains+1, sizeof (Int)) ;
    Symbolic->Chain_maxrows = (Int *) UMF_malloc (nchains+1, sizeof (Int)) ;
    Symbolic->Chain_maxcols = (Int *) UMF_malloc (nchains+1, sizeof (Int)) ;

    if (!Symbolic->Front_npivots || !Symbolic->Front_parent ||
	!Symbolic->Chain_start || !Symbolic->Chain_maxrows ||
	!Symbolic->Chain_maxcols)
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_out_of_memory ;
	error (&Symbolic, SWork) ;
	return (UMFPACK_ERROR_out_of_memory) ;
    }
    DEBUG0 (("(2)Symbolic UMF_malloc_count - init_count = "ID"\n",
	UMF_malloc_count - init_count)) ;
    ASSERT (UMF_malloc_count == init_count + 13) ;

    Front_npivots = Symbolic->Front_npivots ;
    Front_parent = Symbolic->Front_parent ;
    Chain_start = Symbolic->Chain_start ;
    Chain_maxrows = Symbolic->Chain_maxrows ;
    Chain_maxcols = Symbolic->Chain_maxcols ;

    /* ---------------------------------------------------------------------- */
    /* compute memory and flop estimates */
    /* ---------------------------------------------------------------------- */

    nchains = 0 ;			/* number of chains */
    Chain_start [0] = 0 ;		/* front 0 starts a new chain */
    maxrows = 1 ;
    maxcols = 1 ;
    maxfrsize = 1 ;
    dmaxfrsize = 1 ;
    chain_npiv = 0 ;			/* number of pivots in current chain */

    /* ---------------------------------------------------------------------- */
    /* simulate UMF_kernel_init */
    /* ---------------------------------------------------------------------- */

    /* Use SWork->Ci as temporary workspace for initial row degrees */
    Row_degree = SWork->Ci ;
    for (row = 0 ; row < n ; row++)
    {
	Row_degree [row] = 0 ;
    }
    for (newj = 0 ; newj < n ; newj++)
    {
	j = Symbolic->Cperm_init [newj] ;
	for (p = Ap [j] ; p < Ap [j+1] ; p++)
	{
	    Row_degree [Ai [p]]++ ;
	}
    }

    /* Numeric->Memory usage estimate, in Units */
    head_usage = 1 ;		/* head marker (see UMF_mem_init_memoryspace) */
    init_tail_usage = 2 ;	/* tail marker (see UMF_mem_init_memoryspace) */
    dusage = 3 ;		/* head and tail markers */
    dhead_usage = 1 ;

    /* elements and tuples at tail*/
    init_tail_usage += UMF_kernel_init_usage (Ap, Symbolic->Cperm_init,
	Row_degree, n, &dusage) ;

    tail_usage = init_tail_usage ;
    DEBUG2 (("tail_usage: "ID" (initial)\n", tail_usage)) ;
    max_usage = head_usage + init_tail_usage ;
    dmax_usage = dusage ;

    Symbolic->num_mem_init_usage = max_usage ;

    too_large = INT_OVERFLOW (dusage * sizeof (Unit)) ;
    if (too_large)
    {
	/* Initial memory usage, for input matrix only, */
	/* has encountered integer overflow. This is an error */
	/* condition, but keep going to compute other statistics. */
	Info [UMFPACK_VARIABLE_INIT_ESTIMATE] = dusage ;	/* too large */
    }
    else
    {
	Info [UMFPACK_VARIABLE_INIT_ESTIMATE] = (double) max_usage ;
    }

    /* ---------------------------------------------------------------------- */
    /* simulate UMF_kernel */
    /* ---------------------------------------------------------------------- */

    /* Use SWork->Ci as temporary workspace for link lists */
    Link = SWork->Ci ;
    for (i = 0 ; i < nfr ; i++)
    {
	Link [i] = EMPTY ;
    }

    dlnz = n ;			/* upper limit of nz in L (incl diag) */
    dunz = n ;			/* upper limit of nz in U (incl diag) */
    flops = 0 ;			/* flop count upper bound */

    DEBUG1 (("Umfpack symbolic, fronts:  nfr = "ID"\n", nfr)) ;

    for (i = 0 ; i < nfr ; i++)
    {
	fpiv  = SWork->Front_npivots [i] ; /* # of pivots in the front */
	frows = SWork->Front_nrows [i] ;   /* # rows of Schur complement */
	fcols = SWork->Front_ncols [i] ;   /* # cols of Schur complement */
	parent = SWork->Front_parent [i] ; /* parent in column etree */

	DEBUG1 ((ID" : npiv "ID" nrows "ID" ncols "ID" parent "ID"\n",
		INDEX (i), fpiv, frows, fcols, INDEX (parent))) ;

	/* assemble all children of front i in column etree */
	for (child = Link [i] ; child != EMPTY ; child = Link [child])
	{
	    ASSERT (child >= 0 && child < i) ;
	    ASSERT (SWork->Front_parent [child] == i) ;
	    /* free the child element */
	    cr = SWork->Front_nrows [child] ;
	    cc = SWork->Front_ncols [child] ;
	    tail_usage -= GET_ELEMENT_SIZE (cr, cc) + 1 ;
	    dusage -= DGET_ELEMENT_SIZE (cr, cc) + 1 ;
	    /* remove it from tuple lists */
	    tail_usage -= (cr + cc) * UNITS (Tuple, 1) ;
	    dusage -= ((double) cr + (double) cc) * UNITS (Tuple, 1) ;
	    DEBUG2 (("tail_usage: "ID" (assembled "ID" of size "ID")\n",
		tail_usage, child,
		GET_ELEMENT_SIZE (cr, cc) + 1 + (cr + cc) * UNITS (Tuple, 1))) ;
	}

	/* the flop count excludes the BLAS2 calls during pivot search */
	/* the assembly required to search a column, and assembly between */
	/* frontal matrices.  Those are "mushy" flops. */

	/* factorize the frontal matrix */
	f = (double) fpiv ;
	fr = (double) frows ;
	fc = (double) fcols ;
	fb = (f-1)*f*(2*f-1)/3 + (f-1)*f/2	/* factorize f-by-f L11,U11 */
		+ (f-1)*f*fc			/* compute U12 */
		+ f*fr + (f-1)*f*fr		/* compute L21 */
		+ 2*f*fc*fr ;			/* Schur complement for A22 */
	flops += fb ;

	/* count nonzeros in L and U */
	lf = (fpiv*fpiv-fpiv)/2 + fpiv*frows ;	/* nz in L below diagonal */
	uf = (fpiv*fpiv-fpiv)/2 + fpiv*fcols ;	/* nz in U above diagonal */

	head_usage +=
	    UNITS (double, lf + uf)	 /* numerical values (excl diagonal) */
	    + UNITS (Int, frows + fcols + fpiv) ; /* indices (compressed) */

	dlf = (f*f-f)/2 + f*fr ;	/* nz in L below diagonal */
	duf = (f*f-f)/2 + f*fc ;	/* nz in U above diagonal */
	dlnz += dlf ;
	dunz += duf ;
	dh =
	    DUNITS (double, dlf + duf)    /* numerical values (excl diagonal) */
	    + DUNITS (Int, fr + fc + f) ; /* indices (compressed) */
	dusage += dh ;
	dhead_usage += dh ;

	if (parent != EMPTY)
	{
	    /* create new element */
	    tail_usage += GET_ELEMENT_SIZE (frows, fcols) + 1 ;
	    dusage += DGET_ELEMENT_SIZE (frows, fcols) + 1 ;

	    /* place new element in tuple lists */
	    tail_usage += (frows + fcols) * UNITS (Tuple, 1) ;
	    dusage += (fr + fc) * UNITS (Tuple, 1) ;
	    DEBUG2 (("tail_usage: "ID" (create    "ID" of size "ID")\n",
		tail_usage, i,
		GET_ELEMENT_SIZE (frows, fcols) + 1
		+ (frows + fcols) * UNITS (Tuple, 1))) ;

	    /* place in link list of parent */
	    Link [i] = Link [parent] ;
	    Link [parent] = i ;
	}

	/* keep track of peak Numeric->Memory usage */
	max_usage = MAX (max_usage, head_usage + tail_usage) ;

	/* max_usage may encounter integer overflow, so dmax_usage also kept. */
	/* account for possible roundoff errors in dusage. */
	dusage *= (1.0 + MAX_EPSILON) ;
	dusage = MAX (dusage,
	    (double) (head_usage + tail_usage) * (1.0 + MAX_EPSILON)) ;
	dmax_usage = MAX (dmax_usage, dusage) ;
	dhead_usage *= (1.0 + MAX_EPSILON) ;
	dhead_usage = MAX (dhead_usage,
	    (double) head_usage * (1.0 + MAX_EPSILON)) ;

	/* construct info about front and chain, for UMFPACK_numeric */
	Front_npivots [i] = fpiv ;
	Front_parent [i] = parent ;
	/* at most nb or chain_npiv pending pivots from old fronts */
	maxnpiv = MIN (nb, chain_npiv) + fpiv ;
	maxrows = MAX (maxrows, maxnpiv + frows) ;
	maxcols = MAX (maxcols, maxnpiv + fcols) ;

	DEBUG1 (("%d: chain_npiv %d fpiv %d frows %d fcols %d   maxnpiv %d maxrows %d maxcols %d\n",
		i, chain_npiv, fpiv, frows, fcols, maxnpiv, maxrows, maxcols)) ;

	chain_npiv += fpiv ;
	if (parent != i+1)
	{
	    /* this is the end of a chain */
	    Chain_maxrows [nchains] = maxrows ;
	    Chain_maxcols [nchains] = maxcols ;
	    dmaxfrsize = MAX (dmaxfrsize, (double) maxrows * (double) maxcols) ;
	    maxfrsize = MAX (maxfrsize, maxrows * maxcols) ;
	    nchains++ ;
	    Chain_start [nchains] = i+1 ;
	    maxrows = 1 ;
	    maxcols = 1 ;
	    chain_npiv = 0 ;
	}
    }

    dhead_usage = ceil (dhead_usage) ;
    dmax_usage = ceil (dmax_usage) ;

    tail_usage -= init_tail_usage ;

    /* all tuples and elements are now deallocated */
    ASSERT (tail_usage == 0) ;

    DEBUG1 (("dmaxfrsize %30.20g Int_MAX %30d\n", dmaxfrsize, Int_MAX)) ;

    /* check if the frontal matrix is too big */
    too_large = too_large || INT_OVERFLOW (dmaxfrsize * sizeof (double)) ;

    /* ---------------------------------------------------------------------- */
    /* find the biggest frontal matrix, for all chains */
    /* ---------------------------------------------------------------------- */

    maxnrows = 1 ;
    maxncols = 1 ;
    for (i = 0 ; i < nchains ; i++)
    {
	maxnrows = MAX (maxnrows, Chain_maxrows [i]) ;
	maxncols = MAX (maxncols, Chain_maxcols [i]) ;
    }

    /* information to keep for numeric factorization */
    Symbolic->maxfrsize = maxfrsize ;
    Symbolic->maxnrows = maxnrows ;
    Symbolic->maxncols = maxncols ;
    Symbolic->num_mem_usage_est = dmax_usage ;
    Symbolic->num_mem_size_est = dhead_usage ;

    /* ---------------------------------------------------------------------- */
    /* estimate total memory usage in UMFPACK_numeric */
    /* ---------------------------------------------------------------------- */

    UMF_set_stats (
	Info,
	Symbolic,
	dmax_usage,		/* estimated peak size of Numeric->Memory */
	dhead_usage,		/* estimated final size of Numeric->Memory */
	flops,			/* estimated "true flops" */
	dlnz,			/* estimated nz in L */
	dunz,			/* estimated nz in U */
	dmaxfrsize,		/* estimated largest front size */
	ESTIMATE) ;

    /* ---------------------------------------------------------------------- */

#ifndef NDEBUG
    for (i = 0 ; i < nchains ; i++)
    {
	DEBUG2 (("Chain "ID" start "ID" end "ID" maxrows "ID" maxcols "ID"\n",
		i, Chain_start [i], Chain_start [i+1] - 1,
		Chain_maxrows [i], Chain_maxcols [i])) ;
	UMF_dump_chain (Chain_start [i],
	    SWork->Front_parent,
	    SWork->Front_npivots,
	    SWork->Front_nrows,
	    SWork->Front_ncols,
	    nfr) ;
    }
    fpiv = 0 ;
    for (i = 0 ; i < nfr ; i++)
    {
	fpiv = MAX (fpiv, Front_npivots [i]) ;
    }
    DEBUG0 (("Max pivots in any front: "ID"\n", fpiv)) ;
    DEBUG1 (("Largest front: maxnrows "ID" maxncols "ID" maxfrsize "ID"\n",
	maxnrows, maxncols, maxfrsize)) ;
    DEBUG1 (("(savings "ID") nchains "ID"\n",
	(maxnrows*maxncols) - maxfrsize, nchains)) ;
#endif

    /* ---------------------------------------------------------------------- */
    /* is the problem too large? */
    /* ---------------------------------------------------------------------- */

    if (too_large)
    {
	Info [UMFPACK_STATUS] = UMFPACK_ERROR_problem_too_large ;
	error (&Symbolic, SWork) ;
	return (UMFPACK_ERROR_problem_too_large) ;
    }

    /* ---------------------------------------------------------------------- */
    /* UMFPACK_symbolic was successful, return the object handle */
    /* ---------------------------------------------------------------------- */

    Symbolic->valid = SYMBOLIC_VALID ;
    *SymbolicHandle = (void *) Symbolic ;

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

    free_work (SWork) ;
    /* Symbolic contains 7 objects */
    DEBUG0 (("(3)Symbolic UMF_malloc_count - init_count = "ID"\n",
	UMF_malloc_count - init_count)) ;
    ASSERT (UMF_malloc_count == init_count + 7) ;

    /* ---------------------------------------------------------------------- */
    /* get the time used by UMFPACK_*symbolic */
    /* ---------------------------------------------------------------------- */

    tend = umfpack_timer ( ) ;
    Info [UMFPACK_SYMBOLIC_TIME] = MAX (0, tend - tstart) ;

    return (UMFPACK_OK) ;
}


/* ========================================================================== */
/* === free_work ============================================================ */
/* ========================================================================== */

PRIVATE void free_work
(
    SWorkType *SWork
)
{
    ASSERT (SWork) ;

    SWork->Ci = (Int *) UMF_free ((void *) SWork->Ci) ;
    SWork->Front_npivots = (Int *) UMF_free ((void *) SWork->Front_npivots) ;
    SWork->Front_nrows = (Int *) UMF_free ((void *) SWork->Front_nrows) ;
    SWork->Front_ncols = (Int *) UMF_free ((void *) SWork->Front_ncols) ;
    SWork->Front_parent = (Int *) UMF_free ((void *) SWork->Front_parent) ;
    SWork->Front_cols = (Int *) UMF_free ((void *) SWork->Front_cols) ;

}


/* ========================================================================== */
/* === error ================================================================ */
/* ========================================================================== */

/* Error return from UMFPACK_symbolic.  Free all allocated memory. */

PRIVATE void error
(
    SymbolicType **Symbolic,
    SWorkType *SWork
)
{

    free_work (SWork) ;
    UMFPACK_free_symbolic ((void **) Symbolic) ;
    ASSERT (UMF_malloc_count == init_count) ;
}

