//------------------------------------------------------------------------------
// CHOLMOD/Cholesky/cholmod_factorize: numerical Cholesky factorization
//------------------------------------------------------------------------------

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

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

// Computes the numerical factorization of a symmetric matrix.  The primary
// inputs to this routine are a sparse matrix A and the symbolic factor L from
// cholmod_analyze or a prior numerical factor L.  If A is symmetric, this
// routine factorizes A(p,p)+beta*I (beta can be zero), where p is the
// fill-reducing permutation (L->Perm).  If A is unsymmetric, either
// A(p,:)*A(p,:)'+beta*I or A(p,f)*A(p,f)'+beta*I is factorized.  The set f and
// the nonzero pattern of the matrix A must be the same as the matrix passed to
// cholmod_analyze for the supernodal case.  For the simplicial case, it can
// be different, but it should be the same for best performance.  beta is real.
//
// A simplicial factorization or supernodal factorization is chosen, based on
// the type of the factor L.  If L->is_super is TRUE, a supernodal LL'
// factorization is computed.  Otherwise, a simplicial numeric factorization
// is computed, either LL' or LDL', depending on Common->final_ll.
//
// Once the factorization is complete, it can be left as is or optionally
// converted into any simplicial numeric type, depending on the
// Common->final_* parameters.  If converted from a supernodal to simplicial
// type, and the Common->final_resymbol parameter is true, then numerically
// zero entries in L due to relaxed supernodal amalgamation are removed from
// the simplicial factor (they are always left in the supernodal form of L).
// Entries that are numerically zero but present in the simplicial symbolic
// pattern of L are left in place (that is, the graph of L remains chordal).
// This is required for the update/downdate/rowadd/rowdel routines to work
// properly.
//
// workspace: Flag (nrow), Head (nrow+1),
//      if symmetric:   Iwork (2*nrow+2*nsuper)
//      if unsymmetric: Iwork (2*nrow+MAX(2*nsuper,ncol))
//          where nsuper is 0 if simplicial, or the # of relaxed supernodes in
//          L otherwise (nsuper <= nrow).
//      if simplicial: W (nrow).
//      Allocates up to two temporary copies of its input matrix (including
//      both pattern and numerical values).
//
// If the matrix is not positive definite the routine returns TRUE, but
// sets Common->status to CHOLMOD_NOT_POSDEF and L->minor is set to the
// column at which the failure occurred.  Columns L->minor to L->n-1 are
// set to zero.
//
// Supports any xtype (pattern, real, complex, or zomplex), except that the
// input matrix A cannot be pattern-only.  If L is simplicial, its numeric
// xtype matches A on output.  If L is supernodal, its xtype is real if A is
// real, or complex if A is complex or zomplex.
//
// The factorization L is computed with the same dtype as A (L->dtype will
// be the same as A->dtype).

#include "cholmod_internal.h"

#ifndef NCHOLESKY

//------------------------------------------------------------------------------
// cholmod_factorize
//------------------------------------------------------------------------------

// Factorizes PAP' (or PAA'P' if A->stype is 0), using a factor obtained
// from cholmod_analyze.  The analysis can be re-used simply by calling this
// routine a second time with another matrix.  A must have the same nonzero
// pattern as that passed to cholmod_analyze.

int CHOLMOD(factorize)
(
    // input:
    cholmod_sparse *A,  // matrix to factorize
    // input/output:
    cholmod_factor *L,  // resulting factorization
    cholmod_common *Common
)
{
    double zero [2] ;
    zero [0] = 0 ;
    zero [1] = 0 ;
    return (CHOLMOD(factorize_p) (A, zero, NULL, 0, L, Common)) ;
}

//------------------------------------------------------------------------------
// cholmod_factorize_p
//------------------------------------------------------------------------------

// Same as cholmod_factorize, but with more options.

int CHOLMOD(factorize_p)
(
    // input:
    cholmod_sparse *A,  // matrix to factorize
    double beta [2],    // factorize beta*I+A or beta*I+A'*A
    Int *fset,          // subset of 0:(A->ncol)-1
    size_t fsize,       // size of fset
    // input/output:
    cholmod_factor *L,  // resulting factorization
    cholmod_common *Common
)
{

    cholmod_sparse *S, *F, *A1, *A2 ;
    Int nrow, ncol, stype, convert, grow2, status ;
    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) ;
    nrow = A->nrow ;
    ncol = A->ncol ;
    stype = A->stype ;
    if (L->n != A->nrow)
    {
        ERROR (CHOLMOD_INVALID, "A and L dimensions do not match") ;
        return (FALSE) ;
    }
    if (stype != 0 && nrow != ncol)
    {
        ERROR (CHOLMOD_INVALID, "matrix invalid") ;
        return (FALSE) ;
    }
    DEBUG (CHOLMOD(dump_sparse) (A, "A for cholmod_factorize", Common)) ;
    Common->status = CHOLMOD_OK ;

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

    size_t nsuper = (L->is_super ? L->nsuper : 0) ;
    size_t uncol = ((stype != 0) ? 0 : A->ncol) ;

    // s = 2*nrow + MAX (uncol, 2*nsuper)
    size_t s = CHOLMOD(mult_size_t) (nsuper, 2, &ok) ;
    s = MAX (uncol, s) ;
    size_t t = CHOLMOD(mult_size_t) (A->nrow, 2, &ok) ;
    s = CHOLMOD(add_size_t) (s, t, &ok) ;
    if (!ok)
    {
        ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
        return (FALSE) ;
    }

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

    S  = NULL ;
    F  = NULL ;
    A1 = NULL ;
    A2 = NULL ;

    // convert to another form when done, if requested
    convert = !(Common->final_asis) ;

    //--------------------------------------------------------------------------
    // perform supernodal LL' or simplicial LDL' factorization
    //--------------------------------------------------------------------------

    if (L->is_super)
    {

        #ifndef NSUPERNODAL

        //----------------------------------------------------------------------
        // supernodal factorization
        //----------------------------------------------------------------------

        if (L->ordering == CHOLMOD_NATURAL)
        {

            //------------------------------------------------------------------
            // natural ordering
            //------------------------------------------------------------------

            if (stype > 0)
            {
                // S = tril (A'), F not needed
                // workspace: Iwork (nrow)
                A1 = CHOLMOD(ptranspose) (A, 2, NULL, NULL, 0, Common) ;
                S = A1 ;
            }
            else if (stype < 0)
            {
                // This is the fastest option for the natural ordering
                // S = A; F not needed
                S = A ;
            }
            else
            {
                // F = A(:,f)'
                // workspace: Iwork (nrow)
                // workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)
                A1 = CHOLMOD(ptranspose) (A, 2, NULL, fset, fsize, Common) ;
                F = A1 ;
                // S = A
                S = A ;
            }

        }
        else
        {

            //------------------------------------------------------------------
            // permute the input matrix before factorization
            //------------------------------------------------------------------

            if (stype > 0)
            {
                // This is the fastest option for factoring a permuted matrix
                // S = tril (PAP'); F not needed
                // workspace: Iwork (2*nrow)
                A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ;
                S = A1 ;
            }
            else if (stype < 0)
            {
                // A2 = triu (PAP')
                // workspace: Iwork (2*nrow)
                A2 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ;
                // S = tril (A2'); F not needed
                // workspace: Iwork (nrow)
                A1 = CHOLMOD(ptranspose) (A2, 2, NULL, NULL, 0, Common) ;
                S = A1 ;
                CHOLMOD(free_sparse) (&A2, Common) ;
                ASSERT (A2 == NULL) ;
            }
            else
            {
                // F = A(p,f)'
                // workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)
                A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, fset, fsize, Common) ;
                F = A1 ;
                // S = F'
                // workspace: Iwork (nrow)
                A2 = CHOLMOD(ptranspose) (F, 2, NULL, NULL, 0, Common) ;
                S = A2 ;
            }
        }

        //----------------------------------------------------------------------
        // supernodal factorization
        //----------------------------------------------------------------------

        // workspace: Flag (nrow), Head (nrow+1), Iwork (2*nrow+2*nsuper)
        if (Common->status == CHOLMOD_OK)
        {
            CHOLMOD(super_numeric) (S, F, beta, L, Common) ;
        }
        status = Common->status ;
        ASSERT (IMPLIES (status >= CHOLMOD_OK, L->xtype != CHOLMOD_PATTERN)) ;

        //----------------------------------------------------------------------
        // convert to final form, if requested
        //----------------------------------------------------------------------

        if (Common->status >= CHOLMOD_OK && convert)
        {
            // workspace: none
            ok = CHOLMOD(change_factor) (L->xtype, Common->final_ll,
                    Common->final_super, Common->final_pack,
                    Common->final_monotonic, L, Common) ;
            if (ok && Common->final_resymbol && !(L->is_super))
            {
                // workspace: Flag (nrow), Head (nrow+1),
                //      if symmetric:   Iwork (2*nrow)
                //      if unsymmetric: Iwork (2*nrow+ncol)
                CHOLMOD(resymbol_noperm) (S, fset, fsize, Common->final_pack,
                    L, Common) ;
            }
        }

        #else

        //----------------------------------------------------------------------
        // CHOLMOD Supernodal module not installed
        //----------------------------------------------------------------------

        status = CHOLMOD_NOT_INSTALLED ;
        ERROR (CHOLMOD_NOT_INSTALLED,"Supernodal module not installed") ;

        #endif

    }
    else
    {

        //----------------------------------------------------------------------
        // simplicial LDL' factorization
        //----------------------------------------------------------------------

        // Permute the input matrix A if necessary.  cholmod_rowfac requires
        // triu(A) in column form for the symmetric case, and A in column form
        // for the unsymmetric case (the matrix S).  The unsymmetric case
        // requires A in row form, or equivalently A' in column form (the
        // matrix F).

        if (L->ordering == CHOLMOD_NATURAL)
        {

            //------------------------------------------------------------------
            // natural ordering
            //------------------------------------------------------------------

            if (stype > 0)
            {
                // F is not needed, S = A
                S = A ;
            }
            else if (stype < 0)
            {
                // F is not needed, S = A'
                // workspace: Iwork (nrow)
                A2 = CHOLMOD(ptranspose) (A, 2, NULL, NULL, 0, Common) ;
                S = A2 ;
            }
            else
            {
                // F = A (:,f)'
                // workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)
                A1 = CHOLMOD(ptranspose) (A, 2, NULL, fset, fsize, Common) ;
                F = A1 ;
                S = A ;
            }

        }
        else
        {

            //------------------------------------------------------------------
            // permute the input matrix before factorization
            //------------------------------------------------------------------

            if (stype > 0)
            {
                // F = tril (A (p,p)')
                // workspace: Iwork (2*nrow)
                A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ;
                // A2 = triu (F')
                // workspace: Iwork (nrow)
                A2 = CHOLMOD(ptranspose) (A1, 2, NULL, NULL, 0, Common) ;
                // the symmetric case does not need F, free it and set to NULL
                CHOLMOD(free_sparse) (&A1, Common) ;
            }
            else if (stype < 0)
            {
                // A2 = triu (A (p,p)'), F not needed.  This is the fastest
                // way to factorize a matrix using the simplicial routine
                // (cholmod_rowfac).
                // workspace: Iwork (2*nrow)
                A2 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ;
            }
            else
            {
                // F = A (p,f)'
                // workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)
                A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, fset, fsize, Common) ;
                F = A1 ;
                // A2 = F'
                // workspace: Iwork (nrow)
                A2 = CHOLMOD(ptranspose) (F, 2, NULL, NULL, 0, Common) ;
            }
            S = A2 ;
        }

        //----------------------------------------------------------------------
        // simplicial LDL' or LL' factorization
        //----------------------------------------------------------------------

        // factorize beta*I+S (symmetric) or beta*I+F*F' (unsymmetric)
        // workspace: Flag (nrow), W (nrow), Iwork (2*nrow)
        if (Common->status == CHOLMOD_OK)
        {
            grow2 = Common->grow2 ;
            L->is_ll = (Common->final_ll) ? 1 : 0 ;
            if (L->xtype == CHOLMOD_PATTERN && Common->final_pack)
            {
                // allocate a factor with exactly the space required
                Common->grow2 = 0 ;
            }
            CHOLMOD(rowfac) (S, F, beta, 0, nrow, L, Common) ;
            Common->grow2 = grow2 ;
        }
        status = Common->status ;

        //----------------------------------------------------------------------
        // convert to final form, if requested
        //----------------------------------------------------------------------

        if (Common->status >= CHOLMOD_OK && convert)
        {
            // workspace: none
            CHOLMOD(change_factor) (L->xtype, L->is_ll, FALSE,
                    Common->final_pack, Common->final_monotonic, L, Common) ;
        }
    }

    //--------------------------------------------------------------------------
    // free A1 and A2 if they exist
    //--------------------------------------------------------------------------

    CHOLMOD(free_sparse) (&A1, Common) ;
    CHOLMOD(free_sparse) (&A2, Common) ;
    Common->status = MAX (Common->status, status) ;
    return (Common->status >= CHOLMOD_OK) ;
}
#endif

