//------------------------------------------------------------------------------
// BTF/MATLAB/btf.c: MATLAB interface for BTF
//------------------------------------------------------------------------------

// BTF, Copyright (c) 2004-2022, University of Florida.  All Rights Reserved.
// Author: Timothy A. Davis.
// SPDX-License-Identifier: LGPL-2.1+

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

/* BTF: Permute a square matrix to upper block triangular form with a zero-free
 * diagonal, or with a maximum number of nonzeros along the diagonal if a
 * zero-free permutation does not exist.
 *
 * Usage:
 *
 *      [p,q,r] = btf (A) ;
 *      [p,q,r] = btf (A, maxwork) ;
 *
 * If the matrix has structural full rank, this is essentially identical to
 *
 *      [p,q,r] = dmperm (A)
 *
 * except that p, q, and r will differ in trivial ways.  Both return an upper
 * block triangular form with a zero-free diagonal, if the matrix is
 * structurally non-singular.  The number and sizes of the blocks will be
 * identical, but the order of the blocks, and the ordering within the blocks,
 * can be different.
 * 
 * If the matrix is structurally singular, q will contain negative entries.
 * The permuted matrix is C = A(p,abs(q)), and find(q<0) gives a list of
 * indices of the diagonal of C that are equal to zero.  This differs from
 * dmperm, which does not place the maximum matching along the main diagonal
 * of C=A(p,q), but places it above the diagonal instead.
 *
 * See maxtrans, or btf.m, for a description of maxwork.
 *
 * An optional fourth output [p,q,r,work] = btf (...) returns the amount of
 * work performed, or -1 if the maximum work limit is reached (in which case
 * the maximum matching might not have been found).
 *
 * See also maxtrans, strongcomp, dmperm
 */

/* ========================================================================== */

#include "mex.h"
#include "btf.h"

void mexFunction
(
    int nargout,
    mxArray *pargout [ ],
    int nargin,
    const mxArray *pargin [ ]
)
{
    double work, maxwork ;
    int64_t b, n, k, *Ap, *Ai, *P, *R, nblocks, *Work, *Q, nmatch ;
    double *Px, *Rx, *Qx, *w ;

    /* ---------------------------------------------------------------------- */
    /* get inputs and allocate workspace */
    /* ---------------------------------------------------------------------- */

    if (nargin < 1 || nargin > 2 || nargout > 4)
    {
        mexErrMsgTxt ("Usage: [p,q,r] = btf (A)") ;
    }
    n = mxGetM (pargin [0]) ;
    if (!mxIsSparse (pargin [0]) || n != mxGetN (pargin [0]))
    {
        mexErrMsgTxt ("btf: A must be sparse, square, and non-empty") ;
    }

    /* get sparse matrix A */
    Ap = (int64_t *) mxGetJc (pargin [0]) ;
    Ai = (int64_t *) mxGetIr (pargin [0]) ;

    /* get output arrays */
    Q = mxMalloc (n * sizeof (int64_t)) ;
    P = mxMalloc (n * sizeof (int64_t)) ;
    R = mxMalloc ((n+1) * sizeof (int64_t)) ;

    /* get workspace */
    Work = mxMalloc (5*n * sizeof (int64_t)) ;

    maxwork = 0 ;
    if (nargin > 1)
    {
        maxwork = mxGetScalar (pargin [1]) ;
    }
    work = 0 ;

    /* ---------------------------------------------------------------------- */
    /* find the permutation to BTF */
    /* ---------------------------------------------------------------------- */

    nblocks = btf_l_order (n, Ap, Ai, maxwork, &work, P, Q, R, &nmatch, Work) ;

    /* ---------------------------------------------------------------------- */
    /* create outputs and free workspace */
    /* ---------------------------------------------------------------------- */

    /* create P */
    pargout [0] = mxCreateDoubleMatrix (1, n, mxREAL) ;
    Px = mxGetPr (pargout [0]) ;
    for (k = 0 ; k < n ; k++)
    {
        Px [k] = P [k] + 1 ;    /* convert to 1-based */
    }

    /* create Q */
    if (nargout > 1)
    {
        pargout [1] = mxCreateDoubleMatrix (1, n, mxREAL) ;
        Qx = mxGetPr (pargout [1]) ;
        for (k = 0 ; k < n ; k++)
        {
            Qx [k] = Q [k] + 1 ;        /* convert to 1-based */
        }
    }

    /* create R */
    if (nargout > 2)
    {
        pargout [2] = mxCreateDoubleMatrix (1, nblocks+1, mxREAL) ;
        Rx = mxGetPr (pargout [2]) ;
        for (b = 0 ; b <= nblocks ; b++)
        {
            Rx [b] = R [b] + 1 ;        /* convert to 1-based */
        }
    }

    /* create work output */
    if (nargout > 3)
    {
        pargout [3] = mxCreateDoubleMatrix (1, 1, mxREAL) ;
        w = mxGetPr (pargout [3]) ;
        w [0] = work ;
    }

    mxFree (P) ;
    mxFree (R) ;
    mxFree (Work) ;
    mxFree (Q) ;
}
