22 #include "absl/strings/str_format.h"
35 const RowIndex num_rows = basis_matrix.
num_rows();
36 const ColIndex num_cols = basis_matrix.
num_cols();
42 basis_matrix_ = &basis_matrix;
45 lower_.
Reset(num_rows, num_cols);
46 upper_.
Reset(num_rows, num_cols);
47 permuted_lower_.
Reset(num_cols);
48 permuted_upper_.
Reset(num_cols);
49 permuted_lower_column_needs_solve_.
assign(num_cols,
false);
50 contains_only_singleton_columns_ =
true;
56 ExtractSingletonColumns(basis_matrix, row_perm, col_perm, &
index);
57 ExtractResidualSingletonColumns(basis_matrix, row_perm, col_perm, &
index);
58 int stats_num_pivots_without_fill_in =
index;
59 int stats_degree_two_pivot_columns = 0;
64 basis_matrix, *row_perm, *col_perm, &singleton_column_, &singleton_row_);
67 const int end_index =
std::min(num_rows.value(), num_cols.value());
69 parameters_.markowitz_singularity_threshold();
70 while (
index < end_index) {
79 const int64_t min_markowitz = FindPivot(*row_perm, *col_perm, &pivot_row,
80 &pivot_col, &pivot_coefficient);
87 std::abs(pivot_coefficient) <= singularity_threshold) {
88 const std::string error_message = absl::StrFormat(
89 "The matrix is singular! pivot = %E", pivot_coefficient);
90 VLOG(1) <<
"ERROR_LU: " << error_message;
100 const int pivot_col_degree = residual_matrix_non_zero_.
ColDegree(pivot_col);
101 const int pivot_row_degree = residual_matrix_non_zero_.
RowDegree(pivot_row);
103 if (min_markowitz == 0) {
104 ++stats_num_pivots_without_fill_in;
105 if (pivot_col_degree == 1) {
106 RemoveRowFromResidualMatrix(pivot_row, pivot_col);
108 DCHECK_EQ(pivot_row_degree, 1);
109 RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
116 if (pivot_col_degree == 2) { ++stats_degree_two_pivot_columns; });
117 UpdateResidualMatrix(pivot_row, pivot_col);
120 if (contains_only_singleton_columns_) {
126 pivot_row, pivot_coefficient);
130 permuted_upper_.
column(pivot_col), pivot_row, pivot_coefficient);
135 (*col_perm)[pivot_col] = ColIndex(
index);
136 (*row_perm)[pivot_row] = RowIndex(
index);
142 num_fp_operations_ += 10 * lower_.
num_entries().value();
143 num_fp_operations_ += 10 * upper_.
num_entries().value();
145 stats_.pivots_without_fill_in_ratio.Add(
146 1.0 * stats_num_pivots_without_fill_in / num_rows.value());
147 stats_.degree_two_pivot_columns.Add(1.0 * stats_degree_two_pivot_columns /
167 DCHECK(
lower->IsLowerTriangular());
168 DCHECK(
upper->IsUpperTriangular());
174 permuted_lower_.
Clear();
175 permuted_upper_.
Clear();
176 residual_matrix_non_zero_.
Clear();
177 col_by_degree_.
Clear();
178 examined_col_.clear();
179 num_fp_operations_ = 0;
180 is_col_by_degree_initialized_ =
false;
188 MatrixEntry(RowIndex r, ColIndex c,
Fractional coeff)
190 bool operator<(
const MatrixEntry& o)
const {
191 return (
row == o.row) ?
col < o.col :
row < o.row;
197 void Markowitz::ExtractSingletonColumns(
198 const CompactSparseMatrixView& basis_matrix,
RowPermutation* row_perm,
201 std::vector<MatrixEntry> singleton_entries;
202 const ColIndex num_cols = basis_matrix.num_cols();
203 for (ColIndex
col(0);
col < num_cols; ++
col) {
204 const ColumnView&
column = basis_matrix.column(
col);
205 if (
column.num_entries().value() == 1) {
206 singleton_entries.push_back(
213 std::sort(singleton_entries.begin(), singleton_entries.end());
214 for (
const MatrixEntry e : singleton_entries) {
216 (*col_perm)[e.col] = ColIndex(*
index);
217 (*row_perm)[e.row] = RowIndex(*
index);
223 stats_.basis_singleton_column_ratio.Add(
static_cast<double>(*
index) /
224 basis_matrix.num_rows().value());
227 bool Markowitz::IsResidualSingletonColumn(
const ColumnView&
column,
230 int residual_degree = 0;
231 for (
const auto e :
column) {
234 if (residual_degree > 1)
return false;
237 return residual_degree == 1;
240 void Markowitz::ExtractResidualSingletonColumns(
241 const CompactSparseMatrixView& basis_matrix,
RowPermutation* row_perm,
244 const ColIndex num_cols = basis_matrix.num_cols();
246 for (ColIndex
col(0);
col < num_cols; ++
col) {
248 const ColumnView&
column = basis_matrix.column(
col);
249 if (!IsResidualSingletonColumn(
column, *row_perm, &
row))
continue;
250 (*col_perm)[
col] = ColIndex(*
index);
251 (*row_perm)[
row] = RowIndex(*
index);
256 stats_.basis_residual_singleton_column_ratio.Add(
257 static_cast<double>(*
index) / basis_matrix.num_rows().value());
260 const SparseColumn& Markowitz::ComputeColumn(
const RowPermutation& row_perm,
275 if (permuted_lower_column_needs_solve_[
col]) {
279 const ColumnView&
input =
280 first_time ? basis_matrix_->
column(
col) : ColumnView(*lower_column);
283 permuted_lower_column_needs_solve_[
col] =
false;
284 num_fp_operations_ +=
286 return *lower_column;
292 if (lower_column->num_entries() == residual_matrix_non_zero_.
ColDegree(
col)) {
293 return *lower_column;
303 for (
const auto e : basis_matrix_->
column(
col)) {
304 lower_column->SetCoefficient(e.row(), e.coefficient());
307 num_fp_operations_ += lower_column->num_entries().value();
308 lower_column->MoveTaggedEntriesTo(row_perm,
310 return *lower_column;
315 RowIndex* pivot_row, ColIndex* pivot_col,
320 while (!singleton_column_.empty()) {
321 const ColIndex
col = singleton_column_.back();
322 singleton_column_.pop_back();
331 if (residual_matrix_non_zero_.
ColDegree(
col) != 1)
continue;
336 if (contains_only_singleton_columns_) {
340 *pivot_row = e.row();
341 *pivot_coefficient = e.coefficient();
347 const SparseColumn&
column = ComputeColumn(row_perm,
col);
348 if (
column.IsEmpty())
continue;
350 *pivot_row =
column.GetFirstRow();
351 *pivot_coefficient =
column.GetFirstCoefficient();
354 contains_only_singleton_columns_ =
false;
359 while (!singleton_row_.empty()) {
360 const RowIndex
row = singleton_row_.back();
361 singleton_row_.pop_back();
369 if (residual_matrix_non_zero_.
RowDegree(
row) != 1)
continue;
373 const SparseColumn&
column = ComputeColumn(row_perm,
col);
374 if (
column.IsEmpty())
continue;
378 *pivot_coefficient =
column.LookUpCoefficient(
row);
384 if (!is_col_by_degree_initialized_) {
385 is_col_by_degree_initialized_ =
true;
386 const ColIndex num_cols = col_perm.size();
387 col_by_degree_.
Reset(row_perm.size().value(), num_cols);
388 for (ColIndex
col(0);
col < num_cols; ++
col) {
390 const int degree = residual_matrix_non_zero_.
ColDegree(
col);
391 DCHECK_NE(degree, 1);
392 UpdateDegree(
col, degree);
399 examined_col_.clear();
400 const int num_columns_to_examine = parameters_.markowitz_zlatev_parameter();
401 const Fractional threshold = parameters_.lu_factorization_pivot_threshold();
402 while (examined_col_.size() < num_columns_to_examine) {
403 const ColIndex
col = col_by_degree_.
Pop();
406 const int col_degree = residual_matrix_non_zero_.
ColDegree(
col);
407 examined_col_.push_back(
col);
418 const int64_t markowitz_lower_bound = col_degree - 1;
419 if (min_markowitz_number < markowitz_lower_bound)
break;
425 const SparseColumn&
column = ComputeColumn(row_perm,
col);
426 DCHECK_EQ(
column.num_entries(), col_degree);
430 max_magnitude =
std::max(max_magnitude, std::abs(e.coefficient()));
432 if (max_magnitude == 0.0) {
435 examined_col_.pop_back();
439 const Fractional skip_threshold = threshold * max_magnitude;
441 const Fractional magnitude = std::abs(e.coefficient());
442 if (magnitude < skip_threshold)
continue;
444 const int row_degree = residual_matrix_non_zero_.
RowDegree(e.row());
445 const int64_t markowitz_number = (col_degree - 1) * (row_degree - 1);
446 DCHECK_NE(markowitz_number, 0);
447 if (markowitz_number < min_markowitz_number ||
448 ((markowitz_number == min_markowitz_number) &&
449 magnitude > std::abs(*pivot_coefficient))) {
450 min_markowitz_number = markowitz_number;
452 *pivot_row = e.row();
453 *pivot_coefficient = e.coefficient();
461 DCHECK_NE(min_markowitz_number, 0);
462 DCHECK_GE(min_markowitz_number, markowitz_lower_bound);
476 for (
const ColIndex
col : examined_col_) {
477 if (
col != *pivot_col) {
478 const int degree = residual_matrix_non_zero_.
ColDegree(
col);
482 return min_markowitz_number;
485 void Markowitz::UpdateDegree(ColIndex
col,
int degree) {
486 DCHECK(is_col_by_degree_initialized_);
498 singleton_column_.push_back(
col);
504 void Markowitz::RemoveRowFromResidualMatrix(RowIndex pivot_row,
505 ColIndex pivot_col) {
511 if (is_col_by_degree_initialized_) {
512 for (
const ColIndex
col : residual_matrix_non_zero_.
RowNonZero(pivot_row)) {
517 for (
const ColIndex
col : residual_matrix_non_zero_.
RowNonZero(pivot_row)) {
520 singleton_column_.push_back(
col);
526 void Markowitz::RemoveColumnFromResidualMatrix(RowIndex pivot_row,
527 ColIndex pivot_col) {
537 const RowIndex
row = e.row();
539 singleton_row_.push_back(
row);
544 void Markowitz::UpdateResidualMatrix(RowIndex pivot_row, ColIndex pivot_col) {
546 const SparseColumn& pivot_column = permuted_lower_.
column(pivot_col);
547 residual_matrix_non_zero_.
Update(pivot_row, pivot_col, pivot_column);
548 for (
const ColIndex
col : residual_matrix_non_zero_.
RowNonZero(pivot_row)) {
549 DCHECK_NE(
col, pivot_col);
551 permuted_lower_column_needs_solve_[
col] =
true;
553 RemoveColumnFromResidualMatrix(pivot_row, pivot_col);
563 row_non_zero_.
clear();
564 deleted_columns_.
clear();
565 bool_scratchpad_.
clear();
566 num_non_deleted_columns_ = 0;
572 row_non_zero_.
clear();
573 row_non_zero_.
resize(num_rows.value());
574 deleted_columns_.
assign(num_cols,
false);
575 bool_scratchpad_.
assign(num_cols,
false);
576 num_non_deleted_columns_ = num_cols;
582 std::vector<RowIndex>* singleton_rows) {
583 const ColIndex num_cols = basis_matrix.
num_cols();
584 const RowIndex num_rows = basis_matrix.
num_rows();
587 Reset(num_rows, num_cols);
588 singleton_columns->clear();
589 singleton_rows->clear();
592 for (ColIndex
col(0);
col < num_cols; ++
col) {
594 deleted_columns_[
col] =
true;
595 --num_non_deleted_columns_;
599 ++row_degree_[e.row()];
604 for (RowIndex
row(0);
row < num_rows; ++
row) {
607 if (row_degree_[
row] == 1) singleton_rows->push_back(
row);
611 row_degree_[
row] = 0;
616 for (ColIndex
col(0);
col < num_cols; ++
col) {
618 int32_t col_degree = 0;
620 const RowIndex
row = e.row();
626 col_degree_[
col] = col_degree;
638 return --col_degree_[
col];
642 return --row_degree_[
row];
646 ColIndex pivot_col) {
647 DCHECK(!deleted_columns_[pivot_col]);
648 deleted_columns_[pivot_col] =
true;
649 --num_non_deleted_columns_;
652 row_degree_[pivot_row] = 0;
656 return deleted_columns_[
col];
660 auto& ref = row_non_zero_[
row];
662 const int end = ref.size();
663 for (
int i = 0; i <
end; ++i) {
664 const ColIndex
col = ref[i];
665 if (!deleted_columns_[
col]) {
666 ref[new_index] =
col;
670 ref.resize(new_index);
674 RowIndex
row)
const {
686 DCHECK(deleted_columns_[pivot_col]);
687 const int max_row_degree = num_non_deleted_columns_.value() + 1;
690 for (
const ColIndex
col : row_non_zero_[pivot_row]) {
692 bool_scratchpad_[
col] =
false;
700 const RowIndex
row = e.row();
701 if (
row == pivot_row)
continue;
706 if (e.coefficient() == 0.0 || row_degree_[
row] == max_row_degree)
continue;
707 DCHECK_LT(row_degree_[
row], max_row_degree);
715 const int kDeletionThreshold = 4;
716 if (row_non_zero_[
row].size() > row_degree_[
row] + kDeletionThreshold) {
721 MergeInto(pivot_row,
row);
729 MergeIntoSorted(pivot_row,
row);
734 void MatrixNonZeroPattern::MergeInto(RowIndex pivot_row, RowIndex
row) {
737 for (
const ColIndex
col : row_non_zero_[
row]) {
738 bool_scratchpad_[
col] =
true;
741 auto& non_zero = row_non_zero_[
row];
742 const int old_size = non_zero.
size();
743 for (
const ColIndex
col : row_non_zero_[pivot_row]) {
744 if (bool_scratchpad_[
col]) {
745 bool_scratchpad_[
col] =
false;
751 row_degree_[
row] += non_zero.
size() - old_size;
759 template <
typename V,
typename W>
760 void MergeSortedVectors(
const V& input_a, W* out) {
761 if (input_a.empty())
return;
762 const auto& input_b = *out;
763 int index_a = input_a.size() - 1;
764 int index_b = input_b.size() - 1;
765 int index_out = input_a.size() + input_b.size();
766 out->resize(index_out);
767 while (index_a >= 0) {
769 while (index_a >= 0) {
771 (*out)[index_out] = input_a[index_a];
777 if (input_a[index_a] > input_b[index_b]) {
778 (*out)[index_out] = input_a[index_a];
781 (*out)[index_out] = input_b[index_b];
792 void MatrixNonZeroPattern::MergeIntoSorted(RowIndex pivot_row, RowIndex
row) {
794 const auto&
input = row_non_zero_[pivot_row];
795 const auto& output = row_non_zero_[
row];
798 col_scratchpad_.resize(
input.size());
799 col_scratchpad_.resize(std::set_difference(
input.begin(),
input.end(),
800 output.begin(), output.end(),
801 col_scratchpad_.begin()) -
802 col_scratchpad_.begin());
805 for (
const ColIndex
col : col_scratchpad_) {
808 row_degree_[
row] += col_scratchpad_.
size();
809 MergeSortedVectors(col_scratchpad_, &row_non_zero_[
row]);
815 col_by_degree_.clear();
820 col_degree_.
assign(num_cols, 0);
821 col_index_.
assign(num_cols, -1);
822 col_by_degree_.resize(max_degree + 1);
823 min_degree_ = max_degree + 1;
827 DCHECK_GE(degree, 0);
828 DCHECK_LT(degree, col_by_degree_.size());
830 DCHECK_LT(
col, col_degree_.
size());
832 const int32_t old_degree = col_degree_[
col];
833 if (degree != old_degree) {
834 const int32_t old_index = col_index_[
col];
835 if (old_index != -1) {
836 col_by_degree_[old_degree][old_index] = col_by_degree_[old_degree].back();
837 col_index_[col_by_degree_[old_degree].back()] = old_index;
838 col_by_degree_[old_degree].
pop_back();
841 col_index_[
col] = col_by_degree_[degree].size();
842 col_degree_[
col] = degree;
843 col_by_degree_[degree].push_back(
col);
844 min_degree_ =
std::min(min_degree_, degree);
846 col_index_[
col] = -1;
847 col_degree_[
col] = 0;
853 DCHECK_GE(min_degree_, 0);
854 DCHECK_LE(min_degree_, col_by_degree_.size());
856 if (min_degree_ == col_by_degree_.size())
return kInvalidCol;
857 if (!col_by_degree_[min_degree_].empty())
break;
860 const ColIndex
col = col_by_degree_[min_degree_].back();
861 col_by_degree_[min_degree_].pop_back();
862 col_index_[
col] = -1;
863 col_degree_[
col] = 0;
868 mapping_.
assign(num_cols.value(), -1);
869 free_columns_.clear();
874 ColIndex
col)
const {
875 if (mapping_[
col] == -1)
return empty_column_;
876 return columns_[mapping_[
col]];
881 if (mapping_[
col] != -1)
return &columns_[mapping_[
col]];
883 if (free_columns_.empty()) {
884 new_col_index = columns_.size();
887 new_col_index = free_columns_.back();
888 free_columns_.pop_back();
890 mapping_[
col] = new_col_index;
891 return &columns_[new_col_index];
895 DCHECK_NE(mapping_[
col], -1);
896 free_columns_.push_back(mapping_[
col]);
897 columns_[mapping_[
col]].Clear();
903 free_columns_.clear();
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
void reserve(size_type n)
void push_back(const value_type &x)
void Reset(int32_t max_degree, ColIndex num_cols)
void PushOrAdjust(ColIndex col, int32_t degree)
EntryIndex num_entries() const
ColIndex num_cols() const
const ColumnView column(ColIndex col) const
RowIndex num_rows() const
double DeterministicTimeOfLastFactorization() const
ABSL_MUST_USE_RESULT Status ComputeLU(const CompactSparseMatrixView &basis_matrix, RowPermutation *row_perm, ColumnPermutation *col_perm, TriangularMatrix *lower, TriangularMatrix *upper)
ABSL_MUST_USE_RESULT Status ComputeRowAndColumnPermutation(const CompactSparseMatrixView &basis_matrix, RowPermutation *row_perm, ColumnPermutation *col_perm)
const absl::InlinedVector< ColIndex, 6 > & RowNonZero(RowIndex row) const
bool IsColumnDeleted(ColIndex col) const
void RemoveDeletedColumnsFromRow(RowIndex row)
int32_t DecreaseColDegree(ColIndex col)
void DeleteRowAndColumn(RowIndex pivot_row, ColIndex pivot_col)
int32_t RowDegree(RowIndex row) const
void AddEntry(RowIndex row, ColIndex col)
void Reset(RowIndex num_rows, ColIndex num_cols)
void Update(RowIndex pivot_row, ColIndex pivot_col, const SparseColumn &column)
int32_t DecreaseRowDegree(RowIndex row)
int32_t ColDegree(ColIndex col) const
ColIndex GetFirstNonDeletedColumnFromRow(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 assign(IndexType size, IndexType value)
void Reset(ColIndex num_cols)
void ClearAndReleaseColumn(ColIndex col)
SparseColumn * mutable_column(ColIndex col)
const SparseColumn & column(ColIndex col) const
typename Iterator::Entry Entry
void AssignToZero(IntType size)
void assign(IntType size, const T &v)
void AddTriangularColumnWithGivenDiagonalEntry(const SparseColumn &column, RowIndex diagonal_row, Fractional diagonal_value)
void Swap(TriangularMatrix *other)
void AddAndNormalizeTriangularColumn(const SparseColumn &column, RowIndex diagonal_row, Fractional diagonal_coefficient)
void AddTriangularColumn(const ColumnView &column, RowIndex diagonal_row)
void PermutedLowerSparseSolve(const ColumnView &rhs, const RowPermutation &row_perm, SparseColumn *lower, SparseColumn *upper)
void ApplyRowPermutationToNonDiagonalEntries(const RowPermutation &row_perm)
void AddDiagonalOnlyColumn(Fractional diagonal_value)
int64_t NumFpOperationsInLastPermutedLowerSparseSolve() const
void Reset(RowIndex num_rows, ColIndex col_capacity)
EntryIndex num_entries() const
constexpr ColIndex kInvalidCol(-1)
Permutation< ColIndex > ColumnPermutation
constexpr RowIndex kInvalidRow(-1)
Permutation< RowIndex > RowPermutation
static double DeterministicTimeForFpOperations(int64_t n)
Collection of objects used to extend the Constraint Solver library.
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)
#define VLOG(verboselevel)