/* ========================================================================== */
/* === Cholesky/cholmod_rowfac ============================================== */
/* ========================================================================== */

/* -----------------------------------------------------------------------------
 * CHOLMOD/Cholesky Module.  Copyright (C) 2005-2006, Timothy A. Davis
 * The CHOLMOD/Cholesky 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
 * -------------------------------------------------------------------------- */

/* Full or incremental numerical LDL' or LL' factorization (simplicial, not
 * supernodal) cholmod_factorize is the "easy" wrapper for this code, but it
 * does not provide access to incremental factorization.
 *
 * cholmod_rowfac computes the full or incremental LDL' or LL' factorization of
 * A+beta*I (where A is symmetric) or A*F+beta*I (where A and F are unsymmetric
 * and only the upper triangular part of A*F+beta*I is used).  It computes
 * L (and D, for LDL') one row at a time.  beta is real.
 *
 * A is nrow-by-ncol or nrow-by-nrow.  In "packed" form it is a conventional
 * column-oriented sparse matrix.  Row indices of column j are in
 * Ai [Ap [j] ... Ap [j+1]-1] and values in the same locations of Ax.
 * will be faster if A has sorted columns.  In "unpacked" form the column
 * of A ends at Ap [j] + Anz [j] - 1 instead of Ap [j+1] - 1.
 *
 * Row indices in each column of A can be sorted or unsorted, but the routine
 * routine works fastest if A is sorted, or if only triu(A) is provided
 * for the symmetric case.
 *
 * The unit-diagonal nrow-by-nrow output matrix L is returned in "unpacked"
 * column form, with row indices of column j in Li [Lp [j] ...
 * Lp [j] + Lnz [j] - 1] and values in the same location in Lx.  The row
 * indices in each column of L are in sorted order.  The unit diagonal of L
 * is not stored.
 *
 * L can be a simplicial symbolic or numeric (L->is_super must be FALSE).
 * A symbolic factor is converted immediately into a numeric factor containing
 * the identity matrix.
 *
 * For a full factorization, kstart = 0 and kend = nrow.  The existing nonzero
 * entries (numerical values in L->x and L->z for the zomplex case, and indices
 * in L->i), if any, are overwritten.
 *
 * To compute an incremental factorization, select kstart and kend as the range
 * of rows of L you wish to compute.  A correct factorization will be computed
 * only if all descendants of all nodes k = kstart to kend-1 in the etree have
 * been factorized by a prior call to this routine, and if rows kstart to kend-1
 * have not been factorized.  This condition is NOT checked on input.
 *
 * ---------------
 * Symmetric case:
 * ---------------
 *
 *	The factorization (in MATLAB notation) is:
 *
 *	S = beta*I + A
 *	S = triu (S) + triu (S,1)'
 *	L*D*L' = S, or L*L' = S
 *
 *	A is a conventional sparse matrix in compressed column form.  Only the
 *	diagonal and upper triangular part of A is accessed; the lower
 *	triangular part is ignored and assumed to be equal to the upper
 *	triangular part.  For an incremental factorization, only columns kstart
 *	to kend-1 of A are accessed.  F is not used.
 *
 * ---------------
 * Unsymmetric case:
 * ---------------
 *
 *	The factorization (in MATLAB notation) is:
 *
 *	S = beta*I + A*F
 *	S = triu (S) + triu (S,1)'
 *	L*D*L' = S, or L*L' = S
 *
 *	The typical case is F=A'.  Alternatively, if F=A(:,f)', then this
 *	routine factorizes S = beta*I + A(:,f)*A(:,f)'.
 *
 *	All of A and F are accessed, but only the upper triangular part of A*F
 *	is used.  F must be of size A->ncol by A->nrow.  F is used for the
 *	unsymmetric case only.  F can be packed or unpacked and it need not be
 *	sorted.
 *
 *	For a complete factorization of beta*I + A*A',
 *	this routine performs a number of flops exactly equal to:
 *
 *	sum (for each column j of A) of (Anz (j)^2 + Anz (j)), to form S
 *	+
 *	sum (for each column j of L) of (Lnz (j)^2 + 3*Lnz (j)), to factorize S
 *
 *	where Anz (j) is the number of nonzeros in column j of A, and Lnz (j)
 *	is the number of nonzero in column j of L below the diagonal.
 *
 *
 * workspace: Flag (nrow), W (nrow if real, 2*nrow if complex/zomplex),
 * Iwork (nrow)
 *
 * Supports any xtype, except a pattern-only input matrix A cannot be
 * factorized.
 */

#ifndef NCHOLESKY

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

/* ========================================================================== */
/* === subtree ============================================================== */
/* ========================================================================== */

/* Compute the nonzero pattern of the sparse triangular solve Lx=b, where L in
 * this case is L(0:k-1,0:k-1), and b is a column of A.  This is done by
 * traversing the kth row-subtree of the elimination tree of L, starting from
 * each nonzero entry in b.  The pattern is returned postordered, and is valid
 * for a subsequent numerical triangular solve of Lx=b.  The elimination tree
 * can be provided in a Parent array, or extracted from the pattern of L itself.
 *
 * The pattern of x = inv(L)*b is returned in Stack [top...].
 * Also scatters b, or a multiple of b, into the work vector W.
 *
 * The SCATTER macro is defines how the numerical values of A or A*A' are to be
 * scattered.
 *
 * PARENT(i) is a macro the defines how the etree is accessed.  It is either:
 *	#define PARENT(i) Parent [i]
 *	#define PARENT(i) (Lnz [i] > 1) ? (Li [Lp [i] + 1]) : EMPTY
 */

#define SUBTREE \
    for ( ; p < pend ; p++) \
    { \
	i = Ai [p] ; \
	if (i <= k) \
	{ \
	    /* scatter the column of A, or A*A' into Wx and Wz */ \
	    SCATTER ; \
	    /* start at node i and traverse up the subtree, stop at node k */ \
	    for (len = 0 ; i < k && i != EMPTY && Flag [i] < mark ; i = parent) \
	    { \
		/* L(k,i) is nonzero, and seen for the first time */ \
		Stack [len++] = i ;	    /* place i on the stack */ \
		Flag [i] = mark ;	    /* mark i as visited */ \
		parent = PARENT (i) ;   /* traverse up the etree to the parent */ \
	    } \
	    /* move the path down to the bottom of the stack */ \
	    while (len > 0) \
	    { \
		Stack [--top] = Stack [--len] ; \
	    } \
	} \
	else if (sorted) \
	{ \
	    break ; \
	} \
    }


/* ========================================================================== */
/* === TEMPLATE ============================================================= */
/* ========================================================================== */

#define REAL
#include "t_cholmod_rowfac.c"
#define COMPLEX
#include "t_cholmod_rowfac.c"
#define ZOMPLEX
#include "t_cholmod_rowfac.c"

#define MASK
#define REAL
#include "t_cholmod_rowfac.c"
#define COMPLEX
#include "t_cholmod_rowfac.c"
#define ZOMPLEX
#include "t_cholmod_rowfac.c"
#undef MASK


/* ========================================================================== */
/* === cholmod_row_subtree ================================================== */
/* ========================================================================== */

/* Compute the nonzero pattern of the solution to the lower triangular system
 * L(0:k-1,0:k-1) * x = A (0:k-1,k) if A is symmetric, or
 * L(0:k-1,0:k-1) * x = A (0:k-1,:) * A (:,k)' if A is unsymmetric.
 * This gives the nonzero pattern of row k of L (excluding the diagonal).
 * The pattern is returned postordered.
 *
 * The symmetric case requires A to be in symmetric-upper form.
 *
 * The result is returned in R, a pre-allocated sparse matrix of size nrow-by-1,
 * with R->nzmax >= nrow.  R is assumed to be packed (Rnz [0] is not updated);
 * the number of entries in R is given by Rp [0].
 *
 * FUTURE WORK:  a very minor change to this routine could allow it to compute
 * the nonzero pattern of x for any system Lx=b.  The SUBTREE macro would need
 * to change, to eliminate its dependence on k.
 *
 * workspace: Flag (nrow)
 */

int CHOLMOD(row_subtree)
(
    /* ---- input ---- */
    cholmod_sparse *A,	/* matrix to analyze */
    cholmod_sparse *F,	/* used for A*A' case only. F=A' or A(:,f)' */
    size_t krow,	/* row k of L */
    Int *Parent,	/* elimination tree */
    /* ---- output --- */
    cholmod_sparse *R,	/* pattern of L(k,:), 1-by-n with R->nzmax >= n */
    /* --------------- */
    cholmod_common *Common
)
{
    Int *Rp, *Stack, *Flag, *Ap, *Ai, *Anz, *Fp, *Fi, *Fnz ;
    Int p, pend, parent, t, stype, nrow, k, pf, pfend, Fpacked, packed,
	sorted, top, len, i, mark ;

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

    RETURN_IF_NULL_COMMON (FALSE) ;
    RETURN_IF_NULL (A, FALSE) ;
    RETURN_IF_NULL (R, FALSE) ;
    RETURN_IF_NULL (Parent, FALSE) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    RETURN_IF_XTYPE_INVALID (R, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    stype = A->stype ;
    if (stype == 0)
    {
	RETURN_IF_NULL (F, FALSE) ;
	RETURN_IF_XTYPE_INVALID (F, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    }
    if (krow >= A->nrow)
    {
	ERROR (CHOLMOD_INVALID, "subtree: k invalid") ;
	return (FALSE) ;
    }
    if (R->ncol != 1 || A->nrow != R->nrow || A->nrow > R->nzmax)
    {
	ERROR (CHOLMOD_INVALID, "subtree: R invalid") ;
	return (FALSE) ;
    }
    Common->status = CHOLMOD_OK ;

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

    nrow = A->nrow ;
    CHOLMOD(allocate_work) (nrow, 0, 0, Common) ;
    if (Common->status < CHOLMOD_OK)
    {
	return (FALSE) ;
    }
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ;

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

    if (stype > 0)
    {
	/* symmetric upper case: F is not needed.  It may be NULL */
	Fp = NULL ;
	Fi = NULL ;
	Fnz = NULL ;
	Fpacked = TRUE ;
    }
    else if (stype == 0)
    {
	/* unsymmetric case: F is required. */
	Fp = F->p ;
	Fi = F->i ;
	Fnz = F->nz ;
	Fpacked = F->packed ;
    }
    else
    {
	/* symmetric lower triangular form not supported */
	ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ;
	return (FALSE) ;
    }

    Ap = A->p ;
    Ai = A->i ;
    Anz = A->nz ;
    packed = A->packed ;
    sorted = A->sorted ;

    k = krow ;
    Stack = R->i ;

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

    Flag = Common->Flag ;	/* size nrow, Flag [i] < mark must hold */
    /* mark = CHOLMOD(clear_flag) (Common) ; */
    CHOLMOD_CLEAR_FLAG (Common) ;
    mark = Common->mark ;

    /* ---------------------------------------------------------------------- */
    /* compute the pattern of L(k,:) */
    /* ---------------------------------------------------------------------- */

    top = nrow ;		/* Stack is empty */
    Flag [k] = mark ;		/* do not include diagonal entry in Stack */

#define SCATTER			/* do not scatter numerical values */
#define PARENT(i) Parent [i]	/* use Parent for etree */

    if (stype != 0)
    {
	/* scatter kth col of triu (A), get pattern L(k,:) */
	p = Ap [k] ;
	pend = (packed) ? (Ap [k+1]) : (p + Anz [k]) ;
	SUBTREE ;
    }
    else
    {
	/* scatter kth col of triu (beta*I+AA'), get pattern L(k,:) */
	pf = Fp [k] ;
	pfend = (Fpacked) ? (Fp [k+1]) : (pf + Fnz [k]) ;
	for ( ; pf < pfend ; pf++)
	{
	    /* get nonzero entry F (t,k) */
	    t = Fi [pf] ;
	    p = Ap [t] ;
	    pend = (packed) ? (Ap [t+1]) : (p + Anz [t]) ;
	    SUBTREE ;
	}
    }

#undef SCATTER
#undef PARENT

    /* shift the stack upwards, to the first part of R */
    len = nrow - top ;
    for (i = 0 ; i < len ; i++)
    {
	Stack [i] = Stack [top + i] ;
    }

    Rp = R->p ;
    Rp [0] = 0 ;
    Rp [1] = len ;
    R->sorted = FALSE ;

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


/* ========================================================================== */
/* === cholmod_row_lsubtree ================================================= */
/* ========================================================================== */

/* Identical to cholmod_row_subtree, except that the elimination tree is
 * obtained from L itself, as the first off-diagonal entry in each column.
 * L must be simplicial, not supernodal */

int CHOLMOD(row_lsubtree)
(
    /* ---- input ---- */
    cholmod_sparse *A,	/* matrix to analyze */
    Int *Fi, size_t fnz,    /* nonzero pattern of kth row of A', not required
			     * for the symmetric case.  Need not be sorted. */
    size_t krow,	/* row k of L */
    cholmod_factor *L,	/* the factor L from which parent(i) is derived */
    /* ---- output --- */
    cholmod_sparse *R,	/* pattern of L(k,:), 1-by-n with R->nzmax >= n */
    /* --------------- */
    cholmod_common *Common
)
{
    Int *Rp, *Stack, *Flag, *Ap, *Ai, *Anz, *Lp, *Li, *Lnz ;
    Int p, pend, parent, t, stype, nrow, k, pf, packed, sorted, top, len, i,
	mark ;

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

    RETURN_IF_NULL_COMMON (FALSE) ;
    RETURN_IF_NULL (A, FALSE) ;
    RETURN_IF_NULL (R, FALSE) ;
    RETURN_IF_NULL (L, FALSE) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    RETURN_IF_XTYPE_INVALID (R, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ;
    stype = A->stype ;
    if (stype == 0)
    {
	RETURN_IF_NULL (Fi, FALSE) ;
    }
    if (krow >= A->nrow)
    {
	ERROR (CHOLMOD_INVALID, "lsubtree: k invalid") ;
	return (FALSE) ;
    }
    if (R->ncol != 1 || A->nrow != R->nrow || A->nrow > R->nzmax)
    {
	ERROR (CHOLMOD_INVALID, "lsubtree: R invalid") ;
	return (FALSE) ;
    }
    if (L->is_super)
    {
	ERROR (CHOLMOD_INVALID, "lsubtree: L invalid (cannot be supernodal)") ;
	return (FALSE) ;
    }
    Common->status = CHOLMOD_OK ;

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

    nrow = A->nrow ;
    CHOLMOD(allocate_work) (nrow, 0, 0, Common) ;
    if (Common->status < CHOLMOD_OK)
    {
	return (FALSE) ;
    }
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ;

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

    if (stype < 0)
    {
	/* symmetric lower triangular form not supported */
	ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ;
	return (FALSE) ;
    }

    Ap = A->p ;
    Ai = A->i ;
    Anz = A->nz ;
    packed = A->packed ;
    sorted = A->sorted ;

    k = krow ;
    Stack = R->i ;

    Lp = L->p ;
    Li = L->i ;
    Lnz = L->nz ;

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

    Flag = Common->Flag ;	/* size nrow, Flag [i] < mark must hold */
    mark = CHOLMOD(clear_flag) (Common) ;

    /* ---------------------------------------------------------------------- */
    /* compute the pattern of L(k,:) */
    /* ---------------------------------------------------------------------- */

    top = nrow ;		/* Stack is empty */
    Flag [k] = mark ;		/* do not include diagonal entry in Stack */

#define SCATTER			/* do not scatter numerical values */
#define PARENT(i) (Lnz [i] > 1) ? (Li [Lp [i] + 1]) : EMPTY

    if (stype != 0)
    {
	/* scatter kth col of triu (A), get pattern L(k,:) */
	p = Ap [k] ;
	pend = (packed) ? (Ap [k+1]) : (p + Anz [k]) ;
	SUBTREE ;
    }
    else
    {
	/* scatter kth col of triu (beta*I+AA'), get pattern L(k,:) */
	for (pf = 0 ; pf < (Int) fnz ; pf++)
	{
	    /* get nonzero entry F (t,k) */
	    t = Fi [pf] ;
	    p = Ap [t] ;
	    pend = (packed) ? (Ap [t+1]) : (p + Anz [t]) ;
	    SUBTREE ;
	}
    }

#undef SCATTER
#undef PARENT

    /* shift the stack upwards, to the first part of R */
    len = nrow - top ;
    for (i = 0 ; i < len ; i++)
    {
	Stack [i] = Stack [top + i] ;
    }

    Rp = R->p ;
    Rp [0] = 0 ;
    Rp [1] = len ;
    R->sorted = FALSE ;

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


/* ========================================================================== */
/* === cholmod_rowfac ======================================================= */
/* ========================================================================== */

/* This is the incremental factorization for general purpose usage. */

int CHOLMOD(rowfac)
(
    /* ---- input ---- */
    cholmod_sparse *A,	/* matrix to factorize */
    cholmod_sparse *F,	/* used for A*A' case only. F=A' or A(:,f)' */
    double beta [2],	/* factorize beta*I+A or beta*I+AA' */
    size_t kstart,	/* first row to factorize */
    size_t kend,	/* last row to factorize is kend-1 */
    /* ---- in/out --- */
    cholmod_factor *L,
    /* --------------- */
    cholmod_common *Common
)
{
    return (CHOLMOD(rowfac_mask) (A, F, beta, kstart, kend, NULL, NULL, L,
	Common)) ;
}


/* ========================================================================== */
/* === cholmod_rowfac_mask ================================================== */
/* ========================================================================== */

/* This is meant for use in LPDASA only. */

int CHOLMOD(rowfac_mask)
(
    /* ---- input ---- */
    cholmod_sparse *A,	/* matrix to factorize */
    cholmod_sparse *F,	/* used for A*A' case only. F=A' or A(:,f)' */
    double beta [2],	/* factorize beta*I+A or beta*I+AA' */
    size_t kstart,	/* first row to factorize */
    size_t kend,	/* last row to factorize is kend-1 */
    Int *mask,		/* size A->nrow. if mask[i] >= 0 row i is set to zero */
    Int *RLinkUp,	/* size A->nrow. link list of rows to compute */
    /* ---- in/out --- */
    cholmod_factor *L,
    /* --------------- */
    cholmod_common *Common
)
{
    Int n ;
    size_t s ;
    int ok = TRUE ;

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

    RETURN_IF_NULL_COMMON (FALSE) ;
    RETURN_IF_NULL (A, FALSE) ;
    RETURN_IF_NULL (L, FALSE) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ;
    RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    if (L->xtype != CHOLMOD_PATTERN && A->xtype != L->xtype)
    {
	ERROR (CHOLMOD_INVALID, "xtype of A and L do not match") ;
	return (FALSE) ;
    }
    if (L->is_super)
    {
	ERROR (CHOLMOD_INVALID, "can only do simplicial factorization");
	return (FALSE) ;
    }
    if (A->stype == 0)
    {
	RETURN_IF_NULL (F, FALSE) ;
	if (A->xtype != F->xtype)
	{
	    ERROR (CHOLMOD_INVALID, "xtype of A and F do not match") ;
	    return (FALSE) ;
	}
    }
    if (A->stype < 0)
    {
	/* symmetric lower triangular form not supported */
	ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ;
	return (FALSE) ;
    }
    if (kend > L->n)
    {
	ERROR (CHOLMOD_INVALID, "kend invalid") ;
	return (FALSE) ;
    }
    if (A->nrow != L->n)
    {
	ERROR (CHOLMOD_INVALID, "dimensions of A and L do not match") ;
	return (FALSE) ;
    }
    Common->status = CHOLMOD_OK ;
    Common->rowfacfl = 0 ;

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

    /* Xwork is of size n for the real case, 2*n for complex/zomplex */
    n = L->n  ;

    /* s = ((A->xtype != CHOLMOD_REAL) ? 2:1)*n */
    s = CHOLMOD(mult_size_t) (n, ((A->xtype != CHOLMOD_REAL) ? 2:1), &ok) ;
    if (!ok)
    {
	ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
	return (FALSE) ;
    }

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

    /* ---------------------------------------------------------------------- */
    /* factorize the matrix, using template routine */
    /* ---------------------------------------------------------------------- */

    if (RLinkUp == NULL)
    {

	switch (A->xtype)
	{
	    case CHOLMOD_REAL:
		ok = r_cholmod_rowfac (A, F, beta, kstart, kend, L, Common) ;
		break ;

	    case CHOLMOD_COMPLEX:
		ok = c_cholmod_rowfac (A, F, beta, kstart, kend, L, Common) ;
		break ;

	    case CHOLMOD_ZOMPLEX:
		ok = z_cholmod_rowfac (A, F, beta, kstart, kend, L, Common) ;
		break ;
	}

    }
    else
    {

	switch (A->xtype)
	{
	    case CHOLMOD_REAL:
		ok = r_cholmod_rowfac_mask (A, F, beta, kstart, kend,
		    mask, RLinkUp, L, Common) ;
		break ;

	    case CHOLMOD_COMPLEX:
		ok = c_cholmod_rowfac_mask (A, F, beta, kstart, kend,
		    mask, RLinkUp, L, Common) ;
		break ;

	    case CHOLMOD_ZOMPLEX:
		ok = z_cholmod_rowfac_mask (A, F, beta, kstart, kend,
		    mask, RLinkUp, L, Common) ;
		break ;
	}
    }

    return (ok) ;
}
#endif
