23#include "absl/strings/str_format.h"
37 const RowIndex num_rows = basis_matrix.
num_rows();
38 const ColIndex num_cols = basis_matrix.
num_cols();
44 basis_matrix_ = &basis_matrix;
47 lower_.Reset(num_rows, num_cols);
48 upper_.Reset(num_rows, num_cols);
49 permuted_lower_.Reset(num_cols);
50 permuted_upper_.Reset(num_cols);
51 permuted_lower_column_needs_solve_.assign(num_cols,
false);
52 contains_only_singleton_columns_ =
true;
58 ExtractSingletonColumns(basis_matrix, row_perm, col_perm, &index);
59 ExtractResidualSingletonColumns(basis_matrix, row_perm, col_perm, &index);
60 int stats_num_pivots_without_fill_in = index;
61 int stats_degree_two_pivot_columns = 0;
65 residual_matrix_non_zero_.InitializeFromMatrixSubset(
67 &singleton_column_, &singleton_row_);
70 const int end_index = std::min(num_rows.value(), num_cols.value());
72 parameters_.markowitz_singularity_threshold();
73 while (index < end_index) {
82 const int64_t min_markowitz = FindPivot(*row_perm, *col_perm, &pivot_row,
83 &pivot_col, &pivot_coefficient);
90 std::abs(pivot_coefficient) <= singularity_threshold) {
91 const std::string error_message = absl::StrFormat(
92 "The matrix is singular! pivot = %E", pivot_coefficient);
93 VLOG(1) <<
"ERROR_LU: " << error_message;
103 const int pivot_col_degree = residual_matrix_non_zero_.ColDegree(pivot_col);
104 const int pivot_row_degree = residual_matrix_non_zero_.RowDegree(pivot_row);
105 residual_matrix_non_zero_.DeleteRowAndColumn(pivot_row, pivot_col);
106 if (min_markowitz == 0) {
107 ++stats_num_pivots_without_fill_in;
108 if (pivot_col_degree == 1) {
109 RemoveRowFromResidualMatrix(pivot_row, pivot_col);
111 DCHECK_EQ(pivot_row_degree, 1);
112 RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
119 if (pivot_col_degree == 2) { ++stats_degree_two_pivot_columns; });
120 UpdateResidualMatrix(pivot_row, pivot_col);
123 if (contains_only_singleton_columns_) {
124 DCHECK(permuted_upper_.column(pivot_col).IsEmpty());
125 lower_.AddDiagonalOnlyColumn(1.0);
126 upper_.AddTriangularColumn(basis_matrix.
column(pivot_col), pivot_row);
128 lower_.AddAndNormalizeTriangularColumn(permuted_lower_.column(pivot_col),
129 pivot_row, pivot_coefficient);
130 permuted_lower_.ClearAndReleaseColumn(pivot_col);
132 upper_.AddTriangularColumnWithGivenDiagonalEntry(
133 permuted_upper_.column(pivot_col), pivot_row, pivot_coefficient);
134 permuted_upper_.ClearAndReleaseColumn(pivot_col);
138 (*col_perm)[pivot_col] = ColIndex(index);
139 (*row_perm)[pivot_row] = RowIndex(index);
145 num_fp_operations_ += 10 * lower_.num_entries().value();
146 num_fp_operations_ += 10 * upper_.num_entries().value();
148 stats_.pivots_without_fill_in_ratio.Add(
149 1.0 * stats_num_pivots_without_fill_in / num_rows.value());
150 stats_.degree_two_pivot_columns.Add(1.0 * stats_degree_two_pivot_columns /
166 lower_.ApplyRowPermutationToNonDiagonalEntries(*row_perm);
167 upper_.ApplyRowPermutationToNonDiagonalEntries(*row_perm);
177 permuted_lower_.Clear();
178 permuted_upper_.Clear();
179 residual_matrix_non_zero_.Clear();
180 examined_col_.clear();
181 num_fp_operations_ = 0;
182 is_col_by_degree_initialized_ =
false;
185void Markowitz::ExtractSingletonColumns(
189 tmp_singleton_entries_.clear();
190 const ColIndex num_cols = basis_matrix.
num_cols();
191 for (ColIndex col(0); col < num_cols; ++col) {
199 (*row_perm)[row] = 0;
201 tmp_singleton_entries_.push_back(
208 std::sort(tmp_singleton_entries_.begin(), tmp_singleton_entries_.end());
209 for (
const MatrixEntry e : tmp_singleton_entries_) {
210 (*col_perm)[e.col] = ColIndex(*index);
211 (*row_perm)[e.row] = RowIndex(*index);
216 stats_.basis_singleton_column_ratio.Add(
static_cast<double>(*index) /
220bool Markowitz::IsResidualSingletonColumn(
223 int residual_degree = 0;
224 for (
const auto e : column) {
227 if (residual_degree > 1)
return false;
230 return residual_degree == 1;
233void Markowitz::ExtractResidualSingletonColumns(
237 const ColIndex num_cols = basis_matrix.num_cols();
239 for (ColIndex col(0); col < num_cols; ++col) {
241 const ColumnView column = basis_matrix.column(col);
242 if (!IsResidualSingletonColumn(column, row_perm->const_view(), &row)) {
245 (*col_perm)[col] = ColIndex(*index);
246 (*row_perm)[row] = RowIndex(*index);
247 lower_.AddDiagonalOnlyColumn(1.0);
248 upper_.AddTriangularColumn(column, row);
251 stats_.basis_residual_singleton_column_ratio.Add(
252 static_cast<double>(*index) / basis_matrix.num_rows().value());
261 const bool first_time = permuted_lower_.column(col).IsEmpty() &&
262 permuted_upper_.column(col).IsEmpty();
269 SparseColumn* lower_column = permuted_lower_.mutable_column(col);
270 if (permuted_lower_column_needs_solve_[col]) {
274 const ColumnView&
input =
275 first_time ? basis_matrix_->column(col) : ColumnView(*lower_column);
276 lower_.PermutedLowerSparseSolve(
input, row_perm, lower_column,
277 permuted_upper_.mutable_column(col));
278 permuted_lower_column_needs_solve_[col] =
false;
279 num_fp_operations_ +=
280 lower_.NumFpOperationsInLastPermutedLowerSparseSolve();
281 return *lower_column;
287 if (lower_column->num_entries() == residual_matrix_non_zero_.ColDegree(col)) {
288 return *lower_column;
295 const EntryIndex num_entries = basis_matrix_->column(col).num_entries();
296 num_fp_operations_ += num_entries.value();
297 lower_column->Reserve(num_entries);
298 for (
const auto e : basis_matrix_->column(col)) {
299 lower_column->SetCoefficient(e.row(), e.coefficient());
302 num_fp_operations_ += lower_column->num_entries().value();
303 lower_column->MoveTaggedEntriesTo(row_perm,
304 permuted_upper_.mutable_column(col));
305 return *lower_column;
310 RowIndex* pivot_row, ColIndex* pivot_col,
315 while (!singleton_column_.empty()) {
316 const ColIndex col = singleton_column_.back();
317 singleton_column_.pop_back();
326 if (residual_matrix_non_zero_.ColDegree(col) != 1)
continue;
331 if (contains_only_singleton_columns_) {
333 for (
const SparseColumn::Entry e : basis_matrix_->column(col)) {
335 *pivot_row = e.row();
336 *pivot_coefficient = e.coefficient();
342 const SparseColumn& column = ComputeColumn(row_perm, col);
343 if (column.IsEmpty())
continue;
345 *pivot_row = column.GetFirstRow();
346 *pivot_coefficient = column.GetFirstCoefficient();
349 contains_only_singleton_columns_ =
false;
354 while (!singleton_row_.empty()) {
355 const RowIndex row = singleton_row_.back();
356 singleton_row_.pop_back();
364 if (residual_matrix_non_zero_.RowDegree(row) != 1)
continue;
366 residual_matrix_non_zero_.GetFirstNonDeletedColumnFromRow(row);
368 const SparseColumn& column = ComputeColumn(row_perm, col);
369 if (column.IsEmpty())
continue;
373 *pivot_coefficient = column.LookUpCoefficient(row);
379 if (!is_col_by_degree_initialized_) {
380 is_col_by_degree_initialized_ =
true;
381 const ColIndex num_cols = col_perm.size();
382 col_by_degree_.Reset(row_perm.size().value(), num_cols);
383 for (ColIndex col(0); col < num_cols; ++col) {
385 const int degree = residual_matrix_non_zero_.ColDegree(col);
386 DCHECK_NE(degree, 1);
387 UpdateDegree(col, degree);
393 int64_t min_markowitz_number = std::numeric_limits<int64_t>::max();
394 examined_col_.clear();
395 const int num_columns_to_examine = parameters_.markowitz_zlatev_parameter();
396 const Fractional threshold = parameters_.lu_factorization_pivot_threshold();
397 while (examined_col_.size() < num_columns_to_examine) {
398 const ColIndex col = col_by_degree_.Pop();
401 const int col_degree = residual_matrix_non_zero_.ColDegree(col);
402 examined_col_.push_back(col);
413 const int64_t markowitz_lower_bound = col_degree - 1;
414 if (min_markowitz_number < markowitz_lower_bound)
break;
420 const SparseColumn& column = ComputeColumn(row_perm, col);
421 DCHECK_EQ(column.num_entries(), col_degree);
424 for (
const SparseColumn::Entry e : column) {
425 max_magnitude = std::max(max_magnitude, std::abs(e.coefficient()));
427 if (max_magnitude == 0.0) {
430 examined_col_.pop_back();
434 const Fractional skip_threshold = threshold * max_magnitude;
435 for (
const SparseColumn::Entry e : column) {
436 const Fractional magnitude = std::abs(e.coefficient());
437 if (magnitude < skip_threshold)
continue;
439 const int row_degree = residual_matrix_non_zero_.RowDegree(e.row());
440 const int64_t markowitz_number = (col_degree - 1) * (row_degree - 1);
441 DCHECK_NE(markowitz_number, 0);
442 if (markowitz_number < min_markowitz_number ||
443 ((markowitz_number == min_markowitz_number) &&
444 magnitude > std::abs(*pivot_coefficient))) {
445 min_markowitz_number = markowitz_number;
447 *pivot_row = e.row();
448 *pivot_coefficient = e.coefficient();
456 DCHECK_NE(min_markowitz_number, 0);
457 DCHECK_GE(min_markowitz_number, markowitz_lower_bound);
471 for (
const ColIndex col : examined_col_) {
472 if (col != *pivot_col) {
473 const int degree = residual_matrix_non_zero_.ColDegree(col);
474 col_by_degree_.PushOrAdjust(col, degree);
477 return min_markowitz_number;
480void Markowitz::UpdateDegree(ColIndex col,
int degree) {
481 DCHECK(is_col_by_degree_initialized_);
493 singleton_column_.push_back(col);
495 col_by_degree_.PushOrAdjust(col, degree);
499void Markowitz::RemoveRowFromResidualMatrix(RowIndex pivot_row,
500 ColIndex pivot_col) {
506 if (is_col_by_degree_initialized_) {
507 for (
const ColIndex col : residual_matrix_non_zero_.RowNonZero(pivot_row)) {
508 if (residual_matrix_non_zero_.IsColumnDeleted(col))
continue;
509 UpdateDegree(col, residual_matrix_non_zero_.DecreaseColDegree(col));
512 for (
const ColIndex col : residual_matrix_non_zero_.RowNonZero(pivot_row)) {
513 if (residual_matrix_non_zero_.IsColumnDeleted(col))
continue;
514 if (residual_matrix_non_zero_.DecreaseColDegree(col) == 1) {
515 singleton_column_.push_back(col);
521void Markowitz::RemoveColumnFromResidualMatrix(RowIndex pivot_row,
522 ColIndex pivot_col) {
531 for (
const SparseColumn::Entry e : permuted_lower_.column(pivot_col)) {
532 const RowIndex row = e.row();
533 if (residual_matrix_non_zero_.DecreaseRowDegree(row) == 1) {
534 singleton_row_.push_back(row);
539void Markowitz::UpdateResidualMatrix(RowIndex pivot_row, ColIndex pivot_col) {
541 const SparseColumn& pivot_column = permuted_lower_.column(pivot_col);
542 residual_matrix_non_zero_.Update(pivot_row, pivot_col, pivot_column);
543 for (
const ColIndex col : residual_matrix_non_zero_.RowNonZero(pivot_row)) {
544 DCHECK_NE(col, pivot_col);
545 UpdateDegree(col, residual_matrix_non_zero_.ColDegree(col));
546 permuted_lower_column_needs_solve_[col] =
true;
548 RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
558 row_non_zero_.clear();
559 deleted_columns_.clear();
560 bool_scratchpad_.clear();
561 num_non_deleted_columns_ = 0;
565 row_degree_.AssignToZero(num_rows);
566 col_degree_.AssignToZero(num_cols);
567 row_non_zero_.clear();
568 row_non_zero_.resize(num_rows.value());
569 deleted_columns_.assign(num_cols,
false);
570 bool_scratchpad_.assign(num_cols,
false);
571 num_non_deleted_columns_ = num_cols;
578 std::vector<ColIndex>* singleton_columns,
579 std::vector<RowIndex>* singleton_rows) {
580 const ColIndex num_cols = basis_matrix.
num_cols();
581 const RowIndex num_rows = basis_matrix.
num_rows();
584 Reset(num_rows, num_cols);
585 singleton_columns->clear();
586 singleton_rows->clear();
589 for (ColIndex col(0); col < num_cols; ++col) {
591 deleted_columns_[col] =
true;
592 --num_non_deleted_columns_;
595 for (
const SparseColumn::Entry e : basis_matrix.
column(col)) {
596 ++row_degree_[e.row()];
601 for (RowIndex row(0); row < num_rows; ++row) {
603 row_non_zero_[row].reserve(row_degree_[row]);
604 if (row_degree_[row] == 1) singleton_rows->push_back(row);
608 row_degree_[row] = 0;
613 for (ColIndex col(0); col < num_cols; ++col) {
615 int32_t col_degree = 0;
616 for (
const SparseColumn::Entry e : basis_matrix.
column(col)) {
617 const RowIndex row = e.row();
620 row_non_zero_[row].push_back(col);
623 col_degree_[col] = col_degree;
624 if (col_degree == 1) singleton_columns->push_back(col);
631 row_non_zero_[row].push_back(col);
635 return --col_degree_[col];
639 return --row_degree_[row];
643 ColIndex pivot_col) {
644 DCHECK(!deleted_columns_[pivot_col]);
645 deleted_columns_[pivot_col] =
true;
646 --num_non_deleted_columns_;
649 row_degree_[pivot_row] = 0;
653 return deleted_columns_[col];
657 auto& ref = row_non_zero_[row];
659 const int end = ref.size();
660 for (
int i = 0; i < end; ++i) {
661 const ColIndex col = ref[i];
662 if (!deleted_columns_[col]) {
663 ref[new_index] = col;
667 ref.resize(new_index);
671 RowIndex row)
const {
683 DCHECK(deleted_columns_[pivot_col]);
684 const int max_row_degree = num_non_deleted_columns_.value() + 1;
687 for (
const ColIndex col : row_non_zero_[pivot_row]) {
689 bool_scratchpad_[col] =
false;
696 for (
const SparseColumn::Entry e : column) {
697 const RowIndex row = e.row();
698 if (row == pivot_row)
continue;
703 if (e.coefficient() == 0.0 || row_degree_[row] == max_row_degree)
continue;
704 DCHECK_LT(row_degree_[row], max_row_degree);
712 const int kDeletionThreshold = 4;
713 if (row_non_zero_[row].size() > row_degree_[row] + kDeletionThreshold) {
718 MergeInto(pivot_row, row);
726 MergeIntoSorted(pivot_row, row);
731void MatrixNonZeroPattern::MergeInto(RowIndex pivot_row, RowIndex row) {
734 for (
const ColIndex col : row_non_zero_[row]) {
735 bool_scratchpad_[col] =
true;
738 auto& non_zero = row_non_zero_[row];
739 const int old_size = non_zero.size();
740 for (
const ColIndex col : row_non_zero_[pivot_row]) {
741 if (bool_scratchpad_[col]) {
742 bool_scratchpad_[col] =
false;
744 non_zero.push_back(col);
748 row_degree_[row] += non_zero.size() - old_size;
756template <
typename V,
typename W>
757void MergeSortedVectors(
const V& input_a, W* out) {
758 if (input_a.empty())
return;
759 const auto& input_b = *out;
760 int index_a = input_a.size() - 1;
761 int index_b = input_b.size() - 1;
762 int index_out = input_a.size() + input_b.size();
764 while (index_a >= 0) {
766 while (index_a >= 0) {
768 (*out)[index_out] = input_a[index_a];
774 if (input_a[index_a] > input_b[index_b]) {
775 (*out)[index_out] = input_a[index_a];
778 (*out)[index_out] = input_b[index_b];
789void MatrixNonZeroPattern::MergeIntoSorted(RowIndex pivot_row, RowIndex row) {
791 const auto&
input = row_non_zero_[pivot_row];
792 const auto& output = row_non_zero_[row];
795 col_scratchpad_.resize(
input.size());
796 col_scratchpad_.resize(std::set_difference(
input.begin(),
input.end(),
797 output.begin(), output.end(),
798 col_scratchpad_.begin()) -
799 col_scratchpad_.begin());
802 for (
const ColIndex col : col_scratchpad_) {
805 row_degree_[row] += col_scratchpad_.size();
806 MergeSortedVectors(col_scratchpad_, &row_non_zero_[row]);
810 degree_.assign(num_cols, 0);
811 col_by_degree_.assign(max_degree + 1,
kInvalidCol);
812 min_degree_ = max_degree + 1;
819void ColumnPriorityQueue::Remove(ColIndex col, int32_t old_degree) {
820 DCHECK_NE(old_degree, 0);
822 const ColIndex old_next = next_[col];
823 const ColIndex old_prev = prev_[col];
826 if (old_next != -1) prev_[old_next] = old_prev;
827 if (old_prev == -1) {
828 DCHECK_EQ(col_by_degree_[old_degree], col);
829 col_by_degree_[old_degree] = old_next;
831 next_[old_prev] = old_next;
838void ColumnPriorityQueue::Insert(ColIndex col, int32_t degree) {
839 DCHECK_EQ(degree_[col], 0);
840 DCHECK_NE(degree, 0);
842 const ColIndex new_next = col_by_degree_[degree];
843 next_[col] = new_next;
844 if (new_next != -1) {
845 prev_[new_next] = col;
848 col_by_degree_[degree] = col;
850 degree_[col] = degree;
852 min_degree_ = std::min(min_degree_, degree);
856 DCHECK_GE(degree, 0);
857 DCHECK_LT(degree, col_by_degree_.size());
859 DCHECK_LT(col, degree_.size());
861 const int32_t old_degree = degree_[col];
862 if (degree != old_degree) {
863 if (old_degree != 0) {
864 Remove(col, old_degree);
873 DCHECK_GE(min_degree_, 0);
874 DCHECK_LE(min_degree_, col_by_degree_.size());
876 const int limit = col_by_degree_.size();
879 result = col_by_degree_[min_degree_];
883 Remove(result, min_degree_);
888 mapping_.assign(num_cols.value(), -1);
889 free_columns_.clear();
894 ColIndex col)
const {
895 if (mapping_[col] == -1)
return empty_column_;
896 return columns_[mapping_[col]];
901 if (mapping_[col] != -1)
return &columns_[mapping_[col]];
903 if (free_columns_.empty()) {
904 new_col_index = columns_.size();
907 new_col_index = free_columns_.back();
908 free_columns_.pop_back();
910 mapping_[col] = new_col_index;
911 return &columns_[new_col_index];
915 DCHECK_NE(mapping_[col], -1);
916 free_columns_.push_back(mapping_[col]);
917 columns_[mapping_[col]].Clear();
923 free_columns_.clear();
void PushOrAdjust(ColIndex col, int32_t degree)
void Reset(int32_t max_degree, ColIndex num_cols)
EntryIndex num_entries() const
RowIndex GetFirstRow() const
Fractional GetFirstCoefficient() const
ColumnView column(ColIndex col) const
RowIndex num_rows() const
ColIndex num_cols() const
bool IsEmpty() const
Same behavior as the SparseMatrix functions above.
void Clear()
Releases the memory used by this class.
ABSL_MUST_USE_RESULT Status ComputeRowAndColumnPermutation(const CompactSparseMatrixView &basis_matrix, RowPermutation *row_perm, ColumnPermutation *col_perm)
double DeterministicTimeOfLastFactorization() const
Returns an estimate of the time spent in the last factorization.
ABSL_MUST_USE_RESULT Status ComputeLU(const CompactSparseMatrixView &basis_matrix, RowPermutation *row_perm, ColumnPermutation *col_perm, TriangularMatrix *lower, TriangularMatrix *upper)
int32_t DecreaseColDegree(ColIndex col)
void AddEntry(RowIndex row, ColIndex col)
Adds a non-zero entry to the matrix. There should be no duplicates.
bool IsColumnDeleted(ColIndex col) const
Returns true if the column has been deleted by DeleteRowAndColumn().
void InitializeFromMatrixSubset(const CompactSparseMatrixView &basis_matrix, StrictITISpan< RowIndex, const RowIndex > row_perm, StrictITISpan< ColIndex, const ColIndex > col_perm, std::vector< ColIndex > *singleton_columns, std::vector< RowIndex > *singleton_rows)
ColIndex GetFirstNonDeletedColumnFromRow(RowIndex row) const
const absl::InlinedVector< ColIndex, 6 > & RowNonZero(RowIndex row) const
int32_t DecreaseRowDegree(RowIndex row)
void Reset(RowIndex num_rows, ColIndex num_cols)
Resets the pattern to the one of an empty square matrix of the given size.
void DeleteRowAndColumn(RowIndex pivot_row, ColIndex pivot_col)
void RemoveDeletedColumnsFromRow(RowIndex row)
void Update(RowIndex pivot_row, ColIndex pivot_col, const SparseColumn &column)
void Clear()
Releases the memory used by this class.
StrictITISpan< IndexType, const IndexType > const_view() const
void assign(IndexType size, IndexType value)
void ClearAndReleaseColumn(ColIndex col)
const SparseColumn & column(ColIndex col) const
Returns the column with given index.
SparseColumn * mutable_column(ColIndex col)
void Reset(ColIndex num_cols)
Resets the repository to num_cols empty columns.
static Status OK()
Improves readability but identical to 0-arg constructor.
@ ERROR_LU
The LU factorization of the current basis couldn't be computed.
bool IsUpperTriangular() const
bool IsLowerTriangular() const
void AddDiagonalOnlyColumn(Fractional diagonal_value)
void resize(size_type new_size)
constexpr ColIndex kInvalidCol(-1)
Permutation< ColIndex > ColumnPermutation
Permutation< RowIndex > RowPermutation
constexpr RowIndex kInvalidRow(-1)
static double DeterministicTimeForFpOperations(int64_t n)
In SWIG mode, we don't want anything besides these top-level includes.
util_intops::StrongVector< ColumnEntryIndex, ElementIndex > SparseColumn
static int input(yyscan_t yyscanner)
#define IF_STATS_ENABLED(instructions)
#define SCOPED_TIME_STAT(stats)
#define GLOP_RETURN_IF_ERROR(function_call)
Macro to simplify error propagation between function returning Status.
Holds a triplet (row, col, coefficient).