//------------------------------------------------------------------------------
// CHOLMOD/MATLAB/ldlrowmod: MATLAB interface to CHOLMOD rowmod method
//------------------------------------------------------------------------------

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

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

// Rank-1 row add/delete of a sparse LDL' factorization.
//
// 'Adding' or 'deleting' a row does not change the dimension of L.
// Instead, deleting a row A(k,:) and the corresponding column A(:,k) means
// replacing the kth row and column of A with the kth row and column of the
// identity matrix.  Adding a row is the opposite.  This function then modifies
// the LDL' factorization of A to reflect this change.
//
// To add a row k, the kth row of A is assumed to already be the kth row of
// identity.  This condition is not checked.
//
// On input, LD contains the LDL' factorization of A.  See ldlchol for details.
// If A has been permuted, then C must reflect that permutation.  In other
// words, the caller must have already permuted C according the the fill-
// reducing ordering found by ldlchol.
//
// Usage:
//
//      LD = ldlrowmod (LD,k,C)         add row k to an LDL' factorization
//      LD = ldlrowmod (LD,k)           delete row k from an LDL' factorization
//
// See ldlrowmod.m for details.  LD and C must be real and sparse.
// C is a column vector that is the new column A(:,k).
//
// The bulk of the time is spent copying the input LD to the output LD.  This
// mexFunction could be much faster if it could safely modify its input LD.

#include "sputil2.h"

void mexFunction
(
    int nargout,
    mxArray *pargout [ ],
    int nargin,
    const mxArray *pargin [ ]
)
{
    double dummy = 0 ;
    double *Lx, *Lx2 ;
    int64_t *Li, *Lp, *Li2, *Lp2, *Lnz2, *ColCount ;
    cholmod_sparse Cmatrix, *C, *Lsparse ;
    cholmod_factor *L ;
    cholmod_common Common, *cm ;
    int64_t j, k, s, rowadd, n, lnz ;
    int ok ;

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

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

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

    if (nargout > 1 || nargin < 2 || nargin > 3)
    {
        mexErrMsgTxt ("Usage: LD = ldlrowmod (LD,k,C) or ldlrowmod (LD,k)") ;
    }

    n = mxGetN (pargin [0]) ;
    k = (int64_t) mxGetScalar (pargin [1]) ;
    k = k - 1 ;         // change from 1-based to 0-based

    if (!mxIsSparse (pargin [0])
            || n != mxGetM (pargin [0])
            || mxIsComplex (pargin [0]))
    {
        mexErrMsgTxt ("ldlrowmod: L must be real, square, and sparse") ;
    }

    //--------------------------------------------------------------------------
    // determine if we're doing an rowadd or rowdel
    //--------------------------------------------------------------------------

    rowadd = (nargin > 2) ;

    if (rowadd)
    {
        if (!mxIsSparse (pargin [2])
                || n != mxGetM (pargin [2])
                || 1 != mxGetN (pargin [2])
                || mxIsComplex (pargin [2]))
        {
            mexErrMsgTxt ("ldlrowmod: C must be a real sparse vector, "
                "with the same number of rows as LD") ;
        }
    }

    //--------------------------------------------------------------------------
    // get C: sparse vector of incoming/outgoing column
    //--------------------------------------------------------------------------

    size_t C_xsize = 0 ;
    C = (rowadd) ? sputil2_get_sparse (pargin [2], 0, CHOLMOD_DOUBLE, &Cmatrix,
        &C_xsize, cm) : NULL ;

    //--------------------------------------------------------------------------
    // construct a copy of the input sparse matrix L
    //--------------------------------------------------------------------------

    // get the MATLAB L
    Lp = (int64_t *) mxGetJc (pargin [0]) ;
    Li = (int64_t *) mxGetIr (pargin [0]) ;
    Lx = (double *) mxGetData (pargin [0]) ;

    // allocate the CHOLMOD symbolic L
    L = cholmod_l_allocate_factor (n, cm) ;
    L->ordering = CHOLMOD_NATURAL ;
    ColCount = L->ColCount ;
    for (j = 0 ; j < n ; j++)
    {
        ColCount [j] = Lp [j+1] - Lp [j] ;
    }

    // allocate space for a CHOLMOD LDL' packed factor
    cholmod_l_change_factor (CHOLMOD_REAL, FALSE, FALSE, TRUE, TRUE, L, cm) ;

    // copy MATLAB L into CHOLMOD L
    Lp2 = L->p ;
    Li2 = L->i ;
    Lx2 = L->x ;
    Lnz2 = L->nz ;
    lnz = L->nzmax ;
    for (j = 0 ; j <= n ; j++)
    {
        Lp2 [j] = Lp [j] ;
    }
    for (j = 0 ; j < n ; j++)
    {
        Lnz2 [j] = Lp [j+1] - Lp [j] ;
    }
    for (s = 0 ; s < lnz ; s++)
    {
        Li2 [s] = Li [s] ;
    }
    for (s = 0 ; s < lnz ; s++)
    {
        Lx2 [s] = Lx [s] ;
    }

    //--------------------------------------------------------------------------
    // rowadd/rowdel the LDL' factorization
    //--------------------------------------------------------------------------

    if (rowadd)
    {
        ok = cholmod_l_rowadd (k, C, L, cm) ;
    }
    else
    {
        ok = cholmod_l_rowdel (k, NULL, L, cm) ;
    }
    if (!ok) mexErrMsgTxt ("ldlrowmod failed\n") ;

    //--------------------------------------------------------------------------
    // copy the results back to MATLAB
    //--------------------------------------------------------------------------

    // change L back to packed LDL' (it may have become unpacked if the
    // sparsity pattern changed).  This change takes O(n) time if the pattern
    // of L wasn't updated.
    Lsparse = cholmod_l_factor_to_sparse (L, cm) ;

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

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

    cholmod_l_free_factor (&L, cm) ;
    sputil2_free_sparse (&C, &Cmatrix, C_xsize, cm) ;
    cholmod_l_finish (cm) ;
    if (SPUMONI > 0) cholmod_l_print_common (" ", cm) ;
}

