//------------------------------------------------------------------------------
// CHOLMOD/Cholesky/cholmod_resymbol: recompute symbolic pattern of L
//------------------------------------------------------------------------------

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

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

// Recompute the symbolic pattern of L.  Entries not in the symbolic pattern
// are dropped.  L->Perm can be used (or not) to permute the input matrix A.
//
// These routines are used after a supernodal factorization is converted into
// a simplicial one, to remove zero entries that were added due to relaxed
// supernode amalgamation.  They can also be used after a series of downdates
// to remove entries that would no longer be present if the matrix were
// factorized from scratch.  A downdate (cholmod_updown) does not remove any
// entries from L.
//
// workspace: Flag (nrow), Head (nrow+1),
//      if symmetric:   Iwork (2*nrow)
//      if unsymmetric: Iwork (2*nrow+ncol).
//      Allocates up to 2 copies of its input matrix A (pattern only).

#include "cholmod_internal.h"

#ifndef NCHOLESKY

//------------------------------------------------------------------------------
// t_cholmod_resymbol_worker
//------------------------------------------------------------------------------

#define DOUBLE
#define REAL
#include "t_cholmod_resymbol_worker.c"
#define COMPLEX
#include "t_cholmod_resymbol_worker.c"
#define ZOMPLEX
#include "t_cholmod_resymbol_worker.c"

#undef  DOUBLE
#define SINGLE
#define REAL
#include "t_cholmod_resymbol_worker.c"
#define COMPLEX
#include "t_cholmod_resymbol_worker.c"
#define ZOMPLEX
#include "t_cholmod_resymbol_worker.c"

//------------------------------------------------------------------------------
// cholmod_resymbol
//------------------------------------------------------------------------------

// Remove entries from L that are not in the factorization of P*A*P', P*A*A'*P',
// or P*F*F'*P' (depending on A->stype and whether fset is NULL or not).
//
// cholmod_resymbol is the same as cholmod_resymbol_noperm, except that it
// first permutes A according to L->Perm.  A can be upper/lower/unsymmetric,
// in contrast to cholmod_resymbol_noperm (which can be lower or unsym).

int CHOLMOD(resymbol)   // recompute symbolic pattern of L
(
    // input:
    cholmod_sparse *A,  // matrix to analyze
    Int *fset,          // subset of 0:(A->ncol)-1
    size_t fsize,       // size of fset
    int pack,           // if TRUE, pack the columns of L
    // input/output:
    cholmod_factor *L,  // factorization, entries pruned on output
    cholmod_common *Common
)
{

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

    cholmod_sparse *H = NULL, *F = NULL, *G = NULL ;

    RETURN_IF_NULL_COMMON (FALSE) ;
    RETURN_IF_NULL (A, FALSE) ;
    RETURN_IF_NULL (L, FALSE) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ;
    Common->status = CHOLMOD_OK ;
    if (L->is_super)
    {
        // cannot operate on a supernodal factorization
        ERROR (CHOLMOD_INVALID, "cannot operate on supernodal L") ;
        return (FALSE) ;
    }
    if (L->n != A->nrow)
    {
        // dimensions must agree
        ERROR (CHOLMOD_INVALID, "A and L dimensions do not match") ;
        return (FALSE) ;
    }

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

    Int stype = A->stype ;

    // s = 2*nrow + (stype ? 0 : ncol)
    int ok = TRUE ;
    size_t s = CHOLMOD(mult_size_t) (A->nrow, 2, &ok) ;
    s = CHOLMOD(add_size_t) (s, (stype ? 0 : A->ncol), &ok) ;
    if (!ok)
    {
        ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
        return (FALSE) ;
    }

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

    //--------------------------------------------------------------------------
    // permute the input matrix if necessary
    //--------------------------------------------------------------------------

    H = NULL ;
    G = NULL ;

    if (stype > 0)
    {
        if (L->ordering == CHOLMOD_NATURAL)
        {
            // F = triu(A)'
            // workspace: Iwork (nrow)
            G = CHOLMOD(ptranspose) (A, 0, NULL, NULL, 0, Common) ;
        }
        else
        {
            // F = triu(A(p,p))'
            // workspace: Iwork (2*nrow)
            G = CHOLMOD(ptranspose) (A, 0, L->Perm, NULL, 0, Common) ;
        }
        F = G ;
    }
    else if (stype < 0)
    {
        if (L->ordering == CHOLMOD_NATURAL)
        {
            F = A ;
        }
        else
        {
            // G = triu(A(p,p))'
            // workspace: Iwork (2*nrow)
            G = CHOLMOD(ptranspose) (A, 0, L->Perm, NULL, 0, Common) ;
            // H = G'
            // workspace: Iwork (nrow)
            H = CHOLMOD(ptranspose) (G, 0, NULL, NULL, 0, Common) ;
            F = H ;
        }
    }
    else
    {
        if (L->ordering == CHOLMOD_NATURAL)
        {
            F = A ;
        }
        else
        {
            // G = A(p,f)'
            // workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)
            G = CHOLMOD(ptranspose) (A, 0, L->Perm, fset, fsize, Common) ;
            // H = G'
            // workspace: Iwork (ncol)
            H = CHOLMOD(ptranspose) (G, 0, NULL, NULL, 0, Common) ;
            F = H ;
        }
    }

    // No need to check for failure here.  cholmod_resymbol_noperm will return
    // FALSE if F is NULL.

    //--------------------------------------------------------------------------
    // resymbol
    //--------------------------------------------------------------------------

    ok = CHOLMOD(resymbol_noperm) (F, fset, fsize, pack, L, Common) ;

    //--------------------------------------------------------------------------
    // free the temporary matrices, if they exist
    //--------------------------------------------------------------------------

    CHOLMOD(free_sparse) (&H, Common) ;
    CHOLMOD(free_sparse) (&G, Common) ;
    return (ok) ;
}

//------------------------------------------------------------------------------
// cholmod_resymbol_noperm
//------------------------------------------------------------------------------

// Redo symbolic LDL' or LL' factorization of I + F*F' or I+A, where F=A(:,f).
//
// L already exists, but is a superset of the true dynamic pattern (simple
// column downdates and row deletions haven't pruned anything).  Just redo the
// symbolic factorization and drop entries that are no longer there.  The
// diagonal is not modified.  The number of nonzeros in column j of L
// (L->nz[j]) can decrease.  The column pointers (L->p[j]) remain unchanged if
// pack is FALSE or if L is not monotonic.  Otherwise, the columns of L are
// packed in place.
//
// For the symmetric case, the columns of the lower triangular part of A
// are accessed by column.  NOTE that this the transpose of the general case.
//
// For the unsymmetric case, F=A(:,f) is accessed by column.
//
// A need not be sorted, and can be packed or unpacked.  If L->Perm is not
// identity, then A must already be permuted according to the permutation used
// to factorize L.  The advantage of using this routine is that it does not
// need to create permuted copies of A first.
//
// This routine can be called if L is only partially factored via cholmod_rowfac
// since all it does is prune.  If an entry is in F*F' or A, but not in L, it
// isn't added to L.
//
// L must be simplicial LDL' or LL'; it cannot be supernodal or symbolic.
//
// The set f is held in fset and fsize.
//      fset = NULL means ":" in MATLAB. fset is ignored.
//      fset != NULL means f = fset [0..fset-1].
//      fset != NULL and fsize = 0 means f is the empty set.
//      There can be no duplicates in fset.
//      Common->status is set to CHOLMOD_INVALID if fset is invalid.
//
// workspace: Flag (nrow), Head (nrow+1),
//      if symmetric:   Iwork (2*nrow)
//      if unsymmetric: Iwork (2*nrow+ncol).
//      Unlike cholmod_resymbol, this routine does not allocate any temporary
//      copies of its input matrix.

int CHOLMOD(resymbol_noperm)    // recompute symbolic pattern of L
(
    // input:
    cholmod_sparse *A,  // matrix to analyze
    Int *fset,          // subset of 0:(A->ncol)-1
    size_t fsize,       // size of fset
    int pack,           // if TRUE, pack the columns of L
    // input/output:
    cholmod_factor *L,  // factorization, entries pruned on output
    cholmod_common *Common
)
{

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

    RETURN_IF_NULL_COMMON (FALSE) ;
    RETURN_IF_NULL (A, FALSE) ;
    RETURN_IF_NULL (L, FALSE) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ;
    Int ncol = A->ncol ;
    Int nrow = A->nrow ;
    Int stype = A->stype ;
    ASSERT (IMPLIES (stype != 0, nrow == ncol)) ;
    if (stype > 0)
    {
        // symmetric, with upper triangular part, not supported
        ERROR (CHOLMOD_INVALID, "symmetric upper not supported ") ;
        return (FALSE) ;
    }
    if (L->is_super)
    {
        // cannot operate on a supernodal or symbolic factorization
        ERROR (CHOLMOD_INVALID, "cannot operate on supernodal L") ;
        return (FALSE) ;
    }
    if (L->n != A->nrow)
    {
        // dimensions must agree
        ERROR (CHOLMOD_INVALID, "A and L dimensions do not match") ;
        return (FALSE) ;
    }
    Common->status = CHOLMOD_OK ;

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

    // s = nrow + (stype ? 0 : ncol)
    size_t s = A->nrow ;
    int ok = TRUE ;
    if (stype != 0)
    {
        s = CHOLMOD(add_size_t) (s, A->ncol, &ok) ;
    }
    if (!ok)
    {
        ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
        return (FALSE) ;
    }

    CHOLMOD(allocate_work) (A->nrow, s, 0, Common) ;
    if (Common->status < CHOLMOD_OK)
    {
        return (FALSE) ;        // out of memory
    }
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, 0, Common)) ;

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

    Int *Ai = A->i ;
    Int *Ap = A->p ;
    Int *Anz = A->nz ;
    bool apacked = A->packed ;
    bool sorted = A->sorted ;

    Int *Lp = L->p ;

    // If L is monotonic on input, then it can be packed or
    // unpacked on output, depending on the pack input parameter.

    // cannot pack a non-monotonic matrix
    if (!(L->is_monotonic))
    {
        pack = FALSE ;
    }

    ASSERT (L->nzmax >= (size_t) (Lp [L->n])) ;

    PRINT1 (("\n\n===================== Resymbol pack %d Apacked %d\n",
        pack, A->packed)) ;
    ASSERT (CHOLMOD(dump_sparse) (A, "ReSymbol A:", Common) >= 0) ;
    DEBUG (CHOLMOD(dump_factor) (L, "ReSymbol initial L (i, x):", Common)) ;

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

    Int *Head  = Common->Head ;      // size nrow+1
    Int *Iwork = Common->Iwork ;
    Int *Link  = Iwork ;             // size nrow [
    Int *Anext = Iwork + nrow ;      // size ncol, unsym. only
    for (Int j = 0 ; j < nrow ; j++)
    {
        Link [j] = EMPTY ;
    }

    //--------------------------------------------------------------------------
    // for the unsymmetric case, queue each column of A (:,f)
    //--------------------------------------------------------------------------

    // place each column of the basis set on the link list corresponding to
    // the smallest row index in that column

    if (stype == 0)
    {
        Int nf ;
        bool use_fset = (fset != NULL) ;
        if (use_fset)
        {
            nf = fsize ;
            // This is the only O(ncol) loop in cholmod_resymbol.
            // It is required only to check the fset.
            for (Int j = 0 ; j < ncol ; j++)
            {
                Anext [j] = -2 ;
            }
            for (Int jj = 0 ; jj < nf ; jj++)
            {
                Int j = fset [jj] ;
                if (j < 0 || j > ncol || Anext [j] != -2)
                {
                    // out-of-range or duplicate entry in fset
                    ERROR (CHOLMOD_INVALID, "fset invalid") ;
                    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, 0, Common)) ;
                    return (FALSE) ;
                }
                // flag column j as having been seen
                Anext [j] = EMPTY ;
            }
            // the fset is now valid
            ASSERT (CHOLMOD(dump_perm) (fset, nf, ncol, "fset", Common)) ;
        }
        else
        {
            nf = ncol ;
        }
        for (Int jj = 0 ; jj < nf ; jj++)
        {
            Int j = (use_fset) ? (fset [jj]) : jj ;
            // column j is the fset; find the smallest row (if any)
            Int p = Ap [j] ;
            Int pend = (apacked) ? (Ap [j+1]) : (p + Anz [j]) ;
            if (pend > p)
            {
                Int k = Ai [p] ;
                if (!sorted)
                {
                    for ( ; p < pend ; p++)
                    {
                        k = MIN (k, Ai [p]) ;
                    }
                }
                // place column j on link list k
                ASSERT (k >= 0 && k < nrow) ;
                Anext [j] = Head [k] ;
                Head [k] = j ;
            }
        }
    }

    //--------------------------------------------------------------------------
    // recompute symbolic LDL' factorization
    //--------------------------------------------------------------------------

    switch ((L->xtype + L->dtype) % 8)
    {
        case CHOLMOD_REAL    + CHOLMOD_SINGLE:
            rs_cholmod_resymbol_worker (A, pack, L, Common) ;
            break ;

        case CHOLMOD_COMPLEX + CHOLMOD_SINGLE:
            cs_cholmod_resymbol_worker (A, pack, L, Common) ;
            break ;

        case CHOLMOD_ZOMPLEX + CHOLMOD_SINGLE:
            zs_cholmod_resymbol_worker (A, pack, L, Common) ;
            break ;

        case CHOLMOD_REAL    + CHOLMOD_DOUBLE:
            rd_cholmod_resymbol_worker (A, pack, L, Common) ;
            break ;

        case CHOLMOD_COMPLEX + CHOLMOD_DOUBLE:
            cd_cholmod_resymbol_worker (A, pack, L, Common) ;
            break ;

        case CHOLMOD_ZOMPLEX + CHOLMOD_DOUBLE:
            zd_cholmod_resymbol_worker (A, pack, L, Common) ;
            break ;
    }

    // done using Iwork for Link and Anext ]

    //--------------------------------------------------------------------------
    // convert L to packed, if requested
    //--------------------------------------------------------------------------

    if (pack)
    {
        // Shrink L to be just large enough.  It cannot fail.
        // workspace: none
        ASSERT ((size_t) (Lp [nrow]) <= L->nzmax) ;
        CHOLMOD(reallocate_factor) (Lp [nrow], L, Common) ;
        ASSERT (Common->status >= CHOLMOD_OK) ;
    }

    //--------------------------------------------------------------------------
    // clear workspace and return result
    //--------------------------------------------------------------------------

    CLEAR_FLAG (Common) ;
    ASSERT (check_flag (Common)) ;
    DEBUG (CHOLMOD(dump_factor) (L, "ReSymbol final L (i, x):", Common)) ;
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, 0, Common)) ;
    return (TRUE) ;
}
#endif

