OR-Tools  9.3
model_storage.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#include <memory>
19#include <optional>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/container/flat_hash_map.h"
25#include "absl/container/flat_hash_set.h"
26#include "absl/memory/memory.h"
27#include "absl/meta/type_traits.h"
28#include "absl/status/statusor.h"
29#include "absl/strings/string_view.h"
30#include "absl/synchronization/mutex.h"
31#include "absl/types/span.h"
38#include "ortools/math_opt/model.pb.h"
39#include "ortools/math_opt/model_update.pb.h"
40#include "ortools/math_opt/solution.pb.h"
41#include "ortools/math_opt/sparse_containers.pb.h"
43
44namespace operations_research {
45namespace math_opt {
46
47namespace {
48
49template <typename K, typename V>
50std::vector<K> MapKeys(const absl::flat_hash_map<K, V>& in_map) {
51 std::vector<K> keys;
52 keys.reserve(in_map.size());
53 for (const auto& key_pair : in_map) {
54 keys.push_back(key_pair.first);
55 }
56 return keys;
57}
58
59template <typename K, typename V>
60std::vector<K> SortedMapKeys(const absl::flat_hash_map<K, V>& in_map) {
61 std::vector<K> keys = MapKeys(in_map);
62 std::sort(keys.begin(), keys.end());
63 return keys;
64}
65
66template <typename T>
67std::vector<T> SortedSetKeys(const absl::flat_hash_set<T>& in_set) {
68 std::vector<T> keys;
69 keys.reserve(in_set.size());
70 for (const auto& key : in_set) {
71 keys.push_back(key);
72 }
73 std::sort(keys.begin(), keys.end());
74 return keys;
75}
76
77// ids should be sorted.
78template <typename IdType>
79void AppendFromMapOrDefault(const absl::Span<const IdType> ids,
80 const absl::flat_hash_map<IdType, double>& values,
81 SparseDoubleVectorProto& sparse_vector) {
82 for (const IdType id : ids) {
83 sparse_vector.add_ids(id.value());
84 sparse_vector.add_values(gtl::FindWithDefault(values, id));
85 }
86}
87
88// ids should be sorted.
89template <typename IdType, typename IdIterable>
90void AppendFromMapIfPresent(const IdIterable& ids,
91 const absl::flat_hash_map<IdType, double>& values,
92 SparseDoubleVectorProto& sparse_vector) {
93 for (const IdType id : ids) {
94 const double* const double_value = gtl::FindOrNull(values, id);
95 if (double_value != nullptr) {
96 sparse_vector.add_ids(id.value());
97 sparse_vector.add_values(*double_value);
98 }
99 }
100}
101
102template <typename IdType, typename DataType>
103void AppendFromMap(const absl::flat_hash_set<IdType>& dirty_keys,
104 const absl::flat_hash_map<IdType, DataType>& values,
105 double DataType::*field,
106 SparseDoubleVectorProto& sparse_vector) {
107 for (const IdType id : SortedSetKeys(dirty_keys)) {
108 sparse_vector.add_ids(id.value());
109 sparse_vector.add_values(values.at(id).*field);
110 }
111}
112
113template <typename T>
114absl::flat_hash_map<T, BasisStatusProto> SparseBasisVectorToMap(
115 const SparseBasisStatusVector& sparse_vector) {
116 absl::flat_hash_map<T, BasisStatusProto> result;
117 CHECK_EQ(sparse_vector.ids_size(), sparse_vector.values_size());
118 result.reserve(sparse_vector.ids_size());
119 for (const auto [id, value] : MakeView(sparse_vector)) {
120 gtl::InsertOrDie(&result, T(id), static_cast<BasisStatusProto>(value));
121 }
122 return result;
123}
124
125// If an element in keys is not found in coefficients, it is set to 0.0 in
126// matrix. Keys must be in lexicographic ordering (i.e. sorted).
127// NOTE: This signature can be updated to take a Span instead of a vector if
128// needed in the future, but it required specifying parameters at the callsites.
129template <typename RK, typename CK>
130SparseDoubleMatrixProto ExportMatrix(
131 const absl::flat_hash_map<std::pair<RK, CK>, double>& coefficients,
132 const std::vector<std::pair<RK, CK>>& keys) {
133 SparseDoubleMatrixProto matrix;
134 matrix.mutable_row_ids()->Reserve(keys.size());
135 matrix.mutable_column_ids()->Reserve(keys.size());
136 matrix.mutable_coefficients()->Reserve(keys.size());
137 for (const auto [row_id, column_id] : keys) {
138 matrix.add_row_ids(row_id.value());
139 matrix.add_column_ids(column_id.value());
140 matrix.add_coefficients(
141 gtl::FindWithDefault(coefficients, {row_id, column_id}));
142 }
143 return matrix;
144}
145
146} // namespace
147
148absl::StatusOr<std::unique_ptr<ModelStorage>> ModelStorage::FromModelProto(
149 const ModelProto& model_proto) {
150 // We don't check names since ModelStorage does not do so before exporting
151 // models. Thus a model built by ModelStorage can contain duplicated
152 // names. And since we use FromModelProto() to implement Clone(), we must make
153 // sure duplicated names don't fail.
154 RETURN_IF_ERROR(ValidateModel(model_proto, /*check_names=*/false));
155
156 auto storage = std::make_unique<ModelStorage>(model_proto.name());
157
158 // Add variables.
159 storage->AddVariables(model_proto.variables());
160
161 // Set the objective.
162 storage->set_is_maximize(model_proto.objective().maximize());
163 storage->set_objective_offset(model_proto.objective().offset());
164 storage->UpdateLinearObjectiveCoefficients(
165 model_proto.objective().linear_coefficients());
166 storage->UpdateQuadraticObjectiveCoefficients(
167 model_proto.objective().quadratic_coefficients());
168
169 // Add linear constraints.
170 storage->AddLinearConstraints(model_proto.linear_constraints());
171
172 // Set the linear constraints coefficients.
173 storage->UpdateLinearConstraintCoefficients(
174 model_proto.linear_constraint_matrix());
175
176 return storage;
177}
178
179void ModelStorage::UpdateLinearObjectiveCoefficients(
180 const SparseDoubleVectorProto& coefficients) {
181 for (const auto [var_id, value] : MakeView(coefficients)) {
182 set_linear_objective_coefficient(VariableId(var_id), value);
183 }
184}
185
186void ModelStorage::UpdateQuadraticObjectiveCoefficients(
187 const SparseDoubleMatrixProto& coefficients) {
188 for (int i = 0; i < coefficients.row_ids_size(); ++i) {
189 // This call is valid since this is an upper triangular matrix; there is no
190 // duplicated terms.
192 VariableId(coefficients.column_ids(i)),
193 coefficients.coefficients(i));
194 }
195}
196
197void ModelStorage::UpdateLinearConstraintCoefficients(
198 const SparseDoubleMatrixProto& coefficients) {
199 for (int i = 0; i < coefficients.row_ids_size(); ++i) {
200 // This call is valid since there are no duplicated pairs.
202 LinearConstraintId(coefficients.row_ids(i)),
203 VariableId(coefficients.column_ids(i)), coefficients.coefficients(i));
204 }
205}
206
207std::unique_ptr<ModelStorage> ModelStorage::Clone() const {
208 absl::StatusOr<std::unique_ptr<ModelStorage>> clone =
210 // Unless there is a very serious bug, a model exported by ExportModel()
211 // should always be valid.
212 CHECK_OK(clone.status());
213
214 // Update the next ids so that the clone does not reused any deleted id from
215 // the original.
216 CHECK_LE(clone.value()->next_variable_id_, next_variable_id_);
217 clone.value()->next_variable_id_ = next_variable_id_;
218 CHECK_LE(clone.value()->next_linear_constraint_id_,
219 next_linear_constraint_id_);
220 clone.value()->next_linear_constraint_id_ = next_linear_constraint_id_;
221
222 return std::move(clone).value();
223}
224
226 const double upper_bound,
227 const bool is_integer,
228 const absl::string_view name) {
229 const VariableId id = next_variable_id_;
230 AddVariableInternal(/*id=*/id,
231 /*lower_bound=*/lower_bound,
232 /*upper_bound=*/upper_bound,
233 /*is_integer=*/is_integer,
234 /*name=*/name);
235 return id;
236}
237
238void ModelStorage::AddVariableInternal(const VariableId id,
239 const double lower_bound,
240 const double upper_bound,
241 const bool is_integer,
242 const absl::string_view name) {
243 CHECK_GE(id, next_variable_id_);
244 next_variable_id_ = id + VariableId(1);
245
246 VariableData& var_data = variables_[id];
247 var_data.lower_bound = lower_bound;
248 var_data.upper_bound = upper_bound;
249 var_data.is_integer = is_integer;
250 var_data.name = std::string(name);
251 if (!lazy_matrix_columns_.empty()) {
252 gtl::InsertOrDie(&lazy_matrix_columns_, id, {});
253 }
254 if (!lazy_quadratic_objective_by_variable_.empty()) {
255 gtl::InsertOrDie(&lazy_quadratic_objective_by_variable_, id, {});
256 }
257}
258
259void ModelStorage::AddVariables(const VariablesProto& variables) {
260 const bool has_names = !variables.names().empty();
261 for (int v = 0; v < variables.ids_size(); ++v) {
262 // This call is valid since ids are unique and increasing.
263 AddVariableInternal(VariableId(variables.ids(v)),
264 /*lower_bound=*/variables.lower_bounds(v),
265 /*upper_bound=*/variables.upper_bounds(v),
266 /*is_integer=*/variables.integers(v),
267 has_names ? variables.names(v) : absl::string_view());
268 }
269}
270
271void ModelStorage::DeleteVariable(const VariableId id) {
272 CHECK(variables_.contains(id));
273 EnsureLazyMatrixColumns();
274 EnsureLazyMatrixRows();
275 linear_objective_.erase(id);
276 if (id < variables_checkpoint_) {
277 dirty_variable_deletes_.insert(id);
278 dirty_variable_lower_bounds_.erase(id);
279 dirty_variable_upper_bounds_.erase(id);
280 dirty_variable_is_integer_.erase(id);
281 dirty_linear_objective_coefficients_.erase(id);
282 }
283 // If we do not have any quadratic updates to delete, we would like to avoid
284 // initializing the lazy data structures. The updates might tracked in:
285 // 1. dirty_quadratic_objective_coefficients_ (both variables old)
286 // 2. quadratic_objective_ (at least one new variable)
287 // If both maps are empty, we can skip the update and initializiation. Note
288 // that we could be a bit more clever here based on whether the deleted
289 // variable is new or old, but that makes the logic more complex.
290 if (!quadratic_objective_.empty() ||
291 !dirty_quadratic_objective_coefficients_.empty()) {
292 EnsureLazyQuadraticObjective();
293 const auto related_variables =
294 lazy_quadratic_objective_by_variable_.extract(id);
295 for (const VariableId other_id : related_variables.mapped()) {
296 // Due to the extract above, the at lookup will fail if other_id == id.
297 if (id != other_id) {
298 CHECK_GT(lazy_quadratic_objective_by_variable_.at(other_id).erase(id),
299 0);
300 }
301 const auto ordered_pair = internal::MakeOrderedPair(id, other_id);
302 quadratic_objective_.erase(ordered_pair);
303 // We can only have a dirty update to wipe clean if both variables are old
304 if (id < variables_checkpoint_ && other_id < variables_checkpoint_) {
305 dirty_quadratic_objective_coefficients_.erase(ordered_pair);
306 }
307 }
308 }
309 for (const LinearConstraintId related_constraint :
310 lazy_matrix_columns_.at(id)) {
311 CHECK_GT(lazy_matrix_rows_.at(related_constraint).erase(id), 0);
312 CHECK_GT(linear_constraint_matrix_.erase({related_constraint, id}), 0);
313 if (id < variables_checkpoint_ &&
314 related_constraint < linear_constraints_checkpoint_) {
315 dirty_linear_constraint_matrix_keys_.erase({related_constraint, id});
316 }
317 }
318 CHECK_GT(lazy_matrix_columns_.erase(id), 0);
319 variables_.erase(id);
320}
321
322std::vector<VariableId> ModelStorage::variables() const {
323 return MapKeys(variables_);
324}
325
326std::vector<VariableId> ModelStorage::SortedVariables() const {
327 return SortedMapKeys(variables_);
328}
329
331 const double lower_bound, const double upper_bound,
332 const absl::string_view name) {
333 const LinearConstraintId id = next_linear_constraint_id_;
334 AddLinearConstraintInternal(/*id=*/id, /*lower_bound=*/lower_bound,
335 /*upper_bound=*/upper_bound,
336 /*name=*/name);
337 return id;
338}
339
340void ModelStorage::AddLinearConstraintInternal(const LinearConstraintId id,
341 const double lower_bound,
342 const double upper_bound,
343 const absl::string_view name) {
344 CHECK_GE(id, next_linear_constraint_id_);
345 next_linear_constraint_id_ = id + LinearConstraintId(1);
346
347 LinearConstraintData& lin_con_data = linear_constraints_[id];
348 lin_con_data.lower_bound = lower_bound;
349 lin_con_data.upper_bound = upper_bound;
350 lin_con_data.name = std::string(name);
351 if (!lazy_matrix_rows_.empty()) {
352 gtl::InsertOrDie(&lazy_matrix_rows_, id, {});
353 }
354}
355
356void ModelStorage::AddLinearConstraints(
357 const LinearConstraintsProto& linear_constraints) {
358 const bool has_names = !linear_constraints.names().empty();
359 for (int c = 0; c < linear_constraints.ids_size(); ++c) {
360 // This call is valid since ids are unique and increasing.
361 AddLinearConstraintInternal(
362 LinearConstraintId(linear_constraints.ids(c)),
363 /*lower_bound=*/linear_constraints.lower_bounds(c),
364 /*upper_bound=*/linear_constraints.upper_bounds(c),
365 has_names ? linear_constraints.names(c) : absl::string_view());
366 }
367}
368
369void ModelStorage::DeleteLinearConstraint(const LinearConstraintId id) {
370 CHECK(linear_constraints_.contains(id));
371 EnsureLazyMatrixColumns();
372 EnsureLazyMatrixRows();
373 linear_constraints_.erase(id);
374 if (id < linear_constraints_checkpoint_) {
375 dirty_linear_constraint_deletes_.insert(id);
376 dirty_linear_constraint_lower_bounds_.erase(id);
377 dirty_linear_constraint_upper_bounds_.erase(id);
378 }
379 for (const VariableId related_variable : lazy_matrix_rows_.at(id)) {
380 CHECK_GT(lazy_matrix_columns_.at(related_variable).erase(id), 0);
381 CHECK_GT(linear_constraint_matrix_.erase({id, related_variable}), 0);
382 if (id < linear_constraints_checkpoint_ &&
383 related_variable < variables_checkpoint_) {
384 dirty_linear_constraint_matrix_keys_.erase({id, related_variable});
385 }
386 }
387 CHECK_GT(lazy_matrix_rows_.erase(id), 0);
388}
389
390std::vector<LinearConstraintId> ModelStorage::linear_constraints() const {
391 return MapKeys(linear_constraints_);
392}
393
394std::vector<LinearConstraintId> ModelStorage::SortedLinearConstraints() const {
395 return SortedMapKeys(linear_constraints_);
396}
397
399 const {
400 return SortedMapKeys(linear_objective_);
401}
402
403void ModelStorage::AppendVariable(const VariableId id,
404 VariablesProto& variables_proto) const {
405 const VariableData& var_data = variables_.at(id);
406 variables_proto.add_ids(id.value());
407 variables_proto.add_lower_bounds(var_data.lower_bound);
408 variables_proto.add_upper_bounds(var_data.upper_bound);
409 variables_proto.add_integers(var_data.is_integer);
410 variables_proto.add_names(var_data.name);
411}
412
413void ModelStorage::AppendLinearConstraint(
414 const LinearConstraintId id,
415 LinearConstraintsProto& linear_constraints_proto) const {
416 const LinearConstraintData& con_impl = linear_constraints_.at(id);
417 linear_constraints_proto.add_ids(id.value());
418 linear_constraints_proto.add_lower_bounds(con_impl.lower_bound);
419 linear_constraints_proto.add_upper_bounds(con_impl.upper_bound);
420 linear_constraints_proto.add_names(con_impl.name);
421}
422
423ModelProto ModelStorage::ExportModel() const {
424 ModelProto result;
425 result.set_name(name_);
426 // Export the variables.
427 for (const VariableId variable : SortedMapKeys(variables_)) {
428 AppendVariable(variable, *result.mutable_variables());
429 }
430
431 // Pull out the objective.
432 result.mutable_objective()->set_maximize(is_maximize_);
433 result.mutable_objective()->set_offset(objective_offset_);
434 AppendFromMapOrDefault<VariableId>(
435 SortedMapKeys(linear_objective_), linear_objective_,
436 *result.mutable_objective()->mutable_linear_coefficients());
437 *result.mutable_objective()->mutable_quadratic_coefficients() =
438 ExportMatrix(quadratic_objective_, SortedMapKeys(quadratic_objective_));
439
440 // Pull out the linear constraints.
441 for (const LinearConstraintId con : SortedMapKeys(linear_constraints_)) {
442 AppendLinearConstraint(con, *result.mutable_linear_constraints());
443 }
444
445 // Pull out the constraint matrix.
446 *result.mutable_linear_constraint_matrix() =
447 ExportMatrix<LinearConstraintId, VariableId>(
448 linear_constraint_matrix_, SortedMapKeys(linear_constraint_matrix_));
449 return result;
450}
451
452std::optional<ModelUpdateProto> ModelStorage::ExportSharedModelUpdate() {
453 // We must detect the empty case to prevent unneeded copies and merging in
454 // ExportModelUpdate().
455 if (variables_checkpoint_ == next_variable_id_ &&
456 linear_constraints_checkpoint_ == next_linear_constraint_id_ &&
457 !dirty_objective_direction_ && !dirty_objective_offset_ &&
458 dirty_variable_deletes_.empty() && dirty_variable_lower_bounds_.empty() &&
459 dirty_variable_upper_bounds_.empty() &&
460 dirty_variable_is_integer_.empty() &&
461 dirty_linear_objective_coefficients_.empty() &&
462 dirty_quadratic_objective_coefficients_.empty() &&
463 dirty_linear_constraint_deletes_.empty() &&
464 dirty_linear_constraint_lower_bounds_.empty() &&
465 dirty_linear_constraint_upper_bounds_.empty() &&
466 dirty_linear_constraint_matrix_keys_.empty()) {
467 return std::nullopt;
468 }
469
470 // TODO(b/185608026): these are used to efficiently extract the constraint
471 // matrix update, but it would be good to avoid calling these because they
472 // result in a large allocation.
473 EnsureLazyMatrixRows();
474 EnsureLazyMatrixColumns();
475
476 ModelUpdateProto result;
477
478 // Variable/constraint deletions.
479 for (const VariableId del_var : SortedSetKeys(dirty_variable_deletes_)) {
480 result.add_deleted_variable_ids(del_var.value());
481 }
482 for (const LinearConstraintId del_lin_con :
483 SortedSetKeys(dirty_linear_constraint_deletes_)) {
484 result.add_deleted_linear_constraint_ids(del_lin_con.value());
485 }
486
487 // Update the variables.
488 auto var_updates = result.mutable_variable_updates();
489 AppendFromMap(dirty_variable_lower_bounds_, variables_,
491 *var_updates->mutable_lower_bounds());
492 AppendFromMap(dirty_variable_upper_bounds_, variables_,
494 *var_updates->mutable_upper_bounds());
495
496 for (const VariableId integer_var :
497 SortedSetKeys(dirty_variable_is_integer_)) {
498 var_updates->mutable_integers()->add_ids(integer_var.value());
499 var_updates->mutable_integers()->add_values(
500 variables_.at(integer_var).is_integer);
501 }
502 for (VariableId new_id = variables_checkpoint_; new_id < next_variable_id_;
503 ++new_id) {
504 if (variables_.contains(new_id)) {
505 AppendVariable(new_id, *result.mutable_new_variables());
506 }
507 }
508
509 // Update the objective
510 auto obj_updates = result.mutable_objective_updates();
511 if (dirty_objective_direction_) {
512 obj_updates->set_direction_update(is_maximize_);
513 }
514 if (dirty_objective_offset_) {
515 obj_updates->set_offset_update(objective_offset_);
516 }
517 AppendFromMapOrDefault<VariableId>(
518 SortedSetKeys(dirty_linear_objective_coefficients_), linear_objective_,
519 *obj_updates->mutable_linear_coefficients());
520 // TODO(b/182567749): Once StrongInt is in absl, use
521 // AppendFromMapIfPresent<VariableId>(
522 // MakeStrongIntRange(variables_checkpoint_, next_variable_id_),
523 // linear_objective_, *obj_updates->mutable_linear_coefficients());
524 for (VariableId var_id = variables_checkpoint_; var_id < next_variable_id_;
525 ++var_id) {
526 const double* const double_value =
527 gtl::FindOrNull(linear_objective_, var_id);
528 if (double_value != nullptr) {
529 obj_updates->mutable_linear_coefficients()->add_ids(var_id.value());
530 obj_updates->mutable_linear_coefficients()->add_values(*double_value);
531 }
532 }
533 // If we do not have any quadratic updates to push, we would like to avoid
534 // initializing the lazy data structures. The updates might tracked in:
535 // 1. dirty_quadratic_objective_coefficients_ (both variables old)
536 // 2. quadratic_objective_ (at least one new variable)
537 // If both maps are empty, we can skip the update and initializiation.
538 if (!quadratic_objective_.empty() ||
539 !dirty_quadratic_objective_coefficients_.empty()) {
540 EnsureLazyQuadraticObjective();
541 // NOTE: dirty_quadratic_objective_coefficients_ only tracks terms where
542 // both variables are "old".
543 std::vector<std::pair<VariableId, VariableId>> quadratic_objective_updates(
544 dirty_quadratic_objective_coefficients_.begin(),
545 dirty_quadratic_objective_coefficients_.end());
546 // Now, we loop through the "new" variables and track updates involving
547 // them. We need to look out for two things:
548 // * The "other" variable in the term can either be new or old.
549 // * We cannot doubly insert terms when both variables are new.
550 // Note that this traversal is doing at most twice as much work as
551 // necessary.
552 for (VariableId new_var = variables_checkpoint_;
553 new_var < next_variable_id_; ++new_var) {
554 if (variables_.contains(new_var)) {
555 for (const VariableId other_var :
556 lazy_quadratic_objective_by_variable_.at(new_var)) {
557 if (other_var <= new_var) {
558 quadratic_objective_updates.push_back(
559 internal::MakeOrderedPair(new_var, other_var));
560 }
561 }
562 }
563 }
564 std::sort(quadratic_objective_updates.begin(),
565 quadratic_objective_updates.end());
566 *result.mutable_objective_updates()->mutable_quadratic_coefficients() =
567 ExportMatrix(quadratic_objective_, quadratic_objective_updates);
568 }
569
570 // Update the linear constraints
571 auto lin_con_updates = result.mutable_linear_constraint_updates();
572 AppendFromMap(dirty_linear_constraint_lower_bounds_, linear_constraints_,
574 *lin_con_updates->mutable_lower_bounds());
575 AppendFromMap(dirty_linear_constraint_upper_bounds_, linear_constraints_,
577 *lin_con_updates->mutable_upper_bounds());
578
579 for (LinearConstraintId new_id = linear_constraints_checkpoint_;
580 new_id < next_linear_constraint_id_; ++new_id) {
581 if (linear_constraints_.contains(new_id)) {
582 AppendLinearConstraint(new_id, *result.mutable_new_linear_constraints());
583 }
584 }
585
586 // Extract changes to the matrix of linear constraint coefficients
587 std::vector<std::pair<LinearConstraintId, VariableId>>
588 constraint_matrix_updates(dirty_linear_constraint_matrix_keys_.begin(),
589 dirty_linear_constraint_matrix_keys_.end());
590 for (VariableId new_var = variables_checkpoint_; new_var < next_variable_id_;
591 ++new_var) {
592 if (variables_.contains(new_var)) {
593 for (const LinearConstraintId lin_con :
594 lazy_matrix_columns_.at(new_var)) {
595 constraint_matrix_updates.emplace_back(lin_con, new_var);
596 }
597 }
598 }
599 for (LinearConstraintId new_lin_con = linear_constraints_checkpoint_;
600 new_lin_con < next_linear_constraint_id_; ++new_lin_con) {
601 if (linear_constraints_.contains(new_lin_con)) {
602 for (const VariableId var : lazy_matrix_rows_.at(new_lin_con)) {
603 // NOTE(user): we will do at most twice as much as needed here.
604 if (var < variables_checkpoint_) {
605 constraint_matrix_updates.emplace_back(new_lin_con, var);
606 }
607 }
608 }
609 }
610 std::sort(constraint_matrix_updates.begin(), constraint_matrix_updates.end());
611 *result.mutable_linear_constraint_matrix_updates() =
612 ExportMatrix(linear_constraint_matrix_, constraint_matrix_updates);
613
614 // Named returned value optimization (NRVO) does not apply here since the
615 // return type if not the same type as `result`. To make things clear, we
616 // explicitly call the constructor here.
617 return {std::move(result)};
618}
619
620void ModelStorage::EnsureLazyMatrixColumns() {
621 if (lazy_matrix_columns_.empty()) {
622 for (const auto& var_pair : variables_) {
623 lazy_matrix_columns_.insert({var_pair.first, {}});
624 }
625 for (const auto& mat_entry : linear_constraint_matrix_) {
626 lazy_matrix_columns_.at(mat_entry.first.second)
627 .insert(mat_entry.first.first);
628 }
629 }
630}
631
632void ModelStorage::EnsureLazyMatrixRows() {
633 if (lazy_matrix_rows_.empty()) {
634 for (const auto& lin_con_pair : linear_constraints_) {
635 lazy_matrix_rows_.insert({lin_con_pair.first, {}});
636 }
637 for (const auto& mat_entry : linear_constraint_matrix_) {
638 lazy_matrix_rows_.at(mat_entry.first.first)
639 .insert(mat_entry.first.second);
640 }
641 }
642}
643
644void ModelStorage::EnsureLazyQuadraticObjective() {
645 if (lazy_quadratic_objective_by_variable_.empty()) {
646 for (const auto& [var, data] : variables_) {
647 lazy_quadratic_objective_by_variable_.insert({var, {}});
648 }
649 for (const auto& [vars, coeff] : quadratic_objective_) {
650 lazy_quadratic_objective_by_variable_.at(vars.first).insert(vars.second);
651 lazy_quadratic_objective_by_variable_.at(vars.second).insert(vars.first);
652 }
653 for (const auto& vars : dirty_quadratic_objective_coefficients_) {
654 lazy_quadratic_objective_by_variable_.at(vars.first).insert(vars.second);
655 lazy_quadratic_objective_by_variable_.at(vars.second).insert(vars.first);
656 }
657 }
658}
659
660void ModelStorage::SharedCheckpoint() {
661 variables_checkpoint_ = next_variable_id_;
662 linear_constraints_checkpoint_ = next_linear_constraint_id_;
663 dirty_objective_direction_ = false;
664 dirty_objective_offset_ = false;
665
666 dirty_variable_deletes_.clear();
667 dirty_variable_lower_bounds_.clear();
668 dirty_variable_upper_bounds_.clear();
669 dirty_variable_is_integer_.clear();
670
671 dirty_linear_objective_coefficients_.clear();
672 dirty_quadratic_objective_coefficients_.clear();
673
674 dirty_linear_constraint_deletes_.clear();
675 dirty_linear_constraint_lower_bounds_.clear();
676 dirty_linear_constraint_upper_bounds_.clear();
677 dirty_linear_constraint_matrix_keys_.clear();
678}
679
681 const absl::MutexLock lock(&update_trackers_lock_);
682
683 const UpdateTrackerId update_tracker = next_update_tracker_;
684 ++next_update_tracker_;
685
686 CHECK(update_trackers_
687 .try_emplace(update_tracker, std::make_unique<UpdateTrackerData>())
688 .second);
689
690 CheckpointLocked(update_tracker);
691
692 return update_tracker;
693}
694
695void ModelStorage::DeleteUpdateTracker(const UpdateTrackerId update_tracker) {
696 const absl::MutexLock lock(&update_trackers_lock_);
697 const auto found = update_trackers_.find(update_tracker);
698 CHECK(found != update_trackers_.end())
699 << "Update tracker " << update_tracker << " does not exist";
700 update_trackers_.erase(found);
701}
702
703std::optional<ModelUpdateProto> ModelStorage::ExportModelUpdate(
704 const UpdateTrackerId update_tracker) {
705 const absl::MutexLock lock(&update_trackers_lock_);
706
707 const auto found_data = update_trackers_.find(update_tracker);
708 CHECK(found_data != update_trackers_.end())
709 << "Update tracker " << update_tracker << " does not exist";
710 const std::unique_ptr<UpdateTrackerData>& data = found_data->second;
711
712 // No updates have been pushed, the checkpoint of this tracker is in sync with
713 // the shared checkpoint of ModelStorage. We can return the ModelStorage
714 // shared update without merging.
715 if (data->updates.empty()) {
716 return ExportSharedModelUpdate();
717 }
718
719 // Find all trackers with the same checkpoint. By construction, all trackers
720 // that have the same first update also share all next updates.
721 std::vector<UpdateTrackerData*> all_trackers_at_checkpoint;
722 for (const auto& [other_id, other_data] : update_trackers_) {
723 if (!other_data->updates.empty() &&
724 other_data->updates.front() == data->updates.front()) {
725 all_trackers_at_checkpoint.push_back(other_data.get());
726
727 // Validate that we have the same updates in debug mode only. In optimized
728 // mode, only test the size of the updates vectors.
729 CHECK_EQ(data->updates.size(), other_data->updates.size());
730 if (DEBUG_MODE) {
731 for (int i = 0; i < data->updates.size(); ++i) {
732 CHECK_EQ(data->updates[i], other_data->updates[i])
733 << "Two trackers have the same checkpoint but different updates.";
734 }
735 }
736 }
737 }
738
739 // Possible optimizations here:
740 //
741 // * Maybe optimize the case where the first update is singly used by `this`
742 // and use it as starting point instead of making a copy. This may be more
743 // complicated if it is shared with multiple trackers since in that case we
744 // must make sure to only update the shared instance if and only if only
745 // trackers have a pointer to it, not external code (i.e. its use count is
746 // the same as the number of trackers).
747 //
748 // * Use n-way merge here if the performances justify it.
749 const auto merge = std::make_shared<ModelUpdateProto>();
750 for (const auto& update : data->updates) {
751 MergeIntoUpdate(/*from_new=*/*update, /*into_old=*/*merge);
752 }
753
754 // Push the merge to all trackers that have the same checkpoint (including
755 // this tracker).
756 for (UpdateTrackerData* const other_data : all_trackers_at_checkpoint) {
757 other_data->updates.clear();
758 other_data->updates.push_back(merge);
759 }
760
761 ModelUpdateProto update = *merge;
762 const std::optional<ModelUpdateProto> pending_update =
763 ExportSharedModelUpdate();
764 if (pending_update) {
765 MergeIntoUpdate(/*from_new=*/*pending_update, /*into_old=*/update);
766 }
767
768 // Named returned value optimization (NRVO) does not apply here since the
769 // return type if not the same type as `result`. To make things clear, we
770 // explicitly call the constructor here.
771 return {std::move(update)};
772}
773
774void ModelStorage::Checkpoint(const UpdateTrackerId update_tracker) {
775 const absl::MutexLock lock(&update_trackers_lock_);
776
777 CheckpointLocked(update_tracker);
778}
779
780void ModelStorage::CheckpointLocked(const UpdateTrackerId update_tracker) {
781 const auto found_data = update_trackers_.find(update_tracker);
782 CHECK(found_data != update_trackers_.end())
783 << "Update tracker " << update_tracker << " does not exist";
784 const std::unique_ptr<UpdateTrackerData>& data = found_data->second;
785
786 // Optimize the case where we have a single tracker and we don't want to
787 // update it. In that case we don't need to update trackers since we would
788 // only update this one and clear it immediately.
789 if (update_trackers_.size() > 1) {
790 std::optional<ModelUpdateProto> update = ExportSharedModelUpdate();
791 if (update) {
792 const auto shared_update =
793 std::make_shared<ModelUpdateProto>(*std::move(update));
794
795 for (const auto& [other_id, other_data] : update_trackers_) {
796 other_data->updates.push_back(shared_update);
797 }
798 }
799 }
800 SharedCheckpoint();
801 data->updates.clear();
802}
803
805 const ModelUpdateProto& update_proto) {
806 // Check the update first.
807 {
808 ModelSummary summary;
809 // We have to use sorted keys since IdNameBiMap expect Insert() to be called
810 // in sorted order.
811 for (const auto id : SortedVariables()) {
812 summary.variables.Insert(id.value(), variable_name(id));
813 }
814 summary.variables.SetNextFreeId(next_variable_id_.value());
815 for (const auto id : SortedLinearConstraints()) {
817 }
819 next_linear_constraint_id_.value());
820 // We don't check the names for the same reason as in FromModelProto().
822 /*check_names=*/false));
823 }
824
825 // Remove deleted variables and constraints.
826 for (const int64_t v_id : update_proto.deleted_variable_ids()) {
827 DeleteVariable(VariableId(v_id));
828 }
829 for (const int64_t c_id : update_proto.deleted_linear_constraint_ids()) {
830 DeleteLinearConstraint(LinearConstraintId(c_id));
831 }
832
833 // Update existing variables' properties.
834 for (const auto [v_id, lb] :
835 MakeView(update_proto.variable_updates().lower_bounds())) {
836 set_variable_lower_bound(VariableId(v_id), lb);
837 }
838 for (const auto [v_id, ub] :
839 MakeView(update_proto.variable_updates().upper_bounds())) {
840 set_variable_upper_bound(VariableId(v_id), ub);
841 }
842 for (const auto [v_id, is_integer] :
843 MakeView(update_proto.variable_updates().integers())) {
844 set_variable_is_integer(VariableId(v_id), is_integer);
845 }
846
847 // Update existing constraints' properties.
848 for (const auto [c_id, lb] :
849 MakeView(update_proto.linear_constraint_updates().lower_bounds())) {
850 set_linear_constraint_lower_bound(LinearConstraintId(c_id), lb);
851 }
852 for (const auto [c_id, ub] :
853 MakeView(update_proto.linear_constraint_updates().upper_bounds())) {
854 set_linear_constraint_upper_bound(LinearConstraintId(c_id), ub);
855 }
856
857 // Add the new variables and constraints.
858 AddVariables(update_proto.new_variables());
859 AddLinearConstraints(update_proto.new_linear_constraints());
860
861 // Update the objective.
862 if (update_proto.objective_updates().has_direction_update()) {
863 set_is_maximize(update_proto.objective_updates().direction_update());
864 }
865 if (update_proto.objective_updates().has_offset_update()) {
866 set_objective_offset(update_proto.objective_updates().offset_update());
867 }
868 UpdateLinearObjectiveCoefficients(
869 update_proto.objective_updates().linear_coefficients());
870 UpdateQuadraticObjectiveCoefficients(
871 update_proto.objective_updates().quadratic_coefficients());
872
873 // Update the linear constraints' coefficients.
874 UpdateLinearConstraintCoefficients(
875 update_proto.linear_constraint_matrix_updates());
876
877 return absl::OkStatus();
878}
879
880} // namespace math_opt
881} // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GE(val1, val2)
Definition: base/logging.h:707
#define CHECK_GT(val1, val2)
Definition: base/logging.h:708
#define CHECK_OK(x)
Definition: base/logging.h:44
#define CHECK_LE(val1, val2)
Definition: base/logging.h:705
#define RETURN_IF_ERROR(expr)
void SetNextFreeId(int64_t new_next_free_id)
void Insert(int64_t id, std::string name)
void set_quadratic_objective_coefficient(VariableId first_variable, VariableId second_variable, double value)
std::vector< LinearConstraintId > linear_constraints() const
std::vector< VariableId > SortedVariables() const
static absl::StatusOr< std::unique_ptr< ModelStorage > > FromModelProto(const ModelProto &model_proto)
std::vector< VariableId > SortedLinearObjectiveNonzeroVariables() const
void DeleteLinearConstraint(LinearConstraintId id)
void set_linear_objective_coefficient(VariableId variable, double value)
void DeleteUpdateTracker(UpdateTrackerId update_tracker)
std::vector< VariableId > variables() const
absl::Status ApplyUpdateProto(const ModelUpdateProto &update_proto)
std::optional< ModelUpdateProto > ExportModelUpdate(UpdateTrackerId update_tracker)
void set_linear_constraint_coefficient(LinearConstraintId constraint, VariableId variable, double value)
VariableId AddVariable(absl::string_view name="")
void set_variable_upper_bound(VariableId id, double upper_bound)
void set_variable_is_integer(VariableId id, bool is_integer)
const std::string & variable_name(VariableId id) const
void set_linear_constraint_upper_bound(LinearConstraintId id, double upper_bound)
void Checkpoint(UpdateTrackerId update_tracker)
void set_linear_constraint_lower_bound(LinearConstraintId id, double lower_bound)
const std::string & linear_constraint_name(LinearConstraintId id) const
std::unique_ptr< ModelStorage > Clone() const
LinearConstraintId AddLinearConstraint(absl::string_view name="")
std::vector< LinearConstraintId > SortedLinearConstraints() const
void set_variable_lower_bound(VariableId id, double lower_bound)
CpModelProto const * model_proto
const std::string name
int64_t value
IntVar * var
Definition: expr_array.cc:1874
double upper_bound
double lower_bound
absl::Span< const double > coefficients
const bool DEBUG_MODE
Definition: macros.h:24
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:154
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
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:60
std::pair< VariableId, VariableId > MakeOrderedPair(const VariableId a, const VariableId b)
absl::Status ValidateModelUpdateAndSummary(const ModelUpdateProto &model_update, const ModelSummary &model_summary, const bool check_names)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
void MergeIntoUpdate(const ModelUpdateProto &from_new, ModelUpdateProto &into_old)
absl::Status ValidateModel(const ModelProto &model, const bool check_names)
Collection of objects used to extend the Constraint Solver library.
const double coeff