//------------------------------------------------------------------------------
// CHOLMOD/MATLAB/ldlchol: MATLAB interface to CHOLMOD LDL' factorization
//------------------------------------------------------------------------------

// CHOLMOD/MATLAB Module.  Copyright (C) 2005-2023, Timothy A. Davis.
// All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0+

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

// Numeric LDL' factorization.  Note that LL' and LDL' are faster than R'R
// and use less memory.  The LDL' factorization methods use tril(A).
// The unit diagonal L can be obtained with tril(LD,-1)+speye(n) and the
// diagonal D can be obtained with D = diag(diag(LD)) ;
//
// LD = ldlchol (A)             return the LDL' factorization of A
// [LD,p] = ldlchol (A)         like [R,p] = chol(A), except LD is always square
// [LD,p,q] = ldlchol (A)       factorizes A(q,q) into L*D*L'
//
// LD = ldlchol (A,beta)        return the LDL' factorization of A*A'+beta*I
// [LD,p] = ldlchol (A,beta)    like [R,p] = chol(A*A'+beta+I)
// [LD,p,q] = ldlchol (A,beta)  factorizes A(q,:)*A(q,:)'+beta*I into L*D*L'
//
// Explicit zeros may appear in the LD matrix.  The pattern of LD matches the
// pattern of L as computed by symbfact2, even if some entries in LD are
// explicitly zero.  This is to ensure that ldlupdate works properly.  You must
// NOT modify LD in MATLAB itself and then use ldlupdate if LD contains
// explicit zero entries; ldlupdate will fail catastrophically in this case.
//
// You MAY modify LD in MATLAB if you do not pass it back to ldlupdate.  Just be
// aware that LD contains explicit zero entries, contrary to the standard
// practice in MATLAB of removing those entries from all sparse matrices.
//
// Note that CHOLMOD uses a supernodal LL' factorization and then converts it
// to LDL' for large matrices, and a simplicial LDL' factorization otherwise.
// Two-by-two block pivoting is not performed, in any case.  Thus, ldlchol
// will not be able to perform an LDL' factorization of an arbitrary indefinite
// matrix.  The matrix
//
//  0 1
//  1 0
//
// will fail, for example.  You can tell CHOLMOD to always use its simplicial
// LDL' factorization by adding the statement
//
//      cm->supernodal = CHOLMOD_SIMPLICIAL ;
//
// but ldlchol will be much slower for large matrices.  It still will not be
// able to handle the above matrix, but it can then handle matrices such as
//
//  -2  1
//   1 -2
//
// or any other symmetric matrix for which all leading minors are
// well-conditioned.

#include "sputil2.h"

void mexFunction
(
    int nargout,
    mxArray *pargout [ ],
    int nargin,
    const mxArray *pargin [ ]
)
{
    double dummy = 0, beta [2], *px ;
    cholmod_sparse Amatrix, *A, *Lsparse ;
    cholmod_factor *L ;
    cholmod_common Common, *cm ;
    int64_t n, minor ;

    //--------------------------------------------------------------------------
    // start CHOLMOD and set parameters
    //--------------------------------------------------------------------------

    cm = &Common ;
    cholmod_l_start (cm) ;
    sputil2_config (SPUMONI, cm) ;

    // convert to packed LDL' when done
    cm->final_asis = FALSE ;
    cm->final_super = FALSE ;
    cm->final_ll = FALSE ;
    cm->final_pack = TRUE ;
    cm->final_monotonic = TRUE ;

    // since numerically zero entries are NOT dropped from the symbolic
    // pattern, we DO need to drop entries that result from supernodal
    // amalgamation.
    cm->final_resymbol = TRUE ;

    cm->quick_return_if_not_posdef = (nargout < 2) ;

    // This will disable the supernodal LL', which will be slow.
    // cm->supernodal = CHOLMOD_SIMPLICIAL ;

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

    if (nargin < 1 || nargin > 2 || nargout > 3)
    {
        mexErrMsgTxt ("usage: [L,p,q] = ldlchol (A,beta)") ;
    }

    n = mxGetM (pargin [0]) ;

    if (!mxIsSparse (pargin [0]))
    {
        mexErrMsgTxt ("A must be sparse") ;
    }
    if (nargin == 1 && n != mxGetN (pargin [0]))
    {
        mexErrMsgTxt ("A must be square") ;
    }

    // get sparse matrix A, use tril(A)
    size_t A_xsize = 0 ;
    A = sputil2_get_sparse (pargin [0], -1, CHOLMOD_DOUBLE, &Amatrix,
        &A_xsize, cm) ;

    if (nargin == 1)
    {
        A->stype = -1 ;     // use lower part of A
        beta [0] = 0 ;
        beta [1] = 0 ;
    }
    else
    {
        A->stype = 0 ;      // use all of A, factorizing A*A'
        beta [0] = mxGetScalar (pargin [1]) ;
        beta [1] = 0 ;
    }

    // use natural ordering if no q output parameter
    if (nargout < 3)
    {
        cm->nmethods = 1 ;
        cm->method [0].ordering = CHOLMOD_NATURAL ;
        cm->postorder = FALSE ;
    }

    //--------------------------------------------------------------------------
    // analyze and factorize
    //--------------------------------------------------------------------------

    L = cholmod_l_analyze (A, cm) ;
    cholmod_l_factorize_p (A, beta, NULL, 0, L, cm) ;

    if (nargout < 2 && cm->status != CHOLMOD_OK)
    {
        mexErrMsgTxt ("matrix is not positive definite") ;
    }

    //--------------------------------------------------------------------------
    // convert L to a sparse matrix
    //--------------------------------------------------------------------------

    // the conversion sets L->minor back to n, so get a copy of it first
    minor = L->minor ;
    Lsparse = cholmod_l_factor_to_sparse (L, cm) ;
    if (Lsparse->xtype == CHOLMOD_COMPLEX)
    {
        // convert Lsparse from complex to zomplex
        cholmod_l_sparse_xtype (CHOLMOD_ZOMPLEX, Lsparse, cm) ;
    }

    //--------------------------------------------------------------------------
    // return results to MATLAB
    //--------------------------------------------------------------------------

    // return L as a sparse matrix; it may contain numerically zero entries,
    // which must be kept to allow update/downdate to work.
    pargout [0] = sputil2_put_sparse (&Lsparse, mxDOUBLE_CLASS,
        /* return L with explicit zeros kept */ false, cm) ;

    // return minor (translate to MATLAB convention)
    if (nargout > 1)
    {
        pargout [1] = mxCreateDoubleMatrix (1, 1, mxREAL) ;
        px = (double *) mxGetData (pargout [1]) ;
        px [0] = ((minor == n) ? 0 : (minor+1)) ;
    }

    // return permutation
    if (nargout > 2)
    {
        pargout [2] = sputil2_put_int (L->Perm, n, 1) ;
    }

    //--------------------------------------------------------------------------
    // free workspace and the CHOLMOD L, except for what is copied to MATLAB
    //--------------------------------------------------------------------------

    cholmod_l_free_factor (&L, cm) ;
    sputil2_free_sparse (&A, &Amatrix, A_xsize, cm) ;
    cholmod_l_finish (cm) ;
    if (SPUMONI > 0) cholmod_l_print_common (" ", cm) ;
    if (SPUMONI > 1) cholmod_l_gpu_stats (cm) ;
}

