//------------------------------------------------------------------------------
// LAGraph_AllKTruss.c: find all k-trusses of a graph
//------------------------------------------------------------------------------

// LAGraph, (c) 2019-2022 by The LAGraph Contributors, All Rights Reserved.
// SPDX-License-Identifier: BSD-2-Clause
//
// For additional details (including references to third party source code and
// other files) see the LICENSE file or contact permission@sei.cmu.edu. See
// Contributors.txt for a full list of contributors. Created, in part, with
// funding and support from the U.S. Government (see Acknowledgments.txt file).
// DM22-0790

// Contributed by Timothy A. Davis, Texas A&M University

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

// LAGraph_AllKTruss: find all k-trusses of a graph via GraphBLAS.

// Given a symmetric graph A with no-self edges, LAGraph_AllKTruss finds all
// k-trusses of A.

// The output matrices Cset [3..kmax-1] are the k-trusses of A.  Their edges
// are a subset of A.  Each edge in C = Cset [k] is part of at least k-2
// triangles in C.  The structure of C is the adjacency matrix of the k-truss
// subgraph of A.  The edge weights of C are the support of each edge.  That
// is, C(i,j)=nt if the edge (i,j) is part of nt triangles in C.  All edges in
// C have support of at least k-2.  The total number of triangles in C is
// sum(C)/6.  The number of edges in C is nnz(C)/2.  C = Cset [k] is returned
// as symmetric with a zero-free diagonal.  Cset [kmax] is an empty matrix
// since the kmax-truss is empty.

// The arrays ntris, nedges, and nstepss hold the output statistics.
// ntris   [k] = # of triangles in the k-truss
// nedges  [k] = # of edges in the k-truss
// nstepss [k] = # of steps required to compute the k-truss

// Usage: constructs all k-trusses of A, for k = 3:kmax

//      int64_t kmax ;
//      GrB_Matrix_nrows (&n, A) ;
//      GrB_Matrix *Cset = array of size max(n,4)
//      int64_t *ntris   = array of size max(n,4)
//      int64_t *nedges  = array of size max(n,4)
//      int64_t *nstepss = array of size max(n,4)
//      int result = LAGraph_AllKTruss (&Cset, &kmax, ntris, nedges,
//          nstepss, G, msg) ;

// todo: add experimental/benchmark/ktruss_demo.c to benchmark k-truss
// and all-k-truss

// todo: consider LAGraph_KTrussNext to compute the (k+1)-truss from the
// k-truss

// TODO: ready for src

#define LG_FREE_ALL                         \
{                                           \
    for (int64_t kk = 3 ; kk <= k ; kk++)   \
    {                                       \
        GrB_free (&(Cset [kk])) ;           \
    }                                       \
}

#include "LG_internal.h"
#include "LAGraphX.h"

//------------------------------------------------------------------------------
// C = LAGraph_AllKTruss: find all k-trusses a graph
//------------------------------------------------------------------------------

int LAGraph_AllKTruss   // compute all k-trusses of a graph
(
    // outputs
    GrB_Matrix *Cset,   // size n, output k-truss subgraphs
    int64_t *kmax,      // smallest k where k-truss is empty
    int64_t *ntris,     // size max(n,4), ntris [k] is #triangles in k-truss
    int64_t *nedges,    // size max(n,4), nedges [k] is #edges in k-truss
    int64_t *nstepss,   // size max(n,4), nstepss [k] is #steps for k-truss
    // input
    LAGraph_Graph G,    // input graph
    char *msg
)
{

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

    LG_CLEAR_MSG ;
    int64_t k = 0 ;
    LG_ASSERT (Cset != NULL && nstepss != NULL, GrB_NULL_POINTER) ;
    LG_ASSERT (kmax != NULL && ntris != NULL, GrB_NULL_POINTER) ;
    LG_ASSERT (nedges != NULL, GrB_NULL_POINTER) ;
    LG_TRY (LAGraph_CheckGraph (G, msg)) ;

    if (G->kind == LAGraph_ADJACENCY_UNDIRECTED ||
       (G->kind == LAGraph_ADJACENCY_DIRECTED &&
        G->is_symmetric_structure == LAGraph_TRUE))
    {
        // the structure of A is known to be symmetric
        ;
    }
    else
    {
        // A is not known to be symmetric
        LG_ASSERT_MSG (false, -1005, "G->A must be symmetric") ;
    }

    // no self edges can be present
    LG_ASSERT_MSG (G->nself_edges == 0, -1004, "G->nself_edges must be zero") ;

    //--------------------------------------------------------------------------
    // initializations
    //--------------------------------------------------------------------------

    for (k = 0 ; k <= 3 ; k++)
    {
        Cset [k] = NULL ;
        ntris   [k] = 0 ;
        nedges  [k] = 0 ;
        nstepss [k] = 0 ;
    }
    k = 3 ;
    (*kmax) = 0 ;

    //--------------------------------------------------------------------------
    // initialzations
    //--------------------------------------------------------------------------

    GrB_Index n ;
    GrB_Matrix S = G->A ;
    GRB_TRY (GrB_Matrix_nrows (&n, S)) ;
    GRB_TRY (GrB_Matrix_new (&(Cset [k]), GrB_UINT32, n, n)) ;
    GrB_Matrix C = Cset [k] ;
    GrB_Index nvals, nvals_last ;
    GRB_TRY (GrB_Matrix_nvals (&nvals_last, S)) ;
    int64_t nsteps = 0 ;

    //--------------------------------------------------------------------------
    // find all k-trusses
    //--------------------------------------------------------------------------

    #if 0
    int mtx = 0 ;
    #endif

    while (true)
    {
        #if 0
        // dump the matrix S to a file
        uint64_t snvals ;
        GRB_TRY (GrB_Matrix_nvals (&snvals, S)) ;
        char filename [2000] ;
        sprintf (filename, "mtx_%04d.mtx", mtx) ;
        FILE *f = fopen (filename, "w") ;
        printf ("%s: with %" PRId64 " values\n", filename, snvals) ;
        LAGraph_MMWrite (S, f, NULL, msg) ;
        fclose (f) ;
        mtx++ ;
        #endif

        // C{S} = S*S'
        GRB_TRY (GrB_mxm (C, S, NULL, LAGraph_plus_one_uint32, S, S,
            GrB_DESC_RST1)) ;
        // keep entries in C that are >= k-2
        GRB_TRY (GrB_select (C, NULL, NULL, GrB_VALUEGE_UINT32, C, k-2, NULL)) ;
        nsteps++ ;
        // check if k-truss has been found
        GRB_TRY (GrB_Matrix_nvals (&nvals, C)) ;
        if (nvals == nvals_last)
        {
            // k-truss has been found
            int64_t nt = 0 ;
            GRB_TRY (GrB_reduce (&nt, NULL, GrB_PLUS_MONOID_INT64, C, NULL)) ;
            ntris   [k] = nt / 6 ;
            nedges  [k] = nvals / 2 ;
            nstepss [k] = nsteps ;
            nsteps = 0 ;
            if (nvals == 0)
            {
                // this is the last k-truss
                (*kmax) = k ;
                return (GrB_SUCCESS) ;
            }
            S = C ;             // S = current k-truss for k+1 iteration
            k++ ;               // advance to the next k-tryss
            GRB_TRY (GrB_Matrix_new (&(Cset [k]), GrB_UINT32, n, n)) ;
            C = Cset [k] ;      // C = new matrix for next k-truss
        }
        else
        {
            // advance to the next step, still computing the current k-truss
            nvals_last = nvals ;
            S = C ;
        }
    }
}
