// Copyright 2010-2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef OR_TOOLS_GLOP_BASIS_REPRESENTATION_H_
#define OR_TOOLS_GLOP_BASIS_REPRESENTATION_H_

#include "ortools/base/logging.h"
#include "ortools/glop/lu_factorization.h"
#include "ortools/glop/parameters.pb.h"
#include "ortools/glop/rank_one_update.h"
#include "ortools/glop/status.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/lp_data/permutation.h"
#include "ortools/lp_data/scattered_vector.h"
#include "ortools/lp_data/sparse.h"
#include "ortools/util/stats.h"

namespace operations_research {
namespace glop {

// An eta matrix E corresponds to the identity matrix except for one column e of
// index j. In particular, B.E is the matrix of the new basis obtained from B by
// replacing the j-th vector of B by B.e, note that this is exactly what happens
// during a "pivot" of the current basis in the simplex algorithm.
//
// E = [  1  ...  0    e_0    0  ...  0
//       ... ... ...   ...   ... ... ...
//        0  ...  1  e_{j-1}  0  ...  0
//        0  ...  0    e_j    0  ...  0
//        0  ...  0  e_{j+1}  1  ...  0
//       ... ... ...   ...   ... ... ...
//        0  ...  0  e_{n-1}  0  ...  1 ]
//
// The inverse of the eta matrix is:
// E^{-1} = [  1  ...  0      -e_0/e_j  0  ...  0
//            ... ... ...     ...      ... ... ...
//             0  ...  1  -e_{j-1}/e_j  0  ...  0
//             0  ...  0         1/e_j  0  ...  0
//             0  ...  0  -e_{j+1}/e_j  1  ...  0
//            ... ... ...     ...      ... ... ...
//             0  ...  0  -e_{n-1}/e_j  0  ...  1 ]
class EtaMatrix {
 public:
  EtaMatrix(ColIndex eta_col, const ScatteredColumn& direction);
  virtual ~EtaMatrix();

  // Solves the system y.E = c, 'c' beeing the initial value of 'y'.
  // Then y = c.E^{-1}, so y is equal to c except for
  //    y_j = (c_j - \sum_{i != j}{c_i * e_i}) / e_j.
  void LeftSolve(DenseRow* y) const;

  // Same as LeftSolve(), but 'pos' contains the non-zero positions of c. The
  // order of the positions is not important, but there must be no duplicates.
  // The values not in 'pos' are not used. If eta_col_ was not already in 'pos',
  // it is added.
  void SparseLeftSolve(DenseRow* y, ColIndexVector* pos) const;

  // Solves the system E.d = a, 'a' beeing the initial value of 'd'.
  // Then d = E^{-1}.a = [ a_0     - e_0   *   a_j / e_j
  //                                 ...
  //                       a_{j-1} - e_{j-1} * a_j / e_j
  //                                           a_j / e_j
  //                       a_{j+1} - e_{j+1} * a_j / e_j
  //                                 ...
  //                       a_{n-1} - e_{n-1} * a_j / e_j ]
  void RightSolve(DenseColumn* d) const;

 private:
  // Internal RightSolve() and LeftSolve() implementations using either the
  // dense or the sparse representation of the eta vector.
  void LeftSolveWithDenseEta(DenseRow* y) const;
  void LeftSolveWithSparseEta(DenseRow* y) const;
  void RightSolveWithDenseEta(DenseColumn* d) const;
  void RightSolveWithSparseEta(DenseColumn* d) const;

  // If an eta vector density is smaller than this threshold, we use the
  // sparse version of the Solve() functions rather than the dense version.
  // TODO(user): Detect automatically a good parameter? 0.5 is a good value on
  // the Netlib (I only did a few experiments though). Note that in the future
  // we may not even keep the dense representation at all.
  static const Fractional kSparseThreshold;

  const ColIndex eta_col_;
  const Fractional eta_col_coefficient_;

  // Note that to optimize solves, the position eta_col_ is set to 0.0 and
  // stored in eta_col_coefficient_ instead.
  DenseColumn eta_coeff_;
  SparseColumn sparse_eta_coeff_;

  DISALLOW_COPY_AND_ASSIGN(EtaMatrix);
};

// An eta factorization corresponds to the product of k eta matrices,
// i.e. E = E_0.E_1. ... .E_{k-1}
// It is used to solve two systems:
//   - E.d = a (where a is usually the entering column).
//   - y.E = c (where c is usually the objective row).
class EtaFactorization {
 public:
  EtaFactorization();
  virtual ~EtaFactorization();

  // Deletes all eta matrices.
  void Clear();

  // Updates the eta factorization, i.e. adds the new eta matrix defined by
  // the leaving variable and the corresponding eta column.
  void Update(ColIndex entering_col, RowIndex leaving_variable_row,
              const ScatteredColumn& direction);

  // Left solves all systems from right to left, i.e. y_i = y_{i+1}.(E_i)^{-1}
  void LeftSolve(DenseRow* y) const;

  // Same as LeftSolve(), but 'pos' contains the non-zero positions of c. The
  // order of the positions is not important, but there must be no duplicates.
  // The values not in 'pos' are not used. If eta_col_ was not already in 'pos',
  // it is added.
  void SparseLeftSolve(DenseRow* y, ColIndexVector* pos) const;

  // Right solves all systems from left to right, i.e. E_i.d_{i+1} = d_i
  void RightSolve(DenseColumn* d) const;

 private:
  std::vector<EtaMatrix*> eta_matrix_;

  DISALLOW_COPY_AND_ASSIGN(EtaFactorization);
};

// A basis factorization is the product of an eta factorization and
// a L.U decomposition, i.e. B = L.U.E_0.E_1. ... .E_{k-1}
// It is used to solve two systems:
//   - B.d = a where a is the entering column.
//   - y.B = c where c is the objective row.
//
// To speed-up and improve stability the factorization is refactorized at least
// every 'refactorization_period' updates.
//
// This class does not take ownership of the underlying matrix and basis, and
// thus they must outlive this class (and keep the same address in memory).
class BasisFactorization {
 public:
  BasisFactorization(const CompactSparseMatrix* compact_matrix,
                     const RowToColMapping* basis);
  virtual ~BasisFactorization();

  // Sets the parameters for this component.
  void SetParameters(const GlopParameters& parameters) {
    max_num_updates_ = parameters.basis_refactorization_period();
    use_middle_product_form_update_ =
        parameters.use_middle_product_form_update();
    parameters_ = parameters;
    lu_factorization_.SetParameters(parameters);
  }

  // Returns the column permutation used by the LU factorization.
  // This call only makes sense if the basis was just refactorized.
  const ColumnPermutation& GetColumnPermutation() const {
    DCHECK(IsRefactorized());
    return lu_factorization_.GetColumnPermutation();
  }

  // Sets the column permutation used by the LU factorization to the identity.
  // Hense the Solve() results will be computed without this permutation.
  // This call only makes sense if the basis was just refactorized.
  void SetColumnPermutationToIdentity() {
    DCHECK(IsRefactorized());
    lu_factorization_.SetColumnPermutationToIdentity();
  }

  // Clears the factorization and resets it to an identity matrix of size given
  // by matrix_.num_rows().
  void Clear();

  // Clears the factorization and initializes the class using the current
  // matrix_ and basis_. This is fast if IsIdentityBasis() is true, otherwise
  // it will trigger a refactorization and will return an error if the matrix
  // could not be factorized.
  ABSL_MUST_USE_RESULT Status Initialize();

  // Return the number of rows in the basis.
  RowIndex GetNumberOfRows() const { return compact_matrix_.num_rows(); }

  // Clears eta factorization and refactorizes LU.
  // Nothing happens if this is called on an already refactorized basis.
  // Returns an error if the matrix could not be factorized: i.e. not a basis.
  ABSL_MUST_USE_RESULT Status Refactorize();

  // Like Refactorize(), but do it even if IsRefactorized() is true.
  // Call this if the underlying basis_ changed and Update() wasn't called.
  ABSL_MUST_USE_RESULT Status ForceRefactorization();

  // Returns true if the factorization was just recomputed.
  bool IsRefactorized() const;

  // Updates the factorization. The 'eta' column will be modified with a swap to
  // avoid a copy (only if the standard eta update is used). Returns an error if
  // the matrix could not be factorized: i.e. not a basis.
  ABSL_MUST_USE_RESULT Status Update(ColIndex entering_col,
                                     RowIndex leaving_variable_row,
                                     const ScatteredColumn& direction);

  // Left solves the system y.B = rhs, where y initialy contains rhs.
  void LeftSolve(ScatteredRow* y) const;

  // Left solves the system y.B = e_j, where e_j has only 1 non-zero
  // coefficient of value 1.0 at position 'j'.
  void LeftSolveForUnitRow(ColIndex j, ScatteredRow* y) const;

  // Same as LeftSolveForUnitRow() but does not update any internal data.
  void TemporaryLeftSolveForUnitRow(ColIndex j, ScatteredRow* y) const;

  // Right solves the system B.d = a where the input is the initial value of d.
  void RightSolve(ScatteredColumn* d) const;

  // Same as RightSolve() for matrix.column(col). This also exploits its
  // sparsity.
  void RightSolveForProblemColumn(ColIndex col, ScatteredColumn* d) const;

  // Specialized version for ComputeTau() in DualEdgeNorms. This reuses an
  // intermediate result of the last LeftSolveForUnitRow() in order to save a
  // permutation if it is available. Note that the input 'a' should always be
  // equal to the last result of LeftSolveForUnitRow() and will be used for a
  // DCHECK() or if the intermediate result wasn't kept.
  const DenseColumn& RightSolveForTau(const ScatteredColumn& a) const;

  // Returns the norm of B^{-1}.a, this is a specific function because
  // it is a bit faster and it avoids polluting the stats of RightSolve().
  // It can be called only when IsRefactorized() is true.
  Fractional RightSolveSquaredNorm(const ColumnView& a) const;

  // Returns the norm of (B^T)^{-1}.e_row where e is an unit vector.
  // This is a bit faster and avoids polluting the stats of LeftSolve().
  // It can be called only when IsRefactorized() is true.
  Fractional DualEdgeSquaredNorm(RowIndex row) const;

  // Computes the condition number of B.
  // For a given norm, this is the matrix norm times the norm of its inverse.
  // A condition number greater than 1E7 will lead to precision problems.
  Fractional ComputeOneNormConditionNumber() const;
  Fractional ComputeInfinityNormConditionNumber() const;
  Fractional ComputeInfinityNormConditionNumberUpperBound() const;

  // Computes the 1-norm of B.
  // The 1-norm |A| is defined as max_j sum_i |a_ij|
  // http://en.wikipedia.org/wiki/Matrix_norm
  Fractional ComputeOneNorm() const;

  // Computes the infinity-norm of B.
  // The infinity-norm |A| is defined as max_i sum_j |a_ij|
  // http://en.wikipedia.org/wiki/Matrix_norm
  Fractional ComputeInfinityNorm() const;

  // Computes the 1-norm of the inverse of B.
  // For this we iteratively solve B.x = e_j, where e_j is the jth unit vector.
  // The result of this computation is the jth column of B^-1.
  Fractional ComputeInverseOneNorm() const;

  // Computes the infinity-norm of the inverse of B.
  Fractional ComputeInverseInfinityNorm() const;

  // Stats related function.
  // Note that ResetStats() could be const, but until needed it is not to
  // prevent anyone holding a const BasisFactorization& to call it.
  std::string StatString() const {
    return stats_.StatString() + lu_factorization_.StatString();
  }
  void ResetStats() { stats_.Reset(); }

  // The deterministic time used by this class. It is incremented for each
  // solve and each factorization.
  double DeterministicTime() const;

 private:
  // Return true if the submatrix of matrix_ given by basis_ is exactly the
  // identity (without permutation).
  bool IsIdentityBasis() const;

  // Updates the factorization using the middle product form update.
  // Qi Huangfu, J. A. Julian Hall, "Novel update techniques for the revised
  // simplex method", 28 january 2013, Technical Report ERGO-13-0001
  ABSL_MUST_USE_RESULT Status
  MiddleProductFormUpdate(ColIndex entering_col, RowIndex leaving_variable_row);

  // Increases the deterministic time for a solve operation with a vector having
  // this number of non-zero entries (it can be an approximation).
  void BumpDeterministicTimeForSolve(int num_entries) const;

  // Stats about this class.
  struct Stats : public StatsGroup {
    Stats()
        : StatsGroup("BasisFactorization"),
          refactorization_interval("refactorization_interval", this) {}
    IntegerDistribution refactorization_interval;
  };

  // Mutable because we track the running time of const method like
  // RightSolve() and LeftSolve().
  mutable Stats stats_;
  GlopParameters parameters_;

  // References to the basis subpart of the linear program matrix.
  const CompactSparseMatrix& compact_matrix_;
  const RowToColMapping& basis_;

  // Middle form product update factorization and scratchpad_ used to construct
  // new rank one matrices.
  RankOneUpdateFactorization rank_one_factorization_;
  mutable DenseColumn scratchpad_;
  mutable std::vector<RowIndex> scratchpad_non_zeros_;

  // This is used by RightSolveForTau(). It holds an intermediate result from
  // the last LeftSolveForUnitRow() and also the final result of
  // RightSolveForTau().
  mutable ScatteredColumn tau_;

  // Booleans controlling the interaction between LeftSolveForUnitRow() that may
  // or may not keep its intermediate results for the optimized
  // RightSolveForTau().
  //
  // tau_computation_can_be_optimized_ will be true iff LeftSolveForUnitRow()
  // kept its intermediate result when it was called and the factorization
  // didn't change since then. If it is true, then RightSolveForTau() can use
  // this result for a faster computation.
  //
  // tau_is_computed_ is used as an heuristic by LeftSolveForUnitRow() to decide
  // if it is worth keeping its intermediate result (which is sligthly slower).
  // It is simply set to true by RightSolveForTau() and to false by
  // LeftSolveForUnitRow(), this way the optimization will automatically switch
  // itself on when switching from the primal simplex (where RightSolveForTau()
  // is never called) to the dual where it is called after each
  // LeftSolveForUnitRow(), and back off again in the other direction.
  mutable bool tau_computation_can_be_optimized_;
  mutable bool tau_is_computed_;

  // Data structure to store partial solve results for the middle form product
  // update. See LeftSolveForUnitRow() and RightSolveForProblemColumn(). We use
  // two CompactSparseMatrix to have a better cache behavior when solving with
  // the rank_one_factorization_.
  mutable CompactSparseMatrix storage_;
  mutable CompactSparseMatrix right_storage_;
  mutable ColMapping left_pool_mapping_;
  mutable ColMapping right_pool_mapping_;

  bool use_middle_product_form_update_;
  int max_num_updates_;
  int num_updates_;
  EtaFactorization eta_factorization_;
  LuFactorization lu_factorization_;

  // mutable because the Solve() functions are const but need to update this.
  mutable double deterministic_time_;

  DISALLOW_COPY_AND_ASSIGN(BasisFactorization);
};

}  // namespace glop
}  // namespace operations_research

#endif  // OR_TOOLS_GLOP_BASIS_REPRESENTATION_H_
