OR-Tools  9.6
sparse_matrix.h
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 
14 // Classes for modeling sparse matrices.
15 #ifndef OR_TOOLS_MATH_OPT_STORAGE_SPARSE_MATRIX_H_
16 #define OR_TOOLS_MATH_OPT_STORAGE_SPARSE_MATRIX_H_
17 
18 #include <algorithm>
19 #include <cstdint>
20 #include <tuple>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/algorithm/container.h"
25 #include "absl/container/flat_hash_map.h"
26 #include "absl/container/flat_hash_set.h"
27 #include "absl/meta/type_traits.h"
28 #include "absl/types/span.h"
29 #include "ortools/base/map_util.h"
31 #include "ortools/math_opt/sparse_containers.pb.h"
33 
35 
36 // A sparse symmetric double valued matrix over VariableIds.
37 //
38 // Note that the matrix is sparse in both:
39 // * The IDs of the rows/columns (both VariableIds), stored as flat_hash_map.
40 // * The entries with nonzero value.
41 //
42 // Getting/setting/clearing entries are O(1) operations. Getting a row of the
43 // matrix runs in O(size of the row) if nothing has been deleted, and getting
44 // all the rows runs in O(number of nonzero entries), even with deletions
45 // (with deletions, accessing a particular row with many deletions may be slow).
46 //
47 // Implementation: The entries are stored in a
48 // flat_hash_map<pair<VariableId, VariableId>, double> `values_` where for each
49 // key, key.first <= key.second. Additionally, we maintain a
50 // flat_hash_map<VariableId, vector<VariableId>> `related_variables_` that says
51 // for each variable, which variables they have a nonzero term with. When a
52 // coefficient is set to zero or a variable is deleted, we do not immediately
53 // delete the data from values_ or related_variables_, we simply set the
54 // coefficient to zero in values_. We track how many zeros are in values_, and
55 // when more than some constant fraction of all entries are zero (see
56 // kZerosCleanup in cc file), we clean up related_variables_ and values_ to
57 // remove all the zeros. Iteration over the rows or total entries of the matrix
58 // must check for zeros in values_ and skip these terms.
59 //
60 // Memory use:
61 // * 3*8 bytes per nonzero plus hash capacity overhead for values_.
62 // * 2*8 bytes per nonzero plus vector capacity overhead for
63 // related_variables_.
64 // * ~5*8 bytes per variable participating in any quadratic term; one heap
65 // allocation per such variable.
67  public:
68  // Setting `value` to zero removes the value from the matrix.
69  // Returns true if `value` is different from the existing value in the matrix.
70  inline bool set(VariableId first, VariableId second, double value);
71 
72  // Zero is returned if the value is not present.
73  inline double get(VariableId first, VariableId second) const;
74 
75  // Zeros out all coefficients for this variable.
76  void Delete(VariableId variable);
77 
78  // Returns the variables with any nonzero in the matrix.
79  //
80  // The return order is deterministic but not defined.
81  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
82  std::vector<VariableId> Variables() const;
83 
84  // Returns the variables that have nonzero entries with `variable`.
85  //
86  // The return order is deterministic but not defined.
87  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
88  std::vector<VariableId> RelatedVariables(VariableId variable) const;
89 
90  // Returns the variable value pairs (x, c) where `variable` and x have nonzero
91  // coefficient c.
92  //
93  // The return order is deterministic but not defined.
94  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
95  std::vector<std::pair<VariableId, double>> Terms(VariableId variable) const;
96 
97  // Returns (x, y, c) tuples where variables x and y have nonzero coefficient
98  // c, and x <= y.
99  //
100  // The return order is non-deterministic and not defined.
101  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
102  std::vector<std::tuple<VariableId, VariableId, double>> Terms() const;
103 
104  // Removes all terms from the matrix.
105  void Clear();
106 
107  // The number of (var, var) keys with nonzero value. Note that (x, y) and
108  // (y, x) are the same key.
109  int64_t nonzeros() const { return nonzeros_; }
110 
111  // For testing/debugging only, do not depend on this value, behavior may
112  // change based on implementation.
113  int64_t impl_detail_matrix_storage_size() const { return values_.size(); }
114 
115  // TODO(b/233630053): do not expose values_ directly, instead offer a way to
116  // iterate over all the nonzero entries.
117  // Warning: this map will contain zeros.
118  const absl::flat_hash_map<std::pair<VariableId, VariableId>, double>& values()
119  const {
120  return values_;
121  }
122 
123  SparseDoubleMatrixProto Proto() const;
124 
125  SparseDoubleMatrixProto Update(
126  const absl::flat_hash_set<VariableId>& deleted_variables,
127  absl::Span<const VariableId> new_variables,
128  const absl::flat_hash_set<std::pair<VariableId, VariableId>>& dirty)
129  const;
130 
131  private:
132  inline std::pair<VariableId, VariableId> make_key(VariableId first,
133  VariableId second) const;
134  void CompactIfNeeded();
135 
136  // The keys of values_ have key.first <= key.second.
137  absl::flat_hash_map<std::pair<VariableId, VariableId>, double> values_;
138  absl::flat_hash_map<VariableId, std::vector<VariableId>> related_variables_;
139 
140  // The number of nonzero elements in values_.
141  int64_t nonzeros_ = 0;
142 };
143 
144 // A sparse double valued matrix over int-like rows and columns.
145 //
146 // Note that the matrix is sparse in both:
147 // * The IDs of the rows/columns, stored as flat_hash_map.
148 // * The entries with nonzero value.
149 //
150 // Getting/setting/clearing entries are O(1) operations. Getting a row of the
151 // matrix runs in O(size of the row) if nothing has been deleted, and getting
152 // all the rows runs in O(number of nonzero entries), even with deletions
153 // (with deletions, accessing a particular row or columns with many deletions
154 // may be slow).
155 //
156 // Implementation: The entries are stored in a
157 // flat_hash_map<pair<RowId, ColumnId>, double> `values_`. Additionally, we
158 // maintain a flat_hash_map<RowId, vector<ColumnId>> `rows_` and a
159 // flat_hash_map<ColumnId, vector<RowID>> `columns_` that enable efficient
160 // queries of the nonzeros in any row or column. When a coefficient is set to
161 // zero or a variable is deleted, we do not immediately delete the data from
162 // `values_`, `rows_`, or `columns_`, we simply set the coefficient to zero in
163 // `values_`. We track how many zeros are in `values_`, and when more than some
164 // constant fraction of all entries are zero (see `internal::kZerosCleanup`), we
165 // clean up `rows_`, `columns_`, and `values_` to remove all the zeros.
166 // Iteration over the rows or total entries of the matrix must check for zeros
167 // in values_ and skip these terms.
168 //
169 // Memory use:
170 // * 3*8 bytes per nonzero plus hash capacity overhead for values_.
171 // * 2*8 bytes per nonzero plus vector capacity overhead for
172 // rows_ and columns_.
173 // * ~5*8 bytes and one heap allocation per unique row and unique column.
174 template <typename RowId, typename ColumnId>
176  public:
177  // Setting `value` to zero removes the value from the matrix.
178  // Returns true if `value` is different from the existing value in the matrix.
179  bool set(RowId row, ColumnId column, double value);
180 
181  // Zero is returned if the value is not present.
182  double get(RowId row, ColumnId column) const;
183 
184  // Returns true if the value is present (nonzero).
185  bool contains(RowId row, ColumnId column) const;
186 
187  // Zeros out all coefficients for this variable.
188  void DeleteRow(RowId row);
189 
190  // Zeros out all coefficients for this variable.
191  void DeleteColumn(ColumnId column);
192 
193  // Returns the variables that have nonzero entries with `variable`.
194  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
195  std::vector<ColumnId> row(RowId row_id) const;
196 
197  // Returns the variables that have nonzero entries with `variable`.
198  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
199  std::vector<RowId> column(ColumnId column_id) const;
200 
201  // Returns the variable value pairs (x, c) where `variable` and x have nonzero
202  // coefficient c.
203  //
204  // The return order is deterministic but not defined.
205  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
206  std::vector<std::pair<ColumnId, double>> RowTerms(RowId row_id) const;
207 
208  // Returns the variable value pairs (x, c) where `variable` and x have nonzero
209  // coefficient c.
210  //
211  // The return order is deterministic but not defined.
212  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
213  std::vector<std::pair<RowId, double>> ColumnTerms(ColumnId col_id) const;
214 
215  // Returns (x, y, c) tuples where variables x and y have nonzero coefficient
216  // c, and x <= y.
217  //
218  // The return order is non-deterministic and not defined.
219  // TODO(b/233630053): expose an iterator based API to avoid making a copy.
220  std::vector<std::tuple<RowId, ColumnId, double>> Terms() const;
221 
222  // Removes all terms from the matrix.
223  void Clear();
224 
225  // The number of (row, column) keys with nonzero value.
226  int64_t nonzeros() const;
227 
228  // For testing/debugging only, do not depend on this value, behavior may
229  // change based on implementation.
230  int64_t impl_detail_matrix_storage_size() const { return values_.size(); }
231 
232  SparseDoubleMatrixProto Proto() const;
233 
234  SparseDoubleMatrixProto Update(
235  const absl::flat_hash_set<RowId>& deleted_rows,
236  absl::Span<const RowId> new_rows,
237  const absl::flat_hash_set<ColumnId>& deleted_columns,
238  absl::Span<const ColumnId> new_columns,
239  const absl::flat_hash_set<std::pair<RowId, ColumnId>>& dirty) const;
240 
241  private:
242  void CompactIfNeeded();
243 
244  // The values of the map can include zero.
245  absl::flat_hash_map<std::pair<RowId, ColumnId>, double> values_;
246  absl::flat_hash_map<RowId, std::vector<ColumnId>> rows_;
247  absl::flat_hash_map<ColumnId, std::vector<RowId>> columns_;
248 
249  // The number of nonzero elements in values_.
250  int64_t nonzeros_ = 0;
251 };
252 
253 namespace internal {
254 
255 // When the fraction of entries in values_ with value 0.0 is larger than
256 // kZerosCleanup, we compact the data structure and remove all zero entries.
257 constexpr double kZerosCleanup = 1.0 / 3.0;
258 
259 // entries must have unique (row, column) values but can be in any order.
260 template <typename RowId, typename ColumnId>
261 SparseDoubleMatrixProto EntriesToMatrixProto(
262  std::vector<std::tuple<RowId, ColumnId, double>> entries);
263 
264 } // namespace internal
265 
268 // Inlined functions
271 
272 namespace internal {
273 
274 template <typename RowId, typename ColumnId>
275 SparseDoubleMatrixProto EntriesToMatrixProto(
276  std::vector<std::tuple<RowId, ColumnId, double>> entries) {
277  absl::c_sort(entries);
278  const int num_entries = static_cast<int>(entries.size());
279  SparseDoubleMatrixProto result;
280  result.mutable_row_ids()->Reserve(num_entries);
281  result.mutable_column_ids()->Reserve(num_entries);
282  result.mutable_coefficients()->Reserve(num_entries);
283  for (const auto [lin_con, var, coef] : entries) {
284  result.add_row_ids(lin_con.value());
285  result.add_column_ids(var.value());
286  result.add_coefficients(coef);
287  }
288  return result;
289 }
290 
291 } // namespace internal
292 
294 // SparseSymmetricMatrix
296 
297 std::pair<VariableId, VariableId> SparseSymmetricMatrix::make_key(
298  const VariableId first, const VariableId second) const {
299  return {std::min(first, second), std::max(first, second)};
300 }
301 
302 bool SparseSymmetricMatrix::set(const VariableId first, const VariableId second,
303  const double value) {
304  const std::pair<VariableId, VariableId> key = make_key(first, second);
305  auto map_iter = values_.find(key);
306 
307  if (map_iter == values_.end()) {
308  if (value == 0.0) {
309  return false;
310  }
311  related_variables_[first].push_back(second);
312  if (first != second) {
313  related_variables_[second].push_back(first);
314  }
315  values_[key] = value;
316  nonzeros_++;
317  return true;
318  } else {
319  if (map_iter->second == value) {
320  return false;
321  }
322  const double old_value = map_iter->second;
323  map_iter->second = value;
324  if (value == 0.0) {
325  nonzeros_--;
326  CompactIfNeeded();
327  } else if (old_value == 0.0) {
328  nonzeros_++;
329  }
330  return true;
331  }
332 }
333 
334 double SparseSymmetricMatrix::get(const VariableId first,
335  const VariableId second) const {
336  return gtl::FindWithDefault(values_, make_key(first, second));
337 }
338 
340 // SparseMatrix
342 
343 template <typename RowId, typename ColumnId>
344 bool SparseMatrix<RowId, ColumnId>::set(const RowId row, const ColumnId column,
345  const double value) {
346  const std::pair<RowId, ColumnId> key = {row, column};
347  const auto it = values_.find(key);
348  if (it == values_.end()) {
349  if (value == 0.0) {
350  return false;
351  }
352  rows_[row].push_back(column);
353  columns_[column].push_back(row);
354  values_[key] = value;
355  ++nonzeros_;
356  return true;
357  } else {
358  if (it->second == value) {
359  return false;
360  }
361  const double old_value = it->second;
362  it->second = value;
363  if (value == 0.0) {
364  --nonzeros_;
365  CompactIfNeeded();
366  } else if (old_value == 0.0) {
367  ++nonzeros_;
368  }
369  return true;
370  }
371 }
372 
373 template <typename RowId, typename ColumnId>
375  const ColumnId column) const {
376  const auto it = values_.find({row, column});
377  if (it != values_.end()) {
378  return it->second;
379  }
380  return 0.0;
381 }
382 template <typename RowId, typename ColumnId>
384  const ColumnId column) const {
385  const auto it = values_.find({row, column});
386  return it != values_.end() && it->second != 0.0;
387 }
388 
389 template <typename RowId, typename ColumnId>
391  const auto row_it = rows_.find(row);
392  if (row_it == rows_.end()) {
393  return;
394  }
395  for (const ColumnId col : row_it->second) {
396  auto val_it = values_.find({row_it->first, col});
397  if (val_it != values_.end() && val_it->second != 0.0) {
398  val_it->second = 0.0;
399  nonzeros_--;
400  }
401  }
402  CompactIfNeeded();
403 }
404 
405 template <typename RowId, typename ColumnId>
407  const auto col_it = columns_.find(column);
408  if (col_it == columns_.end()) {
409  return;
410  }
411  for (const RowId row : col_it->second) {
412  auto val_it = values_.find({row, col_it->first});
413  if (val_it != values_.end() && val_it->second != 0.0) {
414  val_it->second = 0.0;
415  nonzeros_--;
416  }
417  }
418  CompactIfNeeded();
419 }
420 
421 template <typename RowId, typename ColumnId>
423  const RowId row_id) const {
424  std::vector<ColumnId> result;
425  const auto it = rows_.find(row_id);
426  if (it != rows_.end()) {
427  for (const ColumnId col : it->second) {
428  if (contains(row_id, col)) {
429  result.push_back(col);
430  }
431  }
432  }
433  return result;
434 }
435 
436 template <typename RowId, typename ColumnId>
438  const ColumnId column_id) const {
439  std::vector<RowId> result;
440  const auto it = columns_.find(column_id);
441  if (it != columns_.end()) {
442  for (const RowId row_id : it->second) {
443  if (contains(row_id, column_id)) {
444  result.push_back(row_id);
445  }
446  }
447  }
448  return result;
449 }
450 
451 template <typename RowId, typename ColumnId>
452 std::vector<std::pair<ColumnId, double>>
453 SparseMatrix<RowId, ColumnId>::RowTerms(const RowId row_id) const {
454  std::vector<std::pair<ColumnId, double>> result;
455  const auto it = rows_.find(row_id);
456  if (it != rows_.end()) {
457  for (const ColumnId col : it->second) {
458  const double val = get(row_id, col);
459  if (val != 0.0) {
460  result.push_back({col, val});
461  }
462  }
463  }
464  return result;
465 }
466 
467 template <typename RowId, typename ColumnId>
468 std::vector<std::pair<RowId, double>>
469 SparseMatrix<RowId, ColumnId>::ColumnTerms(const ColumnId col_id) const {
470  std::vector<std::pair<RowId, double>> result;
471  const auto it = columns_.find(col_id);
472  if (it != columns_.end()) {
473  for (const RowId row_id : it->second) {
474  const double val = get(row_id, col_id);
475  if (val != 0.0) {
476  result.push_back({row_id, val});
477  }
478  }
479  }
480  return result;
481 }
482 
483 template <typename RowId, typename ColumnId>
484 std::vector<std::tuple<RowId, ColumnId, double>>
486  std::vector<std::tuple<RowId, ColumnId, double>> result;
487  result.reserve(nonzeros_);
488  for (const auto& [k, v] : values_) {
489  if (v != 0.0) {
490  result.push_back({k.first, k.second, v});
491  }
492  }
493  return result;
494 }
495 
496 template <typename RowId, typename ColumnId>
498  rows_.clear();
499  columns_.clear();
500  values_.clear();
501 }
502 
503 template <typename RowId, typename ColumnId>
505  return nonzeros_;
506 }
507 
508 template <typename RowId, typename ColumnId>
509 SparseDoubleMatrixProto SparseMatrix<RowId, ColumnId>::Proto() const {
510  SparseDoubleMatrixProto result;
511  std::vector<std::tuple<RowId, ColumnId, double>> terms = Terms();
512  absl::c_sort(terms);
513  for (const auto [r, c, v] : terms) {
514  result.add_row_ids(r.value());
515  result.add_column_ids(c.value());
516  result.add_coefficients(v);
517  }
518  return result;
519 }
520 
521 template <typename RowId, typename ColumnId>
523  const absl::flat_hash_set<RowId>& deleted_rows,
524  const absl::Span<const RowId> new_rows,
525  const absl::flat_hash_set<ColumnId>& deleted_columns,
526  const absl::Span<const ColumnId> new_columns,
527  const absl::flat_hash_set<std::pair<RowId, ColumnId>>& dirty) const {
528  // Extract changes to the matrix of linear constraint coefficients
529  std::vector<std::tuple<LinearConstraintId, VariableId, double>>
530  matrix_updates;
531  for (const auto [row, column] : dirty) {
532  // Note: it is important that we check for deleted constraints and variables
533  // here. While we generally try to remove elements from matrix_keys_ when
534  // either their variable or constraint is deleted, if a coefficient is set
535  // to zero and then the variable/constraint is deleted, we will miss it.
536  if (deleted_rows.contains(row) || deleted_columns.contains(column)) {
537  continue;
538  }
539  matrix_updates.push_back({row, column, get(row, column)});
540  }
541 
542  for (const ColumnId new_col : new_columns) {
543  // TODO(b/233630053): use iterator API.
544  for (const auto [row, coef] : ColumnTerms(new_col)) {
545  matrix_updates.push_back({row, new_col, coef});
546  }
547  }
548  for (const RowId new_row : new_rows) {
549  // TODO(b/233630053) use iterator API.
550  for (const auto [col, coef] : RowTerms(new_row)) {
551  // NOTE(user): we already have the columns above the checkpoint from
552  // the loop above, but we will do at most twice as much as needed here.
553  if (new_columns.empty() || col < new_columns[0]) {
554  matrix_updates.push_back({new_row, col, coef});
555  }
556  }
557  }
558  return internal::EntriesToMatrixProto(std::move(matrix_updates));
559 }
560 
561 template <typename RowId, typename ColumnId>
563  const int64_t zeros = values_.size() - nonzeros_;
564  if (values_.empty() ||
565  static_cast<double>(zeros) / values_.size() <= internal::kZerosCleanup) {
566  return;
567  }
568  // Traverse the rows and remove elements where value_ is zero. Delete the row
569  // if it has no entries.
570  for (auto row_it = rows_.begin(); row_it != rows_.end();) {
571  const RowId row = row_it->first;
572  std::vector<ColumnId>& row_entries = row_it->second;
573  int64_t write = 0;
574  for (int64_t read = 0; read < row_entries.size(); ++read) {
575  const ColumnId col = row_entries[read];
576  const auto val_it = values_.find({row, col});
577  if (val_it != values_.end() && val_it->second != 0.0) {
578  row_entries[write] = col;
579  ++write;
580  }
581  }
582  if (write == 0) {
583  rows_.erase(row_it++);
584  } else {
585  row_entries.resize(write);
586  ++row_it;
587  }
588  }
589 
590  // Like above, but now over the columns. Additionally, delete elements from
591  // values_ that are zero in this second pass.
592  for (auto col_it = columns_.begin(); col_it != columns_.end();) {
593  const ColumnId col = col_it->first;
594  std::vector<RowId>& col_entries = col_it->second;
595  int64_t write = 0;
596  for (int64_t read = 0; read < col_entries.size(); ++read) {
597  const RowId row = col_entries[read];
598  const auto val_it = values_.find({row, col});
599  if (val_it != values_.end()) {
600  if (val_it->second != 0.0) {
601  col_entries[write] = row;
602  ++write;
603  } else {
604  values_.erase(val_it);
605  }
606  }
607  }
608  if (write == 0) {
609  columns_.erase(col_it++);
610  } else {
611  col_entries.resize(write);
612  ++col_it;
613  }
614  }
615 }
616 
617 } // namespace operations_research::math_opt
618 
619 #endif // OR_TOOLS_MATH_OPT_STORAGE_SPARSE_MATRIX_H_
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
std::vector< std::pair< ColumnId, double > > RowTerms(RowId row_id) const
bool set(RowId row, ColumnId column, double value)
std::vector< RowId > column(ColumnId column_id) const
std::vector< std::tuple< RowId, ColumnId, double > > Terms() const
std::vector< std::pair< RowId, double > > ColumnTerms(ColumnId col_id) const
SparseDoubleMatrixProto Proto() const
bool contains(RowId row, ColumnId column) const
std::vector< ColumnId > row(RowId row_id) const
SparseDoubleMatrixProto Update(const absl::flat_hash_set< RowId > &deleted_rows, absl::Span< const RowId > new_rows, const absl::flat_hash_set< ColumnId > &deleted_columns, absl::Span< const ColumnId > new_columns, const absl::flat_hash_set< std::pair< RowId, ColumnId >> &dirty) const
double get(RowId row, ColumnId column) const
double get(VariableId first, VariableId second) const
const absl::flat_hash_map< std::pair< VariableId, VariableId >, double > & values() const
std::vector< VariableId > RelatedVariables(VariableId variable) const
std::vector< VariableId > Variables() const
SparseDoubleMatrixProto Update(const absl::flat_hash_set< VariableId > &deleted_variables, absl::Span< const VariableId > new_variables, const absl::flat_hash_set< std::pair< VariableId, VariableId >> &dirty) const
bool set(VariableId first, VariableId second, double value)
std::vector< std::tuple< VariableId, VariableId, double > > Terms() const
int64_t value
IntVar * var
Definition: expr_array.cc:1874
int64_t coef
Definition: expr_array.cc:1875
ColIndex col
Definition: markowitz.cc:186
RowIndex row
Definition: markowitz.cc:185
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
SparseDoubleMatrixProto EntriesToMatrixProto(std::vector< std::tuple< RowId, ColumnId, double >> entries)
int column
Definition: parse_proto.cc:32
EntryIndex num_entries