OR-Tools  9.3
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
22#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>
59 from_deleted_and_into_new_variable_ids = 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
217 const google::protobuf::RepeatedField<int64_t>& from_new,
218 google::protobuf::RepeatedField<int64_t>& into_old,
219 const google::protobuf::RepeatedField<int64_t>& deleted) {
220 google::protobuf::RepeatedField<int64_t> result;
221
222 int from_new_i = 0;
223 int into_old_i = 0;
224 int deleted_i = 0;
225
226 // Functions that adds the input id to the result if it is not in deleted. It
227 // updates deleted_i as a side effect too.
228 const auto add_if_not_deleted = [&](const int64_t id) {
229 while (deleted_i < deleted.size() && deleted[deleted_i] < id) {
230 ++deleted_i;
231 }
232 if (deleted_i == deleted.size() || deleted[deleted_i] != id) {
233 result.Add(id);
234 }
235 };
236
237 while (from_new_i < from_new.size() && into_old_i < into_old.size()) {
238 if (from_new[from_new_i] < into_old[into_old_i]) {
239 add_if_not_deleted(from_new[from_new_i]);
240 ++from_new_i;
241 } else if (from_new[from_new_i] > into_old[into_old_i]) {
242 add_if_not_deleted(into_old[into_old_i]);
243 ++into_old_i;
244 } else { // from_new[from_new_i] == into_old[into_old_i]
245 add_if_not_deleted(from_new[from_new_i]);
246 ++from_new_i;
247 ++into_old_i;
248 }
249 }
250
251 // At this point either from_new_i == from_new.size() or to_i == to.size() or
252 // both. And the one that is not empty, if it exists, has elements greater
253 // than all other elements already inserted.
254 for (; from_new_i < from_new.size(); ++from_new_i) {
255 add_if_not_deleted(from_new[from_new_i]);
256 }
257 for (; into_old_i < into_old.size(); ++into_old_i) {
258 add_if_not_deleted(into_old[into_old_i]);
259 }
260
261 into_old.Swap(&result);
262}
263
265 const SparseDoubleMatrixProto& from_new, SparseDoubleMatrixProto& into_old,
266 const google::protobuf::RepeatedField<int64_t>& deleted_rows,
267 const google::protobuf::RepeatedField<int64_t>& deleted_columns) {
268 SparseDoubleMatrixProto result;
269 auto& result_row_ids = *result.mutable_row_ids();
270 auto& result_column_ids = *result.mutable_column_ids();
271 auto& result_coefficients = *result.mutable_coefficients();
272
273 // Contrary to rows that are traversed in order (the matrix is using row-major
274 // order), columns are not. Thus we would have to start the iteration on
275 // deleted_columns for each new row of the matrix if we wanted to use the same
276 // approach as with rows. This would be O(num_rows * num_deleted_columns).
277 //
278 // Here we use a hash-set to be O(num_matrix_elements +
279 // num_deleted_columns). The downside is that we consumed
280 // O(num_deleted_columns) additional memory.
281 //
282 // We could have used binary search that would be O(num_matrix_elements *
283 // lg(num_deleted_columns)) but without additional memory.
284 const absl::flat_hash_set<int64_t> deleted_columns_set(
285 deleted_columns.begin(), deleted_columns.end());
286
287 int from_new_i = 0;
288 int into_old_i = 0;
289 int deleted_rows_i = 0;
290
291 // Functions that adds the input tuple (row_id, col_id, coefficient) to the
292 // result if the input row_id and col_id are not in deleted_rows or
293 // deleted_columns. It updates deleted_rows_i and deleted_columns_i as a side
294 // effect too.
295 const auto add_if_not_deleted = [&](const int64_t row_id,
296 const int64_t col_id,
297 const double coefficient) {
298 while (deleted_rows_i < deleted_rows.size() &&
299 deleted_rows[deleted_rows_i] < row_id) {
300 ++deleted_rows_i;
301 }
302 if ((deleted_rows_i != deleted_rows.size() &&
303 deleted_rows[deleted_rows_i] == row_id) ||
304 deleted_columns_set.contains(col_id)) {
305 return;
306 }
307 result_row_ids.Add(row_id);
308 result_column_ids.Add(col_id);
309 result_coefficients.Add(coefficient);
310 };
311
312 while (from_new_i < from_new.row_ids_size() &&
313 into_old_i < into_old.row_ids_size()) {
314 // Matrices are in row-major order and std::pair comparison is
315 // lexicographical, thus matrices are sorted in the natural order of pairs
316 // of coordinates (row, col).
317 const auto from_new_coordinates = std::make_pair(
318 from_new.row_ids(from_new_i), from_new.column_ids(from_new_i));
319 const auto into_old_coordinates = std::make_pair(
320 into_old.row_ids(into_old_i), into_old.column_ids(into_old_i));
321 if (from_new_coordinates < into_old_coordinates) {
322 add_if_not_deleted(from_new_coordinates.first,
323 from_new_coordinates.second,
324 from_new.coefficients(from_new_i));
325 ++from_new_i;
326 } else if (from_new_coordinates > into_old_coordinates) {
327 add_if_not_deleted(into_old_coordinates.first,
328 into_old_coordinates.second,
329 into_old.coefficients(into_old_i));
330 ++into_old_i;
331 } else { // from_new_coordinates == into_old_coordinates
332 add_if_not_deleted(from_new_coordinates.first,
333 from_new_coordinates.second,
334 from_new.coefficients(from_new_i));
335 ++from_new_i;
336 ++into_old_i;
337 }
338 }
339
340 // At this point either from_new_i == from_new.row_ids_size() or
341 // to_i == to.row_ids_size() (or both). And the one that is not empty, if it
342 // exists, has elements greater than all other elements already inserted.
343 for (; from_new_i < from_new.row_ids_size(); ++from_new_i) {
344 add_if_not_deleted(from_new.row_ids(from_new_i),
345 from_new.column_ids(from_new_i),
346 from_new.coefficients(from_new_i));
347 }
348 for (; into_old_i < into_old.row_ids_size(); ++into_old_i) {
349 add_if_not_deleted(into_old.row_ids(into_old_i),
350 into_old.column_ids(into_old_i),
351 into_old.coefficients(into_old_i));
352 }
353
354 into_old.Swap(&result);
355}
356
357} // namespace internal
358} // namespace math_opt
359} // namespace operations_research
#define CHECK_GT(val1, val2)
Definition: base/logging.h:708
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