//------------------------------------------------------------------------------
// CHOLMOD/Cholesky/cholmod_rowfac: row-wise numerical LDL' or LL' factorization
//------------------------------------------------------------------------------

// CHOLMOD/Cholesky Module.  Copyright (C) 2005-2023, Timothy A. Davis
// All Rights Reserved.
// SPDX-License-Identifier: LGPL-2.1+

//------------------------------------------------------------------------------

// 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 and dtype, except a pattern-only input matrix A cannot be
// factorized.

#include "cholmod_internal.h"

#ifndef NCHOLESKY

//------------------------------------------------------------------------------
// 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 parent */   \
        }                                                                   \
        /* move the path down to the bottom of the stack */                 \
        while (len > 0)                                                     \
        {                                                                   \
            Stack [--top] = Stack [--len] ;                                 \
        }                                                                   \
    }                                                                       \
    else if (sorted)                                                        \
    {                                                                       \
        break ;                                                             \
    }                                                                       \
}

//------------------------------------------------------------------------------
// t_cholmod_rowfac template
//------------------------------------------------------------------------------

#define DOUBLE

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

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

#undef  DOUBLE
#define SINGLE

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

#define MASK
#define REAL
#include "t_cholmod_rowfac_worker.c"
#define COMPLEX
#include "t_cholmod_rowfac_worker.c"
#define ZOMPLEX
#include "t_cholmod_rowfac_worker.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
)
{

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

    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 ;

    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, 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
    CLEAR_FLAG (Common) ;
    ASSERT (check_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, 0, Common)) ;
    return (TRUE) ;
}


//------------------------------------------------------------------------------
// cholmod_lsolve_pattern
//------------------------------------------------------------------------------

// Compute the nonzero pattern of Y=L\B.  L must be simplicial, and B
// must be a single sparse column vector with B->stype = 0.  The values of
// B are not used; it just specifies a nonzero pattern.  The pattern of
// Y is not sorted, but is in topological order instead (suitable for a
// sparse forward/backsolve).

int CHOLMOD(lsolve_pattern)
(
    // input:
    cholmod_sparse *B,  // sparse right-hand-side (a single sparse column)
    cholmod_factor *L,  // the factor L from which parent(i) is derived
    // output:
    cholmod_sparse *Yset,   // pattern of Y=L\B, n-by-1 with Y->nzmax >= n
    cholmod_common *Common
)
{
    size_t krow ;
    RETURN_IF_NULL (B, FALSE) ;
    krow = B->nrow ;
    return (CHOLMOD(row_lsubtree) (B, NULL, 0, krow, L, Yset, Common)) ;
}


//------------------------------------------------------------------------------
// 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.
//
// If krow = A->nrow, then A must be a single sparse column vector, (A->stype
// must be zero), and the nonzero pattern of x=L\b is computed, where b=A(:,0)
// is the single sparse right-hand-side.  The inputs Fi and fnz are ignored.
// See CHOLMOD(lsolve_pattern) above for a simpler interface for this case.

int CHOLMOD(row_lsubtree)
(
    // input:
    cholmod_sparse *A,  // matrix to analyze
    Int *Fi,            // nonzero pattern of kth row of A', not required
                        // for the symmetric case.  Need not be sorted.
    size_t fnz,         // size of Fi
    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,:), n-by-1 with R->nzmax >= n
    cholmod_common *Common
)
{

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

    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, ka ;

    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) ;

    nrow = A->nrow ;
    stype = A->stype ;
    if (stype < 0)
    {
        // symmetric lower triangular form not supported
        ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ;
        return (FALSE) ;
    }

    if (krow > nrow)
    {
        ERROR (CHOLMOD_INVALID, "lsubtree: krow invalid") ;
        return (FALSE) ;
    }
    else if (krow == nrow)
    {
        // find pattern of x=L\b where b=A(:,0)
        k = nrow ;      // compute all of the result; don't stop in SUBTREE
        ka = 0 ;        // use column A(:,0)
        if (stype != 0 || A->ncol != 1)
        {
            // A must be unsymmetric (it's a single sparse column vector)
            ERROR (CHOLMOD_INVALID, "lsubtree: A invalid") ;
            return (FALSE) ;
        }
    }
    else
    {
        // find pattern of L(k,:) using A(:,k) and Fi if A unsymmetric
        k = krow ;      // which row of L to compute
        ka = k ;        // which column of A to use
        if (stype == 0)
        {
            RETURN_IF_NULL (Fi, FALSE) ;
        }
    }

    if (R->ncol != 1 || nrow != R->nrow || nrow > R->nzmax ||
        ((krow == nrow || stype != 0) && ka >= A->ncol))
    {
        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
    //--------------------------------------------------------------------------

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

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

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

    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
    if (k < nrow)
    {
        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 (krow == nrow || stype != 0)
    {
        // scatter kth col of triu (A), get pattern L(k,:)
        p = Ap [ka] ;
        pend = (packed) ? (Ap [ka+1]) : (p + Anz [ka]) ;
        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, 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
    // input/output:
    cholmod_factor *L,
    cholmod_common *Common
)
{
    return (CHOLMOD(rowfac_mask2) (A, F, beta, kstart, kend, NULL, 0, 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
    // input/output:
    cholmod_factor *L,
    cholmod_common *Common
)
{
    Int maskmark = 0 ;
    return (CHOLMOD(rowfac_mask2) (A, F, beta, kstart, kend, mask, maskmark,
        RLinkUp, L, Common)) ;
}


//------------------------------------------------------------------------------
// cholmod_rowfac_mask2
//------------------------------------------------------------------------------

// This is meant for use in LPDASA only.  For general usage, see
// cholmod_rowfac.

int CHOLMOD(rowfac_mask2)
(
    // 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] >= maskmark row i is set
                        // to zero
    Int maskmark,       // for mask [i] test
    Int *RLinkUp,       // size A->nrow. link list of rows to compute
    // input/output:
    cholmod_factor *L,
    cholmod_common *Common
)
{

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

    Int n ;
    size_t s ;

    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 || A->dtype != L->dtype))
    {
        ERROR (CHOLMOD_INVALID, "xtype or dtype 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 = n * ((A->xtype != CHOLMOD_REAL) ? 2:1)
    int ok = TRUE ;
    s = CHOLMOD(mult_size_t) (L->n, ((A->xtype != CHOLMOD_REAL) ? 2:1), &ok) ;
    if (!ok)
    {
        ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
        return (FALSE) ;
    }

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

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

    float s_beta [2] ;
    s_beta [0] = (float) beta [0] ;
    s_beta [1] = (float) beta [1] ;

    if (RLinkUp == NULL)
    {

        switch ((A->xtype + A->dtype) % 8)
        {
            case CHOLMOD_REAL    + CHOLMOD_SINGLE:
                ok = rs_cholmod_rowfac_worker (A, F, s_beta, kstart, kend, L,
                    Common) ;
                break ;

            case CHOLMOD_COMPLEX + CHOLMOD_SINGLE:
                ok = cs_cholmod_rowfac_worker (A, F, s_beta, kstart, kend, L,
                    Common) ;
                break ;

            case CHOLMOD_ZOMPLEX + CHOLMOD_SINGLE:
                ok = zs_cholmod_rowfac_worker (A, F, s_beta, kstart, kend, L,
                    Common) ;
                break ;

            case CHOLMOD_REAL    + CHOLMOD_DOUBLE:
                ok = rd_cholmod_rowfac_worker (A, F, beta, kstart, kend, L,
                    Common) ;
                break ;

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

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

    }
    else
    {

        switch ((A->xtype + A->dtype) % 8)
        {
            case CHOLMOD_REAL    + CHOLMOD_SINGLE:
                ok = rs_cholmod_rowfac_mask_worker (A, F, s_beta, kstart, kend,
                    mask, maskmark, RLinkUp, L, Common) ;
                break ;

            case CHOLMOD_COMPLEX + CHOLMOD_SINGLE:
                ok = cs_cholmod_rowfac_mask_worker (A, F, s_beta, kstart, kend,
                    mask, maskmark, RLinkUp, L, Common) ;
                break ;

            case CHOLMOD_ZOMPLEX + CHOLMOD_SINGLE:
                ok = zs_cholmod_rowfac_mask_worker (A, F, s_beta, kstart, kend,
                    mask, maskmark, RLinkUp, L, Common) ;
                break ;

            case CHOLMOD_REAL    + CHOLMOD_DOUBLE:
                ok = rd_cholmod_rowfac_mask_worker (A, F, beta, kstart, kend,
                    mask, maskmark, RLinkUp, L, Common) ;
                break ;

            case CHOLMOD_COMPLEX + CHOLMOD_DOUBLE:
                ok = cd_cholmod_rowfac_mask_worker (A, F, beta, kstart, kend,
                    mask, maskmark, RLinkUp, L, Common) ;
                break ;

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

    return (ok) ;
}
#endif

