
#include <string.h>
#include "commonlib.h"
#include "lp_lib.h"
#include "lp_scale.h"
#include "lp_report.h"
#include "lp_price.h"
#include "lp_pricePSE.h"
#include "lp_matrix.h"

#ifdef FORTIFY
# include "lp_fortify.h"
#endif


/* -------------------------------------------------------------------------
   Basic matrix routines in lp_solve v5.0+
   -------------------------------------------------------------------------
    Author:        Michel Berkelaar (to lp_solve v3.2),
                   Kjell Eikland    (v4.0 and forward)
    Contact:       kjell.eikland@broadpark.no
    License terms: LGPL.

    Requires:      lp_lib.h, lp_pricerPSE.h, lp_matrix.h

    Release notes:
    v5.0.0  1 January 2004      First integrated and repackaged version.
    v5.0.1  7 May 2004          Added matrix transpose function.
    v5.1.0  20 July 2004        Reworked with flexible matrix storage model.
    v5.2.0  10 January 2005     Added fast deletion methods.
                                Added data extraction to matrix method.
                                Changed to explicit OF storage mode.

   ------------------------------------------------------------------------- */

STATIC MATrec *mat_create(lprec *lp, int rows, int columns, REAL epsvalue)
{
  MATrec *newmat;

  newmat = (MATrec *) calloc(1, sizeof(*newmat));
  newmat->lp = lp;

  newmat->rows_alloc = 0;
  newmat->columns_alloc = 0;
  newmat->mat_alloc = 0;

  inc_matrow_space(newmat, rows);
  newmat->rows = rows;
  inc_matcol_space(newmat, columns);
  newmat->columns = columns;
  inc_mat_space(newmat, 0);

  newmat->epsvalue = epsvalue;

  return( newmat );
}

STATIC void mat_free(MATrec **matrix)
{
  if((matrix == NULL) || (*matrix == NULL))
    return;

#if MatrixColAccess==CAM_Record
  FREE((*matrix)->col_mat);
#else /*if MatrixColAccess==CAM_Vector*/
  FREE((*matrix)->col_mat_colnr);
  FREE((*matrix)->col_mat_rownr);
  FREE((*matrix)->col_mat_value);
#endif
  FREE((*matrix)->col_end);
  FREE((*matrix)->col_tag);

#if MatrixRowAccess==RAM_Index
  FREE((*matrix)->row_mat);
#elif MatrixColAccess==CAM_Record
  FREE((*matrix)->row_mat);
#else /*if MatrixRowAccess==COL_Vector*/
  FREE((*matrix)->row_mat_colnr);
  FREE((*matrix)->row_mat_rownr);
  FREE((*matrix)->row_mat_value);
#endif
  FREE((*matrix)->row_end);
  FREE((*matrix)->row_tag);

  FREE((*matrix)->colmax);
  FREE((*matrix)->rowmax);

  FREE(*matrix);
}

STATIC MYBOOL mat_memopt(MATrec *mat, int rowextra, int colextra, int nzextra)
{
  MYBOOL status = TRUE;
  int matalloc, colalloc, rowalloc;

  if((mat == NULL) ||
#if 0
     (++rowextra < 1) || (++colextra < 1) || (++nzextra < 1))
#else
     (rowextra < 0) || (colextra < 0) || (nzextra < 0))
#endif
    return( FALSE );

  mat->rows_alloc    = MIN(mat->rows_alloc,    mat->rows + rowextra);
  mat->columns_alloc = MIN(mat->columns_alloc, mat->columns + colextra);
  mat->mat_alloc     = MIN(mat->mat_alloc,     mat->col_end[mat->columns] + nzextra);
#if 0
  rowalloc = mat->rows_alloc;
  colalloc = mat->columns_alloc;
  matalloc = mat->mat_alloc;
#else
  rowalloc = mat->rows_alloc + 1;
  colalloc = mat->columns_alloc + 1;
  matalloc = mat->mat_alloc + 1;
#endif

#if MatrixColAccess==CAM_Record
  mat->col_mat = (MATitem *) realloc(mat->col_mat, matalloc * sizeof(*(mat->col_mat)));
  status &= (mat->col_mat != NULL);
#else /*if MatrixColAccess==CAM_Vector*/
  status &= allocINT(mat->lp,  &(mat->col_mat_colnr), matalloc, AUTOMATIC) &&
            allocINT(mat->lp,  &(mat->col_mat_rownr), matalloc, AUTOMATIC) &&
            allocREAL(mat->lp, &(mat->col_mat_value), matalloc, AUTOMATIC);
#endif
  status &= allocINT(mat->lp, &mat->col_end, colalloc, AUTOMATIC);
  if(mat->col_tag != NULL)
    status &= allocINT(mat->lp, &mat->col_tag, colalloc, AUTOMATIC);

#if MatrixRowAccess==RAM_Index
  status &= allocINT(mat->lp, &(mat->row_mat), matalloc, AUTOMATIC);
#elif MatrixColAccess==CAM_Record
  mat->row_mat = (MATitem *) realloc(mat->row_mat, matalloc * sizeof(*(mat->row_mat)));
  status &= (mat->row_mat != NULL);
#else /*if MatrixRowAccess==COL_Vector*/
  status &= allocINT(mat->lp,  &(mat->row_mat_colnr), matalloc, AUTOMATIC) &&
            allocINT(mat->lp,  &(mat->row_mat_rownr), matalloc, AUTOMATIC) &&
            allocREAL(mat->lp, &(mat->row_mat_value), matalloc, AUTOMATIC);
#endif
  status &= allocINT(mat->lp, &mat->row_end, rowalloc, AUTOMATIC);
  if(mat->row_tag != NULL)
    status &= allocINT(mat->lp, &mat->row_tag, rowalloc, AUTOMATIC);

  if(mat->colmax != NULL)
    status &= allocREAL(mat->lp, &(mat->colmax), colalloc, AUTOMATIC);
  if(mat->rowmax != NULL)
    status &= allocREAL(mat->lp, &(mat->rowmax), rowalloc, AUTOMATIC);

  return( status );
}

STATIC MYBOOL inc_mat_space(MATrec *mat, int mindelta)
{
  int spaceneeded, nz = mat_nonzeros(mat);

  if(mindelta <= 0)
    mindelta = MAX(mat->rows, mat->columns) + 1;
  spaceneeded = DELTA_SIZE(mindelta, nz);
  SETMAX(mindelta, spaceneeded);

  if(mat->mat_alloc == 0)
    spaceneeded = mindelta;
  else
    spaceneeded = nz + mindelta;

  if(spaceneeded >= mat->mat_alloc) {
    /* Let's allocate at least MAT_START_SIZE entries */
    if(mat->mat_alloc < MAT_START_SIZE)
      mat->mat_alloc = MAT_START_SIZE;

    /* Increase the size by RESIZEFACTOR each time it becomes too small */
    while(spaceneeded >= mat->mat_alloc)
      mat->mat_alloc += mat->mat_alloc / RESIZEFACTOR;

#if MatrixColAccess==CAM_Record
    mat->col_mat = (MATitem *) realloc(mat->col_mat, (mat->mat_alloc) * sizeof(*(mat->col_mat)));
#else /*if MatrixColAccess==CAM_Vector*/
    allocINT(mat->lp,  &(mat->col_mat_colnr), mat->mat_alloc, AUTOMATIC);
    allocINT(mat->lp,  &(mat->col_mat_rownr), mat->mat_alloc, AUTOMATIC);
    allocREAL(mat->lp, &(mat->col_mat_value), mat->mat_alloc, AUTOMATIC);
#endif

#if MatrixRowAccess==RAM_Index
    allocINT(mat->lp, &(mat->row_mat), mat->mat_alloc, AUTOMATIC);
#elif MatrixColAccess==CAM_Record
    mat->row_mat = (MATitem *) realloc(mat->row_mat, (mat->mat_alloc) * sizeof(*(mat->row_mat)));
#else /*if MatrixColAccess==CAM_Vector*/
    allocINT(mat->lp,  &(mat->row_mat_colnr), mat->mat_alloc, AUTOMATIC);
    allocINT(mat->lp,  &(mat->row_mat_rownr), mat->mat_alloc, AUTOMATIC);
    allocREAL(mat->lp, &(mat->row_mat_value), mat->mat_alloc, AUTOMATIC);
#endif
  }
  return(TRUE);
}

STATIC MYBOOL inc_matrow_space(MATrec *mat, int deltarows)
{
  int    rowsum, oldrowsalloc;
  MYBOOL status = TRUE;

  /* Adjust lp row structures */
  if(mat->rows+deltarows >= mat->rows_alloc) {

    /* Update memory allocation and sizes */
    oldrowsalloc = mat->rows_alloc;
    deltarows = DELTA_SIZE(deltarows, mat->rows);
    SETMAX(deltarows, DELTAROWALLOC);
    mat->rows_alloc += deltarows;
    rowsum = mat->rows_alloc + 1;

    /* Update row pointers */
    status = allocINT(mat->lp, &mat->row_end, rowsum, AUTOMATIC);
    mat->row_end_valid = FALSE;
  }
  return( status );
}

STATIC MYBOOL inc_matcol_space(MATrec *mat, int deltacols)
{
  int    i, colsum, oldcolsalloc;
  MYBOOL status = TRUE;

  /* Adjust lp column structures */
  if(mat->columns+deltacols >= mat->columns_alloc) {

    /* Update memory allocation and sizes */
    oldcolsalloc = mat->columns_alloc;
    deltacols = DELTA_SIZE(deltacols, mat->columns);
    SETMAX(deltacols, DELTACOLALLOC);
    mat->columns_alloc += deltacols;
    colsum = mat->columns_alloc + 1;
    status = allocINT(mat->lp, &mat->col_end, colsum, AUTOMATIC);

    /* Update column pointers */
    if(oldcolsalloc == 0)
      mat->col_end[0] = 0;
    for(i = MIN(oldcolsalloc, mat->columns) + 1; i < colsum; i++)
      mat->col_end[i] = mat->col_end[i-1];
    mat->row_end_valid = FALSE;
  }
  return( status );
}

STATIC int mat_collength(MATrec *mat, int colnr)
{
  return( mat->col_end[colnr] - mat->col_end[colnr-1] );
}

STATIC int mat_rowlength(MATrec *mat, int rownr)
{
  if(mat_validate(mat)) {
    if(rownr <= 0)
      return( mat->row_end[0] );
    else
      return( mat->row_end[rownr] - mat->row_end[rownr-1] );
  }
  else
    return( 0 );
}

STATIC int mat_nonzeros(MATrec *mat)
{
  return( mat->col_end[mat->columns] );
}

STATIC MYBOOL mat_indexrange(MATrec *mat, int index, MYBOOL isrow, int *startpos, int *endpos)
{
#ifdef Paranoia
  if(isrow && ((index < 0) || (index > mat->rows)))
    return( FALSE );
  else if(!isrow && ((index < 1) || (index > mat->columns)))
    return( FALSE );
#endif

  if(isrow && mat_validate(mat)) {
    if(index == 0)
      *startpos = 0;
    else
      *startpos = mat->row_end[index-1];
    *endpos = mat->row_end[index];
  }
  else {
    *startpos = mat->col_end[index-1];
    *endpos = mat->col_end[index];
  }
  return( TRUE );
}

STATIC int mat_shiftrows(MATrec *mat, int *bbase, int delta, LLrec *varmap)
{
  int     j, k, i, ii, thisrow, *colend, base;
  MYBOOL  preparecompact = FALSE;
  int     *rownr;

  if(delta == 0)
    return( 0 );
  base = abs(*bbase);

  if(delta > 0) {

    /* Insert row by simply incrementing existing row indeces */
    if(base <= mat->rows) {
      k = mat_nonzeros(mat);
      rownr = &COL_MAT_ROWNR(0);
      for(ii = 0; ii < k; ii++, rownr += matRowColStep) {
        if(*rownr >= base)
          *rownr += delta;
      }
    }

    /* Set defaults (actual basis set in separate procedure) */
    for(i = 0; i < delta; i++) {
      ii = base + i;
      mat->row_end[ii] = 0;
    }
  }
  else if(base <= mat->rows) {

    /* Check for preparation of mass-deletion of rows */
    preparecompact = (MYBOOL) (varmap != NULL);
    if(preparecompact) {
      /* Create the offset array */
      int *newrowidx = NULL;
      allocINT(mat->lp, &newrowidx, mat->rows+1, FALSE);
      newrowidx[0] = 0;
      delta = 0;
      for(j = 1; j <= mat->rows; j++) {
        if(isActiveLink(varmap, j)) {
          delta++;
          newrowidx[j] = delta;
        }
        else
          newrowidx[j] = -1;
      }
      k = 0;
      delta = 0;
      base = mat_nonzeros(mat);
      rownr = &COL_MAT_ROWNR(0);
      for(i = 0; i < base; i++, rownr += matRowColStep) {
        thisrow = newrowidx[*rownr];
        if(thisrow < 0) {
          *rownr = -1;
          delta++;
        }
        else
          *rownr = thisrow;
      }
      FREE(newrowidx);
      return(delta);
    }

    /* Check if we should prepare for compacting later
       (this is in order to speed up multiple row deletions) */
    preparecompact = (MYBOOL) (*bbase < 0);
    if(preparecompact)
      *bbase = my_flipsign((*bbase));

    /* First make sure we don't cross the row count border */
    if(base-delta-1 > mat->rows)
      delta = base - mat->rows - 1;

    /* Then scan over all entries shifting and updating rows indeces */
    if(preparecompact) {
      k = 0;
      for(j = 1, colend = mat->col_end + 1;
          j <= mat->columns; j++, colend++) {
        i = k;
        k = *colend;
        rownr = &COL_MAT_ROWNR(i);
        for(; i < k; i++, rownr += matRowColStep) {
          thisrow = *rownr;
          if(thisrow < base)
            continue;
          else if(thisrow >= base-delta)
            *rownr += delta;
          else
            *rownr = -1;
        }
      }
    }
    else {
      k = 0;
      ii = 0;
      for(j = 1, colend = mat->col_end + 1;
          j <= mat->columns; j++, colend++) {
        i = k;
        k = *colend;
        rownr = &COL_MAT_ROWNR(i);
        for(; i < k; i++, rownr += matRowColStep) {
          thisrow = *rownr;
          if(thisrow >= base) {
            if(thisrow >= base-delta)
              *rownr += delta;
            else
              continue;
          }
          if(ii != i) {
            COL_MAT_COPY(ii, i);
          }
          ii++;
        }
        *colend = ii;
      }
    }
  }
  return( 0 );
}

/* Map-based compacting+insertion of matrix elements without changing row and column indeces.
   When mat2 is NULL, a simple compacting of non-deleted rows and columns is done. */
STATIC int mat_mapreplace(MATrec *mat, LLrec *rowmap, LLrec *colmap, MATrec *mat2)
{
  lprec *lp = mat->lp;
  int   i, ib, ie, ii, j, jj, jb, je, nz, *colend, *rownr, *rownr2, *indirect = NULL;
  REAL  *value, *value2;

  /* Check if there is something to insert */
  if((mat2 != NULL) && ((mat2->col_tag == NULL) || (mat2->col_tag[0] <= 0) || (mat_nonzeros(mat2) == 0)))
    return( 0 );

  /* Create map and sort by increasing index in "mat" */
  if(mat2 != NULL) {
    jj = mat2->col_tag[0];
    allocINT(lp, &indirect, jj+1, FALSE);
    indirect[0] = jj;
    for(i = 1; i <= jj; i++)
      indirect[i] = i;
    hpsortex(mat2->col_tag, jj, 1, sizeof(*indirect), FALSE, compareINT, indirect);
  }

  /* Do the compacting */
  mat->row_end_valid = FALSE;
  nz = mat->col_end[mat->columns];
  ie = 0;
  ii = 0;
  if((mat2 == NULL) || (indirect[0] == 0)) {
    je = mat->columns + 1;
    jj = 1;
    jb = 0;
  }
  else {
    je = indirect[0];
    jj = 0;
    do {
      jj++;
      jb = mat2->col_tag[jj];
    } while(jb <= 0);

  }
  for(j = 1, colend = mat->col_end + 1;
      j <= mat->columns; j++, colend++) {
    ib = ie;
    ie = *colend;

    /* Always skip (condense) replacement columns */
    if(j == jb) {
      jj++;
      if(jj <= je)
        jb = mat2->col_tag[jj];
      else
        jb = mat->columns + 1;
    }

    /* Only include active columns */
    else if(isActiveLink(colmap, j)) {
      rownr = &COL_MAT_ROWNR(ib);
      for(; ib < ie; ib++, rownr += matRowColStep) {

        /* Also make sure the row is active */
        if(isActiveLink(rowmap, *rownr)) {
          if(ii != ib) {
            COL_MAT_COPY(ii, ib);
          }
          ii++;
        }
      }
    }
    *colend = ii;
  }
  if(mat2 == NULL)
    goto Finish;

  /* Tally non-zero insertions */
  i = 0;
  for(j = 1; j <= mat2->col_tag[0]; j++) {
    jj = mat2->col_tag[j];
    if((jj > 0) && isActiveLink(colmap, jj)) {
      jj = indirect[j];
      je = mat2->col_end[jj];
      jb = mat2->col_end[jj-1];
      rownr2 = &COL_MAT2_ROWNR(jb);
      for(; jb < je; jb++, rownr2 += matRowColStep) {
        if((*rownr2 > 0) && isActiveLink(rowmap, *rownr2))
          i++;
      }
    }
  }

  /* Make sure we have enough matrix space */
  ii = mat->col_end[mat->columns] + i;
  if(mat->mat_alloc <= ii)
    inc_mat_space(mat, i);

  /* Do shifting and insertion - loop from the end going forward */
  jj = indirect[0];
  jj = mat2->col_tag[jj];
  for(j = mat->columns, colend = mat->col_end + mat->columns, ib = *colend;
      j > 0; j--) {

    /* Update indeces for this loop */
    ie = ib;
    *colend = ii;
    colend--;
    ib = *colend;

    /* Insert new values */
    if(j == jj) {
      /* Only include an active column */
      if(isActiveLink(colmap, j)) {
        jj = indirect[0];
        jj = indirect[jj];
        rownr = &COL_MAT_ROWNR(ii-1);
        value = &COL_MAT_VALUE(ii-1);
        jb = mat2->col_end[jj-1];
        je = mat2->col_end[jj] - 1;
        rownr2 = &COL_MAT2_ROWNR(je);
        value2 = &COL_MAT2_VALUE(je);

        /* Process constraint coefficients */
        for(; je >= jb; je--, rownr2 -= matRowColStep, value2 -= matValueStep) {
          i = *rownr2;
          if(i == 0) {
            i = -1;
            break;
          }
          else if(isActiveLink(rowmap, i)) {
            ii--;
            *rownr = i;
            rownr -= matRowColStep;
            *value = my_chsign(is_chsign(lp, i), *value2);
            value -= matValueStep;
          }
        }

        /* Then handle the objective */
        if(i == -1) {
          lp->orig_obj[j] = my_chsign(is_maxim(lp), *value2);
          rownr2 -= matRowColStep;
          value2 -= matValueStep;
        }
        else
          lp->orig_obj[j] = 0;

      }
      /* Update replacement column index or break if no more candidates */
      jj = --indirect[0];
      if(jj == 0)
        break;
      jj = mat2->col_tag[jj];
      if(jj <= 0)
        break;
    }
    /* Shift existing values down */
    else {
      if(isActiveLink(colmap, j))
      while(ie > ib) {
        ii--;
        ie--;
        if(ie != ii) {
          COL_MAT_COPY(ii, ie);
        }
      }
    }
  }

  /* Return the delta number of non-zero elements */
Finish:
  nz -= mat->col_end[mat->columns];
  FREE(indirect);

  return( nz );
}

/* Routines to compact rows in matrix based on precoded entries */
STATIC int mat_zerocompact(MATrec *mat)
{
  return( mat_rowcompact(mat, TRUE) );
}
STATIC int mat_rowcompact(MATrec *mat, MYBOOL dozeros)
{
  int  i, ie, ii, j, nn, *colend, *rownr;
  REAL *value;

  nn = 0;
  ie = 0;
  ii = 0;
  for(j = 1, colend = mat->col_end + 1;
      j <= mat->columns; j++, colend++) {
    i = ie;
    ie = *colend;
    rownr = &COL_MAT_ROWNR(i);
    value = &COL_MAT_VALUE(i);
    for(; i < ie;
        i++, rownr += matRowColStep, value += matValueStep) {
      if((*rownr < 0) || (dozeros && (fabs(*value) < mat->epsvalue))) {
        nn++;
        continue;
      }
      if(ii != i) {
        COL_MAT_COPY(ii, i);
      }
      ii++;
    }
    *colend = ii;
  }
  return( nn );
}

/* Routines to compact columns and their indeces based on precoded entries */
STATIC int mat_colcompact(MATrec *mat, int prev_rows, int prev_cols)
{
  int             i, ii, j, k, n_del, n_sum, *colend, *newcolend, *colnr, newcolnr;
  MYBOOL          deleted;
  lprec           *lp = mat->lp;
  presolveundorec *lpundo = lp->presolve_undo;


  n_sum = 0;
  k  = 0;
  ii = 0;
  newcolnr = 1;
  for(j = 1, colend = newcolend = mat->col_end + 1;
      j <= prev_cols; j++, colend++) {
    n_del = 0;
    i = k;
    k = *colend;
    for(colnr = &COL_MAT_COLNR(i); i < k;
        i++, colnr += matRowColStep) {
      if(*colnr < 0) {
        n_del++;
        n_sum++;
        continue;
      }
      if(ii < i) {
        COL_MAT_COPY(ii, i);
      }
      if(newcolnr < j) {
        COL_MAT_COLNR(ii) = newcolnr;
      }
      ii++;
    }
    *newcolend = ii;

    deleted = (MYBOOL) (n_del > 0);
#if 1
    /* Do hoops in case there was an empty column */
    deleted |= (MYBOOL) (!lp->wasPresolved && (lpundo->var_to_orig[prev_rows+j] < 0));

#endif
    /* Increment column variables if current column was not deleted */
    if(!deleted) {
      newcolend++;
      newcolnr++;
    }
  }
  return(n_sum);
}

STATIC int mat_shiftcols(MATrec *mat, int *bbase, int delta, LLrec *varmap)
{
  int     i, ii, k, n, base;


  k = 0;
  if(delta == 0)
    return( k );
  base = abs(*bbase);

  if(delta > 0) {
    /* Shift pointers right */
    for(ii = mat->columns; ii > base; ii--) {
      i = ii + delta;
      mat->col_end[i] = mat->col_end[ii];
    }
    /* Set defaults */
    for(i = 0; i < delta; i++) {
      ii = base + i;
      mat->col_end[ii] = mat->col_end[ii-1];
    }
  }
  else {

    /* Check for preparation of mass-deletion of columns */
    MYBOOL preparecompact = (MYBOOL) (varmap != NULL);
    if(preparecompact) {
      /* Create the offset array */
      int j, *colnr, *colend;
      n = 0;
      k = 0;
      base = 0;
      for(j = 1, colend = mat->col_end + 1;
          j <= mat->columns; j++, colend++) {
        i = k;
        k = *colend;
        if(isActiveLink(varmap, j)) {
          base++;
          ii = base;
        }
        else
          ii = -1;
        if(ii < 0)
          n += k - i;
        colnr = &COL_MAT_COLNR(i);
        for(; i < k; i++, colnr += matRowColStep)
          *colnr = ii;
      }
      return(n);
    }

    /* Check if we should prepare for compacting later
       (this is in order to speed up multiple column deletions) */
    preparecompact = (MYBOOL) (*bbase < 0);
    if(preparecompact)
      *bbase = my_flipsign((*bbase));

    /* First make sure we don't cross the column count border */
    if(base-delta-1 > mat->columns)
      delta = base - mat->columns - 1;

    /* Then scan over all entries shifting and updating column indeces */
    if(preparecompact) {
      int *colnr;
      n = 0;
      i = mat->col_end[base-1];
      k = mat->col_end[base-delta-1];
      for(colnr = &COL_MAT_COLNR(i); i < k;
          i++, colnr += matRowColStep) {
        n++;
        *colnr = -1;
      }
      k = n;
    }
    else {
      /* Delete sparse matrix data, if required */
      if(base <= mat->columns) {

        i = mat->col_end[base-1];          /* Beginning of data to be deleted */
        ii = mat->col_end[base-delta-1];   /* Beginning of data to be shifted left */
        n = mat_nonzeros(mat);             /* Total number of non-zeros */
        k = ii-i;                          /* Number of entries to be deleted */
        if((k > 0) && (n > i)) {
          n -= ii;
          COL_MAT_MOVE(i, ii, n);
        }

        /* Update indexes */
        for(i = base; i <= mat->columns + delta; i++) {
          ii = i - delta;
          mat->col_end[i] = mat->col_end[ii] - k;
        }
      }
    }
  }
  return( k );
}

STATIC MATrec *mat_extractmat(MATrec *mat, LLrec *rowmap, LLrec *colmap, MYBOOL negated)
{
  int    *rownr, *colnr, xa, na;
  REAL   *value;
  MATrec *newmat = mat_create(mat->lp, mat->rows, mat->columns, mat->epsvalue);

  /* Initialize */
  na = mat_nonzeros(mat);
  rownr = &COL_MAT_ROWNR(0);
  colnr = &COL_MAT_COLNR(0);
  value = &COL_MAT_VALUE(0);

  /* Loop over the indeces, picking out values in qualifying rows and colums
     (note that the loop could be speeded up for dense matrices by making an
      outer loop for columns and inner loop for rows) */
  for(xa = 0; xa < na; xa++, rownr += matRowColStep, colnr += matRowColStep, value += matValueStep) {
    if((isActiveLink(colmap, *colnr) ^ negated) &&
       (isActiveLink(rowmap, *rownr) ^ negated))
      mat_setvalue(newmat, *rownr, *colnr, *value, FALSE);
  }

  /* Return the populated new matrix */
  return( newmat );
}

STATIC MYBOOL mat_setcol(MATrec *mat, int colno, int count, REAL *column, int *rowno, MYBOOL doscale, MYBOOL checkrowmode)
{
  int    i, jj = 0, elmnr, orignr, newnr, firstrow;
  MYBOOL *addto = NULL, isA, isNZ;
  REAL   value, saved = 0;
  lprec  *lp = mat->lp;

  /* Check if we are in row order mode and should add as row instead;
     the matrix will be transposed at a later stage */
  if(checkrowmode && mat->is_roworder)
    return( mat_setrow(mat, colno, count, column, rowno, doscale, FALSE) );

  /* Initialize and validate */
  isA = (MYBOOL) (mat == mat->lp->matA);
  isNZ = (MYBOOL) (rowno != NULL);
  if(!isNZ)
    count = mat->lp->rows;
  else if((count < 0) || (count > mat->rows+((mat->is_roworder) ? 0 : 1)))
    return( FALSE );
  if(isNZ && (count > 0)) {
    if(count > 1)
      sortREALByINT(column, rowno, count, 0, TRUE);
    if((rowno[0] < 0) || (rowno[count-1] > mat->rows))
      return( FALSE );
  }

  /* Capture OF definition in column mode */
  if(isA && !mat->is_roworder) {
    if(isNZ && (count > 0) && (rowno[0] == 0)) {
      value = column[0];
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      if(doscale)
        value = scaled_mat(lp, value, 0, colno);
      value = my_chsign(is_maxim(lp), value);
      lp->orig_obj[colno] = value;
      count--;
      column++;
      rowno++;
    }
    else if(!isNZ && (column[0] != 0)) {
      value = saved = column[0];
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      if(doscale)
        value = scaled_mat(lp, value, 0, colno);
      value = my_chsign(is_maxim(lp), value);
      lp->orig_obj[colno] = value;
      column[0] = 0;
    }
    else
      lp->orig_obj[colno] = 0;
  }

  /* Optionally tally and map the new non-zero values */
  firstrow = mat->rows + 1;
  if(isNZ) {
    newnr = count;
    if(newnr) {
      firstrow = rowno[0];
      jj = rowno[newnr - 1];
    }
  }
  else {
    newnr = 0;
    if(!allocMYBOOL(lp, &addto, mat->rows + 1, TRUE)) {
      return( FALSE );
    }
    for(i = mat->rows; i >= 0; i--) {
      if(fabs(column[i]) > mat->epsvalue) {
        addto[i] = TRUE;
        firstrow = i;
        newnr++;
      }
    }
  }

  /* Make sure we have enough matrix space */
  if(!inc_mat_space(mat, newnr)) {
    newnr = 0;
    goto Done;
  }

  /* Shift existing column data and adjust position indeces */
  orignr = mat_collength(mat, colno);
  elmnr = newnr - orignr;
  i = mat_nonzeros(mat) - mat->col_end[colno];
  if((elmnr != 0) && (i > 0)) {
    COL_MAT_MOVE(mat->col_end[colno] + elmnr, mat->col_end[colno], i);
  }
  if(elmnr != 0)
    for(i = colno; i <= mat->columns; i++)
      mat->col_end[i] += elmnr;

  /* We are now ready to copy the new data */
  jj = mat->col_end[colno-1];
  if(isNZ) {
    for(i = 0; i < count; jj++, i++) {
      value = column[i];
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      if(mat->is_roworder) {    /* Fix following Ingmar Stein bug report 12.10.2006 */
        if(isA && doscale)
          value = scaled_mat(lp, value, colno, rowno[i]);
        if(isA)
          value = my_chsign(is_chsign(lp, colno), value);
      }
      else {
        if(isA && doscale)
          value = scaled_mat(lp, value, rowno[i], colno);
        if(isA)
          value = my_chsign(is_chsign(lp, rowno[i]), value);
      }
      SET_MAT_ijA(jj, rowno[i], colno, value);
    }
  }
  else {
    for(i = firstrow; i <= mat->rows; i++) {
      if(!addto[i])
        continue;
      value = column[i];
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      if(mat->is_roworder) {    /* Fix following Ingmar Stein bug report 12.10.2006 */
        if(isA && doscale)
          value = scaled_mat(lp, value, colno, i);
        if(isA)
          value = my_chsign(is_chsign(lp, colno), value);
      }
      else {
        if(isA && doscale)
          value = scaled_mat(lp, value, i, colno);
        if(isA)
          value = my_chsign(is_chsign(lp, i), value);
      }
      SET_MAT_ijA(jj, i, colno, value);
      jj++;
    }
  }
  mat->row_end_valid = FALSE;

  /* Finish and return */
Done:
  if(saved != 0)
    column[0] = saved;
  FREE(addto);
  return( TRUE );

}

STATIC MYBOOL mat_mergemat(MATrec *target, MATrec *source, MYBOOL usecolmap)
{
  lprec *lp = target->lp;
  int   i, ix, iy, n, *colmap = NULL;
  REAL  *colvalue = NULL;

  if((target->rows < source->rows) || !allocREAL(lp, &colvalue, target->rows+1, FALSE))
    return( FALSE );

  if(usecolmap) {
    n = source->col_tag[0];
    allocINT(lp, &colmap, n+1, FALSE);
    for(i = 1; i <= n; i++)
      colmap[i] = i;
    hpsortex(source->col_tag, n, 1, sizeof(*colmap), FALSE, compareINT, colmap);
  }
  else
    n = source->columns;
  for(i = 1; i <= n; i++) {
    if(!usecolmap && (mat_collength(source, i) == 0))
      continue;
    if(usecolmap) {
      ix = colmap[i];
      if(ix <= 0)
        continue;
      iy = source->col_tag[i];
      if(iy <= 0)
        continue;
    }
    else
      ix = iy = i;
    mat_expandcolumn(source, ix, colvalue, NULL, FALSE);
    mat_setcol(target, iy, 0, colvalue, NULL, FALSE, FALSE);
  }

  FREE( colvalue );
  FREE( colmap );

  return( TRUE );
}

STATIC int mat_nz_unused(MATrec *mat)
{
  return( mat->mat_alloc - mat->col_end[mat->columns] );
}

#if 0
STATIC MYBOOL mat_setrow(MATrec *mat, int rowno, int count, REAL *row, int *colno, MYBOOL doscale, MYBOOL checkrowmode)
{
  lprec   *lp = mat->lp;
  int     delta;
  int k, kk, i, ii, j, jj = 0, jj_j, elmnr, orignr, newnr, firstcol, rownr, colnr, matz = 0;
  MYBOOL  *addto = NULL, isA, isNZ;
  REAL    value = 0.0, saved = 0;

  /* Check if we are in row order mode and should add as column instead;
     the matrix will be transposed at a later stage */
  if(checkrowmode && mat->is_roworder)
    return( mat_setcol(mat, rowno, count, row, colno, doscale, FALSE) );

  /* Do initialization and validation */
  if(!mat_validate(mat))
    return( FALSE );
  isA = (MYBOOL) (mat == lp->matA);
  isNZ = (MYBOOL) (colno != NULL);
  if(!isNZ)
    count = mat->columns;
  else if((count < 0) || (count > mat->columns))
    return( FALSE );
  if(isNZ && (count > 0)) {
    if(count > 1)
      sortREALByINT(row, (int *) colno, count, 0, TRUE);
    if((colno[0] < 1) || (colno[count-1] > mat->columns))
      return( FALSE );
  }

  /* Capture OF definition in row mode */
  if(isA && mat->is_roworder) {
    lp->orig_obj[rowno] = 0;
    if(isNZ && (count > 0) && (colno[0] == 0)) {
      value = row[0];
      if(doscale)
        value = scaled_mat(lp, value, 0, rowno);
      value = my_chsign(is_maxim(lp), value);
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      lp->orig_obj[rowno] = value;
      count--;
      row++;
      colno++;
    }
    else if(!isNZ && (row[0] != 0)) {
      value = saved = row[0];
      if(doscale)
        value = scaled_mat(lp, value, 0, rowno);
      value = my_chsign(is_maxim(lp), value);
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      lp->orig_obj[rowno] = value;
      row[0] = 0;
    }
    else {
      lp->orig_obj[rowno] = 0;
      value = 0;
    }
  }

  /* Optionally tally and map the new non-zero values */
  i  = mat->row_end[rowno-1];
  ii = mat->row_end[rowno];     // ****** KE 20070106 - was "-1"
  firstcol = mat->columns + 1;
  if(isNZ) {
    /* See if we can do fast in-place replacements of leading items */
    colnr = 1; /* initialise in case of an empty row */
    while((i < ii) /* && (count > 0) */ && ((colnr = ROW_MAT_COLNR(i)) == *colno) && (count > 0)) {
      value = *row;             // ****** KE 20080111 - Added line
      if(mat->is_roworder) {
        if(isA && doscale)
          value = scaled_mat(lp, value, colnr, rowno);
        if(isA)
          value = my_chsign(is_chsign(lp, colnr), value);
      }
      else {
        if(isA && doscale)
          value = scaled_mat(lp, value, rowno, colnr);
        if(isA)
          value = my_chsign(is_chsign(lp, rowno), value);
      }
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
      if(value == 0)
        matz++;
#endif
      ROW_MAT_VALUE(i) = value;
      i++;
      count--;
      row++;
      colno++;
    }
  if(i >= ii)
      colnr = 0;
    /* Proceed with remaining entries */
    newnr = count;
    if(newnr > 0)
      firstcol = colno[0];
  }
  else {
    newnr = 0;
    kk = mat->columns;
    if(i < ii)
      colnr = ROW_MAT_COLNR(i);
    else
      colnr = 0;
    for(k = 1; k <= kk; k++) {
      value = row[k];           // ****** KE 20080111 - Added line
      if(fabs(value) > mat->epsvalue) {
        /* See if we can do fast in-place replacements of leading items */
        if((addto == NULL) && (i < ii) && (colnr == k)) {
          if(mat->is_roworder) {
            if(isA && doscale)
              value = scaled_mat(lp, value, colnr, rowno);
            if(isA)
              value = my_chsign(is_chsign(lp, colnr), value);
          }
          else {
            if(isA && doscale)
              value = scaled_mat(lp, value, rowno, colnr);
            if(isA)
              value = my_chsign(is_chsign(lp, rowno), value);
          }
#ifdef DoMatrixRounding
          value = roundToPrecision(value, mat->epsvalue);
          if(value == 0)
            matz++;
#endif
          ROW_MAT_VALUE(i) = value;
          i++;
          if(i < ii)
            colnr = ROW_MAT_COLNR(i);
          else
            colnr = 0;
        }
        /* Otherwise update addto-list */
        else {
          if(addto == NULL) {
            if(!allocMYBOOL(lp, &addto, mat->columns + 1, TRUE))
              return( FALSE );
            firstcol = k;
          }
          addto[k] = TRUE;
          newnr++;
        }
      }
    }
  }
  if(newnr == 0)
   if (FALSE)
    return( TRUE );

  /* Make sure we have enough matrix space */
  if((newnr > 0) && (mat_nz_unused(mat) <= newnr) && !inc_mat_space(mat, newnr)) {
    newnr = 0;
    goto Done;
  }

  /* Pack initial entries if existing row data has a lower column
     start index than the first index of the new vector */
  orignr = mat_nonzeros(mat);
  /* delta = newnr - mat_rowlength(mat, rowno);*/
  kk = 0;
  if(rowno == 0)
    ii = 0;
  else
    ii = mat->row_end[rowno-1];

  if((orignr == 0) || (ii >= orignr))
    j = firstcol;
  else if(isNZ||TRUE)
    j = colnr;
  else
    j = ROW_MAT_COLNR(ii); /* first column with a value on that row */

  jj = mat->col_end[firstcol-1];  /* Set the index of the insertion point for the first new value */
  if(jj >= orignr)
    colnr = firstcol;
  else
    colnr = COL_MAT_COLNR(jj); /* first column with a value starting from firstcol */

  if((j > 0) && (j < colnr)) {
    jj = elmnr = mat->col_end[j-1];
    for( ; j < colnr; j++) {
      /* Shift entries in current column */
      k = mat->col_end[j];
      for( ; jj < k; jj++) {
        if(COL_MAT_ROWNR(jj) != rowno) {
          COL_MAT_COPY(elmnr, jj);
          elmnr++;
        }
      }
      /* Update next column start index */
      mat->col_end[j] = elmnr;
    }
    delta = elmnr - jj;  /* The shrinkage count */
  }
  else {
    delta = 0;
    /* Adjust for case where we simply append values - jj is initially the first column item */
    if((mat->col_end[firstcol] == orignr) && 0)
      jj = orignr;
  }

  /* Make sure we have sufficient space for any additional entries and move existing data down;
     this ensures that we only have to relocate matrix elements up in the next stage */
  jj_j = MAX(0, newnr + delta);
  if(jj_j > 0) {
    if(!inc_mat_space(mat, jj_j)) {
      FREE(addto);
      return( FALSE );
    }
    if(orignr-jj > 0) {
      COL_MAT_MOVE(jj+jj_j, jj, orignr-jj);
    }
    jj += jj_j;
  }

  /* Handle case where the matrix was empty before (or we can simply append) */
  if((delta >= 0) && (mat->col_end[firstcol] == orignr) && 0) {
    if(isNZ)
      elmnr = count;
    else
      elmnr = mat->columns;
    jj_j = mat->col_end[firstcol];
    for(newnr = 0; newnr < elmnr; newnr++) {
      if(isNZ)
        colnr = colno[newnr];
      else
        colnr = newnr + 1;
      /* Update column start position if we have crossed a column */
      while(colnr > firstcol) {
        mat->col_end[firstcol] = jj_j;
        firstcol++;
      }
      if(isNZ || ((addto != NULL) && addto[colnr])) {
        if(isNZ)
          value = row[newnr];
        else
          value = row[colnr];
        if(isA && doscale)
          value = scaled_mat(lp, value, rowno, colnr);
        if(isA)
          value = my_chsign(is_chsign(lp, rowno), value);
#ifdef DoMatrixRounding
        value = roundToPrecision(value, mat->epsvalue);
        if(value == 0)
          matz++;
#endif
        SET_MAT_ijA(jj_j, rowno, colnr, value);
        jj_j++;
        /* Update last column start position */
        mat->col_end[firstcol] = jj_j;
        firstcol++;
      }
    }

    /* Make sure we update tail empty column offsets */
    while(firstcol <= mat->columns) {
      mat->col_end[firstcol] = jj_j;
      firstcol++;
    }
    jj_j = 0;
  }

  /* Start from the top of the first non-zero column of the new row */
  elmnr = orignr + jj_j;
  if(jj <= elmnr) {
    if(isNZ)
      newnr = 0;
    else
      newnr = firstcol - 1;
    j = jj - mat->col_end[firstcol-1];
    colnr = firstcol;
    while((jj < elmnr) || (newnr < count)) {

      /* Update column start position if we have crossed a column */
      while(colnr > firstcol) {
        mat->col_end[firstcol] = kk;
        firstcol++;
      }

      /* See if we have a row equal to or greater than the target row */
      jj_j = jj - j;
      if(jj < elmnr) {
        rownr = COL_MAT_ROWNR(jj);
        colnr = COL_MAT_COLNR(jj);
      }
      else {
        rownr = rowno;
        if(!isNZ)                              /* KE added this conditional on 13.9.2006 */
          colnr = firstcol + 1;
        else
          colnr = mat->columns + 1;
      }

      if(isNZ) {
        if(newnr < count)
          kk = colno[newnr];
        else
          kk = mat->columns + 1;
      }
      else
        kk = newnr + 1;

      /* Test if there is an available new item ... */
      if((isNZ && (kk > colnr)) ||                    /* If this is not the case */
         (!isNZ && ((kk > colnr) || (!addto[kk])))) {
        /* DELETE if there is an existing value */
        if(!isNZ && (kk <= colnr))
          newnr++;
        if(rownr == rowno) {
          kk = jj_j;
          j++;
          jj++;
          continue;
        }
        /* KEEP otherwise and move entry up */
        if(!isNZ && (colnr > kk)) {
          colnr = kk;
          kk = jj_j;
          continue;
        }
      }
      else if((colnr > kk) ||                         /* Existing column index > new => INSERT */
              ((colnr == kk) && (rownr >= rowno)) ) { /* Same column index, existing row >= target row => INSERT/REPLACE */

        if(isNZ)
          value = row[newnr];
        else
          value = row[newnr+1];
        newnr++;
        if(isA && doscale)
          value = scaled_mat(lp, value, rowno, kk);
        if(isA)
          value = my_chsign(is_chsign(lp, rowno), value);
#ifdef DoMatrixRounding
        value = roundToPrecision(value, mat->epsvalue);
        if(value == 0)
          matz++;
#endif
        SET_MAT_ijA(jj_j, rowno, kk, value);

        /* Adjust if we have inserted an element */
        if((colnr > kk) || (rownr > rowno)) {
          j--;
          jj--;
        }
        colnr = kk;
        kk = jj_j;
        jj++;
        continue;
      }

      /* Shift the matrix element up by the active difference */
      if(jj_j != jj) {
        COL_MAT_COPY(jj_j, jj);
      }
      kk = jj_j;
      jj++;

    }

    /* Update pending / incomplete column start position */
    while(colnr > firstcol) {
      mat->col_end[firstcol] = kk;
      firstcol++;
    }

    /* Make sure we update tail column offsets */
    jj_j = jj - j;
    while(firstcol <= mat->columns) {
      mat->col_end[firstcol] = jj_j;
      firstcol++;
    }
  }

  /* Compact in the case that we added zeros and set flag for row index update */
  if(matz > 0)
    mat_zerocompact(mat);
  mat->row_end_valid = FALSE;

Done:
  if(saved != 0)
    row[0] = saved;
  FREE(addto);
  return( (MYBOOL) (newnr > 0) );

}

#else

STATIC MYBOOL mat_setrow(MATrec *mat, int rowno, int count, REAL *row, int *colno, MYBOOL doscale, MYBOOL checkrowmode)
{
  lprec   *lp = mat->lp;
  int     delta, delta1;
  int k, i, ii, j, jj_j, lendense,
          origidx = 0, newidx, orignz, newnz,
          rownr, colnr, colnr1;
  MYBOOL  isA, isNZ;
  REAL    value = 0.0;

  /* Check if we are in row order mode and should add as column instead;
     the matrix will be transposed at a later stage */
  if(checkrowmode && mat->is_roworder)
    return( mat_setcol(mat, rowno, count, row, colno, doscale, FALSE) );

  /* Do initialization and validation */
  if(!mat_validate(mat))
    return( FALSE );
  isA = (MYBOOL) (mat == lp->matA);
  if(doscale && isA && !lp->scaling_used)
    doscale = FALSE;
  isNZ = (MYBOOL) (colno != NULL);
  lendense = (mat->is_roworder ? lp->rows : lp->columns);
  if((count < 0) || (count > lendense))
    return( FALSE );
  colnr1 = lendense + 1;

  /* Capture OF definition in row mode */
  if(isA && mat->is_roworder) {
    lp->orig_obj[rowno] = 0;
    if((count > 0) && (colno[0] == 0)) {
      value = row[0];
      if(doscale)
        value = scaled_mat(lp, value, 0, rowno);
      value = my_chsign(is_maxim(lp), value);
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      lp->orig_obj[rowno] = value;
      if(isNZ) {
        colno++;
        row++;
        count--;
      }
    }
    else {
      lp->orig_obj[rowno] = 0;
      value = 0;
    }
  }

  /* Make local working data copies */
  if(!isNZ) {
    REAL *tmprow = NULL;
    if(!allocINT(lp, &colno, lendense+1, FALSE))
      return( FALSE );
    newnz = 0;
    for(i = 1; i <= lendense; i++)
      if((value = row[i]) != 0) {
        if((tmprow == NULL) && !allocREAL(lp, &tmprow, lendense-i+1, FALSE)) {
          FREE(colno);
          return( FALSE );
        }
        tmprow[newnz] = value;
        colno[newnz++] = i;
      }
    count = newnz;
    row = tmprow;
  }
  else {
    int *tmpcolno = NULL;
    if(!allocINT(lp, &tmpcolno, lendense, FALSE))
      return( FALSE );
    newnz = count;
    MEMCOPY(tmpcolno, colno, newnz);
    colno = tmpcolno;
    if(newnz > 1)
      sortREALByINT(row, (int *) colno, newnz, 0, TRUE);
    if((newnz > 0) && ((colno[0] < 0) || (colno[newnz-1] > lendense))) {
      FREE(colno);
      newnz = 0;
      return( FALSE );
    }
  }

  /* Make sure we have enough matrix space */
  i  = mat->row_end[rowno-1];
  ii = mat->row_end[rowno];
  delta1 = delta = count - (ii-i);
  colnr1 = (newnz > 0 ? colno[0] : lendense+1);

  /* Pack initial entries if existing row data has a lower column
     start index than the first index of the new vector */
  orignz = mat_nonzeros(mat);
  j = (i >= orignz ? colnr1 : ROW_MAT_COLNR(i));

  /* Index of the column-top insertion point for the first new value */
  origidx = mat->col_end[colnr1-1];
  colnr = (origidx >= orignz ? colnr1 : COL_MAT_COLNR(origidx));

  if(j < colnr) {
    origidx = newidx = mat->col_end[j-1];
    for( ; j < colnr; j++) {
      /* Shift entries in current column */
      jj_j = mat->col_end[j];
      for( ; origidx < jj_j; origidx++) {
        if(COL_MAT_ROWNR(origidx) != rowno) {
          if(newidx != origidx) {
            COL_MAT_COPY(newidx, origidx);
          }
          newidx++;
        }
      }
      /* Update next column start index */
      mat->col_end[j] = newidx;
    }
    delta = newidx - origidx;  /* The first stage element shrinkage count */
  }
  else {
    delta = 0;
    newidx = origidx;
  }

  /* Make sure we have sufficient space for any additional entries and move existing data down;
     this ensures that we only have to relocate matrix elements up in the next stage */
  jj_j = MAX(0, (int) newnz + delta);

  j = !((orignz == lendense) && (newnz == orignz) && (delta1 == 0)) && (jj_j > 0) && (orignz > origidx);

  if ((j) && (jj_j > delta1))
    delta1 = jj_j;

  if((delta1 > 0) && (mat_nz_unused(mat) <= delta1) && !inc_mat_space(mat, delta1)) {
    newnz = 0;
    goto Done;
  }

  if(j) {
    COL_MAT_MOVE(origidx+jj_j, origidx, orignz-origidx);
    origidx += jj_j;
    orignz += jj_j;
  }

  /* Start from the top of the first non-zero column of the new row */
  newnz = 0;
  j = origidx - mat->col_end[colnr1-1];
  k = colnr1;  /* Last column for which col_end is valid/updated */
  while((colnr1 <= lendense) || (origidx < orignz)) {

    /* Get the column index of the active update item */
    if(newnz < count)
      colnr1 = colno[newnz];
    else
      colnr1 = lendense + 1;

    /* Get coordinate of active existing matrix entries */
    if(origidx < orignz) {
      rownr = COL_MAT_ROWNR(origidx);
      colnr = COL_MAT_COLNR(origidx);
    }
    else {
      if(colnr1 > lendense)
        break;
      rownr = rowno;
      colnr = lendense + 1;
    }

    /* Update column start position if we just crossed into a column */
    jj_j = origidx - j;
    i = MIN(colnr, colnr1);
    for(; k < i; k++)
      mat->col_end[k] = jj_j;

    /* Test if there is an available new item ... */
    if(colnr1 > colnr) {             /* If this is not the case */
      /* DELETE if there is an existing value */
      if(rownr == rowno) {
ForceDelete:
        j++;
        delta--;
        origidx++;
        continue;
      }
    }
    else if((colnr > colnr1) ||                         /* Existing column index > new => INSERT */
            ((colnr == colnr1) && (rownr >= rowno)) ) { /* Same column index, existing row >= target row => INSERT/REPLACE */

      value = row[newnz];
      newnz++;
      if(isA && doscale)
        value = scaled_mat(lp, value, rowno, colnr1);
      if(isA)
        value = my_chsign(is_chsign(lp, rowno), value);
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
      if(value == 0) {
        if((colnr > colnr1) || (rownr > rowno))
        ;
        else
          goto ForceDelete;
      }
#endif
      SET_MAT_ijA(jj_j, rowno, colnr1, value);

      /* Adjust if we have inserted an element */
      if((colnr > colnr1) || (rownr > rowno)) {
        j--;
        origidx--;
        jj_j++;
        delta++;
      }
      origidx++;
      continue;
    }

    /* Shift the matrix element up by the active difference */
    if(jj_j != origidx) {
      COL_MAT_COPY(jj_j, origidx);
    }
    origidx++;
  }

  /* Update pending / incomplete column start position */
  jj_j = origidx - j;
  for(; k <= lendense; k++)
    mat->col_end[k] = jj_j;
  mat->row_end_valid = FALSE;

Done:
  if(!isNZ)
    FREE(row);
  FREE(colno);
  return( (MYBOOL) (newnz > 0) );

} /* mat_setrow */
#endif

STATIC int mat_appendrow(MATrec *mat, int count, REAL *row, int *colno, REAL mult, MYBOOL checkrowmode)
{
  int    i, j, jj = 0, stcol, elmnr, orignr, newnr, firstcol;
  MYBOOL *addto = NULL, isA, isNZ;
  REAL   value, saved = 0;
  lprec  *lp = mat->lp;

  /* Check if we are in row order mode and should add as column instead;
     the matrix will be transposed at a later stage */
  if(checkrowmode && mat->is_roworder)
    return( mat_appendcol(mat, count, row, colno, mult, FALSE) );

  /* Do initialization and validation */
  isA = (MYBOOL) (mat == lp->matA);
  isNZ = (MYBOOL) (colno != NULL);
  if(isNZ && (count > 0)) {
    if(count > 1)
      sortREALByINT(row, colno, count, 0, TRUE);
    if((colno[0] < 1) || (colno[count-1] > mat->columns))
      return( 0 );
  }
  /* else if((row != NULL) && !mat->is_roworder) */
  else if(!isNZ && (row != NULL) && !mat->is_roworder)
    row[0] = 0;

  /* Capture OF definition in row mode */
  if(isA && mat->is_roworder) {
    if(isNZ && (colno[0] == 0)) {
      value = row[0];
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      value = scaled_mat(lp, value, 0, lp->columns);
      value = my_chsign(is_maxim(lp), value);
      lp->orig_obj[lp->columns] = value;
      count--;
      row++;
      colno++;
    }
    else if(!isNZ && (row != NULL) && (row[0] != 0)) {
      value = saved = row[0];
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      value = scaled_mat(lp, value, 0, lp->columns);
      value = my_chsign(is_maxim(lp), value);
      lp->orig_obj[lp->columns] = value;
      row[0] = 0;
    }
    else
      lp->orig_obj[lp->columns] = 0;
  }

  /* Optionally tally and map the new non-zero values */
  firstcol = mat->columns + 1;
  if(isNZ) {
    newnr = count;
    if(newnr) {
      firstcol = colno[0];
      jj = colno[newnr - 1];
    }
  }
  else {
    newnr = 0;
    if(row != NULL) {
      if(!allocMYBOOL(lp, &addto, mat->columns + 1, TRUE)) {
        return( newnr );
      }
      for(i = mat->columns; i >= 1; i--) {
        if(fabs(row[i]) > mat->epsvalue) {
          addto[i] = TRUE;
          firstcol = i;
          newnr++;
        }
      }
    }
  }

  /* Make sure we have sufficient space */
  if(!inc_mat_space(mat, newnr)) {
    newnr = 0;
    goto Done;
  }

  /* Insert the non-zero constraint values */
  orignr = mat_nonzeros(mat) - 1;
  elmnr = orignr + newnr;

  for(j = mat->columns; j >= firstcol; j--) {
    stcol = mat->col_end[j] - 1;
    mat->col_end[j] = elmnr + 1;

   /* Add a new non-zero entry */
    if(((isNZ) && (j == jj)) || ((addto != NULL) && (addto[j]))) {
      newnr--;
      if(isNZ) {
        value = row[newnr];
        if(newnr)
          jj = colno[newnr - 1];
        else
          jj = 0;
      }
      else
        value = row[j];
#ifdef DoMatrixRounding
      value = roundToPrecision(value, mat->epsvalue);
#endif
      value *= mult;
      if(isA) {
        if(mat->is_roworder)
          value = my_chsign(is_chsign(lp, j), value);
        value = scaled_mat(lp, value, mat->rows, j);
      }
      SET_MAT_ijA(elmnr, mat->rows, j, value);
      elmnr--;
    }

   /* Shift previous column entries down */
    i = stcol - mat->col_end[j-1] + 1;
    if(i > 0) {
      orignr -= i;
      elmnr  -= i;
      COL_MAT_MOVE(elmnr+1, orignr+1, i);
    }
  }

Done:
  if(saved != 0)
    row[0] = saved;
  FREE(addto);

  return( newnr );

}

STATIC int mat_appendcol(MATrec *mat, int count, REAL *column, int *rowno, REAL mult, MYBOOL checkrowmode)
{
  int     i, row, elmnr, lastnr;
  REAL    value;
  MYBOOL  isA, isNZ;
  lprec   *lp = mat->lp;

  /* Check if we are in row order mode and should add as row instead;
     the matrix will be transposed at a later stage */
  if(checkrowmode && mat->is_roworder)
    return( mat_appendrow(mat, count, column, rowno, mult, FALSE) );

  /* Make sure we have enough space */
/*
  if(!inc_mat_space(mat, mat->rows+1))
    return( 0 );
*/
  if(column == NULL)
    i = 0;
  else if(rowno != NULL)
    i = count;
  else {
    int nrows = mat->rows;

    elmnr = 0;
    for(i = 1; i <= nrows; i++)
      if(column[i] != 0)
        elmnr++;
    i = elmnr;
  }
  if((mat_nz_unused(mat) <= i) && !inc_mat_space(mat, i))
    return( 0 );

  /* Do initialization and validation */
  isA = (MYBOOL) (mat == lp->matA);
  isNZ = (MYBOOL) (column == NULL || rowno != NULL);
  if(isNZ && (count > 0)) {
    if(count > 1)
      sortREALByINT(column, rowno, count, 0, TRUE);
    if((rowno[0] < 0))
      return( 0 );
  }
  if(rowno != NULL)
    count--;

  /* Append sparse regular constraint values */
  elmnr = mat->col_end[mat->columns - 1];
  if(column != NULL) {
    row = -1;
    for(i = ((isNZ || !mat->is_roworder) ? 0 : 1); i <= count ; i++) {
      value = column[i];
      if(fabs(value) > mat->epsvalue) {
        if(isNZ) {
          lastnr = row;
          row = rowno[i];
          /* Check if we have come to the Lagrangean constraints */
          if(row > mat->rows)
            break;
          if(row <= lastnr)
            return( -1 );
        }
        else
          row = i;
#ifdef DoMatrixRounding
        value = roundToPrecision(value, mat->epsvalue);
#endif
        if(mat->is_roworder)
          value *= mult;
        else if(isA) {
          value = my_chsign(is_chsign(lp, row), value);
          value = scaled_mat(lp, value, row, mat->columns);
          if(!mat->is_roworder && (row == 0)) {
            lp->orig_obj[mat->columns] = value;
            continue;
          }
        }

       /* Store the item and update counters */
        SET_MAT_ijA(elmnr, row, mat->columns, value);
        elmnr++;
      }
    }

   /* Fill dense Lagrangean constraints */
    if(get_Lrows(lp) > 0)
      mat_appendcol(lp->matL, get_Lrows(lp), column+mat->rows, NULL, mult, checkrowmode);

  }

 /* Set end of data */
  mat->col_end[mat->columns] = elmnr;

  return( mat->col_end[mat->columns] - mat->col_end[mat->columns-1] );
}

STATIC int mat_checkcounts(MATrec *mat, int *rownum, int *colnum, MYBOOL freeonexit)
{
  int i, j, n;
  int *rownr;

  if(rownum == NULL)
    allocINT(mat->lp, &rownum, mat->rows + 1, TRUE);
  if(colnum == NULL)
    allocINT(mat->lp, &colnum, mat->columns + 1, TRUE);

  for(i = 1 ; i <= mat->columns; i++) {
    j = mat->col_end[i - 1];
    n = mat->col_end[i];
    rownr = &COL_MAT_ROWNR(j);
    for(; j < n;
        j++, rownr += matRowColStep) {
      colnum[i]++;
      rownum[*rownr]++;
    }
  }

  n = 0;
  if((mat->lp->do_presolve != PRESOLVE_NONE) &&
     (mat->lp->spx_trace || (mat->lp->verbose > NORMAL))) {
    for(j = 1; j <= mat->columns; j++)
      if(colnum[j] == 0) {
        n++;
        report(mat->lp, FULL, "mat_checkcounts: Variable %s is not used in any constraints\n",
                              get_col_name(mat->lp, j));
      }
    for(i = 0; i <= mat->rows; i++)
      if(rownum[i] == 0) {
        n++;
        report(mat->lp, FULL, "mat_checkcounts: Constraint %s empty\n",
                              get_row_name(mat->lp, i));
      }
  }

  if(freeonexit) {
    FREE(rownum);
    FREE(colnum);
  }

  return( n );

}

STATIC MYBOOL mat_validate(MATrec *mat)
/* Routine to make sure that row mapping arrays are valid */
{
  int     i, j, je, *rownum;
  int     *rownr, *colnr;

  if(!mat->row_end_valid) {

    MEMCLEAR(mat->row_end, mat->rows + 1);
    allocINT(mat->lp, &rownum, mat->rows + 1, TRUE);

    /* First tally row counts and then cumulate them */
    j = mat_nonzeros(mat);
    rownr = &COL_MAT_ROWNR(0);
    for(i = 0; i < j; i++, rownr += matRowColStep)
      mat->row_end[*rownr]++;
    for(i = 1; i <= mat->rows; i++)
      mat->row_end[i] += mat->row_end[i - 1];

    /* Calculate the column index for every non-zero */
    for(i = 1; i <= mat->columns; i++) {
      j = mat->col_end[i - 1];
      je = mat->col_end[i];
      rownr = &COL_MAT_ROWNR(j);
      colnr = &COL_MAT_COLNR(j);
      for(; j < je; j++, rownr += matRowColStep, colnr += matRowColStep) {
#ifdef Paranoia
        if(/*(*colnr < 0) || (*colnr > mat->columns) || (Normally violated in primal phase 1) */
           (*rownr < 0) || (*rownr > mat->rows)) {
          report(mat->lp, SEVERE, "mat_validate: Matrix value storage error row %d [0..%d], column %d [1..%d]\n",
                                  *rownr, mat->rows, *colnr, mat->columns);
          mat->lp->spx_status = UNKNOWNERROR;
          return(FALSE);
        }
#endif
        *colnr = i;
        if(*rownr == 0)
          mat_set_rowmap(mat, rownum[*rownr],
                              *rownr, i, j);
        else
          mat_set_rowmap(mat, mat->row_end[*rownr - 1] + rownum[*rownr],
                              *rownr, i, j);
        rownum[*rownr]++;
      }
    }

    FREE(rownum);
    mat->row_end_valid = TRUE;
  }

  if(mat == mat->lp->matA)
    mat->lp->model_is_valid = TRUE;
  return( TRUE );
}

MYBOOL mat_get_data(lprec *lp, int matindex, MYBOOL isrow, int **rownr, int **colnr, REAL **value)
{
  MATrec *mat = lp->matA;

#if MatrixRowAccess == RAM_Index
  if(isrow)
    matindex = mat->row_mat[matindex];
  if(rownr != NULL)
    *rownr = &COL_MAT_ROWNR(matindex);
  if(colnr != NULL)
    *colnr = &COL_MAT_COLNR(matindex);
  if(value != NULL)
    *value = &COL_MAT_VALUE(matindex);

#else
  if(isrow) {
    if(rownr != NULL)
      *rownr = &ROW_MAT_ROWNR(matindex);
    if(colnr != NULL)
      *colnr = &ROW_MAT_COLNR(matindex);
    if(value != NULL)
      *value = &ROW_MAT_VALUE(matindex);
  }
  else {
    if(rownr != NULL)
      *rownr = &COL_MAT_ROWNR(matindex);
    if(colnr != NULL)
      *colnr = &COL_MAT_COLNR(matindex);
    if(value != NULL)
      *value = &COL_MAT_VALUE(matindex);
  }

#endif

  return( TRUE );
}


MYBOOL mat_set_rowmap(MATrec *mat, int row_mat_index, int rownr, int colnr, int col_mat_index)
{
#if MatrixRowAccess == RAM_Index
  mat->row_mat[row_mat_index] = col_mat_index;

#elif MatrixColAccess==CAM_Record
  mat->row_mat[row_mat_index].rownr = rownr;
  mat->row_mat[row_mat_index].colnr = colnr;
  mat->row_mat[row_mat_index].value = COL_MAT_VALUE(col_mat_index);

#else /* if MatrixColAccess==CAM_Vector */
  mat->row_mat_rownr[row_mat_index] = rownr;
  mat->row_mat_colnr[row_mat_index] = colnr;
  mat->row_mat_value[row_mat_index] = COL_MAT_VALUE(col_mat_index);

#endif

  return( TRUE );
}

/* Implement combined binary/linear sub-search for matrix look-up */
int mat_findelm(MATrec *mat, int row, int column)
{
  int low, high, mid, item;

#if 0
  if(mat->row_end_valid && (row > 0) &&
     (ROW_MAT_COLNR(mat->row_mat[(low = mat->row_end[row-1])]) == column))
    return(low);
#endif

  if((column < 1) || (column > mat->columns)) {
    report(mat->lp, IMPORTANT, "mat_findelm: Column %d out of range\n", column);
    return( -1 );
  }
  if((row < 0) || (row > mat->rows)) {
    report(mat->lp, IMPORTANT, "mat_findelm: Row %d out of range\n", row);
    return( -1 );
  }

  low = mat->col_end[column - 1];
  high = mat->col_end[column] - 1;
  if(low > high)
    return( -2 );

 /* Do binary search logic */
  mid = (low+high) / 2;
  item = COL_MAT_ROWNR(mid);
  while(high - low > LINEARSEARCH) {
    if(item < row) {
      low = mid + 1;
      mid = (low+high) / 2;
      item = COL_MAT_ROWNR(mid);
    }
    else if(item > row) {
      high = mid - 1;
      mid = (low+high) / 2;
      item = COL_MAT_ROWNR(mid);
    }
    else {
      low = mid;
      high = mid;
    }
  }

 /* Do linear scan search logic */
  if((high > low) && (high - low <= LINEARSEARCH)) {
    item = COL_MAT_ROWNR(low);
    while((low < high) && (item < row)) {
      low++;
      item = COL_MAT_ROWNR(low);
    }
    if(item == row)
      high = low;
  }

  if((low == high) && (row == item))
    return( low );
  else
    return( -2 );
}

int mat_findins(MATrec *mat, int row, int column, int *insertpos, MYBOOL validate)
{
  int low, high, mid, item, exitvalue, insvalue;

#if 0
  if(mat->row_end_valid && (row > 0) &&
     (ROW_MAT_COLNR(mat->row_mat[(low = mat->row_end[row-1])]) == column)) {
    insvalue = low;
    exitvalue = low;
    goto Done;
  }
#endif

  insvalue = -1;

  if((column < 1) || (column > mat->columns)) {
    if((column > 0) && !validate) {
      insvalue = mat->col_end[mat->columns];
      exitvalue = -2;
      goto Done;
    }
    report(mat->lp, IMPORTANT, "mat_findins: Column %d out of range\n", column);
    exitvalue = -1;
    goto Done;
  }
  if((row < 0) || (row > mat->rows)) {
    if((row >= 0) && !validate) {
      insvalue = mat->col_end[column];
      exitvalue = -2;
      goto Done;
    }
    report(mat->lp, IMPORTANT, "mat_findins: Row %d out of range\n", row);
    exitvalue = -1;
    goto Done;
  }

  low = mat->col_end[column - 1];
  insvalue = low;
  high = mat->col_end[column] - 1;
  if(low > high) {
    exitvalue = -2;
    goto Done;
  }

 /* Do binary search logic */
  mid = (low+high) / 2;
  item = COL_MAT_ROWNR(mid);
  while(high - low > LINEARSEARCH) {
    if(item < row) {
      low = mid + 1;
      mid = (low+high) / 2;
      item = COL_MAT_ROWNR(mid);
    }
    else if(item > row) {
      high = mid - 1;
      mid = (low+high) / 2;
      item = COL_MAT_ROWNR(mid);
    }
    else {
      low = mid;
      high = mid;
    }
  }

 /* Do linear scan search logic */
  if((high > low) && (high - low <= LINEARSEARCH)) {
    item = COL_MAT_ROWNR(low);
    while((low < high) && (item < row)) {
      low++;
      item = COL_MAT_ROWNR(low);
    }
    if(item == row)
      high = low;
  }

  insvalue = low;
  if((low == high) && (row == item))
    exitvalue = low;
  else {
    if((low < mat->col_end[column]) && (COL_MAT_ROWNR(low) < row))
      insvalue++;
    exitvalue = -2;
  }

Done:
  if(insertpos != NULL)
    (*insertpos) = insvalue;
  return( exitvalue );
}

STATIC REAL mat_getitem(MATrec *mat, int row, int column)
{
  int elmnr;

#ifdef DirectOverrideOF
  if((row == 0) && (mat == mat->lp->matA) && (mat->lp->OF_override != NULL))
    return( mat->lp->OF_override[column] );
  else
#endif
  {
    elmnr = mat_findelm(mat, row, column);
    if(elmnr >= 0)
      return( COL_MAT_VALUE(elmnr) );
    else
      return( 0 );
  }
}

STATIC MYBOOL mat_additem(MATrec *mat, int row, int column, REAL delta)
{
  int elmnr;

#ifdef DirectOverrideOF
  if((row == 0) && (mat == mat->lp->matA) && (mat->lp->OF_override != NULL))
    return( mat->lp->OF_override[column] );
  else
#endif
  {
    elmnr = mat_findelm(mat, row, column);
    if(elmnr >= 0) {
      COL_MAT_VALUE(elmnr) += delta;
      return( TRUE );
    }
    else {
      mat_setitem(mat, row, column, delta);
      return( FALSE );
    }
  }
}

STATIC MYBOOL mat_setitem(MATrec *mat, int row, int column, REAL value)
{
  return( mat_setvalue(mat, row, column, value, FALSE) );
}

STATIC void mat_multrow(MATrec *mat, int row_nr, REAL mult)
{
  int i, k1, k2;

#if 0
  if(row_nr == 0) {
    k2 = mat->col_end[0];
    for(i = 1; i <= mat->columns; i++) {
      k1 = k2;
      k2 = mat->col_end[i];
      if((k1 < k2) && (COL_MAT_ROWNR(k1) == row_nr))
        COL_MAT_VALUE(k1) *= mult;
    }
  }
  else if(mat_validate(mat)) {
    if(row_nr == 0)
      k1 = 0;
    else
#else
  if(mat_validate(mat)) {
    if(row_nr == 0)
      k1 = 0;
    else
#endif
    k1 = mat->row_end[row_nr-1];
    k2 = mat->row_end[row_nr];
    for(i = k1; i < k2; i++)
      ROW_MAT_VALUE(i) *= mult;
  }
}

STATIC void mat_multcol(MATrec *mat, int col_nr, REAL mult, MYBOOL DoObj)
{
  int    i, ie;
  MYBOOL isA;

#ifdef Paranoia
  if((col_nr < 1) || (col_nr > mat->columns)) {
    report(mat->lp, IMPORTANT, "mult_column: Column %d out of range\n", col_nr);
    return;
  }
#endif
  if(mult == 1.0)
    return;

  isA = (MYBOOL) (mat == mat->lp->matA);

  ie = mat->col_end[col_nr];
  for(i = mat->col_end[col_nr - 1]; i < ie; i++)
    COL_MAT_VALUE(i) *= mult;
  if(isA) {
    if(DoObj)
      mat->lp->orig_obj[col_nr] *= mult;
    if(get_Lrows(mat->lp) > 0)
      mat_multcol(mat->lp->matL, col_nr, mult, DoObj);
  }
}

STATIC void mat_multadd(MATrec *mat, REAL *lhsvector, int varnr, REAL mult)
{
  int               colnr;
  register int      ib, ie, *matRownr;
  register REAL     *matValue;

  /* Handle case of a slack variable */
  if(varnr <= mat->lp->rows) {
    lhsvector[varnr] += mult;
    return;
  }

  /* Do operation on the objective */
  if(mat->lp->matA == mat)
    lhsvector[0] += get_OF_active(mat->lp, varnr, mult);

  /* Scan the constraint matrix target columns */
  colnr = varnr - mat->lp->rows;
  ib = mat->col_end[colnr - 1];
  ie = mat->col_end[colnr];
  if(ib < ie) {

    /* Initialize pointers */
    matRownr = &COL_MAT_ROWNR(ib);
    matValue = &COL_MAT_VALUE(ib);

    /* Then loop over all regular rows */
    for(; ib < ie;
        ib++, matValue += matValueStep, matRownr += matRowColStep) {
      lhsvector[*matRownr] += mult * (*matValue);
    }
  }

}

STATIC MYBOOL mat_setvalue(MATrec *mat, int Row, int Column, REAL Value, MYBOOL doscale)
{
  int    elmnr, lastelm, i, RowA = Row, ColumnA = Column;
  MYBOOL isA;

  /* This function is inefficient if used to add new matrix entries in
     other places than at the end of the matrix. OK for replacing existing
     a non-zero value with another non-zero value */
  isA = (MYBOOL) (mat == mat->lp->matA);
  if(mat->is_roworder)
    swapINT(&Row, &Column);

  /* Set small numbers to zero */
  if(fabs(Value) < mat->epsvalue)
    Value = 0;
#ifdef DoMatrixRounding
  else
    Value = roundToPrecision(Value, mat->epsvalue);
#endif

  /* Check if we need to update column space */
  if(Column > mat->columns) {
    if(isA)
      inc_col_space(mat->lp, ColumnA - mat->columns);
    else
      inc_matcol_space(mat, Column - mat->columns);
  }

  /* Find out if we already have such an entry, or return insertion point */
  i = mat_findins(mat, Row, Column, &elmnr, FALSE);
  if(i == -1)
    return(FALSE);

  if(isA)
    set_action(&mat->lp->spx_action, ACTION_REBASE | ACTION_RECOMPUTE | ACTION_REINVERT);

  if(i >= 0) {
    /* there is an existing entry */
    if(fabs(Value) > mat->epsvalue) { /* we replace it by something non-zero */
      if(isA) {
        Value = my_chsign(is_chsign(mat->lp, RowA), Value);
        if(doscale && mat->lp->scaling_used)
          Value = scaled_mat(mat->lp, Value, RowA, ColumnA);
      }
      COL_MAT_VALUE(elmnr) = Value;
    }
    else { /* setting existing non-zero entry to zero. Remove the entry */
      /* This might remove an entire column, or leave just a bound. No
          nice solution for that yet */

      /* Shift up tail end of the matrix */
      lastelm = mat_nonzeros(mat);
#if 0
      for(i = elmnr; i < lastelm ; i++) {
        COL_MAT_COPY(i, i + 1);
      }
#else
      lastelm -= elmnr;
      COL_MAT_MOVE(elmnr, elmnr + 1, lastelm);
#endif
      for(i = Column; i <= mat->columns; i++)
        mat->col_end[i]--;

      mat->row_end_valid = FALSE;
    }
  }
  else if(fabs(Value) > mat->epsvalue) {
    /* no existing entry. make new one only if not nearly zero */
    /* check if more space is needed for matrix */
    if(!inc_mat_space(mat, 1))
      return(FALSE);

    if(Column > mat->columns) {
      i = mat->columns + 1;
      if(isA)
        shift_coldata(mat->lp, i, ColumnA - mat->columns, NULL);
      else
        mat_shiftcols(mat, &i, Column - mat->columns, NULL);
    }

    /* Shift down tail end of the matrix by one */
    lastelm = mat_nonzeros(mat);
#if 1 /* Does compiler optimization work better here? */
    for(i = lastelm; i > elmnr ; i--) {
      COL_MAT_COPY(i, i - 1);
    }
#else
    lastelm -= elmnr - 1;
    COL_MAT_MOVE(elmnr + 1, elmnr, lastelm);
#endif

    /* Set new element */
    if(isA) {
      Value = my_chsign(is_chsign(mat->lp, RowA), Value);
      if(doscale)
        Value = scaled_mat(mat->lp, Value, RowA, ColumnA);
    }
    SET_MAT_ijA(elmnr, Row, Column, Value);

    /* Update column indexes */
    for(i = Column; i <= mat->columns; i++)
      mat->col_end[i]++;

    mat->row_end_valid = FALSE;
  }

  if(isA && (mat->lp->var_is_free != NULL) && (mat->lp->var_is_free[ColumnA] > 0))
    return( mat_setvalue(mat, RowA, mat->lp->var_is_free[ColumnA], -Value, doscale) );
  return(TRUE);
}

STATIC MYBOOL mat_appendvalue(MATrec *mat, int Row, REAL Value)
{
  int *elmnr, Column = mat->columns;

  /* Set small numbers to zero */
  if(fabs(Value) < mat->epsvalue)
    Value = 0;
#ifdef DoMatrixRounding
  else
    Value = roundToPrecision(Value, mat->epsvalue);
#endif

  /* Check if more space is needed for matrix */
  if(!inc_mat_space(mat, 1))
    return(FALSE);

#ifdef Paranoia
  /* Check valid indeces */
  if((Row < 0) || (Row > mat->rows)) {
    report(mat->lp, SEVERE, "mat_appendvalue: Invalid row index %d specified\n", Row);
    return(FALSE);
  }
#endif

  /* Get insertion point and set value */
  elmnr = mat->col_end + Column;
  SET_MAT_ijA((*elmnr), Row, Column, Value);

  /* Update column count */
  (*elmnr)++;
  mat->row_end_valid = FALSE;

  return(TRUE);
}

STATIC MYBOOL mat_equalRows(MATrec *mat, int baserow, int comprow)
{
  MYBOOL status = FALSE;

  if(mat_validate(mat)) {
    int bj1 = 0, ej1, bj2 = 0, ej2;

    /* Get starting and ending positions */
    if(baserow >= 0)
      bj1 = mat->row_end[baserow-1];
    ej1 = mat->row_end[baserow];
    if(comprow >= 0)
      bj2 = mat->row_end[comprow-1];
    ej2 = mat->row_end[comprow];
    /* Fail if row lengths are unequal */
    if((ej1-bj1) != (ej2-bj2))
      return( status );

    /* Compare column index and value, element by element */
    for(; bj1 < ej1; bj1++, bj2++) {
      if(COL_MAT_COLNR(bj1) != COL_MAT_COLNR(bj2))
        break;
#if 1
      if(fabs(get_mat_byindex(mat->lp, bj1, TRUE, FALSE)-get_mat_byindex(mat->lp, bj2, TRUE, FALSE)) > mat->lp->epsprimal)
#else
      if(fabs(COL_MAT_VALUE(bj1)-COL_MAT_VALUE(bj2)) > mat->lp->epsprimal)
#endif
        break;
    }
    status = (MYBOOL) (bj1 == ej1);
  }
  return( status );
}

STATIC int mat_findcolumn(MATrec *mat, int matindex)
{
  int j;

  for(j = 1; j <= mat->columns; j++) {
    if(matindex < mat->col_end[j])
      break;
  }
  return(j);
}

STATIC int mat_expandcolumn(MATrec *mat, int colnr, REAL *column, int *nzlist, MYBOOL signedA)
{
  MYBOOL  isA = (MYBOOL) (mat->lp->matA == mat);
  int     i, ie, j, nzcount = 0;
  REAL    *matValue;
  int     *matRownr;

  signedA &= isA;

  /* Retrieve a column from the user data matrix A */
  MEMCLEAR(column, mat->rows + 1);
  if(isA) {
    column[0] = mat->lp->orig_obj[colnr];
    if(signedA && is_chsign(mat->lp, 0))
      column[0] = -column[0];
  }

  i = mat->col_end[colnr - 1];
  ie = mat->col_end[colnr];
  matRownr = &COL_MAT_ROWNR(i);
  matValue = &COL_MAT_VALUE(i);
  for(; i < ie;
      i++, matRownr += matRowColStep, matValue += matValueStep) {
    j = *matRownr;
    column[j] = *matValue;
    if(signedA && is_chsign(mat->lp, j))
      column[j] = -column[j];
    nzcount++;
    if(nzlist != NULL)
      nzlist[nzcount] = j;
  }
  if(nzlist != NULL)
    nzlist[0] = nzcount;
  return( nzcount );
}

STATIC MYBOOL mat_computemax(MATrec *mat)
{
  int  *rownr = &COL_MAT_ROWNR(0),
       *colnr = &COL_MAT_COLNR(0),
       i = 0, ie = mat->col_end[mat->columns], ez = 0;
  REAL *value = &COL_MAT_VALUE(0), epsmachine = mat->lp->epsmachine, absvalue;

  /* Prepare arrays */
  if(!allocREAL(mat->lp, &mat->colmax, mat->columns_alloc+1, AUTOMATIC) ||
     !allocREAL(mat->lp, &mat->rowmax, mat->rows_alloc+1, AUTOMATIC))
     return( FALSE );
  MEMCLEAR(mat->colmax, mat->columns+1);
  MEMCLEAR(mat->rowmax, mat->rows+1);

  /* Obtain the row and column maxima in one sweep */
  mat->dynrange = mat->lp->infinite;
  for(; i < ie;
      i++, rownr += matRowColStep, colnr += matRowColStep, value += matValueStep) {
    absvalue = fabs(*value);
    SETMAX(mat->colmax[*colnr], absvalue);
    SETMAX(mat->rowmax[*rownr], absvalue);
    SETMIN(mat->dynrange, absvalue);
    if(absvalue < epsmachine)
      ez++;
  }

  /* Lastly, compute the global maximum and get the dynamic range */
  for(i = 1; i <= mat->rows; i++)
    SETMAX(mat->rowmax[0], mat->rowmax[i]);
  mat->infnorm = mat->colmax[0] = mat->rowmax[0];
  if(mat->dynrange == 0) {
    report(mat->lp, SEVERE, "%d matrix contains zero-valued coefficients.\n", ez);
    mat->dynrange = mat->lp->infinite;
  }
  else {
    mat->dynrange = mat->infnorm / mat->dynrange;
    if(ez > 0)
      report(mat->lp, IMPORTANT, "%d matrix coefficients below machine precision were found.\n", ez);
  }

  return( TRUE );
}

STATIC MYBOOL mat_transpose(MATrec *mat)
{
  int     i, j, nz, k;
  MYBOOL  status;

  status = mat_validate(mat);
  if(status) {

    /* Create a column-ordered sparse element list; "column" index must be shifted */
    nz = mat_nonzeros(mat);
    if(nz > 0) {
#if MatrixColAccess==CAM_Record
      MATitem *newmat;
      newmat = (MATitem *) malloc((mat->mat_alloc) * sizeof(*(mat->col_mat)));
      j = mat->row_end[0];
      for(i = nz-1; i >= j ; i--) {
        k = i-j;
        newmat[k] = mat->col_mat[mat->row_mat[i]];
        newmat[k].row_nr = newmat[k].col_nr;
      }
      for(i = j-1; i >= 0 ; i--) {
        k = nz-j+i;
        newmat[k] = mat->col_mat[mat->row_mat[i]];
        newmat[k].row_nr = newmat[k].col_nr;
      }
      swapPTR((void **) &mat->col_mat, (void **) &newmat);
      FREE(newmat);
#else /*if MatrixColAccess==CAM_Vector*/
      REAL *newValue = NULL;
      int  *newRownr = NULL;
      allocREAL(mat->lp, &newValue, mat->mat_alloc, FALSE);
      allocINT(mat->lp, &newRownr, mat->mat_alloc, FALSE);

      j = mat->row_end[0];
      for(i = nz-1; i >= j ; i--) {
        k = i-j;
        newValue[k] = ROW_MAT_VALUE(i);
        newRownr[k] = ROW_MAT_COLNR(i);
      }
      for(i = j-1; i >= 0 ; i--) {
        k = nz-j+i;
        newValue[k] = ROW_MAT_VALUE(i);
        newRownr[k] = ROW_MAT_COLNR(i);
      }

      swapPTR((void **) &mat->col_mat_rownr, (void **) &newRownr);
      swapPTR((void **) &mat->col_mat_value, (void **) &newValue);
      FREE(newValue);
      FREE(newRownr);
#endif
    }

    /* Transfer row start to column start position; must adjust for different offsets */
    if(mat->rows == mat->rows_alloc)
      inc_matcol_space(mat, 1);
    j = mat->row_end[0];
    for(i = mat->rows; i >= 1; i--)
      mat->row_end[i] -= j;
    mat->row_end[mat->rows] = nz;
    swapPTR((void **) &mat->row_end, (void **) &mat->col_end);

    /* Swap arrays of maximum values */
    swapPTR((void **) &mat->rowmax, (void **) &mat->colmax);

    /* Swap array sizes */
    swapINT(&mat->rows, &mat->columns);
    swapINT(&mat->rows_alloc, &mat->columns_alloc);

    /* Finally set current storage mode */
    mat->is_roworder = (MYBOOL) !mat->is_roworder;
    mat->row_end_valid = FALSE;
  }
  return(status);
}


/* ---------------------------------------------------------------------------------- */
/* Change-tracking routines                                                           */
/* ---------------------------------------------------------------------------------- */
STATIC DeltaVrec *createUndoLadder(lprec *lp, int levelitems, int maxlevels)
{
  DeltaVrec *hold;

  hold = (DeltaVrec *) malloc(sizeof(*hold));
  hold->lp = lp;
  hold->activelevel = 0;
  hold->tracker = mat_create(lp, levelitems, 0, 0.0);
  inc_matcol_space(hold->tracker, maxlevels);
  return( hold );
}
STATIC int incrementUndoLadder(DeltaVrec *DV)
{
  DV->activelevel++;
  inc_matcol_space(DV->tracker, 1);
  mat_shiftcols(DV->tracker, &(DV->activelevel), 1, NULL);
  DV->tracker->columns++;
  return(DV->activelevel);
}
STATIC MYBOOL modifyUndoLadder(DeltaVrec *DV, int itemno, REAL target[], REAL newvalue)
{
  MYBOOL status;
  int    varindex = itemno;
  REAL   oldvalue = target[itemno];

#ifndef UseMilpSlacksRCF  /* Check if we should include ranged constraints */
  varindex -= DV->lp->rows;
#endif
  status = mat_appendvalue(DV->tracker, varindex, oldvalue);
  target[itemno] = newvalue;
  return(status);
}
STATIC int countsUndoLadder(DeltaVrec *DV)
{
  if(DV->activelevel > 0)
    return( mat_collength(DV->tracker, DV->activelevel) );
  else
    return( 0 );
}
STATIC int restoreUndoLadder(DeltaVrec *DV, REAL target[])
{
  int iD = 0;

  if(DV->activelevel > 0) {
    MATrec *mat = DV->tracker;
    int    iB = mat->col_end[DV->activelevel-1],
           iE = mat->col_end[DV->activelevel],
           *matRownr = &COL_MAT_ROWNR(iB);
    REAL   *matValue = &COL_MAT_VALUE(iB),
           oldvalue;

    /* Restore the values */
    iD = iE-iB;
    for(; iB < iE; iB++, matValue += matValueStep, matRownr += matRowColStep) {
      oldvalue = *matValue;
#ifdef UseMilpSlacksRCF  /* Check if we should include ranged constraints */
      target[(*matRownr)] = oldvalue;
#else
      target[DV->lp->rows+(*matRownr)] = oldvalue;
#endif
    }

    /* Get rid of the changes */
    mat_shiftcols(DV->tracker, &(DV->activelevel), -1, NULL);
  }

  return(iD);
}
STATIC int decrementUndoLadder(DeltaVrec *DV)
{
  int deleted = 0;

  if(DV->activelevel > 0) {
    deleted = mat_shiftcols(DV->tracker, &(DV->activelevel), -1, NULL);
    DV->activelevel--;
    DV->tracker->columns--;
  }
  return(deleted);
}
STATIC MYBOOL freeUndoLadder(DeltaVrec **DV)
{
  if((DV == NULL) || (*DV == NULL))
    return(FALSE);

  mat_free(&((*DV)->tracker));
  FREE(*DV);
  return(TRUE);
}

STATIC MYBOOL appendUndoPresolve(lprec *lp, MYBOOL isprimal, REAL beta, int colnrDep)
{
  MATrec *mat;

  /* Point to correct undo structure */
  if(isprimal)
    mat = lp->presolve_undo->primalundo->tracker;
  else
    mat = lp->presolve_undo->dualundo->tracker;

  /* Append the data */
  if((colnrDep > 0) && (beta != 0) &&
     (mat != NULL) && (mat->col_tag[0] > 0)) {
    int ix = mat->col_tag[0];
#if 0
    report(lp, NORMAL, "appendUndoPresolve: %s %g * x%d\n",
                       ( beta < 0 ? "-" : "+"), fabs(beta), colnrDep);
#endif

    /* Do normal user variable case */
    if(colnrDep <= lp->columns)
      mat_setvalue(mat, colnrDep, ix, beta, FALSE);

    /* Handle case where a slack variable is referenced */
    else {
      int ipos, jx = mat->col_tag[ix];
      mat_setvalue(mat, jx, ix, beta, FALSE);
      jx = mat_findins(mat, jx, ix, &ipos, FALSE);
      COL_MAT_ROWNR(ipos) = colnrDep;
    }
    return( TRUE );
  }
  else
    return( FALSE );
}
STATIC MYBOOL addUndoPresolve(lprec *lp, MYBOOL isprimal, int colnrElim, REAL alpha, REAL beta, int colnrDep)
{
  int       ix;
  DeltaVrec **DV;
  MATrec    *mat;
  presolveundorec *psdata = lp->presolve_undo;

  /* Point to and initialize undo structure at first call */
  if(isprimal) {
    DV = &(psdata->primalundo);
    if(*DV == NULL) {
      *DV = createUndoLadder(lp, lp->columns+1, lp->columns);
      mat = (*DV)->tracker;
      mat->epsvalue = lp->matA->epsvalue;
      allocINT(lp, &(mat->col_tag), lp->columns+1, FALSE);
      mat->col_tag[0] = 0;
    }
  }
  else {
    DV = &(psdata->dualundo);
    if(*DV == NULL) {
      *DV = createUndoLadder(lp, lp->rows+1, lp->rows);
      mat = (*DV)->tracker;
      mat->epsvalue = lp->matA->epsvalue;
      allocINT(lp, &(mat->col_tag), lp->rows+1, FALSE);
      mat->col_tag[0] = 0;
    }
  }
  mat = (*DV)->tracker;
#if 0
  report(lp, NORMAL, "addUndoPresolve: x%d = %g %s %g * x%d\n",
                     colnrElim, alpha, ( beta < 0 ? "-" : "+"), fabs(beta), colnrDep);
#endif
  /* Add the data */
  ix = mat->col_tag[0] = incrementUndoLadder(*DV);
  mat->col_tag[ix] = colnrElim;
  if(alpha != 0)
    mat_setvalue(mat, 0, ix, alpha, FALSE);
/*    mat_appendvalue(*mat, 0, alpha);*/
  if((colnrDep > 0) && (beta != 0)) {
    if(colnrDep > lp->columns)
      return( appendUndoPresolve(lp, isprimal, beta, colnrDep) );
    else
      mat_setvalue(mat, colnrDep, ix, beta, FALSE);
  }

  return( TRUE );
}



/* ---------------------------------------------------------------------------------- */
/* High level matrix inverse and product routines in lp_solve                         */
/* ---------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------- */
/*    A brief description of the basis inverse and factorization logic in lp_solve    */
/* ---------------------------------------------------------------------------------- */
/*

   In order to better understand the legacy code for operating with the
   basis and its factorization in lp_solve I (KE) will briefly explain
   the conventions and associated matrix algebra.  Note that with lp_solve
   version 5.5, it is also possible to direct lp_solve to use the traditional
   (textbook) format by setting the obj_in_B parameter to FALSE.

   The matrix description of a linear program (as represented by lp_solve) goes
   like this:

           maximize         c'x
           subject to  r <=  Ax <= b
           where       l <=   x <= u

   The matrix A is partitioned into two column sets [B|N], where B is
   a square matrix of "basis" variables containing non-fixed
   variables of the linear program at any given stage and N is the
   submatrix of corresponding non-basic, fixed variables. The
   variables (columns) in N may be fixed at their lower or upper levels.

   Similarly, the c vector is partitioned into the basic and non-basic
   parts [z|n].

   While lp_solve stores the objective vector c in a dense format, and
   the constraint matrix A in a (fairly standard) sparse format, the
   column vectors passed to the factorization routine include the
   objective coefficient at row index 0.  (In versions of lp_solve
   before v5.2, c was actually explicitly stored as the 0-th row of A).
   The expanded matrix may be called the "A~" form and looks like this:

                       A~ = [ c ]
                            [ A ]

   Linear programming involves solving linear equations based on the
   square basis matrix B, which includes is a subset of columns from A~.
   The implications of the common storage of c and A (e.g. A~) vs. the
   inverse / factorization of B for the operations and updates performed
   by the simplex routine therefore needs to be understood.  As a consquence
   of A~, in lp_solve B is stored in an expanded, bordered format using the
   following (non-singular) representation:

                       B~ = [ 1 z ]
                            [ 0 B ]

   Any basis inversion / factorization engine used by lp_solve must therefore
   explicitly represent and handle the implications of this structure for
   associated matrix operations.

   The standard matrix formula for computing the inverse of a bordered
   matrix shows what the inversion of B~ actually produces:

                  Inv(B~) = [ 1 -z*Inv(B) ]
                            [ 0   Inv(B)  ]

   The A~ and B~ representations require awareness by the developer of the side
   effects of the presence of the top row when doing product operations such as
   b'N, btran and ftran.  Note in particular z*Inv(B) in the top row of Inv(B~),
   which is actually the dual solution vector of the given basis.  This fact
   makes a very common update in the simplex algorithm (reduced costs) returnable
   as a vector simply by setting 1 at the top of a vector being pre-multiplied
   with Inv(B~).

   However, if the objective vector (c) is changed, the expanded representation
   requires that B / B~ be refactorized.  Also, when doing FTRAN, BTRAN
   and x'A-type operations, you will patently get the incorrect result
   if you simply copy the operations given in textbooks.  First I'll show the
   results of an FTRAN operation:

                   Bx = a  ==>  x = FTRAN(a)

   In lp_solve, this operation solves:

                   [ 1 z ] [y] = [d]
                   [ 0 B ] [x]   [a]

   Using the Inv(B~) expression earlier, the FTRAN result is therefore:

             [y] = [ 1 -z*Inv(B) ] [d] = [ d - z*Inv(B)*a ]
             [x]   [ 0   Inv(B)  ] [a]   [   Inv(B)*a     ]

   As an example, the value of the dual objective can be returned at the
   0-th index by passing the active RHS vector with 0 at the 0-th position.

   Similarily, doing the left solve - performing the BTRAN calculation:

                   [x y] [ 1 z ] = [d a']
                         [ 0 B ]

   ... will produce the following result in lp_solve:

   [x y] = [d a'] [ 1 -z*Inv(B) ] = [ d | -d*z*Inv(B) + a'*Inv(B) ]
                  [ 0   Inv(B)  ]

   So, if you thought you were simply computing "a'*Inv(B)", look again.
   In order to produce the desired result, you have to set d to 0 before
   the BTRAN operation.  On the other hand, if you set d to 1 and a to 0,
   then you are very conveniently on your way to obtain the reduced costs
   (needs a further matrix premultiplication with non-basic variables).

   Incidentally, the BTRAN with [1 0] that yields [ 1 | -z*Inv(B) ] can
   also be used as a fast way of checking the accuracy of the current
   factorization.

   Equipped with this understanding, I hope that you see that
   the approach in lp_solve is actually pretty convenient.  It also
   becomes easier to extend functionality in lp_solve by drawing on
   formulas and expressions from LP literature that otherwise assume
   the non-bordered syntax and representation.

                                     Kjell Eikland -- November 2003
                                     KE update     -- April 2005
                                     KE update     -- June 2005

*/

STATIC MYBOOL __WINAPI invert(lprec *lp, MYBOOL shiftbounds, MYBOOL final)
{
  MYBOOL *usedpos, resetbasis;
  REAL   test;
  int    k, i, j;
  int    singularities, usercolB;

 /* Make sure the tags are correct */
  if(!mat_validate(lp->matA)) {
    lp->spx_status = INFEASIBLE;
    return(FALSE);
  }

 /* Create the inverse management object at the first call to invert() */
  if(lp->invB == NULL)
    lp->bfp_init(lp, lp->rows, 0, NULL);
  else
    lp->bfp_preparefactorization(lp);
  singularities = 0;

 /* Must save spx_status since it is used to carry information about
    the presence and handling of singular columns in the matrix */
  if(userabort(lp, MSG_INVERT))
    return(FALSE);

#ifdef Paranoia
  if(lp->spx_trace)
    report(lp, DETAILED, "invert: Iter %10g, fact-length %7d, OF " RESULTVALUEMASK ".\n",
                         (double) get_total_iter(lp), lp->bfp_colcount(lp), (double) -lp->rhs[0]);
#endif

 /* Store state of pre-existing basis, and at the same time check if
    the basis is I; in this case take the easy way out */
  if(!allocMYBOOL(lp, &usedpos, lp->sum + 1, TRUE)) {
    lp->bb_break = TRUE;
    return(FALSE);
  }
  usedpos[0] = TRUE;
  usercolB = 0;
  for(i = 1; i <= lp->rows; i++) {
    k = lp->var_basic[i];
    if(k > lp->rows)
      usercolB++;
    usedpos[k] = TRUE;
  }
#ifdef Paranoia
  if(!verify_basis(lp))
    report(lp, SEVERE, "invert: Invalid basis detected (iter %g).\n",
                       (double) get_total_iter(lp));
#endif

 /* Tally matrix nz-counts and check if we should reset basis
    indicators to all slacks */
  resetbasis = (MYBOOL) ((usercolB > 0) && lp->bfp_canresetbasis(lp));
  k = 0;
  for(i = 1; i <= lp->rows; i++) {
    if(lp->var_basic[i] > lp->rows)
      k += mat_collength(lp->matA, lp->var_basic[i] - lp->rows) + (is_OF_nz(lp,lp->var_basic[i] - lp->rows) ? 1 : 0);
    if(resetbasis) {
      j = lp->var_basic[i];
      if(j > lp->rows)
        lp->is_basic[j] = FALSE;
      lp->var_basic[i] = i;
      lp->is_basic[i] = TRUE;
    }
  }

 /* Now do the refactorization */
  singularities = lp->bfp_factorize(lp, usercolB, k, usedpos, final);

 /* Do user reporting */
  if(userabort(lp, MSG_INVERT))
    goto Cleanup;

 /* Finalize factorization/inversion */
  lp->bfp_finishfactorization(lp);

  /* Recompute the RHS ( Ref. lp_solve inverse logic and Chvatal p. 121 ) */
#ifdef DebugInv
  blockWriteLREAL(stdout, "RHS-values pre invert", lp->rhs, 0, lp->rows);
#endif
  recompute_solution(lp, shiftbounds);
  restartPricer(lp, AUTOMATIC);
#ifdef DebugInv
  blockWriteLREAL(stdout, "RHS-values post invert", lp->rhs, 0, lp->rows);
#endif

Cleanup:
  /* Check for numerical instability indicated by frequent refactorizations */
  test = get_refactfrequency(lp, FALSE);
  if(test < MIN_REFACTFREQUENCY) {
    test = get_refactfrequency(lp, TRUE);
    report(lp, NORMAL, "invert: Refactorization frequency %.1g indicates numeric instability.\n",
                       test);
    lp->spx_status = NUMFAILURE;
  }

  FREE(usedpos);
  return((MYBOOL) (singularities <= 0));
} /* invert */


STATIC MYBOOL fimprove(lprec *lp, REAL *pcol, int *nzidx, REAL roundzero)
{
  REAL   *errors, sdp;
  int    j;
  MYBOOL Ok = TRUE;

  allocREAL(lp, &errors, lp->rows + 1, FALSE);
  if(errors == NULL) {
    Ok = FALSE;
    return(Ok);
  }
  MEMCOPY(errors, pcol, lp->rows + 1);
  lp->bfp_ftran_normal(lp, pcol, nzidx);
  prod_Ax(lp, NULL, pcol, NULL, 0.0, -1,
                                errors, NULL, MAT_ROUNDDEFAULT);
  lp->bfp_ftran_normal(lp, errors, NULL);

  sdp = 0;
  for(j = 1; j <= lp->rows; j++)
    if(fabs(errors[j])>sdp)
      sdp = fabs(errors[j]);
  if(sdp > lp->epsmachine) {
    report(lp, DETAILED, "Iterative FTRAN correction metric %g", sdp);
    for(j = 1; j <= lp->rows; j++) {
      pcol[j] += errors[j];
      my_roundzero(pcol[j], roundzero);
    }
  }
  FREE(errors);
  return(Ok);
}

STATIC MYBOOL bimprove(lprec *lp, REAL *rhsvector, int *nzidx, REAL roundzero)
{
  int    j;
  REAL   *errors, err, maxerr;
  MYBOOL Ok = TRUE;

  allocREAL(lp, &errors, lp->sum + 1, FALSE);
  if(errors == NULL) {
    Ok = FALSE;
    return(Ok);
  }
  MEMCOPY(errors, rhsvector, lp->sum + 1);

  /* Solve Ax=b for x, compute b back */
  lp->bfp_btran_normal(lp, errors, nzidx);
  prod_xA(lp, NULL, errors, NULL, 0.0, 1.0,
                                  errors, NULL,
                                  MAT_ROUNDDEFAULT);

  /* Take difference with ingoing values, while shifting the column values
     to the rows section and zeroing the columns again */
  for(j = 1; j <= lp->rows; j++)
    errors[j] = errors[lp->rows+lp->var_basic[j]] - rhsvector[j];
  for(j = lp->rows; j <= lp->sum; j++)
    errors[j] = 0;

  /* Solve the b errors for the iterative x adjustment */
  lp->bfp_btran_normal(lp, errors, NULL);

  /* Generate the adjustments and compute statistic */
  maxerr = 0;
  for(j = 1; j <= lp->rows; j++) {
    if(lp->var_basic[j]<=lp->rows) continue;
    err = errors[lp->rows+lp->var_basic[j]];
    if(fabs(err)>maxerr)
      maxerr = fabs(err);
  }
  if(maxerr > lp->epsmachine) {
    report(lp, DETAILED, "Iterative BTRAN correction metric %g", maxerr);
    for(j = 1; j <= lp->rows; j++) {
      if(lp->var_basic[j]<=lp->rows) continue;
      rhsvector[j] += errors[lp->rows+lp->var_basic[j]];
      my_roundzero(rhsvector[j], roundzero);
    }
  }
  FREE(errors);
  return(Ok);
}

STATIC void ftran(lprec *lp, REAL *rhsvector, int *nzidx, REAL roundzero)
{
#if 0
  if(is_action(lp->improve, IMPROVE_SOLUTION) && lp->bfp_pivotcount(lp))
    fimprove(lp, rhsvector, nzidx, roundzero);
  else
#endif
    lp->bfp_ftran_normal(lp, rhsvector, nzidx);
}

STATIC void btran(lprec *lp, REAL *rhsvector, int *nzidx, REAL roundzero)
{
#if 0
  if(is_action(lp->improve, IMPROVE_SOLUTION) && lp->bfp_pivotcount(lp))
    bimprove(lp, rhsvector, nzidx, roundzero);
  else
#endif
    lp->bfp_btran_normal(lp, rhsvector, nzidx);
}

STATIC MYBOOL fsolve(lprec *lp, int varin, REAL *pcol, int *nzidx, REAL roundzero, REAL ofscalar, MYBOOL prepareupdate)
/* Was setpivcol in versions earlier than 4.0.1.8 - KE */
{
  MYBOOL ok = TRUE;

  if(varin > 0)
    obtain_column(lp, varin, pcol, nzidx, NULL);

 /* Solve, adjusted for objective function scalar */
  pcol[0] *= ofscalar;
  if(prepareupdate)
    lp->bfp_ftran_prepare(lp, pcol, nzidx);
  else
    ftran(lp, pcol, nzidx, roundzero);

  return(ok);

} /* fsolve */


STATIC MYBOOL bsolve(lprec *lp, int row_nr, REAL *rhsvector, int *nzidx, REAL roundzero, REAL ofscalar)
{
  MYBOOL ok = TRUE;

  if(row_nr >= 0) /* Note that row_nr == 0 returns the [1, 0...0 ] vector */
    row_nr = obtain_column(lp, row_nr, rhsvector, nzidx, NULL);

  /* Solve, adjusted for objective function scalar */
  rhsvector[0] *= ofscalar;
  btran(lp, rhsvector, nzidx, roundzero);

  return(ok);

} /* bsolve */


/* Vector compression and expansion routines */
STATIC MYBOOL vec_compress(REAL *densevector, int startpos, int endpos, REAL epsilon,
                           REAL *nzvector, int *nzindex)
{
  int n;

  if((densevector == NULL) || (nzindex == NULL) || (startpos > endpos))
    return( FALSE );

  n = 0;
  densevector += startpos;
  while(startpos <= endpos) {
    if(fabs(*densevector) > epsilon) {  /* Apply zero-threshold */
      if(nzvector != NULL)                       /* Only produce index if no nzvector is given */
        nzvector[n] = *densevector;
      n++;
      nzindex[n] = startpos;
    }
    startpos++;
    densevector++;
  }
  nzindex[0] = n;
  return( TRUE );
}

STATIC MYBOOL vec_expand(REAL *nzvector, int *nzindex, REAL *densevector, int startpos, int endpos)
{
  int i, n;

  n = nzindex[0];
  i = nzindex[n];
  densevector += endpos;
  while(endpos >= startpos) {                     /* Loop from behind to allow densevector == nzvector */
    if(endpos == i) {
      n--;
      *densevector = nzvector[n];
      i = nzindex[n];
    }
    else
      *densevector = 0;
    endpos--;
    densevector--;
  }
  return( TRUE );
}


/* ----------------------------------------------------------------------- */
/* Sparse matrix product routines and utility                              */
/* ----------------------------------------------------------------------- */

STATIC MYBOOL get_colIndexA(lprec *lp, int varset, int *colindex, MYBOOL append)
{
  int      i, varnr, P1extraDim, vb, ve, n, nrows = lp->rows, nsum = lp->sum;
  MYBOOL   omitfixed, omitnonfixed;
  REAL     v;

  /* Find what variable range to scan - default is {SCAN_USERVARS} */
  /* First determine the starting position; add from the top, going down */
  P1extraDim = abs(lp->P1extraDim);
  vb = nrows + 1;
  if(varset & SCAN_ARTIFICIALVARS)
    vb = nsum - P1extraDim + 1;
  if(varset & SCAN_USERVARS)
    vb = nrows + 1;
  if(varset & SCAN_SLACKVARS)
    vb = 1;

  /* Then determine the ending position, add from the bottom, going up */
  ve = nsum;
  if(varset & SCAN_SLACKVARS)
    ve = nrows;
  if(varset & SCAN_USERVARS)
    ve = nsum - P1extraDim;
  if(varset & SCAN_ARTIFICIALVARS)
    ve = nsum;

  /* Adjust for partial pricing */
  if(varset & SCAN_PARTIALBLOCK) {
    SETMAX(vb, partial_blockStart(lp, FALSE));
    SETMIN(ve, partial_blockEnd(lp, FALSE));
  }

  /* Determine exclusion columns */
  omitfixed = (MYBOOL) ((varset & OMIT_FIXED) != 0);
  omitnonfixed = (MYBOOL) ((varset & OMIT_NONFIXED) != 0);
  if(omitfixed && omitnonfixed)
    return(FALSE);

  /* Scan the target colums */
  if(append)
    n = colindex[0];
  else
    n = 0;
  for(varnr = vb; varnr <= ve; varnr++) {

    /* Skip gap in the specified column scan range (possibly user variables) */
    if(varnr > nrows) {
      if((varnr <= nsum-P1extraDim) && !(varset & SCAN_USERVARS))
        continue;
#if 1
      /* Skip empty columns */
      if(/*(lp->P1extraVal == 0) &&*/
         (mat_collength(lp->matA, varnr-nrows) == 0))
        continue;
#endif
    }

    /* Find if the variable is in the scope - default is {Ø} */
    i = lp->is_basic[varnr];
    if((varset & USE_BASICVARS) > 0 && (i))
      ;
    else if((varset & USE_NONBASICVARS) > 0 && (!i))
      ;
    else
      continue;

    v = lp->upbo[varnr];
    if((omitfixed && (v == 0)) ||
       (omitnonfixed && (v != 0)))
      continue;

    /* Append to list */
    n++;
    colindex[n] = varnr;
  }
  colindex[0] = n;

  return(TRUE);
}

STATIC int prod_Ax(lprec *lp, int *coltarget, REAL *input, int *nzinput,
                              REAL roundzero, REAL ofscalar,
                              REAL *output, int *nzoutput, int roundmode)
/* prod_Ax is only used in fimprove; note that it is NOT VALIDATED/verified as of 20030801 - KE */
{
  int      j, colnr, ib, ie, vb, ve;
  MYBOOL   localset, localnz = FALSE, isRC;
  MATrec   *mat = lp->matA;
  REAL     sdp;
  REAL     *value;
  int      *rownr;

  /* Find what variable range to scan - default is {SCAN_USERVARS} */
  /* Define default column target if none was provided */
  isRC = (MYBOOL) ((roundmode & MAT_ROUNDRC) != 0);
  localset = (MYBOOL) (coltarget == NULL);
  if(localset) {
    int varset = SCAN_SLACKVARS | SCAN_USERVARS |
                 USE_BASICVARS | OMIT_FIXED;
    if(isRC && is_piv_mode(lp, PRICE_PARTIAL) && !is_piv_mode(lp, PRICE_FORCEFULL))
      varset |= SCAN_PARTIALBLOCK;
    coltarget = (int *) mempool_obtainVector(lp->workarrays, lp->sum+1, sizeof(*coltarget));
    if(!get_colIndexA(lp, varset, coltarget, FALSE)) {
      mempool_releaseVector(lp->workarrays, (char *) coltarget, FALSE);
      return(FALSE);
    }
  }
  localnz = (MYBOOL) (nzinput == NULL);
  if(localnz) {
    nzinput = (int *) mempool_obtainVector(lp->workarrays, lp->rows+1, sizeof(*nzinput));
    vec_compress(input, 0, lp->rows, lp->matA->epsvalue, NULL, nzinput);
  }

  /* Scan the columns */
  vb = 1;
  ve = coltarget[0];
  for(vb = 1; vb <= coltarget[0]; vb++) {
    colnr = coltarget[vb];
    j = lp->is_basic[colnr];

    /* Perform the multiplication */
    sdp = ofscalar*input[j];
    if(colnr <= lp->rows)               /* A slack variable is in the basis */
      output[colnr] += sdp;
    else {                              /* A normal variable is in the basis */
      colnr -= lp->rows;
      ib = mat->col_end[colnr - 1];
      ie = mat->col_end[colnr];
      rownr = &COL_MAT_ROWNR(ib);
      value = &COL_MAT_VALUE(ib);
      for(; ib < ie;
          ib++, rownr += matRowColStep, value += matValueStep) {
        output[*rownr] += (*value)*sdp;
      }
    }
  }
  roundVector(output+1, lp->rows-1, roundzero);

  /* Clean up and return */
  if(localset)
    mempool_releaseVector(lp->workarrays, (char *) coltarget, FALSE);
  if(localnz)
    mempool_releaseVector(lp->workarrays, (char *) nzinput, FALSE);

  return(TRUE);
}

STATIC int prod_xA(lprec *lp, int *coltarget,
                              REAL *input, int *nzinput, REAL roundzero, REAL ofscalar,
                              REAL *output, int *nzoutput, int roundmode)
/* Note that the dot product xa is stored at the active column index of A, i.e. of a.
   This means that if the basis only contains non-slack variables, output may point to
   the same vector as input, without overwriting the [0..rows] elements. */
{
  int      colnr, rownr, varnr, ib, ie, vb, ve, nrows = lp->rows;
  MYBOOL   localset, localnz = FALSE, includeOF, isRC;
  REALXP   vmax;
  register REALXP v;
  int      inz, *rowin, countNZ = 0;
  MATrec   *mat = lp->matA;
  register REAL     *matValue;
  register int      *matRownr;

  /* Clean output area (only necessary if we are returning the full vector) */
  isRC = (MYBOOL) ((roundmode & MAT_ROUNDRC) != 0);
  if(nzoutput == NULL) {
    if(input == output)
      MEMCLEAR(output+nrows+1, lp->columns);
    else
      MEMCLEAR(output, lp->sum+1);
  }

  /* Find what variable range to scan - default is {SCAN_USERVARS} */
  /* Define default column target if none was provided */
  localset = (MYBOOL) (coltarget == NULL);
  if(localset) {
    int varset = SCAN_SLACKVARS | SCAN_USERVARS |
                 USE_NONBASICVARS | OMIT_FIXED;
    if(isRC && is_piv_mode(lp, PRICE_PARTIAL) && !is_piv_mode(lp, PRICE_FORCEFULL))
      varset |= SCAN_PARTIALBLOCK;
    coltarget = (int *) mempool_obtainVector(lp->workarrays, lp->sum+1, sizeof(*coltarget));
    if(!get_colIndexA(lp, varset, coltarget, FALSE)) {
      mempool_releaseVector(lp->workarrays, (char *) coltarget, FALSE);
      return(FALSE);
    }
  }
/*#define UseLocalNZ*/
#ifdef UseLocalNZ
  localnz = (MYBOOL) (nzinput == NULL);
  if(localnz) {
    nzinput = (int *) mempool_obtainVector(lp->workarrays, nrows+1, sizeof(*nzinput));
    vec_compress(input, 0, nrows, lp->matA->epsvalue, NULL, nzinput);
  }
#endif
  includeOF = (MYBOOL) (((nzinput == NULL) || (nzinput[1] == 0)) &&
                        (input[0] != 0) && lp->obj_in_basis);

  /* Scan the target colums */
  vmax = 0;
  ve = coltarget[0];
  for(vb = 1; vb <= ve; vb++) {

    varnr = coltarget[vb];

    if(varnr <= nrows) {
      v = input[varnr];
    }
    else {
      colnr = varnr - nrows;
      v = 0;
      ib = mat->col_end[colnr - 1];
      ie = mat->col_end[colnr];
      if(ib < ie) {

        /* Do dense input vector version */
#ifdef UseLocalNZ
        if(localnz || (nzinput == NULL)) {
#else
        if(nzinput == NULL) {
#endif
          /* Do the OF */
          if(includeOF)
#ifdef DirectArrayOF
            v += input[0] * lp->obj[colnr] * ofscalar;
#else
            v += input[0] * get_OF_active(lp, varnr, ofscalar);
#endif

          /* Initialize pointers */
          matRownr = &COL_MAT_ROWNR(ib);
          matValue = &COL_MAT_VALUE(ib);

          /* Do extra loop optimization based on target window overlaps */
#ifdef UseLocalNZ
          if((ib < ie)
             && (colnr <= *nzinput)
             && (COL_MAT_ROWNR(ie-1) >= nzinput[colnr])
             && (*matRownr <= nzinput[*nzinput])
             )
#endif
#ifdef NoLoopUnroll
          /* Then loop over all regular rows */
          for(; ib < ie; ib++) {
            v += input[*matRownr] * (*matValue);
            matValue += matValueStep;
            matRownr += matRowColStep;
          }
#else
          /* Prepare for simple loop unrolling */
          if(((ie-ib) % 2) == 1) {
            v += input[*matRownr] * (*matValue);
            ib++;
            matValue += matValueStep;
            matRownr += matRowColStep;
          }

          /* Then loop over remaining pairs of regular rows */
          while(ib < ie) {
            v += input[*matRownr] * (*matValue);
            v += input[*(matRownr+matRowColStep)] * (*(matValue+matValueStep));
            ib += 2;
            matValue += 2*matValueStep;
            matRownr += 2*matRowColStep;
          }
#endif
        }
        /* Do sparse input vector version */
        else {

          /* Do the OF */
          if(includeOF)
#ifdef DirectArrayOF
            v += input[0] * lp->obj[colnr] * ofscalar;
#else
            v += input[0] * get_OF_active(lp, varnr, ofscalar);
#endif

          /* Initialize pointers */
          inz = 1;
          rowin = nzinput+inz;
          matRownr = &COL_MAT_ROWNR(ib);
          matValue = &COL_MAT_VALUE(ib);
          ie--;

          /* Then loop over all non-OF rows */
          while((inz <= *nzinput) && (ib <= ie)) {

           /* Try to synchronize at right */
            while((*rowin > *matRownr) && (ib < ie)) {
              ib++;
              matValue += matValueStep;
              matRownr += matRowColStep;
            }
            /* Try to synchronize at left */
            while((*rowin < *matRownr) && (inz < *nzinput)) {
              inz++;
              rowin++;
            }
            /* Perform dot product operation if there was a match */
            if(*rowin == *matRownr) {
              v += input[*rowin] * (*matValue);
              /* Step forward at left */
              inz++;
              rowin++;
            }
          }
        }
      }
      if((roundmode & MAT_ROUNDABS) != 0) {
        my_roundzero(v, roundzero);
      }
    }

    /* Special handling of small reduced cost values */
    if(!isRC || (my_chsign(lp->is_lower[varnr], v) < 0)) {
      SETMAX(vmax, fabs((REAL) v));
    }
    if(v != 0) {
      countNZ++;
      if(nzoutput != NULL)
        nzoutput[countNZ] = varnr;
    }
    output[varnr] = (REAL) v;
  }

  /* Compute reduced cost if this option is active */
  if(isRC && !lp->obj_in_basis)
    countNZ = get_basisOF(lp, coltarget, output, nzoutput);

  /* Check if we should do relative rounding */
  if((roundmode & MAT_ROUNDREL) != 0) {
    if((roundzero > 0) && (nzoutput != NULL)) {
      ie = 0;
      if(isRC) {
        SETMAX(vmax, MAT_ROUNDRCMIN);  /* Make sure we don't use very small values */
      }
      vmax *= roundzero;
      for(ib = 1; ib <= countNZ;  ib++) {
        rownr = nzoutput[ib];
        if(fabs(output[rownr]) < vmax)
          output[rownr] = 0;
        else {
          ie++;
          nzoutput[ie] = rownr;
        }
      }
      countNZ = ie;
    }
  }

  /* Clean up and return */
  if(localset)
    mempool_releaseVector(lp->workarrays, (char *) coltarget, FALSE);
  if(localnz)
    mempool_releaseVector(lp->workarrays, (char *) nzinput, FALSE);

  if(nzoutput != NULL)
    *nzoutput = countNZ;
  return(countNZ);
}

STATIC MYBOOL prod_xA2(lprec *lp, int *coltarget,
                                  REAL *prow, REAL proundzero, int *nzprow,
                                  REAL *drow, REAL droundzero, int *nzdrow,
                                  REAL ofscalar, int roundmode)
{
  int      varnr, colnr, ib, ie, vb, ve, nrows = lp->rows;
  MYBOOL   includeOF, isRC;
  REALXP   dmax, pmax;
  register REALXP d, p;
  MATrec   *mat = lp->matA;
  REAL     value;
  register REAL     *matValue;
  register int      *matRownr;
  MYBOOL localset;

  /* Find what variable range to scan - default is {SCAN_USERVARS} */
  /* First determine the starting position; add from the top, going down */
  localset = (MYBOOL) (coltarget == NULL);
  if(localset) {
    int varset = SCAN_SLACKVARS + SCAN_USERVARS + /*SCAN_ALLVARS +*/
                 /*SCAN_PARTIALBLOCK+*/
                 USE_NONBASICVARS+OMIT_FIXED;
    coltarget = (int *) mempool_obtainVector(lp->workarrays, lp->sum+1, sizeof(*coltarget));
    if(!get_colIndexA(lp, varset, coltarget, FALSE)) {
      mempool_releaseVector(lp->workarrays, (char *) coltarget, FALSE);
      return(FALSE);
    }
  }

  /* Initialize variables */
  isRC = (MYBOOL) ((roundmode & MAT_ROUNDRC) != 0);
  pmax = 0;
  dmax = 0;
  if(nzprow != NULL)
    *nzprow = 0;
  if(nzdrow != NULL)
    *nzdrow = 0;
  includeOF = (MYBOOL) (((prow[0] != 0) || (drow[0] != 0)) &&
                        lp->obj_in_basis);

  /* Scan the target colums */
  ve = coltarget[0];
  for(vb = 1; vb <= ve; vb++) {

    varnr = coltarget[vb];

    if(varnr <= nrows) {
      p = prow[varnr];
      d = drow[varnr];
    }
    else {

      colnr = varnr - nrows;

      p = 0;
      d = 0;
      ib = mat->col_end[colnr - 1];
      ie = mat->col_end[colnr];

      if(ib < ie) {

        /* Do the OF */
        if(includeOF) {
#ifdef DirectArrayOF
          value = lp->obj[colnr] * ofscalar;
#else
          value = get_OF_active(lp, varnr, ofscalar);
#endif
          p += prow[0] * value;
          d += drow[0] * value;
        }

        /* Then loop over all regular rows */
        matRownr = &COL_MAT_ROWNR(ib);
        matValue = &COL_MAT_VALUE(ib);
#ifdef NoLoopUnroll
        for( ; ib < ie; ib++) {
          p += prow[*matRownr] * (*matValue);
          d += drow[*matRownr] * (*matValue);
          matValue += matValueStep;
          matRownr += matRowColStep;
        }
#else
        /* Prepare for simple loop unrolling */
        if(((ie-ib) % 2) == 1) {
          p += prow[*matRownr] * (*matValue);
          d += drow[*matRownr] * (*matValue);
          ib++;
          matValue += matValueStep;
          matRownr += matRowColStep;
        }

        /* Then loop over remaining pairs of regular rows */
        while(ib < ie) {
          p += prow[*matRownr] * (*matValue);
          p += prow[*(matRownr+matRowColStep)] * (*(matValue+matValueStep));
          d += drow[*matRownr] * (*matValue);
          d += drow[*(matRownr+matRowColStep)] * (*(matValue+matValueStep));
          ib += 2;
          matValue += 2*matValueStep;
          matRownr += 2*matRowColStep;
        }
#endif

      }
      if((roundmode & MAT_ROUNDABS) != 0) {
        my_roundzero(p, proundzero);
        my_roundzero(d, droundzero);
      }
    }

    SETMAX(pmax, fabs((REAL) p));
    prow[varnr] = (REAL) p;
    if((nzprow != NULL) && (p != 0)) {
      (*nzprow)++;
      nzprow[*nzprow] = varnr;
    }

    /* Special handling of reduced cost rounding */
    if(!isRC || (my_chsign(lp->is_lower[varnr], d) < 0)) {
      SETMAX(dmax, fabs((REAL) d));
    }
    drow[varnr] = (REAL) d;
    if((nzdrow != NULL) && (d != 0)) {
      (*nzdrow)++;
      nzdrow[*nzdrow] = varnr;
    }
  }

  /* Compute reduced cost here if this option is active */
  if((drow != 0) && !lp->obj_in_basis)
    get_basisOF(lp, coltarget, drow, nzdrow);

  /* Check if we should do relative rounding */
  if((roundmode & MAT_ROUNDREL) != 0) {
    if((proundzero > 0) && (nzprow != NULL)) {
      ie = 0;
      pmax *= proundzero;
      for(ib = 1; ib <= *nzprow;  ib++) {
        varnr = nzprow[ib];
        if(fabs(prow[varnr]) < pmax)
          prow[varnr] = 0;
        else {
          ie++;
          nzprow[ie] = varnr;
        }
      }
      *nzprow = ie;
    }
    if((droundzero > 0) && (nzdrow != NULL)) {
      ie = 0;
      if(isRC) {
        SETMAX(dmax, MAT_ROUNDRCMIN);  /* Make sure we don't use very small values */
      }
      dmax *= droundzero;
      for(ib = 1; ib <= *nzdrow;  ib++) {
        varnr = nzdrow[ib];
        if(fabs(drow[varnr]) < dmax)
          drow[varnr] = 0;
        else {
          ie++;
          nzdrow[ie] = varnr;
        }
      }
      *nzdrow = ie;
    }
  }

  /* Clean up and return */
  if(localset)
    mempool_releaseVector(lp->workarrays, (char *) coltarget, FALSE);
  return( TRUE );
}

STATIC void bsolve_xA2(lprec *lp, int* coltarget,
                                  int row_nr1, REAL *vector1, REAL roundzero1, int *nzvector1,
                                  int row_nr2, REAL *vector2, REAL roundzero2, int *nzvector2, int roundmode)
{
  REAL ofscalar = 1.0;

 /* Clear and initialize first vector */
  if(nzvector1 == NULL)
    MEMCLEAR(vector1, lp->sum + 1);
  else
    MEMCLEAR(vector1, lp->rows + 1);
  vector1[row_nr1] = 1;
/*  workINT[0] = 1;
  workINT[1] = row_nr1; */

  if(vector2 == NULL) {
    lp->bfp_btran_normal(lp, vector1, NULL);
    prod_xA(lp, coltarget, vector1, NULL, roundzero1, ofscalar*0,
                           vector1, nzvector1, roundmode);
  }
  else {

   /* Clear and initialize second vector */
    if(nzvector2 == NULL)
      MEMCLEAR(vector2, lp->sum + 1);
    else
      MEMCLEAR(vector2, lp->rows + 1);
    if(lp->obj_in_basis || (row_nr2 > 0)) {
      vector2[row_nr2] = 1;
/*      workINT[2] = 1;
      workINT[3] = row_nr2; */
    }
    else
      get_basisOF(lp, NULL, vector2, nzvector2);

   /* A double BTRAN equation solver process is implemented "in-line" below in
      order to save time and to implement different rounding for the two */
    lp->bfp_btran_double(lp, vector1, NULL, vector2, NULL);

   /* Multiply solution vectors with matrix values */
    prod_xA2(lp, coltarget, vector1, roundzero1, nzvector1,
                            vector2, roundzero2, nzvector2,
                            ofscalar, roundmode);
  }
}

