2024-01-04 13:43:15 +01:00
|
|
|
// Copyright 2010-2024 Google LLC
|
2014-07-08 17:35:15 +00:00
|
|
|
// 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.
|
2014-07-09 15:18:27 +00:00
|
|
|
|
2014-12-16 11:35:09 +00:00
|
|
|
#ifndef OR_TOOLS_GLOP_LU_FACTORIZATION_H_
|
|
|
|
|
#define OR_TOOLS_GLOP_LU_FACTORIZATION_H_
|
|
|
|
|
|
2022-05-02 16:51:52 +02:00
|
|
|
#include <string>
|
2022-07-22 14:35:40 +02:00
|
|
|
#include <vector>
|
2022-05-02 16:51:52 +02:00
|
|
|
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/glop/markowitz.h"
|
|
|
|
|
#include "ortools/glop/parameters.pb.h"
|
|
|
|
|
#include "ortools/glop/status.h"
|
2019-07-18 11:35:32 -07:00
|
|
|
#include "ortools/lp_data/lp_types.h"
|
2019-07-24 13:49:08 -07:00
|
|
|
#include "ortools/lp_data/permutation.h"
|
|
|
|
|
#include "ortools/lp_data/scattered_vector.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/lp_data/sparse.h"
|
2019-07-24 13:49:08 -07:00
|
|
|
#include "ortools/lp_data/sparse_column.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/util/stats.h"
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace glop {
|
|
|
|
|
|
|
|
|
|
// An LU-Factorization class encapsulating the LU factorization data and
|
|
|
|
|
// algorithms. The actual algorithm is in markowitz.h and .cc. This class holds
|
|
|
|
|
// all the Solve() functions that deal with the permutations and the L and U
|
|
|
|
|
// factors once they are computed.
|
|
|
|
|
class LuFactorization {
|
2020-10-22 23:36:58 +02:00
|
|
|
public:
|
2014-07-08 09:27:02 +00:00
|
|
|
LuFactorization();
|
|
|
|
|
|
2023-08-30 10:04:47 -04:00
|
|
|
// This type is neither copyable nor movable.
|
|
|
|
|
LuFactorization(const LuFactorization&) = delete;
|
|
|
|
|
LuFactorization& operator=(const LuFactorization&) = delete;
|
|
|
|
|
|
2014-07-08 09:27:02 +00:00
|
|
|
// Returns true if the LuFactorization is a factorization of the identity
|
|
|
|
|
// matrix. In this state, all the Solve() functions will work for any
|
|
|
|
|
// vector dimension.
|
|
|
|
|
bool IsIdentityFactorization() { return is_identity_factorization_; }
|
|
|
|
|
|
|
|
|
|
// Clears internal data structure and reset this class to the factorization
|
|
|
|
|
// of an identity matrix.
|
|
|
|
|
void Clear();
|
|
|
|
|
|
|
|
|
|
// Computes an LU-decomposition for a given matrix B. If for some reason,
|
|
|
|
|
// there was an error, then the factorization is reset to the one of the
|
|
|
|
|
// identity matrix, and an error is reported.
|
|
|
|
|
//
|
|
|
|
|
// Note(user): Since a client must use the result, there is little chance of
|
|
|
|
|
// it being confused by this revert to identity factorization behavior. The
|
|
|
|
|
// reason behind it is that this way, calling any public function of this
|
|
|
|
|
// class will never cause a crash of the program.
|
2019-06-17 11:34:10 +02:00
|
|
|
ABSL_MUST_USE_RESULT Status
|
2020-10-29 14:25:39 +01:00
|
|
|
ComputeFactorization(const CompactSparseMatrixView& compact_matrix);
|
2014-07-08 09:27:02 +00:00
|
|
|
|
2021-08-20 13:37:19 +02:00
|
|
|
// Given a set of columns, find a maximum linearly independent subset that can
|
|
|
|
|
// be factorized in a stable way, and complete it into a square matrix using
|
|
|
|
|
// slack columns. The initial set can have less, more or the same number of
|
|
|
|
|
// columns as the number of rows.
|
|
|
|
|
RowToColMapping ComputeInitialBasis(const CompactSparseMatrix& matrix,
|
|
|
|
|
const std::vector<ColIndex>& candidates);
|
|
|
|
|
|
2014-07-08 09:27:02 +00:00
|
|
|
// Returns the column permutation used by the LU factorization.
|
2020-10-29 14:25:39 +01:00
|
|
|
const ColumnPermutation& GetColumnPermutation() const { return col_perm_; }
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Sets the column permutation to the identity permutation. The idea is that
|
|
|
|
|
// the column permutation can be incorporated in the basis RowToColMapping,
|
|
|
|
|
// and once this is done, then a client can call this and effectively remove
|
|
|
|
|
// the need for a column permutation on each solve.
|
|
|
|
|
void SetColumnPermutationToIdentity() {
|
|
|
|
|
col_perm_.clear();
|
|
|
|
|
inverse_col_perm_.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Solves 'B.x = b', x initially contains b, and is replaced by 'B^{-1}.b'.
|
|
|
|
|
// Since P.B.Q^{-1} = L.U, we have B = P^{-1}.L.U.Q.
|
|
|
|
|
// 1/ Solve P^{-1}.y = b for y by computing y = P.b,
|
|
|
|
|
// 2/ solve L.z = y for z,
|
|
|
|
|
// 3/ solve U.t = z for t,
|
|
|
|
|
// 4/ finally solve Q.x = t, by computing x = Q^{-1}.t.
|
2020-10-29 14:25:39 +01:00
|
|
|
void RightSolve(DenseColumn* x) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Solves 'y.B = r', y initially contains r, and is replaced by r.B^{-1}.
|
|
|
|
|
// Internally, it takes x = y^T, b = r^T and solves B^T.x = b.
|
|
|
|
|
// We have P.B.Q^{-1} = P.B.Q^T = L.U, thus (L.U)^T = Q.B^T.P^T.
|
|
|
|
|
// Therefore B^T = Q^{-1}.U^T.L^T.P^T.P^{-1} = Q^{-1}.U^T.L^T.P
|
|
|
|
|
// The procedure is thus:
|
|
|
|
|
// 1/ Solve Q^{-1}.y = b for y, by computing y = Q.b,
|
|
|
|
|
// 2/ solve U^T.z = y for z,
|
|
|
|
|
// 3/ solve L^T.t = z for t,
|
|
|
|
|
// 4/ finally, solve P.x = t for x by computing x = P^{-1}.t.
|
2020-10-29 14:25:39 +01:00
|
|
|
void LeftSolve(DenseRow* y) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
2018-02-16 17:08:01 +01:00
|
|
|
// More fine-grained right/left solve functions that may exploit the initial
|
|
|
|
|
// non-zeros of the input vector if non-empty. Note that a solve involving L
|
|
|
|
|
// actually solves P^{-1}.L and a solve involving U actually solves U.Q. To
|
|
|
|
|
// solve a system with the initial matrix B, one needs to call:
|
2014-07-08 09:27:02 +00:00
|
|
|
// - RightSolveL() and then RightSolveU() for a right solve (B.x = initial x).
|
|
|
|
|
// - LeftSolveU() and then LeftSolveL() for a left solve (y.B = initial y).
|
2020-10-29 14:25:39 +01:00
|
|
|
void RightSolveLWithNonZeros(ScatteredColumn* x) const;
|
|
|
|
|
void RightSolveUWithNonZeros(ScatteredColumn* x) const;
|
|
|
|
|
void LeftSolveUWithNonZeros(ScatteredRow* y) const;
|
2018-02-16 17:08:01 +01:00
|
|
|
|
|
|
|
|
// Specialized version of LeftSolveL() that may exploit the initial non_zeros
|
|
|
|
|
// of y if it is non empty. Moreover, if result_before_permutation is not
|
|
|
|
|
// NULL, it might be filled with the result just before row_perm_ is applied
|
|
|
|
|
// to it and true is returned. If result_before_permutation is not filled,
|
|
|
|
|
// then false is returned.
|
2020-10-29 14:25:39 +01:00
|
|
|
bool LeftSolveLWithNonZeros(ScatteredRow* y,
|
|
|
|
|
ScatteredColumn* result_before_permutation) const;
|
|
|
|
|
void LeftSolveLWithNonZeros(ScatteredRow* y) const;
|
2018-02-16 17:08:01 +01:00
|
|
|
|
|
|
|
|
// Specialized version of RightSolveLWithNonZeros() that takes a SparseColumn
|
|
|
|
|
// or a ScatteredColumn as input. non_zeros will either be cleared or set to
|
|
|
|
|
// the non zeros of the result. Important: the output x must be of the correct
|
2018-02-12 11:35:51 +01:00
|
|
|
// size and all zero.
|
2020-10-29 14:25:39 +01:00
|
|
|
void RightSolveLForColumnView(const ColumnView& b, ScatteredColumn* x) const;
|
|
|
|
|
void RightSolveLForScatteredColumn(const ScatteredColumn& b,
|
|
|
|
|
ScatteredColumn* x) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
2021-08-23 16:04:39 +02:00
|
|
|
// Specialized version of RightSolveLWithNonZeros() where x is originally
|
|
|
|
|
// equal to 'a' permuted by row_perm_. Note that 'a' is only used for DCHECK.
|
2020-10-29 14:25:39 +01:00
|
|
|
void RightSolveLWithPermutedInput(const DenseColumn& a,
|
|
|
|
|
ScatteredColumn* x) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Specialized version of LeftSolveU() for an unit right-hand side.
|
|
|
|
|
// non_zeros will either be cleared or set to the non zeros of the results.
|
|
|
|
|
// It also returns the value of col permuted by Q (which is the position
|
|
|
|
|
// of the unit-vector rhs in the solve system: y.U = rhs).
|
|
|
|
|
// Important: the output y must be of the correct size and all zero.
|
2020-10-29 14:25:39 +01:00
|
|
|
ColIndex LeftSolveUForUnitRow(ColIndex col, ScatteredRow* y) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Returns the given column of U.
|
|
|
|
|
// It will only be valid until the next call to GetColumnOfU().
|
2020-10-29 14:25:39 +01:00
|
|
|
const SparseColumn& GetColumnOfU(ColIndex col) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Returns the norm of B^{-1}.a
|
2020-10-29 14:25:39 +01:00
|
|
|
Fractional RightSolveSquaredNorm(const ColumnView& a) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Returns the norm of (B^T)^{-1}.e_row where e is an unit vector.
|
|
|
|
|
Fractional DualEdgeSquaredNorm(RowIndex row) const;
|
|
|
|
|
|
|
|
|
|
// The fill-in of the LU-factorization is defined as the sum of the number
|
|
|
|
|
// of entries of both the lower- and upper-triangular matrices L and U minus
|
|
|
|
|
// the number of entries in the initial matrix B.
|
|
|
|
|
//
|
|
|
|
|
// This returns the number of entries in lower + upper as the percentage of
|
|
|
|
|
// the number of entries in B.
|
2020-10-29 14:25:39 +01:00
|
|
|
double GetFillInPercentage(const CompactSparseMatrixView& matrix) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
2014-12-16 11:35:09 +00:00
|
|
|
// Returns the number of entries in L + U.
|
|
|
|
|
// If the factorization is the identity, this returns 0.
|
|
|
|
|
EntryIndex NumberOfEntries() const;
|
|
|
|
|
|
2014-07-08 09:27:02 +00:00
|
|
|
// Computes the determinant of the input matrix B.
|
|
|
|
|
// Since P.B.Q^{-1} = L.U, det(P) * det(B) * det(Q^{-1}) = det(L) * det(U).
|
|
|
|
|
// det(L) = 1 since L is a lower-triangular matrix with 1 on the diagonal.
|
|
|
|
|
// det(P) = +1 or -1 (by definition it is the sign of the permutation P)
|
|
|
|
|
// det(Q^{-1}) = +1 or -1 (the sign of the permutation Q^{-1})
|
|
|
|
|
// Finally det(U) = product of the diagonal elements of U, since U is an
|
|
|
|
|
// upper-triangular matrix.
|
|
|
|
|
// Taking all this into account:
|
|
|
|
|
// det(B) = sign(P) * sign(Q^{-1}) * prod_i u_ii .
|
|
|
|
|
Fractional ComputeDeterminant() const;
|
|
|
|
|
|
|
|
|
|
// Computes the 1-norm of the inverse of the input matrix 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.
|
|
|
|
|
// The 1-norm |B| is defined as max_j sum_i |a_ij|
|
|
|
|
|
// http://en.wikipedia.org/wiki/Matrix_norm
|
|
|
|
|
Fractional ComputeInverseOneNorm() const;
|
|
|
|
|
|
|
|
|
|
// Computes the infinity-norm of the inverse of the input matrix B.
|
|
|
|
|
// The infinity-norm |B| is defined as max_i sum_j |a_ij|
|
|
|
|
|
// http://en.wikipedia.org/wiki/Matrix_norm
|
|
|
|
|
Fractional ComputeInverseInfinityNorm() const;
|
|
|
|
|
|
|
|
|
|
// Computes the condition number of the input matrix B.
|
|
|
|
|
// For a given norm, this is the matrix norm times the norm of its inverse.
|
|
|
|
|
//
|
|
|
|
|
// Note that because the LuFactorization class does not keep the
|
|
|
|
|
// non-factorized matrix in memory, it needs to be passed to these functions.
|
|
|
|
|
// It is up to the client to pass exactly the same matrix as the one used
|
|
|
|
|
// for ComputeFactorization().
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): separate this from LuFactorization.
|
2019-06-17 11:34:10 +02:00
|
|
|
Fractional ComputeOneNormConditionNumber(
|
2020-10-29 14:25:39 +01:00
|
|
|
const CompactSparseMatrixView& matrix) const;
|
2019-06-17 11:34:10 +02:00
|
|
|
Fractional ComputeInfinityNormConditionNumber(
|
2020-10-29 14:25:39 +01:00
|
|
|
const CompactSparseMatrixView& matrix) const;
|
2018-07-24 08:28:16 -07:00
|
|
|
Fractional ComputeInverseInfinityNormUpperBound() const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Sets the current parameters.
|
2020-10-29 14:25:39 +01:00
|
|
|
void SetParameters(const GlopParameters& parameters) {
|
2014-07-08 09:27:02 +00:00
|
|
|
parameters_ = parameters;
|
|
|
|
|
markowitz_.SetParameters(parameters);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-20 14:28:11 -08:00
|
|
|
// Returns a string containing the statistics for this class.
|
2014-07-08 09:27:02 +00:00
|
|
|
std::string StatString() const {
|
|
|
|
|
return stats_.StatString() + markowitz_.StatString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is only used for testing and in debug mode.
|
|
|
|
|
// TODO(user): avoid the matrix conversion by multiplying TriangularMatrix
|
|
|
|
|
// directly.
|
2020-10-29 14:25:39 +01:00
|
|
|
void ComputeLowerTimesUpper(SparseMatrix* product) const {
|
2014-07-08 09:27:02 +00:00
|
|
|
SparseMatrix temp_lower, temp_upper;
|
|
|
|
|
lower_.CopyToSparseMatrix(&temp_lower);
|
|
|
|
|
upper_.CopyToSparseMatrix(&temp_upper);
|
|
|
|
|
product->PopulateFromProduct(temp_lower, temp_upper);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 17:05:54 +01:00
|
|
|
// Returns the deterministic time of the last factorization.
|
|
|
|
|
double DeterministicTimeOfLastFactorization() const;
|
|
|
|
|
|
2014-07-08 09:27:02 +00:00
|
|
|
// Visible for testing.
|
2020-10-29 14:25:39 +01:00
|
|
|
const RowPermutation& row_perm() const { return row_perm_; }
|
|
|
|
|
const ColumnPermutation& inverse_col_perm() const {
|
2014-07-08 09:27:02 +00:00
|
|
|
return inverse_col_perm_;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
private:
|
2014-07-08 09:27:02 +00:00
|
|
|
// Statistics about this class.
|
|
|
|
|
struct Stats : public StatsGroup {
|
|
|
|
|
Stats()
|
|
|
|
|
: StatsGroup("LuFactorization"),
|
|
|
|
|
basis_num_entries("basis_num_entries", this),
|
|
|
|
|
lu_fill_in("lu_fill_in", this) {}
|
|
|
|
|
IntegerDistribution basis_num_entries;
|
|
|
|
|
RatioDistribution lu_fill_in;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Internal function used in the left solve functions.
|
|
|
|
|
void LeftSolveScratchpad() const;
|
|
|
|
|
|
2019-07-18 11:35:32 -07:00
|
|
|
// Internal function used in the right solve functions
|
|
|
|
|
template <typename Column>
|
2020-10-29 14:25:39 +01:00
|
|
|
void RightSolveLInternal(const Column& b, ScatteredColumn* x) const;
|
2019-07-18 11:35:32 -07:00
|
|
|
|
2014-07-08 09:27:02 +00:00
|
|
|
// Fills transpose_upper_ from upper_.
|
|
|
|
|
void ComputeTransposeUpper();
|
|
|
|
|
|
|
|
|
|
// transpose_lower_ is only needed when we compute dual norms.
|
|
|
|
|
void ComputeTransposeLower() const;
|
|
|
|
|
|
|
|
|
|
// Computes R = P.B.Q^{-1} - L.U and returns false if the largest magnitude of
|
|
|
|
|
// the coefficients of P.B.Q^{-1} - L.U is greater than tolerance.
|
2020-10-29 14:25:39 +01:00
|
|
|
bool CheckFactorization(const CompactSparseMatrixView& matrix,
|
2019-06-17 11:34:10 +02:00
|
|
|
Fractional tolerance) const;
|
2014-07-08 09:27:02 +00:00
|
|
|
|
|
|
|
|
// Special case where we have nothing to do. This happens at the beginning
|
|
|
|
|
// when we start the problem with an all-slack basis and gives a good speedup
|
|
|
|
|
// on really easy problems. It is initially true and set to true each time we
|
|
|
|
|
// call Clear(). We set it to false if a call to ComputeFactorization()
|
|
|
|
|
// succeeds.
|
|
|
|
|
bool is_identity_factorization_;
|
|
|
|
|
|
|
|
|
|
// The triangular factor L and U (and its transpose).
|
|
|
|
|
TriangularMatrix lower_;
|
|
|
|
|
TriangularMatrix upper_;
|
|
|
|
|
TriangularMatrix transpose_upper_;
|
|
|
|
|
|
|
|
|
|
// The transpose of lower_. It is just used by DualEdgeSquaredNorm()
|
|
|
|
|
// and mutable so it can be lazily initialized.
|
|
|
|
|
mutable TriangularMatrix transpose_lower_;
|
|
|
|
|
|
|
|
|
|
// The column permutation Q and its inverse Q^{-1} in P.B.Q^{-1} = L.U.
|
|
|
|
|
ColumnPermutation col_perm_;
|
|
|
|
|
ColumnPermutation inverse_col_perm_;
|
|
|
|
|
|
|
|
|
|
// The row permutation P and its inverse P^{-1} in P.B.Q^{-1} = L.U.
|
|
|
|
|
RowPermutation row_perm_;
|
|
|
|
|
RowPermutation inverse_row_perm_;
|
|
|
|
|
|
|
|
|
|
// Temporary storage used by LeftSolve()/RightSolve().
|
|
|
|
|
mutable DenseColumn dense_column_scratchpad_;
|
|
|
|
|
|
|
|
|
|
// Temporary storage used by GetColumnOfU().
|
|
|
|
|
mutable SparseColumn column_of_upper_;
|
|
|
|
|
|
|
|
|
|
// Same as dense_column_scratchpad_ but this vector is always reset to zero by
|
|
|
|
|
// the functions that use it. non_zero_rows_ is used to track the
|
|
|
|
|
// non_zero_rows_ position of dense_column_scratchpad_.
|
|
|
|
|
mutable DenseColumn dense_zero_scratchpad_;
|
|
|
|
|
mutable std::vector<RowIndex> non_zero_rows_;
|
|
|
|
|
|
|
|
|
|
// Statistics, mutable so const functions can still update it.
|
|
|
|
|
mutable Stats stats_;
|
|
|
|
|
|
|
|
|
|
// Proto holding all the parameters of this algorithm.
|
|
|
|
|
GlopParameters parameters_;
|
|
|
|
|
|
|
|
|
|
// The class doing the Markowitz LU factorization.
|
|
|
|
|
Markowitz markowitz_;
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
} // namespace glop
|
|
|
|
|
} // namespace operations_research
|
|
|
|
|
#endif // OR_TOOLS_GLOP_LU_FACTORIZATION_H_
|