Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
model_summary.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#ifndef OR_TOOLS_MATH_OPT_CORE_MODEL_SUMMARY_H_
15#define OR_TOOLS_MATH_OPT_CORE_MODEL_SUMMARY_H_
16
17#include <cstdint>
18#include <initializer_list>
19#include <limits>
20#include <list>
21#include <optional>
22#include <string>
23#include <utility>
24#include <vector>
25
26#include "absl/algorithm/container.h"
27#include "absl/container/flat_hash_map.h"
28#include "absl/log/check.h"
29#include "absl/status/status.h"
30#include "absl/status/statusor.h"
31#include "absl/strings/string_view.h"
32#include "absl/types/span.h"
35#include "ortools/math_opt/model.pb.h"
36#include "ortools/math_opt/model_update.pb.h"
37
38namespace operations_research {
39namespace math_opt {
40
41// Maintains a bidirectional mapping between names and ids, e.g. as used for
42// variables and linear constraints.
43//
44// The following invariants are enforced:
45// * Ids must be unique and increasing (in insertion order).
46// * Ids are non-negative.
47// * Ids are not equal to std::numeric_limits<int64_t>::max()
48// * Ids removed are never reused.
49// * Names must be either empty or unique when built with check_names=true.
51 public:
52 // If check_names=false, the names need not be unique and the reverse mapping
53 // of name to id is not available.
54 explicit IdNameBiMap(bool check_names = true)
55 : nonempty_name_to_id_(
56 check_names ? std::make_optional<
57 absl::flat_hash_map<absl::string_view, int64_t>>()
58 : std::nullopt) {}
59
60 // Needs a custom copy constructor/assign because absl::string_view to
61 // internal data is held as a member. No custom move is needed.
62 IdNameBiMap(const IdNameBiMap& other);
63 IdNameBiMap& operator=(const IdNameBiMap& other);
64 IdNameBiMap(IdNameBiMap&& other) = default;
65 IdNameBiMap& operator=(IdNameBiMap&& other) = default;
66
67 // This constructor CHECKs that the input ids are sorted in increasing
68 // order. This constructor is expected to be used only for unit tests of
69 // validation code.
70 IdNameBiMap(std::initializer_list<std::pair<int64_t, absl::string_view>> ids);
71
72 // Inserts the provided id and associate the provided name to it.
73 //
74 // An error is returned if:
75 // * id is negative
76 // * id is not at least next_free_id()
77 // * id is max(int64_t)
78 // * name is a duplicated and check_names was true at construction.
79 //
80 // As a side effect it updates next_free_id to id + 1.
81 inline absl::Status Insert(int64_t id, std::string name);
82
83 // Removes the given id, or returns an error if id is not present.
84 inline absl::Status Erase(int64_t id);
85
86 inline bool HasId(int64_t id) const;
87
88 // Will always return false if name is empty or if check_names was false.
89 inline bool HasName(absl::string_view name) const;
90 inline bool Empty() const;
91 inline int Size() const;
92
93 // The next id that has never been used (0 initially since ids are
94 // non-negative).
95 inline int64_t next_free_id() const;
96
97 // Updates next_free_id(). Succeeds when the provided id is greater than every
98 // exiting id and is non-negative.
99 inline absl::Status SetNextFreeId(int64_t new_next_free_id);
100
101 // Iteration order is in increasing id order.
103 return id_to_name_;
104 }
105
106 // Is std::nullopt if check_names was false at construction.
107 const std::optional<absl::flat_hash_map<absl::string_view, int64_t>>&
109 return nonempty_name_to_id_;
110 }
111
112 // Warning: this may be mutated (partially updated) if an error is returned.
113 absl::Status BulkUpdate(absl::Span<const int64_t> deleted_ids,
114 absl::Span<const int64_t> new_ids,
115 absl::Span<const std::string* const> names);
116
117 private:
118 // Next unused id.
119 int64_t next_free_id_ = 0;
120
121 // Pointer stability for name strings and iterating in insertion order are
122 // both needed (so we do not use flat_hash_map).
124 std::optional<absl::flat_hash_map<absl::string_view, int64_t>>
125 nonempty_name_to_id_;
126};
127
129 explicit ModelSummary(bool check_names = true);
130 static absl::StatusOr<ModelSummary> Create(const ModelProto& model,
131 bool check_names = true);
132 absl::Status Update(const ModelUpdateProto& model_update);
133
143 bool maximize = false;
144};
145
147// Inline function implementations
149
150absl::Status IdNameBiMap::Insert(const int64_t id, std::string name) {
151 if (id < next_free_id_) {
153 << "expected id=" << id
154 << " to be at least next_free_id_=" << next_free_id_
155 << " (ids should be nonnegative and inserted in strictly increasing "
156 "order)";
157 }
158 if (id == std::numeric_limits<int64_t>::max()) {
159 return absl::InvalidArgumentError("id of max(int64_t) is not allowed");
160 }
161 next_free_id_ = id + 1;
162
163 const auto [it, success] = id_to_name_.emplace(id, std::move(name));
164 CHECK(success); // CHECK is okay, we have the invariant that next_free_id_ is
165 // larger than everything in the map.
166 const absl::string_view name_view(it->second);
167 if (nonempty_name_to_id_.has_value() && !name_view.empty()) {
168 const auto [it, success] = nonempty_name_to_id_->insert({name_view, id});
169 if (!success) {
171 << "duplicate name inserted: " << name_view;
172 }
173 }
174 return absl::OkStatus();
175}
176
177absl::Status IdNameBiMap::Erase(const int64_t id) {
178 const auto it = id_to_name_.find(id);
179 if (it == id_to_name_.end()) {
181 << "cannot delete missing id " << id;
182 }
183 const absl::string_view name_view(it->second);
184 if (nonempty_name_to_id_.has_value() && !name_view.empty()) {
185 // CHECK OK, name_view being in nonempty_name_to_id_ when the above is met
186 // is a class invariant.
187 CHECK_EQ(nonempty_name_to_id_->erase(name_view), 1)
188 << "name: " << name_view << " id: " << id;
189 }
190 id_to_name_.erase(it);
191 return absl::OkStatus();
192}
193
194bool IdNameBiMap::HasId(const int64_t id) const {
195 return id_to_name_.contains(id);
197bool IdNameBiMap::HasName(const absl::string_view name) const {
198 if (name.empty()) {
199 return false;
200 }
201 if (!nonempty_name_to_id_.has_value()) {
202 return false;
203 }
204 return nonempty_name_to_id_->contains(name);
205}
206
207bool IdNameBiMap::Empty() const { return id_to_name_.empty(); }
208
209int IdNameBiMap::Size() const { return id_to_name_.size(); }
210
211int64_t IdNameBiMap::next_free_id() const { return next_free_id_; }
212
213absl::Status IdNameBiMap::SetNextFreeId(const int64_t new_next_free_id) {
214 if (!Empty()) {
215 const int64_t largest_id = id_to_name_.back().first;
216 if (new_next_free_id <= largest_id) {
218 << "new_next_free_id=" << new_next_free_id
219 << " must be greater than largest_id=" << largest_id;
220 }
221 } else {
222 if (new_next_free_id < 0) {
224 << "new_next_free_id=" << new_next_free_id
225 << " must be nonnegative";
226 }
227 }
228 next_free_id_ = new_next_free_id;
229 return absl::OkStatus();
230}
231
232namespace internal {
233
234// TODO(b/232526223): this is an exact copy of
235// CheckIdsRangeAndStrictlyIncreasing from ids_validator.h, find a way to share
236// the code.
237// NOTE: This function is only exposed in the header because we need
238// UpdateBiMapFromMappedData here for testing purposes.
239absl::Status CheckIdsRangeAndStrictlyIncreasing2(absl::Span<const int64_t> ids);
240
241// NOTE: This is only exposed in the header for testing purposes.
242template <typename DataProto>
243absl::Status UpdateBiMapFromMappedData(
244 const absl::Span<const int64_t> deleted_ids,
245 const google::protobuf::Map<int64_t, DataProto>& proto_map,
246 IdNameBiMap& bimap) {
248 << "invalid deleted ids";
249 for (const int64_t id : deleted_ids) {
250 RETURN_IF_ERROR(bimap.Erase(id));
251 }
252 std::vector<int64_t> new_ids;
253 new_ids.reserve(proto_map.size());
254 for (const auto& [id, value] : proto_map) {
255 new_ids.push_back(id);
256 }
257 absl::c_sort(new_ids);
258 for (const int64_t id : new_ids) {
259 RETURN_IF_ERROR(bimap.Insert(id, proto_map.at(id).name()));
260 }
261 return absl::OkStatus();
262}
263
264} // namespace internal
265
266} // namespace math_opt
267} // namespace operations_research
268
269#endif // OR_TOOLS_MATH_OPT_CORE_MODEL_SUMMARY_H_
#define RETURN_IF_ERROR(expr)
bool contains(const key_arg< K > &key) const
iterator find(const key_arg< K > &key)
size_type erase(const key_arg< K > &key)
size_type size() const
Derive size_ from set_, as list::size might be O(N).
std::pair< iterator, bool > emplace(Args &&... args)
absl::Status Erase(int64_t id)
Removes the given id, or returns an error if id is not present.
const std::optional< absl::flat_hash_map< absl::string_view, int64_t > > & nonempty_name_to_id() const
Is std::nullopt if check_names was false at construction.
absl::Status SetNextFreeId(int64_t new_next_free_id)
const gtl::linked_hash_map< int64_t, std::string > & id_to_name() const
Iteration order is in increasing id order.
IdNameBiMap & operator=(IdNameBiMap &&other)=default
bool HasName(absl::string_view name) const
Will always return false if name is empty or if check_names was false.
IdNameBiMap & operator=(const IdNameBiMap &other)
absl::Status BulkUpdate(absl::Span< const int64_t > deleted_ids, absl::Span< const int64_t > new_ids, absl::Span< const std::string *const > names)
IdNameBiMap(IdNameBiMap &&other)=default
absl::Status Insert(int64_t id, std::string name)
const std::string name
A name for logging purposes.
int64_t value
GRBmodel * model
absl::Status UpdateBiMapFromMappedData(const absl::Span< const int64_t > deleted_ids, const google::protobuf::Map< int64_t, DataProto > &proto_map, IdNameBiMap &bimap)
absl::Status CheckIdsRangeAndStrictlyIncreasing2(absl::Span< const int64_t > ids)
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
StatusBuilder InvalidArgumentErrorBuilder()
absl::Status Update(const ModelUpdateProto &model_update)
static absl::StatusOr< ModelSummary > Create(const ModelProto &model, bool check_names=true)