Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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#if !defined(_MSC_VER)
17#include <unistd.h>
18#endif
19
20#include <algorithm>
21#include <atomic>
22#include <cmath>
23#include <cstddef>
24#include <cstdint>
25#include <functional>
26#include <iostream>
27#include <limits>
28#include <optional>
29#include <string>
30#include <utility>
31#include <vector>
32
33#include "absl/base/const_init.h"
34#include "absl/container/flat_hash_map.h"
35#include "absl/container/flat_hash_set.h"
36#include "absl/flags/flag.h"
37#include "absl/log/check.h"
38#include "absl/status/status.h"
39#include "absl/status/statusor.h"
40#include "absl/strings/ascii.h"
41#include "absl/strings/match.h"
42#include "absl/strings/str_cat.h"
43#include "absl/strings/str_format.h"
44#include "absl/strings/str_replace.h"
45#include "absl/strings/string_view.h"
46#include "absl/synchronization/mutex.h"
47#include "absl/synchronization/notification.h"
48#include "absl/time/clock.h"
49#include "absl/time/time.h"
50#include "google/protobuf/text_format.h"
57#include "ortools/linear_solver/linear_solver.pb.h"
61#include "ortools/port/file.h"
66
67ABSL_FLAG(bool, verify_solution, false,
68 "Systematically verify the solution when calling Solve()"
69 ", and change the return value of Solve() to ABNORMAL if"
70 " an error was detected.");
71ABSL_FLAG(bool, log_verification_errors, true,
72 "If --verify_solution is set: LOG(ERROR) all errors detected"
73 " during the verification of the solution.");
74ABSL_FLAG(bool, linear_solver_enable_verbose_output, false,
75 "If set, enables verbose output for the solver. Setting this flag"
76 " is the same as calling MPSolver::EnableOutput().");
77
78ABSL_FLAG(bool, mpsolver_bypass_model_validation, false,
79 "If set, the user-provided Model won't be verified before Solve()."
80 " Invalid models will typically trigger various error responses"
81 " from the underlying solvers; sometimes crashes.");
82
83namespace operations_research {
84
85bool SolverTypeIsMip(MPModelRequest::SolverType solver_type) {
86 switch (solver_type) {
87 case MPModelRequest::PDLP_LINEAR_PROGRAMMING:
88 case MPModelRequest::GLOP_LINEAR_PROGRAMMING:
89 case MPModelRequest::CLP_LINEAR_PROGRAMMING:
90 case MPModelRequest::GLPK_LINEAR_PROGRAMMING:
91 case MPModelRequest::GUROBI_LINEAR_PROGRAMMING:
92 case MPModelRequest::HIGHS_LINEAR_PROGRAMMING:
93 case MPModelRequest::XPRESS_LINEAR_PROGRAMMING:
94 case MPModelRequest::CPLEX_LINEAR_PROGRAMMING:
95 return false;
96
97 case MPModelRequest::SCIP_MIXED_INTEGER_PROGRAMMING:
98 case MPModelRequest::GLPK_MIXED_INTEGER_PROGRAMMING:
99 case MPModelRequest::CBC_MIXED_INTEGER_PROGRAMMING:
100 case MPModelRequest::GUROBI_MIXED_INTEGER_PROGRAMMING:
101 case MPModelRequest::KNAPSACK_MIXED_INTEGER_PROGRAMMING:
102 case MPModelRequest::BOP_INTEGER_PROGRAMMING:
103 case MPModelRequest::SAT_INTEGER_PROGRAMMING:
104 case MPModelRequest::HIGHS_MIXED_INTEGER_PROGRAMMING:
105 case MPModelRequest::XPRESS_MIXED_INTEGER_PROGRAMMING:
106 case MPModelRequest::CPLEX_MIXED_INTEGER_PROGRAMMING:
107 return true;
108 }
109 LOG(DFATAL) << "Invalid SolverType: " << solver_type;
110 return false;
111}
112
113double MPConstraint::GetCoefficient(const MPVariable* const var) const {
114 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
115 if (var == nullptr) return 0.0;
116 return gtl::FindWithDefault(coefficients_, var);
117}
118
119void MPConstraint::SetCoefficient(const MPVariable* const var, double coeff) {
120 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
121 if (var == nullptr) return;
122 if (coeff == 0.0) {
123 auto it = coefficients_.find(var);
124 // If setting a coefficient to 0 when this coefficient did not
125 // exist or was already 0, do nothing: skip
126 // interface_->SetCoefficient() and do not store a coefficient in
127 // the map. Note that if the coefficient being set to 0 did exist
128 // and was not 0, we do have to keep a 0 in the coefficients_ map,
129 // because the extraction of the constraint might rely on it,
130 // depending on the underlying solver.
131 if (it != coefficients_.end() && it->second != 0.0) {
132 const double old_value = it->second;
133 it->second = 0.0;
134 interface_->SetCoefficient(this, var, 0.0, old_value);
135 }
136 return;
137 }
138 auto insertion_result = coefficients_.insert(std::make_pair(var, coeff));
139 const double old_value =
140 insertion_result.second ? 0.0 : insertion_result.first->second;
141 insertion_result.first->second = coeff;
142 interface_->SetCoefficient(this, var, coeff, old_value);
143}
144
146 interface_->ClearConstraint(this);
147 coefficients_.clear();
148}
149
150void MPConstraint::SetBounds(double lb, double ub) {
151 const bool change = lb != lb_ || ub != ub_;
152 lb_ = lb;
153 ub_ = ub;
154 if (change && interface_->constraint_is_extracted(index_)) {
155 interface_->SetConstraintBounds(index_, lb_, ub_);
156 }
157}
158
160 if (!interface_->IsContinuous()) {
161 LOG(DFATAL) << "Dual value only available for continuous problems";
162 return 0.0;
163 }
164 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
165 return dual_value_;
166}
167
169 if (!interface_->IsContinuous()) {
170 LOG(DFATAL) << "Basis status only available for continuous problems";
171 return MPSolver::FREE;
172 }
173 if (!interface_->CheckSolutionIsSynchronizedAndExists()) {
174 return MPSolver::FREE;
175 }
176 // This is done lazily as this method is expected to be rarely used.
177 return interface_->row_status(index_);
178}
179
180bool MPConstraint::ContainsNewVariables() {
181 const int last_variable_index = interface_->last_variable_index();
182 for (const auto& entry : coefficients_) {
183 const int variable_index = entry.first->index();
184 if (variable_index >= last_variable_index ||
185 !interface_->variable_is_extracted(variable_index)) {
186 return true;
187 }
188 }
189 return false;
190}
191
192// ----- MPObjective -----
193
194double MPObjective::GetCoefficient(const MPVariable* const var) const {
195 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
196 if (var == nullptr) return 0.0;
197 return gtl::FindWithDefault(coefficients_, var);
198}
199
200void MPObjective::SetCoefficient(const MPVariable* const var, double coeff) {
201 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
202 if (var == nullptr) return;
203 if (coeff == 0.0) {
204 auto it = coefficients_.find(var);
205 // See the discussion on MPConstraint::SetCoefficient() for 0 coefficients,
206 // the same reasoning applies here.
207 if (it == coefficients_.end() || it->second == 0.0) return;
208 it->second = 0.0;
209 } else {
210 coefficients_[var] = coeff;
211 }
212 interface_->SetObjectiveCoefficient(var, coeff);
213}
214
216 offset_ = value;
217 interface_->SetObjectiveOffset(offset_);
218}
219
220namespace {
221void CheckLinearExpr(const MPSolver& solver, const LinearExpr& linear_expr) {
222 for (auto var_value_pair : linear_expr.terms()) {
223 CHECK(solver.OwnsVariable(var_value_pair.first))
224 << "Bad MPVariable* in LinearExpr, did you try adding an integer to an "
225 "MPVariable* directly?";
226 }
227}
228} // namespace
229
231 bool is_maximization) {
232 CheckLinearExpr(*interface_->solver_, linear_expr);
233 interface_->ClearObjective();
234 coefficients_.clear();
235 SetOffset(linear_expr.offset());
236 for (const auto& kv : linear_expr.terms()) {
237 SetCoefficient(kv.first, kv.second);
238 }
239 SetOptimizationDirection(is_maximization);
240}
241
242void MPObjective::AddLinearExpr(const LinearExpr& linear_expr) {
243 CheckLinearExpr(*interface_->solver_, linear_expr);
244 SetOffset(offset_ + linear_expr.offset());
245 for (const auto& kv : linear_expr.terms()) {
246 SetCoefficient(kv.first, GetCoefficient(kv.first) + kv.second);
247 }
248}
249
251 interface_->ClearObjective();
252 coefficients_.clear();
253 offset_ = 0.0;
255}
256
258 // Note(user): The maximize_ bool would more naturally belong to the
259 // MPObjective, but it actually has to be a member of MPSolverInterface,
260 // because some implementations (such as GLPK) need that bool for the
261 // MPSolverInterface constructor, i.e. at a time when the MPObjective is not
262 // constructed yet (MPSolverInterface is always built before MPObjective
263 // when a new MPSolver is constructed).
264 interface_->maximize_ = maximize;
265 interface_->SetOptimizationDirection(maximize);
266}
267
268bool MPObjective::maximization() const { return interface_->maximize_; }
269
270bool MPObjective::minimization() const { return !interface_->maximize_; }
271
272double MPObjective::Value() const {
273 // Note(user): implementation-wise, the objective value belongs more
274 // naturally to the MPSolverInterface, since all of its implementations write
275 // to it directly.
276 return interface_->objective_value();
277}
278
280 // Note(user): the best objective bound belongs to the interface for the
281 // same reasons as the objective value does.
282 return interface_->best_objective_bound();
283}
284
285// ----- MPVariable -----
286
288 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
289 // If the underlying solver supports integer variables, and this is an integer
290 // variable, we round the solution value (i.e., clients usually expect precise
291 // integer values for integer variables).
292 return (integer_ && interface_->IsMIP()) ? round(solution_value_)
293 : solution_value_;
294}
295
297 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
298 return solution_value_;
299}
300
302 if (!interface_->IsContinuous()) {
303 LOG(DFATAL) << "Reduced cost only available for continuous problems";
304 return 0.0;
305 }
306 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
307 return reduced_cost_;
308}
309
311 if (!interface_->IsContinuous()) {
312 LOG(DFATAL) << "Basis status only available for continuous problems";
313 return MPSolver::FREE;
314 }
315 if (!interface_->CheckSolutionIsSynchronizedAndExists()) {
316 return MPSolver::FREE;
317 }
318 // This is done lazily as this method is expected to be rarely used.
319 return interface_->column_status(index_);
320}
321
322void MPVariable::SetBounds(double lb, double ub) {
323 const bool change = lb != lb_ || ub != ub_;
324 lb_ = lb;
325 ub_ = ub;
326 if (change && interface_->variable_is_extracted(index_)) {
327 interface_->SetVariableBounds(index_, lb_, ub_);
328 }
329}
330
331void MPVariable::SetInteger(bool integer) {
332 if (integer_ != integer) {
333 integer_ = integer;
334 if (interface_->variable_is_extracted(index_)) {
335 interface_->SetVariableInteger(index_, integer);
336 }
337 }
338}
339
341 if (priority == branching_priority_) return;
342 branching_priority_ = priority;
343 interface_->BranchingPriorityChangedForVariable(index_);
344}
345
346// ----- Interface shortcuts -----
347
348bool MPSolver::IsMIP() const { return interface_->IsMIP(); }
349
350std::string MPSolver::SolverVersion() const {
351 return interface_->SolverVersion();
352}
353
354void* MPSolver::underlying_solver() { return interface_->underlying_solver(); }
355
356// ---- Solver-specific parameters ----
357
358absl::Status MPSolver::SetNumThreads(int num_threads) {
359 if (num_threads < 1) {
360 return absl::InvalidArgumentError("num_threads must be a positive number.");
361 }
362 const absl::Status status = interface_->SetNumThreads(num_threads);
363 if (status.ok()) {
364 num_threads_ = num_threads;
365 }
366 return status;
367}
368
370 const std::string& parameters) {
371 solver_specific_parameter_string_ = parameters;
372 return interface_->SetSolverSpecificParametersAsString(parameters);
373}
374
375// ----- Solver -----
376
377#if defined(USE_BOP)
378extern MPSolverInterface* BuildBopInterface(MPSolver* const solver);
379#endif
380#if defined(USE_CBC)
381extern MPSolverInterface* BuildCBCInterface(MPSolver* const solver);
382#endif
383#if defined(USE_CLP) || defined(USE_CBC)
384extern MPSolverInterface* BuildCLPInterface(MPSolver* const solver);
385#endif
386#if defined(USE_GLOP)
387extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver);
388#endif
389#if defined(USE_GLPK)
390extern MPSolverInterface* BuildGLPKInterface(bool mip, MPSolver* const solver);
391#endif
392#if defined(USE_HIGHS)
393extern MPSolverInterface* BuildHighsInterface(bool mip, MPSolver* const solver);
394#endif
395#if defined(USE_PDLP)
396extern MPSolverInterface* BuildPdlpInterface(MPSolver* const solver);
397#endif
398extern MPSolverInterface* BuildSatInterface(MPSolver* const solver);
399#if defined(USE_SCIP)
400extern MPSolverInterface* BuildSCIPInterface(MPSolver* const solver);
401#endif
403 MPSolver* const solver);
404#if defined(USE_CPLEX)
405extern MPSolverInterface* BuildCplexInterface(bool mip, MPSolver* const solver);
406#endif
408 MPSolver* const solver);
409
410namespace {
411MPSolverInterface* BuildSolverInterface(MPSolver* const solver) {
412 DCHECK(solver != nullptr);
413 switch (solver->ProblemType()) {
414#if defined(USE_BOP)
416 return BuildBopInterface(solver);
417#endif
418#if defined(USE_CBC)
420 return BuildCBCInterface(solver);
421#endif
422#if defined(USE_CLP) || defined(USE_CBC)
424 return BuildCLPInterface(solver);
425#endif
426#if defined(USE_GLOP)
428 return BuildGLOPInterface(solver);
429#endif
430#if defined(USE_GLPK)
432 return BuildGLPKInterface(false, solver);
434 return BuildGLPKInterface(true, solver);
435#endif
436#if defined(USE_HIGHS)
438 return BuildHighsInterface(false, solver);
440 return BuildHighsInterface(true, solver);
441#endif
442#if defined(USE_PDLP)
444 return BuildPdlpInterface(solver);
445#endif
447 return BuildSatInterface(solver);
448#if defined(USE_SCIP)
450 return BuildSCIPInterface(solver);
451#endif
453 return BuildGurobiInterface(false, solver);
455 return BuildGurobiInterface(true, solver);
456#if defined(USE_CPLEX)
458 return BuildCplexInterface(false, solver);
460 return BuildCplexInterface(true, solver);
461#endif
463 return BuildXpressInterface(true, solver);
465 return BuildXpressInterface(false, solver);
466 default:
467 // TODO(user): Revert to the best *available* interface.
468 LOG(FATAL) << "Linear solver not recognized.";
469 }
470 return nullptr;
471}
472} // namespace
473
474namespace {
475int NumDigits(int n) {
476// Number of digits needed to write a non-negative integer in base 10.
477// Note(user): max(1, log(0) + 1) == max(1, -inf) == 1.
478#if defined(_MSC_VER)
479 return static_cast<int>(std::max(1.0L, log(1.0L * n) / log(10.0L) + 1.0));
480#else
481 return static_cast<int>(std::max(1.0, log10(static_cast<double>(n)) + 1.0));
482#endif
483}
484} // namespace
485
486MPSolver::MPSolver(const std::string& name,
488 : name_(name),
489 problem_type_(problem_type),
490 construction_time_(absl::Now()) {
491 interface_.reset(BuildSolverInterface(this));
492 if (absl::GetFlag(FLAGS_linear_solver_enable_verbose_output)) {
493 EnableOutput();
494 }
495 objective_.reset(new MPObjective(interface_.get()));
496}
497
499
500extern bool GurobiIsCorrectlyInstalled();
501extern bool XpressIsCorrectlyInstalled();
502
503// static
505#ifdef USE_BOP
506 if (problem_type == BOP_INTEGER_PROGRAMMING) return true;
507#endif
508#ifdef USE_CBC
510#endif
511#ifdef USE_CLP
512 if (problem_type == CLP_LINEAR_PROGRAMMING) return true;
513#endif
514#ifdef USE_GLOP
515 if (problem_type == GLOP_LINEAR_PROGRAMMING) return true;
516#endif
517#ifdef USE_GLPK
520 return true;
521 }
522#endif
523#ifdef USE_HIGHS
526 return true;
527 }
528#endif
529#ifdef USE_PDLP
530 if (problem_type == PDLP_LINEAR_PROGRAMMING) return true;
531#endif
535 }
536 if (problem_type == SAT_INTEGER_PROGRAMMING) return true;
537#ifdef USE_SCIP
539#endif
540#ifdef USE_CPLEX
543 return true;
544 }
545#endif
549 }
550
551 return false;
552}
553
554// TODO(user): post c++ 14, instead use
555// std::pair<MPSolver::OptimizationProblemType, const absl::string_view>
556// once pair gets a constexpr constructor.
557namespace {
558struct NamedOptimizationProblemType {
560 absl::string_view name;
561};
562} // namespace
563
564#if defined(_MSC_VER)
565const
566#else
567constexpr
568#endif
589// static
590bool MPSolver::ParseSolverType(absl::string_view solver_id,
592 // Normalize the solver id.
593 const std::string id =
594 absl::StrReplaceAll(absl::AsciiStrToUpper(solver_id), {{"-", "_"}});
595
596 // Support the full enum name
597 MPModelRequest::SolverType solver_type;
598 if (MPModelRequest::SolverType_Parse(id, &solver_type)) {
599 *type = static_cast<MPSolver::OptimizationProblemType>(solver_type);
600 return true;
601 }
602
603 // Names are stored in lower case.
604 std::string lower_id = absl::AsciiStrToLower(id);
605
606 // Remove any "_mip" suffix, since they are optional.
607 if (absl::EndsWith(lower_id, "_mip")) {
608 lower_id = lower_id.substr(0, lower_id.size() - 4);
609 }
610
611 // Rewrite CP-SAT into SAT.
612 if (lower_id == "cp_sat") {
613 lower_id = "sat";
614 }
615
616 // Reverse lookup in the kOptimizationProblemTypeNames[] array.
617 for (auto& named_solver : kOptimizationProblemTypeNames) {
618 if (named_solver.name == lower_id) {
619 *type = named_solver.problem_type;
620 return true;
621 }
622 }
623
624 return false;
625}
626
627absl::string_view ToString(
628 MPSolver::OptimizationProblemType optimization_problem_type) {
629 for (const auto& named_solver : kOptimizationProblemTypeNames) {
630 if (named_solver.problem_type == optimization_problem_type) {
631 return named_solver.name;
632 }
633 }
634 LOG(FATAL) << "Unrecognized solver type: "
635 << static_cast<int>(optimization_problem_type);
636 return "";
637}
638
639bool AbslParseFlag(const absl::string_view text,
641 std::string* error) {
642 DCHECK(solver_type != nullptr);
643 DCHECK(error != nullptr);
644 const bool result = MPSolver::ParseSolverType(text, solver_type);
645 if (!result) {
646 *error = absl::StrCat("Solver type: ", text, " does not exist.");
647 }
648 return result;
649}
650
651/* static */
658
659/* static */
660MPSolver* MPSolver::CreateSolver(const std::string& solver_id) {
662 if (!MPSolver::ParseSolverType(solver_id, &problem_type)) {
663 LOG(WARNING) << "Unrecognized solver type: " << solver_id;
664 return nullptr;
665 }
667 LOG(WARNING) << "Support for " << solver_id
668 << " not linked in, or the license was not found.";
669 return nullptr;
670 }
671 MPSolver* solver = new MPSolver("", problem_type);
672 return solver;
673}
674
675MPVariable* MPSolver::LookupVariableOrNull(const std::string& var_name) const {
676 if (!variable_name_to_index_) GenerateVariableNameIndex();
677
678 absl::flat_hash_map<std::string, int>::const_iterator it =
679 variable_name_to_index_->find(var_name);
680 if (it == variable_name_to_index_->end()) return nullptr;
681 return variables_[it->second];
682}
683
685 const std::string& constraint_name) const {
686 if (!constraint_name_to_index_) GenerateConstraintNameIndex();
687
688 const auto it = constraint_name_to_index_->find(constraint_name);
689 if (it == constraint_name_to_index_->end()) return nullptr;
690 return constraints_[it->second];
691}
692
693// ----- Methods using protocol buffers -----
694
695MPSolverResponseStatus MPSolver::LoadModelFromProto(
696 const MPModelProto& input_model, std::string* error_message,
697 bool clear_names) {
698 Clear();
699
700 // The variable and constraint names are dropped, because we allow
701 // duplicate names in the proto (they're not considered as 'ids'),
702 // unlike the MPSolver C++ API which crashes if there are duplicate names.
703 // Clearing the names makes the MPSolver generate unique names.
704 return LoadModelFromProtoInternal(
705 input_model,
706 /*name_policy=*/
707 clear_names ? DEFAULT_CLEAR_NAMES
708 : INVALID_MODEL_ON_DUPLICATE_NONEMPTY_NAMES,
709 /*check_model_validity=*/true, error_message);
710}
711
713 const MPModelProto& input_model, std::string* error_message) {
714 Clear();
715
716 // Force variable and constraint name indexing (which CHECKs name uniqueness).
717 GenerateVariableNameIndex();
718 GenerateConstraintNameIndex();
719
720 return LoadModelFromProtoInternal(
721 input_model, /*name_policy=*/DIE_ON_DUPLICATE_NONEMPTY_NAMES,
722 /*check_model_validity=*/true, error_message);
723}
724
725namespace {
726// Iterates over all the variable names. See usage.
727class MPVariableNamesIterator {
728 public:
729 explicit MPVariableNamesIterator(const MPModelProto& model) : model_(model) {}
730 int index() const { return index_; }
731 absl::string_view name() const { return model_.variable(index_).name(); }
732 static std::string DescribeIndex(int index) {
733 return absl::StrFormat("variable[%d]", index);
734 }
735 void Advance() { ++index_; }
736 bool AtEnd() const { return index_ == model_.variable_size(); }
737
738 private:
739 const MPModelProto& model_;
740 int index_ = 0;
741};
742
743// Iterates over all the constraint and general_constaint names. See usage.
744class MPConstraintNamesIterator {
745 public:
746 explicit MPConstraintNamesIterator(const MPModelProto& model)
747 : model_(model),
748 // To iterate both on the constraint[] and the general_constraint[]
749 // field, we use the bit trick that i ≥ 0 corresponds to constraint[i],
750 // and i < 0 corresponds to general_constraint[~i = -i-1].
751 index_(model_.constraint().empty() ? ~0 : 0) {}
752 int index() const { return index_; }
753 absl::string_view name() const {
754 return index_ >= 0 ? model_.constraint(index_).name()
755 // As of 2023-04, the only names of MPGeneralConstraints
756 // that are actually ingested are the
757 // MPIndicatorConstraint.constraint.name.
758 : model_.general_constraint(~index_)
759 .indicator_constraint()
760 .constraint()
761 .name();
762 }
763 static std::string DescribeIndex(int index) {
764 return index >= 0
765 ? absl::StrFormat("constraint[%d]", index)
766 : absl::StrFormat(
767 "general_constraint[%d].indicator_constraint.constraint",
768 ~index);
769 }
770 void Advance() {
771 if (index_ >= 0) {
772 if (++index_ == model_.constraint_size()) index_ = ~0;
773 } else {
774 --index_;
775 }
776 }
777 bool AtEnd() const { return ~index_ == model_.general_constraint_size(); }
778
779 private:
780 const MPModelProto& model_;
781 int index_ = 0;
782};
783
784// Looks at the `name` field of all the given MPModelProto fields, and if there
785// is a duplicate name, returns a descriptive error message.
786// If no duplicate is found, returns the empty string.
787template <class NameIterator>
788std::string FindDuplicateNamesError(NameIterator name_iterator) {
789 absl::flat_hash_map<absl::string_view, int> name_to_index;
790 for (; !name_iterator.AtEnd(); name_iterator.Advance()) {
791 if (name_iterator.name().empty()) continue;
792 const int index =
793 name_to_index.insert({name_iterator.name(), name_iterator.index()})
794 .first->second;
795 if (index != name_iterator.index()) {
796 return absl::StrFormat(
797 "Duplicate name '%s' in %s.name() and %s.name()",
798 name_iterator.name(), NameIterator::DescribeIndex(index),
799 NameIterator::DescribeIndex(name_iterator.index()));
800 }
801 }
802 return ""; // No duplicate found.
803}
804} // namespace
805
806MPSolverResponseStatus MPSolver::LoadModelFromProtoInternal(
807 const MPModelProto& input_model, ModelProtoNamesPolicy name_policy,
808 bool check_model_validity, std::string* error_message) {
809 CHECK(error_message != nullptr);
810 std::string error;
811 if (check_model_validity) {
812 error = FindErrorInMPModelProto(input_model);
813 }
814 // We preemptively check for duplicate names even for the
815 // DIE_ON_DUPLICATE_NONEMPTY_NAMES policy, because it yields more informative
816 // error messages than if we wait for InsertIfNotPresent() to crash.
817 if (error.empty() && name_policy != DEFAULT_CLEAR_NAMES) {
818 error = FindDuplicateNamesError(MPVariableNamesIterator(input_model));
819 if (error.empty()) {
820 error = FindDuplicateNamesError(MPConstraintNamesIterator(input_model));
821 }
822 if (!error.empty() && name_policy == DIE_ON_DUPLICATE_NONEMPTY_NAMES) {
823 LOG(FATAL) << error;
824 }
825 }
826 const bool clear_names = name_policy == DEFAULT_CLEAR_NAMES;
827
828 if (!error.empty()) {
829 *error_message = error;
830 LOG_IF(INFO, OutputIsEnabled())
831 << "Invalid model given to LoadModelFromProto(): " << error;
832 if (absl::GetFlag(FLAGS_mpsolver_bypass_model_validation)) {
833 LOG_IF(INFO, OutputIsEnabled())
834 << "Ignoring the model error(s) because of"
835 << " --mpsolver_bypass_model_validation.";
836 } else {
837 return absl::StrContains(error, "Infeasible") ? MPSOLVER_INFEASIBLE
838 : MPSOLVER_MODEL_INVALID;
839 }
840 }
841
842 if (input_model.has_quadratic_objective()) {
843 *error_message =
844 "Optimizing a quadratic objective is only supported through direct "
845 "proto solves. Please use MPSolver::SolveWithProto, or the solver's "
846 "direct proto solve function.";
847 return MPSOLVER_MODEL_INVALID;
848 }
849
850 MPObjective* const objective = MutableObjective();
851 // Passing empty names makes the MPSolver generate unique names.
852 const std::string empty;
853 for (int i = 0; i < input_model.variable_size(); ++i) {
854 const MPVariableProto& var_proto = input_model.variable(i);
855 MPVariable* variable =
856 MakeNumVar(var_proto.lower_bound(), var_proto.upper_bound(),
857 clear_names ? empty : var_proto.name());
858 variable->SetInteger(var_proto.is_integer());
859 if (var_proto.branching_priority() != 0) {
860 variable->SetBranchingPriority(var_proto.branching_priority());
861 }
862 objective->SetCoefficient(variable, var_proto.objective_coefficient());
863 }
864
865 for (const MPConstraintProto& ct_proto : input_model.constraint()) {
866 if (ct_proto.lower_bound() == -infinity() &&
867 ct_proto.upper_bound() == infinity()) {
868 continue;
869 }
870
871 MPConstraint* const ct =
872 MakeRowConstraint(ct_proto.lower_bound(), ct_proto.upper_bound(),
873 clear_names ? empty : ct_proto.name());
874 ct->set_is_lazy(ct_proto.is_lazy());
875 for (int j = 0; j < ct_proto.var_index_size(); ++j) {
876 ct->SetCoefficient(variables_[ct_proto.var_index(j)],
877 ct_proto.coefficient(j));
878 }
879 }
880
881 for (const MPGeneralConstraintProto& general_constraint :
882 input_model.general_constraint()) {
883 switch (general_constraint.general_constraint_case()) {
884 case MPGeneralConstraintProto::kIndicatorConstraint: {
885 const auto& proto =
886 general_constraint.indicator_constraint().constraint();
887 if (proto.lower_bound() == -infinity() &&
888 proto.upper_bound() == infinity()) {
889 continue;
890 }
891
892 const int constraint_index = NumConstraints();
893 MPConstraint* const constraint = new MPConstraint(
894 constraint_index, proto.lower_bound(), proto.upper_bound(),
895 clear_names ? "" : proto.name(), interface_.get());
896 if (constraint_name_to_index_) {
897 gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(),
899 }
900 constraints_.push_back(constraint);
901 constraint_is_extracted_.push_back(false);
902
903 constraint->set_is_lazy(proto.is_lazy());
904 for (int j = 0; j < proto.var_index_size(); ++j) {
905 constraint->SetCoefficient(variables_[proto.var_index(j)],
906 proto.coefficient(j));
907 }
908
909 MPVariable* const variable =
910 variables_[general_constraint.indicator_constraint().var_index()];
911 constraint->indicator_variable_ = variable;
912 constraint->indicator_value_ =
913 general_constraint.indicator_constraint().var_value();
914
915 if (!interface_->AddIndicatorConstraint(constraint)) {
916 *error_message = "Solver doesn't support indicator constraints";
917 return MPSOLVER_MODEL_INVALID;
918 }
919 break;
920 }
921 default:
922 *error_message = absl::StrFormat(
923 "Optimizing general constraints of type %i is only supported "
924 "through direct proto solves. Please use MPSolver::SolveWithProto, "
925 "or the solver's direct proto solve function.",
926 general_constraint.general_constraint_case());
927 return MPSOLVER_MODEL_INVALID;
928 }
929 }
930
931 objective->SetOptimizationDirection(input_model.maximize());
932 if (input_model.has_objective_offset()) {
933 objective->SetOffset(input_model.objective_offset());
934 }
935
936 // Stores any hints about where to start the solve.
937 solution_hint_.clear();
938 for (int i = 0; i < input_model.solution_hint().var_index_size(); ++i) {
939 solution_hint_.push_back(
940 std::make_pair(variables_[input_model.solution_hint().var_index(i)],
941 input_model.solution_hint().var_value(i)));
942 }
943 return MPSOLVER_MODEL_IS_VALID;
944}
945
946namespace {
947MPSolverResponseStatus ResultStatusToMPSolverResponseStatus(
949 switch (status) {
951 return MPSOLVER_OPTIMAL;
953 return MPSOLVER_FEASIBLE;
955 return MPSOLVER_INFEASIBLE;
957 return MPSOLVER_UNBOUNDED;
959 return MPSOLVER_ABNORMAL;
961 return MPSOLVER_MODEL_INVALID;
963 return MPSOLVER_NOT_SOLVED;
964 }
965 return MPSOLVER_UNKNOWN_STATUS;
966}
967} // namespace
968
969void MPSolver::FillSolutionResponseProto(MPSolutionResponse* response) const {
970 CHECK(response != nullptr);
971 response->Clear();
972 response->set_status(
973 ResultStatusToMPSolverResponseStatus(interface_->result_status_));
974 response->mutable_solve_info()->set_solve_wall_time_seconds(wall_time() /
975 1000.0);
976 if (interface_->result_status_ == MPSolver::OPTIMAL ||
977 interface_->result_status_ == MPSolver::FEASIBLE) {
978 response->set_objective_value(Objective().Value());
979 for (MPVariable* variable : variables_) {
980 response->add_variable_value(variable->solution_value());
981 }
982
983 if (interface_->IsMIP()) {
984 response->set_best_objective_bound(interface_->best_objective_bound());
985 } else {
986 // Dual values have no meaning in MIP.
987 for (MPConstraint* constraint : constraints_) {
988 response->add_dual_value(constraint->dual_value());
989 }
990 // Reduced cost have no meaning in MIP.
991 for (MPVariable* variable : variables_) {
992 response->add_reduced_cost(variable->reduced_cost());
993 }
994 }
995 }
996}
997
998namespace {
999bool InCategory(int status, int category) {
1000 if (category == MPSOLVER_OPTIMAL) return status == MPSOLVER_OPTIMAL;
1001 while (status > category) status >>= 4;
1002 return status == category;
1003}
1004
1005void AppendStatusStr(absl::string_view msg, MPSolutionResponse* response) {
1006 response->set_status_str(
1007 absl::StrCat(response->status_str(),
1008 (response->status_str().empty() ? "" : "\n"), msg));
1009}
1010
1011} // namespace
1012
1013// static
1014void MPSolver::SolveWithProto(const MPModelRequest& model_request,
1015 MPSolutionResponse* response,
1016 std::atomic<bool>* interrupt) {
1017 return SolveLazyMutableRequest(model_request, response, interrupt);
1018}
1019
1020// static
1022 MPSolutionResponse* response,
1023 std::atomic<bool>* interrupt) {
1024 CHECK(response != nullptr);
1025
1026 if (interrupt != nullptr &&
1027 !SolverTypeSupportsInterruption(request->solver_type())) {
1028 response->set_status(MPSOLVER_INCOMPATIBLE_OPTIONS);
1029 response->set_status_str(
1030 "Called MPSolver::SolveWithProto with an underlying solver that "
1031 "doesn't support interruption.");
1032 return;
1033 }
1034
1035 MPSolver solver(
1036 request->model().name(),
1037 static_cast<MPSolver::OptimizationProblemType>(request->solver_type()));
1038 if (request->enable_internal_solver_output()) {
1039 solver.EnableOutput();
1040 std::cout << "MPModelRequest info:\n"
1041 << GetMPModelRequestLoggingInfo(*request) << std::endl;
1042 }
1043
1044 // If the solver supports it, we can std::move() the request since we will
1045 // return right after this in all cases.
1046 if (solver.interface_->SupportsDirectlySolveProto(interrupt)) {
1047 *response =
1048 solver.interface_->DirectlySolveProto(std::move(request), interrupt);
1049 return;
1050 }
1051
1052 // Validate and extract model delta. Also deal with trivial problems.
1053 const std::optional<LazyMutableCopy<MPModelProto>> optional_model =
1054 GetMPModelOrPopulateResponse(request, response);
1055 if (!optional_model) return;
1056
1057 std::string error_message;
1058 response->set_status(solver.LoadModelFromProtoInternal(
1059 **optional_model, /*name_policy=*/DEFAULT_CLEAR_NAMES,
1060 /*check_model_validity=*/false, &error_message));
1061 // Even though we don't re-check model validity here, there can be some
1062 // problems found by LoadModelFromProto, eg. unsupported features.
1063 if (response->status() != MPSOLVER_MODEL_IS_VALID) {
1064 response->set_status_str(error_message);
1065 LOG_IF(WARNING, request->enable_internal_solver_output())
1066 << "LoadModelFromProtoInternal() failed even though the model was "
1067 << "valid! Status: "
1068 << ProtoEnumToString<MPSolverResponseStatus>(response->status()) << " ("
1069 << response->status() << "); Error: " << error_message;
1070 return;
1071 }
1072 if (request->has_solver_time_limit_seconds()) {
1073 solver.SetTimeLimit(absl::Seconds(request->solver_time_limit_seconds()));
1074 }
1075 std::string warning_message;
1076 if (request->has_solver_specific_parameters()) {
1078 request->solver_specific_parameters())) {
1079 if (request->ignore_solver_specific_parameters_failure()) {
1080 // We'll add a warning message in status_str after the solve.
1081 warning_message =
1082 "Warning: the solver specific parameters were not successfully "
1083 "applied";
1084 } else {
1085 response->set_status(MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS);
1086 return;
1087 }
1088 }
1089 }
1090
1091 if (interrupt == nullptr) {
1092 // If we don't need interruption support, we can save some overhead by
1093 // running the solve in the current thread.
1094 solver.Solve();
1095 solver.FillSolutionResponseProto(response);
1096 } else {
1097 const absl::Time start_time = absl::Now();
1098 absl::Time interrupt_time;
1099 bool interrupted_by_user = false;
1100 {
1101 absl::Notification solve_finished;
1102 auto polling_func = [&interrupt, &solve_finished, &solver,
1103 &interrupted_by_user, &interrupt_time, &request]() {
1104 constexpr absl::Duration kPollDelay = absl::Microseconds(100);
1105 constexpr absl::Duration kMaxInterruptionDelay = absl::Seconds(10);
1106
1107 while (!interrupt->load()) {
1108 if (solve_finished.HasBeenNotified()) return;
1109 absl::SleepFor(kPollDelay);
1110 }
1111
1112 // If we get here, we received an interruption notification before the
1113 // solve finished "naturally".
1114 solver.InterruptSolve();
1115 interrupt_time = absl::Now();
1116 interrupted_by_user = true;
1117
1118 // SUBTLE: our call to InterruptSolve() can be ignored by the
1119 // underlying solver for several reasons:
1120 // 1) The solver thread doesn't poll its 'interrupted' bit often
1121 // enough and takes too long to realize that it should return, or
1122 // its mere return + FillSolutionResponse() takes too long.
1123 // 2) The user interrupted the solve so early that Solve() hadn't
1124 // really started yet when we called InterruptSolve().
1125 // In case 1), we should just wait a little longer. In case 2), we
1126 // should call InterruptSolve() again, maybe several times. To both
1127 // accommodate cases where the solver takes really a long time to
1128 // react to the interruption, while returning as quickly as possible,
1129 // we poll the solve_finished notification with increasing durations
1130 // and call InterruptSolve again, each time.
1131 for (absl::Duration poll_delay = kPollDelay;
1132 absl::Now() <= interrupt_time + kMaxInterruptionDelay;
1133 poll_delay *= 2) {
1134 if (solve_finished.WaitForNotificationWithTimeout(poll_delay)) {
1135 return;
1136 } else {
1137 solver.InterruptSolve();
1138 }
1139 }
1140
1141 LOG(DFATAL)
1142 << "MPSolver::InterruptSolve() seems to be ignored by the "
1143 "underlying solver, despite repeated calls over at least "
1144 << absl::FormatDuration(kMaxInterruptionDelay)
1145 << ". Solver type used: "
1146 << MPModelRequest_SolverType_Name(request->solver_type());
1147
1148 // Note that in opt builds, the polling thread terminates here with an
1149 // error message, but we let Solve() finish, ignoring the user
1150 // interruption request.
1151 };
1152
1153 // The choice to do polling rather than solving in the second thread is
1154 // not arbitrary, as we want to maintain any custom thread options set by
1155 // the user. They shouldn't matter for polling, but for solving we might
1156 // e.g. use a larger stack.
1157 ThreadPool thread_pool("SolverThread", /*num_threads=*/1);
1158 thread_pool.StartWorkers();
1159 thread_pool.Schedule(polling_func);
1160
1161 // Make sure the interruption notification didn't arrive while waiting to
1162 // be scheduled.
1163 if (!interrupt->load()) {
1164 solver.Solve();
1165 solver.FillSolutionResponseProto(response);
1166 } else { // *interrupt == true
1167 response->set_status(MPSOLVER_CANCELLED_BY_USER);
1168 response->set_status_str(
1169 "Solve not started, because the user set the atomic<bool> in "
1170 "MPSolver::SolveWithProto() to true before solving could "
1171 "start.");
1172 }
1173 solve_finished.Notify();
1174
1175 // We block until the thread finishes when thread_pool goes out of scope.
1176 }
1177
1178 if (interrupted_by_user) {
1179 // Despite the interruption, the solver might still have found a useful
1180 // result. If so, don't overwrite the status.
1181 if (InCategory(response->status(), MPSOLVER_NOT_SOLVED)) {
1182 response->set_status(MPSOLVER_CANCELLED_BY_USER);
1183 }
1184 AppendStatusStr(
1185 absl::StrFormat(
1186 "User interrupted MPSolver::SolveWithProto() by setting the "
1187 "atomic<bool> to true at %s (%s after solving started.)",
1188 absl::FormatTime(interrupt_time),
1189 absl::FormatDuration(interrupt_time - start_time)),
1190 response);
1191 }
1192 }
1193
1194 if (!warning_message.empty()) {
1195 AppendStatusStr(warning_message, response);
1196 }
1197}
1198
1199void MPSolver::ExportModelToProto(MPModelProto* output_model) const {
1200 DCHECK(output_model != nullptr);
1201 output_model->Clear();
1202 // Name
1203 output_model->set_name(Name());
1204 // Variables
1205 for (const MPVariable* var : variables_) {
1206 MPVariableProto* const variable_proto = output_model->add_variable();
1207 // TODO(user): Add option to avoid filling the var name to avoid overly
1208 // large protocol buffers.
1209 variable_proto->set_name(var->name());
1210 variable_proto->set_lower_bound(var->lb());
1211 variable_proto->set_upper_bound(var->ub());
1212 variable_proto->set_is_integer(var->integer());
1213 if (objective_->GetCoefficient(var) != 0.0) {
1214 variable_proto->set_objective_coefficient(
1215 objective_->GetCoefficient(var));
1216 }
1217 if (var->branching_priority() != 0) {
1218 variable_proto->set_branching_priority(var->branching_priority());
1219 }
1220 }
1221
1222 // Map the variables to their indices. This is needed to output the
1223 // variables in the order they were created, which in turn is needed to have
1224 // repeatable results with ExportModelAsLpFormat and ExportModelAsMpsFormat.
1225 // This step is needed as long as the variable indices are given by the
1226 // underlying solver at the time of model extraction.
1227 // TODO(user): remove this step.
1228 absl::flat_hash_map<const MPVariable*, int> var_to_index;
1229 for (int j = 0; j < static_cast<int>(variables_.size()); ++j) {
1230 var_to_index[variables_[j]] = j;
1231 }
1232
1233 // Constraints
1234 for (MPConstraint* const constraint : constraints_) {
1235 MPConstraintProto* constraint_proto;
1236 if (constraint->indicator_variable() != nullptr) {
1237 MPGeneralConstraintProto* const general_constraint_proto =
1238 output_model->add_general_constraint();
1239 general_constraint_proto->set_name(constraint->name());
1240 MPIndicatorConstraint* const indicator_constraint_proto =
1241 general_constraint_proto->mutable_indicator_constraint();
1242 indicator_constraint_proto->set_var_index(
1244 indicator_constraint_proto->set_var_value(constraint->indicator_value());
1245 constraint_proto = indicator_constraint_proto->mutable_constraint();
1246 } else {
1247 constraint_proto = output_model->add_constraint();
1248 }
1249 constraint_proto->set_name(constraint->name());
1250 constraint_proto->set_lower_bound(constraint->lb());
1251 constraint_proto->set_upper_bound(constraint->ub());
1252 constraint_proto->set_is_lazy(constraint->is_lazy());
1253 // Vector linear_term will contain pairs (variable index, coeff), that will
1254 // be sorted by variable index.
1255 std::vector<std::pair<int, double>> linear_term;
1256 for (const auto& entry : constraint->coefficients_) {
1257 const MPVariable* const var = entry.first;
1258 const int var_index = gtl::FindWithDefault(var_to_index, var, -1);
1259 DCHECK_NE(-1, var_index);
1260 const double coeff = entry.second;
1261 linear_term.push_back(std::pair<int, double>(var_index, coeff));
1262 }
1263 // The cost of sort is expected to be low as constraints usually have very
1264 // few terms.
1265 std::sort(linear_term.begin(), linear_term.end());
1266 // Now use linear term.
1267 for (const std::pair<int, double>& var_and_coeff : linear_term) {
1268 constraint_proto->add_var_index(var_and_coeff.first);
1269 constraint_proto->add_coefficient(var_and_coeff.second);
1270 }
1271 }
1272
1273 output_model->set_maximize(Objective().maximization());
1274 output_model->set_objective_offset(Objective().offset());
1275
1276 if (!solution_hint_.empty()) {
1277 PartialVariableAssignment* const hint =
1278 output_model->mutable_solution_hint();
1279 for (const auto& var_value_pair : solution_hint_) {
1280 hint->add_var_index(var_value_pair.first->index());
1281 hint->add_var_value(var_value_pair.second);
1282 }
1283 }
1284}
1285
1286absl::Status MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response,
1287 double tolerance) {
1288 interface_->result_status_ = static_cast<ResultStatus>(response.status());
1289 if (response.status() != MPSOLVER_OPTIMAL &&
1290 response.status() != MPSOLVER_FEASIBLE) {
1291 return absl::InvalidArgumentError(absl::StrCat(
1292 "Cannot load a solution unless its status is OPTIMAL or FEASIBLE"
1293 " (status was: ",
1294 ProtoEnumToString<MPSolverResponseStatus>(response.status()), ")"));
1295 }
1296 // Before touching the variables, verify that the solution looks legit:
1297 // each variable of the MPSolver must have its value listed exactly once, and
1298 // each listed solution should correspond to a known variable.
1299 if (static_cast<size_t>(response.variable_value_size()) !=
1300 variables_.size()) {
1301 return absl::InvalidArgumentError(absl::StrCat(
1302 "Trying to load a solution whose number of variables (",
1303 response.variable_value_size(),
1304 ") does not correspond to the Solver's (", variables_.size(), ")"));
1305 }
1306 interface_->ExtractModel();
1307
1308 if (tolerance != infinity()) {
1309 // Look further: verify that the variable values are within the bounds.
1310 double largest_error = 0;
1311 int num_vars_out_of_bounds = 0;
1312 int last_offending_var = -1;
1313 for (int i = 0; i < response.variable_value_size(); ++i) {
1314 const double var_value = response.variable_value(i);
1315 MPVariable* var = variables_[i];
1316 // TODO(user): Use parameter when they become available in this class.
1317 const double lb_error = var->lb() - var_value;
1318 const double ub_error = var_value - var->ub();
1319 if (lb_error > tolerance || ub_error > tolerance) {
1320 ++num_vars_out_of_bounds;
1321 largest_error = std::max(largest_error, std::max(lb_error, ub_error));
1322 last_offending_var = i;
1323 }
1324 }
1325 if (num_vars_out_of_bounds > 0) {
1326 return absl::InvalidArgumentError(absl::StrCat(
1327 "Loaded a solution whose variables matched the solver's, but ",
1328 num_vars_out_of_bounds, " of ", variables_.size(),
1329 " variables were out of their bounds, by more than the primal"
1330 " tolerance which is: ",
1331 tolerance, ". Max error: ", largest_error, ", last offender var is #",
1332 last_offending_var, ": '", variables_[last_offending_var]->name(),
1333 "'"));
1334 }
1335 }
1336 for (int i = 0; i < response.variable_value_size(); ++i) {
1337 variables_[i]->set_solution_value(response.variable_value(i));
1338 }
1339 if (response.dual_value_size() > 0) {
1340 if (static_cast<size_t>(response.dual_value_size()) !=
1341 constraints_.size()) {
1342 return absl::InvalidArgumentError(absl::StrCat(
1343 "Trying to load a dual solution whose number of entries (",
1344 response.dual_value_size(), ") does not correspond to the Solver's (",
1345 constraints_.size(), ")"));
1346 }
1347 for (int i = 0; i < response.dual_value_size(); ++i) {
1348 constraints_[i]->set_dual_value(response.dual_value(i));
1349 }
1350 }
1351 if (response.reduced_cost_size() > 0) {
1352 if (static_cast<size_t>(response.reduced_cost_size()) !=
1353 variables_.size()) {
1354 return absl::InvalidArgumentError(absl::StrCat(
1355 "Trying to load a reduced cost solution whose number of entries (",
1356 response.reduced_cost_size(),
1357 ") does not correspond to the Solver's (", variables_.size(), ")"));
1358 }
1359 for (int i = 0; i < response.reduced_cost_size(); ++i) {
1360 variables_[i]->set_reduced_cost(response.reduced_cost(i));
1361 }
1362 }
1363 // Set the objective value, if is known.
1364 // NOTE(user): We do not verify the objective, even though we could!
1365 if (response.has_objective_value()) {
1366 interface_->objective_value_ = response.objective_value();
1367 }
1368 if (response.has_best_objective_bound()) {
1369 interface_->best_objective_bound_ = response.best_objective_bound();
1370 }
1371 // Mark the status as SOLUTION_SYNCHRONIZED, so that users may inspect the
1372 // solution normally.
1373 interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
1374 return absl::OkStatus();
1375}
1376
1378 {
1379 absl::MutexLock lock(&global_count_mutex_);
1380 global_num_variables_ += variables_.size();
1381 global_num_constraints_ += constraints_.size();
1382 }
1384 gtl::STLDeleteElements(&variables_);
1385 gtl::STLDeleteElements(&constraints_);
1386 if (variable_name_to_index_) {
1387 variable_name_to_index_->clear();
1388 }
1389 variable_is_extracted_.clear();
1390 if (constraint_name_to_index_) {
1391 constraint_name_to_index_->clear();
1392 }
1393 constraint_is_extracted_.clear();
1394 interface_->Reset();
1395 solution_hint_.clear();
1396}
1397
1398void MPSolver::Reset() { interface_->Reset(); }
1399
1400bool MPSolver::InterruptSolve() { return interface_->InterruptSolve(); }
1401
1403 const std::vector<BasisStatus>& variable_statuses,
1404 const std::vector<BasisStatus>& constraint_statuses) {
1405 interface_->SetStartingLpBasis(variable_statuses, constraint_statuses);
1406}
1407
1408double MPSolver::solver_infinity() { return interface_->infinity(); }
1409
1410MPVariable* MPSolver::MakeVar(double lb, double ub, bool integer,
1411 const std::string& name) {
1412 const int var_index = NumVariables();
1413 MPVariable* v =
1414 new MPVariable(var_index, lb, ub, integer, name, interface_.get());
1415 if (variable_name_to_index_) {
1416 gtl::InsertOrDie(&*variable_name_to_index_, v->name(), var_index);
1417 }
1418 variables_.push_back(v);
1419 variable_is_extracted_.push_back(false);
1420 interface_->AddVariable(v);
1421 return v;
1422}
1423
1424MPVariable* MPSolver::MakeNumVar(double lb, double ub,
1425 const std::string& name) {
1426 return MakeVar(lb, ub, false, name);
1427}
1428
1429MPVariable* MPSolver::MakeIntVar(double lb, double ub,
1430 const std::string& name) {
1431 return MakeVar(lb, ub, true, name);
1432}
1433
1435 return MakeVar(0.0, 1.0, true, name);
1436}
1437
1438void MPSolver::MakeVarArray(int nb, double lb, double ub, bool integer,
1439 const std::string& name,
1440 std::vector<MPVariable*>* vars) {
1441 DCHECK_GE(nb, 0);
1442 if (nb <= 0) return;
1443 const int num_digits = NumDigits(nb);
1444 for (int i = 0; i < nb; ++i) {
1445 if (name.empty()) {
1446 vars->push_back(MakeVar(lb, ub, integer, name));
1447 } else {
1448 std::string vname =
1449 absl::StrFormat("%s%0*d", name.c_str(), num_digits, i);
1450 vars->push_back(MakeVar(lb, ub, integer, vname));
1451 }
1452 }
1453}
1454
1455void MPSolver::MakeNumVarArray(int nb, double lb, double ub,
1456 const std::string& name,
1457 std::vector<MPVariable*>* vars) {
1458 MakeVarArray(nb, lb, ub, false, name, vars);
1459}
1460
1461void MPSolver::MakeIntVarArray(int nb, double lb, double ub,
1462 const std::string& name,
1463 std::vector<MPVariable*>* vars) {
1464 MakeVarArray(nb, lb, ub, true, name, vars);
1465}
1466
1467void MPSolver::MakeBoolVarArray(int nb, const std::string& name,
1468 std::vector<MPVariable*>* vars) {
1469 MakeVarArray(nb, 0.0, 1.0, true, name, vars);
1470}
1471
1473 return MakeRowConstraint(lb, ub, "");
1474}
1475
1479
1481 const std::string& name) {
1482 const int constraint_index = NumConstraints();
1483 MPConstraint* const constraint =
1484 new MPConstraint(constraint_index, lb, ub, name, interface_.get());
1485 if (constraint_name_to_index_) {
1486 gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(),
1488 }
1489 constraints_.push_back(constraint);
1490 constraint_is_extracted_.push_back(false);
1491 interface_->AddRowConstraint(constraint);
1492 return constraint;
1493}
1494
1496 return MakeRowConstraint(-infinity(), infinity(), name);
1497}
1498
1502
1504 const std::string& name) {
1505 CheckLinearExpr(*this, range.linear_expr());
1507 MakeRowConstraint(range.lower_bound(), range.upper_bound(), name);
1508 for (const auto& kv : range.linear_expr().terms()) {
1509 constraint->SetCoefficient(kv.first, kv.second);
1510 }
1511 return constraint;
1512}
1513
1514int MPSolver::ComputeMaxConstraintSize(int min_constraint_index,
1515 int max_constraint_index) const {
1516 int max_constraint_size = 0;
1517 DCHECK_GE(min_constraint_index, 0);
1518 DCHECK_LE(max_constraint_index, constraints_.size());
1519 for (int i = min_constraint_index; i < max_constraint_index; ++i) {
1520 MPConstraint* const ct = constraints_[i];
1521 if (static_cast<int>(ct->coefficients_.size()) > max_constraint_size) {
1522 max_constraint_size = ct->coefficients_.size();
1523 }
1524 }
1525 return max_constraint_size;
1526}
1527
1528bool MPSolver::HasInfeasibleConstraints() const {
1529 bool hasInfeasibleConstraints = false;
1530 for (int i = 0; i < static_cast<int>(constraints_.size()); ++i) {
1531 if (constraints_[i]->lb() > constraints_[i]->ub()) {
1532 LOG(WARNING) << "Constraint " << constraints_[i]->name() << " (" << i
1533 << ") has contradictory bounds:" << " lower bound = "
1534 << constraints_[i]->lb()
1535 << " upper bound = " << constraints_[i]->ub();
1536 hasInfeasibleConstraints = true;
1537 }
1538 }
1539 return hasInfeasibleConstraints;
1540}
1541
1542bool MPSolver::HasIntegerVariables() const {
1543 for (const MPVariable* const variable : variables_) {
1544 if (variable->integer()) return true;
1545 }
1546 return false;
1547}
1548
1550 MPSolverParameters default_param;
1551 return Solve(default_param);
1552}
1553
1555 // Special case for infeasible constraints so that all solvers have
1556 // the same behavior.
1557 // TODO(user): replace this by model extraction to proto + proto validation
1558 // (the proto has very low overhead compared to the wrapper, both in
1559 // performance and memory, so it's ok).
1560 if (HasInfeasibleConstraints()) {
1561 interface_->result_status_ = MPSolver::INFEASIBLE;
1562 return interface_->result_status_;
1563 }
1564
1565 MPSolver::ResultStatus status = interface_->Solve(param);
1566 if (absl::GetFlag(FLAGS_verify_solution)) {
1568 VLOG(1) << "--verify_solution enabled, but the solver did not find a"
1569 << " solution: skipping the verification.";
1570 } else if (!VerifySolution(
1572 absl::GetFlag(FLAGS_log_verification_errors))) {
1574 interface_->result_status_ = status;
1575 }
1576 }
1577 DCHECK_EQ(interface_->result_status_, status);
1578 return status;
1579}
1580
1581void MPSolver::Write(const std::string& file_name) {
1582 interface_->Write(file_name);
1583}
1584
1585namespace {
1586std::string PrettyPrintVar(const MPVariable& var) {
1587 const std::string prefix = "Variable '" + var.name() + "': domain = ";
1588 if (var.lb() >= MPSolver::infinity() || var.ub() <= -MPSolver::infinity() ||
1589 var.lb() > var.ub()) {
1590 return prefix + "∅"; // Empty set.
1591 }
1592 // Special case: integer variable with at most two possible values
1593 // (and potentially none).
1594 if (var.integer() && var.ub() - var.lb() <= 1) {
1595 const int64_t lb = static_cast<int64_t>(ceil(var.lb()));
1596 const int64_t ub = static_cast<int64_t>(floor(var.ub()));
1597 if (lb > ub) {
1598 return prefix + "∅";
1599 } else if (lb == ub) {
1600 return absl::StrFormat("%s{ %d }", prefix.c_str(), lb);
1601 } else {
1602 return absl::StrFormat("%s{ %d, %d }", prefix.c_str(), lb, ub);
1603 }
1604 }
1605 // Special case: single (non-infinite) real value.
1606 if (var.lb() == var.ub()) {
1607 return absl::StrFormat("%s{ %f }", prefix.c_str(), var.lb());
1608 }
1609 return prefix + (var.integer() ? "Integer" : "Real") + " in " +
1610 (var.lb() <= -MPSolver::infinity()
1611 ? std::string("]-∞")
1612 : absl::StrFormat("[%f", var.lb())) +
1613 ", " +
1614 (var.ub() >= MPSolver::infinity() ? std::string("+∞[")
1615 : absl::StrFormat("%f]", var.ub()));
1616}
1617
1618std::string PrettyPrintConstraint(const MPConstraint& constraint) {
1619 std::string prefix = "Constraint '" + constraint.name() + "': ";
1620 if (constraint.lb() >= MPSolver::infinity() ||
1621 constraint.ub() <= -MPSolver::infinity() ||
1622 constraint.lb() > constraint.ub()) {
1623 return prefix + "ALWAYS FALSE";
1624 }
1625 if (constraint.lb() <= -MPSolver::infinity() &&
1626 constraint.ub() >= MPSolver::infinity()) {
1627 return prefix + "ALWAYS TRUE";
1628 }
1629 prefix += "<linear expr>";
1630 // Equality.
1631 if (constraint.lb() == constraint.ub()) {
1632 return absl::StrFormat("%s = %f", prefix.c_str(), constraint.lb());
1633 }
1634 // Inequalities.
1635 if (constraint.lb() <= -MPSolver::infinity()) {
1636 return absl::StrFormat("%s ≤ %f", prefix.c_str(), constraint.ub());
1637 }
1638 if (constraint.ub() >= MPSolver::infinity()) {
1639 return absl::StrFormat("%s ≥ %f", prefix.c_str(), constraint.lb());
1640 }
1641 return absl::StrFormat("%s ∈ [%f, %f]", prefix.c_str(), constraint.lb(),
1642 constraint.ub());
1643}
1644} // namespace
1645
1647 interface_->ExtractModel();
1648 for (MPVariable* const variable : variables_) {
1649 const double value = variable->solution_value();
1650 if (std::isnan(value)) {
1651 return absl::InvalidArgumentError(
1652 absl::StrCat("NaN value for ", PrettyPrintVar(*variable)));
1653 }
1654 if (value < variable->lb()) {
1656 } else if (value > variable->ub()) {
1658 }
1659 }
1660 interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
1661 return absl::OkStatus();
1662}
1663
1664std::vector<double> MPSolver::ComputeConstraintActivities() const {
1665 // TODO(user): test this failure case.
1666 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return {};
1667 std::vector<double> activities(constraints_.size(), 0.0);
1668 for (int i = 0; i < static_cast<int>(constraints_.size()); ++i) {
1669 const MPConstraint& constraint = *constraints_[i];
1671 for (const auto& entry : constraint.coefficients_) {
1672 sum.Add(entry.first->solution_value() * entry.second);
1673 }
1674 activities[i] = sum.Value();
1675 }
1676 return activities;
1677}
1678
1679// TODO(user): split.
1680bool MPSolver::VerifySolution(double tolerance, bool log_errors) const {
1681 double max_observed_error = 0;
1682 if (tolerance < 0) tolerance = infinity();
1683 int num_errors = 0;
1684
1685 // Verify variables.
1686 for (MPVariable* variable : variables_) {
1687 const MPVariable& var = *variable;
1688 const double value = var.solution_value();
1689 // Check for NaN.
1690 if (std::isnan(value)) {
1691 ++num_errors;
1692 max_observed_error = infinity();
1693 LOG_IF(ERROR, log_errors) << "NaN value for " << PrettyPrintVar(var);
1694 continue;
1695 }
1696 // Check lower bound.
1697 if (var.lb() != -infinity()) {
1698 if (value < var.lb() - tolerance) {
1699 ++num_errors;
1700 max_observed_error = std::max(max_observed_error, var.lb() - value);
1701 LOG_IF(ERROR, log_errors)
1702 << "Value " << value << " too low for " << PrettyPrintVar(var);
1703 }
1704 }
1705 // Check upper bound.
1706 if (var.ub() != infinity()) {
1707 if (value > var.ub() + tolerance) {
1708 ++num_errors;
1709 max_observed_error = std::max(max_observed_error, value - var.ub());
1710 LOG_IF(ERROR, log_errors)
1711 << "Value " << value << " too high for " << PrettyPrintVar(var);
1712 }
1713 }
1714 // Check integrality.
1715 if (IsMIP() && var.integer()) {
1716 if (fabs(value - round(value)) > tolerance) {
1717 ++num_errors;
1718 max_observed_error =
1719 std::max(max_observed_error, fabs(value - round(value)));
1720 LOG_IF(ERROR, log_errors)
1721 << "Non-integer value " << value << " for " << PrettyPrintVar(var);
1722 }
1723 }
1724 }
1725 if (!IsMIP() && HasIntegerVariables()) {
1726 LOG_IF(INFO, log_errors) << "Skipped variable integrality check, because "
1727 << "a continuous relaxation of the model was "
1728 << "solved (i.e., the selected solver does not "
1729 << "support integer variables).";
1730 }
1731
1732 // Verify constraints.
1733 const std::vector<double> activities = ComputeConstraintActivities();
1734 for (int i = 0; i < static_cast<int>(constraints_.size()); ++i) {
1735 const MPConstraint& constraint = *constraints_[i];
1736 const double activity = activities[i];
1737 // Re-compute the activity with a inaccurate summing algorithm.
1738 double inaccurate_activity = 0.0;
1739 for (const auto& entry : constraint.coefficients_) {
1740 inaccurate_activity += entry.first->solution_value() * entry.second;
1741 }
1742 // Catch NaNs.
1743 if (std::isnan(activity) || std::isnan(inaccurate_activity)) {
1744 ++num_errors;
1745 max_observed_error = infinity();
1746 LOG_IF(ERROR, log_errors)
1747 << "NaN value for " << PrettyPrintConstraint(constraint);
1748 continue;
1749 }
1750 // Check bounds.
1751 if (constraint.indicator_variable() == nullptr ||
1752 std::round(constraint.indicator_variable()->solution_value()) ==
1754 if (constraint.lb() != -infinity()) {
1755 if (activity < constraint.lb() - tolerance) {
1756 ++num_errors;
1757 max_observed_error =
1758 std::max(max_observed_error, constraint.lb() - activity);
1759 LOG_IF(ERROR, log_errors)
1760 << "Activity " << activity << " too low for "
1761 << PrettyPrintConstraint(constraint);
1762 } else if (inaccurate_activity < constraint.lb() - tolerance) {
1763 LOG_IF(WARNING, log_errors)
1764 << "Activity " << activity << ", computed with the (inaccurate)"
1765 << " standard sum of its terms, is too low for "
1766 << PrettyPrintConstraint(constraint);
1767 }
1768 }
1769 if (constraint.ub() != infinity()) {
1770 if (activity > constraint.ub() + tolerance) {
1771 ++num_errors;
1772 max_observed_error =
1773 std::max(max_observed_error, activity - constraint.ub());
1774 LOG_IF(ERROR, log_errors)
1775 << "Activity " << activity << " too high for "
1776 << PrettyPrintConstraint(constraint);
1777 } else if (inaccurate_activity > constraint.ub() + tolerance) {
1778 LOG_IF(WARNING, log_errors)
1779 << "Activity " << activity << ", computed with the (inaccurate)"
1780 << " standard sum of its terms, is too high for "
1781 << PrettyPrintConstraint(constraint);
1782 }
1783 }
1784 }
1785 }
1786
1787 // Verify that the objective value wasn't reported incorrectly.
1788 const MPObjective& objective = Objective();
1789 AccurateSum<double> objective_sum;
1790 objective_sum.Add(objective.offset());
1791 double inaccurate_objective_value = objective.offset();
1792 for (const auto& entry : objective.coefficients_) {
1793 const double term = entry.first->solution_value() * entry.second;
1794 objective_sum.Add(term);
1795 inaccurate_objective_value += term;
1796 }
1797 const double actual_objective_value = objective_sum.Value();
1799 objective.Value(), actual_objective_value, tolerance, tolerance)) {
1800 ++num_errors;
1801 max_observed_error = std::max(
1802 max_observed_error, fabs(actual_objective_value - objective.Value()));
1803 LOG_IF(ERROR, log_errors)
1804 << "Objective value " << objective.Value() << " isn't accurate"
1805 << ", it should be " << actual_objective_value
1806 << " (delta=" << actual_objective_value - objective.Value() << ").";
1807 } else if (!AreWithinAbsoluteOrRelativeTolerances(objective.Value(),
1808 inaccurate_objective_value,
1809 tolerance, tolerance)) {
1810 LOG_IF(WARNING, log_errors)
1811 << "Objective value " << objective.Value() << " doesn't correspond"
1812 << " to the value computed with the standard (and therefore inaccurate)"
1813 << " sum of its terms.";
1814 }
1815 if (num_errors > 0) {
1816 LOG_IF(ERROR, log_errors)
1817 << "There were " << num_errors << " errors above the tolerance ("
1818 << tolerance << "), the largest was " << max_observed_error;
1819 return false;
1820 }
1821 return true;
1822}
1823
1824bool MPSolver::OutputIsEnabled() const { return !interface_->quiet(); }
1825
1826void MPSolver::EnableOutput() { interface_->set_quiet(false); }
1827
1828void MPSolver::SuppressOutput() { interface_->set_quiet(true); }
1829
1830int64_t MPSolver::iterations() const { return interface_->iterations(); }
1831
1832int64_t MPSolver::nodes() const { return interface_->nodes(); }
1833
1835 return interface_->ComputeExactConditionNumber();
1836}
1837
1839 if (var == nullptr) return false;
1840 if (var->index() >= 0 && var->index() < static_cast<int>(variables_.size())) {
1841 // Then, verify that the variable with this index has the same address.
1842 return variables_[var->index()] == var;
1843 }
1844 return false;
1845}
1846
1848 std::string* model_str) const {
1849 MPModelProto proto;
1851 MPModelExportOptions options;
1852 options.obfuscate = obfuscate;
1853 const auto status_or =
1855 *model_str = status_or.value_or("");
1856 return status_or.ok();
1857}
1858
1859bool MPSolver::ExportModelAsMpsFormat(bool fixed_format, bool obfuscate,
1860 std::string* model_str) const {
1861 MPModelProto proto;
1863 MPModelExportOptions options;
1864 options.obfuscate = obfuscate;
1865 const auto status_or =
1867 *model_str = status_or.value_or("");
1868 return status_or.ok();
1869}
1870
1871void MPSolver::SetHint(std::vector<std::pair<const MPVariable*, double>> hint) {
1872 for (const auto& var_value_pair : hint) {
1873 CHECK(OwnsVariable(var_value_pair.first))
1874 << "hint variable does not belong to this solver";
1875 }
1876 solution_hint_ = std::move(hint);
1877}
1878
1879void MPSolver::GenerateVariableNameIndex() const {
1880 if (variable_name_to_index_) return;
1881 variable_name_to_index_ = absl::flat_hash_map<std::string, int>();
1882 for (const MPVariable* const var : variables_) {
1883 gtl::InsertOrDie(&*variable_name_to_index_, var->name(), var->index());
1884 }
1885}
1886
1887void MPSolver::GenerateConstraintNameIndex() const {
1888 if (constraint_name_to_index_) return;
1889 constraint_name_to_index_ = absl::flat_hash_map<std::string, int>();
1890 for (const MPConstraint* const cst : constraints_) {
1891 gtl::InsertOrDie(&*constraint_name_to_index_, cst->name(), cst->index());
1892 }
1893}
1894
1895bool MPSolver::NextSolution() { return interface_->NextSolution(); }
1896
1898 interface_->SetCallback(mp_callback);
1899}
1900
1902 return interface_->SupportsCallbacks();
1903}
1904
1905// Global counters.
1906absl::Mutex MPSolver::global_count_mutex_(absl::kConstInit);
1907int64_t MPSolver::global_num_variables_ = 0;
1908int64_t MPSolver::global_num_constraints_ = 0;
1909
1910// static
1912 absl::MutexLock lock(&global_count_mutex_);
1913 return global_num_variables_;
1914}
1915
1916// static
1918 absl::MutexLock lock(&global_count_mutex_);
1919 return global_num_constraints_;
1920}
1921
1922bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status) {
1923 switch (status) {
1924 // Cases that don't yield an RPC error when they happen on the server.
1925 case MPSOLVER_OPTIMAL:
1926 case MPSOLVER_FEASIBLE:
1927 case MPSOLVER_INFEASIBLE:
1928 case MPSOLVER_NOT_SOLVED:
1929 case MPSOLVER_UNBOUNDED:
1930 case MPSOLVER_ABNORMAL:
1931 case MPSOLVER_UNKNOWN_STATUS:
1932 return false;
1933 // Cases that should never happen with the linear solver server. We prefer
1934 // to consider those as "not RPC errors".
1935 case MPSOLVER_MODEL_IS_VALID:
1936 case MPSOLVER_CANCELLED_BY_USER:
1937 return false;
1938 // Cases that yield an RPC error when they happen on the server.
1939 case MPSOLVER_MODEL_INVALID:
1940 case MPSOLVER_MODEL_INVALID_SOLUTION_HINT:
1941 case MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS:
1942 case MPSOLVER_SOLVER_TYPE_UNAVAILABLE:
1943 case MPSOLVER_INCOMPATIBLE_OPTIONS:
1944 return true;
1945 }
1946 LOG(DFATAL)
1947 << "MPSolverResponseStatusIsRpcError() called with invalid status "
1948 << "(value: " << status << ")";
1949 return false;
1950}
1951
1952// ---------- MPSolverInterface ----------
1953
1955
1956// TODO(user): Initialize objective value and bound to +/- inf (depending on
1957// optimization direction).
1959 : solver_(solver),
1960 sync_status_(MODEL_SYNCHRONIZED),
1961 result_status_(MPSolver::NOT_SOLVED),
1962 maximize_(false),
1963 last_constraint_index_(0),
1964 last_variable_index_(0),
1965 objective_value_(0.0),
1966 best_objective_bound_(0.0),
1967 quiet_(true) {}
1968
1970
1971void MPSolverInterface::Write(const std::string& filename) {
1972 LOG(WARNING) << "Writing model not implemented in this solver interface.";
1973}
1974
1976 switch (sync_status_) {
1977 case MUST_RELOAD: {
1981
1982 last_constraint_index_ = solver_->constraints_.size();
1983 last_variable_index_ = solver_->variables_.size();
1985 break;
1986 }
1987 case MODEL_SYNCHRONIZED: {
1988 // Everything has already been extracted.
1989 DCHECK_EQ(last_constraint_index_, solver_->constraints_.size());
1990 DCHECK_EQ(last_variable_index_, solver_->variables_.size());
1991 break;
1992 }
1993 case SOLUTION_SYNCHRONIZED: {
1994 // Nothing has changed since last solve.
1995 DCHECK_EQ(last_constraint_index_, solver_->constraints_.size());
1996 DCHECK_EQ(last_variable_index_, solver_->variables_.size());
1997 break;
1998 }
1999 }
2000}
2001
2002// TODO(user): remove this method.
2007 solver_->variable_is_extracted_.assign(solver_->variables_.size(), false);
2008 solver_->constraint_is_extracted_.assign(solver_->constraints_.size(), false);
2009}
2010
2013 LOG(DFATAL)
2014 << "The model has been changed since the solution was last computed."
2015 << " MPSolverInterface::sync_status_ = " << sync_status_;
2016 return false;
2017 }
2018 return true;
2019}
2020
2021// Default version that can be overwritten by a solver-specific
2022// version to accommodate for the quirks of each solver.
2026 LOG(DFATAL) << "No solution exists. MPSolverInterface::result_status_ = "
2027 << result_status_;
2028 return false;
2029 }
2030 return true;
2031}
2032
2034 if (!CheckSolutionIsSynchronizedAndExists()) return 0;
2035 return objective_value_;
2036}
2037
2039 const double trivial_worst_bound =
2040 maximize_ ? -std::numeric_limits<double>::infinity()
2041 : std::numeric_limits<double>::infinity();
2042 if (!IsMIP()) {
2043 VLOG(1) << "Best objective bound only available for discrete problems.";
2044 return trivial_worst_bound;
2045 }
2047 return trivial_worst_bound;
2048 }
2049 // Special case for empty model.
2050 if (solver_->variables_.empty() && solver_->constraints_.empty()) {
2051 return solver_->Objective().offset();
2052 }
2053 return best_objective_bound_;
2054}
2055
2061
2063 // Override this method in interfaces that actually support it.
2064 LOG(DFATAL) << "ComputeExactConditionNumber not implemented for "
2066 static_cast<MPModelRequest::SolverType>(
2067 solver_->ProblemType()));
2068 return 0.0;
2069}
2070
2072 // TODO(user): Overhaul the code that sets parameters to enable changing
2073 // GLOP parameters without issuing warnings.
2074 // By default, we let GLOP keep its own default tolerance, much more accurate
2075 // than for the rest of the solvers.
2076 //
2081 }
2083 // TODO(user): In the future, we could distinguish between the
2084 // algorithm to solve the root LP and the algorithm to solve node
2085 // LPs. Not sure if underlying solvers support it.
2089 }
2090}
2091
2098
2101 LOG(WARNING) << "Trying to set an unsupported parameter: " << param << ".";
2102}
2105 LOG(WARNING) << "Trying to set an unsupported parameter: " << param << ".";
2106}
2108 MPSolverParameters::DoubleParam param, double value) {
2109 LOG(WARNING) << "Trying to set a supported parameter: " << param
2110 << " to an unsupported value: " << value;
2111}
2114 LOG(WARNING) << "Trying to set a supported parameter: " << param
2115 << " to an unsupported value: " << value;
2116}
2117
2118absl::Status MPSolverInterface::SetNumThreads(int num_threads) {
2119 return absl::UnimplementedError(
2120 absl::StrFormat("SetNumThreads() not supported by %s.", SolverVersion()));
2121}
2122
2124 const std::string& parameters) {
2125 if (parameters.empty()) {
2126 return true;
2127 }
2128
2129 LOG(WARNING) << "SetSolverSpecificParametersAsString() not supported by "
2130 << SolverVersion();
2131 return false;
2132}
2133
2134// ---------- MPSolverParameters ----------
2135
2137// For the primal and dual tolerances, choose the same default as CLP and GLPK.
2146
2151
2152// The constructor sets all parameters to their default value.
2154 : relative_mip_gap_value_(kDefaultRelativeMipGap),
2155 primal_tolerance_value_(kDefaultPrimalTolerance),
2156 dual_tolerance_value_(kDefaultDualTolerance),
2157 presolve_value_(kDefaultPresolve),
2158 scaling_value_(kDefaultIntegerParamValue),
2159 lp_algorithm_value_(kDefaultIntegerParamValue),
2160 incrementality_value_(kDefaultIncrementality),
2161 lp_algorithm_is_default_(true) {}
2162
2164 double value) {
2165 switch (param) {
2166 case RELATIVE_MIP_GAP: {
2167 relative_mip_gap_value_ = value;
2168 break;
2169 }
2170 case PRIMAL_TOLERANCE: {
2171 primal_tolerance_value_ = value;
2172 break;
2173 }
2174 case DUAL_TOLERANCE: {
2175 dual_tolerance_value_ = value;
2176 break;
2177 }
2178 default: {
2179 LOG(ERROR) << "Trying to set an unknown parameter: " << param << ".";
2180 }
2181 }
2182}
2183
2185 int value) {
2186 switch (param) {
2187 case PRESOLVE: {
2188 if (value != PRESOLVE_OFF && value != PRESOLVE_ON) {
2189 LOG(ERROR) << "Trying to set a supported parameter: " << param
2190 << " to an unknown value: " << value;
2191 }
2192 presolve_value_ = value;
2193 break;
2194 }
2195 case SCALING: {
2196 if (value != SCALING_OFF && value != SCALING_ON) {
2197 LOG(ERROR) << "Trying to set a supported parameter: " << param
2198 << " to an unknown value: " << value;
2199 }
2200 scaling_value_ = value;
2201 break;
2202 }
2203 case LP_ALGORITHM: {
2204 if (value != DUAL && value != PRIMAL && value != BARRIER) {
2205 LOG(ERROR) << "Trying to set a supported parameter: " << param
2206 << " to an unknown value: " << value;
2207 }
2208 lp_algorithm_value_ = value;
2209 lp_algorithm_is_default_ = false;
2210 break;
2211 }
2212 case INCREMENTALITY: {
2214 LOG(ERROR) << "Trying to set a supported parameter: " << param
2215 << " to an unknown value: " << value;
2216 }
2217 incrementality_value_ = value;
2218 break;
2219 }
2220 default: {
2221 LOG(ERROR) << "Trying to set an unknown parameter: " << param << ".";
2222 }
2223 }
2224}
2225
2228 switch (param) {
2229 case RELATIVE_MIP_GAP: {
2230 relative_mip_gap_value_ = kDefaultRelativeMipGap;
2231 break;
2232 }
2233 case PRIMAL_TOLERANCE: {
2234 primal_tolerance_value_ = kDefaultPrimalTolerance;
2235 break;
2236 }
2237 case DUAL_TOLERANCE: {
2238 dual_tolerance_value_ = kDefaultDualTolerance;
2239 break;
2240 }
2241 default: {
2242 LOG(ERROR) << "Trying to reset an unknown parameter: " << param << ".";
2243 }
2244 }
2245}
2246
2249 switch (param) {
2250 case PRESOLVE: {
2251 presolve_value_ = kDefaultPresolve;
2252 break;
2253 }
2254 case SCALING: {
2255 scaling_value_ = kDefaultIntegerParamValue;
2256 break;
2257 }
2258 case LP_ALGORITHM: {
2259 lp_algorithm_is_default_ = true;
2260 break;
2261 }
2262 case INCREMENTALITY: {
2263 incrementality_value_ = kDefaultIncrementality;
2264 break;
2265 }
2266 default: {
2267 LOG(ERROR) << "Trying to reset an unknown parameter: " << param << ".";
2268 }
2269 }
2270}
2271
2281
2283 MPSolverParameters::DoubleParam param) const {
2284 switch (param) {
2285 case RELATIVE_MIP_GAP: {
2286 return relative_mip_gap_value_;
2287 }
2288 case PRIMAL_TOLERANCE: {
2289 return primal_tolerance_value_;
2290 }
2291 case DUAL_TOLERANCE: {
2292 return dual_tolerance_value_;
2293 }
2294 default: {
2295 LOG(ERROR) << "Trying to get an unknown parameter: " << param << ".";
2297 }
2298 }
2299}
2300
2303 switch (param) {
2304 case PRESOLVE: {
2305 return presolve_value_;
2306 }
2307 case LP_ALGORITHM: {
2308 if (lp_algorithm_is_default_) return kDefaultIntegerParamValue;
2309 return lp_algorithm_value_;
2310 }
2311 case INCREMENTALITY: {
2312 return incrementality_value_;
2313 }
2314 case SCALING: {
2315 return scaling_value_;
2316 }
2317 default: {
2318 LOG(ERROR) << "Trying to get an unknown parameter: " << param << ".";
2320 }
2321 }
2322}
2323
2324// static
2326 const MPModelRequest& request) {
2327 std::string out;
2328#if !defined(__PORTABLE_PLATFORM__)
2329 MPModelRequest abbreviated_request;
2330 abbreviated_request = request;
2331 abbreviated_request.clear_model();
2332 abbreviated_request.clear_model_delta();
2333 google::protobuf::TextFormat::PrintToString(abbreviated_request, &out);
2334#else // __PORTABLE_PLATFORM__
2335 out = "<Info unavailable because: __PORTABLE_PLATFORM__>\n";
2336#endif // __PORTABLE_PLATFORM__
2337 if (request.model().has_name()) {
2338 absl::StrAppendFormat(&out, "model_name: '%s'\n", request.model().name());
2339 }
2340 return out;
2341}
2342
2343} // namespace operations_research
void Add(const FpNumber &value)
Adds an FpNumber to the sum.
FpNumber Value() const
Gets the value of the sum.
const absl::flat_hash_map< const MPVariable *, double > & terms() const
bool is_lazy() const
Advanced usage: returns true if the constraint is "lazy" (see below).
void Clear()
Clears all variables and coefficients. Does not clear the bounds.
void SetCoefficient(const MPVariable *var, double coeff)
double lb() const
Returns the lower bound.
double ub() const
Returns the upper bound.
MPSolver::BasisStatus basis_status() const
const MPVariable * indicator_variable() const
double GetCoefficient(const MPVariable *var) const
const std::string & name() const
Returns the name of the constraint.
void SetBounds(double lb, double ub)
Sets both the lower and upper bounds.
A class to express a linear objective.
void SetCoefficient(const MPVariable *var, double coeff)
double GetCoefficient(const MPVariable *var) const
--— MPObjective --—
void SetOffset(double value)
Sets the constant term in the objective.
bool minimization() const
Is the optimization direction set to minimize?
bool maximization() const
Is the optimization direction set to maximize?
double offset() const
Gets the constant term in the objective.
void AddLinearExpr(const LinearExpr &linear_expr)
Adds linear_expr to the current objective, does not change the direction.
void OptimizeLinearExpr(const LinearExpr &linear_expr, bool is_maximization)
void SetOptimizationDirection(bool maximize)
Sets the optimization direction (maximize: true or minimize: false).
void SetMinimization()
Sets the optimization direction to minimize.
virtual void SetPrimalTolerance(double value)=0
virtual void ExtractNewConstraints()=0
Extracts the constraints that have not been extracted yet.
virtual void ClearObjective()=0
Clears the objective from all its terms.
virtual void ExtractObjective()=0
Extracts the objective.
virtual void SetRelativeMipGap(double value)=0
Sets each parameter in the underlying solver.
virtual void SetObjectiveCoefficient(const MPVariable *variable, double coefficient)=0
Changes a coefficient in the linear objective.
virtual bool IsMIP() const =0
Returns true if the problem is discrete and linear.
void ResetExtractionInformation()
Resets the extraction information.
int last_variable_index_
Index in MPSolver::constraints_ of last variable extracted.
virtual void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param, int value)
Sets a supported integer parameter to an unsupported value.
int last_variable_index() const
Returns the index of the last variable extracted.
virtual void SetPresolveMode(int value)=0
int last_constraint_index_
Index in MPSolver::variables_ of last constraint extracted.
virtual MPSolver::BasisStatus column_status(int variable_index) const =0
Returns the basis status of a constraint.
virtual void SetCoefficient(MPConstraint *constraint, const MPVariable *variable, double new_value, double old_value)=0
Changes a coefficient in a constraint.
virtual std::string SolverVersion() const =0
Returns a string describing the underlying solver and its version.
virtual void SetVariableInteger(int index, bool integer)=0
Modifies integrality of an extracted variable.
virtual absl::Status SetNumThreads(int num_threads)
Sets the number of threads to be used by the solver.
virtual void BranchingPriorityChangedForVariable(int)
virtual bool SetSolverSpecificParametersAsString(const std::string &parameters)
static const int kDummyVariableIndex
-------— MPSolverInterface -------—
virtual void SetObjectiveOffset(double value)=0
Changes the constant term in the linear objective.
virtual bool IsContinuous() const =0
virtual void SetLpAlgorithm(int value)=0
virtual void SetUnsupportedIntegerParam(MPSolverParameters::IntegerParam param)
Sets an unsupported integer parameter.
bool variable_is_extracted(int var_index) const
void SetUnsupportedDoubleParam(MPSolverParameters::DoubleParam param)
Sets an unsupported double parameter.
bool constraint_is_extracted(int ct_index) const
virtual void ExtractNewVariables()=0
Extracts the variables that have not been extracted yet.
double objective_value() const
Returns the objective value of the best solution found so far.
void ExtractModel()
Extracts model stored in MPSolver.
double objective_value_
The value of the objective function.
double best_objective_bound_
The value of the best objective bound. Used only for MIP solvers.
void SetDoubleParamToUnsupportedValue(MPSolverParameters::DoubleParam param, double value)
Sets a supported double parameter to an unsupported value.
bool maximize_
Optimization direction.
virtual double ComputeExactConditionNumber() const
virtual MPSolver::BasisStatus row_status(int constraint_index) const =0
Returns the basis status of a row.
virtual void Write(const std::string &filename)
void SetMIPParameters(const MPSolverParameters &param)
Sets MIP specific parameters in the underlying solver.
virtual void SetDualTolerance(double value)=0
virtual void SetOptimizationDirection(bool maximize)=0
Sets the optimization direction (min/max).
void SetCommonParameters(const MPSolverParameters &param)
Sets parameters common to LP and MIP in the underlying solver.
virtual void ClearConstraint(MPConstraint *constraint)=0
Clears a constraint from all its terms.
bool CheckSolutionIsSynchronizedAndExists() const
Handy shortcut to do both checks above (it is often used).
virtual void SetConstraintBounds(int index, double lb, double ub)=0
Modify bounds of an extracted variable.
SynchronizationStatus sync_status_
Indicates whether the model and the solution are synchronized.
virtual void SetVariableBounds(int index, double lb, double ub)=0
Modifies bounds of an extracted variable.
static const PresolveValues kDefaultPresolve
void ResetIntegerParam(MPSolverParameters::IntegerParam param)
static const double kDefaultPrimalTolerance
For the primal and dual tolerances, choose the same default as CLP and GLPK.
DoubleParam
Enumeration of parameters that take continuous values.
@ DUAL_TOLERANCE
Advanced usage: tolerance for dual feasibility of basic solutions.
@ RELATIVE_MIP_GAP
Limit for relative MIP gap.
double GetDoubleParam(MPSolverParameters::DoubleParam param) const
Returns the value of a double parameter.
PresolveValues
For each categorical parameter, enumeration of possible values.
static const double kUnknownDoubleParamValue
Placeholder value to indicate that a parameter is unknown.
void ResetDoubleParam(MPSolverParameters::DoubleParam param)
void SetDoubleParam(MPSolverParameters::DoubleParam param, double value)
Sets a double parameter to a specific value.
IntegerParam
Enumeration of parameters that take integer or categorical values.
@ INCREMENTALITY
Advanced usage: incrementality from one solve to the next.
@ PRESOLVE
Advanced usage: presolve mode.
@ LP_ALGORITHM
Algorithm to solve linear programs.
@ SCALING
Advanced usage: enable or disable matrix scaling.
MPSolverParameters()
The constructor sets all parameters to their default value.
static const IncrementalityValues kDefaultIncrementality
IncrementalityValues
Advanced usage: Incrementality options.
@ INCREMENTALITY_OFF
Start solve from scratch.
static const double kDefaultRelativeMipGap
-------— MPSolverParameters -------—
void Reset()
Sets all parameters to their default value.
int GetIntegerParam(MPSolverParameters::IntegerParam param) const
Returns the value of an integer parameter.
void SetIntegerParam(MPSolverParameters::IntegerParam param, int value)
Sets a integer parameter to a specific value.
static void SolveLazyMutableRequest(LazyMutableCopy< MPModelRequest > request, MPSolutionResponse *response, std::atomic< bool > *interrupt=nullptr)
static
int64_t iterations() const
Returns the number of simplex iterations.
int NumVariables() const
Returns the number of variables.
static MPSolver * CreateSolver(const std::string &solver_id)
void SetStartingLpBasis(const std::vector< MPSolver::BasisStatus > &variable_statuses, const std::vector< MPSolver::BasisStatus > &constraint_statuses)
MPVariable * MakeVar(double lb, double ub, bool integer, const std::string &name)
MPVariable * MakeIntVar(double lb, double ub, const std::string &name)
Creates an integer variable.
int NumConstraints() const
Returns the number of constraints.
@ MODEL_INVALID
the model is trivially invalid (NaN coefficients, etc).
@ FEASIBLE
feasible, or stopped by limit.
@ NOT_SOLVED
not been solved yet.
@ INFEASIBLE
proven infeasible.
@ ABNORMAL
abnormal, i.e., error of some kind.
ResultStatus Solve()
Solves the problem using the default parameter values.
MPSolverResponseStatus LoadModelFromProto(const MPModelProto &input_model, std::string *error_message, bool clear_names=true)
--— Methods using protocol buffers --—
bool IsMIP() const
--— Interface shortcuts --—
bool VerifySolution(double tolerance, bool log_errors) const
void MakeVarArray(int nb, double lb, double ub, bool integer, const std::string &name_prefix, std::vector< MPVariable * > *vars)
bool ExportModelAsLpFormat(bool obfuscate, std::string *model_str) const
const MPObjective & Objective() const
void SetCallback(MPCallback *mp_callback)
@ SCIP_MIXED_INTEGER_PROGRAMMING
Recommended default value for MIP problems.
@ KNAPSACK_MIXED_INTEGER_PROGRAMMING
Dedicated knapsack solvers.
@ GUROBI_LINEAR_PROGRAMMING
Commercial software (need license).
void SetTimeLimit(absl::Duration time_limit)
MPSolverResponseStatus LoadModelFromProtoWithUniqueNamesOrDie(const MPModelProto &input_model, std::string *error_message)
bool SetSolverSpecificParametersAsString(const std::string &parameters)
MPVariable * MakeBoolVar(const std::string &name)
Creates a boolean variable.
virtual OptimizationProblemType ProblemType() const
Returns the optimization problem type set at construction.
ABSL_MUST_USE_RESULT bool NextSolution()
static bool SupportsProblemType(OptimizationProblemType problem_type)
static
void MakeIntVarArray(int nb, double lb, double ub, const std::string &name, std::vector< MPVariable * > *vars)
Creates an array of integer variables.
double ComputeExactConditionNumber() const
absl::Status SetNumThreads(int num_threads)
-— Solver-specific parameters -—
MPVariable * variable(int index) const
bool OwnsVariable(const MPVariable *var) const
Debugging: verify that the given MPVariable* belongs to this solver.
void MakeNumVarArray(int nb, double lb, double ub, const std::string &name, std::vector< MPVariable * > *vars)
Creates an array of continuous variables.
std::vector< double > ComputeConstraintActivities() const
void FillSolutionResponseProto(MPSolutionResponse *response) const
Encodes the current solution in a solution response protocol buffer.
void ExportModelToProto(MPModelProto *output_model) const
Exports model to protocol buffer.
static std::string GetMPModelRequestLoggingInfo(const MPModelRequest &request)
static
MPConstraint * MakeRowConstraint()
Creates a constraint with -infinity and +infinity bounds.
MPObjective * MutableObjective()
Returns the mutable objective object.
static OptimizationProblemType ParseSolverTypeOrDie(const std::string &solver_id)
absl::Status LoadSolutionFromProto(const MPSolutionResponse &response, double tolerance=std::numeric_limits< double >::infinity())
MPVariable * MakeNumVar(double lb, double ub, const std::string &name)
Creates a continuous variable.
MPSolver(const std::string &name, OptimizationProblemType problem_type)
Create a solver with the given name and underlying solver backend.
MPConstraint * LookupConstraintOrNull(const std::string &constraint_name) const
MPConstraint * constraint(int index) const
void EnableOutput()
Enables solver logging.
static int64_t global_num_variables()
static
void SetHint(std::vector< std::pair< const MPVariable *, double > > hint)
void SuppressOutput()
Suppresses solver logging.
MPVariable * LookupVariableOrNull(const std::string &var_name) const
void Write(const std::string &file_name)
static int64_t global_num_constraints()
static
const std::string & Name() const
Returns the name of the model set at construction.
bool ExportModelAsMpsFormat(bool fixed_format, bool obfuscate, std::string *model_str) const
static bool ParseSolverType(absl::string_view solver_id, OptimizationProblemType *type)
static
static void SolveWithProto(const MPModelRequest &model_request, MPSolutionResponse *response, std::atomic< bool > *interrupt=nullptr)
static
std::string SolverVersion() const
Returns a string describing the underlying solver and its version.
void MakeBoolVarArray(int nb, const std::string &name, std::vector< MPVariable * > *vars)
Creates an array of boolean variables.
The class for variables of a Mathematical Programming (MP) model.
void SetInteger(bool integer)
Sets the integrality requirement of the variable.
bool integer() const
Returns the integrality requirement of the variable.
void SetBranchingPriority(int priority)
double lb() const
Returns the lower bound.
double ub() const
Returns the upper bound.
void SetBounds(double lb, double ub)
Sets both the lower and upper bounds.
const std::string & name() const
Returns the name of the variable.
void set_solution_value(double value)
double solution_value() const
--— MPVariable --—
MPSolver::BasisStatus basis_status() const
int index() const
Returns the index of the variable in the MPSolver::variables_.
virtual std::string name() const
Object naming.
void Schedule(std::function< void()> closure)
Definition threadpool.cc:83
SatParameters parameters
CpModelProto proto
The output proto.
const std::string name
A name for logging purposes.
const Constraint * ct
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
int constraint_index
ABSL_FLAG(bool, verify_solution, false, "Systematically verify the solution when calling Solve()" ", and change the return value of Solve() to ABNORMAL if" " an error was detected.")
MPSolver::OptimizationProblemType problem_type
int index
std::optional< ModelSolveParameters::SolutionHint > hint
void STLDeleteElements(T *container)
Definition stl_util.h:372
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition map_util.h:159
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
Definition map_util.h:36
In SWIG mode, we don't want anything besides these top-level includes.
constexpr double kDefaultPrimalTolerance
MPSolverInterface * BuildCLPInterface(MPSolver *const solver)
MPSolverInterface * BuildCBCInterface(MPSolver *const solver)
bool AreWithinAbsoluteOrRelativeTolerances(FloatType x, FloatType y, FloatType relative_tolerance, FloatType absolute_tolerance)
Definition fp_utils.h:126
absl::StatusOr< std::string > ExportModelAsLpFormat(const MPModelProto &model, const MPModelExportOptions &options)
bool GurobiIsCorrectlyInstalled()
bool SolverTypeSupportsInterruption(const MPModelRequest::SolverType solver)
bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status)
MPSolverInterface * BuildPdlpInterface(MPSolver *const solver)
Register PDLP in the global linear solver factory.
bool SolverTypeIsMip(MPModelRequest::SolverType solver_type)
There is a homonymous version taking a MPSolver::OptimizationProblemType.
MPSolverInterface * BuildHighsInterface(bool mip, MPSolver *const solver)
Register PDLP in the global linear solver factory.
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:50
constexpr NamedOptimizationProblemType kOptimizationProblemTypeNames[]
MPSolverInterface * BuildBopInterface(MPSolver *const solver)
Register BOP in the global linear solver factory.
bool XpressIsCorrectlyInstalled()
MPSolverInterface * BuildGLPKInterface(bool mip, MPSolver *const solver)
MPSolverInterface * BuildSatInterface(MPSolver *const solver)
Register Sat in the global linear solver factory.
MPSolverInterface * BuildXpressInterface(bool mip, MPSolver *const solver)
std::optional< LazyMutableCopy< MPModelProto > > GetMPModelOrPopulateResponse(LazyMutableCopy< MPModelRequest > &request, MPSolutionResponse *response)
std::string FindErrorInMPModelProto(const MPModelProto &model, double abs_value_threshold, const bool accept_trivially_infeasible_bounds)
bool AbslParseFlag(const absl::string_view text, MPSolver::OptimizationProblemType *solver_type, std::string *error)
absl::StatusOr< std::string > ExportModelAsMpsFormat(const MPModelProto &model, const MPModelExportOptions &options)
absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
MPSolverInterface * BuildGurobiInterface(bool mip, MPSolver *const solver)
MPSolverInterface * BuildSCIPInterface(MPSolver *const solver)
MPSolverInterface * BuildCplexInterface(bool mip, MPSolver *const solver)
MPSolverInterface * BuildGLOPInterface(MPSolver *const solver)
Register GLOP in the global linear solver factory.
int var_index
Definition search.cc:3268
const std::optional< Range > & range
Definition statistics.cc:37
bool obfuscate
Obfuscates variable and constraint names.