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