//------------------------------------------------------------------------------
// CHOLMOD/Cholesky/cholmod_amd: AMD interface for CHOLMOD
//------------------------------------------------------------------------------

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

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

// CHOLMOD interface to the AMD ordering routine.  Orders A if the matrix is
// symmetric.  On output, Perm [k] = i if row/column i of A is the kth
// row/column of P*A*P'.  This corresponds to A(p,p) in MATLAB notation.
//
// If A is unsymmetric, cholmod_amd orders A*A'.  On output, Perm [k] = i if
// row/column i of A*A' is the kth row/column of P*A*A'*P'.  This corresponds to
// A(p,:)*A(p,:)' in MATLAB notation.  If f is present, A(p,f)*A(p,f)' is
// ordered.
//
// Computes the flop count for a subsequent LL' factorization, the number
// of nonzeros in L, and the number of nonzeros in the matrix ordered (A,
// A*A' or A(:,f)*A(:,f)').
//
// workspace: Iwork (6*nrow). Head (nrow).
//
// Allocates a temporary copy of A+A' or A*A' (with
// both upper and lower triangular parts) as input to AMD.
//
// Supports any xtype (pattern, real, complex, or zomplex) and any dtype.

#include "cholmod_internal.h"

#ifndef NCHOLESKY

#include "amd.h"
#if (!defined (AMD__VERSION) || (AMD__VERSION < SUITESPARSE__VERCODE(3,3,3)))
#error "CHOLMOD:Cholesky requires AMD 3.3.3 or later"
#endif

//------------------------------------------------------------------------------
// cholmod_amd
//------------------------------------------------------------------------------

int CHOLMOD(amd)
(
    // input:
    cholmod_sparse *A,  // matrix to order
    Int *fset,          // subset of 0:(A->ncol)-1
    size_t fsize,       // size of fset
    // output:
    Int *Perm,          // size A->nrow, output permutation
    cholmod_common *Common
)
{

    double Info [AMD_INFO], Control2 [AMD_CONTROL], *Control ;
    Int *Cp, *Len, *Nv, *Head, *Elen, *Degree, *Wi, *Iwork, *Next ;
    cholmod_sparse *C ;
    Int j, n, cnz ;
    int ok = TRUE ;

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

    RETURN_IF_NULL_COMMON (FALSE) ;
    RETURN_IF_NULL (A, FALSE) ;
    n = A->nrow ;

    RETURN_IF_NULL (Perm, FALSE) ;
    RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ;
    Common->status = CHOLMOD_OK ;
    if (n == 0)
    {
        // nothing to do
        Common->fl = 0 ;
        Common->lnz = 0 ;
        Common->anz = 0 ;
        return (TRUE) ;
    }

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

    // Note: this is less than the space used in cholmod_analyze, so if
    // cholmod_amd is being called by that routine, no space will be
    // allocated.

    // s = MAX (6*nrow, ncol)
    size_t s = CHOLMOD(mult_size_t) (A->nrow, 6, &ok) ;
    if (!ok)
    {
        ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
        return (FALSE) ;
    }
    s = MAX (s, A->ncol) ;

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

    Iwork  = Common->Iwork ;
    Degree = Iwork ;                    // size n
    Wi     = Iwork + n ;                // size n
    Len    = Iwork + 2*((size_t) n) ;   // size n
    Nv     = Iwork + 3*((size_t) n) ;   // size n
    Next   = Iwork + 4*((size_t) n) ;   // size n
    Elen   = Iwork + 5*((size_t) n) ;   // size n

    Head = Common->Head ;   // size n+1, but only n is used

    //--------------------------------------------------------------------------
    // construct the input matrix for AMD
    //--------------------------------------------------------------------------

    if (A->stype == 0)
    {
        // C = A*A' or A(:,f)*A(:,f)', add extra space of nnz(C)/2+n to C
        C = CHOLMOD(aat) (A, fset, fsize, -2, Common) ;
    }
    else
    {
        // C = A+A', but use only the upper triangular part of A if A->stype = 1
        // and only the lower part of A if A->stype = -1.  Add extra space of
        // nnz(C)/2+n to C.
        C = CHOLMOD(copy) (A, 0, -2, Common) ;
    }

    if (Common->status < CHOLMOD_OK)
    {
        // out of memory, fset invalid, or other error
        return (FALSE) ;
    }

    Cp = C->p ;
    for (j = 0 ; j < n ; j++)
    {
        Len [j] = Cp [j+1] - Cp [j] ;
    }

    // C does not include the diagonal, and both upper and lower parts.
    // Common->anz includes the diagonal, and just the lower part of C
    cnz = Cp [n] ;
    Common->anz = cnz / 2 + n ;

    //--------------------------------------------------------------------------
    // order C using AMD
    //--------------------------------------------------------------------------

    // get parameters
    if (Common->current < 0 || Common->current >= CHOLMOD_MAXMETHODS)
    {
        // use AMD defaults
        Control = NULL ;
    }
    else
    {
        Control = Control2 ;
        Control [AMD_DENSE] = Common->method [Common->current].prune_dense ;
        Control [AMD_AGGRESSIVE] = Common->method [Common->current].aggressive ;
    }

    #if defined ( CHOLMOD_INT64 )
    amd_l2 (n, C->p,  C->i, Len, C->nzmax, cnz, Nv, Next, Perm, Head, Elen,
            Degree, Wi, Control, Info) ;
    #else
    amd_2 (n, C->p,  C->i, Len, C->nzmax, cnz, Nv, Next, Perm, Head, Elen,
            Degree, Wi, Control, Info) ;
    #endif

    // LL' flop count.  Need to subtract n for LL' flop count.  Note that this
    // is a slight upper bound which is often exact (see AMD/Source/amd_2.c for
    // details).  cholmod_analyze computes an exact flop count and fill-in.
    Common->fl = Info [AMD_NDIV] + 2 * Info [AMD_NMULTSUBS_LDL] + n ;

    // Info [AMD_LNZ] excludes the diagonal
    Common->lnz = n + Info [AMD_LNZ] ;

    //--------------------------------------------------------------------------
    // free the AMD workspace and clear the persistent workspace in Common
    //--------------------------------------------------------------------------

    ASSERT (IMPLIES (Common->status == CHOLMOD_OK,
                CHOLMOD(dump_perm) (Perm, n, n, "AMD2 perm", Common))) ;
    CHOLMOD(free_sparse) (&C, Common) ;
    for (j = 0 ; j <= n ; j++)
    {
        Head [j] = EMPTY ;
    }
    return (TRUE) ;
}
#endif

