/* $Id$ */
// Copyright (C) 2003, International Business Machines
// Corporation and others.  All Rights Reserved.
// This code is licensed under the terms of the Eclipse Public License (EPL).

#include "CoinPragma.hpp"
#include "CoinHelperFunctions.hpp"
#include "ClpHelperFunctions.hpp"

#include "ClpInterior.hpp"
#include "ClpCholeskyWssmp.hpp"
#include "ClpCholeskyDense.hpp"
#include "ClpMessage.hpp"

//#############################################################################
// Constructors / Destructor / Assignment
//#############################################################################

//-------------------------------------------------------------------
// Default Constructor
//-------------------------------------------------------------------
ClpCholeskyWssmp::ClpCholeskyWssmp(int denseThreshold)
  : ClpCholeskyBase(denseThreshold)
{
  type_ = 12;
}

//-------------------------------------------------------------------
// Copy constructor
//-------------------------------------------------------------------
ClpCholeskyWssmp::ClpCholeskyWssmp(const ClpCholeskyWssmp &rhs)
  : ClpCholeskyBase(rhs)
{
}

//-------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------
ClpCholeskyWssmp::~ClpCholeskyWssmp()
{
}

//----------------------------------------------------------------
// Assignment operator
//-------------------------------------------------------------------
ClpCholeskyWssmp &
ClpCholeskyWssmp::operator=(const ClpCholeskyWssmp &rhs)
{
  if (this != &rhs) {
    ClpCholeskyBase::operator=(rhs);
  }
  return *this;
}
//-------------------------------------------------------------------
// Clone
//-------------------------------------------------------------------
ClpCholeskyBase *ClpCholeskyWssmp::clone() const
{
  return new ClpCholeskyWssmp(*this);
}
// At present I can't get wssmp to work as my libraries seem to be out of sync
// so I have linked in ekkwssmp which is an older version
#ifndef USE_EKKWSSMP
extern "C" {
void F77_FUNC(wsetmaxthrds, WSETMAXTHRDS)(const int *NTHREADS);

void F77_FUNC(wssmp, WSSMP)(const int *N, const int *IA,
  const int *JA, const double *AVALS,
  double *DIAG, int *PERM,
  int *INVP, double *B,
  const int *LDB, const int *NRHS,
  double *AUX, const int *NAUX,
  int *MRP, int *IPARM,
  double *DPARM);
void F77_FUNC_(wsmp_clear, WSMP_CLEAR)(void);
}
#else
/* minimum needed for user */
typedef struct EKKModel EKKModel;
typedef struct EKKContext EKKContext;

extern "C" {
EKKContext *ekk_initializeContext();
void ekk_endContext(EKKContext *context);
EKKModel *ekk_newModel(EKKContext *env, const char *name);
int ekk_deleteModel(EKKModel *model);
}
static EKKModel *model = NULL;
static EKKContext *context = NULL;
extern "C" void ekkwssmp(EKKModel *, int *n,
  int *columnStart, int *rowIndex, double *element,
  double *diagonal, int *perm, int *invp,
  double *rhs, int *ldb, int *nrhs,
  double *aux, int *naux,
  int *mrp, int *iparm, double *dparm);
static void F77_FUNC(wssmp, WSSMP)(int *n, int *ia, int *ja,
  double *avals, double *diag, int *perm, int *invp,
  double *b, int *ldb, int *nrhs, double *aux, int *naux, int *mrp, int *iparm, double *dparm)
{
  if (!context) {
    /* initialize OSL environment */
    context = ekk_initializeContext();
    model = ekk_newModel(context, "");
  }
  ekkwssmp(model, n, ia, ja,
    avals, diag, perm, invp,
    b, ldb, nrhs, aux,
    naux, mrp, iparm, dparm);
  //ekk_deleteModel(model);
  //ekk_endContext(context);
}
#endif
/* Orders rows and saves pointer to matrix.and model */
int ClpCholeskyWssmp::order(ClpInterior *model)
{
  numberRows_ = model->numberRows();
  rowsDropped_ = new char[numberRows_];
  memset(rowsDropped_, 0, numberRows_);
  numberRowsDropped_ = 0;
  model_ = model;
  rowCopy_ = model->clpMatrix()->reverseOrderedCopy();
  // Space for starts
  choleskyStart_ = new CoinBigIndex[numberRows_ + 1];
  const CoinBigIndex *columnStart = model_->clpMatrix()->getVectorStarts();
  const int *columnLength = model_->clpMatrix()->getVectorLengths();
  const int *row = model_->clpMatrix()->getIndices();
  const CoinBigIndex *rowStart = rowCopy_->getVectorStarts();
  const int *rowLength = rowCopy_->getVectorLengths();
  const int *column = rowCopy_->getIndices();
  // We need two arrays for counts
  int *which = new int[numberRows_];
  int *used = new int[numberRows_ + 1];
  CoinZeroN(used, numberRows_);
  int iRow;
  sizeFactor_ = 0;
  int numberColumns = model->numberColumns();
  int numberDense = 0;
  if (denseThreshold_ > 0) {
    delete[] whichDense_;
    delete[] denseColumn_;
    delete dense_;
    whichDense_ = new char[numberColumns];
    int iColumn;
    used[numberRows_] = 0;
    for (iColumn = 0; iColumn < numberColumns; iColumn++) {
      int length = columnLength[iColumn];
      used[length] += 1;
    }
    int nLong = 0;
    int stop = CoinMax(denseThreshold_ / 2, 100);
    for (iRow = numberRows_; iRow >= stop; iRow--) {
      if (used[iRow])
        COIN_DETAIL_PRINT(printf("%d columns are of length %d\n", used[iRow], iRow));
      nLong += used[iRow];
      if (nLong > 50 || nLong > (numberColumns >> 2))
        break;
    }
    CoinZeroN(used, numberRows_);
    for (iColumn = 0; iColumn < numberColumns; iColumn++) {
      if (columnLength[iColumn] < denseThreshold_) {
        whichDense_[iColumn] = 0;
      } else {
        whichDense_[iColumn] = 1;
        numberDense++;
      }
    }
    if (!numberDense || numberDense > 100) {
      // free
      delete[] whichDense_;
      whichDense_ = NULL;
      denseColumn_ = NULL;
      dense_ = NULL;
    } else {
      // space for dense columns
      denseColumn_ = new double[numberDense * numberRows_];
      // dense cholesky
      dense_ = new ClpCholeskyDense();
      dense_->reserveSpace(NULL, numberDense);
      COIN_DETAIL_PRINT(printf("Taking %d columns as dense\n", numberDense));
    }
  }
  for (iRow = 0; iRow < numberRows_; iRow++) {
    int number = 1;
    // make sure diagonal exists
    which[0] = iRow;
    used[iRow] = 1;
    if (!rowsDropped_[iRow]) {
      CoinBigIndex startRow = rowStart[iRow];
      CoinBigIndex endRow = rowStart[iRow] + rowLength[iRow];
      for (CoinBigIndex k = startRow; k < endRow; k++) {
        int iColumn = column[k];
        if (!whichDense_ || !whichDense_[iColumn]) {
          CoinBigIndex start = columnStart[iColumn];
          CoinBigIndex end = columnStart[iColumn] + columnLength[iColumn];
          for (CoinBigIndex j = start; j < end; j++) {
            int jRow = row[j];
            if (jRow >= iRow && !rowsDropped_[jRow]) {
              if (!used[jRow]) {
                used[jRow] = 1;
                which[number++] = jRow;
              }
            }
          }
        }
      }
      sizeFactor_ += number;
      int j;
      for (j = 0; j < number; j++)
        used[which[j]] = 0;
    }
  }
  delete[] which;
  // Now we have size - create arrays and fill in
  try {
    choleskyRow_ = new int[sizeFactor_];
  } catch (...) {
    // no memory
    delete[] choleskyStart_;
    choleskyStart_ = NULL;
    return -1;
  }
  try {
    sparseFactor_ = new double[sizeFactor_];
  } catch (...) {
    // no memory
    delete[] choleskyRow_;
    choleskyRow_ = NULL;
    delete[] choleskyStart_;
    choleskyStart_ = NULL;
    return -1;
  }

  sizeFactor_ = 0;
  which = choleskyRow_;
  for (iRow = 0; iRow < numberRows_; iRow++) {
    int number = 1;
    // make sure diagonal exists
    which[0] = iRow;
    used[iRow] = 1;
    choleskyStart_[iRow] = sizeFactor_;
    if (!rowsDropped_[iRow]) {
      CoinBigIndex startRow = rowStart[iRow];
      CoinBigIndex endRow = rowStart[iRow] + rowLength[iRow];
      for (CoinBigIndex k = startRow; k < endRow; k++) {
        int iColumn = column[k];
        if (!whichDense_ || !whichDense_[iColumn]) {
          CoinBigIndex start = columnStart[iColumn];
          CoinBigIndex end = columnStart[iColumn] + columnLength[iColumn];
          for (CoinBigIndex j = start; j < end; j++) {
            int jRow = row[j];
            if (jRow >= iRow && !rowsDropped_[jRow]) {
              if (!used[jRow]) {
                used[jRow] = 1;
                which[number++] = jRow;
              }
            }
          }
        }
      }
      sizeFactor_ += number;
      int j;
      for (j = 0; j < number; j++)
        used[which[j]] = 0;
      // Sort
      std::sort(which, which + number);
      // move which on
      which += number;
    }
  }
  choleskyStart_[numberRows_] = sizeFactor_;
  delete[] used;
  permuteInverse_ = new int[numberRows_];
  permute_ = new int[numberRows_];
  integerParameters_[0] = 0;
  int i0 = 0;
  int i1 = 1;
#ifndef USE_EKKWSSMP
  int i2 = 1;
  if (model->numberThreads() <= 0)
    i2 = 1;
  else
    i2 = model->numberThreads();
  F77_FUNC(wsetmaxthrds, WSETMAXTHRDS)
  (&i2);
#endif
  F77_FUNC(wssmp, WSSMP)
  (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
    NULL, permute_, permuteInverse_, 0, &numberRows_, &i1,
    NULL, &i0, NULL, integerParameters_, doubleParameters_);
  integerParameters_[1] = 1; //order and symbolic
  integerParameters_[2] = 2;
  integerParameters_[3] = 0; //CSR
  integerParameters_[4] = 0; //C style
  integerParameters_[13] = 1; //reuse initial factorization space
  integerParameters_[15 + 0] = 1; //ordering
  integerParameters_[15 + 1] = 0;
  integerParameters_[15 + 2] = 1;
  integerParameters_[15 + 3] = 0;
  integerParameters_[15 + 4] = 1;
  doubleParameters_[10] = 1.0e-20;
  doubleParameters_[11] = 1.0e-15;
  F77_FUNC(wssmp, WSSMP)
  (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
    NULL, permute_, permuteInverse_, NULL, &numberRows_, &i1,
    NULL, &i0, NULL, integerParameters_, doubleParameters_);
  //std::cout<<"Ordering and symbolic factorization took "<<doubleParameters_[0]<<std::endl;
  if (integerParameters_[63]) {
    std::cout << "wssmp returning error code of " << integerParameters_[63] << std::endl;
    return 1;
  }
  // Modify gamma and delta if 0.0
  if (!model->gamma() && !model->delta()) {
    model->setGamma(5.0e-5);
    model->setDelta(5.0e-5);
  }
  std::cout << integerParameters_[23] << " elements in sparse Cholesky" << std::endl;
  if (!integerParameters_[23]) {
    for (int iRow = 0; iRow < numberRows_; iRow++) {
      permuteInverse_[iRow] = iRow;
      permute_[iRow] = iRow;
    }
    std::cout << "wssmp says no elements - fully dense? - switching to dense" << std::endl;
    integerParameters_[1] = 2;
    integerParameters_[2] = 2;
    integerParameters_[7] = 1; // no permute
    F77_FUNC(wssmp, WSSMP)
    (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
      NULL, permute_, permuteInverse_, NULL, &numberRows_, &i1,
      NULL, &i0, NULL, integerParameters_, doubleParameters_);
    std::cout << integerParameters_[23] << " elements in dense Cholesky" << std::endl;
  }
  return 0;
}
/* Does Symbolic factorization given permutation.
   This is called immediately after order.  If user provides this then
   user must provide factorize and solve.  Otherwise the default factorization is used
   returns non-zero if not enough memory */
int ClpCholeskyWssmp::symbolic()
{
  return 0;
}
/* Factorize - filling in rowsDropped and returning number dropped */
int ClpCholeskyWssmp::factorize(const double *diagonal, int *rowsDropped)
{
  const CoinBigIndex *columnStart = model_->clpMatrix()->getVectorStarts();
  const int *columnLength = model_->clpMatrix()->getVectorLengths();
  const int *row = model_->clpMatrix()->getIndices();
  const double *element = model_->clpMatrix()->getElements();
  const CoinBigIndex *rowStart = rowCopy_->getVectorStarts();
  const int *rowLength = rowCopy_->getVectorLengths();
  const int *column = rowCopy_->getIndices();
  const double *elementByRow = rowCopy_->getElements();
  int numberColumns = model_->clpMatrix()->getNumCols();
  int iRow;
  double *work = new double[numberRows_];
  CoinZeroN(work, numberRows_);
  const double *diagonalSlack = diagonal + numberColumns;
  int newDropped = 0;
  double largest;
  double smallest;
  int numberDense = 0;
  if (dense_)
    numberDense = dense_->numberRows();
  //perturbation
  double perturbation = model_->diagonalPerturbation() * model_->diagonalNorm();
  perturbation = perturbation * perturbation;
  if (perturbation > 1.0) {
#ifdef COIN_DEVELOP
    //if (model_->model()->logLevel()&4)
    std::cout << "large perturbation " << perturbation << std::endl;
#endif
    perturbation = sqrt(perturbation);
    ;
    perturbation = 1.0;
  }
  if (whichDense_) {
    double *denseDiagonal = dense_->diagonal();
    double *dense = denseColumn_;
    int iDense = 0;
    for (int iColumn = 0; iColumn < numberColumns; iColumn++) {
      if (whichDense_[iColumn]) {
        denseDiagonal[iDense++] = 1.0 / diagonal[iColumn];
        CoinZeroN(dense, numberRows_);
        CoinBigIndex start = columnStart[iColumn];
        CoinBigIndex end = columnStart[iColumn] + columnLength[iColumn];
        for (CoinBigIndex j = start; j < end; j++) {
          int jRow = row[j];
          dense[jRow] = element[j];
        }
        dense += numberRows_;
      }
    }
  }
  double delta2 = model_->delta(); // add delta*delta to diagonal
  delta2 *= delta2;
  for (iRow = 0; iRow < numberRows_; iRow++) {
    double *put = sparseFactor_ + choleskyStart_[iRow];
    int *which = choleskyRow_ + choleskyStart_[iRow];
    int number = choleskyStart_[iRow + 1] - choleskyStart_[iRow];
    if (!rowLength[iRow])
      rowsDropped_[iRow] = 1;
    if (!rowsDropped_[iRow]) {
      CoinBigIndex startRow = rowStart[iRow];
      CoinBigIndex endRow = rowStart[iRow] + rowLength[iRow];
      work[iRow] = diagonalSlack[iRow] + delta2;
      for (CoinBigIndex k = startRow; k < endRow; k++) {
        int iColumn = column[k];
        if (!whichDense_ || !whichDense_[iColumn]) {
          CoinBigIndex start = columnStart[iColumn];
          CoinBigIndex end = columnStart[iColumn] + columnLength[iColumn];
          double multiplier = diagonal[iColumn] * elementByRow[k];
          for (CoinBigIndex j = start; j < end; j++) {
            int jRow = row[j];
            if (jRow >= iRow && !rowsDropped_[jRow]) {
              double value = element[j] * multiplier;
              work[jRow] += value;
            }
          }
        }
      }
      int j;
      for (j = 0; j < number; j++) {
        int jRow = which[j];
        put[j] = work[jRow];
        work[jRow] = 0.0;
      }
    } else {
      // dropped
      int j;
      for (j = 1; j < number; j++) {
        put[j] = 0.0;
      }
      put[0] = 1.0;
    }
  }
  //check sizes
  double largest2 = maximumAbsElement(sparseFactor_, sizeFactor_);
  largest2 *= 1.0e-20;
  largest = CoinMin(largest2, 1.0e-11);
  int numberDroppedBefore = 0;
  for (iRow = 0; iRow < numberRows_; iRow++) {
    int dropped = rowsDropped_[iRow];
    // Move to int array
    rowsDropped[iRow] = dropped;
    if (!dropped) {
      CoinBigIndex start = choleskyStart_[iRow];
      double diagonal = sparseFactor_[start];
      if (diagonal > largest2) {
        sparseFactor_[start] = diagonal + perturbation;
      } else {
        sparseFactor_[start] = diagonal + perturbation;
        rowsDropped[iRow] = 2;
        numberDroppedBefore++;
      }
    }
  }
  int i1 = 1;
  int i0 = 0;
  integerParameters_[1] = 3;
  integerParameters_[2] = 3;
  integerParameters_[10] = 2;
  //integerParameters_[11]=1;
  integerParameters_[12] = 2;
  doubleParameters_[10] = CoinMax(1.0e-20, largest);
  if (doubleParameters_[10] > 1.0e-3)
    integerParameters_[9] = 1;
  else
    integerParameters_[9] = 0;
  F77_FUNC(wssmp, WSSMP)
  (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
    NULL, permute_, permuteInverse_, NULL, &numberRows_, &i1,
    NULL, &i0, rowsDropped, integerParameters_, doubleParameters_);
  //std::cout<<"factorization took "<<doubleParameters_[0]<<std::endl;
  if (integerParameters_[9]) {
    std::cout << "scaling applied" << std::endl;
  }
  newDropped = integerParameters_[20] + numberDroppedBefore;
  //if (integerParameters_[20])
  //std::cout<<integerParameters_[20]<<" rows dropped"<<std::endl;
  largest = doubleParameters_[3];
  smallest = doubleParameters_[4];
  delete[] work;
  if (model_->messageHandler()->logLevel() > 1)
    std::cout << "Cholesky - largest " << largest << " smallest " << smallest << std::endl;
  choleskyCondition_ = largest / smallest;
  if (integerParameters_[63] < 0)
    return -1; // out of memory
  if (whichDense_) {
    // Update dense columns (just L)
    // Zero out dropped rows
    int i;
    for (i = 0; i < numberDense; i++) {
      double *a = denseColumn_ + i * numberRows_;
      for (int j = 0; j < numberRows_; j++) {
        if (rowsDropped[j])
          a[j] = 0.0;
      }
    }
    integerParameters_[29] = 1;
    int i0 = 0;
    integerParameters_[1] = 4;
    integerParameters_[2] = 4;
    F77_FUNC(wssmp, WSSMP)
    (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
      NULL, permute_, permuteInverse_, denseColumn_, &numberRows_, &numberDense,
      NULL, &i0, NULL, integerParameters_, doubleParameters_);
    integerParameters_[29] = 0;
    dense_->resetRowsDropped();
    longDouble *denseBlob = dense_->aMatrix();
    double *denseDiagonal = dense_->diagonal();
    // Update dense matrix
    for (i = 0; i < numberDense; i++) {
      const double *a = denseColumn_ + i * numberRows_;
      // do diagonal
      double value = denseDiagonal[i];
      const double *b = denseColumn_ + i * numberRows_;
      for (int k = 0; k < numberRows_; k++)
        value += a[k] * b[k];
      denseDiagonal[i] = value;
      for (int j = i + 1; j < numberDense; j++) {
        double value = 0.0;
        const double *b = denseColumn_ + j * numberRows_;
        for (int k = 0; k < numberRows_; k++)
          value += a[k] * b[k];
        *denseBlob = value;
        denseBlob++;
      }
    }
    // dense cholesky (? long double)
    int *dropped = new int[numberDense];
    dense_->factorizePart2(dropped);
    delete[] dropped;
  }
  bool cleanCholesky;
  if (model_->numberIterations() < 2000)
    cleanCholesky = true;
  else
    cleanCholesky = false;
  if (cleanCholesky) {
    //drop fresh makes some formADAT easier
    //int oldDropped=numberRowsDropped_;
    if (newDropped || numberRowsDropped_) {
      //std::cout <<"Rank "<<numberRows_-newDropped<<" ( "<<
      //  newDropped<<" dropped)";
      //if (newDropped>oldDropped)
      //std::cout<<" ( "<<newDropped-oldDropped<<" dropped this time)";
      //std::cout<<std::endl;
      newDropped = 0;
      for (int i = 0; i < numberRows_; i++) {
        char dropped = static_cast< char >(rowsDropped[i]);
        rowsDropped_[i] = dropped;
        rowsDropped_[i] = 0;
        if (dropped == 2) {
          //dropped this time
          rowsDropped[newDropped++] = i;
          rowsDropped_[i] = 0;
        }
      }
      numberRowsDropped_ = newDropped;
      newDropped = -(2 + newDropped);
    }
  } else {
    if (newDropped) {
      newDropped = 0;
      for (int i = 0; i < numberRows_; i++) {
        char dropped = static_cast< char >(rowsDropped[i]);
        rowsDropped_[i] = dropped;
        if (dropped == 2) {
          //dropped this time
          rowsDropped[newDropped++] = i;
          rowsDropped_[i] = 1;
        }
      }
    }
    numberRowsDropped_ += newDropped;
    if (numberRowsDropped_ && 0) {
      std::cout << "Rank " << numberRows_ - numberRowsDropped_ << " ( " << numberRowsDropped_ << " dropped)";
      if (newDropped) {
        std::cout << " ( " << newDropped << " dropped this time)";
      }
      std::cout << std::endl;
    }
  }
  status_ = 0;
  return newDropped;
}
/* Uses factorization to solve. */
void ClpCholeskyWssmp::solve(double *region)
{
  int i1 = 1;
  int i0 = 0;
  integerParameters_[1] = 4;
  integerParameters_[2] = 4;
#if 1
  integerParameters_[5] = 3;
  doubleParameters_[5] = 1.0e-10;
  integerParameters_[6] = 6;
#endif
  if (!whichDense_) {
    F77_FUNC(wssmp, WSSMP)
    (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
      NULL, permute_, permuteInverse_, region, &numberRows_, &i1,
      NULL, &i0, NULL, integerParameters_, doubleParameters_);
  } else {
    // dense columns
    integerParameters_[29] = 1;
    F77_FUNC(wssmp, WSSMP)
    (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
      NULL, permute_, permuteInverse_, region, &numberRows_, &i1,
      NULL, &i0, NULL, integerParameters_, doubleParameters_);
    // do change;
    int numberDense = dense_->numberRows();
    double *change = new double[numberDense];
    int i;
    for (i = 0; i < numberDense; i++) {
      const double *a = denseColumn_ + i * numberRows_;
      double value = 0.0;
      for (int iRow = 0; iRow < numberRows_; iRow++)
        value += a[iRow] * region[iRow];
      change[i] = value;
    }
    // solve
    dense_->solve(change);
    for (i = 0; i < numberDense; i++) {
      const double *a = denseColumn_ + i * numberRows_;
      double value = change[i];
      for (int iRow = 0; iRow < numberRows_; iRow++)
        region[iRow] -= value * a[iRow];
    }
    delete[] change;
    // and finish off
    integerParameters_[29] = 2;
    integerParameters_[1] = 4;
    F77_FUNC(wssmp, WSSMP)
    (&numberRows_, choleskyStart_, choleskyRow_, sparseFactor_,
      NULL, permute_, permuteInverse_, region, &numberRows_, &i1,
      NULL, &i0, NULL, integerParameters_, doubleParameters_);
    integerParameters_[29] = 0;
  }
#if 0
     if (integerParameters_[5]) {
          std::cout << integerParameters_[5] << " refinements ";
     }
     std::cout << doubleParameters_[6] << std::endl;
#endif
}

/* vi: softtabstop=2 shiftwidth=2 expandtab tabstop=2
*/
