OR-Tools  9.6
markowitz.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 
14 #include "ortools/glop/markowitz.h"
15 
16 #include <algorithm>
17 #include <cstdint>
18 #include <limits>
19 #include <string>
20 #include <vector>
21 
22 #include "absl/strings/str_format.h"
25 #include "ortools/lp_data/sparse.h"
26 
27 namespace operations_research {
28 namespace glop {
29 
31  const CompactSparseMatrixView& basis_matrix, RowPermutation* row_perm,
32  ColumnPermutation* col_perm) {
33  SCOPED_TIME_STAT(&stats_);
34  Clear();
35  const RowIndex num_rows = basis_matrix.num_rows();
36  const ColIndex num_cols = basis_matrix.num_cols();
37  col_perm->assign(num_cols, kInvalidCol);
38  row_perm->assign(num_rows, kInvalidRow);
39 
40  // Get the empty matrix corner case out of the way.
41  if (basis_matrix.IsEmpty()) return Status::OK();
42  basis_matrix_ = &basis_matrix;
43 
44  // Initialize all the matrices.
45  lower_.Reset(num_rows, num_cols);
46  upper_.Reset(num_rows, num_cols);
47  permuted_lower_.Reset(num_cols);
48  permuted_upper_.Reset(num_cols);
49  permuted_lower_column_needs_solve_.assign(num_cols, false);
50  contains_only_singleton_columns_ = true;
51 
52  // Start by moving the singleton columns to the front and by putting their
53  // non-zero coefficient on the diagonal. The general algorithm below would
54  // have the same effect, but this function is a lot faster.
55  int index = 0;
56  ExtractSingletonColumns(basis_matrix, row_perm, col_perm, &index);
57  ExtractResidualSingletonColumns(basis_matrix, row_perm, col_perm, &index);
58  int stats_num_pivots_without_fill_in = index;
59  int stats_degree_two_pivot_columns = 0;
60 
61  // Initialize residual_matrix_non_zero_ with the submatrix left after we
62  // removed the singleton and residual singleton columns.
63  residual_matrix_non_zero_.InitializeFromMatrixSubset(
64  basis_matrix, *row_perm, *col_perm, &singleton_column_, &singleton_row_);
65 
66  // Perform Gaussian elimination.
67  const int end_index = std::min(num_rows.value(), num_cols.value());
68  const Fractional singularity_threshold =
69  parameters_.markowitz_singularity_threshold();
70  while (index < end_index) {
71  Fractional pivot_coefficient = 0.0;
72  RowIndex pivot_row = kInvalidRow;
73  ColIndex pivot_col = kInvalidCol;
74 
75  // TODO(user): If we don't need L and U, we can abort when the residual
76  // matrix becomes dense (i.e. when its density factor is above a certain
77  // threshold). The residual size is 'end_index - index' and the
78  // density can either be computed exactly or estimated from min_markowitz.
79  const int64_t min_markowitz = FindPivot(*row_perm, *col_perm, &pivot_row,
80  &pivot_col, &pivot_coefficient);
81 
82  // Singular matrix? No pivot will be selected if a column has no entries. If
83  // a column has some entries, then we are sure that a pivot will be selected
84  // but its magnitude can be really close to zero. In both cases, we
85  // report the singularity of the matrix.
86  if (pivot_row == kInvalidRow || pivot_col == kInvalidCol ||
87  std::abs(pivot_coefficient) <= singularity_threshold) {
88  const std::string error_message = absl::StrFormat(
89  "The matrix is singular! pivot = %E", pivot_coefficient);
90  VLOG(1) << "ERROR_LU: " << error_message;
91  return Status(Status::ERROR_LU, error_message);
92  }
93  DCHECK_EQ((*row_perm)[pivot_row], kInvalidRow);
94  DCHECK_EQ((*col_perm)[pivot_col], kInvalidCol);
95 
96  // Update residual_matrix_non_zero_.
97  // TODO(user): This step can be skipped, once a fully dense matrix is
98  // obtained. But note that permuted_lower_column_needs_solve_ needs to be
99  // updated.
100  const int pivot_col_degree = residual_matrix_non_zero_.ColDegree(pivot_col);
101  const int pivot_row_degree = residual_matrix_non_zero_.RowDegree(pivot_row);
102  residual_matrix_non_zero_.DeleteRowAndColumn(pivot_row, pivot_col);
103  if (min_markowitz == 0) {
104  ++stats_num_pivots_without_fill_in;
105  if (pivot_col_degree == 1) {
106  RemoveRowFromResidualMatrix(pivot_row, pivot_col);
107  } else {
108  DCHECK_EQ(pivot_row_degree, 1);
109  RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
110  }
111  } else {
112  // TODO(user): Note that in some rare cases, because of numerical
113  // cancellation, the column degree may actually be smaller than
114  // pivot_col_degree. Exploit that better?
116  if (pivot_col_degree == 2) { ++stats_degree_two_pivot_columns; });
117  UpdateResidualMatrix(pivot_row, pivot_col);
118  }
119 
120  if (contains_only_singleton_columns_) {
121  DCHECK(permuted_upper_.column(pivot_col).IsEmpty());
122  lower_.AddDiagonalOnlyColumn(1.0);
123  upper_.AddTriangularColumn(basis_matrix.column(pivot_col), pivot_row);
124  } else {
125  lower_.AddAndNormalizeTriangularColumn(permuted_lower_.column(pivot_col),
126  pivot_row, pivot_coefficient);
127  permuted_lower_.ClearAndReleaseColumn(pivot_col);
128 
130  permuted_upper_.column(pivot_col), pivot_row, pivot_coefficient);
131  permuted_upper_.ClearAndReleaseColumn(pivot_col);
132  }
133 
134  // Update the permutations.
135  (*col_perm)[pivot_col] = ColIndex(index);
136  (*row_perm)[pivot_row] = RowIndex(index);
137  ++index;
138  }
139 
140  // To get a better deterministic time, we add a factor that depend on the
141  // final number of entries in the result.
142  num_fp_operations_ += 10 * lower_.num_entries().value();
143  num_fp_operations_ += 10 * upper_.num_entries().value();
144 
145  stats_.pivots_without_fill_in_ratio.Add(
146  1.0 * stats_num_pivots_without_fill_in / num_rows.value());
147  stats_.degree_two_pivot_columns.Add(1.0 * stats_degree_two_pivot_columns /
148  num_rows.value());
149  return Status::OK();
150 }
151 
153  RowPermutation* row_perm,
154  ColumnPermutation* col_perm,
156  // The two first swaps allow to use less memory since this way upper_
157  // and lower_ will always stay empty at the end of this function.
158  lower_.Swap(lower);
159  upper_.Swap(upper);
161  ComputeRowAndColumnPermutation(basis_matrix, row_perm, col_perm));
162  SCOPED_TIME_STAT(&stats_);
163  lower_.ApplyRowPermutationToNonDiagonalEntries(*row_perm);
164  upper_.ApplyRowPermutationToNonDiagonalEntries(*row_perm);
165  lower_.Swap(lower);
166  upper_.Swap(upper);
167  DCHECK(lower->IsLowerTriangular());
168  DCHECK(upper->IsUpperTriangular());
169  return Status::OK();
170 }
171 
173  SCOPED_TIME_STAT(&stats_);
174  permuted_lower_.Clear();
175  permuted_upper_.Clear();
176  residual_matrix_non_zero_.Clear();
177  col_by_degree_.Clear();
178  examined_col_.clear();
179  num_fp_operations_ = 0;
180  is_col_by_degree_initialized_ = false;
181 }
182 
183 namespace {
184 struct MatrixEntry {
185  RowIndex row;
186  ColIndex col;
188  MatrixEntry(RowIndex r, ColIndex c, Fractional coeff)
189  : row(r), col(c), coefficient(coeff) {}
190  bool operator<(const MatrixEntry& o) const {
191  return (row == o.row) ? col < o.col : row < o.row;
192  }
193 };
194 
195 } // namespace
196 
197 void Markowitz::ExtractSingletonColumns(
198  const CompactSparseMatrixView& basis_matrix, RowPermutation* row_perm,
199  ColumnPermutation* col_perm, int* index) {
200  SCOPED_TIME_STAT(&stats_);
201  std::vector<MatrixEntry> singleton_entries;
202  const ColIndex num_cols = basis_matrix.num_cols();
203  for (ColIndex col(0); col < num_cols; ++col) {
204  const ColumnView& column = basis_matrix.column(col);
205  if (column.num_entries().value() == 1) {
206  singleton_entries.push_back(
207  MatrixEntry(column.GetFirstRow(), col, column.GetFirstCoefficient()));
208  }
209  }
210 
211  // Sorting the entries by row indices allows the row_permutation to be closer
212  // to identity which seems like a good idea.
213  std::sort(singleton_entries.begin(), singleton_entries.end());
214  for (const MatrixEntry e : singleton_entries) {
215  if ((*row_perm)[e.row] == kInvalidRow) {
216  (*col_perm)[e.col] = ColIndex(*index);
217  (*row_perm)[e.row] = RowIndex(*index);
218  lower_.AddDiagonalOnlyColumn(1.0);
219  upper_.AddDiagonalOnlyColumn(e.coefficient);
220  ++(*index);
221  }
222  }
223  stats_.basis_singleton_column_ratio.Add(static_cast<double>(*index) /
224  basis_matrix.num_rows().value());
225 }
226 
227 bool Markowitz::IsResidualSingletonColumn(const ColumnView& column,
228  const RowPermutation& row_perm,
229  RowIndex* row) {
230  int residual_degree = 0;
231  for (const auto e : column) {
232  if (row_perm[e.row()] != kInvalidRow) continue;
233  ++residual_degree;
234  if (residual_degree > 1) return false;
235  *row = e.row();
236  }
237  return residual_degree == 1;
238 }
239 
240 void Markowitz::ExtractResidualSingletonColumns(
241  const CompactSparseMatrixView& basis_matrix, RowPermutation* row_perm,
242  ColumnPermutation* col_perm, int* index) {
243  SCOPED_TIME_STAT(&stats_);
244  const ColIndex num_cols = basis_matrix.num_cols();
245  RowIndex row = kInvalidRow;
246  for (ColIndex col(0); col < num_cols; ++col) {
247  if ((*col_perm)[col] != kInvalidCol) continue;
248  const ColumnView& column = basis_matrix.column(col);
249  if (!IsResidualSingletonColumn(column, *row_perm, &row)) continue;
250  (*col_perm)[col] = ColIndex(*index);
251  (*row_perm)[row] = RowIndex(*index);
252  lower_.AddDiagonalOnlyColumn(1.0);
253  upper_.AddTriangularColumn(column, row);
254  ++(*index);
255  }
256  stats_.basis_residual_singleton_column_ratio.Add(
257  static_cast<double>(*index) / basis_matrix.num_rows().value());
258 }
259 
260 const SparseColumn& Markowitz::ComputeColumn(const RowPermutation& row_perm,
261  ColIndex col) {
262  SCOPED_TIME_STAT(&stats_);
263  // Is this the first time ComputeColumn() sees this column? This is a bit
264  // tricky because just one of the tests is not sufficient in case the matrix
265  // is degenerate.
266  const bool first_time = permuted_lower_.column(col).IsEmpty() &&
267  permuted_upper_.column(col).IsEmpty();
268 
269  // If !permuted_lower_column_needs_solve_[col] then the result of the
270  // PermutedLowerSparseSolve() below is already stored in
271  // permuted_lower_.column(col) and we just need to split this column. Note
272  // that this is just an optimization and the code would work if we just
273  // assumed permuted_lower_column_needs_solve_[col] to be always true.
274  SparseColumn* lower_column = permuted_lower_.mutable_column(col);
275  if (permuted_lower_column_needs_solve_[col]) {
276  // Solve a sparse triangular system. If the column 'col' of permuted_lower_
277  // was never computed before by ComputeColumn(), we use the column 'col' of
278  // the matrix to factorize.
279  const ColumnView& input =
280  first_time ? basis_matrix_->column(col) : ColumnView(*lower_column);
281  lower_.PermutedLowerSparseSolve(input, row_perm, lower_column,
282  permuted_upper_.mutable_column(col));
283  permuted_lower_column_needs_solve_[col] = false;
284  num_fp_operations_ +=
286  return *lower_column;
287  }
288 
289  // All the symbolic non-zeros are always present in lower. So if this test is
290  // true, we can conclude that there is no entries from upper that need to be
291  // moved by a cardinality argument.
292  if (lower_column->num_entries() == residual_matrix_non_zero_.ColDegree(col)) {
293  return *lower_column;
294  }
295 
296  // In this case, we just need to "split" the lower column. We copy from the
297  // appropriate ColumnView in basis_matrix_.
298  // TODO(user): add PopulateFromColumnView if it is useful elsewhere.
299  if (first_time) {
300  const EntryIndex num_entries = basis_matrix_->column(col).num_entries();
301  num_fp_operations_ += num_entries.value();
302  lower_column->Reserve(num_entries);
303  for (const auto e : basis_matrix_->column(col)) {
304  lower_column->SetCoefficient(e.row(), e.coefficient());
305  }
306  }
307  num_fp_operations_ += lower_column->num_entries().value();
308  lower_column->MoveTaggedEntriesTo(row_perm,
309  permuted_upper_.mutable_column(col));
310  return *lower_column;
311 }
312 
313 int64_t Markowitz::FindPivot(const RowPermutation& row_perm,
314  const ColumnPermutation& col_perm,
315  RowIndex* pivot_row, ColIndex* pivot_col,
316  Fractional* pivot_coefficient) {
317  SCOPED_TIME_STAT(&stats_);
318 
319  // Fast track for singleton columns.
320  while (!singleton_column_.empty()) {
321  const ColIndex col = singleton_column_.back();
322  singleton_column_.pop_back();
323  DCHECK_EQ(kInvalidCol, col_perm[col]);
324 
325  // This can only happen if the matrix is singular. Continuing will cause
326  // the algorithm to detect the singularity at the end when we stop before
327  // the end.
328  //
329  // TODO(user): We could detect the singularity at this point, but that
330  // may make the code more complex.
331  if (residual_matrix_non_zero_.ColDegree(col) != 1) continue;
332 
333  // ComputeColumn() is not used as long as only singleton columns of the
334  // residual matrix are used. See the other condition in
335  // ComputeRowAndColumnPermutation().
336  if (contains_only_singleton_columns_) {
337  *pivot_col = col;
338  for (const SparseColumn::Entry e : basis_matrix_->column(col)) {
339  if (row_perm[e.row()] == kInvalidRow) {
340  *pivot_row = e.row();
341  *pivot_coefficient = e.coefficient();
342  break;
343  }
344  }
345  return 0;
346  }
347  const SparseColumn& column = ComputeColumn(row_perm, col);
348  if (column.IsEmpty()) continue;
349  *pivot_col = col;
350  *pivot_row = column.GetFirstRow();
351  *pivot_coefficient = column.GetFirstCoefficient();
352  return 0;
353  }
354  contains_only_singleton_columns_ = false;
355 
356  // Fast track for singleton rows. Note that this is actually more than a fast
357  // track because of the Zlatev heuristic. Such rows may not be processed as
358  // soon as possible otherwise, resulting in more fill-in.
359  while (!singleton_row_.empty()) {
360  const RowIndex row = singleton_row_.back();
361  singleton_row_.pop_back();
362 
363  // A singleton row could have been processed when processing a singleton
364  // column. Skip if this is the case.
365  if (row_perm[row] != kInvalidRow) continue;
366 
367  // This shows that the matrix is singular, see comment above for the same
368  // case when processing singleton columns.
369  if (residual_matrix_non_zero_.RowDegree(row) != 1) continue;
370  const ColIndex col =
371  residual_matrix_non_zero_.GetFirstNonDeletedColumnFromRow(row);
372  if (col == kInvalidCol) continue;
373  const SparseColumn& column = ComputeColumn(row_perm, col);
374  if (column.IsEmpty()) continue;
375 
376  *pivot_col = col;
377  *pivot_row = row;
378  *pivot_coefficient = column.LookUpCoefficient(row);
379  return 0;
380  }
381 
382  // col_by_degree_ is not needed before we reach this point. Exploit this with
383  // a lazy initialization.
384  if (!is_col_by_degree_initialized_) {
385  is_col_by_degree_initialized_ = true;
386  const ColIndex num_cols = col_perm.size();
387  col_by_degree_.Reset(row_perm.size().value(), num_cols);
388  for (ColIndex col(0); col < num_cols; ++col) {
389  if (col_perm[col] != kInvalidCol) continue;
390  const int degree = residual_matrix_non_zero_.ColDegree(col);
391  DCHECK_NE(degree, 1);
392  UpdateDegree(col, degree);
393  }
394  }
395 
396  // Note(user): we use int64_t since this is a product of two ints, moreover
397  // the ints should be relatively small, so that should be fine for a while.
398  int64_t min_markowitz_number = std::numeric_limits<int64_t>::max();
399  examined_col_.clear();
400  const int num_columns_to_examine = parameters_.markowitz_zlatev_parameter();
401  const Fractional threshold = parameters_.lu_factorization_pivot_threshold();
402  while (examined_col_.size() < num_columns_to_examine) {
403  const ColIndex col = col_by_degree_.Pop();
404  if (col == kInvalidCol) break;
405  if (col_perm[col] != kInvalidCol) continue;
406  const int col_degree = residual_matrix_non_zero_.ColDegree(col);
407  examined_col_.push_back(col);
408 
409  // Because of the two singleton special cases at the beginning of this
410  // function and because we process columns by increasing degree, we can
411  // derive a lower bound on the best markowitz number we can get by exploring
412  // this column. If we cannot beat this number, we can stop here.
413  //
414  // Note(user): we still process extra column if we can meet the lower bound
415  // to eventually have a better pivot.
416  //
417  // Todo(user): keep the minimum row degree to have a better bound?
418  const int64_t markowitz_lower_bound = col_degree - 1;
419  if (min_markowitz_number < markowitz_lower_bound) break;
420 
421  // TODO(user): col_degree (which is the same as column.num_entries()) is
422  // actually an upper bound on the number of non-zeros since there may be
423  // numerical cancellations. Exploit this here? Note that it is already used
424  // when we update the non_zero pattern of the residual matrix.
425  const SparseColumn& column = ComputeColumn(row_perm, col);
426  DCHECK_EQ(column.num_entries(), col_degree);
427 
428  Fractional max_magnitude = 0.0;
429  for (const SparseColumn::Entry e : column) {
430  max_magnitude = std::max(max_magnitude, std::abs(e.coefficient()));
431  }
432  if (max_magnitude == 0.0) {
433  // All symbolic non-zero entries have been cancelled!
434  // The matrix is singular, but we continue with the other columns.
435  examined_col_.pop_back();
436  continue;
437  }
438 
439  const Fractional skip_threshold = threshold * max_magnitude;
440  for (const SparseColumn::Entry e : column) {
441  const Fractional magnitude = std::abs(e.coefficient());
442  if (magnitude < skip_threshold) continue;
443 
444  const int row_degree = residual_matrix_non_zero_.RowDegree(e.row());
445  const int64_t markowitz_number = (col_degree - 1) * (row_degree - 1);
446  DCHECK_NE(markowitz_number, 0);
447  if (markowitz_number < min_markowitz_number ||
448  ((markowitz_number == min_markowitz_number) &&
449  magnitude > std::abs(*pivot_coefficient))) {
450  min_markowitz_number = markowitz_number;
451  *pivot_col = col;
452  *pivot_row = e.row();
453  *pivot_coefficient = e.coefficient();
454 
455  // Note(user): We could abort early here if the markowitz_lower_bound is
456  // reached, but finishing to loop over this column is fast and may lead
457  // to a pivot with a greater magnitude (i.e. a more robust
458  // factorization).
459  }
460  }
461  DCHECK_NE(min_markowitz_number, 0);
462  DCHECK_GE(min_markowitz_number, markowitz_lower_bound);
463  }
464 
465  // Push back the columns that we just looked at in the queue since they
466  // are candidates for the next pivot.
467  //
468  // TODO(user): Do that after having updated the matrix? Rationale:
469  // - col_by_degree_ is LIFO, so that may save work in ComputeColumn() by
470  // calling it again on the same columns.
471  // - Maybe the earliest low-degree columns have a better precision? This
472  // actually depends on the number of operations so is not really true.
473  // - Maybe picking the column randomly from the ones with lowest degree would
474  // help having more diversity from one factorization to the next. This is
475  // for the case we do implement this TODO.
476  for (const ColIndex col : examined_col_) {
477  if (col != *pivot_col) {
478  const int degree = residual_matrix_non_zero_.ColDegree(col);
479  col_by_degree_.PushOrAdjust(col, degree);
480  }
481  }
482  return min_markowitz_number;
483 }
484 
485 void Markowitz::UpdateDegree(ColIndex col, int degree) {
486  DCHECK(is_col_by_degree_initialized_);
487 
488  // Separating the degree one columns work because we always select such
489  // a column first and pivoting by such columns does not affect the degree of
490  // any other singleton columns (except if the matrix is not inversible).
491  //
492  // Note that using this optimization does change the order in which the
493  // degree one columns are taken compared to pushing them in the queue.
494  if (degree == 1) {
495  // Note that there is no need to remove this column from col_by_degree_
496  // because it will be processed before col_by_degree_.Pop() is called and
497  // then just be ignored.
498  singleton_column_.push_back(col);
499  } else {
500  col_by_degree_.PushOrAdjust(col, degree);
501  }
502 }
503 
504 void Markowitz::RemoveRowFromResidualMatrix(RowIndex pivot_row,
505  ColIndex pivot_col) {
506  SCOPED_TIME_STAT(&stats_);
507  // Note that instead of calling:
508  // residual_matrix_non_zero_.RemoveDeletedColumnsFromRow(pivot_row);
509  // it is a bit faster to test each position with IsColumnDeleted() since we
510  // will not need the pivot row anymore.
511  if (is_col_by_degree_initialized_) {
512  for (const ColIndex col : residual_matrix_non_zero_.RowNonZero(pivot_row)) {
513  if (residual_matrix_non_zero_.IsColumnDeleted(col)) continue;
514  UpdateDegree(col, residual_matrix_non_zero_.DecreaseColDegree(col));
515  }
516  } else {
517  for (const ColIndex col : residual_matrix_non_zero_.RowNonZero(pivot_row)) {
518  if (residual_matrix_non_zero_.IsColumnDeleted(col)) continue;
519  if (residual_matrix_non_zero_.DecreaseColDegree(col) == 1) {
520  singleton_column_.push_back(col);
521  }
522  }
523  }
524 }
525 
526 void Markowitz::RemoveColumnFromResidualMatrix(RowIndex pivot_row,
527  ColIndex pivot_col) {
528  SCOPED_TIME_STAT(&stats_);
529  // The entries of the pivot column are exactly the symbolic non-zeros of the
530  // residual matrix, since we didn't remove the entries with a coefficient of
531  // zero during PermutedLowerSparseSolve().
532  //
533  // Note that it is okay to decrease the degree of a previous pivot row since
534  // it was set to 0 and will never trigger this test. Even if it triggers it,
535  // we just ignore such singleton rows in FindPivot().
536  for (const SparseColumn::Entry e : permuted_lower_.column(pivot_col)) {
537  const RowIndex row = e.row();
538  if (residual_matrix_non_zero_.DecreaseRowDegree(row) == 1) {
539  singleton_row_.push_back(row);
540  }
541  }
542 }
543 
544 void Markowitz::UpdateResidualMatrix(RowIndex pivot_row, ColIndex pivot_col) {
545  SCOPED_TIME_STAT(&stats_);
546  const SparseColumn& pivot_column = permuted_lower_.column(pivot_col);
547  residual_matrix_non_zero_.Update(pivot_row, pivot_col, pivot_column);
548  for (const ColIndex col : residual_matrix_non_zero_.RowNonZero(pivot_row)) {
549  DCHECK_NE(col, pivot_col);
550  UpdateDegree(col, residual_matrix_non_zero_.ColDegree(col));
551  permuted_lower_column_needs_solve_[col] = true;
552  }
553  RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
554 }
555 
557  return DeterministicTimeForFpOperations(num_fp_operations_);
558 }
559 
561  row_degree_.clear();
562  col_degree_.clear();
563  row_non_zero_.clear();
564  deleted_columns_.clear();
565  bool_scratchpad_.clear();
566  num_non_deleted_columns_ = 0;
567 }
568 
569 void MatrixNonZeroPattern::Reset(RowIndex num_rows, ColIndex num_cols) {
570  row_degree_.AssignToZero(num_rows);
571  col_degree_.AssignToZero(num_cols);
572  row_non_zero_.clear();
573  row_non_zero_.resize(num_rows.value());
574  deleted_columns_.assign(num_cols, false);
575  bool_scratchpad_.assign(num_cols, false);
576  num_non_deleted_columns_ = num_cols;
577 }
578 
580  const CompactSparseMatrixView& basis_matrix, const RowPermutation& row_perm,
581  const ColumnPermutation& col_perm, std::vector<ColIndex>* singleton_columns,
582  std::vector<RowIndex>* singleton_rows) {
583  const ColIndex num_cols = basis_matrix.num_cols();
584  const RowIndex num_rows = basis_matrix.num_rows();
585 
586  // Reset the matrix and initialize the vectors to the correct sizes.
587  Reset(num_rows, num_cols);
588  singleton_columns->clear();
589  singleton_rows->clear();
590 
591  // Compute the number of entries in each row.
592  for (ColIndex col(0); col < num_cols; ++col) {
593  if (col_perm[col] != kInvalidCol) {
594  deleted_columns_[col] = true;
595  --num_non_deleted_columns_;
596  continue;
597  }
598  for (const SparseColumn::Entry e : basis_matrix.column(col)) {
599  ++row_degree_[e.row()];
600  }
601  }
602 
603  // Reserve the row_non_zero_ vector sizes.
604  for (RowIndex row(0); row < num_rows; ++row) {
605  if (row_perm[row] == kInvalidRow) {
606  row_non_zero_[row].reserve(row_degree_[row]);
607  if (row_degree_[row] == 1) singleton_rows->push_back(row);
608  } else {
609  // This is needed because in the row degree computation above, we do not
610  // test for row_perm[row] == kInvalidRow because it is a bit faster.
611  row_degree_[row] = 0;
612  }
613  }
614 
615  // Initialize row_non_zero_.
616  for (ColIndex col(0); col < num_cols; ++col) {
617  if (col_perm[col] != kInvalidCol) continue;
618  int32_t col_degree = 0;
619  for (const SparseColumn::Entry e : basis_matrix.column(col)) {
620  const RowIndex row = e.row();
621  if (row_perm[row] == kInvalidRow) {
622  ++col_degree;
623  row_non_zero_[row].push_back(col);
624  }
625  }
626  col_degree_[col] = col_degree;
627  if (col_degree == 1) singleton_columns->push_back(col);
628  }
629 }
630 
631 void MatrixNonZeroPattern::AddEntry(RowIndex row, ColIndex col) {
632  ++row_degree_[row];
633  ++col_degree_[col];
634  row_non_zero_[row].push_back(col);
635 }
636 
638  return --col_degree_[col];
639 }
640 
642  return --row_degree_[row];
643 }
644 
646  ColIndex pivot_col) {
647  DCHECK(!deleted_columns_[pivot_col]);
648  deleted_columns_[pivot_col] = true;
649  --num_non_deleted_columns_;
650 
651  // We do that to optimize RemoveColumnFromResidualMatrix().
652  row_degree_[pivot_row] = 0;
653 }
654 
656  return deleted_columns_[col];
657 }
658 
660  auto& ref = row_non_zero_[row];
661  int new_index = 0;
662  const int end = ref.size();
663  for (int i = 0; i < end; ++i) {
664  const ColIndex col = ref[i];
665  if (!deleted_columns_[col]) {
666  ref[new_index] = col;
667  ++new_index;
668  }
669  }
670  ref.resize(new_index);
671 }
672 
674  RowIndex row) const {
675  for (const ColIndex col : RowNonZero(row)) {
676  if (!IsColumnDeleted(col)) return col;
677  }
678  return kInvalidCol;
679 }
680 
681 void MatrixNonZeroPattern::Update(RowIndex pivot_row, ColIndex pivot_col,
682  const SparseColumn& column) {
683  // Since DeleteRowAndColumn() must be called just before this function,
684  // the pivot column has been marked as deleted but degrees have not been
685  // updated yet. Hence the +1.
686  DCHECK(deleted_columns_[pivot_col]);
687  const int max_row_degree = num_non_deleted_columns_.value() + 1;
688 
689  RemoveDeletedColumnsFromRow(pivot_row);
690  for (const ColIndex col : row_non_zero_[pivot_row]) {
692  bool_scratchpad_[col] = false;
693  }
694 
695  // We only need to merge the row for the position with a coefficient different
696  // from 0.0. Note that the column must contain all the symbolic non-zeros for
697  // the row degree to be updated correctly. Note also that decreasing the row
698  // degrees due to the deletion of pivot_col will happen outside this function.
699  for (const SparseColumn::Entry e : column) {
700  const RowIndex row = e.row();
701  if (row == pivot_row) continue;
702 
703  // If the row is fully dense, there is nothing to do (the merge below will
704  // not change anything). This is a small price to pay for a huge gain when
705  // the matrix becomes dense.
706  if (e.coefficient() == 0.0 || row_degree_[row] == max_row_degree) continue;
707  DCHECK_LT(row_degree_[row], max_row_degree);
708 
709  // We only clean row_non_zero_[row] if there are more than 4 entries to
710  // delete. Note(user): the 4 is somewhat arbitrary, but gives good results
711  // on the Netlib (23/04/2013). Note that calling
712  // RemoveDeletedColumnsFromRow() is not mandatory and does not change the LU
713  // decomposition, so we could call it all the time or never and the
714  // algorithm would still work.
715  const int kDeletionThreshold = 4;
716  if (row_non_zero_[row].size() > row_degree_[row] + kDeletionThreshold) {
718  }
719  // TODO(user): Special case if row_non_zero_[pivot_row].size() == 1?
720  if (/* DISABLES CODE */ (true)) {
721  MergeInto(pivot_row, row);
722  } else {
723  // This is currently not used, but kept as an alternative algorithm to
724  // investigate. The performance is really similar, but the final L.U is
725  // different. Note that when this is used, there is no need to modify
726  // bool_scratchpad_ at the beginning of this function.
727  //
728  // TODO(user): Add unit tests before using this.
729  MergeIntoSorted(pivot_row, row);
730  }
731  }
732 }
733 
734 void MatrixNonZeroPattern::MergeInto(RowIndex pivot_row, RowIndex row) {
735  // Note that bool_scratchpad_ must be already false on the positions in
736  // row_non_zero_[pivot_row].
737  for (const ColIndex col : row_non_zero_[row]) {
738  bool_scratchpad_[col] = true;
739  }
740 
741  auto& non_zero = row_non_zero_[row];
742  const int old_size = non_zero.size();
743  for (const ColIndex col : row_non_zero_[pivot_row]) {
744  if (bool_scratchpad_[col]) {
745  bool_scratchpad_[col] = false;
746  } else {
747  non_zero.push_back(col);
748  ++col_degree_[col];
749  }
750  }
751  row_degree_[row] += non_zero.size() - old_size;
752 }
753 
754 namespace {
755 
756 // Given two sorted vectors (the second one is the initial value of out), merges
757 // them and outputs the sorted result in out. The merge is stable and an element
758 // of input_a will appear before the identical elements of the second input.
759 template <typename V, typename W>
760 void MergeSortedVectors(const V& input_a, W* out) {
761  if (input_a.empty()) return;
762  const auto& input_b = *out;
763  int index_a = input_a.size() - 1;
764  int index_b = input_b.size() - 1;
765  int index_out = input_a.size() + input_b.size();
766  out->resize(index_out);
767  while (index_a >= 0) {
768  if (index_b < 0) {
769  while (index_a >= 0) {
770  --index_out;
771  (*out)[index_out] = input_a[index_a];
772  --index_a;
773  }
774  return;
775  }
776  --index_out;
777  if (input_a[index_a] > input_b[index_b]) {
778  (*out)[index_out] = input_a[index_a];
779  --index_a;
780  } else {
781  (*out)[index_out] = input_b[index_b];
782  --index_b;
783  }
784  }
785 }
786 
787 } // namespace
788 
789 // The algorithm first computes into col_scratchpad_ the entries in pivot_row
790 // that are not in the row (i.e. the fill-in). It then updates the non-zero
791 // pattern using this temporary vector.
792 void MatrixNonZeroPattern::MergeIntoSorted(RowIndex pivot_row, RowIndex row) {
793  // We want to add the entries of the input not already in the output.
794  const auto& input = row_non_zero_[pivot_row];
795  const auto& output = row_non_zero_[row];
796 
797  // These two resizes are because of the set_difference() output iterator api.
798  col_scratchpad_.resize(input.size());
799  col_scratchpad_.resize(std::set_difference(input.begin(), input.end(),
800  output.begin(), output.end(),
801  col_scratchpad_.begin()) -
802  col_scratchpad_.begin());
803 
804  // Add the fill-in to the pattern.
805  for (const ColIndex col : col_scratchpad_) {
806  ++col_degree_[col];
807  }
808  row_degree_[row] += col_scratchpad_.size();
809  MergeSortedVectors(col_scratchpad_, &row_non_zero_[row]);
810 }
811 
813  col_degree_.clear();
814  col_index_.clear();
815  col_by_degree_.clear();
816 }
817 
818 void ColumnPriorityQueue::Reset(int max_degree, ColIndex num_cols) {
819  Clear();
820  col_degree_.assign(num_cols, 0);
821  col_index_.assign(num_cols, -1);
822  col_by_degree_.resize(max_degree + 1);
823  min_degree_ = max_degree + 1;
824 }
825 
826 void ColumnPriorityQueue::PushOrAdjust(ColIndex col, int32_t degree) {
827  DCHECK_GE(degree, 0);
828  DCHECK_LT(degree, col_by_degree_.size());
829  DCHECK_GE(col, 0);
830  DCHECK_LT(col, col_degree_.size());
831 
832  const int32_t old_degree = col_degree_[col];
833  if (degree != old_degree) {
834  const int32_t old_index = col_index_[col];
835  if (old_index != -1) {
836  col_by_degree_[old_degree][old_index] = col_by_degree_[old_degree].back();
837  col_index_[col_by_degree_[old_degree].back()] = old_index;
838  col_by_degree_[old_degree].pop_back();
839  }
840  if (degree > 0) {
841  col_index_[col] = col_by_degree_[degree].size();
842  col_degree_[col] = degree;
843  col_by_degree_[degree].push_back(col);
844  min_degree_ = std::min(min_degree_, degree);
845  } else {
846  col_index_[col] = -1;
847  col_degree_[col] = 0;
848  }
849  }
850 }
851 
853  DCHECK_GE(min_degree_, 0);
854  DCHECK_LE(min_degree_, col_by_degree_.size());
855  while (true) {
856  if (min_degree_ == col_by_degree_.size()) return kInvalidCol;
857  if (!col_by_degree_[min_degree_].empty()) break;
858  min_degree_++;
859  }
860  const ColIndex col = col_by_degree_[min_degree_].back();
861  col_by_degree_[min_degree_].pop_back();
862  col_index_[col] = -1;
863  col_degree_[col] = 0;
864  return col;
865 }
866 
868  mapping_.assign(num_cols.value(), -1);
869  free_columns_.clear();
870  columns_.clear();
871 }
872 
874  ColIndex col) const {
875  if (mapping_[col] == -1) return empty_column_;
876  return columns_[mapping_[col]];
877 }
878 
880  ColIndex col) {
881  if (mapping_[col] != -1) return &columns_[mapping_[col]];
882  int new_col_index;
883  if (free_columns_.empty()) {
884  new_col_index = columns_.size();
885  columns_.push_back(SparseColumn());
886  } else {
887  new_col_index = free_columns_.back();
888  free_columns_.pop_back();
889  }
890  mapping_[col] = new_col_index;
891  return &columns_[new_col_index];
892 }
893 
895  DCHECK_NE(mapping_[col], -1);
896  free_columns_.push_back(mapping_[col]);
897  columns_[mapping_[col]].Clear();
898  mapping_[col] = -1;
899 }
900 
902  mapping_.clear();
903  free_columns_.clear();
904  columns_.clear();
905 }
906 
907 } // namespace glop
908 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
void reserve(size_type n)
size_type size() const
void push_back(const value_type &x)
void Reset(int32_t max_degree, ColIndex num_cols)
Definition: markowitz.cc:818
void PushOrAdjust(ColIndex col, int32_t degree)
Definition: markowitz.cc:826
const ColumnView column(ColIndex col) const
Definition: sparse.h:562
double DeterministicTimeOfLastFactorization() const
Definition: markowitz.cc:556
ABSL_MUST_USE_RESULT Status ComputeLU(const CompactSparseMatrixView &basis_matrix, RowPermutation *row_perm, ColumnPermutation *col_perm, TriangularMatrix *lower, TriangularMatrix *upper)
Definition: markowitz.cc:152
ABSL_MUST_USE_RESULT Status ComputeRowAndColumnPermutation(const CompactSparseMatrixView &basis_matrix, RowPermutation *row_perm, ColumnPermutation *col_perm)
Definition: markowitz.cc:30
const absl::InlinedVector< ColIndex, 6 > & RowNonZero(RowIndex row) const
Definition: markowitz.h:170
void DeleteRowAndColumn(RowIndex pivot_row, ColIndex pivot_col)
Definition: markowitz.cc:645
int32_t RowDegree(RowIndex row) const
Definition: markowitz.h:165
void AddEntry(RowIndex row, ColIndex col)
Definition: markowitz.cc:631
void Reset(RowIndex num_rows, ColIndex num_cols)
Definition: markowitz.cc:569
void Update(RowIndex pivot_row, ColIndex pivot_col, const SparseColumn &column)
Definition: markowitz.cc:681
int32_t ColDegree(ColIndex col) const
Definition: markowitz.h:158
ColIndex GetFirstNonDeletedColumnFromRow(RowIndex row) const
Definition: markowitz.cc:673
void InitializeFromMatrixSubset(const CompactSparseMatrixView &basis_matrix, const RowPermutation &row_perm, const ColumnPermutation &col_perm, std::vector< ColIndex > *singleton_columns, std::vector< RowIndex > *singleton_rows)
Definition: markowitz.cc:579
void assign(IndexType size, IndexType value)
const SparseColumn & column(ColIndex col) const
Definition: markowitz.cc:873
static const Status OK()
Definition: status.h:55
void assign(IntType size, const T &v)
Definition: lp_types.h:312
void AddTriangularColumnWithGivenDiagonalEntry(const SparseColumn &column, RowIndex diagonal_row, Fractional diagonal_value)
Definition: sparse.cc:710
void Swap(TriangularMatrix *other)
Definition: sparse.cc:631
void AddAndNormalizeTriangularColumn(const SparseColumn &column, RowIndex diagonal_row, Fractional diagonal_coefficient)
Definition: sparse.cc:693
void AddTriangularColumn(const ColumnView &column, RowIndex diagonal_row)
Definition: sparse.cc:678
void PermutedLowerSparseSolve(const ColumnView &rhs, const RowPermutation &row_perm, SparseColumn *lower, SparseColumn *upper)
Definition: sparse.cc:1172
void ApplyRowPermutationToNonDiagonalEntries(const RowPermutation &row_perm)
Definition: sparse.cc:750
void AddDiagonalOnlyColumn(Fractional diagonal_value)
Definition: sparse.cc:674
int64_t NumFpOperationsInLastPermutedLowerSparseSolve() const
Definition: sparse.h:781
void Reset(RowIndex num_rows, ColIndex col_capacity)
Definition: sparse.cc:562
double lower
Definition: glpk_solver.cc:81
double upper
Definition: glpk_solver.cc:82
int index
Fractional coefficient
Definition: markowitz.cc:187
ColIndex col
Definition: markowitz.cc:186
RowIndex row
Definition: markowitz.cc:185
constexpr ColIndex kInvalidCol(-1)
Permutation< ColIndex > ColumnPermutation
constexpr RowIndex kInvalidRow(-1)
Permutation< RowIndex > RowPermutation
static double DeterministicTimeForFpOperations(int64_t n)
Definition: lp_types.h:421
Collection of objects used to extend the Constraint Solver library.
int column
Definition: parse_proto.cc:32
static int input(yyscan_t yyscanner)
EntryIndex num_entries
std::optional< int64_t > end
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:438
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:439
#define GLOP_RETURN_IF_ERROR(function_call)
Definition: status.h:71
#define VLOG(verboselevel)
Definition: vlog.h:39