Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
basis_representation.h
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#ifndef OR_TOOLS_GLOP_BASIS_REPRESENTATION_H_
15#define OR_TOOLS_GLOP_BASIS_REPRESENTATION_H_
16
17#include <string>
18#include <vector>
19
22#include "ortools/glop/parameters.pb.h"
24#include "ortools/glop/status.h"
29#include "ortools/util/stats.h"
30
31namespace operations_research {
32namespace glop {
33
34// An eta matrix E corresponds to the identity matrix except for one column e of
35// index j. In particular, B.E is the matrix of the new basis obtained from B by
36// replacing the j-th vector of B by B.e, note that this is exactly what happens
37// during a "pivot" of the current basis in the simplex algorithm.
38//
39// E = [ 1 ... 0 e_0 0 ... 0
40// ... ... ... ... ... ... ...
41// 0 ... 1 e_{j-1} 0 ... 0
42// 0 ... 0 e_j 0 ... 0
43// 0 ... 0 e_{j+1} 1 ... 0
44// ... ... ... ... ... ... ...
45// 0 ... 0 e_{n-1} 0 ... 1 ]
46//
47// The inverse of the eta matrix is:
48// E^{-1} = [ 1 ... 0 -e_0/e_j 0 ... 0
49// ... ... ... ... ... ... ...
50// 0 ... 1 -e_{j-1}/e_j 0 ... 0
51// 0 ... 0 1/e_j 0 ... 0
52// 0 ... 0 -e_{j+1}/e_j 1 ... 0
53// ... ... ... ... ... ... ...
54// 0 ... 0 -e_{n-1}/e_j 0 ... 1 ]
55class EtaMatrix {
56 public:
57 EtaMatrix(ColIndex eta_col, const ScatteredColumn& direction);
58
59 // This type is neither copyable nor movable.
60 EtaMatrix(const EtaMatrix&) = delete;
61 EtaMatrix& operator=(const EtaMatrix&) = delete;
62
63 virtual ~EtaMatrix();
64
65 // Solves the system y.E = c, 'c' beeing the initial value of 'y'.
66 // Then y = c.E^{-1}, so y is equal to c except for
67 // y_j = (c_j - \sum_{i != j}{c_i * e_i}) / e_j.
68 void LeftSolve(DenseRow* y) const;
69
70 // Same as LeftSolve(), but 'pos' contains the non-zero positions of c. The
71 // order of the positions is not important, but there must be no duplicates.
72 // The values not in 'pos' are not used. If eta_col_ was not already in 'pos',
73 // it is added.
74 void SparseLeftSolve(DenseRow* y, ColIndexVector* pos) const;
75
76 // Solves the system E.d = a, 'a' beeing the initial value of 'd'.
77 // Then d = E^{-1}.a = [ a_0 - e_0 * a_j / e_j
78 // ...
79 // a_{j-1} - e_{j-1} * a_j / e_j
80 // a_j / e_j
81 // a_{j+1} - e_{j+1} * a_j / e_j
82 // ...
83 // a_{n-1} - e_{n-1} * a_j / e_j ]
84 void RightSolve(DenseColumn* d) const;
85
86 private:
87 // Internal RightSolve() and LeftSolve() implementations using either the
88 // dense or the sparse representation of the eta vector.
89 void LeftSolveWithDenseEta(DenseRow* y) const;
90 void LeftSolveWithSparseEta(DenseRow* y) const;
91 void RightSolveWithDenseEta(DenseColumn* d) const;
92 void RightSolveWithSparseEta(DenseColumn* d) const;
93
94 // If an eta vector density is smaller than this threshold, we use the
95 // sparse version of the Solve() functions rather than the dense version.
96 // TODO(user): Detect automatically a good parameter? 0.5 is a good value on
97 // the Netlib (I only did a few experiments though). Note that in the future
98 // we may not even keep the dense representation at all.
99 static const Fractional kSparseThreshold;
100
101 const ColIndex eta_col_;
102 const Fractional eta_col_coefficient_;
103
104 // Note that to optimize solves, the position eta_col_ is set to 0.0 and
105 // stored in eta_col_coefficient_ instead.
106 DenseColumn eta_coeff_;
107 SparseColumn sparse_eta_coeff_;
108};
109
110// An eta factorization corresponds to the product of k eta matrices,
111// i.e. E = E_0.E_1. ... .E_{k-1}
112// It is used to solve two systems:
113// - E.d = a (where a is usually the entering column).
114// - y.E = c (where c is usually the objective row).
116 public:
118
119 // This type is neither copyable nor movable.
122
123 virtual ~EtaFactorization();
124
125 // Deletes all eta matrices.
126 void Clear();
127
128 // Updates the eta factorization, i.e. adds the new eta matrix defined by
129 // the leaving variable and the corresponding eta column.
130 void Update(ColIndex entering_col, RowIndex leaving_variable_row,
131 const ScatteredColumn& direction);
132
133 // Left solves all systems from right to left, i.e. y_i = y_{i+1}.(E_i)^{-1}
134 void LeftSolve(DenseRow* y) const;
135
136 // Same as LeftSolve(), but 'pos' contains the non-zero positions of c. The
137 // order of the positions is not important, but there must be no duplicates.
138 // The values not in 'pos' are not used. If eta_col_ was not already in 'pos',
139 // it is added.
140 void SparseLeftSolve(DenseRow* y, ColIndexVector* pos) const;
141
142 // Right solves all systems from left to right, i.e. E_i.d_{i+1} = d_i
143 void RightSolve(DenseColumn* d) const;
144
145 private:
146 std::vector<EtaMatrix*> eta_matrix_;
147};
148
149// A basis factorization is the product of an eta factorization and
150// a L.U decomposition, i.e. B = L.U.E_0.E_1. ... .E_{k-1}
151// It is used to solve two systems:
152// - B.d = a where a is the entering column.
153// - y.B = c where c is the objective row.
154//
155// To speed-up and improve stability the factorization is refactorized at least
156// every 'refactorization_period' updates.
157//
158// This class does not take ownership of the underlying matrix and basis, and
159// thus they must outlive this class (and keep the same address in memory).
161 public:
162 BasisFactorization(const CompactSparseMatrix* compact_matrix,
163 const RowToColMapping* basis);
164
165 // This type is neither copyable nor movable.
168
170
171 // Sets the parameters for this component.
172 void SetParameters(const GlopParameters& parameters) {
173 max_num_updates_ = parameters.basis_refactorization_period();
174 use_middle_product_form_update_ =
175 parameters.use_middle_product_form_update();
176 parameters_ = parameters;
177 lu_factorization_.SetParameters(parameters);
178 }
179
180 // Returns the column permutation used by the LU factorization.
181 // This call only makes sense if the basis was just refactorized.
183 DCHECK(IsRefactorized());
184 return lu_factorization_.GetColumnPermutation();
185 }
186
187 // Sets the column permutation used by the LU factorization to the identity.
188 // Hense the Solve() results will be computed without this permutation.
189 // This call only makes sense if the basis was just refactorized.
191 DCHECK(IsRefactorized());
192 lu_factorization_.SetColumnPermutationToIdentity();
193 }
194
195 // Clears the factorization and resets it to an identity matrix of size given
196 // by matrix_.num_rows().
197 void Clear();
198
199 // Clears the factorization and initializes the class using the current
200 // matrix_ and basis_. This is fast if IsIdentityBasis() is true, otherwise
201 // it will trigger a refactorization and will return an error if the matrix
202 // could not be factorized.
203 ABSL_MUST_USE_RESULT Status Initialize();
204
205 // This mainly forward the call to LuFactorization::ComputeInitialBasis().
206 //
207 // Note that once this is called, one would need to call Initialize() to
208 // actually create the factorization. The only side effect of this is to
209 // update the deterministic time.
210 //
211 // TODO(user): This "double" factorization is a bit inefficient, and we should
212 // probably Initialize() right away the factorization with the new basis, but
213 // more code is needed for that. It is also not that easy also because we want
214 // to permute all the added slack first.
215 RowToColMapping ComputeInitialBasis(const std::vector<ColIndex>& candidates);
216
217 // Return the number of rows in the basis.
218 RowIndex GetNumberOfRows() const { return compact_matrix_.num_rows(); }
219
220 // Clears eta factorization and refactorizes LU.
221 // Nothing happens if this is called on an already refactorized basis.
222 // Returns an error if the matrix could not be factorized: i.e. not a basis.
223 ABSL_MUST_USE_RESULT Status Refactorize();
224
225 // Like Refactorize(), but do it even if IsRefactorized() is true.
226 // Call this if the underlying basis_ changed and Update() wasn't called.
227 ABSL_MUST_USE_RESULT Status ForceRefactorization();
228
229 // Returns true if the factorization was just recomputed.
230 bool IsRefactorized() const;
231
232 // Updates the factorization. The 'eta' column will be modified with a swap to
233 // avoid a copy (only if the standard eta update is used). Returns an error if
234 // the matrix could not be factorized: i.e. not a basis.
235 ABSL_MUST_USE_RESULT Status Update(ColIndex entering_col,
236 RowIndex leaving_variable_row,
237 const ScatteredColumn& direction);
238
239 // Left solves the system y.B = rhs, where y initially contains rhs.
240 void LeftSolve(ScatteredRow* y) const;
241
242 // Left solves the system y.B = e_j, where e_j has only 1 non-zero
243 // coefficient of value 1.0 at position 'j'.
244 void LeftSolveForUnitRow(ColIndex j, ScatteredRow* y) const;
245
246 // Same as LeftSolveForUnitRow() but does not update any internal data.
247 void TemporaryLeftSolveForUnitRow(ColIndex j, ScatteredRow* y) const;
248
249 // Right solves the system B.d = a where the input is the initial value of d.
250 void RightSolve(ScatteredColumn* d) const;
251
252 // Same as RightSolve() for matrix.column(col). This also exploits its
253 // sparsity.
254 void RightSolveForProblemColumn(ColIndex col, ScatteredColumn* d) const;
255
256 // Specialized version for ComputeTau() in DualEdgeNorms. This reuses an
257 // intermediate result of the last LeftSolveForUnitRow() in order to save a
258 // permutation if it is available. Note that the input 'a' should always be
259 // equal to the last result of LeftSolveForUnitRow() and will be used for a
260 // DCHECK() or if the intermediate result wasn't kept.
261 const DenseColumn& RightSolveForTau(const ScatteredColumn& a) const;
262
263 // Returns the norm of B^{-1}.a, this is a specific function because
264 // it is a bit faster and it avoids polluting the stats of RightSolve().
265 // It can be called only when IsRefactorized() is true.
267
268 // Returns the norm of (B^T)^{-1}.e_row where e is an unit vector.
269 // This is a bit faster and avoids polluting the stats of LeftSolve().
270 // It can be called only when IsRefactorized() is true.
271 Fractional DualEdgeSquaredNorm(RowIndex row) const;
272
273 // Computes the condition number of B.
274 // For a given norm, this is the matrix norm times the norm of its inverse.
275 // A condition number greater than 1E7 will lead to precision problems.
279
280 // Computes the 1-norm of B.
281 // The 1-norm |A| is defined as max_j sum_i |a_ij|
282 // http://en.wikipedia.org/wiki/Matrix_norm
284
285 // Computes the infinity-norm of B.
286 // The infinity-norm |A| is defined as max_i sum_j |a_ij|
287 // http://en.wikipedia.org/wiki/Matrix_norm
289
290 // Computes the 1-norm of the inverse of B.
291 // For this we iteratively solve B.x = e_j, where e_j is the jth unit vector.
292 // The result of this computation is the jth column of B^-1.
294
295 // Computes the infinity-norm of the inverse of B.
297
298 // Stats related function.
299 // Note that ResetStats() could be const, but until needed it is not to
300 // prevent anyone holding a const BasisFactorization& to call it.
301 std::string StatString() const {
302 return stats_.StatString() + lu_factorization_.StatString();
303 }
304 void ResetStats() { stats_.Reset(); }
305
306 // The deterministic time used by this class. It is incremented for each
307 // solve and each factorization.
308 double DeterministicTime() const;
309
310 // Returns the number of updates since last refactorization.
311 int NumUpdates() const { return num_updates_; }
312
313 private:
314 // Called by ForceRefactorization() or Refactorize() or Initialize().
315 Status ComputeFactorization();
316
317 // Return true if the submatrix of matrix_ given by basis_ is exactly the
318 // identity (without permutation).
319 bool IsIdentityBasis() const;
320
321 // Updates the factorization using the middle product form update.
322 // Qi Huangfu, J. A. Julian Hall, "Novel update techniques for the revised
323 // simplex method", 28 january 2013, Technical Report ERGO-13-0001
324 ABSL_MUST_USE_RESULT Status
325 MiddleProductFormUpdate(ColIndex entering_col, RowIndex leaving_variable_row);
326
327 // Increases the deterministic time for a solve operation with a vector having
328 // this number of non-zero entries (it can be an approximation).
329 void BumpDeterministicTimeForSolve(int num_entries) const;
330
331 // Stats about this class.
332 struct Stats : public StatsGroup {
333 Stats()
334 : StatsGroup("BasisFactorization"),
335 refactorization_interval("refactorization_interval", this) {}
336 IntegerDistribution refactorization_interval;
337 };
338
339 // Mutable because we track the running time of const method like
340 // RightSolve() and LeftSolve().
341 mutable Stats stats_;
342 GlopParameters parameters_;
343
344 // References to the basis subpart of the linear program matrix.
345 const CompactSparseMatrix& compact_matrix_;
346 const RowToColMapping& basis_;
347
348 // Middle form product update factorization and scratchpad_ used to construct
349 // new rank one matrices.
350 RankOneUpdateFactorization rank_one_factorization_;
351 mutable DenseColumn scratchpad_;
352 mutable std::vector<RowIndex> scratchpad_non_zeros_;
353
354 // This is used by RightSolveForTau(). It holds an intermediate result from
355 // the last LeftSolveForUnitRow() and also the final result of
356 // RightSolveForTau().
357 mutable ScatteredColumn tau_;
358
359 // Booleans controlling the interaction between LeftSolveForUnitRow() that may
360 // or may not keep its intermediate results for the optimized
361 // RightSolveForTau().
362 //
363 // tau_computation_can_be_optimized_ will be true iff LeftSolveForUnitRow()
364 // kept its intermediate result when it was called and the factorization
365 // didn't change since then. If it is true, then RightSolveForTau() can use
366 // this result for a faster computation.
367 //
368 // tau_is_computed_ is used as an heuristic by LeftSolveForUnitRow() to decide
369 // if it is worth keeping its intermediate result (which is sligthly slower).
370 // It is simply set to true by RightSolveForTau() and to false by
371 // LeftSolveForUnitRow(), this way the optimization will automatically switch
372 // itself on when switching from the primal simplex (where RightSolveForTau()
373 // is never called) to the dual where it is called after each
374 // LeftSolveForUnitRow(), and back off again in the other direction.
375 mutable bool tau_computation_can_be_optimized_;
376 mutable bool tau_is_computed_;
377
378 // Data structure to store partial solve results for the middle form product
379 // update. See LeftSolveForUnitRow() and RightSolveForProblemColumn(). We use
380 // two CompactSparseMatrix to have a better cache behavior when solving with
381 // the rank_one_factorization_.
382 mutable CompactSparseMatrix storage_;
383 mutable CompactSparseMatrix right_storage_;
384 mutable ColMapping left_pool_mapping_;
385 mutable ColMapping right_pool_mapping_;
386
387 bool use_middle_product_form_update_;
388 int max_num_updates_;
389 int num_updates_;
390 EtaFactorization eta_factorization_;
391 LuFactorization lu_factorization_;
392
393 // mutable because the Solve() functions are const but need to update this.
394 double last_factorization_deterministic_time_ = 0.0;
395 mutable double deterministic_time_;
396};
397
398} // namespace glop
399} // namespace operations_research
400
401#endif // OR_TOOLS_GLOP_BASIS_REPRESENTATION_H_
IntegerValue y
Statistic on the distribution of a sequence of integers.
Definition stats.h:288
Base class to print a nice summary of a group of statistics.
Definition stats.h:128
std::string StatString() const
Definition stats.cc:77
void Reset()
Calls Reset() on all the statistics registered with this group.
Definition stats.cc:58
Fractional ComputeInverseInfinityNorm() const
Computes the infinity-norm of the inverse of B.
ABSL_MUST_USE_RESULT Status Update(ColIndex entering_col, RowIndex leaving_variable_row, const ScatteredColumn &direction)
const DenseColumn & RightSolveForTau(const ScatteredColumn &a) const
void SetParameters(const GlopParameters &parameters)
Sets the parameters for this component.
const ColumnPermutation & GetColumnPermutation() const
int NumUpdates() const
Returns the number of updates since last refactorization.
void RightSolve(ScatteredColumn *d) const
Right solves the system B.d = a where the input is the initial value of d.
void LeftSolveForUnitRow(ColIndex j, ScatteredRow *y) const
RowToColMapping ComputeInitialBasis(const std::vector< ColIndex > &candidates)
void TemporaryLeftSolveForUnitRow(ColIndex j, ScatteredRow *y) const
Same as LeftSolveForUnitRow() but does not update any internal data.
bool IsRefactorized() const
Returns true if the factorization was just recomputed.
BasisFactorization(const CompactSparseMatrix *compact_matrix, const RowToColMapping *basis)
Fractional RightSolveSquaredNorm(const ColumnView &a) const
RowIndex GetNumberOfRows() const
Return the number of rows in the basis.
BasisFactorization(const BasisFactorization &)=delete
This type is neither copyable nor movable.
void RightSolveForProblemColumn(ColIndex col, ScatteredColumn *d) const
BasisFactorization & operator=(const BasisFactorization &)=delete
void LeftSolve(ScatteredRow *y) const
Left solves the system y.B = rhs, where y initially contains rhs.
void RightSolve(DenseColumn *d) const
Right solves all systems from left to right, i.e. E_i.d_{i+1} = d_i.
EtaFactorization & operator=(const EtaFactorization &)=delete
void SparseLeftSolve(DenseRow *y, ColIndexVector *pos) const
void LeftSolve(DenseRow *y) const
Left solves all systems from right to left, i.e. y_i = y_{i+1}.(E_i)^{-1}.
void Update(ColIndex entering_col, RowIndex leaving_variable_row, const ScatteredColumn &direction)
EtaFactorization(const EtaFactorization &)=delete
This type is neither copyable nor movable.
void SetParameters(const GlopParameters &parameters)
Sets the current parameters.
std::string StatString() const
Returns a string containing the statistics for this class.
const ColumnPermutation & GetColumnPermutation() const
Returns the column permutation used by the LU factorization.
int64_t a
Definition table.cc:44
SatParameters parameters
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
StrictITIVector< RowIndex, ColIndex > RowToColMapping
Definition lp_types.h:396
std::vector< ColIndex > ColIndexVector
Vector of row or column indices. Useful to list the non-zero positions.
Definition lp_types.h:362
StrictITIVector< ColIndex, ColIndex > ColMapping
Row of column indices. Used to represent mappings between columns.
Definition lp_types.h:359
StrictITIVector< RowIndex, Fractional > DenseColumn
Column-vector types. Column-vector types are indexed by a row index.
Definition lp_types.h:382
In SWIG mode, we don't want anything besides these top-level includes.