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