Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
elemental_from_proto.cc
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#include <cstdint>
15#include <string>
16#include <utility>
17#include <vector>
18
19#include "absl/algorithm/container.h"
20#include "absl/log/log.h"
21#include "absl/status/status.h"
22#include "absl/status/statusor.h"
23#include "absl/strings/string_view.h"
24#include "google/protobuf/map.h"
25#include "google/protobuf/repeated_field.h"
26#include "google/protobuf/repeated_ptr_field.h"
35#include "ortools/math_opt/model.pb.h"
36#include "ortools/math_opt/model_update.pb.h"
37#include "ortools/math_opt/sparse_containers.pb.h"
39
41namespace {
42
43absl::string_view SafeName(
44 const google::protobuf::RepeatedPtrField<std::string>& names,
45 const int index) {
46 if (index >= names.size() || index < 0) {
47 return "";
48 }
49 return names[index];
50}
51
52template <ElementType e>
53ElementId<e> SafeAddElement(const int64_t id, const absl::string_view name,
54 Elemental& elemental) {
55 elemental.EnsureNextElementIdAtLeastUntyped(e, id);
56 return elemental.AddElement<e>(name);
57}
58
59template <typename T>
60std::vector<std::pair<int64_t, const T*>> SortMapByKey(
61 const google::protobuf::Map<int64_t, T>& proto_map) {
62 std::vector<std::pair<int64_t, const T*>> result;
63 result.reserve(proto_map.size());
64 for (const auto& [id, val] : proto_map) {
65 result.push_back({id, &val});
66 }
67 absl::c_sort(result);
68 return result;
69}
70
71void AddVariables(const VariablesProto& variables, Elemental& elemental) {
72 for (int i = 0; i < variables.ids_size(); ++i) {
73 const VariableId id = SafeAddElement<ElementType::kVariable>(
74 variables.ids(i), SafeName(variables.names(), i), elemental);
75 elemental.SetAttr<Elemental::UBPolicy>(BoolAttr1::kVarInteger, AttrKey(id),
76 variables.integers(i));
77 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kVarLb, AttrKey(id),
78 variables.lower_bounds(i));
79 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kVarUb, AttrKey(id),
80 variables.upper_bounds(i));
81 }
82}
83
84void AddLinearConstraints(const LinearConstraintsProto& linear_constraints,
85 Elemental& elemental) {
86 for (int i = 0; i < linear_constraints.ids_size(); ++i) {
87 const LinearConstraintId id =
88 SafeAddElement<ElementType::kLinearConstraint>(
89 linear_constraints.ids(i), SafeName(linear_constraints.names(), i),
90 elemental);
91 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kLinConLb, AttrKey(id),
92 linear_constraints.lower_bounds(i));
93 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kLinConUb, AttrKey(id),
94 linear_constraints.upper_bounds(i));
95 }
96}
97
98void SetDoubleAttr1FromProto(const DoubleAttr1 attr,
99 const SparseDoubleVectorProto& vec,
100 Elemental& elemental) {
101 for (int i = 0; i < vec.ids_size(); ++i) {
102 elemental.SetAttr<Elemental::UBPolicy>(attr, AttrKey(vec.ids(i)),
103 vec.values(i));
104 }
105}
106
107void SetBoolAttr1FromProto(const BoolAttr1 attr,
108 const SparseBoolVectorProto& vec,
109 Elemental& elemental) {
110 for (int i = 0; i < vec.ids_size(); ++i) {
111 elemental.SetAttr<Elemental::UBPolicy>(attr, AttrKey(vec.ids(i)),
112 vec.values(i));
113 }
114}
115
116// Below, AttrType can be DoubleAttr2 or SymmetricDoubleAttr2.
117template <typename AttrType>
118void SetDoubleAttr2FromProto(const AttrType attr,
119 const SparseDoubleMatrixProto& mat,
120 Elemental& elemental) {
121 static_assert(GetAttrKeySize<AttrType>() == 2,
122 "Attribute must have key size two");
123 static_assert(
124 std::is_same_v<double, typename AttrTypeDescriptorT<AttrType>::ValueType>,
125 "Attribute must be double valued");
126 for (int i = 0; i < mat.row_ids_size(); ++i) {
127 elemental.SetAttr<Elemental::UBPolicy>(
128 attr, AttrKeyFor<AttrType>(mat.row_ids(i), mat.column_ids(i)),
129 mat.coefficients(i));
130 }
131}
132
133// Below, AttrType can be DoubleAttr2 or SymmetricDoubleAttr2.
134template <typename AttrType>
135void SetDoubleAttr2SliceFromProto(const AttrType attr, const int64_t first_id,
136 const SparseDoubleVectorProto& slice,
137 Elemental& elemental) {
138 static_assert(GetAttrKeySize<AttrType>() == 2,
139 "Attribute must have key size two");
140 static_assert(
141 std::is_same_v<double, typename AttrTypeDescriptorT<AttrType>::ValueType>,
142 "Attribute must be double valued");
143 for (int i = 0; i < slice.ids_size(); ++i) {
144 elemental.SetAttr<Elemental::UBPolicy>(
145 attr, AttrKey(first_id, slice.ids(i)), slice.values(i));
146 }
147}
148
149// Below, AttrType can be DoubleAttr3 or SymmetricDoubleAttr3.
150template <typename AttrType>
151void SetDoubleAttr3SliceFromProto(const AttrType attr, const int64_t first_id,
152 const SparseDoubleMatrixProto& slice,
153 Elemental& elemental) {
154 static_assert(GetAttrKeySize<AttrType>() == 3,
155 "Attribute must have key size three");
156 static_assert(
157 std::is_same_v<double, typename AttrTypeDescriptorT<AttrType>::ValueType>,
158 "Attribute must be double valued");
159 for (int i = 0; i < slice.row_ids_size(); ++i) {
160 elemental.SetAttr<Elemental::UBPolicy>(
161 attr,
162 AttrKeyFor<AttrType>(first_id, slice.row_ids(i), slice.column_ids(i)),
163 slice.coefficients(i));
164 }
165}
166
167absl::Status SetAuxiliaryObjectives(
168 const google::protobuf::Map<int64_t, ObjectiveProto>& aux_objectives,
169 Elemental& elemental) {
170 for (const auto [proto_id, obj_ptr] : SortMapByKey(aux_objectives)) {
171 const ObjectiveProto& objective = *obj_ptr;
172 const AuxiliaryObjectiveId id =
173 SafeAddElement<ElementType::kAuxiliaryObjective>(
174 proto_id, objective.name(), elemental);
175 elemental.SetAttr(BoolAttr1::kAuxObjMaximize, AttrKey(id),
176 objective.maximize());
177 elemental.SetAttr(DoubleAttr1::kAuxObjOffset, AttrKey(id),
178 objective.offset());
179 SetDoubleAttr2SliceFromProto(DoubleAttr2::kAuxObjLinCoef, id.value(),
180 objective.linear_coefficients(), elemental);
181 elemental.SetAttr(IntAttr1::kAuxObjPriority, AttrKey(id),
182 objective.priority());
183 if (objective.quadratic_coefficients().row_ids_size() > 0) {
185 << "quadratic coefficients not supported for auxiliary "
186 "objectives, but found them in objective with id: "
187 << id << " and name: " << objective.name();
188 }
189 }
190 return absl::OkStatus();
191}
192
193void AddQuadraticConstraints(
194 const google::protobuf::Map<int64_t, QuadraticConstraintProto>&
195 quadratic_constraints,
196 Elemental& elemental) {
197 for (const auto [proto_id, quad_con_ptr] :
198 SortMapByKey(quadratic_constraints)) {
199 const QuadraticConstraintProto& quad_con = *quad_con_ptr;
200 const QuadraticConstraintId id =
201 SafeAddElement<ElementType::kQuadraticConstraint>(
202 proto_id, quad_con.name(), elemental);
203 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kQuadConLb, AttrKey(id),
204 quad_con.lower_bound());
205 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kQuadConUb, AttrKey(id),
206 quad_con.upper_bound());
207 SetDoubleAttr2SliceFromProto(DoubleAttr2::kQuadConLinCoef, id.value(),
208 quad_con.linear_terms(), elemental);
209 SetDoubleAttr3SliceFromProto(SymmetricDoubleAttr3::kQuadConQuadCoef,
210 id.value(), quad_con.quadratic_terms(),
211 elemental);
212 }
213}
214
215void AddIndicatorConstraints(
216 const google::protobuf::Map<int64_t, IndicatorConstraintProto>&
217 indicator_constraints,
218 Elemental& elemental) {
219 for (const auto [proto_id, ind_con_ptr] :
220 SortMapByKey(indicator_constraints)) {
221 const IndicatorConstraintProto& ind_con = *ind_con_ptr;
222 const IndicatorConstraintId id =
223 SafeAddElement<ElementType::kIndicatorConstraint>(
224 proto_id, ind_con.name(), elemental);
225 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kIndConLb, AttrKey(id),
226 ind_con.lower_bound());
227 elemental.SetAttr<Elemental::UBPolicy>(DoubleAttr1::kIndConUb, AttrKey(id),
228 ind_con.upper_bound());
229 SetDoubleAttr2SliceFromProto(DoubleAttr2::kIndConLinCoef, id.value(),
230 ind_con.expression(), elemental);
231 elemental.SetAttr<Elemental::UBPolicy>(BoolAttr1::kIndConActivateOnZero,
232 AttrKey(id),
233 ind_con.activate_on_zero());
234 if (ind_con.has_indicator_id()) {
235 const VariableId ind(ind_con.indicator_id());
236 elemental.SetAttr<Elemental::UBPolicy>(VariableAttr1::kIndConIndicator,
237 AttrKey(id), ind);
238 }
239 }
240}
241
242absl::StatusOr<Elemental> ElementalFromModelProtoImpl(const ModelProto& proto) {
243 RETURN_IF_ERROR(ValidateModel(proto, /*check_names=*/false).status());
244 if (proto.second_order_cone_constraints_size() > 0) {
245 return absl::UnimplementedError(
246 "Elemental does not support second order cone constraints yet");
247 }
248 if (proto.sos1_constraints_size() > 0) {
249 return absl::UnimplementedError(
250 "Elemental does not support sos1 constraints yet");
251 }
252 if (proto.sos2_constraints_size() > 0) {
253 return absl::UnimplementedError(
254 "Elemental does not support sos2 constraints yet");
255 }
256 Elemental elemental(proto.name(), proto.objective().name());
257 AddVariables(proto.variables(), elemental);
258 {
259 const ObjectiveProto& objective = proto.objective();
260 elemental.SetAttr(BoolAttr0::kMaximize, AttrKey(), objective.maximize());
261 elemental.SetAttr(DoubleAttr0::kObjOffset, AttrKey(), objective.offset());
262 SetDoubleAttr1FromProto(DoubleAttr1::kObjLinCoef,
263 objective.linear_coefficients(), elemental);
264 SetDoubleAttr2FromProto(SymmetricDoubleAttr2::kObjQuadCoef,
265 objective.quadratic_coefficients(), elemental);
266 elemental.SetAttr(IntAttr0::kObjPriority, AttrKey(), objective.priority());
267 }
269 SetAuxiliaryObjectives(proto.auxiliary_objectives(), elemental));
270 AddLinearConstraints(proto.linear_constraints(), elemental);
271 SetDoubleAttr2FromProto(DoubleAttr2::kLinConCoef,
272 proto.linear_constraint_matrix(), elemental);
273 AddQuadraticConstraints(proto.quadratic_constraints(), elemental);
274 AddIndicatorConstraints(proto.indicator_constraints(), elemental);
275 return elemental;
276}
277
279// ModelUpdateProto
281
282IdNameBiMap& GetIdBiMip(ModelSummary& summary, const ElementType e) {
283 switch (e) {
285 return summary.variables;
287 return summary.linear_constraints;
289 return summary.auxiliary_objectives;
291 return summary.quadratic_constraints;
293 return summary.indicator_constraints;
294 }
295 LOG(FATAL) << "unreachable";
296}
297
298absl::StatusOr<ModelSummary> MakeSummary(const Elemental& elemental) {
299 ModelSummary summary(/*check_names=*/false);
300 summary.primary_objective_name = elemental.primary_objective_name();
301 summary.maximize = elemental.GetAttr(BoolAttr0::kMaximize, {});
302 for (const ElementType e : kElements) {
303 IdNameBiMap& id_map = GetIdBiMip(summary, e);
304 std::vector<int64_t> ids = elemental.AllElementsUntyped(e);
305 absl::c_sort(ids);
306 for (const int64_t id : ids) {
307 ASSIGN_OR_RETURN(const absl::string_view name,
308 elemental.GetElementNameUntyped(e, id));
309 RETURN_IF_ERROR(id_map.Insert(id, std::string(name)));
310 }
311 RETURN_IF_ERROR(id_map.SetNextFreeId(elemental.NextElementId(e)));
312 }
313 return summary;
314}
315
316const google::protobuf::RepeatedField<int64_t>& GetDeletedIds(
317 const ElementType e, const ModelUpdateProto& update_proto) {
318 switch (e) {
320 return update_proto.deleted_variable_ids();
322 return update_proto.deleted_linear_constraint_ids();
324 return update_proto.quadratic_constraint_updates()
325 .deleted_constraint_ids();
327 return update_proto.auxiliary_objectives_updates()
328 .deleted_objective_ids();
330 return update_proto.indicator_constraint_updates()
331 .deleted_constraint_ids();
332 }
333 LOG(FATAL) << "unreachable";
334}
335
336template <typename AtomicConstraintUpdatesProto>
337bool AtomicConstraintUpdateIsEmpty(
338 const AtomicConstraintUpdatesProto& message) {
339 return message.deleted_constraint_ids_size() == 0 &&
340 message.new_constraints().empty();
341}
342
343absl::Status ValidateModelUpdateProto(const Elemental& elemental,
344 const ModelUpdateProto& update_proto) {
345 if (!AtomicConstraintUpdateIsEmpty(
346 update_proto.second_order_cone_constraint_updates())) {
347 return absl::UnimplementedError(
348 "Elemental does not support second order cone constraints yet");
349 }
350 if (!AtomicConstraintUpdateIsEmpty(update_proto.sos1_constraint_updates())) {
351 return absl::UnimplementedError(
352 "Elemental does not support sos1 constraints yet");
353 }
354 if (!AtomicConstraintUpdateIsEmpty(update_proto.sos2_constraint_updates())) {
355 return absl::UnimplementedError(
356 "Elemental does not support sos2 constraints yet");
357 }
358 ASSIGN_OR_RETURN(ModelSummary summary, MakeSummary(elemental));
359 return ValidateModelUpdate(update_proto, summary);
360}
361
362// IMPORTANT: do this after adding new variables, it references old and new.
363void ApplyObjectiveUpdates(const ObjectiveUpdatesProto& objective_updates,
364 Elemental& elemental) {
365 if (objective_updates.has_direction_update()) {
366 elemental.SetAttr(BoolAttr0::kMaximize, {},
367 objective_updates.direction_update());
368 }
369 if (objective_updates.has_offset_update()) {
370 elemental.SetAttr(DoubleAttr0::kObjOffset, {},
371 objective_updates.offset_update());
372 }
373 if (objective_updates.has_priority_update()) {
374 elemental.SetAttr(IntAttr0::kObjPriority, {},
375 objective_updates.priority_update());
376 }
377 SetDoubleAttr1FromProto(DoubleAttr1::kObjLinCoef,
378 objective_updates.linear_coefficients(), elemental);
379 SetDoubleAttr2FromProto(SymmetricDoubleAttr2::kObjQuadCoef,
380 objective_updates.quadratic_coefficients(),
381 elemental);
382}
383
384// IMPORTANT: do this after adding new variables, it references old and new.
385absl::Status ApplyAuxiliaryObjectiveUpdates(
386 const ObjectiveUpdatesProto& objective_updates, const int64_t aux_obj_id,
387 Elemental& elemental) {
388 if (objective_updates.quadratic_coefficients().row_ids_size() > 0) {
389 return absl::InvalidArgumentError(
390 "quadratic coefficients are not supported for auxiliary objectives");
391 }
392 if (objective_updates.has_direction_update()) {
393 elemental.SetAttr(BoolAttr1::kAuxObjMaximize, AttrKey(aux_obj_id),
394 objective_updates.direction_update());
395 }
396 if (objective_updates.has_offset_update()) {
397 elemental.SetAttr(DoubleAttr1::kAuxObjOffset, AttrKey(aux_obj_id),
398 objective_updates.offset_update());
399 }
400 if (objective_updates.has_priority_update()) {
401 elemental.SetAttr(IntAttr1::kAuxObjPriority, AttrKey(aux_obj_id),
402 objective_updates.priority_update());
403 }
404 SetDoubleAttr2SliceFromProto(DoubleAttr2::kAuxObjLinCoef, aux_obj_id,
405 objective_updates.linear_coefficients(),
406 elemental);
407 return absl::OkStatus();
408}
409
410absl::Status ElementalApplyUpdateProto(const ModelUpdateProto& update_proto,
411 Elemental& elemental) {
412 RETURN_IF_ERROR(ValidateModelUpdateProto(elemental, update_proto));
413 for (const ElementType e : kElements) {
414 for (const int64_t id : GetDeletedIds(e, update_proto)) {
415 elemental.DeleteElementUntyped(e, id);
416 }
417 }
418
419 // Update variables.
420 SetDoubleAttr1FromProto(DoubleAttr1::kVarLb,
421 update_proto.variable_updates().lower_bounds(),
422 elemental);
423 SetDoubleAttr1FromProto(DoubleAttr1::kVarUb,
424 update_proto.variable_updates().upper_bounds(),
425 elemental);
426 SetBoolAttr1FromProto(BoolAttr1::kVarInteger,
427 update_proto.variable_updates().integers(), elemental);
428 // Add new variables.
429 AddVariables(update_proto.new_variables(), elemental);
430
431 // Update the objectives. IMPORTANT: do this after adding new variables.
432 ApplyObjectiveUpdates(update_proto.objective_updates(), elemental);
433 for (const auto& [id, aux_obj_update] :
434 update_proto.auxiliary_objectives_updates().objective_updates()) {
436 ApplyAuxiliaryObjectiveUpdates(aux_obj_update, id, elemental));
437 }
438 RETURN_IF_ERROR(SetAuxiliaryObjectives(
439 update_proto.auxiliary_objectives_updates().new_objectives(), elemental));
440
441 // Update linear constraints.
442 SetDoubleAttr1FromProto(
443 DoubleAttr1::kLinConLb,
444 update_proto.linear_constraint_updates().lower_bounds(), elemental);
445 SetDoubleAttr1FromProto(
446 DoubleAttr1::kLinConUb,
447 update_proto.linear_constraint_updates().upper_bounds(), elemental);
448 // Add linear constraints.
449 AddLinearConstraints(update_proto.new_linear_constraints(), elemental);
450 // Update linear constraint matrix. IMPORTANT: do this after adding both
451 // new variables and new linear constraints.
452 SetDoubleAttr2FromProto(DoubleAttr2::kLinConCoef,
453 update_proto.linear_constraint_matrix_updates(),
454 elemental);
455
456 // Quadratic constraints.
457 AddQuadraticConstraints(
458 update_proto.quadratic_constraint_updates().new_constraints(), elemental);
459 AddIndicatorConstraints(
460 update_proto.indicator_constraint_updates().new_constraints(), elemental);
461 return absl::OkStatus();
462}
463} // namespace
464
465absl::StatusOr<Elemental> Elemental::FromModelProto(const ModelProto& proto) {
466 // It intentional that that this function is implemented without access to the
467 // private API of elemental. This allows us to change the implementation
468 // elemental without breaking the proto export code.
469 return ElementalFromModelProtoImpl(proto);
470}
471
472absl::Status Elemental::ApplyUpdateProto(const ModelUpdateProto& update_proto) {
473 return ElementalApplyUpdateProto(update_proto, *this);
475
476} // namespace operations_research::math_opt
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
A strongly typed element id. Value type.
Definition elements.h:64
absl::Status ApplyUpdateProto(const ModelUpdateProto &update_proto)
Applies the changes to the model in update_proto.
static absl::StatusOr< Elemental > FromModelProto(const ModelProto &proto)
Creates an equivalent Elemental to proto.
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
absl::Status ValidateModelUpdate(const ModelUpdateProto &model_update, ModelSummary &model_summary)
BoolAttr1TypeDescriptor::AttrType BoolAttr1
Definition attributes.h:300
AttrKey< AttrTypeDescriptorT< AttrType >::kNumKeyElements, typename AttrTypeDescriptorT< AttrType >::Symmetry > AttrKeyFor
The type of the AttrKey for attribute type AttrType.
ElementId< ElementType::kAuxiliaryObjective > AuxiliaryObjectiveId
Definition elements.h:266
ElementId< ElementType::kVariable > VariableId
Definition elements.h:264
AttrKey(Ints... dims) -> AttrKey< sizeof...(Ints), NoSymmetry >
CTAD for AttrKey(1,2).
DoubleAttr1TypeDescriptor::AttrType DoubleAttr1
Definition attributes.h:304
ElementId< ElementType::kQuadraticConstraint > QuadraticConstraintId
Definition elements.h:267
ElementId< ElementType::kLinearConstraint > LinearConstraintId
Definition elements.h:265
ElementId< ElementType::kIndicatorConstraint > IndicatorConstraintId
Definition elements.h:268
absl::StatusOr< ModelSummary > ValidateModel(const ModelProto &model, const bool check_names)
StatusBuilder InvalidArgumentErrorBuilder()