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

#ifndef OsiColCut_H
#define OsiColCut_H

#include <string>

#include "CoinPackedVector.hpp"

#include "OsiCollections.hpp"
#include "OsiCut.hpp"

/** Column Cut Class

Column Cut Class has:
  <ul>
  <li>a sparse vector of column lower bounds
  <li>a sparse vector of column upper bounds
  </ul>
*/
class OsiColCut : public OsiCut {
   friend void OsiColCutUnitTest(const OsiSolverInterface * baseSiP,
				 const std::string & mpsDir);

public:

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

  /**@name Setting column bounds */
  //@{
  /// Set column lower bounds
  inline void setLbs(
    int nElements,
    const int * colIndices,
    const double * lbElements );

  /// Set column lower bounds from a packed vector
  inline void setLbs( const CoinPackedVector & lbs );

  /// Set column upper bounds
  inline void setUbs(
    int nElements,
    const int * colIndices,
    const double * ubElements );

  /// Set column upper bounds from a packed vector
  inline void setUbs( const CoinPackedVector & ubs );
  //@}

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

  /**@name Getting column bounds */
  //@{
  /// Get column lower bounds
  inline const CoinPackedVector & lbs() const;
  /// Get column upper bounds
  inline const CoinPackedVector & ubs() const;
  //@}

  /**@name Comparison operators  */
  //@{
#if __GNUC__ != 2
  using OsiCut::operator== ;
#endif
  /** equal - true if lower bounds, upper bounds,
  and OsiCut are equal.
  */
  inline virtual bool operator==(const OsiColCut& rhs) const;

#if __GNUC__ != 2
  using OsiCut::operator!= ;
#endif
  /// not equal
  inline virtual bool operator!=(const OsiColCut& rhs) const;
  //@}


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

  /**@name Sanity checks on cut */
  //@{
  /** Returns true if the cut is consistent with respect to itself.
  This checks to ensure that:
  <ul>
  <li>The bound vectors do not have duplicate indices,
  <li>The bound vectors indices are >=0
  </ul>
  */
  inline virtual bool consistent() const override;

  /** Returns true if cut is consistent with respect to the solver
  interface's model. This checks to ensure that
  the lower & upperbound packed vectors:
  <ul>
  <li>do not have an index >= the number of column is the model.
  </ul>
  */
  inline virtual bool consistent(const OsiSolverInterface& im) const override;

  /** Returns true if the cut is infeasible with respect to its bounds and the
  column bounds in the solver interface's models.
  This checks whether:
  <ul>
  <li>the maximum of the new and existing lower bounds is strictly
  greater than the minimum of the new and existing upper bounds.
</ul>
  */
  inline virtual bool infeasible(const OsiSolverInterface &im) const override;
  /** Returns infeasibility of the cut with respect to solution
      passed in i.e. is positive if cuts off that solution.
      solution is getNumCols() long..
  */
  virtual double violated(const double * solution) const override;
  //@}

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

  /**@name Constructors and destructors */
  //@{
  /// Assignment operator
  OsiColCut & operator=( const OsiColCut& rhs);

  /// Copy constructor
  OsiColCut ( const OsiColCut &);

  /// Default Constructor
  OsiColCut ();

  /// Clone
  virtual OsiColCut * clone() const;

  /// Destructor
  virtual ~OsiColCut ();
  //@}

  /**@name Debug stuff */
  //@{
    /// Print cuts in collection
  virtual void print() const override;
  //@}

private:

  /**@name Private member data */
  //@{
  /// Lower bounds
  CoinPackedVector lbs_;
  /// Upper bounds
  CoinPackedVector ubs_;
  //@}

};



//-------------------------------------------------------------------
// Set lower & upper bound vectors
//-------------------------------------------------------------------
void OsiColCut::setLbs(
                       int size,
                       const int * colIndices,
                       const double * lbElements )
{
  lbs_.setVector(size,colIndices,lbElements);
}
//
void OsiColCut::setUbs(
                       int size,
                       const int * colIndices,
                       const double * ubElements )
{
  ubs_.setVector(size,colIndices,ubElements);
}
//
void OsiColCut::setLbs( const CoinPackedVector & lbs )
{
  lbs_ = lbs;
}
//
void OsiColCut::setUbs( const CoinPackedVector & ubs )
{
  ubs_ = ubs;
}

//-------------------------------------------------------------------
// Get Column Lower Bounds and Column Upper Bounds
//-------------------------------------------------------------------
const CoinPackedVector & OsiColCut::lbs() const
{
  return lbs_;
}
//
const CoinPackedVector & OsiColCut::ubs() const
{
  return ubs_;
}

//----------------------------------------------------------------
// == operator
//-------------------------------------------------------------------
bool
OsiColCut::operator==(
                      const OsiColCut& rhs) const
{
  if ( this->OsiCut::operator!=(rhs) )
    return false;
  if ( lbs() != rhs.lbs() )
    return false;
  if ( ubs() != rhs.ubs() )
    return false;
  return true;
}
//
bool
OsiColCut::operator!=(
                      const OsiColCut& rhs) const
{
  return !( (*this)==rhs );
}

//----------------------------------------------------------------
// consistent & infeasible
//-------------------------------------------------------------------
bool OsiColCut::consistent() const
{
  const CoinPackedVector & lb = lbs();
  const CoinPackedVector & ub = ubs();
  // Test for consistent cut.
  // Are packed vectors consistent?
  lb.duplicateIndex("consistent", "OsiColCut");
  ub.duplicateIndex("consistent", "OsiColCut");
  if ( lb.getMinIndex() < 0 ) return false;
  if ( ub.getMinIndex() < 0 ) return false;
  return true;
}
//
bool OsiColCut::consistent(const OsiSolverInterface& im) const
{
  const CoinPackedVector & lb = lbs();
  const CoinPackedVector & ub = ubs();

  // Test for consistent cut.
  if ( lb.getMaxIndex() >= im.getNumCols() ) return false;
  if ( ub.getMaxIndex() >= im.getNumCols() ) return false;

  return true;
}

#if 0
bool OsiColCut::feasible(const OsiSolverInterface &im) const
{
  const double * oldColLb = im.getColLower();
  const double * oldColUb = im.getColUpper();
  const CoinPackedVector & cutLbs = lbs();
  const CoinPackedVector & cutUbs = ubs();
  int i;

  for ( i=0; i<cutLbs.size(); i++ ) {
    int colIndx = cutLbs.indices()[i];
    double newLb;
    if ( cutLbs.elements()[i] > oldColLb[colIndx] )
      newLb = cutLbs.elements()[i];
    else
      newLb = oldColLb[colIndx];

    double newUb = oldColUb[colIndx];
    if ( cutUbs.indexExists(colIndx) )
      if ( cutUbs[colIndx] < newUb ) newUb = cutUbs[colIndx];
      if ( newLb > newUb )
        return false;
  }

  for ( i=0; i<cutUbs.size(); i++ ) {
    int colIndx = cutUbs.indices()[i];
    double newUb = cutUbs.elements()[i] < oldColUb[colIndx] ? cutUbs.elements()[i] : oldColUb[colIndx];
    double newLb = oldColLb[colIndx];
    if ( cutLbs.indexExists(colIndx) )
      if ( cutLbs[colIndx] > newLb ) newLb = cutLbs[colIndx];
      if ( newUb < newLb )
        return false;
  }

  return true;
}
#endif


bool OsiColCut::infeasible(const OsiSolverInterface &im) const
{
  const double * oldColLb = im.getColLower();
  const double * oldColUb = im.getColUpper();
  const CoinPackedVector & cutLbs = lbs();
  const CoinPackedVector & cutUbs = ubs();
  int i;

  for ( i=0; i<cutLbs.getNumElements(); i++ ) {
    int colIndx = cutLbs.getIndices()[i];
    double newLb= cutLbs.getElements()[i] > oldColLb[colIndx] ?
       cutLbs.getElements()[i] : oldColLb[colIndx];

    double newUb = oldColUb[colIndx];
    if ( cutUbs.isExistingIndex(colIndx) )
      if ( cutUbs[colIndx] < newUb ) newUb = cutUbs[colIndx];
    if ( newLb > newUb )
      return true;
  }

  for ( i=0; i<cutUbs.getNumElements(); i++ ) {
    int colIndx = cutUbs.getIndices()[i];
    double newUb = cutUbs.getElements()[i] < oldColUb[colIndx] ?
       cutUbs.getElements()[i] : oldColUb[colIndx];
    double newLb = oldColLb[colIndx];
    if ( cutLbs.isExistingIndex(colIndx) )
      if ( cutLbs[colIndx] > newLb ) newLb = cutLbs[colIndx];
    if ( newUb < newLb )
      return true;
  }

  return false;
}

#endif
