OR-Tools  9.6
matrix_utils.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <cstdint>
18 #include <limits>
19 #include <vector>
20 
21 #include "ortools/base/hash.h"
22 
23 namespace operations_research {
24 namespace glop {
25 
26 namespace {
27 
28 // Returns true iff the two given sparse columns are proportional. The two
29 // sparse columns must be ordered by row and must not contain any zero entry.
30 //
31 // See the header comment on FindProportionalColumns() for the exact definition
32 // of two proportional columns with a given tolerance.
33 bool AreColumnsProportional(const SparseColumn& a, const SparseColumn& b,
34  Fractional tolerance) {
35  DCHECK(a.IsCleanedUp());
36  DCHECK(b.IsCleanedUp());
37  if (a.num_entries() != b.num_entries()) return false;
38  Fractional multiple = 0.0;
39  bool a_is_larger = true;
40  for (const EntryIndex i : a.AllEntryIndices()) {
41  if (a.EntryRow(i) != b.EntryRow(i)) return false;
42  const Fractional coeff_a = a.EntryCoefficient(i);
43  const Fractional coeff_b = b.EntryCoefficient(i);
44  if (multiple == 0.0) {
45  a_is_larger = std::abs(coeff_a) > std::abs(coeff_b);
46  multiple = a_is_larger ? coeff_a / coeff_b : coeff_b / coeff_a;
47  } else {
48  if (a_is_larger) {
49  if (std::abs(coeff_a / coeff_b - multiple) > tolerance) return false;
50  } else {
51  if (std::abs(coeff_b / coeff_a - multiple) > tolerance) return false;
52  }
53  }
54  }
55  return true;
56 }
57 
58 // A column index together with its fingerprint. See ComputeFingerprint().
59 struct ColumnFingerprint {
60  ColumnFingerprint(ColIndex _col, int64_t _hash, double _value)
61  : col(_col), hash(_hash), value(_value) {}
62  ColIndex col;
63  int64_t hash;
64  double value;
65 
66  // This order has the property that if AreProportionalCandidates() is true for
67  // two given columns, then in a sorted list of columns
68  // AreProportionalCandidates() will be true for all the pairs of columns
69  // between the two given ones (included).
70  bool operator<(const ColumnFingerprint& other) const {
71  if (hash == other.hash) {
72  return value < other.value;
73  }
74  return hash < other.hash;
75  }
76 };
77 
78 // Two columns can be proportional only if:
79 // - Their non-zero pattern hashes are the same.
80 // - Their double fingerprints are close to each other.
81 bool AreProportionalCandidates(ColumnFingerprint a, ColumnFingerprint b,
82  Fractional tolerance) {
83  if (a.hash != b.hash) return false;
84  return std::abs(a.value - b.value) < tolerance;
85 }
86 
87 // The fingerprint of a column has two parts:
88 // - A hash value of the column non-zero pattern.
89 // - A double value which should be the same for two proportional columns
90 // modulo numerical errors.
91 ColumnFingerprint ComputeFingerprint(ColIndex col, const SparseColumn& column) {
92  int64_t non_zero_pattern_hash = 0;
94  Fractional max_abs = 0.0;
95  Fractional sum = 0.0;
96  for (const SparseColumn::Entry e : column) {
97  non_zero_pattern_hash =
98  util_hash::Hash(e.row().value(), non_zero_pattern_hash);
99  sum += e.coefficient();
100  min_abs = std::min(min_abs, std::abs(e.coefficient()));
101  max_abs = std::max(max_abs, std::abs(e.coefficient()));
102  }
103 
104  // The two scaled values are in [0, 1].
105  // TODO(user): A better way to discriminate columns would be to take the
106  // scalar product with a constant but random vector scaled by max_abs.
107  DCHECK_NE(0.0, max_abs);
108  const double inverse_dynamic_range = min_abs / max_abs;
109  const double scaled_average =
110  std::abs(sum) /
111  (static_cast<double>(column.num_entries().value()) * max_abs);
112  return ColumnFingerprint(col, non_zero_pattern_hash,
113  inverse_dynamic_range + scaled_average);
114 }
115 
116 } // namespace
117 
119  Fractional tolerance) {
120  const ColIndex num_cols = matrix.num_cols();
121  ColMapping mapping(num_cols, kInvalidCol);
122 
123  // Compute the fingerprint of each columns and sort them.
124  std::vector<ColumnFingerprint> fingerprints;
125  for (ColIndex col(0); col < num_cols; ++col) {
126  if (!matrix.column(col).IsEmpty()) {
127  fingerprints.push_back(ComputeFingerprint(col, matrix.column(col)));
128  }
129  }
130  std::sort(fingerprints.begin(), fingerprints.end());
131 
132  // Find a representative of each proportional columns class. This only
133  // compares columns with a close-enough fingerprint.
134  for (int i = 0; i < fingerprints.size(); ++i) {
135  const ColIndex col_a = fingerprints[i].col;
136  if (mapping[col_a] != kInvalidCol) continue;
137  for (int j = i + 1; j < fingerprints.size(); ++j) {
138  const ColIndex col_b = fingerprints[j].col;
139  if (mapping[col_b] != kInvalidCol) continue;
140 
141  // Note that we use the same tolerance for the fingerprints.
142  // TODO(user): Derive precise bounds on what this tolerance should be so
143  // that no proportional columns are missed.
144  if (!AreProportionalCandidates(fingerprints[i], fingerprints[j],
145  tolerance)) {
146  break;
147  }
148  if (AreColumnsProportional(matrix.column(col_a), matrix.column(col_b),
149  tolerance)) {
150  mapping[col_b] = col_a;
151  }
152  }
153  }
154 
155  // Sort the mapping so that the representative of each class is the smallest
156  // column. To achieve this, the current representative is used as a pointer
157  // to the new one, a bit like in an union find algorithm.
158  for (ColIndex col(0); col < num_cols; ++col) {
159  if (mapping[col] == kInvalidCol) continue;
160  const ColIndex new_representative = mapping[mapping[col]];
161  if (new_representative != kInvalidCol) {
162  mapping[col] = new_representative;
163  } else {
164  if (mapping[col] > col) {
165  mapping[mapping[col]] = col;
166  mapping[col] = kInvalidCol;
167  }
168  }
169  }
170 
171  return mapping;
172 }
173 
175  const SparseMatrix& matrix, Fractional tolerance) {
176  const ColIndex num_cols = matrix.num_cols();
177  ColMapping mapping(num_cols, kInvalidCol);
178  for (ColIndex col_a(0); col_a < num_cols; ++col_a) {
179  if (matrix.column(col_a).IsEmpty()) continue;
180  if (mapping[col_a] != kInvalidCol) continue;
181  for (ColIndex col_b(col_a + 1); col_b < num_cols; ++col_b) {
182  if (matrix.column(col_b).IsEmpty()) continue;
183  if (mapping[col_b] != kInvalidCol) continue;
184  if (AreColumnsProportional(matrix.column(col_a), matrix.column(col_b),
185  tolerance)) {
186  mapping[col_b] = col_a;
187  }
188  }
189  }
190  return mapping;
191 }
192 
193 bool AreFirstColumnsAndRowsExactlyEquals(RowIndex num_rows, ColIndex num_cols,
194  const SparseMatrix& matrix_a,
195  const CompactSparseMatrix& matrix_b) {
196  // TODO(user): Also DCHECK() that matrix_b is ordered by rows.
197  DCHECK(matrix_a.IsCleanedUp());
198  if (num_rows > matrix_a.num_rows() || num_rows > matrix_b.num_rows() ||
199  num_cols > matrix_a.num_cols() || num_cols > matrix_b.num_cols()) {
200  return false;
201  }
202  for (ColIndex col(0); col < num_cols; ++col) {
203  const SparseColumn& col_a = matrix_a.column(col);
204  const ColumnView& col_b = matrix_b.column(col);
205  const EntryIndex end = std::min(col_a.num_entries(), col_b.num_entries());
206  if (end < col_a.num_entries() && col_a.EntryRow(end) < num_rows) {
207  return false;
208  }
209  if (end < col_b.num_entries() && col_b.EntryRow(end) < num_rows) {
210  return false;
211  }
212  for (EntryIndex i(0); i < end; ++i) {
213  if (col_a.EntryRow(i) != col_b.EntryRow(i)) {
214  if (col_a.EntryRow(i) < num_rows || col_b.EntryRow(i) < num_rows) {
215  return false;
216  } else {
217  break;
218  }
219  }
220  if (col_a.EntryCoefficient(i) != col_b.EntryCoefficient(i)) {
221  return false;
222  }
223  if (col_a.num_entries() > end && col_a.EntryRow(end) < num_rows) {
224  return false;
225  }
226  if (col_b.num_entries() > end && col_b.EntryRow(end) < num_rows) {
227  return false;
228  }
229  }
230  }
231  return true;
232 }
233 
235  DCHECK(matrix.IsCleanedUp());
236  if (matrix.num_rows().value() > matrix.num_cols().value()) return false;
237  const ColIndex first_identity_col =
238  matrix.num_cols() - RowToColIndex(matrix.num_rows());
239  for (ColIndex col = first_identity_col; col < matrix.num_cols(); ++col) {
240  const SparseColumn& column = matrix.column(col);
241  if (column.num_entries() != 1 ||
242  column.EntryCoefficient(EntryIndex(0)) != 1.0) {
243  return false;
244  }
245  }
246  return true;
247 }
248 
249 } // namespace glop
250 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
Fractional EntryCoefficient(EntryIndex i) const
Definition: sparse_column.h:85
RowIndex EntryRow(EntryIndex i) const
Definition: sparse_column.h:91
ColumnView column(ColIndex col) const
Definition: sparse.h:403
Fractional EntryCoefficient(EntryIndex i) const
Definition: sparse_column.h:54
RowIndex EntryRow(EntryIndex i) const
Definition: sparse_column.h:53
const SparseColumn & column(ColIndex col) const
Definition: sparse.h:183
int64_t b
int64_t a
int64_t hash
Definition: matrix_utils.cc:63
ColIndex col
Definition: matrix_utils.cc:62
double value
Definition: matrix_utils.cc:64
constexpr ColIndex kInvalidCol(-1)
bool IsRightMostSquareMatrixIdentity(const SparseMatrix &matrix)
ColMapping FindProportionalColumnsUsingSimpleAlgorithm(const SparseMatrix &matrix, Fractional tolerance)
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:53
bool AreFirstColumnsAndRowsExactlyEquals(RowIndex num_rows, ColIndex num_cols, const SparseMatrix &matrix_a, const CompactSparseMatrix &matrix_b)
ColMapping FindProportionalColumns(const SparseMatrix &matrix, Fractional tolerance)
Collection of objects used to extend the Constraint Solver library.
uint64_t Hash(uint64_t num, uint64_t c)
Definition: hash.h:74
int column
Definition: parse_proto.cc:32
std::optional< int64_t > end