Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
attr_storage.h
Go to the documentation of this file.
1// Copyright 2010-2025 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#ifndef OR_TOOLS_MATH_OPT_ELEMENTAL_ATTR_STORAGE_H_
15#define OR_TOOLS_MATH_OPT_ELEMENTAL_ATTR_STORAGE_H_
16
17#include <array>
18#include <cstddef>
19#include <cstdint>
20#include <optional>
21#include <type_traits>
22#include <variant>
23#include <vector>
24
25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
30
32
33namespace detail {
34
35// A non-default key set based on a vector. This is very efficient for
36// insertions, reads, and slicing, but does not support deletions.
37template <int n>
39 public:
40 // {Dense,Sparse}KeySet stores symmetric keys, symmetry is handled by
41 // `SlicingStorage`.
43
44 DenseKeySet() = default;
45
46 size_t size() const { return key_set_.size(); }
47
48 template <typename F>
49 // requires std::invocable<F, const Key&>
50 void ForEach(F f) const {
51 for (const Key& key : key_set_) {
52 f(key);
53 }
54 }
55
56 // Note: this does not check for duplicates. This is fine because inserting
57 // into this map is gated on inserting into the AttrStorage, which does check
58 // for duplicates.
59 void Insert(const Key& key) { key_set_.push_back(key); }
60
61 auto begin() const { return key_set_.begin(); }
62 auto end() const { return key_set_.end(); }
63
64 private:
65 std::vector<Key> key_set_;
66};
67
68// A non-default key set based on a hash set. Simple, but requires a hash lookup
69// for each insertion and deletion.
70template <int n>
72 public:
73 // {Dense,Sparse}KeySet stores symmetric keys, symmetry is handled by
74 // `SlicingStorage`.
76
77 explicit SparseKeySet(const DenseKeySet<n>& dense_set)
78 : key_set_(dense_set.begin(), dense_set.end()) {}
79
80 size_t size() const { return key_set_.size(); }
81
82 template <typename F>
83 // requires std::invocable<F, const Key&>
84 void ForEach(F f) const {
85 for (const Key& key : key_set_) {
86 f(key);
87 }
88 }
89
90 void Erase(const Key& key) { key_set_.erase(key); }
91 void Insert(const Key& key) { key_set_.insert(key); }
92
93 private:
94 absl::flat_hash_set<Key> key_set_;
95};
96
97// A non-default key set that switches between implementations
98// opportunistically: It starts dense, and switches to sparse if there are
99// deletions.
100template <int n>
101class KeySet {
102 public:
104
105 size_t size() const {
106 return std::visit([](const auto& impl) { return impl.size(); }, impl_);
107 }
108
109 // We can't do begin/end because the iterator types are not the same.
110 template <typename F>
111 // requires std::invocable<F, const Key&>
112 void ForEach(F f) const {
113 return std::visit(
114 [f = std::move(f)](const auto& impl) {
115 return impl.ForEach(std::move(f));
116 },
117 impl_);
118 }
119
120 auto Erase(const Key& key) { return AsSparse().Erase(key); }
121
122 void Insert(const Key& key) {
123 std::visit([&](auto& impl) { impl.Insert(key); }, impl_);
124 }
125
126 private:
127 SparseKeySet<n>& AsSparse() {
128 if (auto* sparse = std::get_if<SparseKeySet<n>>(&impl_)) {
129 return *sparse;
130 }
131 // Switch to a sparse representation.
132 impl_ = SparseKeySet<n>(std::get<DenseKeySet<n>>(impl_));
133 return std::get<SparseKeySet<n>>(impl_);
134 }
135
136 std::variant<DenseKeySet<n>, SparseKeySet<n>> impl_;
137};
138
139// When we have two or more dimensions, we need to store the nondefaults for
140// each dimension to support slicing.
141template <int n, typename Symmetry, typename = void>
143 public:
145
146 void AddRowsAndColumns(const Key key) {
147 // NOLINTNEXTLINE(clang-diagnostic-pre-c++20-compat)
148 ForEachDimension([this, key]<int i>() {
149 if (MustInsertNondefault<i>(key, Symmetry{})) {
150 key_nondefaults_[i][key[i]].Insert(key.template RemoveElement<i>());
151 }
152 });
153 }
154
155 // Requires key is currently stored with a non-default value.
157 // NOLINTNEXTLINE(clang-diagnostic-pre-c++20-compat)
158 ForEachDimension([this, key]<int i>() {
159 const auto& key_elem = key[i];
160 auto& nondefaults = key_nondefaults_[i];
161 if (nondefaults[key_elem].size() == 1) {
162 nondefaults.erase(key_elem);
163 } else {
164 nondefaults[key_elem].Erase(key.template RemoveElement<i>());
165 }
166 });
167 }
168
169 void Clear() {
170 for (auto& key_nondefaults : key_nondefaults_) {
171 key_nondefaults.clear();
172 }
173 }
174
175 template <int i>
176 std::vector<Key> Slice(const int64_t key_elem) const {
177 return SliceImpl<i>(
178 key_elem, Symmetry{},
179 // NOLINTNEXTLINE(clang-diagnostic-pre-c++20-compat)
180 [key_elem]<int... is>(KeySetExpansion<is>... expansions) {
181 std::vector<Key> slice((expansions.key_set.size() + ...));
182 Key* out = slice.data();
183 // NOLINTNEXTLINE(clang-diagnostic-pre-c++20-compat)
184 const auto append = [key_elem, &out]<int j>(
185 const KeySetExpansion<j>& expansion) {
186 expansion.key_set.ForEach(
187 [key_elem, &out](const AttrKey<n - 1> other_elems) {
188 *out = other_elems.template AddElement<j, Symmetry>(key_elem);
189 ++out;
190 });
191 };
192 (append(expansions), ...);
193 return slice;
194 });
195 }
196
197 template <int i>
198 int64_t GetSliceSize(const int64_t key_elem) const {
199 return SliceImpl<i>(key_elem, Symmetry{}, [](const auto... expansions) {
200 return (expansions.key_set.size() + ...);
201 });
202 }
203
204 private:
205 // We store the nondefaults for a given id along a given dimension as a set of
206 // `AttrKey<n-1, NoSymmetry>` (the current dimension is not stored).
207 using NonDefaultKeySet = KeySet<n - 1>;
208 // For each dimension, we store the nondefaults for each id.
209 using NonDefaultKeySetById = absl::flat_hash_map<int64_t, NonDefaultKeySet>;
210 // We need one such set per dimension.
211 using NonDefaultsPerDimension = std::array<NonDefaultKeySetById, n>;
212
213 // Represents a NonDefaultKeySet to be expanded by inserting an element on
214 // dimension `i`.
215 template <int i>
216 struct KeySetExpansion {
217 KeySetExpansion(const NonDefaultsPerDimension& key_nondefaults,
218 int64_t key_elem)
219 : key_set(gtl::FindWithDefault(key_nondefaults[i], key_elem)) {}
220 const NonDefaultKeySet& key_set;
221 };
222
223 // A helper function that calls `F` `i` times with template arguments `n-1` to
224 // `0`.
225 template <typename F, int i = n - 1>
226 static void ForEachDimension(const F& f) {
227 f.template operator()<i>();
228 if constexpr (i > 0) {
229 ForEachDimension<F, i - 1>(f);
230 }
231 }
232
233 template <int i>
234 static bool MustInsertNondefault(const Key&, NoSymmetry) {
235 return true;
236 }
237
238 template <int i, int k, int l>
239 static bool MustInsertNondefault(const Key& key, ElementSymmetry<k, l>) {
240 // For attributes that are symmetric on `k` and `l`, elements on the
241 // diagonal need to be in only one of the nondefaults for `k` or `l`
242 // (otherwise they would be counted twice in `Slice()`). We arbitrarily pick
243 // `k`.
244 if constexpr (i == l) {
245 const bool is_diagonal = key[k] == key[l];
246 return !is_diagonal;
247 }
248 return true;
249 }
250
251 // `Fn` should be a functor that takes any number of `KeySetExpansion`
252 // arguments.
253 template <int i, typename Fn>
254 auto SliceImpl(const int64_t key_elem, NoSymmetry, const Fn& fn) const {
255 static_assert(n > 1);
256 return fn(KeySetExpansion<i>(key_nondefaults_, key_elem));
257 }
258
259 template <int i, int k, int l, typename Fn>
260 auto SliceImpl(const int64_t key_elem, ElementSymmetry<k, l>,
261 const Fn& fn) const {
262 static_assert(n > 1);
263 if constexpr (i != k && i != l) {
264 // This is a normal dimension, not a symmetric one.
265 return SliceImpl<i>(key_elem, NoSymmetry(), fn);
266 } else {
267 // For symmetric dimensions, we need to look up the keys on both
268 // dimensions `l` and `k`.
269 return fn(KeySetExpansion<k>(key_nondefaults_, key_elem),
270 KeySetExpansion<l>(key_nondefaults_, key_elem));
271 }
272 }
273
274 NonDefaultsPerDimension key_nondefaults_;
275};
276
277// Without slicing we don't need to track anything.
278template <int n, typename Symmetry>
279struct SlicingSupport<n, Symmetry, std::enable_if_t<(n < 2), std::void_t<>>> {
281
284 void Clear() {}
285};
286
287} // namespace detail
288
289// Stores the value of an attribute keyed on n elements (e.g.
290// linear_constraint_coefficient is a double valued attribute keyed first on
291// LinearConstraint and then on Variable).
292//
293// Memory usage:
294// Storing `k` elements with non-default values in a `AttrStorage<V, n>` uses
295// `sizeof(V) * (n^2 + 1) * k / load_factor` (where load_factor is the absl
296// hash map load factor, typically 0.8), plus a small allocation overhead of
297// `O(k)`.
298template <typename V, int n, typename Symmetry>
300 public:
302 // If this no longer holds, we should sprinkle the code with `move`s and
303 // return `V`s by ref.
304 static_assert(std::is_trivially_copyable_v<V>);
305
306 // Generally avoid, provided to make working with std::array easier.
307 explicit AttrStorage() : AttrStorage(V{}) {}
308
309 // The default value of the attribute is its value when the model is created
310 // (e.g. for linear_constraint_coefficient, 0.0).
311 explicit AttrStorage(const V default_value) : default_value_(default_value) {}
312
313 AttrStorage(const AttrStorage&) = default;
317
318 // Returns true if the attribute for `key` has a value different from its
319 // default.
320 bool IsNonDefault(const Key key) const {
321 return non_default_values_.contains(key);
322 }
323
324 // Returns the previous value if value has changed, otherwise returns
325 // `std::nullopt`.
326 std::optional<V> Set(const Key key, const V value) {
327 bool is_default = value == default_value_;
328 if (is_default) {
329 const auto it = non_default_values_.find(key);
330 if (it == non_default_values_.end()) {
331 return std::nullopt;
332 }
333 const V prev_value = it->second;
334 non_default_values_.erase(it);
335 slicing_support_.ClearRowsAndColumns(key);
336 return prev_value;
337 }
338 const auto [it, inserted] = non_default_values_.try_emplace(key, value);
339 if (inserted) {
340 slicing_support_.AddRowsAndColumns(key);
341 return default_value_;
342 }
343 // !is_default and !inserted
344 if (value == it->second) {
345 return std::nullopt;
346 }
347 return std::exchange(it->second, value);
348 }
349
350 // Returns the value of the attribute for `key` (return the default value if
351 // the attribute value for `key` is unset).
352 V Get(const Key key) const {
353 return GetIfNonDefault(key).value_or(default_value_);
354 }
355
356 // Returns the value of the attribute for `key`, or nullopt.
357 std::optional<V> GetIfNonDefault(const Key key) const {
358 auto it = non_default_values_.find(key);
359 if (it == non_default_values_.end()) {
360 return std::nullopt;
361 }
362 return it->second;
363 }
364
365 // Sets the value of the attribute for `key` to the default value.
366 void Erase(const Key key) {
367 if (non_default_values_.erase(key)) {
368 slicing_support_.ClearRowsAndColumns(key);
369 }
370 }
371
372 // Returns the keys (ids pairs) the of the elements with a non-default value
373 // for this attribute.
374 std::vector<Key> NonDefaults() const {
375 std::vector<Key> result;
376 result.reserve(non_default_values_.size());
377 for (const auto& [key, unused] : non_default_values_) {
378 result.push_back(key);
379 }
380 return result;
381 }
382
383 // Returns the set of all keys `K` such that:
384 // - There exists `k_{0}..k_{n-1}` such that
385 // `K == AttrKey(k_{0}, ..., k_{i-1}, key_elem, k_{i+1}, ..., k_{n-1})`, and
386 // - `K` has a non-default value for this attribute.
387 template <int i>
388 std::vector<Key> Slice(const int64_t key_elem) const {
389 static_assert(n >= 1);
390 if constexpr (n == 1) {
391 return non_default_values_.contains(Key(key_elem))
392 ? std::vector<Key>({Key(key_elem)})
393 : std::vector<Key>();
394 } else {
395 return slicing_support_.template Slice<i>(key_elem);
396 }
397 }
398
399 // Returns the size of the given slice: This is equivalent to
400 // `Slice(key_elem).size()`, but `O(1)`.
401 template <int i>
402 int64_t GetSliceSize(const int64_t key_elem) const {
403 static_assert(n >= 1);
404 if constexpr (n == 1) {
405 return non_default_values_.count(Key(key_elem));
406 } else {
407 return slicing_support_.template GetSliceSize<i>(key_elem);
408 }
409 }
410
411 // Returns the number of keys (element pairs) with non-default values for this
412 // attribute.
413 int64_t num_non_defaults() const { return non_default_values_.size(); }
414
415 // Restore all elements to their default value for this attribute.
416 void Clear() {
417 non_default_values_.clear();
418 slicing_support_.Clear();
419 }
420
421 private:
422 V default_value_;
423 AttrKeyHashMap<Key, V> non_default_values_;
425};
426
427} // namespace operations_research::math_opt
428
429#endif // OR_TOOLS_MATH_OPT_ELEMENTAL_ATTR_STORAGE_H_
AttrStorage & operator=(const AttrStorage &)=default
std::optional< V > GetIfNonDefault(const Key key) const
Returns the value of the attribute for key, or nullopt.
std::vector< Key > NonDefaults() const
std::optional< V > Set(const Key key, const V value)
AttrStorage()
Generally avoid, provided to make working with std::array easier.
AttrStorage(const AttrStorage &)=default
int64_t GetSliceSize(const int64_t key_elem) const
void Erase(const Key key)
Sets the value of the attribute for key to the default value.
AttrStorage & operator=(AttrStorage &&)=default
void Clear()
Restore all elements to their default value for this attribute.
std::vector< Key > Slice(const int64_t key_elem) const
void ForEach(F f) const
requires std::invocable<F, const Key&>
void ForEach(F f) const
We can't do begin/end because the iterator types are not the same.
std::vector< Key > Slice(const int64_t key_elem) const
int64_t GetSliceSize(const int64_t key_elem) const
void ClearRowsAndColumns(Key key)
Requires key is currently stored with a non-default value.
SparseKeySet(const DenseKeySet< n > &dense_set)
void ForEach(F f) const
requires std::invocable<F, const Key&>
!
Definition array.h:26
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
std::conditional_t<(AttrKeyT::size() > 0), absl::flat_hash_map< AttrKeyT, V >, detail::AttrKey0RawSet< typename AttrKeyT::SymmetryT, std::pair< AttrKeyT, V > > > AttrKeyHashMap
Definition attr_key.h:355
ClosedInterval::Iterator end(ClosedInterval interval)
ClosedInterval::Iterator begin(ClosedInterval interval)
STL namespace.