OR-Tools  9.2
model_update_merge.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 <iterator>
19#include <string>
20#include <utility>
21
24#include "absl/container/flat_hash_set.h"
26#include "ortools/math_opt/model.pb.h"
27#include "ortools/math_opt/model_update.pb.h"
28#include "ortools/math_opt/sparse_containers.pb.h"
29
30namespace operations_research {
31namespace math_opt {
32
33void MergeIntoUpdate(const ModelUpdateProto& from_new,
34 ModelUpdateProto& into_old) {
35 // Merge the deleted variables. Note that we remove from the merge the
36 // variables that were created in `into_old`. Below we will simply remove
37 // those variables from the list of new variables in the merge; thus making
38 // the update as if those variables never existed.
39 internal::MergeIntoSortedIds(from_new.deleted_variable_ids(),
40 *into_old.mutable_deleted_variable_ids(),
41 /*deleted=*/into_old.new_variables().ids());
43 from_new.deleted_linear_constraint_ids(),
44 *into_old.mutable_deleted_linear_constraint_ids(),
45 /*deleted=*/into_old.new_linear_constraints().ids());
46
47 // For variables and linear constraints updates, we want to ignore updates of:
48 //
49 // 1. variable or linear constraints deleted in `from_new` (that could have
50 // been updated in `into_old`).
51 //
52 // 2. variable or linear constraints created in `into_old`. For those the code
53 // of UpdateNewElementProperty() will use the new value directly as the
54 // value of the created variable.
55 //
56 // Thus we create here the list of indices to ignore when filtering updates
57 // for both variables and linear constraints.
58 google::protobuf::RepeatedField<int64_t> from_deleted_and_into_new_variable_ids =
59 from_new.deleted_variable_ids();
60 from_deleted_and_into_new_variable_ids.MergeFrom(
61 into_old.new_variables().ids());
62
63 google::protobuf::RepeatedField<int64_t>
64 from_deleted_and_into_new_linear_constraint_ids =
65 from_new.deleted_linear_constraint_ids();
66 from_deleted_and_into_new_linear_constraint_ids.MergeFrom(
67 into_old.new_linear_constraints().ids());
68
69 // Merge updates of variable properties.
71 from_new.variable_updates().lower_bounds(),
72 *into_old.mutable_variable_updates()->mutable_lower_bounds(),
73 from_deleted_and_into_new_variable_ids);
75 from_new.variable_updates().upper_bounds(),
76 *into_old.mutable_variable_updates()->mutable_upper_bounds(),
77 from_deleted_and_into_new_variable_ids);
79 from_new.variable_updates().integers(),
80 *into_old.mutable_variable_updates()->mutable_integers(),
81 from_deleted_and_into_new_variable_ids);
82
83 // Merge updates of linear constraints properties.
85 from_new.linear_constraint_updates().lower_bounds(),
86 *into_old.mutable_linear_constraint_updates()->mutable_lower_bounds(),
87 from_deleted_and_into_new_linear_constraint_ids);
89 from_new.linear_constraint_updates().upper_bounds(),
90 *into_old.mutable_linear_constraint_updates()->mutable_upper_bounds(),
91 from_deleted_and_into_new_linear_constraint_ids);
92
93 // Merge new variables.
94 //
95 // The merge occurs in two steps:
96 //
97 // 1. For each property we remove from the merge the new variables from
98 // `into_old` that are removed in `from_new` since those don't have to
99 // exist. The code above has removed those from the deleted set to).
100 //
101 // We also update the value of the property to the one of its update in
102 // `from_new` if it exists. The code above has removed those updates
103 // already.
104 //
105 // 2. We append all new variables of `from_new` at once by using MergeFrom()
106 // on the VariablesProto. No merges are needed for those since they can't
107 // have been know by `into_old`.
108 if (!from_new.new_variables().ids().empty() &&
109 !into_old.new_variables().ids().empty()) {
110 CHECK_GT(*from_new.new_variables().ids().begin(),
111 *into_old.new_variables().ids().rbegin());
112 }
114 /*ids=*/into_old.new_variables().ids(),
115 /*values=*/*into_old.mutable_new_variables()->mutable_lower_bounds(),
116 /*deleted=*/from_new.deleted_variable_ids(),
117 /*updates=*/from_new.variable_updates().lower_bounds());
119 /*ids=*/into_old.new_variables().ids(),
120 /*values=*/*into_old.mutable_new_variables()->mutable_upper_bounds(),
121 /*deleted=*/from_new.deleted_variable_ids(),
122 /*updates=*/from_new.variable_updates().upper_bounds());
124 /*ids=*/into_old.new_variables().ids(),
125 /*values=*/*into_old.mutable_new_variables()->mutable_integers(),
126 /*deleted=*/from_new.deleted_variable_ids(),
127 /*updates=*/from_new.variable_updates().integers());
129 /*ids=*/into_old.new_variables().ids(),
130 /*values=*/*into_old.mutable_new_variables()->mutable_names(),
131 /*deleted=*/from_new.deleted_variable_ids(),
132 // We use an empty view here since names can't be updated.
133 /*updates=*/SparseVectorView<std::string>());
135 /*ids=*/*into_old.mutable_new_variables()->mutable_ids(),
136 /*deleted=*/from_new.deleted_variable_ids());
137 into_old.mutable_new_variables()->MergeFrom(from_new.new_variables());
138
139 // Merge of new linear constraints. The algorithm is similar to variables; see
140 // comment above for details.
141 if (!from_new.new_linear_constraints().ids().empty() &&
142 !into_old.new_linear_constraints().ids().empty()) {
143 CHECK_GT(*from_new.new_linear_constraints().ids().begin(),
144 *into_old.new_linear_constraints().ids().rbegin());
145 }
147 /*ids=*/into_old.new_linear_constraints().ids(),
148 /*values=*/
149 *into_old.mutable_new_linear_constraints()->mutable_lower_bounds(),
150 /*deleted=*/from_new.deleted_linear_constraint_ids(),
151 /*updates=*/from_new.linear_constraint_updates().lower_bounds());
153 /*ids=*/into_old.new_linear_constraints().ids(),
154 /*values=*/
155 *into_old.mutable_new_linear_constraints()->mutable_upper_bounds(),
156 /*deleted=*/from_new.deleted_linear_constraint_ids(),
157 /*updates=*/from_new.linear_constraint_updates().upper_bounds());
159 /*ids=*/into_old.new_linear_constraints().ids(),
160 /*values=*/*into_old.mutable_new_linear_constraints()->mutable_names(),
161 /*deleted=*/from_new.deleted_linear_constraint_ids(),
162 // We use an empty view here since names can't be updated.
163 /*updates=*/SparseVectorView<std::string>());
165 /*ids=*/*into_old.mutable_new_linear_constraints()->mutable_ids(),
166 /*deleted=*/from_new.deleted_linear_constraint_ids());
167 into_old.mutable_new_linear_constraints()->MergeFrom(
168 from_new.new_linear_constraints());
169
170 // Merge the objective.
171 if (from_new.objective_updates().has_direction_update()) {
172 into_old.mutable_objective_updates()->set_direction_update(
173 from_new.objective_updates().direction_update());
174 }
175 if (from_new.objective_updates().has_offset_update()) {
176 into_old.mutable_objective_updates()->set_offset_update(
177 from_new.objective_updates().offset_update());
178 }
180 from_new.objective_updates().linear_coefficients(),
181 *into_old.mutable_objective_updates()->mutable_linear_coefficients(),
182 from_new.deleted_variable_ids());
184 from_new.objective_updates().quadratic_coefficients(),
185 *into_old.mutable_objective_updates()->mutable_quadratic_coefficients(),
186 /*deleted_rows=*/from_new.deleted_variable_ids(),
187 /*deleted_columns=*/from_new.deleted_variable_ids());
188
189 // Merge the linear constraints coefficients.
191 from_new.linear_constraint_matrix_updates(),
192 *into_old.mutable_linear_constraint_matrix_updates(),
193 /*deleted_rows=*/from_new.deleted_linear_constraint_ids(),
194 /*deleted_columns=*/from_new.deleted_variable_ids());
195}
196
197namespace internal {
198
199void RemoveDeletedIds(google::protobuf::RepeatedField<int64_t>& ids,
200 const google::protobuf::RepeatedField<int64_t>& deleted) {
201 int next_insertion_point = 0;
202 int deleted_i = 0;
203 for (const int64_t id : ids) {
204 while (deleted_i < deleted.size() && deleted[deleted_i] < id) {
205 ++deleted_i;
206 }
207 if (deleted_i < deleted.size() && deleted[deleted_i] == id) {
208 continue;
209 }
210 ids[next_insertion_point] = id;
211 ++next_insertion_point;
212 }
213 ids.Truncate(next_insertion_point);
214}
215
216void MergeIntoSortedIds(const google::protobuf::RepeatedField<int64_t>& from_new,
217 google::protobuf::RepeatedField<int64_t>& into_old,
218 const google::protobuf::RepeatedField<int64_t>& deleted) {
219 google::protobuf::RepeatedField<int64_t> result;
220
221 int from_new_i = 0;
222 int into_old_i = 0;
223 int deleted_i = 0;
224
225 // Functions that adds the input id to the result if it is not in deleted. It
226 // updates deleted_i as a side effect too.
227 const auto add_if_not_deleted = [&](const int64_t id) {
228 while (deleted_i < deleted.size() && deleted[deleted_i] < id) {
229 ++deleted_i;
230 }
231 if (deleted_i == deleted.size() || deleted[deleted_i] != id) {
232 result.Add(id);
233 }
234 };
235
236 while (from_new_i < from_new.size() && into_old_i < into_old.size()) {
237 if (from_new[from_new_i] < into_old[into_old_i]) {
238 add_if_not_deleted(from_new[from_new_i]);
239 ++from_new_i;
240 } else if (from_new[from_new_i] > into_old[into_old_i]) {
241 add_if_not_deleted(into_old[into_old_i]);
242 ++into_old_i;
243 } else { // from_new[from_new_i] == into_old[into_old_i]
244 add_if_not_deleted(from_new[from_new_i]);
245 ++from_new_i;
246 ++into_old_i;
247 }
248 }
249
250 // At this point either from_new_i == from_new.size() or to_i == to.size() or
251 // both. And the one that is not empty, if it exists, has elements greater
252 // than all other elements already inserted.
253 for (; from_new_i < from_new.size(); ++from_new_i) {
254 add_if_not_deleted(from_new[from_new_i]);
255 }
256 for (; into_old_i < into_old.size(); ++into_old_i) {
257 add_if_not_deleted(into_old[into_old_i]);
258 }
259
260 into_old.Swap(&result);
261}
262
264 const SparseDoubleMatrixProto& from_new, SparseDoubleMatrixProto& into_old,
265 const google::protobuf::RepeatedField<int64_t>& deleted_rows,
266 const google::protobuf::RepeatedField<int64_t>& deleted_columns) {
267 SparseDoubleMatrixProto result;
268 auto& result_row_ids = *result.mutable_row_ids();
269 auto& result_column_ids = *result.mutable_column_ids();
270 auto& result_coefficients = *result.mutable_coefficients();
271
272 // Contrary to rows that are traversed in order (the matrix is using row-major
273 // order), columns are not. Thus we would have to start the iteration on
274 // deleted_columns for each new row of the matrix if we wanted to use the same
275 // approach as with rows. This would be O(num_rows * num_deleted_columns).
276 //
277 // Here we use a hash-set to be O(num_matrix_elements +
278 // num_deleted_columns). The downside is that we consumed
279 // O(num_deleted_columns) additional memory.
280 //
281 // We could have used binary search that would be O(num_matrix_elements *
282 // lg(num_deleted_columns)) but without additional memory.
283 const absl::flat_hash_set<int64_t> deleted_columns_set(
284 deleted_columns.begin(), deleted_columns.end());
285
286 int from_new_i = 0;
287 int into_old_i = 0;
288 int deleted_rows_i = 0;
289
290 // Functions that adds the input tuple (row_id, col_id, coefficient) to the
291 // result if the input row_id and col_id are not in deleted_rows or
292 // deleted_columns. It updates deleted_rows_i and deleted_columns_i as a side
293 // effect too.
294 const auto add_if_not_deleted = [&](const int64_t row_id,
295 const int64_t col_id,
296 const double coefficient) {
297 while (deleted_rows_i < deleted_rows.size() &&
298 deleted_rows[deleted_rows_i] < row_id) {
299 ++deleted_rows_i;
300 }
301 if ((deleted_rows_i != deleted_rows.size() &&
302 deleted_rows[deleted_rows_i] == row_id) ||
303 deleted_columns_set.contains(col_id)) {
304 return;
305 }
306 result_row_ids.Add(row_id);
307 result_column_ids.Add(col_id);
308 result_coefficients.Add(coefficient);
309 };
310
311 while (from_new_i < from_new.row_ids_size() &&
312 into_old_i < into_old.row_ids_size()) {
313 // Matrices are in row-major order and std::pair comparison is
314 // lexicographical, thus matrices are sorted in the natural order of pairs
315 // of coordinates (row, col).
316 const auto from_new_coordinates = std::make_pair(
317 from_new.row_ids(from_new_i), from_new.column_ids(from_new_i));
318 const auto into_old_coordinates = std::make_pair(
319 into_old.row_ids(into_old_i), into_old.column_ids(into_old_i));
320 if (from_new_coordinates < into_old_coordinates) {
321 add_if_not_deleted(from_new_coordinates.first,
322 from_new_coordinates.second,
323 from_new.coefficients(from_new_i));
324 ++from_new_i;
325 } else if (from_new_coordinates > into_old_coordinates) {
326 add_if_not_deleted(into_old_coordinates.first,
327 into_old_coordinates.second,
328 into_old.coefficients(into_old_i));
329 ++into_old_i;
330 } else { // from_new_coordinates == into_old_coordinates
331 add_if_not_deleted(from_new_coordinates.first,
332 from_new_coordinates.second,
333 from_new.coefficients(from_new_i));
334 ++from_new_i;
335 ++into_old_i;
336 }
337 }
338
339 // At this point either from_new_i == from_new.row_ids_size() or
340 // to_i == to.row_ids_size() (or both). And the one that is not empty, if it
341 // exists, has elements greater than all other elements already inserted.
342 for (; from_new_i < from_new.row_ids_size(); ++from_new_i) {
343 add_if_not_deleted(from_new.row_ids(from_new_i),
344 from_new.column_ids(from_new_i),
345 from_new.coefficients(from_new_i));
346 }
347 for (; into_old_i < into_old.row_ids_size(); ++into_old_i) {
348 add_if_not_deleted(into_old.row_ids(into_old_i),
349 into_old.column_ids(into_old_i),
350 into_old.coefficients(into_old_i));
351 }
352
353 into_old.Swap(&result);
354}
355
356} // namespace internal
357} // namespace math_opt
358} // namespace operations_research
#define CHECK_GT(val1, val2)
Definition: base/logging.h:707
void MergeIntoSparseDoubleMatrix(const SparseDoubleMatrixProto &from_new, SparseDoubleMatrixProto &into_old, const google::protobuf::RepeatedField< int64_t > &deleted_rows, const google::protobuf::RepeatedField< int64_t > &deleted_columns)
void MergeIntoSortedIds(const google::protobuf::RepeatedField< int64_t > &from_new, google::protobuf::RepeatedField< int64_t > &into_old, const google::protobuf::RepeatedField< int64_t > &deleted)
void UpdateNewElementProperty(const google::protobuf::RepeatedField< int64_t > &ids, RepeatedField &values, const google::protobuf::RepeatedField< int64_t > &deleted, const SparseVector &updates)
void RemoveDeletedIds(google::protobuf::RepeatedField< int64_t > &ids, const google::protobuf::RepeatedField< int64_t > &deleted)
void MergeIntoSparseVector(const SparseVector &from_new, SparseVector &into_old, const google::protobuf::RepeatedField< int64_t > &deleted)
void MergeIntoUpdate(const ModelUpdateProto &from_new, ModelUpdateProto &into_old)
Collection of objects used to extend the Constraint Solver library.
int64_t coefficient