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
21namespace operations_research {
22namespace glop {
23
24namespace {
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.
31bool 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().
57struct 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.
79bool 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.
89ColumnFingerprint 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
191bool 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
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
#define DCHECK(condition)
Definition: base/logging.h:885
Fractional EntryCoefficient(EntryIndex i) const
Definition: sparse_column.h:83
RowIndex EntryRow(EntryIndex i) const
Definition: sparse_column.h:89
ColumnView column(ColIndex col) const
Definition: sparse.h:369
Fractional EntryCoefficient(EntryIndex i) const
Definition: sparse_column.h:52
RowIndex EntryRow(EntryIndex i) const
Definition: sparse_column.h:51
const SparseColumn & column(ColIndex col) const
Definition: sparse.h:181
int64_t b
int64_t a
int64_t hash
Definition: matrix_utils.cc:61
ColIndex col
Definition: matrix_utils.cc:60
double value
Definition: matrix_utils.cc:62
bool IsRightMostSquareMatrixIdentity(const SparseMatrix &matrix)
ColMapping FindProportionalColumnsUsingSimpleAlgorithm(const SparseMatrix &matrix, Fractional tolerance)
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
bool AreFirstColumnsAndRowsExactlyEquals(RowIndex num_rows, ColIndex num_cols, const SparseMatrix &matrix_a, const CompactSparseMatrix &matrix_b)
ColMapping FindProportionalColumns(const SparseMatrix &matrix, Fractional tolerance)
const ColIndex kInvalidCol(-1)
Collection of objects used to extend the Constraint Solver library.
uint64_t Hash(uint64_t num, uint64_t c)
Definition: hash.h:150