// Copyright (C) 2002, International Business Machines
// Corporation and others.  All Rights Reserved.
/*
   Authors

   Jeremy Omer

   Last update: april 10, 2015

 */

#include "CoinPragma.hpp"
#include "ClpPEDualRowDantzig.hpp"
#include "CoinHelperFunctions.hpp"
#ifndef CLP_DUAL_COLUMN_MULTIPLIER
#define CLP_DUAL_COLUMN_MULTIPLIER 1.01
#endif

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

//-------------------------------------------------------------------
// Default Constructor
//-------------------------------------------------------------------
ClpPEDualRowDantzig::ClpPEDualRowDantzig(double psi)
  : ClpDualRowDantzig()
  , modelPE_(NULL)
  , psi_(psi)
  , iCurrent_(0)
  , iInterval_(100)
  , updateCompatibles_(true)
  , coDegenCompatibles_(0)
  , coConsecutiveCompatibles_(0)
{
}

//-------------------------------------------------------------------
// Copy constructor
//-------------------------------------------------------------------
ClpPEDualRowDantzig::ClpPEDualRowDantzig(const ClpPEDualRowDantzig &source)
  : ClpDualRowDantzig(source)
{
  modelPE_ = NULL;
  psi_ = source.psi_;
  iCurrent_ = source.iCurrent_;
  iInterval_ = source.iInterval_;
  updateCompatibles_ = source.updateCompatibles_;
  coDegenCompatibles_ = source.coDegenCompatibles_;
  coConsecutiveCompatibles_ = source.coConsecutiveCompatibles_;
}

//-------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------
ClpPEDualRowDantzig::~ClpPEDualRowDantzig()
{
  delete modelPE_;
}

//----------------------------------------------------------------
// Assignment operator
//-------------------------------------------------------------------
ClpPEDualRowDantzig &
ClpPEDualRowDantzig::operator=(const ClpPEDualRowDantzig &rhs)
{
  if (this != &rhs) {
    ClpDualRowDantzig::operator=(rhs);
    delete modelPE_;
    modelPE_ = NULL;
  }
  return *this;
}

//-------------------------------------------------------------------
// Clone
//-------------------------------------------------------------------
ClpDualRowPivot *ClpPEDualRowDantzig::clone(bool CopyData) const
{
  if (CopyData) {
    return new ClpPEDualRowDantzig(*this);
  } else {
    return new ClpPEDualRowDantzig(psi_);
  }
}

// Returns pivot row, -1 if none
int ClpPEDualRowDantzig::pivotRow()
{
  assert(model_);

  /* Determine whether the set of compatible variables should be updated 
*/
  // store the number of degenerate pivots on compatible variables and the
  // overal number of degenerate pivots
  double progress = fabs(modelPE_->lastObjectiveValue() - model_->objectiveValue());
  bool isLastDegenerate = progress <= 1.0e-12 * fabs(model_->objectiveValue()) ? true : false;
  if (isLastDegenerate) {
    modelPE_->addDegeneratePivot();
    modelPE_->addDegeneratePivotConsecutive();

    if (modelPE_->isLastPivotCompatible()) {
      modelPE_->addDegenerateCompatiblePivot();
    }
  } else {
    modelPE_->resetDegeneratePivotsConsecutive();
  }

  // the compatible variables are updated when the number of degenerate pivots
  // on compatible variables is more than 20% the overall number of degenerate pivots
  if (modelPE_->isLastPivotCompatible()) {
    coConsecutiveCompatibles_++;
    if (isLastDegenerate) {
      coDegenCompatibles_++;
      if (coConsecutiveCompatibles_ >= 10 && 5 * coDegenCompatibles_ * model_->numberIterations() > modelPE_->coDegeneratePivots() * coConsecutiveCompatibles_) {
        updateCompatibles_ = true;
      }
    }
  }

  if (modelPE_->doStatistics()) {
    // For results comparison.
    // count the number of degenerate variables if psi==1
    // add the time spent in counting to the time limit

    modelPE_->startTimer();
    if (psi_ >= 1 && iCurrent_ >= 100) {
      modelPE_->updateDualDegenerates();
      modelPE_->updateDualDegeneratesAvg(100);
      model_->setMaximumSeconds(36000.0 + modelPE_->timeCompatibility() - CoinCpuTime());
      iCurrent_ = 0;
    }
    modelPE_->stopTimer();
  }

  // Update the set of compatible variables if necessary
  //
  if (modelPE_->doStatistics())
    modelPE_->startTimer();
  double psiTmp = psi_;
  if ((psi_ < 1.0) && (iCurrent_ >= iInterval_) && (updateCompatibles_ || iCurrent_ >= 1000)) {
    // the compatible variables are never updated if the last pivot is non degenerate
    // this could be counterproductive
    if (isLastDegenerate) {

      modelPE_->updateDualDegenerates();
      modelPE_->identifyCompatibleRows(model_->rowArray(2),
        model_->rowArray(1));

      if (modelPE_->doStatistics()) {
        // update the average number of degenerates and compatibles for statistics
        modelPE_->updateDualDegeneratesAvg(iCurrent_);
        modelPE_->updateCompatibleRowsAvg(iCurrent_);
      }

#if PE_DEBUG >= 1
      std::cout << "Number of compatible rows = " << modelPE_->coCompatibleRows() << " ; average  = " << modelPE_->coCompatibleRowsAvg() << std::endl;
      std::cout << "Number of degenerates = " << modelPE_->coDualDegenerates() << " ; average  = " << modelPE_->coDualDegeneratesAvg() << std::endl;
#endif

      // the checking frequency is modified to reflect what appears to be needed
      if (iCurrent_ == iInterval_)
        iInterval_ = std::max(50, iInterval_ - 50);
      else
        iInterval_ = std::min(300, iInterval_ + 50);

      // reset all the indicators that are used to decide whether the compatible
      // variables must be updated
      iCurrent_ = 0;
      updateCompatibles_ = false;
      coConsecutiveCompatibles_ = 0;
      coDegenCompatibles_ = 0;
    } else
      iInterval_++;
  }
  // otherwise, update the value of the priority factor depending on the number of
  // consecutive degenerate pivots
  //
  else {
    // the idea is that when a lot of consecutive pivots are degenerate, there is
    // a potentially hign added value in putting a very high priroity on compatible
    // variables
    if (modelPE_->coDegeneratePivotsConsecutive() >= 10) {
      psiTmp = 0.01;

#if PE_DEBUG >= 1
      std::cout << "Lower the priority factor to " << psiTmp << std::endl;
      std::cout << "Consecutive degenerate pivots=" << modelPE_->coDegeneratePivotsConsecutive() << std::endl;
#endif
    }
  }
  iCurrent_++;
  if (modelPE_->doStatistics())
    modelPE_->stopTimer();

  // Do the pricing and give priority to dual compatible variables
  //
  int iRow;
  const int *pivotVariable = model_->pivotVariable();
  double tolerance = model_->currentPrimalTolerance();
  // we can't really trust infeasibilities if there is primal error
  if (model_->largestPrimalError() > 1.0e-8)
    tolerance *= model_->largestPrimalError() / 1.0e-8;
  double largest = 0.0;
  double largestComp = 0.0;
  int chosenRow = -1;
  int chosenRowComp = -1;
  int numberRows = model_->numberRows();
#ifdef CLP_DUAL_COLUMN_MULTIPLIER
  int numberColumns = model_->numberColumns();
#endif

  // only check the compatible variables when the bidimensional factor is less than 1
  // and the ratio of compatible variables is larger than 0.01
  // the percentage of compatible variables is computed as the ratio to the
  // smallest number among columns and rows
  bool checkCompatibles = true;
  double ratioCompatibles = static_cast< double >(modelPE_->coCompatibleRows()) / static_cast< double >(std::min(model_->numberRows(), model_->numberColumns()));

  if (psi_ >= 1.0 || ratioCompatibles < 0.01)
    checkCompatibles = false;

  // check the infeasibility of the variables (there is no partial pricing!)
  for (iRow = 0; iRow < numberRows; iRow++) {
    int iSequence = pivotVariable[iRow];
    double value = model_->solution(iSequence);
    double lower = model_->lower(iSequence);
    double upper = model_->upper(iSequence);
    double infeas = CoinMax(value - upper, lower - value);
    double largestMax = std::max(psi_ * largest, largestComp);
    if (infeas > tolerance) {
#ifdef CLP_DUAL_COLUMN_MULTIPLIER
      if (iSequence < numberColumns)
        infeas *= CLP_DUAL_COLUMN_MULTIPLIER;
#endif
      if (infeas > largestMax) {
        if (model_->flagged(iSequence)) {
        } else if (checkCompatibles && modelPE_->isCompatibleRow(iRow) && infeas > largestComp) {
          chosenRowComp = iRow;
          largestComp = infeas;
        } else if (infeas > largest) {
          chosenRow = iRow;
          largest = infeas;
        }
      }
    }
  }

  // choose a compatible or an incompatible row depending on the
  // largest values and on the factor of preference psi_
  if (modelPE_->doStatistics())
    modelPE_->startTimer();
  if (chosenRowComp >= 0 && largestComp >= psiTmp * largest) {
    chosenRow = chosenRowComp;

    if (modelPE_->doStatistics()) {
      // record the number of pivots done on compatible variables
      // that would not have been done without positive edge
      if (largestComp < largest)
        modelPE_->addPriorityPivot();
    }
#if PE_DEBUG >= 1
    modelPE_->checkCompatibilityRow(chosenRow);
#endif
  }
  if (psi_ < 1 && modelPE_->isCompatibleRow(chosenRow)) {
    modelPE_->isLastPivotCompatible(true);
    modelPE_->addCompatiblePivot();
  } else
    modelPE_->isLastPivotCompatible(false);
  if (modelPE_->doStatistics())
    modelPE_->stopTimer();

  modelPE_->updateLastObjectiveValue();
  return chosenRow;
}

//-------------------------------------------------------------------
// updateWeights
// Update the compatible variables and
// call the base class method to update weights
//-------------------------------------------------------------------

double ClpPEDualRowDantzig::updateWeights(CoinIndexedVector *input,
  CoinIndexedVector *spare,
  CoinIndexedVector *spare2,
  CoinIndexedVector *updatedColumn)
{
  double value = ClpDualRowDantzig::updateWeights(input, spare, spare2, updatedColumn);

  return value;
}
/* Save weights - this may initialize weights as well
   This is as parent but may initialize ClpPESimplex
*/
void ClpPEDualRowDantzig::saveWeights(ClpSimplex *model, int mode)
{
  // See if we need to initialize ClpPESimplex
  if (!modelPE_ || model != modelPE_->clpModel()) {
    delete modelPE_;
    modelPE_ = new ClpPESimplex(model);
  }
  ClpDualRowDantzig::saveWeights(model, mode);
}

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