Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
model.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
15
16#include <algorithm>
17#include <limits>
18#include <memory>
19#include <optional>
20#include <ostream>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/log/check.h"
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28#include "absl/strings/string_view.h"
29#include "absl/types/span.h"
48
49namespace operations_research {
50namespace math_opt {
51
52constexpr double kInf = std::numeric_limits<double>::infinity();
53
54absl::StatusOr<std::unique_ptr<Model>> Model::FromModelProto(
55 const ModelProto& model_proto) {
56 ASSIGN_OR_RETURN(std::unique_ptr<ModelStorage> storage,
57 ModelStorage::FromModelProto(model_proto));
58 return std::make_unique<Model>(std::move(storage));
59}
60
61Model::Model(const absl::string_view name)
62 : storage_(std::make_shared<ModelStorage>(name)) {}
63
64Model::Model(std::unique_ptr<ModelStorage> storage)
65 : storage_(std::move(storage)) {}
66
67std::unique_ptr<Model> Model::Clone(
68 const std::optional<absl::string_view> new_name) const {
69 return std::make_unique<Model>(storage_->Clone(new_name));
70}
71
73 const BoundedLinearExpression& bounded_expr, absl::string_view name) {
74 CheckOptionalModel(bounded_expr.expression.storage());
75
76 const LinearConstraintId constraint = storage()->AddLinearConstraint(
77 bounded_expr.lower_bound_minus_offset(),
78 bounded_expr.upper_bound_minus_offset(), name);
79 for (const auto& [variable, coef] : bounded_expr.expression.terms()) {
80 storage()->set_linear_constraint_coefficient(constraint,
81 variable.typed_id(), coef);
82 }
83 return LinearConstraint(storage(), constraint);
84}
85
86std::vector<Variable> Model::Variables() const {
87 std::vector<Variable> result;
88 result.reserve(storage()->num_variables());
89 for (const VariableId var_id : storage()->variables()) {
90 result.push_back(Variable(storage(), var_id));
91 }
92 return result;
93}
94
95std::vector<Variable> Model::SortedVariables() const {
96 std::vector<Variable> result = Variables();
97 std::sort(result.begin(), result.end(),
98 [](const Variable& l, const Variable& r) {
99 return l.typed_id() < r.typed_id();
100 });
101 return result;
102}
103
104std::vector<LinearConstraint> Model::ColumnNonzeros(
105 const Variable variable) const {
106 CheckModel(variable.storage());
107 std::vector<LinearConstraint> result;
108 for (const LinearConstraintId constraint :
109 storage()->linear_constraints_with_variable(variable.typed_id())) {
110 result.push_back(LinearConstraint(storage(), constraint));
111 }
112 return result;
113}
114
115std::vector<Variable> Model::RowNonzeros(
116 const LinearConstraint constraint) const {
117 CheckModel(constraint.storage());
118 std::vector<Variable> result;
119 for (const VariableId variable :
120 storage()->variables_in_linear_constraint(constraint.typed_id())) {
121 result.push_back(Variable(storage(), variable));
122 }
123 return result;
124}
125
126std::vector<LinearConstraint> Model::LinearConstraints() const {
127 std::vector<LinearConstraint> result;
128 result.reserve(storage()->num_linear_constraints());
129 for (const LinearConstraintId lin_con_id : storage()->LinearConstraints()) {
130 result.push_back(LinearConstraint(storage(), lin_con_id));
131 }
132 return result;
133}
134
135std::vector<LinearConstraint> Model::SortedLinearConstraints() const {
136 std::vector<LinearConstraint> result = LinearConstraints();
137 std::sort(result.begin(), result.end(),
138 [](const LinearConstraint& l, const LinearConstraint& r) {
139 return l.typed_id() < r.typed_id();
140 });
141 return result;
142}
143
145 const bool is_maximize) {
146 CheckOptionalModel(objective.storage());
147 storage()->clear_objective(kPrimaryObjectiveId);
148 storage()->set_is_maximize(kPrimaryObjectiveId, is_maximize);
149 storage()->set_objective_offset(kPrimaryObjectiveId, objective.offset());
150 for (const auto& [var, coef] : objective.terms()) {
151 storage()->set_linear_objective_coefficient(kPrimaryObjectiveId,
152 var.typed_id(), coef);
153 }
154}
155
157 const bool is_maximize) {
158 CheckOptionalModel(objective.storage());
159 storage()->clear_objective(kPrimaryObjectiveId);
160 storage()->set_is_maximize(kPrimaryObjectiveId, is_maximize);
161 storage()->set_objective_offset(kPrimaryObjectiveId, objective.offset());
162 for (const auto& [var, coef] : objective.linear_terms()) {
163 storage()->set_linear_objective_coefficient(kPrimaryObjectiveId,
164 var.typed_id(), coef);
165 }
166 for (const auto& [vars, coef] : objective.quadratic_terms()) {
167 storage()->set_quadratic_objective_coefficient(
168 kPrimaryObjectiveId, vars.typed_id().first, vars.typed_id().second,
169 coef);
170 }
171}
172
173void Model::AddToObjective(const LinearExpression& objective_terms) {
174 CheckOptionalModel(objective_terms.storage());
175 storage()->set_objective_offset(
177 objective_terms.offset() +
179 for (const auto& [var, coef] : objective_terms.terms()) {
180 storage()->set_linear_objective_coefficient(
181 kPrimaryObjectiveId, var.typed_id(),
182 coef + storage()->linear_objective_coefficient(kPrimaryObjectiveId,
183 var.typed_id()));
184 }
185}
186
187void Model::AddToObjective(const QuadraticExpression& objective_terms) {
188 CheckOptionalModel(objective_terms.storage());
189 storage()->set_objective_offset(
191 objective_terms.offset() +
193 for (const auto& [var, coef] : objective_terms.linear_terms()) {
194 storage()->set_linear_objective_coefficient(
195 kPrimaryObjectiveId, var.typed_id(),
196 coef + storage()->linear_objective_coefficient(kPrimaryObjectiveId,
197 var.typed_id()));
198 }
199 for (const auto& [vars, coef] : objective_terms.quadratic_terms()) {
200 storage()->set_quadratic_objective_coefficient(
201 kPrimaryObjectiveId, vars.typed_id().first, vars.typed_id().second,
202 coef + storage()->quadratic_objective_coefficient(
203 kPrimaryObjectiveId, vars.typed_id().first,
204 vars.typed_id().second));
205 }
206}
207
209 CHECK_EQ(storage()->num_quadratic_objective_terms(kPrimaryObjectiveId), 0)
210 << "The objective function contains quadratic terms and cannot be "
211 "represented as a LinearExpression";
212 LinearExpression result = storage()->objective_offset(kPrimaryObjectiveId);
213 for (const auto& [v, coef] :
214 storage()->linear_objective(kPrimaryObjectiveId)) {
215 result += Variable(storage(), v) * coef;
216 }
217 return result;
218}
219
221 QuadraticExpression result = storage()->objective_offset(kPrimaryObjectiveId);
222 for (const auto& [v, coef] :
223 storage()->linear_objective(kPrimaryObjectiveId)) {
224 result += Variable(storage(), v) * coef;
225 }
226 for (const auto& [v1, v2, coef] :
227 storage()->quadratic_objective_terms(kPrimaryObjectiveId)) {
228 result +=
229 QuadraticTerm(Variable(storage(), v1), Variable(storage(), v2), coef);
230 }
231 return result;
232}
233
234std::vector<Objective> Model::AuxiliaryObjectives() const {
235 std::vector<Objective> result;
236 result.reserve(num_auxiliary_objectives());
237 for (const AuxiliaryObjectiveId id : storage()->AuxiliaryObjectives()) {
238 result.push_back(auxiliary_objective(id));
239 }
240 return result;
241}
242
243std::vector<Objective> Model::SortedAuxiliaryObjectives() const {
244 std::vector<Objective> result = AuxiliaryObjectives();
245 std::sort(result.begin(), result.end(),
246 [](const Objective& l, const Objective& r) {
247 return l.typed_id() < r.typed_id();
248 });
249 return result;
250}
251
252void Model::SetObjective(const Objective objective,
253 const LinearExpression& expression,
254 const bool is_maximize) {
255 CheckModel(objective.storage());
256 CheckOptionalModel(expression.storage());
257 storage()->clear_objective(objective.typed_id());
258 set_is_maximize(objective, is_maximize);
259 set_objective_offset(objective, expression.offset());
260 for (const auto [var, coef] : expression.terms()) {
261 set_objective_coefficient(objective, var, coef);
262 }
263}
264
265void Model::AddToObjective(const Objective objective,
266 const LinearExpression& expression) {
267 CheckModel(objective.storage());
268 CheckOptionalModel(expression.storage());
269 set_objective_offset(objective, objective.offset() + expression.offset());
270 for (const auto [var, coef] : expression.terms()) {
271 set_objective_coefficient(objective, var,
272 objective.coefficient(var) + coef);
273 }
274}
275
277 const Objective objective) const {
278 CheckModel(objective.storage());
279 std::vector<Variable> result;
280 result.reserve(storage()->num_linear_objective_terms(objective.typed_id()));
281 for (const auto [var_id, unused] :
282 storage()->linear_objective(objective.typed_id())) {
283 result.push_back(Variable(storage(), var_id));
284 }
285 return result;
286}
287
288std::vector<Variable> Model::NonzeroVariablesInQuadraticObjective() const {
289 std::vector<Variable> result;
290 for (const auto& [var_id1, var_id2, unused] :
291 storage()->quadratic_objective_terms(kPrimaryObjectiveId)) {
292 result.push_back(Variable(storage(), var_id1));
293 result.push_back(Variable(storage(), var_id2));
294 }
295 return result;
296}
297
298ModelProto Model::ExportModel(const bool remove_names) const {
299 return storage()->ExportModel(remove_names);
300}
301
302std::unique_ptr<UpdateTracker> Model::NewUpdateTracker() {
303 return std::make_unique<UpdateTracker>(storage_);
304}
305
306absl::Status Model::ApplyUpdateProto(const ModelUpdateProto& update_proto) {
307 return storage()->ApplyUpdateProto(update_proto);
308}
309
310std::ostream& operator<<(std::ostream& ostr, const Model& model) {
311 ostr << "Model";
312 if (!model.name().empty()) ostr << " " << model.name();
313 ostr << ":\n";
314
315 if (model.num_auxiliary_objectives() == 0) {
316 ostr << " Objective:\n"
317 << (model.is_maximize() ? " maximize " : " minimize ")
318 << model.ObjectiveAsQuadraticExpression() << "\n";
319 } else {
320 ostr << " Objectives:\n";
321 const auto stream_objective = [](std::ostream& ostr, const Objective obj) {
322 ostr << " " << obj << " (priority " << obj.priority()
323 << "): " << (obj.maximize() ? "maximize " : "minimize ")
324 << obj.AsQuadraticExpression() << "\n";
325 };
326 stream_objective(ostr, model.primary_objective());
327 for (const Objective obj : model.SortedAuxiliaryObjectives()) {
328 stream_objective(ostr, obj);
329 }
330 }
331
332 ostr << " Linear constraints:\n";
333 for (const LinearConstraint constraint : model.SortedLinearConstraints()) {
334 ostr << " " << constraint << ": " << constraint.AsBoundedLinearExpression()
335 << "\n";
336 }
337
338 if (model.num_quadratic_constraints() > 0) {
339 ostr << " Quadratic constraints:\n";
340 for (const QuadraticConstraint constraint :
342 ostr << " " << constraint << ": "
343 << constraint.AsBoundedQuadraticExpression() << "\n";
344 }
345 }
346
347 if (model.num_second_order_cone_constraints() > 0) {
348 ostr << " Second-order cone constraints:\n";
349 for (const SecondOrderConeConstraint constraint :
351 ostr << " " << constraint << ": " << constraint.ToString() << "\n";
352 }
353 }
354
355 if (model.num_sos1_constraints() > 0) {
356 ostr << " SOS1 constraints:\n";
357 for (const Sos1Constraint constraint : model.SortedSos1Constraints()) {
358 ostr << " " << constraint << ": " << constraint.ToString() << "\n";
359 }
360 }
361
362 if (model.num_sos2_constraints() > 0) {
363 ostr << " SOS2 constraints:\n";
364 for (const Sos2Constraint constraint : model.SortedSos2Constraints()) {
365 ostr << " " << constraint << ": " << constraint.ToString() << "\n";
366 }
367 }
368
369 if (model.num_indicator_constraints() > 0) {
370 ostr << " Indicator constraints:\n";
371 for (const IndicatorConstraint constraint :
373 ostr << " " << constraint << ": " << constraint.ToString() << "\n";
374 }
375 }
376
377 ostr << " Variables:\n";
378 for (const Variable v : model.SortedVariables()) {
379 ostr << " " << v;
380 if (v.is_integer()) {
381 if (v.lower_bound() == 0 && v.upper_bound() == 1) {
382 ostr << " (binary)\n";
383 continue;
384 }
385 ostr << " (integer)";
386 }
387 ostr << " in ";
388 if (v.lower_bound() == -kInf) {
389 ostr << "(-∞";
390 } else {
391 ostr << "[" << RoundTripDoubleFormat(v.lower_bound());
392 }
393 ostr << ", ";
394 if (v.upper_bound() == kInf) {
395 ostr << "+∞)";
396 } else {
397 ostr << RoundTripDoubleFormat(v.upper_bound()) << "]";
398 }
399 ostr << "\n";
400 }
401 return ostr;
402}
403
404// ------------------------- Quadratic constraints -----------------------------
405
407 const BoundedQuadraticExpression& bounded_expr,
408 const absl::string_view name) {
409 CheckOptionalModel(bounded_expr.expression.storage());
410 SparseCoefficientMap linear_terms;
411 for (const auto [var, coeff] : bounded_expr.expression.linear_terms()) {
412 linear_terms.set(var.typed_id(), coeff);
413 }
414 SparseSymmetricMatrix quadratic_terms;
415 for (const auto& [var_ids, coeff] :
416 bounded_expr.expression.quadratic_terms()) {
417 quadratic_terms.set(var_ids.typed_id().first, var_ids.typed_id().second,
418 coeff);
419 }
420 const QuadraticConstraintId id =
421 storage()->AddAtomicConstraint(QuadraticConstraintData{
422 .lower_bound = bounded_expr.lower_bound_minus_offset(),
423 .upper_bound = bounded_expr.upper_bound_minus_offset(),
424 .linear_terms = std::move(linear_terms),
425 .quadratic_terms = std::move(quadratic_terms),
426 .name = std::string(name),
427 });
428 return QuadraticConstraint(storage(), id);
429}
430
431// --------------------- Second-order cone constraints -------------------------
432
434 absl::Span<const LinearExpression> arguments_to_norm,
435 const LinearExpression& upper_bound, const absl::string_view name) {
436 CheckOptionalModel(upper_bound.storage());
437 std::vector<LinearExpressionData> arguments_to_norm_data;
438 arguments_to_norm_data.reserve(arguments_to_norm.size());
439 for (const LinearExpression& expr : arguments_to_norm) {
440 CheckOptionalModel(expr.storage());
441 arguments_to_norm_data.push_back(FromLinearExpression(expr));
442 }
443 const SecondOrderConeConstraintId id =
444 storage()->AddAtomicConstraint(SecondOrderConeConstraintData{
445 .upper_bound = FromLinearExpression(upper_bound),
446 .arguments_to_norm = std::move(arguments_to_norm_data),
447 .name = std::string(name),
448 });
450}
451
452// --------------------------- SOS1 constraints --------------------------------
453
454namespace {
455
456template <typename SosData>
457SosData MakeSosData(const std::vector<LinearExpression>& expressions,
458 std::vector<double> weights, const absl::string_view name) {
459 std::vector<LinearExpressionData> storage_expressions;
460 storage_expressions.reserve(expressions.size());
461 for (const LinearExpression& expr : expressions) {
462 LinearExpressionData& storage_expr = storage_expressions.emplace_back();
463 storage_expr.offset = expr.offset();
464 for (const auto& [var, coeff] : expr.terms()) {
465 storage_expr.coeffs.set(var.typed_id(), coeff);
466 }
467 }
468 return SosData(std::move(storage_expressions), std::move(weights),
469 std::string(name));
470}
471
472} // namespace
473
475 const std::vector<LinearExpression>& expressions,
476 std::vector<double> weights, const absl::string_view name) {
477 for (const LinearExpression& expr : expressions) {
478 CheckOptionalModel(expr.storage());
479 }
480 const Sos1ConstraintId id = storage()->AddAtomicConstraint(
481 MakeSosData<Sos1ConstraintData>(expressions, std::move(weights), name));
482 return Sos1Constraint(storage(), id);
483}
484
485// --------------------------- SOS2 constraints --------------------------------
486
488 const std::vector<LinearExpression>& expressions,
489 std::vector<double> weights, const absl::string_view name) {
490 for (const LinearExpression& expr : expressions) {
491 CheckOptionalModel(expr.storage());
492 }
493 const Sos2ConstraintId id = storage()->AddAtomicConstraint(
494 MakeSosData<Sos2ConstraintData>(expressions, std::move(weights), name));
495 return Sos2Constraint(storage(), id);
496}
497
498// --------------------------- Indicator constraints ---------------------------
499
501 const Variable indicator_variable,
502 const BoundedLinearExpression& implied_constraint,
503 const bool activate_on_zero, const absl::string_view name) {
504 CheckModel(indicator_variable.storage());
505 CheckOptionalModel(implied_constraint.expression.storage());
506 // We ignore the offset while unpacking here; instead, we account for it below
507 // by using the `{lower,upper}_bound_minus_offset` member functions.
508 auto [expr, _] = FromLinearExpression(implied_constraint.expression);
509 const IndicatorConstraintId id =
510 storage()->AddAtomicConstraint(IndicatorConstraintData{
511 .lower_bound = implied_constraint.lower_bound_minus_offset(),
512 .upper_bound = implied_constraint.upper_bound_minus_offset(),
513 .linear_terms = std::move(expr),
514 .indicator = indicator_variable.typed_id(),
515 .activate_on_zero = activate_on_zero,
516 .name = std::string(name),
517 });
518 return IndicatorConstraint(storage(), id);
519}
520
521} // namespace math_opt
522} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
const VariableMap< double > & terms() const
Returns the terms in this expression.
Sos1Constraint AddSos1Constraint(const std::vector< LinearExpression > &expressions, std::vector< double > weights={}, absl::string_view name="")
Definition model.cc:474
std::vector< IndicatorConstraint > SortedIndicatorConstraints() const
Definition model.h:1394
Objective auxiliary_objective(int64_t id) const
Will CHECK if has_auxiliary_objective(id) is false.
Definition model.h:1587
int64_t num_sos2_constraints() const
Definition model.h:1322
const ModelStorage * storage() const
Definition model.h:916
QuadraticConstraint AddQuadraticConstraint(const BoundedQuadraticExpression &bounded_expr, absl::string_view name="")
----------------------— Quadratic constraints --------------------------—
Definition model.cc:406
Sos2Constraint AddSos2Constraint(const std::vector< LinearExpression > &expressions, std::vector< double > weights={}, absl::string_view name="")
------------------------— SOS2 constraints -----------------------------—
Definition model.cc:487
ModelProto ExportModel(bool remove_names=false) const
Definition model.cc:298
static absl::StatusOr< std::unique_ptr< Model > > FromModelProto(const ModelProto &model_proto)
Definition model.cc:54
std::vector< Sos2Constraint > SortedSos2Constraints() const
Definition model.h:1352
std::vector< Sos1Constraint > SortedSos1Constraints() const
Definition model.h:1311
absl::string_view name() const
----------------------------— Variables --------------------------------—
Definition model.h:958
std::vector< QuadraticConstraint > SortedQuadraticConstraints() const
Definition model.h:1223
SecondOrderConeConstraint AddSecondOrderConeConstraint(absl::Span< const LinearExpression > arguments_to_norm, const LinearExpression &upper_bound, absl::string_view name="")
------------------— Second-order cone constraints ----------------------—
Definition model.cc:433
std::vector< Variable > NonzeroVariablesInLinearObjective() const
Definition model.h:1527
std::vector< SecondOrderConeConstraint > SortedSecondOrderConeConstraints() const
Definition model.h:1269
std::vector< Objective > AuxiliaryObjectives() const
Definition model.cc:234
int64_t num_second_order_cone_constraints() const
Definition model.h:1235
std::unique_ptr< UpdateTracker > NewUpdateTracker()
Definition model.cc:302
std::vector< LinearConstraint > SortedLinearConstraints() const
Definition model.cc:135
void set_is_maximize(bool is_maximize)
Prefer set_maximize() and set_minimize() above for more readable code.
Definition model.h:1523
std::unique_ptr< Model > Clone(std::optional< absl::string_view > new_name=std::nullopt) const
Definition model.cc:67
int64_t num_sos1_constraints() const
Definition model.h:1281
Variable variable(int64_t id) const
Will CHECK if has_variable(id) is false.
Definition model.h:1005
QuadraticExpression ObjectiveAsQuadraticExpression() const
Definition model.cc:220
void AddToObjective(double objective)
Adds the provided expression terms to the objective.
Definition model.h:1442
void set_objective_offset(double value)
Definition model.h:1511
absl::Status ApplyUpdateProto(const ModelUpdateProto &update_proto)
Definition model.cc:306
std::vector< LinearConstraint > ColumnNonzeros(Variable variable) const
Definition model.cc:104
int64_t num_indicator_constraints() const
Definition model.h:1363
std::vector< LinearConstraint > LinearConstraints() const
Definition model.cc:126
std::vector< Objective > SortedAuxiliaryObjectives() const
Definition model.cc:243
int64_t num_auxiliary_objectives() const
Definition model.h:1571
void set_objective_coefficient(Variable variable, double value)
Definition model.h:1471
double upper_bound(Variable variable) const
Returns a variable upper bound.
Definition model.h:1034
std::vector< Variable > Variables() const
Definition model.cc:86
std::vector< Variable > NonzeroVariablesInQuadraticObjective() const
Definition model.cc:288
std::vector< Variable > SortedVariables() const
Definition model.cc:95
Model(absl::string_view name="")
Creates an empty minimization problem.
Definition model.cc:61
Objective primary_objective() const
Definition model.h:1452
LinearExpression ObjectiveAsLinearExpression() const
Definition model.cc:208
int64_t num_quadratic_constraints() const
Definition model.h:1192
void SetObjective(double objective, bool is_maximize)
Sets the objective to optimize the provided expression.
Definition model.h:1432
IndicatorConstraint AddIndicatorConstraint(Variable indicator_variable, const BoundedLinearExpression &implied_constraint, bool activate_on_zero=false, absl::string_view name={})
------------------------— Indicator constraints ------------------------—
Definition model.cc:500
LinearConstraint AddLinearConstraint(absl::string_view name="")
Adds a linear constraint to the model with bounds [-inf, +inf].
Definition model.h:1075
std::vector< Variable > RowNonzeros(LinearConstraint constraint) const
Definition model.cc:115
double coefficient(Variable variable) const
Returns the linear coefficient for the variable in the model.
Definition objective.h:171
const ModelStorage * storage() const
Returns a const-pointer to the underlying storage object for the model.
Definition objective.h:144
double offset() const
Returns the constant offset of the objective.
Definition objective.h:161
const QuadraticTermMap< double > & quadratic_terms() const
bool set(VariableId id, double coeff)
Returns true if the stored value changes.
bool set(VariableId first, VariableId second, double value)
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
constexpr ObjectiveId kPrimaryObjectiveId
LinearExpressionData FromLinearExpression(const LinearExpression &expression)
Converts a LinearExpression to the associated "raw ID" format.
Definition model_util.cc:34
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
A LinearExpression with upper and lower bounds.
A QuadraticExpression with upper and lower bounds.