/*
 * lu_singletons.c
 *
 * Copyright (C) 2016-2018  ERGO-Code
 *
 * Build initial triangular factors
 *
 */

#include "ipm/basiclu/lu_internal.h"

static lu_int singleton_cols
(
    const lu_int m,
    const lu_int *Bbegin,       /* B columnwise */
    const lu_int *Bend,
    const lu_int *Bi,
    const double *Bx,
    const lu_int *Btp,          /* B rowwise */
    const lu_int *Bti,
    const double *Btx,
    lu_int *Up,
    lu_int *Ui,
    double *Ux,
    lu_int *Lp,
    lu_int *Li,
    double *Lx,
    double *col_pivot,
    lu_int *pinv,
    lu_int *qinv,
    lu_int *iset,               /* size m workspace */
    lu_int *queue,              /* size m workspace */
    lu_int rank,
    double abstol
);

static lu_int singleton_rows
(
    const lu_int m,
    const lu_int *Bbegin,       /* B columnwise */
    const lu_int *Bend,
    const lu_int *Bi,
    const double *Bx,
    const lu_int *Btp,          /* B rowwise */
    const lu_int *Bti,
    const double *Btx,
    lu_int *Up,
    lu_int *Ui,
    double *Ux,
    lu_int *Lp,
    lu_int *Li,
    double *Lx,
    double *col_pivot,
    lu_int *pinv,
    lu_int *qinv,
    lu_int *iset,               /* size m workspace */
    lu_int *queue,              /* size m workspace */
    lu_int rank,
    double abstol
);


/*
 * lu_singletons()
 *
 * Initialize the data structures which store the LU factors during
 * factorization and eliminate pivots with Markowitz cost zero.
 *
 * During factorization the inverse pivot sequence is recorded in pinv, qinv:
 *
 *   pinv[i] >=  0   if row i was pivot row in stage pinv[i]
 *   pinv[i] == -1   if row i has not been pivot row yet
 *   qinv[j] >=  0   if col j was pivot col in stage qinv[j]
 *   qinv[j] == -1   if col j has not been pivot col yet
 *
 * The lower triangular factor is composed columnwise in Lindex, Lvalue.
 * The upper triangular factor is composed rowwise in Uindex, Uvalue.
 * After rank steps of factorization:
 *
 *   Lbegin_p[rank] is the next unused position in Lindex, Lvalue.
 *
 *   Lindex[Lbegin_p[k]..], Lvalue[Lbegin_p[k]..] for 0 <= k < rank
 *   stores the column of L computed in stage k without the unit diagonal.
 *   The column is terminated by a negative index.
 *
 *   Ubegin[rank] is the next unused position in Uindex, Uvalue.
 *
 *   Uindex[Ubegin[k]..Ubegin[k+1]-1], Uvalue[Ubegin[k]..Ubegin[k+1]-1]
 *   stores the row of U computed in stage k without the pivot element.
 *
 * lu_singletons() does rank >= 0 steps of factorization until no singletons are
 * left. We can either eliminate singleton columns before singleton rows or vice
 * versa. When nzbias >= 0, then eliminate singleton columns first to keep L
 * sparse. Otherwise eliminate singleton rows first. The resulting permutations
 * P, Q (stored in inverse form) make PBQ' of the form
 *
 *          \uuuuuuuuuuuuuuuuuuuuuuu
 *           \u                    u
 *            \u                   u
 *             \u                  u
 *              \u                 u
 *  PBQ' =       \uuuuuuu__________u               singleton columns before
 *                \     |          |               singleton rows
 *                l\    |          |
 *                ll\   |          |
 *                l l\  |   BUMP   |
 *                l  l\ |          |
 *                lllll\|__________|
 *
 *          \
 *          l\
 *          ll\
 *          l l\
 *          l  l\
 *          l   l\       __________
 *  PBQ' =  l    l\uuuuu|          |               singleton rows before
 *          l    l \u  u|          |               singleton columns
 *          l    l  \u u|          |
 *          l    l   \uu|   BUMP   |
 *          l    l    \u|          |
 *          llllll     \|__________|
 *
 * Off-diagonals from singleton columns (u) are stored in U, off-diagonals from
 * singleton rows (l) are stored in L and divided by the diagonal. Diagonals (\)
 * are stored in col_pivot.
 *
 * Do not pivot on elements which are zero or less than abstol in magnitude.
 * When such pivots occur, the row/column remains in the active submatrix and
 * the bump factorization will detect the singularity.
 *
 * Return:
 *
 *  BASICLU_REALLOCATE              less than nnz(B) memory in L, U or W
 *  BASICLU_ERROR_invalid_argument  matrix B is invalid (negative number of
 *                                  entries in column, index out of range,
 *                                  duplicates)
 *  BASICLU_OK
 */

lu_int lu_singletons(
    struct lu *this, const lu_int *Bbegin, const lu_int *Bend, const lu_int *Bi,
    const double *Bx)
{
    const lu_int m      = this->m;
    const lu_int Lmem   = this->Lmem;
    const lu_int Umem   = this->Umem;
    const lu_int Wmem   = this->Wmem;
    const double abstol = this->abstol;
    const lu_int nzbias = this->nzbias;
    lu_int *pinv        = this->pinv;
    lu_int *qinv        = this->qinv;
    lu_int *Lbegin_p    = this->Lbegin_p;
    lu_int *Ubegin      = this->Ubegin;
    double *col_pivot   = this->col_pivot;
    lu_int *Lindex      = this->Lindex;
    double *Lvalue      = this->Lvalue;
    lu_int *Uindex      = this->Uindex;
    double *Uvalue      = this->Uvalue;
    lu_int *iwork1      = this->iwork1;
    lu_int *iwork2      = iwork1 + m;

    lu_int *Btp         = this->Wbegin; /* build B rowwise in W */
    lu_int *Bti         = this->Windex;
    double *Btx         = this->Wvalue;

    lu_int i, j, pos, put, rank, Bnz, ok;

    /* -------------------------------- */
    /* Check matrix and build transpose */
    /* -------------------------------- */

    /* Check pointers and count nnz(B). */
    Bnz = 0;
    ok = 1;
    for (j = 0; j < m && ok; j++)
    {
        if (Bend[j] < Bbegin[j])
            ok = 0;
        else
            Bnz += Bend[j] - Bbegin[j];
    }
    if (!ok)
        return BASICLU_ERROR_invalid_argument;

    /* Check if sufficient memory in L, U, W. */
    ok = 1;
    if (Lmem < Bnz) { this->addmemL = Bnz-Lmem; ok = 0; }
    if (Umem < Bnz) { this->addmemU = Bnz-Umem; ok = 0; }
    if (Wmem < Bnz) { this->addmemW = Bnz-Wmem; ok = 0; }
    if (!ok)
        return BASICLU_REALLOCATE;

    /* Count nz per row, check indices. */
    memset(iwork1, 0, m*sizeof(lu_int)); /* row counts */
    ok = 1;
    for (j = 0; j < m && ok; j++)
    {
        for (pos = Bbegin[j]; pos < Bend[j] && ok; pos++)
        {
            i = Bi[pos];
            if (i < 0 || i >= m)
                ok = 0;
            else
                iwork1[i]++;
        }
    }
    if (!ok)
        return BASICLU_ERROR_invalid_argument;

    /* Pack matrix rowwise, check for duplicates. */
    put = 0;
    for (i = 0; i < m; i++)     /* set row pointers */
    {
        Btp[i] = put;
        put += iwork1[i];
        iwork1[i] = Btp[i];
    }
    Btp[m] = put;
    assert(put == Bnz);
    ok = 1;
    for (j = 0; j < m; j++)     /* fill rows */
    {
        for (pos = Bbegin[j]; pos < Bend[j]; pos++)
        {
            i = Bi[pos];
            put = iwork1[i]++;
            Bti[put] = j;
            Btx[put] = Bx [pos];
            if (put > Btp[i] && Bti[put-1] == j)
                ok = 0;
        }
    }
    if (!ok)
        return BASICLU_ERROR_invalid_argument;

    /* ---------------- */
    /* Pivot singletons */
    /* ---------------- */

    /* No pivot rows or pivot columns so far. */
    for (i = 0; i < m; i++)
        pinv[i] = -1;
    for (j = 0; j < m; j++)
        qinv[j] = -1;

    if (nzbias >= 0)            /* put more in U */
    {
        Lbegin_p[0] = Ubegin[0] = rank = 0;

        rank = singleton_cols(m, Bbegin, Bend, Bi, Bx, Btp, Bti, Btx,
                              Ubegin, Uindex, Uvalue, Lbegin_p, Lindex, Lvalue,
                              col_pivot, pinv, qinv, iwork1, iwork2, rank,
                              abstol);

        rank = singleton_rows(m, Bbegin, Bend, Bi, Bx, Btp, Bti, Btx,
                              Ubegin, Uindex, Uvalue, Lbegin_p, Lindex, Lvalue,
                              col_pivot, pinv, qinv, iwork1, iwork2, rank,
                              abstol);
    }
    else                        /* put more in L */
    {
        Lbegin_p[0] = Ubegin[0] = rank = 0;

        rank = singleton_rows(m, Bbegin, Bend, Bi, Bx, Btp, Bti, Btx,
                              Ubegin, Uindex, Uvalue, Lbegin_p, Lindex, Lvalue,
                              col_pivot, pinv, qinv, iwork1, iwork2, rank,
                              abstol);

        rank = singleton_cols(m, Bbegin, Bend, Bi, Bx, Btp, Bti, Btx,
                              Ubegin, Uindex, Uvalue, Lbegin_p, Lindex, Lvalue,
                              col_pivot, pinv, qinv, iwork1, iwork2, rank,
                              abstol);
    }

    /* pinv, qinv were used as nonzero counters. Reset to -1 if not pivoted. */
    for (i = 0; i < m; i++)
        if (pinv[i] < 0)
            pinv[i] = -1;
    for (j = 0; j < m; j++)
        if (qinv[j] < 0)
            qinv[j] = -1;

    this->matrix_nz = Bnz;
    this->rank = rank;
    return BASICLU_OK;
}


/*
 * singleton_cols()
 *
 * The method successively removes singleton cols from an active submatrix.
 * The active submatrix is composed of columns j for which qinv[j] < 0 and
 * rows i for which pinv[i] < 0. When removing a singleton column and its
 * associated row generates new singleton columns, these are appended to a
 * queue. The method stops when the active submatrix has no more singleton
 * columns.
 *
 * For each active column j iset[j] is the XOR of row indices in the column
 * in the active submatrix. For a singleton column, this is its single row
 * index. The technique is due to J. Gilbert and described in [1], ex 3.7.
 *
 * For each eliminated column its associated row is stored in U without the
 * pivot element. The pivot elements are stored in col_pivot. For each
 * eliminated pivot an empty column is appended to L.
 *
 * Pivot elements which are zero or less than abstol, and empty columns in
 * the active submatrix are not eliminated. In these cases the matrix is
 * numerically or structurally singular and the bump factorization handles
 * it. (We want singularities at the end of the pivot sequence.)
 *
 * [1] T. Davis, "Direct methods for sparse linear systems"
 */

static lu_int singleton_cols
(
    const lu_int m,
    const lu_int *Bbegin,       /* B columnwise */
    const lu_int *Bend,
    const lu_int *Bi,
    const double *Bx,
    const lu_int *Btp,          /* B rowwise */
    const lu_int *Bti,
    const double *Btx,
    lu_int *Up,
    lu_int *Ui,
    double *Ux,
    lu_int *Lp,
    lu_int *Li,
    double *Lx,
    double *col_pivot,
    lu_int *pinv,
    lu_int *qinv,
    lu_int *iset,               /* size m workspace */
    lu_int *queue,              /* size m workspace */
    lu_int rank,
    double abstol
)
{
    lu_int i, j, j2, nz, pos, put, end, front, tail, rk = rank;
    double piv;

    /* Build index sets and initialize queue. */
    tail = 0;
    for (j = 0; j < m; j++)
    {
        if (qinv[j] < 0)
        {
            nz = Bend[j] - Bbegin[j];
            i = 0;
            for (pos = Bbegin[j]; pos < Bend[j]; pos++)
                i ^= Bi[pos];   /* put row into set j */
            iset[j] = i;
            qinv[j] = -nz-1;    /* use as nonzero counter */
            if (nz == 1)
                queue[tail++] = j;
        }
    }

    /* Eliminate singleton columns. */
    put = Up [rank];
    for (front = 0; front < tail; front++)
    {
        j = queue[front];
        assert(qinv[j] == -2 || qinv[j] == -1);
        if (qinv[j] == -1)
            continue;           /* empty column in active submatrix */
        i = iset[j];
        assert(i >= 0 && i < m);
        assert(pinv[i] < 0);
        end = Btp[i+1];
        for (pos = Btp[i]; Bti[pos] != j; pos++) /* find pivot */
            assert(pos < end-1);
        piv = Btx[pos];
        if (!piv || fabs(piv) < abstol)
            continue;           /* skip singularity */

        /* Eliminate pivot. */
        qinv[j] = rank;
        pinv[i] = rank;
        for (pos = Btp[i]; pos < end; pos++)
        {
            j2 = Bti[pos];
            if (qinv[j2] < 0)
                /* test is mandatory because the initial active submatrix may
                   not be the entire matrix (rows eliminated before) */
            {
                Ui[put] = j2;
                Ux[put++] = Btx[pos];
                iset[j2] ^= i;  /* remove i from set j2 */
                if (++qinv[j2] == -2)
                    queue[tail++] = j2; /* new singleton */
            }
        }
        Up[rank+1] = put;
        col_pivot[j] = piv;
        rank++;
    }

    /* Put empty columns into L. */
    pos = Lp[rk];
    for ( ; rk < rank; rk++)
    {
        Li[pos++] = -1;
        Lp[rk+1] = pos;
    }
    return rank;
}


/*
 * singleton_rows()
 *
 * Analogous singleton_cols except that for each singleton row the
 * associated column is stored in L and divided by the pivot element. The
 * pivot element is stored in col_pivot.
 */

static lu_int singleton_rows
(
    const lu_int m,
    const lu_int *Bbegin,       /* B columnwise */
    const lu_int *Bend,
    const lu_int *Bi,
    const double *Bx,
    const lu_int *Btp,          /* B rowwise */
    const lu_int *Bti,
    const double *Btx,
    lu_int *Up,
    lu_int *Ui,
    double *Ux,
    lu_int *Lp,
    lu_int *Li,
    double *Lx,
    double *col_pivot,
    lu_int *pinv,
    lu_int *qinv,
    lu_int *iset,               /* size m workspace */
    lu_int *queue,              /* size m workspace */
    lu_int rank,
    double abstol
)
{
    lu_int i, j, i2, nz, pos, put, end, front, tail, rk = rank;
    double piv;

    /* Build index sets and initialize queue. */
    tail = 0;
    for (i = 0; i < m; i++)
    {
        if (pinv[i] < 0)
        {
            nz = Btp[i+1] - Btp[i];
            j = 0;
            for (pos = Btp[i]; pos < Btp[i+1]; pos++)
                j ^= Bti[pos];  /* put column into set i */
            iset[i] = j;
            pinv[i] = -nz-1;    /* use as nonzero counter */
            if (nz == 1)
                queue[tail++] = i;
        }
    }

    /* Eliminate singleton rows. */
    put = Lp[rank];
    for (front = 0; front < tail; front++)
    {
        i = queue[front];
        assert(pinv[i] == -2 || pinv[i] == -1);
        if (pinv[i] == -1)
            continue;           /* empty column in active submatrix */
        j = iset [i];
        assert(j >= 0 && j < m);
        assert(qinv[j] < 0);
        end = Bend[j];
        for (pos = Bbegin[j]; Bi[pos] != i; pos++) /* find pivot */
            assert(pos < end-1);
        piv = Bx[pos];
        if (!piv || fabs(piv) < abstol)
            continue;           /* skip singularity */

        /* Eliminate pivot. */
        qinv[j] = rank;
        pinv[i] = rank;
        for (pos = Bbegin[j]; pos < end; pos++)
        {
            i2 = Bi[pos];
            if (pinv[i2] < 0)
                /* test is mandatory because the initial active submatrix may
                   not be the entire matrix (columns eliminated before) */
            {
                Li[put] = i2;
                Lx[put++] = Bx[pos] / piv;
                iset[i2] ^= j;  /* remove j from set i2 */
                if (++pinv[i2] == -2)
                    queue[tail++] = i2; /* new singleton */
            }
        }
        Li[put++] = -1;         /* terminate column */
        Lp[rank+1] = put;
        col_pivot[j] = piv;
        rank++;
    }

    /* Put empty rows into U. */
    pos = Up[rk];
    for ( ; rk < rank; rk++)
        Up[rk+1] = pos;

    return rank;
}
