basis_representation.h 15.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
// 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_