23#include "absl/strings/str_format.h"
36 const RowIndex num_rows = basis_matrix.
num_rows();
37 const ColIndex num_cols = basis_matrix.
num_cols();
43 basis_matrix_ = &basis_matrix;
46 lower_.
Reset(num_rows, num_cols);
47 upper_.
Reset(num_rows, num_cols);
48 permuted_lower_.
Reset(num_cols);
49 permuted_upper_.
Reset(num_cols);
50 permuted_lower_column_needs_solve_.
assign(num_cols,
false);
51 contains_only_singleton_columns_ =
true;
57 ExtractSingletonColumns(basis_matrix, row_perm, col_perm, &
index);
58 ExtractResidualSingletonColumns(basis_matrix, row_perm, col_perm, &
index);
59 int stats_num_pivots_without_fill_in =
index;
60 int stats_degree_two_pivot_columns = 0;
65 basis_matrix, *row_perm, *col_perm, &singleton_column_, &singleton_row_);
68 const int end_index = std::min(num_rows.value(), num_cols.value());
70 parameters_.markowitz_singularity_threshold();
71 while (
index < end_index) {
80 const int64_t min_markowitz = FindPivot(*row_perm, *col_perm, &pivot_row,
81 &pivot_col, &pivot_coefficient);
88 std::abs(pivot_coefficient) <= singularity_threshold) {
89 const std::string error_message = absl::StrFormat(
90 "The matrix is singular! pivot = %E", pivot_coefficient);
91 VLOG(1) <<
"ERROR_LU: " << error_message;
101 const int pivot_col_degree = residual_matrix_non_zero_.
ColDegree(pivot_col);
102 const int pivot_row_degree = residual_matrix_non_zero_.
RowDegree(pivot_row);
104 if (min_markowitz == 0) {
105 ++stats_num_pivots_without_fill_in;
106 if (pivot_col_degree == 1) {
107 RemoveRowFromResidualMatrix(pivot_row, pivot_col);
109 DCHECK_EQ(pivot_row_degree, 1);
110 RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
117 if (pivot_col_degree == 2) { ++stats_degree_two_pivot_columns; });
118 UpdateResidualMatrix(pivot_row, pivot_col);
121 if (contains_only_singleton_columns_) {
127 pivot_row, pivot_coefficient);
131 permuted_upper_.
column(pivot_col), pivot_row, pivot_coefficient);
136 (*col_perm)[pivot_col] = ColIndex(
index);
137 (*row_perm)[pivot_row] = RowIndex(
index);
143 num_fp_operations_ += 10 * lower_.
num_entries().value();
144 num_fp_operations_ += 10 * upper_.
num_entries().value();
146 stats_.pivots_without_fill_in_ratio.
Add(
147 1.0 * stats_num_pivots_without_fill_in / num_rows.value());
148 stats_.degree_two_pivot_columns.
Add(1.0 * stats_degree_two_pivot_columns /
168 DCHECK(
lower->IsLowerTriangular());
169 DCHECK(
upper->IsUpperTriangular());
175 permuted_lower_.
Clear();
176 permuted_upper_.
Clear();
177 residual_matrix_non_zero_.
Clear();
178 col_by_degree_.
Clear();
179 examined_col_.clear();
180 num_fp_operations_ = 0;
181 is_col_by_degree_initialized_ =
false;
189 MatrixEntry(RowIndex r, ColIndex c,
Fractional coeff)
191 bool operator<(
const MatrixEntry& o)
const {
192 return (row == o.row) ?
col < o.col :
row < o.row;
198void Markowitz::ExtractSingletonColumns(
199 const CompactSparseMatrixView& basis_matrix,
RowPermutation* row_perm,
202 std::vector<MatrixEntry> singleton_entries;
203 const ColIndex num_cols = basis_matrix.num_cols();
204 for (ColIndex
col(0);
col < num_cols; ++
col) {
205 const ColumnView&
column = basis_matrix.column(
col);
206 if (
column.num_entries().value() == 1) {
207 singleton_entries.push_back(
214 std::sort(singleton_entries.begin(), singleton_entries.end());
215 for (
const MatrixEntry e : singleton_entries) {
217 (*col_perm)[e.col] = ColIndex(*
index);
218 (*row_perm)[e.row] = RowIndex(*
index);
224 stats_.basis_singleton_column_ratio.
Add(
static_cast<double>(*
index) /
225 basis_matrix.num_rows().value());
228bool Markowitz::IsResidualSingletonColumn(
const ColumnView&
column,
231 int residual_degree = 0;
232 for (
const auto e :
column) {
235 if (residual_degree > 1)
return false;
238 return residual_degree == 1;
241void Markowitz::ExtractResidualSingletonColumns(
242 const CompactSparseMatrixView& basis_matrix,
RowPermutation* row_perm,
245 const ColIndex num_cols = basis_matrix.num_cols();
247 for (ColIndex
col(0);
col < num_cols; ++
col) {
249 const ColumnView&
column = basis_matrix.column(
col);
250 if (!IsResidualSingletonColumn(
column, *row_perm, &
row))
continue;
251 (*col_perm)[
col] = ColIndex(*
index);
252 (*row_perm)[
row] = RowIndex(*
index);
257 stats_.basis_residual_singleton_column_ratio.
Add(
258 static_cast<double>(*
index) / basis_matrix.num_rows().value());
276 if (permuted_lower_column_needs_solve_[
col]) {
280 const ColumnView&
input =
281 first_time ? basis_matrix_->
column(
col) : ColumnView(*lower_column);
284 permuted_lower_column_needs_solve_[
col] =
false;
285 num_fp_operations_ +=
287 return *lower_column;
293 if (lower_column->num_entries() == residual_matrix_non_zero_.
ColDegree(
col)) {
294 return *lower_column;
302 num_fp_operations_ += num_entries.value();
303 lower_column->Reserve(num_entries);
304 for (
const auto e : basis_matrix_->
column(
col)) {
305 lower_column->SetCoefficient(e.row(), e.coefficient());
308 num_fp_operations_ += lower_column->num_entries().value();
309 lower_column->MoveTaggedEntriesTo(row_perm,
311 return *lower_column;
316 RowIndex* pivot_row, ColIndex* pivot_col,
321 while (!singleton_column_.empty()) {
322 const ColIndex
col = singleton_column_.back();
323 singleton_column_.pop_back();
332 if (residual_matrix_non_zero_.
ColDegree(
col) != 1)
continue;
337 if (contains_only_singleton_columns_) {
339 for (
const SparseColumn::Entry e : basis_matrix_->
column(
col)) {
341 *pivot_row = e.row();
342 *pivot_coefficient = e.coefficient();
349 if (
column.IsEmpty())
continue;
351 *pivot_row =
column.GetFirstRow();
352 *pivot_coefficient =
column.GetFirstCoefficient();
355 contains_only_singleton_columns_ =
false;
360 while (!singleton_row_.empty()) {
361 const RowIndex
row = singleton_row_.back();
362 singleton_row_.pop_back();
370 if (residual_matrix_non_zero_.
RowDegree(
row) != 1)
continue;
375 if (
column.IsEmpty())
continue;
379 *pivot_coefficient =
column.LookUpCoefficient(
row);
385 if (!is_col_by_degree_initialized_) {
386 is_col_by_degree_initialized_ =
true;
387 const ColIndex num_cols = col_perm.size();
388 col_by_degree_.
Reset(row_perm.size().value(), num_cols);
389 for (ColIndex
col(0);
col < num_cols; ++
col) {
391 const int degree = residual_matrix_non_zero_.
ColDegree(
col);
392 DCHECK_NE(degree, 1);
393 UpdateDegree(
col, degree);
399 int64_t min_markowitz_number = std::numeric_limits<int64_t>::max();
400 examined_col_.clear();
401 const int num_columns_to_examine = parameters_.markowitz_zlatev_parameter();
402 const Fractional threshold = parameters_.lu_factorization_pivot_threshold();
403 while (examined_col_.size() < num_columns_to_examine) {
404 const ColIndex
col = col_by_degree_.
Pop();
407 const int col_degree = residual_matrix_non_zero_.
ColDegree(
col);
408 examined_col_.push_back(
col);
419 const int64_t markowitz_lower_bound = col_degree - 1;
420 if (min_markowitz_number < markowitz_lower_bound)
break;
427 DCHECK_EQ(
column.num_entries(), col_degree);
430 for (
const SparseColumn::Entry e :
column) {
431 max_magnitude = std::max(max_magnitude, std::abs(e.coefficient()));
433 if (max_magnitude == 0.0) {
436 examined_col_.pop_back();
440 const Fractional skip_threshold = threshold * max_magnitude;
441 for (
const SparseColumn::Entry e :
column) {
442 const Fractional magnitude = std::abs(e.coefficient());
443 if (magnitude < skip_threshold)
continue;
445 const int row_degree = residual_matrix_non_zero_.
RowDegree(e.row());
446 const int64_t markowitz_number = (col_degree - 1) * (row_degree - 1);
447 DCHECK_NE(markowitz_number, 0);
448 if (markowitz_number < min_markowitz_number ||
449 ((markowitz_number == min_markowitz_number) &&
450 magnitude > std::abs(*pivot_coefficient))) {
451 min_markowitz_number = markowitz_number;
453 *pivot_row = e.row();
454 *pivot_coefficient = e.coefficient();
462 DCHECK_NE(min_markowitz_number, 0);
463 DCHECK_GE(min_markowitz_number, markowitz_lower_bound);
477 for (
const ColIndex
col : examined_col_) {
478 if (
col != *pivot_col) {
479 const int degree = residual_matrix_non_zero_.
ColDegree(
col);
483 return min_markowitz_number;
486void Markowitz::UpdateDegree(ColIndex
col,
int degree) {
487 DCHECK(is_col_by_degree_initialized_);
499 singleton_column_.push_back(
col);
505void Markowitz::RemoveRowFromResidualMatrix(RowIndex pivot_row,
506 ColIndex pivot_col) {
512 if (is_col_by_degree_initialized_) {
513 for (
const ColIndex
col : residual_matrix_non_zero_.
RowNonZero(pivot_row)) {
518 for (
const ColIndex
col : residual_matrix_non_zero_.
RowNonZero(pivot_row)) {
521 singleton_column_.push_back(
col);
527void Markowitz::RemoveColumnFromResidualMatrix(RowIndex pivot_row,
528 ColIndex pivot_col) {
537 for (
const SparseColumn::Entry e : permuted_lower_.
column(pivot_col)) {
538 const RowIndex
row = e.row();
540 singleton_row_.push_back(
row);
545void Markowitz::UpdateResidualMatrix(RowIndex pivot_row, ColIndex pivot_col) {
548 residual_matrix_non_zero_.
Update(pivot_row, pivot_col, pivot_column);
549 for (
const ColIndex
col : residual_matrix_non_zero_.
RowNonZero(pivot_row)) {
550 DCHECK_NE(
col, pivot_col);
552 permuted_lower_column_needs_solve_[
col] =
true;
554 RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
564 row_non_zero_.clear();
565 deleted_columns_.clear();
566 bool_scratchpad_.clear();
567 num_non_deleted_columns_ = 0;
573 row_non_zero_.clear();
574 row_non_zero_.
resize(num_rows.value());
575 deleted_columns_.
assign(num_cols,
false);
576 bool_scratchpad_.
assign(num_cols,
false);
577 num_non_deleted_columns_ = num_cols;
583 std::vector<RowIndex>* singleton_rows) {
584 const ColIndex num_cols = basis_matrix.
num_cols();
585 const RowIndex num_rows = basis_matrix.
num_rows();
588 Reset(num_rows, num_cols);
589 singleton_columns->clear();
590 singleton_rows->clear();
593 for (ColIndex
col(0);
col < num_cols; ++
col) {
595 deleted_columns_[
col] =
true;
596 --num_non_deleted_columns_;
599 for (
const SparseColumn::Entry e : basis_matrix.
column(
col)) {
600 ++row_degree_[e.row()];
605 for (RowIndex
row(0);
row < num_rows; ++
row) {
608 if (row_degree_[
row] == 1) singleton_rows->push_back(
row);
612 row_degree_[
row] = 0;
617 for (ColIndex
col(0);
col < num_cols; ++
col) {
619 int32_t col_degree = 0;
620 for (
const SparseColumn::Entry e : basis_matrix.
column(
col)) {
621 const RowIndex
row = e.row();
627 col_degree_[
col] = col_degree;
628 if (col_degree == 1) singleton_columns->push_back(
col);
639 return --col_degree_[
col];
643 return --row_degree_[
row];
647 ColIndex pivot_col) {
648 DCHECK(!deleted_columns_[pivot_col]);
649 deleted_columns_[pivot_col] =
true;
650 --num_non_deleted_columns_;
653 row_degree_[pivot_row] = 0;
657 return deleted_columns_[
col];
661 auto& ref = row_non_zero_[
row];
663 const int end = ref.size();
664 for (
int i = 0; i <
end; ++i) {
665 const ColIndex
col = ref[i];
666 if (!deleted_columns_[
col]) {
667 ref[new_index] =
col;
675 RowIndex
row)
const {
687 DCHECK(deleted_columns_[pivot_col]);
688 const int max_row_degree = num_non_deleted_columns_.value() + 1;
691 for (
const ColIndex
col : row_non_zero_[pivot_row]) {
693 bool_scratchpad_[
col] =
false;
700 for (
const SparseColumn::Entry e :
column) {
701 const RowIndex
row = e.row();
702 if (
row == pivot_row)
continue;
707 if (e.coefficient() == 0.0 || row_degree_[
row] == max_row_degree)
continue;
708 DCHECK_LT(row_degree_[
row], max_row_degree);
716 const int kDeletionThreshold = 4;
717 if (row_non_zero_[
row].
size() > row_degree_[
row] + kDeletionThreshold) {
722 MergeInto(pivot_row,
row);
730 MergeIntoSorted(pivot_row,
row);
735void MatrixNonZeroPattern::MergeInto(RowIndex pivot_row, RowIndex
row) {
738 for (
const ColIndex
col : row_non_zero_[
row]) {
739 bool_scratchpad_[
col] =
true;
742 auto& non_zero = row_non_zero_[
row];
743 const int old_size = non_zero.size();
744 for (
const ColIndex
col : row_non_zero_[pivot_row]) {
745 if (bool_scratchpad_[
col]) {
746 bool_scratchpad_[
col] =
false;
748 non_zero.push_back(
col);
752 row_degree_[
row] += non_zero.
size() - old_size;
760template <
typename V,
typename W>
761void MergeSortedVectors(
const V& input_a, W* out) {
762 if (input_a.empty())
return;
763 const auto& input_b = *out;
764 int index_a = input_a.size() - 1;
765 int index_b = input_b.size() - 1;
766 int index_out = input_a.size() + input_b.size();
767 out->resize(index_out);
768 while (index_a >= 0) {
770 while (index_a >= 0) {
772 (*out)[index_out] = input_a[index_a];
778 if (input_a[index_a] > input_b[index_b]) {
779 (*out)[index_out] = input_a[index_a];
782 (*out)[index_out] = input_b[index_b];
793void MatrixNonZeroPattern::MergeIntoSorted(RowIndex pivot_row, RowIndex
row) {
795 const auto&
input = row_non_zero_[pivot_row];
796 const auto& output = row_non_zero_[
row];
799 col_scratchpad_.resize(
input.size());
800 col_scratchpad_.resize(std::set_difference(
input.begin(),
input.end(),
801 output.begin(), output.end(),
802 col_scratchpad_.begin()) -
803 col_scratchpad_.begin());
806 for (
const ColIndex
col : col_scratchpad_) {
809 row_degree_[
row] += col_scratchpad_.
size();
810 MergeSortedVectors(col_scratchpad_, &row_non_zero_[
row]);
816 col_by_degree_.clear();
821 col_degree_.
assign(num_cols, 0);
822 col_index_.
assign(num_cols, -1);
823 col_by_degree_.resize(max_degree + 1);
824 min_degree_ = max_degree + 1;
828 DCHECK_GE(degree, 0);
829 DCHECK_LT(degree, col_by_degree_.size());
831 DCHECK_LT(
col, col_degree_.
size());
833 const int32_t old_degree = col_degree_[
col];
834 if (degree != old_degree) {
835 const int32_t old_index = col_index_[
col];
836 if (old_index != -1) {
837 col_by_degree_[old_degree][old_index] = col_by_degree_[old_degree].back();
838 col_index_[col_by_degree_[old_degree].back()] = old_index;
839 col_by_degree_[old_degree].pop_back();
842 col_index_[
col] = col_by_degree_[degree].size();
843 col_degree_[
col] = degree;
844 col_by_degree_[degree].push_back(
col);
845 min_degree_ = std::min(min_degree_, degree);
847 col_index_[
col] = -1;
848 col_degree_[
col] = 0;
854 DCHECK_GE(min_degree_, 0);
855 DCHECK_LE(min_degree_, col_by_degree_.size());
857 if (min_degree_ == col_by_degree_.size())
return kInvalidCol;
858 if (!col_by_degree_[min_degree_].empty())
break;
861 const ColIndex
col = col_by_degree_[min_degree_].back();
862 col_by_degree_[min_degree_].pop_back();
863 col_index_[
col] = -1;
864 col_degree_[
col] = 0;
869 mapping_.
assign(num_cols.value(), -1);
870 free_columns_.clear();
875 ColIndex
col)
const {
876 if (mapping_[
col] == -1)
return empty_column_;
877 return columns_[mapping_[
col]];
882 if (mapping_[
col] != -1)
return &columns_[mapping_[
col]];
884 if (free_columns_.empty()) {
885 new_col_index = columns_.size();
888 new_col_index = free_columns_.back();
889 free_columns_.pop_back();
891 mapping_[
col] = new_col_index;
892 return &columns_[new_col_index];
896 DCHECK_NE(mapping_[
col], -1);
897 free_columns_.push_back(mapping_[
col]);
898 columns_[mapping_[
col]].Clear();
904 free_columns_.clear();
void Clear()
Releases the memory used by this class.
void PushOrAdjust(ColIndex col, int32_t degree)
void Reset(int32_t max_degree, ColIndex num_cols)
EntryIndex num_entries() 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().
int32_t ColDegree(ColIndex col) const
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)
int32_t RowDegree(RowIndex row) const
void InitializeFromMatrixSubset(const CompactSparseMatrixView &basis_matrix, const RowPermutation &row_perm, const ColumnPermutation &col_perm, std::vector< ColIndex > *singleton_columns, std::vector< RowIndex > *singleton_rows)
void Clear()
Releases the memory used by this class.
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.
bool IsEmpty() const
Returns true if the vector is empty.
static Status OK()
Improves readability but identical to 0-arg constructor.
@ ERROR_LU
The LU factorization of the current basis couldn't be computed.
void assign(IntType size, const T &v)
void AssignToZero(IntType size)
void Reset(RowIndex num_rows, ColIndex col_capacity)
void AddTriangularColumnWithGivenDiagonalEntry(const SparseColumn &column, RowIndex diagonal_row, Fractional diagonal_value)
void AddTriangularColumn(const ColumnView &column, RowIndex diagonal_row)
EntryIndex num_entries() const
void ApplyRowPermutationToNonDiagonalEntries(const RowPermutation &row_perm)
Applies the given row permutation to all entries except the diagonal ones.
void AddDiagonalOnlyColumn(Fractional diagonal_value)
void PermutedLowerSparseSolve(const ColumnView &rhs, const RowPermutation &row_perm, SparseColumn *lower, SparseColumn *upper)
int64_t NumFpOperationsInLastPermutedLowerSparseSolve() const
This is used to compute the deterministic time of a matrix factorization.
void AddAndNormalizeTriangularColumn(const SparseColumn &column, RowIndex diagonal_row, Fractional diagonal_coefficient)
void Swap(TriangularMatrix *other)
void assign(size_type n, const value_type &val)
void push_back(const value_type &val)
void reserve(size_type n)
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, ElementAllocator > SparseColumn
static int input(yyscan_t yyscanner)
std::optional< int64_t > end
#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.