Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sparse.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <cstdlib>
18#include <initializer_list>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/log/check.h"
24#include "absl/strings/str_format.h"
25#include "absl/types/span.h"
30
31namespace operations_research {
32namespace glop {
33
34namespace {
35
36using ::util::Reverse;
37
38template <typename Matrix>
39EntryIndex ComputeNumEntries(const Matrix& matrix) {
40 EntryIndex num_entries(0);
41 const ColIndex num_cols(matrix.num_cols());
42 for (ColIndex col(0); col < num_cols; ++col) {
43 num_entries += matrix.column(col).num_entries();
44 }
45 return num_entries;
46}
47
48// Computes the 1-norm of the matrix.
49// The 1-norm |A| is defined as max_j sum_i |a_ij| or
50// max_col sum_row |a(row,col)|.
51template <typename Matrix>
52Fractional ComputeOneNormTemplate(const Matrix& matrix) {
53 Fractional norm(0.0);
54 const ColIndex num_cols(matrix.num_cols());
55 for (ColIndex col(0); col < num_cols; ++col) {
56 Fractional column_norm(0);
57 for (const SparseColumn::Entry e : matrix.column(col)) {
58 // Compute sum_i |a_ij|.
59 column_norm += fabs(e.coefficient());
60 }
61 // Compute max_j sum_i |a_ij|
62 norm = std::max(norm, column_norm);
63 }
64 return norm;
65}
66
67// Computes the oo-norm (infinity-norm) of the matrix.
68// The oo-norm |A| is defined as max_i sum_j |a_ij| or
69// max_row sum_col |a(row,col)|.
70template <typename Matrix>
71Fractional ComputeInfinityNormTemplate(const Matrix& matrix) {
72 DenseColumn row_sum(matrix.num_rows(), 0.0);
73 const ColIndex num_cols(matrix.num_cols());
74 for (ColIndex col(0); col < num_cols; ++col) {
75 for (const SparseColumn::Entry e : matrix.column(col)) {
76 // Compute sum_j |a_ij|.
77 row_sum[e.row()] += fabs(e.coefficient());
78 }
79 }
80
81 // Compute max_i sum_j |a_ij|
82 Fractional norm = 0.0;
83 const RowIndex num_rows(matrix.num_rows());
84 for (RowIndex row(0); row < num_rows; ++row) {
85 norm = std::max(norm, row_sum[row]);
86 }
87 return norm;
88}
89
90} // namespace
91
92// --------------------------------------------------------
93// SparseMatrix
94// --------------------------------------------------------
95SparseMatrix::SparseMatrix() : columns_(), num_rows_(0) {}
96
97#if (!defined(_MSC_VER) || (_MSC_VER >= 1800))
99 std::initializer_list<std::initializer_list<Fractional>> init_list) {
100 ColIndex num_cols(0);
101 num_rows_ = RowIndex(init_list.size());
102 RowIndex row(0);
103 for (std::initializer_list<Fractional> init_row : init_list) {
104 num_cols = std::max(num_cols, ColIndex(init_row.size()));
105 columns_.resize(num_cols, SparseColumn());
106 ColIndex col(0);
107 for (Fractional value : init_row) {
108 if (value != 0.0) {
109 columns_[col].SetCoefficient(row, value);
110 }
111 ++col;
112 }
113 ++row;
114 }
115}
116#endif
117
118void SparseMatrix::Clear() {
119 columns_.clear();
120 num_rows_ = RowIndex(0);
121}
122
123bool SparseMatrix::IsEmpty() const {
124 return columns_.empty() || num_rows_ == 0;
126
128 const ColIndex num_cols(columns_.size());
129 for (ColIndex col(0); col < num_cols; ++col) {
130 columns_[col].CleanUp();
131 }
132}
133
134bool SparseMatrix::CheckNoDuplicates() const {
135 DenseBooleanColumn boolean_column;
136 const ColIndex num_cols(columns_.size());
137 for (ColIndex col(0); col < num_cols; ++col) {
138 if (!columns_[col].CheckNoDuplicates(&boolean_column)) return false;
139 }
140 return true;
141}
142
143bool SparseMatrix::IsCleanedUp() const {
144 const ColIndex num_cols(columns_.size());
145 for (ColIndex col(0); col < num_cols; ++col) {
146 if (!columns_[col].IsCleanedUp()) return false;
147 }
148 return true;
149}
150
151void SparseMatrix::SetNumRows(RowIndex num_rows) { num_rows_ = num_rows; }
152
154 const ColIndex result = columns_.size();
155 columns_.push_back(SparseColumn());
156 return result;
157}
158
159void SparseMatrix::AppendUnitVector(RowIndex row, Fractional value) {
160 DCHECK_LT(row, num_rows_);
162 new_col.SetCoefficient(row, value);
163 columns_.push_back(std::move(new_col));
164}
165
166void SparseMatrix::Swap(SparseMatrix* matrix) {
167 // We do not need to swap the different mutable scratchpads we use.
168 columns_.swap(matrix->columns_);
169 std::swap(num_rows_, matrix->num_rows_);
170}
171
172void SparseMatrix::PopulateFromZero(RowIndex num_rows, ColIndex num_cols) {
173 columns_.resize(num_cols, SparseColumn());
174 for (ColIndex col(0); col < num_cols; ++col) {
175 columns_[col].Clear();
176 }
177 num_rows_ = num_rows;
178}
179
180void SparseMatrix::PopulateFromIdentity(ColIndex num_cols) {
181 PopulateFromZero(ColToRowIndex(num_cols), num_cols);
182 for (ColIndex col(0); col < num_cols; ++col) {
183 const RowIndex row = ColToRowIndex(col);
184 columns_[col].SetCoefficient(row, Fractional(1.0));
185 }
186}
187
188template <typename Matrix>
189void SparseMatrix::PopulateFromTranspose(const Matrix& input) {
190 Reset(RowToColIndex(input.num_rows()), ColToRowIndex(input.num_cols()));
192 // We do a first pass on the input matrix to resize the new columns properly.
193 StrictITIVector<RowIndex, EntryIndex> row_degree(input.num_rows(),
194 EntryIndex(0));
195 for (ColIndex col(0); col < input.num_cols(); ++col) {
196 for (const SparseColumn::Entry e : input.column(col)) {
197 ++row_degree[e.row()];
198 }
199 }
200 for (RowIndex row(0); row < input.num_rows(); ++row) {
201 columns_[RowToColIndex(row)].Reserve(row_degree[row]);
202 }
203
204 for (ColIndex col(0); col < input.num_cols(); ++col) {
205 const RowIndex transposed_row = ColToRowIndex(col);
206 for (const SparseColumn::Entry e : input.column(col)) {
207 const ColIndex transposed_col = RowToColIndex(e.row());
208 columns_[transposed_col].SetCoefficient(transposed_row, e.coefficient());
209 }
210 }
211 DCHECK(IsCleanedUp());
212}
213
214void SparseMatrix::PopulateFromSparseMatrix(const SparseMatrix& matrix) {
215 Reset(ColIndex(0), matrix.num_rows_);
216 columns_ = matrix.columns_;
217}
218
219template <typename Matrix>
221 const Matrix& a, const RowPermutation& row_perm,
222 const ColumnPermutation& inverse_col_perm) {
223 const ColIndex num_cols = a.num_cols();
224 Reset(num_cols, a.num_rows());
225 for (ColIndex col(0); col < num_cols; ++col) {
226 for (const auto e : a.column(inverse_col_perm[col])) {
227 columns_[col].SetCoefficient(row_perm[e.row()], e.coefficient());
228 }
229 }
230 DCHECK(CheckNoDuplicates());
231}
232
234 const SparseMatrix& a,
236 const SparseMatrix& b) {
237 DCHECK_EQ(a.num_cols(), b.num_cols());
238 DCHECK_EQ(a.num_rows(), b.num_rows());
239
240 const ColIndex num_cols = a.num_cols();
241 Reset(num_cols, a.num_rows());
242
243 const RowIndex num_rows = a.num_rows();
245 for (ColIndex col(0); col < num_cols; ++col) {
246 for (const SparseColumn::Entry e : a.columns_[col]) {
247 dense_column.AddToCoefficient(e.row(), alpha * e.coefficient());
248 }
249 for (const SparseColumn::Entry e : b.columns_[col]) {
250 dense_column.AddToCoefficient(e.row(), beta * e.coefficient());
251 }
252 dense_column.PopulateSparseColumn(&columns_[col]);
253 columns_[col].CleanUp();
254 dense_column.Clear();
255 }
256}
257
259 const SparseMatrix& b) {
260 const ColIndex num_cols = b.num_cols();
261 const RowIndex num_rows = a.num_rows();
262 Reset(num_cols, num_rows);
263
265 for (ColIndex col_b(0); col_b < num_cols; ++col_b) {
266 for (const SparseColumn::Entry eb : b.columns_[col_b]) {
267 if (eb.coefficient() == 0.0) {
268 continue;
269 }
270 const ColIndex col_a = RowToColIndex(eb.row());
271 for (const SparseColumn::Entry ea : a.columns_[col_a]) {
272 const Fractional value = ea.coefficient() * eb.coefficient();
273 tmp_column.AddToCoefficient(ea.row(), value);
274 }
275 }
276
277 // Populate column col_b.
278 tmp_column.PopulateSparseColumn(&columns_[col_b]);
279 columns_[col_b].CleanUp();
280 tmp_column.Clear();
281 }
282}
283
284void SparseMatrix::DeleteColumns(const DenseBooleanRow& columns_to_delete) {
285 if (columns_to_delete.empty()) return;
286 ColIndex new_index(0);
287 const ColIndex num_cols = columns_.size();
288 for (ColIndex col(0); col < num_cols; ++col) {
289 if (col >= columns_to_delete.size() || !columns_to_delete[col]) {
290 columns_[col].Swap(&(columns_[new_index]));
291 ++new_index;
292 }
293 }
294 columns_.resize(new_index);
295}
296
297void SparseMatrix::DeleteRows(RowIndex new_num_rows,
298 const RowPermutation& permutation) {
299 DCHECK_EQ(num_rows_, permutation.size());
300 for (RowIndex row(0); row < num_rows_; ++row) {
301 DCHECK_LT(permutation[row], new_num_rows);
302 }
303 const ColIndex end = num_cols();
304 for (ColIndex col(0); col < end; ++col) {
305 columns_[col].ApplyPartialRowPermutation(permutation);
306 }
307 SetNumRows(new_num_rows);
308}
309
310bool SparseMatrix::AppendRowsFromSparseMatrix(const SparseMatrix& matrix) {
311 const ColIndex end = num_cols();
312 if (end != matrix.num_cols()) {
313 return false;
314 }
315 const RowIndex offset = num_rows();
316 for (ColIndex col(0); col < end; ++col) {
317 const SparseColumn& source_column = matrix.columns_[col];
318 columns_[col].AppendEntriesWithOffset(source_column, offset);
319 }
320 SetNumRows(offset + matrix.num_rows());
321 return true;
322}
323
324void SparseMatrix::ApplyRowPermutation(const RowPermutation& row_perm) {
325 const ColIndex num_cols(columns_.size());
326 for (ColIndex col(0); col < num_cols; ++col) {
327 columns_[col].ApplyRowPermutation(row_perm);
328 }
329}
330
331Fractional SparseMatrix::LookUpValue(RowIndex row, ColIndex col) const {
332 return columns_[col].LookUpCoefficient(row);
334
335bool SparseMatrix::Equals(const SparseMatrix& a, Fractional tolerance) const {
336 if (num_cols() != a.num_cols() || num_rows() != a.num_rows()) {
337 return false;
338 }
339
340 RandomAccessSparseColumn dense_column(num_rows());
341 RandomAccessSparseColumn dense_column_a(num_rows());
342 const ColIndex num_cols = a.num_cols();
343 for (ColIndex col(0); col < num_cols; ++col) {
344 // Store all entries of current matrix in a dense column.
345 for (const SparseColumn::Entry e : columns_[col]) {
346 dense_column.AddToCoefficient(e.row(), e.coefficient());
347 }
348
349 // Check all entries of a are those stored in the dense column.
350 for (const SparseColumn::Entry e : a.columns_[col]) {
351 if (fabs(e.coefficient() - dense_column.GetCoefficient(e.row())) >
352 tolerance) {
353 return false;
354 }
355 }
356
357 // Store all entries of matrix a in a dense column.
358 for (const SparseColumn::Entry e : a.columns_[col]) {
359 dense_column_a.AddToCoefficient(e.row(), e.coefficient());
360 }
361
362 // Check all entries are those stored in the dense column a.
363 for (const SparseColumn::Entry e : columns_[col]) {
364 if (fabs(e.coefficient() - dense_column_a.GetCoefficient(e.row())) >
365 tolerance) {
366 return false;
367 }
368 }
369
370 dense_column.Clear();
371 dense_column_a.Clear();
372 }
373
374 return true;
375}
376
377void SparseMatrix::ComputeMinAndMaxMagnitudes(Fractional* min_magnitude,
378 Fractional* max_magnitude) const {
379 RETURN_IF_NULL(min_magnitude);
380 RETURN_IF_NULL(max_magnitude);
381 *min_magnitude = kInfinity;
382 *max_magnitude = 0.0;
383 for (ColIndex col(0); col < num_cols(); ++col) {
384 for (const SparseColumn::Entry e : columns_[col]) {
385 const Fractional magnitude = fabs(e.coefficient());
386 if (magnitude != 0.0) {
387 *min_magnitude = std::min(*min_magnitude, magnitude);
388 *max_magnitude = std::max(*max_magnitude, magnitude);
389 }
390 }
391 }
392 if (*max_magnitude == 0.0) {
393 *min_magnitude = 0.0;
394 }
395}
396
397EntryIndex SparseMatrix::num_entries() const {
398 return ComputeNumEntries(*this);
401 return ComputeOneNormTemplate(*this);
404 return ComputeInfinityNormTemplate(*this);
406
407std::string SparseMatrix::Dump() const {
408 std::string result;
409 const ColIndex num_cols(columns_.size());
410
411 for (RowIndex row(0); row < num_rows_; ++row) {
412 result.append("{ ");
413 for (ColIndex col(0); col < num_cols; ++col) {
414 absl::StrAppendFormat(&result, "%g ", ToDouble(LookUpValue(row, col)));
415 }
416 result.append("}\n");
417 }
418 return result;
419}
420
421void SparseMatrix::Reset(ColIndex num_cols, RowIndex num_rows) {
422 Clear();
423 columns_.resize(num_cols, SparseColumn());
424 num_rows_ = num_rows;
425}
426
427EntryIndex MatrixView::num_entries() const { return ComputeNumEntries(*this); }
428Fractional MatrixView::ComputeOneNorm() const {
429 return ComputeOneNormTemplate(*this);
432 return ComputeInfinityNormTemplate(*this);
434
435// Instantiate needed templates.
437 const SparseMatrix& input);
439 const SparseMatrix& a, const RowPermutation& row_perm,
440 const ColumnPermutation& inverse_col_perm);
442 const CompactSparseMatrixView& a, const RowPermutation& row_perm,
443 const ColumnPermutation& inverse_col_perm);
444
446 num_cols_ = input.num_cols();
447 num_rows_ = input.num_rows();
448 const EntryIndex num_entries = input.num_entries();
449 starts_.assign(num_cols_ + 1, EntryIndex(0));
450 coefficients_.assign(num_entries, 0.0);
451 rows_.assign(num_entries, RowIndex(0));
452 EntryIndex index(0);
453 for (ColIndex col(0); col < input.num_cols(); ++col) {
454 starts_[col] = index;
455 for (const SparseColumn::Entry e : input.column(col)) {
456 coefficients_[index] = e.coefficient();
457 rows_[index] = e.row();
458 ++index;
459 }
460 }
461 starts_[input.num_cols()] = index;
462}
463
464void CompactSparseMatrix::PopulateFromSparseMatrixAndAddSlacks(
465 const SparseMatrix& input) {
466 const int input_num_cols = input.num_cols().value();
467 num_cols_ = input_num_cols + RowToColIndex(input.num_rows());
468 num_rows_ = input.num_rows();
469 const EntryIndex num_entries =
470 input.num_entries() + EntryIndex(num_rows_.value());
471 starts_.assign(num_cols_ + 1, EntryIndex(0));
472 coefficients_.resize(num_entries, 0.0);
473 rows_.resize(num_entries, RowIndex(0));
474 EntryIndex index(0);
475 for (ColIndex col(0); col < input_num_cols; ++col) {
476 starts_[col] = index;
477 for (const SparseColumn::Entry e : input.column(col)) {
478 coefficients_[index] = e.coefficient();
479 rows_[index] = e.row();
480 ++index;
481 }
482 }
483 for (RowIndex row(0); row < num_rows_; ++row) {
484 starts_[input_num_cols + RowToColIndex(row)] = index;
485 coefficients_[index] = 1.0;
486 rows_[index] = row;
487 ++index;
488 }
489 DCHECK_EQ(index, num_entries);
490 starts_[num_cols_] = index;
491}
492
493void CompactSparseMatrix::PopulateFromTranspose(
494 const CompactSparseMatrix& input) {
496 num_rows_ = ColToRowIndex(input.num_cols());
497
498 // Fill the starts_ vector by computing the number of entries of each rows and
499 // then doing a cumulative sum. After this step starts_[col + 1] will be the
500 // actual start of the column col when we are done.
501 const ColIndex start_size = num_cols_ + 2;
502 starts_.assign(start_size, EntryIndex(0));
503 for (const RowIndex row : input.rows_) {
504 ++starts_[RowToColIndex(row) + 2];
505 }
506 for (ColIndex col(2); col < start_size; ++col) {
507 starts_[col] += starts_[col - 1];
508 }
509 coefficients_.resize(starts_.back(), 0.0);
510 rows_.resize(starts_.back(), kInvalidRow);
511 starts_.pop_back();
512
513 // Use starts_ to fill the matrix. Note that starts_ is modified so that at
514 // the end it has its final values.
515 const auto entry_rows = rows_.view();
516 const auto input_entry_rows = input.rows_.view();
517 const auto entry_coefficients = coefficients_.view();
518 const auto input_entry_coefficients = input.coefficients_.view();
519 const auto num_cols = input.num_cols();
520 const auto starts = starts_.view();
521 for (ColIndex col(0); col < num_cols; ++col) {
522 const RowIndex transposed_row = ColToRowIndex(col);
523 for (const EntryIndex i : input.Column(col)) {
524 const ColIndex transposed_col = RowToColIndex(input_entry_rows[i]);
525 const EntryIndex index = starts[transposed_col + 1]++;
526 entry_coefficients[index] = input_entry_coefficients[i];
527 entry_rows[index] = transposed_row;
528 }
529 }
530
531 DCHECK_EQ(starts_.front(), 0);
532 DCHECK_EQ(starts_.back(), rows_.size());
533}
534
535void TriangularMatrix::PopulateFromTranspose(const TriangularMatrix& input) {
536 CompactSparseMatrix::PopulateFromTranspose(input);
538 // This takes care of the triangular special case.
539 diagonal_coefficients_ = input.diagonal_coefficients_;
540 all_diagonal_coefficients_are_one_ = input.all_diagonal_coefficients_are_one_;
541
542 // The elimination structure of the transpose is not the same.
543 pruned_ends_.resize(num_cols_, EntryIndex(0));
544 for (ColIndex col(0); col < num_cols_; ++col) {
545 pruned_ends_[col] = starts_[col + 1];
546 }
547
548 // Compute first_non_identity_column_. Note that this is not necessarily the
549 // same as input.first_non_identity_column_ for an upper triangular matrix.
550 first_non_identity_column_ = 0;
551 const ColIndex end = diagonal_coefficients_.size();
552 while (first_non_identity_column_ < end &&
553 ColumnNumEntries(first_non_identity_column_) == 0 &&
554 diagonal_coefficients_[first_non_identity_column_] == 1.0) {
555 ++first_non_identity_column_;
556 }
557}
558
559void CompactSparseMatrix::Reset(RowIndex num_rows) {
560 num_rows_ = num_rows;
562 rows_.clear();
563 coefficients_.clear();
564 starts_.clear();
565 starts_.push_back(EntryIndex(0));
566}
567
568void TriangularMatrix::Reset(RowIndex num_rows, ColIndex col_capacity) {
570 first_non_identity_column_ = 0;
571 all_diagonal_coefficients_are_one_ = true;
572
573 pruned_ends_.resize(col_capacity);
574 diagonal_coefficients_.resize(col_capacity);
575 starts_.resize(col_capacity + 1);
576 // Non-zero entries in the first column always have an offset of 0.
577 starts_[ColIndex(0)] = 0;
578}
579
581 Fractional coeff) {
582 rows_.push_back(row);
583 coefficients_.push_back(coeff);
584}
585
587 starts_.push_back(rows_.size());
589}
590
591ColIndex CompactSparseMatrix::AddDenseColumn(const DenseColumn& dense_column) {
592 return AddDenseColumnPrefix(dense_column.const_view(), RowIndex(0));
596 DenseColumn::ConstView dense_column, RowIndex start) {
597 const RowIndex num_rows(dense_column.size());
598 for (RowIndex row(start); row < num_rows; ++row) {
599 if (dense_column[row] != 0.0) {
600 rows_.push_back(row);
601 coefficients_.push_back(dense_column[row]);
602 }
603 }
604 starts_.push_back(rows_.size());
605 ++num_cols_;
606 return num_cols_ - 1;
607}
608
609ColIndex CompactSparseMatrix::AddDenseColumnWithNonZeros(
610 const DenseColumn& dense_column, absl::Span<const RowIndex> non_zeros) {
611 if (non_zeros.empty()) return AddDenseColumn(dense_column);
612 for (const RowIndex row : non_zeros) {
613 const Fractional value = dense_column[row];
614 if (value != 0.0) {
615 rows_.push_back(row);
616 coefficients_.push_back(value);
617 }
618 }
619 starts_.push_back(rows_.size());
620 ++num_cols_;
621 return num_cols_ - 1;
622}
623
624ColIndex CompactSparseMatrix::AddAndClearColumnWithNonZeros(
625 DenseColumn* column, std::vector<RowIndex>* non_zeros) {
626 for (const RowIndex row : *non_zeros) {
627 const Fractional value = (*column)[row];
628 if (value != 0.0) {
629 rows_.push_back(row);
630 coefficients_.push_back(value);
631 (*column)[row] = 0.0;
632 }
633 }
634 non_zeros->clear();
635 starts_.push_back(rows_.size());
636 ++num_cols_;
637 return num_cols_ - 1;
638}
639
640void CompactSparseMatrix::Swap(CompactSparseMatrix* other) {
641 std::swap(num_rows_, other->num_rows_);
642 std::swap(num_cols_, other->num_cols_);
643 coefficients_.swap(other->coefficients_);
644 rows_.swap(other->rows_);
645 starts_.swap(other->starts_);
646}
647
650 diagonal_coefficients_.swap(other->diagonal_coefficients_);
651 std::swap(first_non_identity_column_, other->first_non_identity_column_);
652 std::swap(all_diagonal_coefficients_are_one_,
653 other->all_diagonal_coefficients_are_one_);
654}
655
656EntryIndex CompactSparseMatrixView::num_entries() const {
657 return ComputeNumEntries(*this);
660 return ComputeOneNormTemplate(*this);
663 return ComputeInfinityNormTemplate(*this);
665
666// Internal function used to finish adding one column to a triangular matrix.
667// This sets the diagonal coefficient to the given value, and prepares the
668// matrix for the next column addition.
669void TriangularMatrix::CloseCurrentColumn(Fractional diagonal_value) {
670 DCHECK_NE(diagonal_value, 0.0);
671 // The vectors diagonal_coefficients, pruned_ends, and starts_ should have all
672 // been preallocated by a call to SetTotalNumberOfColumns().
673 DCHECK_LT(num_cols_, diagonal_coefficients_.size());
674 diagonal_coefficients_[num_cols_] = diagonal_value;
675
676 // TODO(user): This is currently not used by all matrices. It will be good
677 // to fill it only when needed.
678 DCHECK_LT(num_cols_, pruned_ends_.size());
679 const EntryIndex num_entries = coefficients_.size();
680 pruned_ends_[num_cols_] = num_entries;
681 ++num_cols_;
682 DCHECK_LT(num_cols_, starts_.size());
683 starts_[num_cols_] = num_entries;
684 if (first_non_identity_column_ == num_cols_ - 1 && diagonal_value == 1.0 &&
685 num_entries == 0) {
686 first_non_identity_column_ = num_cols_;
687 }
688 all_diagonal_coefficients_are_one_ =
689 all_diagonal_coefficients_are_one_ && (diagonal_value == 1.0);
690}
691
692void TriangularMatrix::AddDiagonalOnlyColumn(Fractional diagonal_value) {
693 CloseCurrentColumn(diagonal_value);
697 RowIndex diagonal_row) {
698 Fractional diagonal_value = 0.0;
699 for (const SparseColumn::Entry e : column) {
700 if (e.row() == diagonal_row) {
701 diagonal_value = e.coefficient();
702 } else {
703 DCHECK_NE(0.0, e.coefficient());
704 rows_.push_back(e.row());
705 coefficients_.push_back(e.coefficient());
706 }
707 }
708 CloseCurrentColumn(diagonal_value);
709}
710
711void TriangularMatrix::AddAndNormalizeTriangularColumn(
712 const SparseColumn& column, RowIndex diagonal_row,
713 Fractional diagonal_coefficient) {
714 // TODO(user): use division by a constant using multiplication.
715 for (const SparseColumn::Entry e : column) {
716 if (e.row() != diagonal_row) {
717 if (e.coefficient() != 0.0) {
718 rows_.push_back(e.row());
719 coefficients_.push_back(e.coefficient() / diagonal_coefficient);
720 }
721 } else {
722 DCHECK_EQ(e.coefficient(), diagonal_coefficient);
723 }
724 }
725 CloseCurrentColumn(1.0);
726}
727
729 const SparseColumn& column, RowIndex diagonal_row,
730 Fractional diagonal_value) {
731 for (SparseColumn::Entry e : column) {
732 DCHECK_NE(e.row(), diagonal_row);
733 rows_.push_back(e.row());
734 coefficients_.push_back(e.coefficient());
735 }
736 CloseCurrentColumn(diagonal_value);
737}
738
740 const SparseMatrix& input) {
741 Reset(input.num_rows(), input.num_cols());
742 for (ColIndex col(0); col < input.num_cols(); ++col) {
744 }
745 DCHECK(IsLowerTriangular() || IsUpperTriangular());
746}
747
748bool TriangularMatrix::IsLowerTriangular() const {
749 for (ColIndex col(0); col < num_cols_; ++col) {
750 if (diagonal_coefficients_[col] == 0.0) return false;
751 for (EntryIndex i : Column(col)) {
752 if (rows_[i] <= ColToRowIndex(col)) return false;
753 }
754 }
755 return true;
756}
757
758bool TriangularMatrix::IsUpperTriangular() const {
759 for (ColIndex col(0); col < num_cols_; ++col) {
760 if (diagonal_coefficients_[col] == 0.0) return false;
761 for (EntryIndex i : Column(col)) {
762 if (rows_[i] >= ColToRowIndex(col)) return false;
763 }
764 }
765 return true;
766}
767
768void TriangularMatrix::ApplyRowPermutationToNonDiagonalEntries(
769 const RowPermutation& row_perm) {
770 EntryIndex num_entries = rows_.size();
771 for (EntryIndex i(0); i < num_entries; ++i) {
772 rows_[i] = row_perm[rows_[i]];
773 }
774}
775
776void TriangularMatrix::CopyColumnToSparseColumn(ColIndex col,
777 SparseColumn* output) const {
778 output->Clear();
779 const auto entry_rows = rows_.view();
780 const auto entry_coefficients = coefficients_.view();
781 for (const EntryIndex i : Column(col)) {
782 output->SetCoefficient(entry_rows[i], entry_coefficients[i]);
783 }
784 output->SetCoefficient(ColToRowIndex(col), diagonal_coefficients_[col]);
785 output->CleanUp();
786}
787
788void TriangularMatrix::CopyToSparseMatrix(SparseMatrix* output) const {
789 output->PopulateFromZero(num_rows_, num_cols_);
790 for (ColIndex col(0); col < num_cols_; ++col) {
791 CopyColumnToSparseColumn(col, output->mutable_column(col));
792 }
793}
794
795void TriangularMatrix::LowerSolve(DenseColumn* rhs) const {
796 LowerSolveStartingAt(ColIndex(0), rhs);
798
800 DenseColumn* rhs) const {
802 if (all_diagonal_coefficients_are_one_) {
803 LowerSolveStartingAtInternal<true>(start, rhs->view());
804 } else {
805 LowerSolveStartingAtInternal<false>(start, rhs->view());
806 }
807}
808
809template <bool diagonal_of_ones>
810void TriangularMatrix::LowerSolveStartingAtInternal(
811 ColIndex start, DenseColumn::View rhs) const {
812 const ColIndex begin = std::max(start, first_non_identity_column_);
813 const auto entry_rows = rows_.view();
814 const auto entry_coefficients = coefficients_.view();
815 const auto diagonal_coefficients = diagonal_coefficients_.view();
816 const ColIndex end = diagonal_coefficients.size();
817 for (ColIndex col(begin); col < end; ++col) {
818 const Fractional value = rhs[ColToRowIndex(col)];
819 if (value == 0.0) continue;
820 const Fractional coeff =
821 diagonal_of_ones ? value : value / diagonal_coefficients[col];
822 if (!diagonal_of_ones) {
823 rhs[ColToRowIndex(col)] = coeff;
824 }
825 for (const EntryIndex i : Column(col)) {
826 rhs[entry_rows[i]] -= coeff * entry_coefficients[i];
827 }
828 }
829}
830
831void TriangularMatrix::UpperSolve(DenseColumn* rhs) const {
832 RETURN_IF_NULL(rhs);
833 if (all_diagonal_coefficients_are_one_) {
834 UpperSolveInternal<true>(rhs->view());
835 } else {
836 UpperSolveInternal<false>(rhs->view());
837 }
838}
839
840template <bool diagonal_of_ones>
841void TriangularMatrix::UpperSolveInternal(DenseColumn::View rhs) const {
842 const ColIndex end = first_non_identity_column_;
843 const auto entry_rows = rows_.view();
844 const auto entry_coefficients = coefficients_.view();
845 const auto diagonal_coefficients = diagonal_coefficients_.view();
846 const auto starts = starts_.view();
847 for (ColIndex col(diagonal_coefficients.size() - 1); col >= end; --col) {
848 const Fractional value = rhs[ColToRowIndex(col)];
849 if (value == 0.0) continue;
850 const Fractional coeff =
851 diagonal_of_ones ? value : value / diagonal_coefficients[col];
852 if (!diagonal_of_ones) {
853 rhs[ColToRowIndex(col)] = coeff;
854 }
855
856 // It is faster to iterate this way (instead of i : Column(col)) because of
857 // cache locality. Note that the floating-point computations are exactly the
858 // same in both cases.
859 const EntryIndex i_end = starts[col];
860 for (EntryIndex i(starts[col + 1] - 1); i >= i_end; --i) {
861 rhs[entry_rows[i]] -= coeff * entry_coefficients[i];
862 }
863 }
864}
865
866void TriangularMatrix::TransposeUpperSolve(DenseColumn* rhs) const {
867 RETURN_IF_NULL(rhs);
868 if (all_diagonal_coefficients_are_one_) {
869 TransposeUpperSolveInternal<true>(rhs->view());
870 } else {
871 TransposeUpperSolveInternal<false>(rhs->view());
872 }
873}
874
875template <bool diagonal_of_ones>
876void TriangularMatrix::TransposeUpperSolveInternal(
877 DenseColumn::View rhs) const {
878 const ColIndex end = num_cols_;
879 const auto starts = starts_.view();
880 const auto entry_rows = rows_.view();
881 const auto entry_coefficients = coefficients_.view();
882 const auto diagonal_coefficients = diagonal_coefficients_.view();
883
884 EntryIndex i = starts_[first_non_identity_column_];
885 for (ColIndex col(first_non_identity_column_); col < end; ++col) {
886 Fractional sum = rhs[ColToRowIndex(col)];
887
888 // Note that this is a bit faster than the simpler
889 // for (const EntryIndex i : Column(col)) {
890 // EntryIndex i is explicitly not modified in outer iterations, since
891 // the last entry in column col is stored contiguously just before the
892 // first entry in column col+1.
893 const EntryIndex i_end = starts[col + 1];
894 const EntryIndex shifted_end = i_end - 3;
895 for (; i < shifted_end; i += 4) {
896 sum -= entry_coefficients[i] * rhs[entry_rows[i]] +
897 entry_coefficients[i + 1] * rhs[entry_rows[i + 1]] +
898 entry_coefficients[i + 2] * rhs[entry_rows[i + 2]] +
899 entry_coefficients[i + 3] * rhs[entry_rows[i + 3]];
900 }
901 if (i < i_end) {
902 sum -= entry_coefficients[i] * rhs[entry_rows[i]];
903 if (i + 1 < i_end) {
904 sum -= entry_coefficients[i + 1] * rhs[entry_rows[i + 1]];
905 if (i + 2 < i_end) {
906 sum -= entry_coefficients[i + 2] * rhs[entry_rows[i + 2]];
907 }
908 }
909 i = i_end;
910 }
911
912 rhs[ColToRowIndex(col)] =
913 diagonal_of_ones ? sum : sum / diagonal_coefficients[col];
914 }
915}
916
917void TriangularMatrix::TransposeLowerSolve(DenseColumn* rhs) const {
918 RETURN_IF_NULL(rhs);
919 if (all_diagonal_coefficients_are_one_) {
920 TransposeLowerSolveInternal<true>(rhs->view());
921 } else {
922 TransposeLowerSolveInternal<false>(rhs->view());
923 }
924}
925
926template <bool diagonal_of_ones>
927void TriangularMatrix::TransposeLowerSolveInternal(
928 DenseColumn::View rhs) const {
929 const ColIndex end = first_non_identity_column_;
930
931 // We optimize a bit the solve by skipping the last 0.0 positions.
932 ColIndex col = num_cols_ - 1;
933 while (col >= end && rhs[ColToRowIndex(col)] == 0.0) {
934 --col;
935 }
936
937 const auto starts = starts_.view();
938 const auto diagonal_coeffs = diagonal_coefficients_.view();
939 const auto entry_rows = rows_.view();
940 const auto entry_coefficients = coefficients_.view();
941 EntryIndex i = starts[col + 1] - 1;
942 for (; col >= end; --col) {
943 Fractional sum = rhs[ColToRowIndex(col)];
944
945 // Note that this is a bit faster than the simpler
946 // for (const EntryIndex i : Column(col)) {
947 // mainly because we iterate in a good direction for the cache.
948 // EntryIndex i is explicitly not modified in outer iterations, since
949 // the last entry in column col is stored contiguously just before the
950 // first entry in column col+1.
951 const EntryIndex i_end = starts[col];
952 const EntryIndex shifted_end = i_end + 3;
953 for (; i >= shifted_end; i -= 4) {
954 sum -= entry_coefficients[i] * rhs[entry_rows[i]] +
955 entry_coefficients[i - 1] * rhs[entry_rows[i - 1]] +
956 entry_coefficients[i - 2] * rhs[entry_rows[i - 2]] +
957 entry_coefficients[i - 3] * rhs[entry_rows[i - 3]];
958 }
959 if (i >= i_end) {
960 sum -= entry_coefficients[i] * rhs[entry_rows[i]];
961 if (i >= i_end + 1) {
962 sum -= entry_coefficients[i - 1] * rhs[entry_rows[i - 1]];
963 if (i >= i_end + 2) {
964 sum -= entry_coefficients[i - 2] * rhs[entry_rows[i - 2]];
965 }
966 }
967 i = i_end - 1;
968 }
969
970 rhs[ColToRowIndex(col)] =
971 diagonal_of_ones ? sum : sum / diagonal_coeffs[col];
972 }
973}
974
975void TriangularMatrix::HyperSparseSolve(DenseColumn* rhs,
976 RowIndexVector* non_zero_rows) const {
978 if (all_diagonal_coefficients_are_one_) {
979 HyperSparseSolveInternal<true>(rhs->view(), non_zero_rows);
980 } else {
981 HyperSparseSolveInternal<false>(rhs->view(), non_zero_rows);
982 }
983}
984
985template <bool diagonal_of_ones>
986void TriangularMatrix::HyperSparseSolveInternal(
987 DenseColumn::View rhs, RowIndexVector* non_zero_rows) const {
988 int new_size = 0;
989 const auto entry_rows = rows_.view();
990 const auto entry_coefficients = coefficients_.view();
991 for (const RowIndex row : *non_zero_rows) {
992 if (rhs[row] == 0.0) continue;
993 const ColIndex row_as_col = RowToColIndex(row);
994 const Fractional coeff =
995 diagonal_of_ones ? rhs[row]
996 : rhs[row] / diagonal_coefficients_[row_as_col];
997 rhs[row] = coeff;
998 for (const EntryIndex i : Column(row_as_col)) {
999 rhs[entry_rows[i]] -= coeff * entry_coefficients[i];
1000 }
1001 (*non_zero_rows)[new_size] = row;
1002 ++new_size;
1003 }
1004 non_zero_rows->resize(new_size);
1005}
1006
1007void TriangularMatrix::HyperSparseSolveWithReversedNonZeros(
1008 DenseColumn* rhs, RowIndexVector* non_zero_rows) const {
1010 if (all_diagonal_coefficients_are_one_) {
1011 HyperSparseSolveWithReversedNonZerosInternal<true>(rhs->view(),
1012 non_zero_rows);
1013 } else {
1014 HyperSparseSolveWithReversedNonZerosInternal<false>(rhs->view(),
1015 non_zero_rows);
1016 }
1017}
1018
1019template <bool diagonal_of_ones>
1020void TriangularMatrix::HyperSparseSolveWithReversedNonZerosInternal(
1021 DenseColumn::View rhs, RowIndexVector* non_zero_rows) const {
1022 int new_start = non_zero_rows->size();
1023 const auto entry_rows = rows_.view();
1024 const auto entry_coefficients = coefficients_.view();
1025 for (const RowIndex row : Reverse(*non_zero_rows)) {
1026 if (rhs[row] == 0.0) continue;
1027 const ColIndex row_as_col = RowToColIndex(row);
1028 const Fractional coeff =
1029 diagonal_of_ones ? rhs[row]
1030 : rhs[row] / diagonal_coefficients_[row_as_col];
1031 rhs[row] = coeff;
1032 for (const EntryIndex i : Column(row_as_col)) {
1033 rhs[entry_rows[i]] -= coeff * entry_coefficients[i];
1034 }
1035 --new_start;
1036 (*non_zero_rows)[new_start] = row;
1037 }
1038 non_zero_rows->erase(non_zero_rows->begin(),
1039 non_zero_rows->begin() + new_start);
1040}
1041
1042void TriangularMatrix::TransposeHyperSparseSolve(
1043 DenseColumn* rhs, RowIndexVector* non_zero_rows) const {
1045 if (all_diagonal_coefficients_are_one_) {
1046 TransposeHyperSparseSolveInternal<true>(rhs->view(), non_zero_rows);
1047 } else {
1048 TransposeHyperSparseSolveInternal<false>(rhs->view(), non_zero_rows);
1049 }
1050}
1051
1052template <bool diagonal_of_ones>
1053void TriangularMatrix::TransposeHyperSparseSolveInternal(
1054 DenseColumn::View rhs, RowIndexVector* non_zero_rows) const {
1055 int new_size = 0;
1056
1057 const auto entry_rows = rows_.view();
1058 const auto entry_coefficients = coefficients_.view();
1059 for (const RowIndex row : *non_zero_rows) {
1060 Fractional sum = rhs[row];
1061 const ColIndex row_as_col = RowToColIndex(row);
1062
1063 // Note that we do the loop in exactly the same way as
1064 // in TransposeUpperSolveInternal().
1065 EntryIndex i = starts_[row_as_col];
1066 const EntryIndex i_end = starts_[row_as_col + 1];
1067 const EntryIndex shifted_end = i_end - 3;
1068 for (; i < shifted_end; i += 4) {
1069 sum -= entry_coefficients[i] * rhs[entry_rows[i]] +
1070 entry_coefficients[i + 1] * rhs[entry_rows[i + 1]] +
1071 entry_coefficients[i + 2] * rhs[entry_rows[i + 2]] +
1072 entry_coefficients[i + 3] * rhs[entry_rows[i + 3]];
1073 }
1074 if (i < i_end) {
1075 sum -= entry_coefficients[i] * rhs[entry_rows[i]];
1076 if (i + 1 < i_end) {
1077 sum -= entry_coefficients[i + 1] * rhs[entry_rows[i + 1]];
1078 if (i + 2 < i_end) {
1079 sum -= entry_coefficients[i + 2] * rhs[entry_rows[i + 2]];
1080 }
1081 }
1082 }
1083
1084 rhs[row] =
1085 diagonal_of_ones ? sum : sum / diagonal_coefficients_[row_as_col];
1086 if (sum != 0.0) {
1087 (*non_zero_rows)[new_size] = row;
1088 ++new_size;
1089 }
1090 }
1091 non_zero_rows->resize(new_size);
1092}
1093
1094void TriangularMatrix::TransposeHyperSparseSolveWithReversedNonZeros(
1095 DenseColumn* rhs, RowIndexVector* non_zero_rows) const {
1097 if (all_diagonal_coefficients_are_one_) {
1098 TransposeHyperSparseSolveWithReversedNonZerosInternal<true>(rhs->view(),
1099 non_zero_rows);
1100 } else {
1101 TransposeHyperSparseSolveWithReversedNonZerosInternal<false>(rhs->view(),
1102 non_zero_rows);
1103 }
1104}
1105
1106template <bool diagonal_of_ones>
1107void TriangularMatrix::TransposeHyperSparseSolveWithReversedNonZerosInternal(
1108 DenseColumn::View rhs, RowIndexVector* non_zero_rows) const {
1109 int new_start = non_zero_rows->size();
1110 const auto entry_rows = rows_.view();
1111 const auto entry_coefficients = coefficients_.view();
1112 for (const RowIndex row : Reverse(*non_zero_rows)) {
1113 Fractional sum = rhs[row];
1114 const ColIndex row_as_col = RowToColIndex(row);
1115
1116 // We do the loop this way so that the floating point operations are exactly
1117 // the same as the ones performed by TransposeLowerSolveInternal().
1118 EntryIndex i = starts_[row_as_col + 1] - 1;
1119 const EntryIndex i_end = starts_[row_as_col];
1120 const EntryIndex shifted_end = i_end + 3;
1121 for (; i >= shifted_end; i -= 4) {
1122 sum -= entry_coefficients[i] * rhs[entry_rows[i]] +
1123 entry_coefficients[i - 1] * rhs[entry_rows[i - 1]] +
1124 entry_coefficients[i - 2] * rhs[entry_rows[i - 2]] +
1125 entry_coefficients[i - 3] * rhs[entry_rows[i - 3]];
1126 }
1127 if (i >= i_end) {
1128 sum -= entry_coefficients[i] * rhs[entry_rows[i]];
1129 if (i >= i_end + 1) {
1130 sum -= entry_coefficients[i - 1] * rhs[entry_rows[i - 1]];
1131 if (i >= i_end + 2) {
1132 sum -= entry_coefficients[i - 2] * rhs[entry_rows[i - 2]];
1133 }
1134 }
1135 }
1136
1137 rhs[row] =
1138 diagonal_of_ones ? sum : sum / diagonal_coefficients_[row_as_col];
1139 if (sum != 0.0) {
1140 --new_start;
1141 (*non_zero_rows)[new_start] = row;
1142 }
1143 }
1144 non_zero_rows->erase(non_zero_rows->begin(),
1145 non_zero_rows->begin() + new_start);
1146}
1147
1148void TriangularMatrix::PermutedLowerSolve(
1149 const SparseColumn& rhs, const RowPermutation& row_perm,
1150 const RowMapping& partial_inverse_row_perm, SparseColumn* lower,
1151 SparseColumn* upper) const {
1152 DCHECK(all_diagonal_coefficients_are_one_);
1153 RETURN_IF_NULL(lower);
1154 RETURN_IF_NULL(upper);
1155
1156 initially_all_zero_scratchpad_.resize(num_rows_, 0.0);
1157 for (const SparseColumn::Entry e : rhs) {
1158 initially_all_zero_scratchpad_[e.row()] = e.coefficient();
1159 }
1160
1161 const auto entry_rows = rows_.view();
1162 const auto entry_coefficients = coefficients_.view();
1163 const RowIndex end_row(partial_inverse_row_perm.size());
1164 for (RowIndex row(ColToRowIndex(first_non_identity_column_)); row < end_row;
1165 ++row) {
1166 const RowIndex permuted_row = partial_inverse_row_perm[row];
1167 const Fractional pivot = initially_all_zero_scratchpad_[permuted_row];
1168 if (pivot == 0.0) continue;
1169
1170 for (EntryIndex i : Column(RowToColIndex(row))) {
1171 initially_all_zero_scratchpad_[entry_rows[i]] -=
1172 entry_coefficients[i] * pivot;
1173 }
1174 }
1175
1176 lower->Clear();
1177 const RowIndex num_rows = num_rows_;
1178 for (RowIndex row(0); row < num_rows; ++row) {
1179 if (initially_all_zero_scratchpad_[row] != 0.0) {
1180 if (row_perm[row] < 0) {
1181 lower->SetCoefficient(row, initially_all_zero_scratchpad_[row]);
1182 } else {
1183 upper->SetCoefficient(row, initially_all_zero_scratchpad_[row]);
1184 }
1185 initially_all_zero_scratchpad_[row] = 0.0;
1186 }
1187 }
1188 DCHECK(lower->CheckNoDuplicates());
1189}
1190
1192 const RowPermutation& row_perm,
1193 SparseColumn* lower_column,
1194 SparseColumn* upper_column) {
1195 DCHECK(all_diagonal_coefficients_are_one_);
1196 RETURN_IF_NULL(lower_column);
1197 RETURN_IF_NULL(upper_column);
1198
1199 // Compute the set of rows that will be non zero in the result (lower_column,
1200 // upper_column).
1201 PermutedComputeRowsToConsider(rhs, row_perm, &lower_column_rows_,
1202 &upper_column_rows_);
1203
1204 // Copy rhs into initially_all_zero_scratchpad_.
1205 initially_all_zero_scratchpad_.resize(num_rows_, 0.0);
1206 for (const auto e : rhs) {
1207 initially_all_zero_scratchpad_[e.row()] = e.coefficient();
1208 }
1209
1210 // We clear lower_column first in case upper_column and lower_column point to
1211 // the same underlying SparseColumn.
1212 num_fp_operations_ = 0;
1213 lower_column->Clear();
1214
1215 // rows_to_consider_ contains the row to process in reverse order. Note in
1216 // particular that each "permuted_row" will never be touched again and so its
1217 // value is final. We copy the result in (lower_column, upper_column) and
1218 // clear initially_all_zero_scratchpad_ at the same time.
1219 upper_column->Reserve(upper_column->num_entries() +
1220 EntryIndex(upper_column_rows_.size()));
1221 for (const RowIndex permuted_row : Reverse(upper_column_rows_)) {
1222 const Fractional pivot = initially_all_zero_scratchpad_[permuted_row];
1223 if (pivot == 0.0) continue;
1224 // Note that permuted_row will not appear in the loop below so we
1225 // already know the value of the solution at this position.
1226 initially_all_zero_scratchpad_[permuted_row] = 0.0;
1227 const ColIndex row_as_col = RowToColIndex(row_perm[permuted_row]);
1228 DCHECK_GE(row_as_col, 0);
1229 upper_column->SetCoefficient(permuted_row, pivot);
1230 DCHECK_EQ(diagonal_coefficients_[row_as_col], 1.0);
1231 num_fp_operations_ += 1 + ColumnNumEntries(row_as_col).value();
1232 for (const auto e : column(row_as_col)) {
1233 initially_all_zero_scratchpad_[e.row()] -= e.coefficient() * pivot;
1234 }
1235 }
1236
1237 // TODO(user): The size of lower is exact, so we could be slighly faster here.
1238 lower_column->Reserve(EntryIndex(lower_column_rows_.size()));
1239 for (const RowIndex permuted_row : lower_column_rows_) {
1240 const Fractional pivot = initially_all_zero_scratchpad_[permuted_row];
1241 initially_all_zero_scratchpad_[permuted_row] = 0.0;
1242 lower_column->SetCoefficient(permuted_row, pivot);
1243 }
1244 DCHECK(lower_column->CheckNoDuplicates());
1245 DCHECK(upper_column->CheckNoDuplicates());
1246}
1247
1248// The goal is to find which rows of the working column we will need to look
1249// at in PermutedLowerSparseSolve() when solving P^{-1}.L.P.x = rhs, 'P' being a
1250// row permutation, 'L' a lower triangular matrix and 'this' being 'P^{-1}.L'.
1251// Note that the columns of L that are identity columns (this is the case for
1252// the ones corresponding to a kNonPivotal in P) can be skipped since they will
1253// leave the working column unchanged.
1254//
1255// Let G denote the graph G = (V,E) of the column-to-row adjacency of A:
1256// - 'V' is the set of nodes, one node i corresponds to a both a row
1257// and a column (the matrix is square).
1258// - 'E' is the set of arcs. There is an arc from node i to node j iff the
1259// coefficient of i-th column, j-th row of A = P^{-1}.L.P is non zero.
1260//
1261// Let S denote the set of nodes i such that rhs_i != 0.
1262// Let R denote the set of all accessible nodes from S in G.
1263// x_k is possibly non-zero iff k is in R, i.e. if k is not in R then x_k = 0
1264// for sure, and there is no need to look a the row k during the solve.
1265//
1266// So, to solve P^{-1}.L.P.x = rhs, only rows corresponding to P.R have to be
1267// considered (ignoring the one that map to identity column of L). A topological
1268// sort of P.R is used to decide in which order one should iterate on them. This
1269// will be given by upper_column_rows_ and it will be populated in reverse
1270// order.
1272 const ColumnView& rhs, const RowPermutation& row_perm,
1273 RowIndexVector* lower_column_rows, RowIndexVector* upper_column_rows) {
1274 stored_.Resize(num_rows_);
1275 marked_.resize(num_rows_, false);
1276 lower_column_rows->clear();
1277 upper_column_rows->clear();
1278 nodes_to_explore_.clear();
1279
1280 for (SparseColumn::Entry e : rhs) {
1281 const ColIndex col = RowToColIndex(row_perm[e.row()]);
1282 if (col < 0) {
1283 stored_.Set(e.row());
1284 lower_column_rows->push_back(e.row());
1285 } else {
1286 nodes_to_explore_.push_back(e.row());
1287 }
1288 }
1289
1290 // Topological sort based on Depth-First-Search.
1291 // A few notes:
1292 // - By construction, if the matrix can be permuted into a lower triangular
1293 // form, there is no cycle. This code does nothing to test for cycles, but
1294 // there is a DCHECK() to detect them during debugging.
1295 // - This version uses sentinels (kInvalidRow) on nodes_to_explore_ to know
1296 // when a node has been explored (i.e. when the recursive dfs goes back in
1297 // the call stack). This is faster than an alternate implementation that
1298 // uses another Boolean array to detect when we go back in the
1299 // depth-first search.
1300 const auto entry_rows = rows_.view();
1301 while (!nodes_to_explore_.empty()) {
1302 const RowIndex row = nodes_to_explore_.back();
1303
1304 // If the depth-first search from the current node is finished (i.e. there
1305 // is a sentinel on the stack), we store the node (which is just before on
1306 // the stack). This will store the nodes in reverse topological order.
1307 if (row < 0) {
1308 nodes_to_explore_.pop_back();
1309 const RowIndex explored_row = nodes_to_explore_.back();
1310 nodes_to_explore_.pop_back();
1311 DCHECK(!stored_[explored_row]);
1312 stored_.Set(explored_row);
1313 upper_column_rows->push_back(explored_row);
1314
1315 // Unmark and prune the nodes that are already unmarked. See the header
1316 // comment on marked_ for the algorithm description.
1317 //
1318 // Complexity note: The only difference with the "normal" DFS doing no
1319 // pruning is this extra loop here and the marked_[entry_row] = true in
1320 // the loop later in this function. On an already pruned graph, this is
1321 // probably between 1 and 2 times slower than the "normal" DFS.
1322 const ColIndex col = RowToColIndex(row_perm[explored_row]);
1323 EntryIndex i = starts_[col];
1324 EntryIndex end = pruned_ends_[col];
1325 while (i < end) {
1326 const RowIndex entry_row = entry_rows[i];
1327 if (!marked_[entry_row]) {
1328 --end;
1329
1330 // Note that we could keep the pruned row in a separate vector and
1331 // not touch the triangular matrix. But the current solution seems
1332 // better cache-wise and memory-wise.
1333 std::swap(rows_[i], rows_[end]);
1334 std::swap(coefficients_[i], coefficients_[end]);
1335 } else {
1336 marked_[entry_row] = false;
1337 ++i;
1338 }
1339 }
1340 pruned_ends_[col] = end;
1341 continue;
1342 }
1343
1344 // If the node is already stored, skip.
1345 if (stored_[row]) {
1346 nodes_to_explore_.pop_back();
1347 continue;
1348 }
1349
1350 // Expand only if we are not on a kNonPivotal row.
1351 // Otherwise we can store the node right away.
1352 const ColIndex col = RowToColIndex(row_perm[row]);
1353 if (col < 0) {
1354 stored_.Set(row);
1355 lower_column_rows->push_back(row);
1356 nodes_to_explore_.pop_back();
1357 continue;
1358 }
1359
1360 // Go one level forward in the depth-first search, and store the 'adjacent'
1361 // node on nodes_to_explore_ for further processing.
1362 nodes_to_explore_.push_back(kInvalidRow);
1363 const EntryIndex end = pruned_ends_[col];
1364 for (EntryIndex i = starts_[col]; i < end; ++i) {
1365 const RowIndex entry_row = entry_rows[i];
1366 if (!stored_[entry_row]) {
1367 nodes_to_explore_.push_back(entry_row);
1368 }
1369 marked_[entry_row] = true;
1370 }
1371
1372 // The graph contains cycles? this is not supposed to happen.
1373 DCHECK_LE(nodes_to_explore_.size(), 2 * num_rows_.value() + rows_.size());
1374 }
1375
1376 // Clear stored_.
1377 for (const RowIndex row : *lower_column_rows) {
1378 stored_.ClearBucket(row);
1379 }
1380 for (const RowIndex row : *upper_column_rows) {
1381 stored_.ClearBucket(row);
1382 }
1383}
1384
1386 RowIndexVector* non_zero_rows) const {
1387 if (non_zero_rows->empty()) return;
1388
1389 // We don't start the DFS if the initial number of non-zeros is under the
1390 // sparsity_threshold. During the DFS, we abort it if the number of floating
1391 // points operations get larger than the num_ops_threshold.
1392 //
1393 // In both cases, we make sure to clear non_zero_rows so that the solving part
1394 // will use the non-hypersparse version of the code.
1395 //
1396 // TODO(user): Investigate the best thresholds.
1397 const int sparsity_threshold =
1398 static_cast<int>(0.025 * static_cast<double>(num_rows_.value()));
1399 const int num_ops_threshold =
1400 static_cast<int>(0.05 * static_cast<double>(num_rows_.value()));
1401 int num_ops = non_zero_rows->size();
1402 if (num_ops > sparsity_threshold) {
1403 non_zero_rows->clear();
1404 return;
1405 }
1406
1407 // Initialize using the non-zero positions of the input.
1408 stored_.Resize(num_rows_);
1409 nodes_to_explore_.clear();
1410 nodes_to_explore_.swap(*non_zero_rows);
1411
1412 // Topological sort based on Depth-First-Search.
1413 // Same remarks as the version implemented in PermutedComputeRowsToConsider().
1414 const auto entry_rows = rows_.view();
1415 while (!nodes_to_explore_.empty()) {
1416 const RowIndex row = nodes_to_explore_.back();
1417
1418 // If the depth-first search from the current node is finished, we store the
1419 // node. This will store the node in reverse topological order.
1420 if (row < 0) {
1421 nodes_to_explore_.pop_back();
1422 const RowIndex explored_row = -row - 1;
1423 stored_.Set(explored_row);
1424 non_zero_rows->push_back(explored_row);
1425 continue;
1426 }
1427
1428 // If the node is already stored, skip.
1429 if (stored_[row]) {
1430 nodes_to_explore_.pop_back();
1431 continue;
1432 }
1433
1434 // Go one level forward in the depth-first search, and store the 'adjacent'
1435 // node on nodes_to_explore_ for further processing.
1436 //
1437 // We reverse the sign of nodes_to_explore_.back() to detect when the
1438 // DFS will be back on this node.
1439 nodes_to_explore_.back() = -row - 1;
1440 for (const EntryIndex i : Column(RowToColIndex(row))) {
1441 ++num_ops;
1442 const RowIndex entry_row = entry_rows[i];
1443 if (!stored_[entry_row]) {
1444 nodes_to_explore_.push_back(entry_row);
1445 }
1446 }
1447
1448 // Abort if the number of operations is not negligible compared to the
1449 // number of rows. Note that this test also prevents the code from cycling
1450 // in case the matrix is actually not triangular.
1451 if (num_ops > num_ops_threshold) break;
1452 }
1453
1454 // Clear stored_.
1455 for (const RowIndex row : *non_zero_rows) {
1456 stored_.ClearBucket(row);
1457 }
1458
1459 // If we aborted, clear the result.
1460 if (num_ops > num_ops_threshold) non_zero_rows->clear();
1461}
1462
1463void TriangularMatrix::ComputeRowsToConsiderInSortedOrder(
1464 RowIndexVector* non_zero_rows) const {
1465 if (non_zero_rows->empty()) return;
1466
1467 // TODO(user): Investigate the best thresholds.
1468 const int sparsity_threshold =
1469 static_cast<int>(0.025 * static_cast<double>(num_rows_.value()));
1470 const int num_ops_threshold =
1471 static_cast<int>(0.05 * static_cast<double>(num_rows_.value()));
1472 int num_ops = non_zero_rows->size();
1473 if (num_ops > sparsity_threshold) {
1474 non_zero_rows->clear();
1475 return;
1476 }
1477
1478 stored_.Resize(num_rows_);
1479 Bitset64<RowIndex>::View stored = stored_.view();
1480 for (const RowIndex row : *non_zero_rows) {
1481 stored.Set(row);
1482 }
1483
1484 const auto matrix_view = view();
1485 const auto entry_rows = rows_.view();
1486 for (int i = 0; i < non_zero_rows->size(); ++i) {
1487 const RowIndex row = (*non_zero_rows)[i];
1488 for (const EntryIndex index : matrix_view.Column(RowToColIndex(row))) {
1489 ++num_ops;
1490 const RowIndex entry_row = entry_rows[index];
1491 if (!stored[entry_row]) {
1492 non_zero_rows->push_back(entry_row);
1493 stored.Set(entry_row);
1494 }
1495 }
1496 if (num_ops > num_ops_threshold) break;
1497 }
1498
1499 if (num_ops > num_ops_threshold) {
1500 stored_.ClearAll();
1501 non_zero_rows->clear();
1502 } else {
1503 std::sort(non_zero_rows->begin(), non_zero_rows->end());
1504 for (const RowIndex row : *non_zero_rows) {
1505 stored_.ClearBucket(row);
1506 }
1507 }
1508}
1509
1510// A known upper bound for the infinity norm of T^{-1} is the
1511// infinity norm of y where T'*y = x with:
1512// - x the all 1s vector.
1513// - Each entry in T' is the absolute value of the same entry in T.
1514Fractional TriangularMatrix::ComputeInverseInfinityNormUpperBound() const {
1515 if (first_non_identity_column_ == num_cols_) {
1516 // Identity matrix
1517 return 1.0;
1518 }
1519
1520 const bool is_upper = IsUpperTriangular();
1521 DenseColumn row_norm_estimate(num_rows_, 1.0);
1522 const int num_cols = num_cols_.value();
1523
1524 const auto entry_rows = rows_.view();
1525 const auto entry_coefficients = coefficients_.view();
1526 for (int i = 0; i < num_cols; ++i) {
1527 const ColIndex col(is_upper ? num_cols - 1 - i : i);
1528 DCHECK_NE(diagonal_coefficients_[col], 0.0);
1529 const Fractional coeff = row_norm_estimate[ColToRowIndex(col)] /
1530 std::abs(diagonal_coefficients_[col]);
1531
1532 row_norm_estimate[ColToRowIndex(col)] = coeff;
1533 for (const EntryIndex i : Column(col)) {
1534 row_norm_estimate[entry_rows[i]] +=
1535 coeff * std::abs(entry_coefficients[i]);
1536 }
1537 }
1538
1539 return *std::max_element(row_norm_estimate.begin(), row_norm_estimate.end());
1540}
1541
1542Fractional TriangularMatrix::ComputeInverseInfinityNorm() const {
1543 const bool is_upper = IsUpperTriangular();
1545 DenseColumn row_sum(num_rows_, 0.0);
1546 DenseColumn right_hand_side;
1547 for (ColIndex col(0); col < num_cols_; ++col) {
1548 right_hand_side.assign(num_rows_, 0);
1549 right_hand_side[ColToRowIndex(col)] = 1.0;
1550
1551 // Get the col-th column of the matrix inverse.
1552 if (is_upper) {
1553 UpperSolve(&right_hand_side);
1554 } else {
1555 LowerSolve(&right_hand_side);
1556 }
1557
1558 // Compute sum_j |inverse_ij|.
1559 for (RowIndex row(0); row < num_rows_; ++row) {
1560 row_sum[row] += std::abs(right_hand_side[row]);
1561 }
1562 }
1563 // Compute max_i sum_j |inverse_ij|.
1564 Fractional norm = 0.0;
1565 for (RowIndex row(0); row < num_rows_; ++row) {
1566 norm = std::max(norm, row_sum[row]);
1567 }
1568
1569 return norm;
1570}
1571} // namespace glop
1572} // namespace operations_research
void PopulateFromMatrixView(const MatrixView &input)
Definition sparse.cc:447
ColIndex AddDenseColumnPrefix(DenseColumn::ConstView dense_column, RowIndex start)
Same as AddDenseColumn(), but only adds the non-zero from the given start.
Definition sparse.cc:597
StrictITIVector< EntryIndex, Fractional > coefficients_
Definition sparse.h:527
ColIndex AddDenseColumn(const DenseColumn &dense_column)
Definition sparse.cc:593
StrictITIVector< ColIndex, EntryIndex > starts_
Definition sparse.h:529
EntryIndex num_entries() const
Returns the matrix dimensions. See same functions in SparseMatrix.
Definition sparse.h:405
void AddEntryToCurrentColumn(RowIndex row, Fractional coeff)
Api to add columns one at the time.
Definition sparse.cc:582
EntryIndex ColumnNumEntries(ColIndex col) const
Returns the number of entries (i.e. degree) of the given column.
Definition sparse.h:400
StrictITIVector< EntryIndex, RowIndex > rows_
Definition sparse.h:528
void Swap(CompactSparseMatrix *other)
Definition sparse.cc:642
RowIndex num_rows_
The matrix dimensions, properly updated by full and incremental builders.
Definition sparse.h:521
::util::IntegerRange< EntryIndex > Column(ColIndex col) const
Functions to iterate on the entries of a given column.
Definition sparse.h:516
ColumnView column(ColIndex col) const
Definition sparse.h:420
Fractional ComputeInfinityNorm() const
Definition sparse.cc:433
void PopulateSparseColumn(SparseColumn *sparse_column) const
void AddToCoefficient(RowIndex row, Fractional value)
void PopulateFromProduct(const SparseMatrix &a, const SparseMatrix &b)
Multiplies SparseMatrix a by SparseMatrix b.
Definition sparse.cc:260
std::string Dump() const
Returns a dense representation of the matrix.
Definition sparse.cc:409
void PopulateFromPermutedMatrix(const Matrix &a, const RowPermutation &row_perm, const ColumnPermutation &inverse_col_perm)
Definition sparse.cc:222
void PopulateFromTranspose(const Matrix &input)
Instantiate needed templates.
Definition sparse.cc:191
bool CheckNoDuplicates() const
Call CheckNoDuplicates() on all columns, useful for doing a DCHECK.
Definition sparse.cc:136
void PopulateFromLinearCombination(Fractional alpha, const SparseMatrix &a, Fractional beta, const SparseMatrix &b)
Definition sparse.cc:235
void AppendUnitVector(RowIndex row, Fractional value)
Definition sparse.cc:161
void Swap(SparseMatrix *matrix)
Definition sparse.cc:168
bool IsCleanedUp() const
Call IsCleanedUp() on all columns, useful for doing a DCHECK.
Definition sparse.cc:145
RowIndex num_rows() const
Return the matrix dimension.
Definition sparse.h:190
Fractional ComputeInfinityNorm() const
Definition sparse.cc:405
void PopulateFromZero(RowIndex num_rows, ColIndex num_cols)
Definition sparse.cc:174
bool Equals(const SparseMatrix &a, Fractional tolerance) const
Definition sparse.cc:337
ColIndex AppendEmptyColumn()
Appends an empty column and returns its index.
Definition sparse.cc:155
Fractional LookUpValue(RowIndex row, ColIndex col) const
Definition sparse.cc:333
EntryIndex num_entries() const
Note this method can only be used when the vector has no duplicates.
void Clear()
Clears the vector, i.e. removes all entries.
void AppendEntriesWithOffset(const SparseVector &sparse_vector, Index offset)
void SetCoefficient(Index index, Fractional value)
Defines the coefficient at index, i.e. vector[index] = value;.
void Reserve(EntryIndex new_capacity)
Reserve the underlying storage for the given number of entries.
void assign(IntType size, const T &v)
Definition lp_types.h:317
StrictITISpan< RowIndex, const Fractional > ConstView
Definition lp_types.h:291
void UpperSolve(DenseColumn *rhs) const
Solves the system U.x = rhs for an upper triangular matrix.
Definition sparse.cc:833
void Reset(RowIndex num_rows, ColIndex col_capacity)
Definition sparse.cc:570
void LowerSolveStartingAt(ColIndex start, DenseColumn *rhs) const
This assumes that the rhs is all zero before the given position.
Definition sparse.cc:801
void AddTriangularColumnWithGivenDiagonalEntry(const SparseColumn &column, RowIndex diagonal_row, Fractional diagonal_value)
Definition sparse.cc:730
void AddTriangularColumn(const ColumnView &column, RowIndex diagonal_row)
Definition sparse.cc:698
void PermutedComputeRowsToConsider(const ColumnView &rhs, const RowPermutation &row_perm, RowIndexVector *lower_column_rows, RowIndexVector *upper_column_rows)
Definition sparse.cc:1273
void PopulateFromTriangularSparseMatrix(const SparseMatrix &input)
Definition sparse.cc:741
void CopyColumnToSparseColumn(ColIndex col, SparseColumn *output) const
Copy a triangular column with its diagonal entry to the given SparseColumn.
Definition sparse.cc:778
void PermutedLowerSparseSolve(const ColumnView &rhs, const RowPermutation &row_perm, SparseColumn *lower, SparseColumn *upper)
Definition sparse.cc:1193
void ComputeRowsToConsiderWithDfs(RowIndexVector *non_zero_rows) const
Definition sparse.cc:1387
void Swap(TriangularMatrix *other)
Definition sparse.cc:650
constexpr double kInfinity
Infinity for type Fractional.
Definition lp_types.h:87
std::vector< RowIndex > RowIndexVector
Definition lp_types.h:361
BeginEndReverseIteratorWrapper< Container > Reverse(const Container &c)
Definition iterators.h:99
Permutation< ColIndex > ColumnPermutation
Permutation< RowIndex > RowPermutation
ColIndex RowToColIndex(RowIndex row)
Get the ColIndex corresponding to the column # row.
Definition lp_types.h:54
constexpr RowIndex kInvalidRow(-1)
StrictITIVector< RowIndex, RowIndex > RowMapping
Column of row indices. Used to represent mappings between rows.
Definition lp_types.h:389
RowIndex ColToRowIndex(ColIndex col)
Get the RowIndex corresponding to the row # col.
Definition lp_types.h:57
StrictITIVector< RowIndex, Fractional > DenseColumn
Column-vector types. Column-vector types are indexed by a row index.
Definition lp_types.h:380
static double ToDouble(double f)
Definition lp_types.h:74
In SWIG mode, we don't want anything besides these top-level includes.
util_intops::StrongVector< ColumnEntryIndex, ElementIndex > SparseColumn
BeginEndReverseIteratorWrapper< Container > Reverse(const Container &c)
Definition iterators.h:99
static int input(yyscan_t yyscanner)
#define RETURN_IF_NULL(x)