OR-Tools  9.6
matrix_scaler.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <memory>
19 #include <string>
20 #include <vector>
21 
22 #include "absl/strings/str_format.h"
23 #include "ortools/base/logging.h"
27 #include "ortools/lp_data/sparse.h"
29 
30 namespace operations_research {
31 namespace glop {
32 
34  : matrix_(nullptr), row_scale_(), col_scale_() {}
35 
37  DCHECK(matrix != nullptr);
38  matrix_ = matrix;
39  row_scale_.resize(matrix_->num_rows(), 1.0);
40  col_scale_.resize(matrix_->num_cols(), 1.0);
41 }
42 
44  matrix_ = nullptr;
45  row_scale_.clear();
46  col_scale_.clear();
47 }
48 
50  DCHECK_GE(row, 0);
51  return row < row_scale_.size() ? row_scale_[row] : 1.0;
52 }
53 
55  DCHECK_GE(col, 0);
56  return col < col_scale_.size() ? col_scale_[col] : 1.0;
57 }
58 
60  return 1.0 / RowUnscalingFactor(row);
61 }
62 
64  return 1.0 / ColUnscalingFactor(col);
65 }
66 
67 std::string SparseMatrixScaler::DebugInformationString() const {
68  // Note that some computations are redundant with the computations made in
69  // some callees, but we do not care as this function is supposed to be called
70  // with FLAGS_v set to 1.
71  DCHECK(!row_scale_.empty());
72  DCHECK(!col_scale_.empty());
73  Fractional max_magnitude;
74  Fractional min_magnitude;
75  matrix_->ComputeMinAndMaxMagnitudes(&min_magnitude, &max_magnitude);
76  const Fractional dynamic_range = max_magnitude / min_magnitude;
77  std::string output = absl::StrFormat(
78  "Min magnitude = %g, max magnitude = %g\n"
79  "Dynamic range = %g\n"
80  "Variance = %g\n"
81  "Minimum row scale = %g, maximum row scale = %g\n"
82  "Minimum col scale = %g, maximum col scale = %g\n",
83  min_magnitude, max_magnitude, dynamic_range,
85  *std::min_element(row_scale_.begin(), row_scale_.end()),
86  *std::max_element(row_scale_.begin(), row_scale_.end()),
87  *std::min_element(col_scale_.begin(), col_scale_.end()),
88  *std::max_element(col_scale_.begin(), col_scale_.end()));
89  return output;
90 }
91 
92 void SparseMatrixScaler::Scale(GlopParameters::ScalingAlgorithm method) {
93  // This is an implementation of the algorithm described in
94  // Benichou, M., Gauthier, J-M., Hentges, G., and Ribiere, G.,
95  // "The efficient solution of large-scale linear programming problems —
96  // some algorithmic techniques and computational results,"
97  // Mathematical Programming 13(3) (December 1977).
98  // http://www.springerlink.com/content/j3367676856m0064/
99  DCHECK(matrix_ != nullptr);
100  Fractional max_magnitude;
101  Fractional min_magnitude;
102  matrix_->ComputeMinAndMaxMagnitudes(&min_magnitude, &max_magnitude);
103  if (min_magnitude == 0.0) {
104  DCHECK_EQ(0.0, max_magnitude);
105  return; // Null matrix: nothing to do.
106  }
107  VLOG(1) << "Before scaling:\n" << DebugInformationString();
108  if (method == GlopParameters::LINEAR_PROGRAM) {
109  Status lp_status = LPScale();
110  // Revert to the default scaling method if there is an error with the LP.
111  if (lp_status.ok()) {
112  return;
113  } else {
114  VLOG(1) << "Error with LP scaling: " << lp_status.error_message();
115  }
116  }
117  // TODO(user): Decide precisely for which value of dynamic range we should cut
118  // off geometric scaling.
119  const Fractional dynamic_range = max_magnitude / min_magnitude;
120  const Fractional kMaxDynamicRangeForGeometricScaling = 1e20;
121  if (dynamic_range < kMaxDynamicRangeForGeometricScaling) {
122  const int kScalingIterations = 4;
123  const Fractional kVarianceThreshold(10.0);
124  for (int iteration = 0; iteration < kScalingIterations; ++iteration) {
125  const RowIndex num_rows_scaled = ScaleRowsGeometrically();
126  const ColIndex num_cols_scaled = ScaleColumnsGeometrically();
128  VLOG(1) << "Geometric scaling iteration " << iteration
129  << ". Rows scaled = " << num_rows_scaled
130  << ", columns scaled = " << num_cols_scaled << "\n";
131  VLOG(1) << DebugInformationString();
132  if (variance < kVarianceThreshold ||
133  (num_cols_scaled == 0 && num_rows_scaled == 0)) {
134  break;
135  }
136  }
137  }
138  RowIndex rows_equilibrated = EquilibrateRows();
139  ColIndex cols_equilibrated = EquilibrateColumns();
140  VLOG(1) << "Equilibration step: Rows scaled = " << rows_equilibrated
141  << ", columns scaled = " << cols_equilibrated << "\n";
142  VLOG(1) << DebugInformationString();
143 }
144 
145 namespace {
146 template <class I>
147 void ScaleVector(const absl::StrongVector<I, Fractional>& scale, bool up,
148  absl::StrongVector<I, Fractional>* vector_to_scale) {
149  RETURN_IF_NULL(vector_to_scale);
150  const I size(std::min(scale.size(), vector_to_scale->size()));
151  if (up) {
152  for (I i(0); i < size; ++i) {
153  (*vector_to_scale)[i] *= scale[i];
154  }
155  } else {
156  for (I i(0); i < size; ++i) {
157  (*vector_to_scale)[i] /= scale[i];
158  }
159  }
160 }
161 
162 template <typename InputIndexType>
163 ColIndex CreateOrGetScaleIndex(
164  InputIndexType num, LinearProgram* lp,
166  if ((*scale_var_indices)[num] == -1) {
167  (*scale_var_indices)[num] = lp->CreateNewVariable();
168  }
169  return (*scale_var_indices)[num];
170 }
171 } // anonymous namespace
172 
173 void SparseMatrixScaler::ScaleRowVector(bool up, DenseRow* row_vector) const {
174  DCHECK(row_vector != nullptr);
175  ScaleVector(col_scale_, up, row_vector);
176 }
177 
179  DenseColumn* column_vector) const {
180  DCHECK(column_vector != nullptr);
181  ScaleVector(row_scale_, up, column_vector);
182 }
183 
185  DCHECK(matrix_ != nullptr);
186  Fractional sigma_square(0.0);
187  Fractional sigma_abs(0.0);
188  double n = 0.0; // n is used in a calculation involving doubles.
189  const ColIndex num_cols = matrix_->num_cols();
190  for (ColIndex col(0); col < num_cols; ++col) {
191  for (const SparseColumn::Entry e : matrix_->column(col)) {
192  const Fractional magnitude = fabs(e.coefficient());
193  if (magnitude != 0.0) {
194  sigma_square += magnitude * magnitude;
195  sigma_abs += magnitude;
196  ++n;
197  }
198  }
199  }
200  if (n == 0.0) return 0.0;
201  // Since we know all the population (the non-zeros) and we are not using a
202  // sample, the variance is defined as below.
203  // For an explanation, see:
204  // http://en.wikipedia.org/wiki/Variance
205  // #Population_variance_and_sample_variance
206  return (sigma_square - sigma_abs * sigma_abs / n) / n;
207 }
208 
209 // For geometric scaling, we compute the maximum and minimum magnitudes
210 // of non-zeros in a row (resp. column). Let us denote these numbers as
211 // max and min. We then scale the row (resp. column) by dividing the
212 // coefficients by sqrt(min * max).
213 
215  DCHECK(matrix_ != nullptr);
216  DenseColumn max_in_row(matrix_->num_rows(), 0.0);
217  DenseColumn min_in_row(matrix_->num_rows(), kInfinity);
218  const ColIndex num_cols = matrix_->num_cols();
219  for (ColIndex col(0); col < num_cols; ++col) {
220  for (const SparseColumn::Entry e : matrix_->column(col)) {
221  const Fractional magnitude = fabs(e.coefficient());
222  const RowIndex row = e.row();
223  if (magnitude != 0.0) {
224  max_in_row[row] = std::max(max_in_row[row], magnitude);
225  min_in_row[row] = std::min(min_in_row[row], magnitude);
226  }
227  }
228  }
229  const RowIndex num_rows = matrix_->num_rows();
230  DenseColumn scaling_factor(num_rows, 0.0);
231  for (RowIndex row(0); row < num_rows; ++row) {
232  if (max_in_row[row] == 0.0) {
233  scaling_factor[row] = 1.0;
234  } else {
235  DCHECK_NE(kInfinity, min_in_row[row]);
236  scaling_factor[row] = sqrt(max_in_row[row] * min_in_row[row]);
237  }
238  }
239  return ScaleMatrixRows(scaling_factor);
240 }
241 
243  DCHECK(matrix_ != nullptr);
244  ColIndex num_cols_scaled(0);
245  const ColIndex num_cols = matrix_->num_cols();
246  for (ColIndex col(0); col < num_cols; ++col) {
247  Fractional max_in_col(0.0);
248  Fractional min_in_col(kInfinity);
249  for (const SparseColumn::Entry e : matrix_->column(col)) {
250  const Fractional magnitude = fabs(e.coefficient());
251  if (magnitude != 0.0) {
252  max_in_col = std::max(max_in_col, magnitude);
253  min_in_col = std::min(min_in_col, magnitude);
254  }
255  }
256  if (max_in_col != 0.0) {
257  const Fractional factor(sqrt(ToDouble(max_in_col * min_in_col)));
258  ScaleMatrixColumn(col, factor);
259  num_cols_scaled++;
260  }
261  }
262  return num_cols_scaled;
263 }
264 
265 // For equilibration, we compute the maximum magnitude of non-zeros
266 // in a row (resp. column), and then scale the row (resp. column) by dividing
267 // the coefficients this maximum magnitude.
268 // This brings the largest coefficient in a row equal to 1.0.
269 
271  DCHECK(matrix_ != nullptr);
272  const RowIndex num_rows = matrix_->num_rows();
273  DenseColumn max_magnitude(num_rows, 0.0);
274  const ColIndex num_cols = matrix_->num_cols();
275  for (ColIndex col(0); col < num_cols; ++col) {
276  for (const SparseColumn::Entry e : matrix_->column(col)) {
277  const Fractional magnitude = fabs(e.coefficient());
278  if (magnitude != 0.0) {
279  const RowIndex row = e.row();
280  max_magnitude[row] = std::max(max_magnitude[row], magnitude);
281  }
282  }
283  }
284  for (RowIndex row(0); row < num_rows; ++row) {
285  if (max_magnitude[row] == 0.0) {
286  max_magnitude[row] = 1.0;
287  }
288  }
289  return ScaleMatrixRows(max_magnitude);
290 }
291 
293  DCHECK(matrix_ != nullptr);
294  ColIndex num_cols_scaled(0);
295  const ColIndex num_cols = matrix_->num_cols();
296  for (ColIndex col(0); col < num_cols; ++col) {
297  const Fractional max_magnitude = InfinityNorm(matrix_->column(col));
298  if (max_magnitude != 0.0) {
299  ScaleMatrixColumn(col, max_magnitude);
300  num_cols_scaled++;
301  }
302  }
303  return num_cols_scaled;
304 }
305 
306 RowIndex SparseMatrixScaler::ScaleMatrixRows(const DenseColumn& factors) {
307  // Matrix rows are scaled by dividing their coefficients by factors[row].
308  DCHECK(matrix_ != nullptr);
309  const RowIndex num_rows = matrix_->num_rows();
310  DCHECK_EQ(num_rows, factors.size());
311  RowIndex num_rows_scaled(0);
312  for (RowIndex row(0); row < num_rows; ++row) {
313  const Fractional factor = factors[row];
314  DCHECK_NE(0.0, factor);
315  if (factor != 1.0) {
316  ++num_rows_scaled;
317  row_scale_[row] *= factor;
318  }
319  }
320 
321  const ColIndex num_cols = matrix_->num_cols();
322  for (ColIndex col(0); col < num_cols; ++col) {
323  SparseColumn* const column = matrix_->mutable_column(col);
324  if (column != nullptr) {
325  column->ComponentWiseDivide(factors);
326  }
327  }
328 
329  return num_rows_scaled;
330 }
331 
332 void SparseMatrixScaler::ScaleMatrixColumn(ColIndex col, Fractional factor) {
333  // A column is scaled by dividing by factor.
334  DCHECK(matrix_ != nullptr);
335  col_scale_[col] *= factor;
336  DCHECK_NE(0.0, factor);
337 
338  SparseColumn* const column = matrix_->mutable_column(col);
339  if (column != nullptr) {
340  column->DivideByConstant(factor);
341  }
342 }
343 
345  // Unscaling is easier than scaling since all scaling factors are stored.
346  DCHECK(matrix_ != nullptr);
347  const ColIndex num_cols = matrix_->num_cols();
348  for (ColIndex col(0); col < num_cols; ++col) {
349  const Fractional column_scale = col_scale_[col];
350  DCHECK_NE(0.0, column_scale);
351 
352  SparseColumn* const column = matrix_->mutable_column(col);
353  if (column != nullptr) {
354  column->MultiplyByConstant(column_scale);
355  column->ComponentWiseMultiply(row_scale_);
356  }
357  }
358 }
359 
361  DCHECK(matrix_ != nullptr);
362 
363  auto linear_program = std::make_unique<LinearProgram>();
364  GlopParameters params;
365  auto simplex = std::make_unique<RevisedSimplex>();
366  simplex->SetParameters(params);
367 
368  // Begin linear program construction.
369  // Beta represents the largest distance from zero among the constraint pairs.
370  // It resembles a slack variable because the 'objective' of each constraint is
371  // to cancel out the log "w" of the original nonzero |a_ij| (a.k.a. |a_rc|).
372  // Approaching 0 by addition in log space is the same as approaching 1 by
373  // multiplication in linear space. Hence, each variable's log magnitude is
374  // subtracted from the log row scale and log column scale. If the sum is
375  // positive, the positive constraint is trivially satisfied, but the negative
376  // constraint will determine the minimum necessary value of beta for that
377  // variable and scaling factors, and vice versa.
378  // For an MxN matrix, the resulting scaling LP has M+N+1 variables and
379  // O(M*N) constraints (2*M*N at maximum). As a result, using this LP to scale
380  // another linear program, will typically increase the time to
381  // optimization by a factor of 4, and has increased the time of some benchmark
382  // LPs by up to 10.
383 
384  // Indices to variables in the LinearProgram populated by
385  // GenerateLinearProgram.
386  absl::StrongVector<ColIndex, ColIndex> col_scale_var_indices;
387  absl::StrongVector<RowIndex, ColIndex> row_scale_var_indices;
388  row_scale_var_indices.resize(RowToIntIndex(matrix_->num_rows()), kInvalidCol);
389  col_scale_var_indices.resize(ColToIntIndex(matrix_->num_cols()), kInvalidCol);
390  const ColIndex beta = linear_program->CreateNewVariable();
391  linear_program->SetVariableBounds(beta, -kInfinity, kInfinity);
392  // Default objective is to minimize.
393  linear_program->SetObjectiveCoefficient(beta, 1);
394  matrix_->CleanUp();
395  const ColIndex num_cols = matrix_->num_cols();
396  for (ColIndex col(0); col < num_cols; ++col) {
397  SparseColumn* const column = matrix_->mutable_column(col);
398  // This is the variable representing the log of the scale factor for col.
399  const ColIndex column_scale = CreateOrGetScaleIndex<ColIndex>(
400  col, linear_program.get(), &col_scale_var_indices);
401  linear_program->SetVariableBounds(column_scale, -kInfinity, kInfinity);
402  for (EntryIndex i : column->AllEntryIndices()) {
403  const Fractional log_magnitude =
404  log2(std::abs(column->EntryCoefficient(i)));
405  const RowIndex row = column->EntryRow(i);
406  // This is the variable representing the log of the scale factor for row.
407  const ColIndex row_scale = CreateOrGetScaleIndex<RowIndex>(
408  row, linear_program.get(), &row_scale_var_indices);
409 
410  linear_program->SetVariableBounds(row_scale, -kInfinity, kInfinity);
411  // clang-format off
412  // This is derived from the formulation in
413  // min β
414  // Subject to:
415  // ∀ c∈C, v∈V, p_{c,v} ≠ 0.0, w_{c,v} + s^{var}_v + s^{comb}_c + β ≥ 0.0
416  // ∀ c∈C, v∈V, p_{c,v} ≠ 0.0, w_{c,v} + s^{var}_v + s^{comb}_c ≤ β
417  // If a variable is integer, its scale factor is zero.
418  // clang-format on
419 
420  // Start with the constraint w_cv + s_c + s_v + beta >= 0.
421  const RowIndex positive_constraint =
422  linear_program->CreateNewConstraint();
423  // Subtract the constant w_cv from both sides.
424  linear_program->SetConstraintBounds(positive_constraint, -log_magnitude,
425  kInfinity);
426  // +s_c, meaning (log) scale of the constraint C, pointed by row_scale.
427  linear_program->SetCoefficient(positive_constraint, row_scale, 1);
428  // +s_v, meaning (log) scale of the variable V, pointed by column_scale.
429  linear_program->SetCoefficient(positive_constraint, column_scale, 1);
430  // +beta
431  linear_program->SetCoefficient(positive_constraint, beta, 1);
432 
433  // Construct the constraint w_cv + s_c + s_v <= beta.
434  const RowIndex negative_constraint =
435  linear_program->CreateNewConstraint();
436  // Subtract w (and beta) from both sides.
437  linear_program->SetConstraintBounds(negative_constraint, -kInfinity,
438  -log_magnitude);
439  // +s_c, meaning (log) scale of the constraint C, pointed by row_scale.
440  linear_program->SetCoefficient(negative_constraint, row_scale, 1);
441  // +s_v, meaning (log) scale of the variable V, pointed by column_scale.
442  linear_program->SetCoefficient(negative_constraint, column_scale, 1);
443  // -beta
444  linear_program->SetCoefficient(negative_constraint, beta, -1);
445  }
446  }
447  // End linear program construction.
448 
449  linear_program->AddSlackVariablesWhereNecessary(false);
450  const Status simplex_status =
451  simplex->Solve(*linear_program, TimeLimit::Infinite().get());
452  if (!simplex_status.ok()) {
453  return simplex_status;
454  } else {
455  // Now the solution variables can be interpreted and translated from log
456  // space.
457  // For each row scale, unlog it and scale the constraints and constraint
458  // bounds.
459  const ColIndex num_cols = matrix_->num_cols();
460  for (ColIndex col(0); col < num_cols; ++col) {
461  const Fractional column_scale =
462  exp2(-simplex->GetVariableValue(CreateOrGetScaleIndex<ColIndex>(
463  col, linear_program.get(), &col_scale_var_indices)));
464  ScaleMatrixColumn(col, column_scale);
465  }
466  const RowIndex num_rows = matrix_->num_rows();
467  DenseColumn row_scale(num_rows, 0.0);
468  for (RowIndex row(0); row < num_rows; ++row) {
469  row_scale[row] =
470  exp2(-simplex->GetVariableValue(CreateOrGetScaleIndex<RowIndex>(
471  row, linear_program.get(), &row_scale_var_indices)));
472  }
473  ScaleMatrixRows(row_scale);
474  return Status::OK();
475  }
476 }
477 
478 } // namespace glop
479 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void resize(size_type new_size)
size_type size() const
bool empty() const
static std::unique_ptr< TimeLimit > Infinite()
Creates a time limit object that uses infinite time for wall time, deterministic time and instruction...
Definition: time_limit.h:135
SparseColumn * mutable_column(ColIndex col)
Definition: sparse.h:184
void ComputeMinAndMaxMagnitudes(Fractional *min_magnitude, Fractional *max_magnitude) const
Definition: sparse.cc:374
const SparseColumn & column(ColIndex col) const
Definition: sparse.h:183
Fractional RowScalingFactor(RowIndex row) const
Fractional ColScalingFactor(ColIndex col) const
void ScaleColumnVector(bool up, DenseColumn *column_vector) const
void ScaleRowVector(bool up, DenseRow *row_vector) const
void Scale(GlopParameters::ScalingAlgorithm method)
Fractional ColUnscalingFactor(ColIndex col) const
Fractional RowUnscalingFactor(RowIndex row) const
static const Status OK()
Definition: status.h:55
const std::string & error_message() const
Definition: status.h:59
ColIndex col
Definition: markowitz.cc:186
RowIndex row
Definition: markowitz.cc:185
constexpr ColIndex kInvalidCol(-1)
Fractional InfinityNorm(const DenseColumn &v)
Index ColToIntIndex(ColIndex col)
Definition: lp_types.h:59
constexpr double kInfinity
Definition: lp_types.h:88
Index RowToIntIndex(RowIndex row)
Definition: lp_types.h:62
static double ToDouble(double f)
Definition: lp_types.h:73
Collection of objects used to extend the Constraint Solver library.
int column
Definition: parse_proto.cc:32
#define RETURN_IF_NULL(x)
Definition: return_macros.h:20
#define VLOG(verboselevel)
Definition: vlog.h:39