Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sparse_matrix.h
Go to the documentation of this file.
1// Copyright 2010-2024 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"
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.
174template <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
253namespace 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.
257constexpr double kZerosCleanup = 1.0 / 3.0;
258
259// entries must have unique (row, column) values but can be in any order.
260template <typename RowId, typename ColumnId>
261SparseDoubleMatrixProto EntriesToMatrixProto(
262 std::vector<std::tuple<RowId, ColumnId, double>> entries);
263
264} // namespace internal
265
268// Inlined functions
271
272namespace internal {
273
274template <typename RowId, typename ColumnId>
275SparseDoubleMatrixProto 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
297std::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
302bool 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
334double SparseSymmetricMatrix::get(const VariableId first,
335 const VariableId second) const {
336 return gtl::FindWithDefault(values_, make_key(first, second));
337}
338
340// SparseMatrix
342
343template <typename RowId, typename ColumnId>
344bool 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
373template <typename RowId, typename ColumnId>
374double SparseMatrix<RowId, ColumnId>::get(const RowId row,
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}
382template <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
389template <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
405template <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
421template <typename RowId, typename ColumnId>
422std::vector<ColumnId> SparseMatrix<RowId, ColumnId>::row(
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);
431 }
432 }
433 return result;
434}
435
436template <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);
446 }
447 }
448 return result;
449}
450
451template <typename RowId, typename ColumnId>
452std::vector<std::pair<ColumnId, double>>
453SparseMatrix<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});
462 }
463 }
464 return result;
465}
466
467template <typename RowId, typename ColumnId>
468std::vector<std::pair<RowId, double>>
469SparseMatrix<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});
478 }
479 }
480 return result;
481}
482
483template <typename RowId, typename ColumnId>
484std::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
496template <typename RowId, typename ColumnId>
498 rows_.clear();
499 columns_.clear();
500 values_.clear();
501}
502
503template <typename RowId, typename ColumnId>
505 return nonzeros_;
506}
507
508template <typename RowId, typename ColumnId>
509SparseDoubleMatrixProto 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);
518 return result;
519}
520
521template <typename RowId, typename ColumnId>
522SparseDoubleMatrixProto SparseMatrix<RowId, ColumnId>::Update(
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
561template <typename RowId, typename ColumnId>
562void SparseMatrix<RowId, ColumnId>::CompactIfNeeded() {
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_
std::vector< RowId > column(ColumnId column_id) const
bool set(RowId row, ColumnId column, double value)
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
std::vector< ColumnId > row(RowId row_id) const
void Clear()
Removes all terms from the matrix.
double get(RowId row, ColumnId column) const
Zero is returned if the value is not present.
std::vector< std::pair< RowId, double > > ColumnTerms(ColumnId col_id) const
void DeleteColumn(ColumnId column)
Zeros out all coefficients for this variable.
std::vector< std::tuple< RowId, ColumnId, double > > Terms() const
void DeleteRow(RowId row)
Zeros out all coefficients for this variable.
bool contains(RowId row, ColumnId column) const
Returns true if the value is present (nonzero).
SparseDoubleMatrixProto Proto() const
int64_t nonzeros() const
The number of (row, column) keys with nonzero value.
std::vector< std::pair< ColumnId, double > > RowTerms(RowId row_id) 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
std::vector< VariableId > RelatedVariables(VariableId variable) const
std::vector< std::tuple< VariableId, VariableId, double > > Terms() const
const absl::flat_hash_map< std::pair< VariableId, VariableId >, double > & values() const
void Clear()
Removes all terms from the matrix.
double get(VariableId first, VariableId second) const
Zero is returned if the value is not present.
void Delete(VariableId variable)
Zeros out all coefficients for this variable.
bool set(VariableId first, VariableId second, double value)
int64_t value
IntVar * var
int64_t coef
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
Definition map_util.h:36
SparseDoubleMatrixProto EntriesToMatrixProto(std::vector< std::tuple< RowId, ColumnId, double > > entries)
entries must have unique (row, column) values but can be in any order.
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
int column