OR-Tools  9.3
linear_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//
15
17
18#if !defined(_MSC_VER)
19#include <unistd.h>
20#endif
21
22#include <atomic>
23#include <cmath>
24#include <cstddef>
25#include <cstdint>
26#include <string>
27#include <utility>
28
29#include "absl/status/status.h"
30#include "absl/status/statusor.h"
31#include "absl/strings/ascii.h"
32#include "absl/strings/match.h"
33#include "absl/strings/str_cat.h"
34#include "absl/strings/str_format.h"
35#include "absl/strings/str_replace.h"
36#include "absl/synchronization/mutex.h"
37#include "absl/synchronization/notification.h"
38#include "absl/time/time.h"
46#include "ortools/linear_solver/linear_solver.pb.h"
49#include "ortools/port/file.h"
52
53ABSL_FLAG(bool, verify_solution, false,
54 "Systematically verify the solution when calling Solve()"
55 ", and change the return value of Solve() to ABNORMAL if"
56 " an error was detected.");
57ABSL_FLAG(bool, log_verification_errors, true,
58 "If --verify_solution is set: LOG(ERROR) all errors detected"
59 " during the verification of the solution.");
60ABSL_FLAG(bool, linear_solver_enable_verbose_output, false,
61 "If set, enables verbose output for the solver. Setting this flag"
62 " is the same as calling MPSolver::EnableOutput().");
63
64ABSL_FLAG(bool, mpsolver_bypass_model_validation, false,
65 "If set, the user-provided Model won't be verified before Solve()."
66 " Invalid models will typically trigger various error responses"
67 " from the underlying solvers; sometimes crashes.");
68
69namespace operations_research {
70
72 switch (solver_type) {
73 case MPModelRequest::PDLP_LINEAR_PROGRAMMING:
74 case MPModelRequest::GLOP_LINEAR_PROGRAMMING:
75 case MPModelRequest::CLP_LINEAR_PROGRAMMING:
76 case MPModelRequest::GLPK_LINEAR_PROGRAMMING:
77 case MPModelRequest::GUROBI_LINEAR_PROGRAMMING:
78 case MPModelRequest::XPRESS_LINEAR_PROGRAMMING:
79 case MPModelRequest::CPLEX_LINEAR_PROGRAMMING:
80 return false;
81
82 case MPModelRequest::SCIP_MIXED_INTEGER_PROGRAMMING:
83 case MPModelRequest::GLPK_MIXED_INTEGER_PROGRAMMING:
84 case MPModelRequest::CBC_MIXED_INTEGER_PROGRAMMING:
85 case MPModelRequest::GUROBI_MIXED_INTEGER_PROGRAMMING:
86 case MPModelRequest::KNAPSACK_MIXED_INTEGER_PROGRAMMING:
87 case MPModelRequest::BOP_INTEGER_PROGRAMMING:
88 case MPModelRequest::SAT_INTEGER_PROGRAMMING:
89 case MPModelRequest::XPRESS_MIXED_INTEGER_PROGRAMMING:
90 case MPModelRequest::CPLEX_MIXED_INTEGER_PROGRAMMING:
91 return true;
92 }
93 LOG(DFATAL) << "Invalid SolverType: " << solver_type;
94 return false;
95}
96
97double MPConstraint::GetCoefficient(const MPVariable* const var) const {
98 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
99 if (var == nullptr) return 0.0;
100 return gtl::FindWithDefault(coefficients_, var, 0.0);
101}
102
104 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
105 if (var == nullptr) return;
106 if (coeff == 0.0) {
107 auto it = coefficients_.find(var);
108 // If setting a coefficient to 0 when this coefficient did not
109 // exist or was already 0, do nothing: skip
110 // interface_->SetCoefficient() and do not store a coefficient in
111 // the map. Note that if the coefficient being set to 0 did exist
112 // and was not 0, we do have to keep a 0 in the coefficients_ map,
113 // because the extraction of the constraint might rely on it,
114 // depending on the underlying solver.
115 if (it != coefficients_.end() && it->second != 0.0) {
116 const double old_value = it->second;
117 it->second = 0.0;
118 interface_->SetCoefficient(this, var, 0.0, old_value);
119 }
120 return;
121 }
122 auto insertion_result = coefficients_.insert(std::make_pair(var, coeff));
123 const double old_value =
124 insertion_result.second ? 0.0 : insertion_result.first->second;
125 insertion_result.first->second = coeff;
126 interface_->SetCoefficient(this, var, coeff, old_value);
127}
128
130 interface_->ClearConstraint(this);
131 coefficients_.clear();
132}
133
134void MPConstraint::SetBounds(double lb, double ub) {
135 const bool change = lb != lb_ || ub != ub_;
136 lb_ = lb;
137 ub_ = ub;
138 if (change && interface_->constraint_is_extracted(index_)) {
139 interface_->SetConstraintBounds(index_, lb_, ub_);
140 }
141}
142
144 if (!interface_->IsContinuous()) {
145 LOG(DFATAL) << "Dual value only available for continuous problems";
146 return 0.0;
147 }
148 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
149 return dual_value_;
150}
151
153 if (!interface_->IsContinuous()) {
154 LOG(DFATAL) << "Basis status only available for continuous problems";
155 return MPSolver::FREE;
156 }
157 if (!interface_->CheckSolutionIsSynchronizedAndExists()) {
158 return MPSolver::FREE;
159 }
160 // This is done lazily as this method is expected to be rarely used.
161 return interface_->row_status(index_);
162}
163
164bool MPConstraint::ContainsNewVariables() {
165 const int last_variable_index = interface_->last_variable_index();
166 for (const auto& entry : coefficients_) {
167 const int variable_index = entry.first->index();
168 if (variable_index >= last_variable_index ||
169 !interface_->variable_is_extracted(variable_index)) {
170 return true;
171 }
172 }
173 return false;
174}
175
176// ----- MPObjective -----
177
178double MPObjective::GetCoefficient(const MPVariable* const var) const {
179 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
180 if (var == nullptr) return 0.0;
181 return gtl::FindWithDefault(coefficients_, var, 0.0);
182}
183
185 DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
186 if (var == nullptr) return;
187 if (coeff == 0.0) {
188 auto it = coefficients_.find(var);
189 // See the discussion on MPConstraint::SetCoefficient() for 0 coefficients,
190 // the same reasoning applies here.
191 if (it == coefficients_.end() || it->second == 0.0) return;
192 it->second = 0.0;
193 } else {
194 coefficients_[var] = coeff;
195 }
196 interface_->SetObjectiveCoefficient(var, coeff);
197}
198
200 offset_ = value;
201 interface_->SetObjectiveOffset(offset_);
202}
203
204namespace {
205void CheckLinearExpr(const MPSolver& solver, const LinearExpr& linear_expr) {
206 for (auto var_value_pair : linear_expr.terms()) {
207 CHECK(solver.OwnsVariable(var_value_pair.first))
208 << "Bad MPVariable* in LinearExpr, did you try adding an integer to an "
209 "MPVariable* directly?";
210 }
211}
212} // namespace
213
215 bool is_maximization) {
216 CheckLinearExpr(*interface_->solver_, linear_expr);
217 interface_->ClearObjective();
218 coefficients_.clear();
219 SetOffset(linear_expr.offset());
220 for (const auto& kv : linear_expr.terms()) {
221 SetCoefficient(kv.first, kv.second);
222 }
223 SetOptimizationDirection(is_maximization);
224}
225
226void MPObjective::AddLinearExpr(const LinearExpr& linear_expr) {
227 CheckLinearExpr(*interface_->solver_, linear_expr);
228 SetOffset(offset_ + linear_expr.offset());
229 for (const auto& kv : linear_expr.terms()) {
230 SetCoefficient(kv.first, GetCoefficient(kv.first) + kv.second);
231 }
232}
233
235 interface_->ClearObjective();
236 coefficients_.clear();
237 offset_ = 0.0;
239}
240
242 // Note(user): The maximize_ bool would more naturally belong to the
243 // MPObjective, but it actually has to be a member of MPSolverInterface,
244 // because some implementations (such as GLPK) need that bool for the
245 // MPSolverInterface constructor, i.e. at a time when the MPObjective is not
246 // constructed yet (MPSolverInterface is always built before MPObjective
247 // when a new MPSolver is constructed).
248 interface_->maximize_ = maximize;
249 interface_->SetOptimizationDirection(maximize);
250}
251
252bool MPObjective::maximization() const { return interface_->maximize_; }
253
254bool MPObjective::minimization() const { return !interface_->maximize_; }
255
256double MPObjective::Value() const {
257 // Note(user): implementation-wise, the objective value belongs more
258 // naturally to the MPSolverInterface, since all of its implementations write
259 // to it directly.
260 return interface_->objective_value();
261}
262
264 // Note(user): the best objective bound belongs to the interface for the
265 // same reasons as the objective value does.
266 return interface_->best_objective_bound();
267}
268
269// ----- MPVariable -----
270
272 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
273 // If the underlying solver supports integer variables, and this is an integer
274 // variable, we round the solution value (i.e., clients usually expect precise
275 // integer values for integer variables).
276 return (integer_ && interface_->IsMIP()) ? round(solution_value_)
277 : solution_value_;
278}
279
281 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
282 return solution_value_;
283}
284
286 if (!interface_->IsContinuous()) {
287 LOG(DFATAL) << "Reduced cost only available for continuous problems";
288 return 0.0;
289 }
290 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0;
291 return reduced_cost_;
292}
293
295 if (!interface_->IsContinuous()) {
296 LOG(DFATAL) << "Basis status only available for continuous problems";
297 return MPSolver::FREE;
298 }
299 if (!interface_->CheckSolutionIsSynchronizedAndExists()) {
300 return MPSolver::FREE;
301 }
302 // This is done lazily as this method is expected to be rarely used.
303 return interface_->column_status(index_);
304}
305
306void MPVariable::SetBounds(double lb, double ub) {
307 const bool change = lb != lb_ || ub != ub_;
308 lb_ = lb;
309 ub_ = ub;
310 if (change && interface_->variable_is_extracted(index_)) {
311 interface_->SetVariableBounds(index_, lb_, ub_);
312 }
313}
314
315void MPVariable::SetInteger(bool integer) {
316 if (integer_ != integer) {
317 integer_ = integer;
318 if (interface_->variable_is_extracted(index_)) {
319 interface_->SetVariableInteger(index_, integer);
320 }
321 }
322}
323
325 if (priority == branching_priority_) return;
326 branching_priority_ = priority;
327 interface_->BranchingPriorityChangedForVariable(index_);
328}
329
330// ----- Interface shortcuts -----
331
332bool MPSolver::IsMIP() const { return interface_->IsMIP(); }
333
334std::string MPSolver::SolverVersion() const {
335 return interface_->SolverVersion();
336}
337
338void* MPSolver::underlying_solver() { return interface_->underlying_solver(); }
339
340// ---- Solver-specific parameters ----
341
342absl::Status MPSolver::SetNumThreads(int num_threads) {
343 if (num_threads < 1) {
344 return absl::InvalidArgumentError("num_threads must be a positive number.");
345 }
346 const absl::Status status = interface_->SetNumThreads(num_threads);
347 if (status.ok()) {
348 num_threads_ = num_threads;
349 }
350 return status;
351}
352
354 const std::string& parameters) {
355 solver_specific_parameter_string_ = parameters;
356 return interface_->SetSolverSpecificParametersAsString(parameters);
357}
358
359// ----- Solver -----
360
361#if defined(USE_CLP) || defined(USE_CBC)
362extern MPSolverInterface* BuildCLPInterface(MPSolver* const solver);
363#endif
364#if defined(USE_CBC)
365extern MPSolverInterface* BuildCBCInterface(MPSolver* const solver);
366#endif
367#if defined(USE_GLPK)
368extern MPSolverInterface* BuildGLPKInterface(bool mip, MPSolver* const solver);
369#endif
370extern MPSolverInterface* BuildBopInterface(MPSolver* const solver);
371extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver);
372extern MPSolverInterface* BuildPdlpInterface(MPSolver* const solver);
373extern MPSolverInterface* BuildSatInterface(MPSolver* const solver);
374#if defined(USE_SCIP)
375extern MPSolverInterface* BuildSCIPInterface(MPSolver* const solver);
376#endif
378 MPSolver* const solver);
379#if defined(USE_CPLEX)
380extern MPSolverInterface* BuildCplexInterface(bool mip, MPSolver* const solver);
381#endif
382#if defined(USE_XPRESS)
383extern MPSolverInterface* BuildXpressInterface(bool mip,
384 MPSolver* const solver);
385#endif
386
387namespace {
388MPSolverInterface* BuildSolverInterface(MPSolver* const solver) {
389 DCHECK(solver != nullptr);
390 switch (solver->ProblemType()) {
392 return BuildBopInterface(solver);
394 return BuildGLOPInterface(solver);
396 return BuildPdlpInterface(solver);
398 return BuildSatInterface(solver);
399#if defined(USE_GLPK)
401 return BuildGLPKInterface(false, solver);
403 return BuildGLPKInterface(true, solver);
404#endif
405#if defined(USE_CLP) || defined(USE_CBC)
407 return BuildCLPInterface(solver);
408#endif
409#if defined(USE_CBC)
411 return BuildCBCInterface(solver);
412#endif
413#if defined(USE_SCIP)
415 return BuildSCIPInterface(solver);
416#endif
418 return BuildGurobiInterface(false, solver);
420 return BuildGurobiInterface(true, solver);
421#if defined(USE_CPLEX)
423 return BuildCplexInterface(false, solver);
425 return BuildCplexInterface(true, solver);
426#endif
427#if defined(USE_XPRESS)
429 return BuildXpressInterface(true, solver);
431 return BuildXpressInterface(false, solver);
432#endif
433 default:
434 // TODO(user): Revert to the best *available* interface.
435 LOG(FATAL) << "Linear solver not recognized.";
436 }
437 return nullptr;
438}
439} // namespace
440
441namespace {
442int NumDigits(int n) {
443// Number of digits needed to write a non-negative integer in base 10.
444// Note(user): max(1, log(0) + 1) == max(1, -inf) == 1.
445#if defined(_MSC_VER)
446 return static_cast<int>(std::max(1.0L, log(1.0L * n) / log(10.0L) + 1.0));
447#else
448 return static_cast<int>(std::max(1.0, log10(static_cast<double>(n)) + 1.0));
449#endif
450}
451} // namespace
452
453MPSolver::MPSolver(const std::string& name,
455 : name_(name),
456 problem_type_(problem_type),
457 construction_time_(absl::Now()) {
458 interface_.reset(BuildSolverInterface(this));
459 if (absl::GetFlag(FLAGS_linear_solver_enable_verbose_output)) {
460 EnableOutput();
461 }
462 objective_.reset(new MPObjective(interface_.get()));
463}
464
466
467extern bool GurobiIsCorrectlyInstalled();
468
469// static
471#ifdef USE_CLP
472 if (problem_type == CLP_LINEAR_PROGRAMMING) return true;
473#endif
474#ifdef USE_GLPK
477 return true;
478 }
479#endif
480 if (problem_type == BOP_INTEGER_PROGRAMMING) return true;
481 if (problem_type == SAT_INTEGER_PROGRAMMING) return true;
482 if (problem_type == GLOP_LINEAR_PROGRAMMING) return true;
483 if (problem_type == PDLP_LINEAR_PROGRAMMING) return true;
487 }
488#ifdef USE_SCIP
490#endif
491#ifdef USE_CBC
493#endif
494#ifdef USE_XPRESS
497 return true;
498 }
499#endif
500#ifdef USE_CPLEX
503 return true;
504 }
505#endif
506 return false;
507}
508
509// TODO(user): post c++ 14, instead use
510// std::pair<MPSolver::OptimizationProblemType, const absl::string_view>
511// once pair gets a constexpr constructor.
512namespace {
513struct NamedOptimizationProblemType {
515 absl::string_view name;
516};
517} // namespace
518
519#if defined(_MSC_VER)
520const
521#else
522constexpr
523#endif
524 NamedOptimizationProblemType kOptimizationProblemTypeNames[] = {
541
542};
543// static
544bool MPSolver::ParseSolverType(absl::string_view solver_id,
546 // Normalize the solver id.
547 const std::string id =
548 absl::StrReplaceAll(absl::AsciiStrToUpper(solver_id), {{"-", "_"}});
549
550 // Support the full enum name
551 MPModelRequest::SolverType solver_type;
552 if (MPModelRequest::SolverType_Parse(id, &solver_type)) {
553 *type = static_cast<MPSolver::OptimizationProblemType>(solver_type);
554 return true;
555 }
556
557 // Names are stored in lower case.
558 std::string lower_id = absl::AsciiStrToLower(id);
559
560 // Remove any "_mip" suffix, since they are optional.
561 if (absl::EndsWith(lower_id, "_mip")) {
562 lower_id = lower_id.substr(0, lower_id.size() - 4);
563 }
564
565 // Rewrite CP-SAT into SAT.
566 if (lower_id == "cp_sat") {
567 lower_id = "sat";
568 }
569
570 // Reverse lookup in the kOptimizationProblemTypeNames[] array.
571 for (auto& named_solver : kOptimizationProblemTypeNames) {
572 if (named_solver.name == lower_id) {
573 *type = named_solver.problem_type;
574 return true;
575 }
576 }
577
578 return false;
579}
580
581const absl::string_view ToString(
582 MPSolver::OptimizationProblemType optimization_problem_type) {
583 for (const auto& named_solver : kOptimizationProblemTypeNames) {
584 if (named_solver.problem_type == optimization_problem_type) {
585 return named_solver.name;
586 }
587 }
588 LOG(FATAL) << "Unrecognized solver type: "
589 << static_cast<int>(optimization_problem_type);
590 return "";
591}
592
593bool AbslParseFlag(const absl::string_view text,
595 std::string* error) {
596 DCHECK(solver_type != nullptr);
597 DCHECK(error != nullptr);
598 const bool result = MPSolver::ParseSolverType(text, solver_type);
599 if (!result) {
600 *error = absl::StrCat("Solver type: ", text, " does not exist.");
601 }
602 return result;
603}
604
605/* static */
607 const std::string& solver_id) {
609 CHECK(MPSolver::ParseSolverType(solver_id, &problem_type)) << solver_id;
610 return problem_type;
611}
612
613/* static */
614MPSolver* MPSolver::CreateSolver(const std::string& solver_id) {
616 if (!MPSolver::ParseSolverType(solver_id, &problem_type)) {
617 LOG(WARNING) << "Unrecognized solver type: " << solver_id;
618 return nullptr;
619 }
621 LOG(WARNING) << "Support for " << solver_id
622 << " not linked in, or the license was not found.";
623 return nullptr;
624 }
625 MPSolver* solver = new MPSolver("", problem_type);
626 return solver;
627}
628
629MPVariable* MPSolver::LookupVariableOrNull(const std::string& var_name) const {
630 if (!variable_name_to_index_) GenerateVariableNameIndex();
631
632 absl::flat_hash_map<std::string, int>::const_iterator it =
633 variable_name_to_index_->find(var_name);
634 if (it == variable_name_to_index_->end()) return nullptr;
635 return variables_[it->second];
636}
637
639 const std::string& constraint_name) const {
640 if (!constraint_name_to_index_) GenerateConstraintNameIndex();
641
642 const auto it = constraint_name_to_index_->find(constraint_name);
643 if (it == constraint_name_to_index_->end()) return nullptr;
644 return constraints_[it->second];
645}
646
647// ----- Methods using protocol buffers -----
648
649MPSolverResponseStatus MPSolver::LoadModelFromProto(
650 const MPModelProto& input_model, std::string* error_message) {
651 Clear();
652
653 // The variable and constraint names are dropped, because we allow
654 // duplicate names in the proto (they're not considered as 'ids'),
655 // unlike the MPSolver C++ API which crashes if there are duplicate names.
656 // Clearing the names makes the MPSolver generate unique names.
657 return LoadModelFromProtoInternal(input_model, /*clear_names=*/true,
658 /*check_model_validity=*/true,
659 error_message);
660}
661
663 const MPModelProto& input_model, std::string* error_message) {
664 Clear();
665
666 // Force variable and constraint name indexing (which CHECKs name uniqueness).
667 GenerateVariableNameIndex();
668 GenerateConstraintNameIndex();
669
670 return LoadModelFromProtoInternal(input_model, /*clear_names=*/false,
671 /*check_model_validity=*/true,
672 error_message);
673}
674
675MPSolverResponseStatus MPSolver::LoadModelFromProtoInternal(
676 const MPModelProto& input_model, bool clear_names,
677 bool check_model_validity, std::string* error_message) {
678 CHECK(error_message != nullptr);
679 if (check_model_validity) {
680 const std::string error = FindErrorInMPModelProto(input_model);
681 if (!error.empty()) {
682 *error_message = error;
684 << "Invalid model given to LoadModelFromProto(): " << error;
685 if (absl::GetFlag(FLAGS_mpsolver_bypass_model_validation)) {
687 << "Ignoring the model error(s) because of"
688 << " --mpsolver_bypass_model_validation.";
689 } else {
690 return absl::StrContains(error, "Infeasible") ? MPSOLVER_INFEASIBLE
691 : MPSOLVER_MODEL_INVALID;
692 }
693 }
694 }
695
696 if (input_model.has_quadratic_objective()) {
697 *error_message =
698 "Optimizing a quadratic objective is only supported through direct "
699 "proto solves. Please use MPSolver::SolveWithProto, or the solver's "
700 "direct proto solve function.";
701 return MPSOLVER_MODEL_INVALID;
702 }
703
704 MPObjective* const objective = MutableObjective();
705 // Passing empty names makes the MPSolver generate unique names.
706 const std::string empty;
707 for (int i = 0; i < input_model.variable_size(); ++i) {
708 const MPVariableProto& var_proto = input_model.variable(i);
709 MPVariable* variable =
710 MakeNumVar(var_proto.lower_bound(), var_proto.upper_bound(),
711 clear_names ? empty : var_proto.name());
712 variable->SetInteger(var_proto.is_integer());
713 if (var_proto.branching_priority() != 0) {
714 variable->SetBranchingPriority(var_proto.branching_priority());
715 }
716 objective->SetCoefficient(variable, var_proto.objective_coefficient());
717 }
718
719 for (const MPConstraintProto& ct_proto : input_model.constraint()) {
720 if (ct_proto.lower_bound() == -infinity() &&
721 ct_proto.upper_bound() == infinity()) {
722 continue;
723 }
724
725 MPConstraint* const ct =
726 MakeRowConstraint(ct_proto.lower_bound(), ct_proto.upper_bound(),
727 clear_names ? empty : ct_proto.name());
728 ct->set_is_lazy(ct_proto.is_lazy());
729 for (int j = 0; j < ct_proto.var_index_size(); ++j) {
730 ct->SetCoefficient(variables_[ct_proto.var_index(j)],
731 ct_proto.coefficient(j));
732 }
733 }
734
735 for (const MPGeneralConstraintProto& general_constraint :
736 input_model.general_constraint()) {
737 switch (general_constraint.general_constraint_case()) {
738 case MPGeneralConstraintProto::kIndicatorConstraint: {
739 const auto& proto =
740 general_constraint.indicator_constraint().constraint();
741 if (proto.lower_bound() == -infinity() &&
742 proto.upper_bound() == infinity()) {
743 continue;
744 }
745
746 const int constraint_index = NumConstraints();
747 MPConstraint* const constraint = new MPConstraint(
748 constraint_index, proto.lower_bound(), proto.upper_bound(),
749 clear_names ? "" : proto.name(), interface_.get());
750 if (constraint_name_to_index_) {
751 gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(),
752 constraint_index);
753 }
754 constraints_.push_back(constraint);
755 constraint_is_extracted_.push_back(false);
756
757 constraint->set_is_lazy(proto.is_lazy());
758 for (int j = 0; j < proto.var_index_size(); ++j) {
759 constraint->SetCoefficient(variables_[proto.var_index(j)],
760 proto.coefficient(j));
761 }
762
763 MPVariable* const variable =
764 variables_[general_constraint.indicator_constraint().var_index()];
765 constraint->indicator_variable_ = variable;
766 constraint->indicator_value_ =
767 general_constraint.indicator_constraint().var_value();
768
769 if (!interface_->AddIndicatorConstraint(constraint)) {
770 *error_message = "Solver doesn't support indicator constraints";
771 return MPSOLVER_MODEL_INVALID;
772 }
773 break;
774 }
775 default:
776 *error_message = absl::StrFormat(
777 "Optimizing general constraints of type %i is only supported "
778 "through direct proto solves. Please use MPSolver::SolveWithProto, "
779 "or the solver's direct proto solve function.",
780 general_constraint.general_constraint_case());
781 return MPSOLVER_MODEL_INVALID;
782 }
783 }
784
785 objective->SetOptimizationDirection(input_model.maximize());
786 if (input_model.has_objective_offset()) {
787 objective->SetOffset(input_model.objective_offset());
788 }
789
790 // Stores any hints about where to start the solve.
791 solution_hint_.clear();
792 for (int i = 0; i < input_model.solution_hint().var_index_size(); ++i) {
793 solution_hint_.push_back(
794 std::make_pair(variables_[input_model.solution_hint().var_index(i)],
795 input_model.solution_hint().var_value(i)));
796 }
797 return MPSOLVER_MODEL_IS_VALID;
798}
799
800namespace {
801MPSolverResponseStatus ResultStatusToMPSolverResponseStatus(
803 switch (status) {
805 return MPSOLVER_OPTIMAL;
807 return MPSOLVER_FEASIBLE;
809 return MPSOLVER_INFEASIBLE;
811 return MPSOLVER_UNBOUNDED;
813 return MPSOLVER_ABNORMAL;
815 return MPSOLVER_MODEL_INVALID;
817 return MPSOLVER_NOT_SOLVED;
818 }
819 return MPSOLVER_UNKNOWN_STATUS;
820}
821} // namespace
822
823void MPSolver::FillSolutionResponseProto(MPSolutionResponse* response) const {
824 CHECK(response != nullptr);
825 response->Clear();
826 response->set_status(
827 ResultStatusToMPSolverResponseStatus(interface_->result_status_));
828 if (interface_->result_status_ == MPSolver::OPTIMAL ||
829 interface_->result_status_ == MPSolver::FEASIBLE) {
830 response->set_objective_value(Objective().Value());
831 for (int i = 0; i < variables_.size(); ++i) {
832 response->add_variable_value(variables_[i]->solution_value());
833 }
834
835 if (interface_->IsMIP()) {
836 response->set_best_objective_bound(interface_->best_objective_bound());
837 } else {
838 // Dual values have no meaning in MIP.
839 for (int j = 0; j < constraints_.size(); ++j) {
840 response->add_dual_value(constraints_[j]->dual_value());
841 }
842 // Reduced cost have no meaning in MIP.
843 for (int i = 0; i < variables_.size(); ++i) {
844 response->add_reduced_cost(variables_[i]->reduced_cost());
845 }
846 }
847 }
848}
849
850namespace {
851bool InCategory(int status, int category) {
852 if (category == MPSOLVER_OPTIMAL) return status == MPSOLVER_OPTIMAL;
853 while (status > category) status >>= 4;
854 return status == category;
855}
856
857void AppendStatusStr(const std::string& msg, MPSolutionResponse* response) {
858 response->set_status_str(
859 absl::StrCat(response->status_str(),
860 (response->status_str().empty() ? "" : "\n"), msg));
861}
862} // namespace
863
864// static
865void MPSolver::SolveWithProto(const MPModelRequest& model_request,
866 MPSolutionResponse* response,
867 std::atomic<bool>* interrupt) {
868 CHECK(response != nullptr);
869
870 if (interrupt != nullptr &&
871 !SolverTypeSupportsInterruption(model_request.solver_type())) {
872 response->set_status(MPSOLVER_INCOMPATIBLE_OPTIONS);
873 response->set_status_str(
874 "Called MPSolver::SolveWithProto with an underlying solver that "
875 "doesn't support interruption.");
876 return;
877 }
878
879 MPSolver solver(model_request.model().name(),
881 model_request.solver_type()));
882 if (model_request.enable_internal_solver_output()) {
883 solver.EnableOutput();
884 }
885
886 // If interruption support is not required, we don't need access to the
887 // underlying solver and can solve it directly if the interface supports it.
888 auto optional_response =
889 solver.interface_->DirectlySolveProto(model_request, interrupt);
890 if (optional_response) {
891 *response = std::move(optional_response).value();
892 return;
893 }
894
895 const absl::optional<LazyMutableCopy<MPModelProto>> optional_model =
897 if (!optional_model) {
898 LOG_IF(WARNING, model_request.enable_internal_solver_output())
899 << "Failed to extract a valid model from protocol buffer. Status: "
900 << ProtoEnumToString<MPSolverResponseStatus>(response->status()) << " ("
901 << response->status() << "): " << response->status_str();
902 return;
903 }
904 std::string error_message;
905 response->set_status(solver.LoadModelFromProtoInternal(
906 optional_model->get(), /*clear_names=*/true,
907 /*check_model_validity=*/false, &error_message));
908 // Even though we don't re-check model validity here, there can be some
909 // problems found by LoadModelFromProto, eg. unsupported features.
910 if (response->status() != MPSOLVER_MODEL_IS_VALID) {
911 response->set_status_str(error_message);
912 LOG_IF(WARNING, model_request.enable_internal_solver_output())
913 << "LoadModelFromProtoInternal() failed even though the model was "
914 << "valid! Status: "
915 << ProtoEnumToString<MPSolverResponseStatus>(response->status()) << " ("
916 << response->status() << "); Error: " << error_message;
917 return;
918 }
919 if (model_request.has_solver_time_limit_seconds()) {
920 solver.SetTimeLimit(
921 absl::Seconds(model_request.solver_time_limit_seconds()));
922 }
923 std::string warning_message;
924 if (model_request.has_solver_specific_parameters()) {
926 model_request.solver_specific_parameters())) {
927 if (model_request.ignore_solver_specific_parameters_failure()) {
928 // We'll add a warning message in status_str after the solve.
929 warning_message =
930 "Warning: the solver specific parameters were not successfully "
931 "applied";
932 } else {
933 response->set_status(MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS);
934 return;
935 }
936 }
937 }
938
939 if (interrupt == nullptr) {
940 // If we don't need interruption support, we can save some overhead by
941 // running the solve in the current thread.
942 solver.Solve();
944 } else {
945 const absl::Time start_time = absl::Now();
946 absl::Time interrupt_time;
947 bool interrupted_by_user = false;
948 {
949 absl::Notification solve_finished;
950 auto polling_func = [&interrupt, &solve_finished, &solver,
951 &interrupted_by_user, &interrupt_time,
952 &model_request]() {
953 constexpr absl::Duration kPollDelay = absl::Microseconds(100);
954 constexpr absl::Duration kMaxInterruptionDelay = absl::Seconds(10);
955
956 while (!interrupt->load()) {
957 if (solve_finished.HasBeenNotified()) return;
958 absl::SleepFor(kPollDelay);
959 }
960
961 // If we get here, we received an interruption notification before the
962 // solve finished "naturally".
963 solver.InterruptSolve();
964 interrupt_time = absl::Now();
965 interrupted_by_user = true;
966
967 // SUBTLE: our call to InterruptSolve() can be ignored by the
968 // underlying solver for several reasons:
969 // 1) The solver thread doesn't poll its 'interrupted' bit often
970 // enough and takes too long to realize that it should return, or
971 // its mere return + FillSolutionResponse() takes too long.
972 // 2) The user interrupted the solve so early that Solve() hadn't
973 // really started yet when we called InterruptSolve().
974 // In case 1), we should just wait a little longer. In case 2), we
975 // should call InterruptSolve() again, maybe several times. To both
976 // accommodate cases where the solver takes really a long time to
977 // react to the interruption, while returning as quickly as possible,
978 // we poll the solve_finished notification with increasing durations
979 // and call InterruptSolve again, each time.
980 for (absl::Duration poll_delay = kPollDelay;
981 absl::Now() <= interrupt_time + kMaxInterruptionDelay;
982 poll_delay *= 2) {
983 if (solve_finished.WaitForNotificationWithTimeout(poll_delay)) {
984 return;
985 } else {
986 solver.InterruptSolve();
987 }
988 }
989
990 LOG(DFATAL)
991 << "MPSolver::InterruptSolve() seems to be ignored by the "
992 "underlying solver, despite repeated calls over at least "
993 << absl::FormatDuration(kMaxInterruptionDelay)
994 << ". Solver type used: "
995 << MPModelRequest_SolverType_Name(model_request.solver_type());
996
997 // Note that in opt builds, the polling thread terminates here with an
998 // error message, but we let Solve() finish, ignoring the user
999 // interruption request.
1000 };
1001
1002 // The choice to do polling rather than solving in the second thread is
1003 // not arbitrary, as we want to maintain any custom thread options set by
1004 // the user. They shouldn't matter for polling, but for solving we might
1005 // e.g. use a larger stack.
1006 ThreadPool thread_pool("SolverThread", /*num_threads=*/1);
1007 thread_pool.StartWorkers();
1008 thread_pool.Schedule(polling_func);
1009
1010 // Make sure the interruption notification didn't arrive while waiting to
1011 // be scheduled.
1012 if (!interrupt->load()) {
1013 solver.Solve();
1014 solver.FillSolutionResponseProto(response);
1015 } else { // *interrupt == true
1016 response->set_status(MPSOLVER_CANCELLED_BY_USER);
1017 response->set_status_str(
1018 "Solve not started, because the user set the atomic<bool> in "
1019 "MPSolver::SolveWithProto() to true before solving could "
1020 "start.");
1021 }
1022 solve_finished.Notify();
1023
1024 // We block until the thread finishes when thread_pool goes out of scope.
1025 }
1026
1027 if (interrupted_by_user) {
1028 // Despite the interruption, the solver might still have found a useful
1029 // result. If so, don't overwrite the status.
1030 if (InCategory(response->status(), MPSOLVER_NOT_SOLVED)) {
1031 response->set_status(MPSOLVER_CANCELLED_BY_USER);
1032 }
1033 AppendStatusStr(
1034 absl::StrFormat(
1035 "User interrupted MPSolver::SolveWithProto() by setting the "
1036 "atomic<bool> to true at %s (%s after solving started.)",
1037 absl::FormatTime(interrupt_time),
1038 absl::FormatDuration(interrupt_time - start_time)),
1039 response);
1040 }
1041 }
1042
1043 if (!warning_message.empty()) {
1044 AppendStatusStr(warning_message, response);
1045 }
1046}
1047
1048void MPSolver::ExportModelToProto(MPModelProto* output_model) const {
1049 DCHECK(output_model != nullptr);
1050 output_model->Clear();
1051 // Name
1052 output_model->set_name(Name());
1053 // Variables
1054 for (int j = 0; j < variables_.size(); ++j) {
1055 const MPVariable* const var = variables_[j];
1056 MPVariableProto* const variable_proto = output_model->add_variable();
1057 // TODO(user): Add option to avoid filling the var name to avoid overly
1058 // large protocol buffers.
1059 variable_proto->set_name(var->name());
1060 variable_proto->set_lower_bound(var->lb());
1061 variable_proto->set_upper_bound(var->ub());
1062 variable_proto->set_is_integer(var->integer());
1063 if (objective_->GetCoefficient(var) != 0.0) {
1064 variable_proto->set_objective_coefficient(
1065 objective_->GetCoefficient(var));
1066 }
1067 if (var->branching_priority() != 0) {
1068 variable_proto->set_branching_priority(var->branching_priority());
1069 }
1070 }
1071
1072 // Map the variables to their indices. This is needed to output the
1073 // variables in the order they were created, which in turn is needed to have
1074 // repeatable results with ExportModelAsLpFormat and ExportModelAsMpsFormat.
1075 // This step is needed as long as the variable indices are given by the
1076 // underlying solver at the time of model extraction.
1077 // TODO(user): remove this step.
1078 absl::flat_hash_map<const MPVariable*, int> var_to_index;
1079 for (int j = 0; j < variables_.size(); ++j) {
1080 var_to_index[variables_[j]] = j;
1081 }
1082
1083 // Constraints
1084 for (int i = 0; i < constraints_.size(); ++i) {
1085 MPConstraint* const constraint = constraints_[i];
1086 MPConstraintProto* constraint_proto;
1087 if (constraint->indicator_variable() != nullptr) {
1088 MPGeneralConstraintProto* const general_constraint_proto =
1089 output_model->add_general_constraint();
1090 general_constraint_proto->set_name(constraint->name());
1091 MPIndicatorConstraint* const indicator_constraint_proto =
1092 general_constraint_proto->mutable_indicator_constraint();
1093 indicator_constraint_proto->set_var_index(
1095 indicator_constraint_proto->set_var_value(constraint->indicator_value());
1096 constraint_proto = indicator_constraint_proto->mutable_constraint();
1097 } else {
1098 constraint_proto = output_model->add_constraint();
1099 }
1100 constraint_proto->set_name(constraint->name());
1101 constraint_proto->set_lower_bound(constraint->lb());
1102 constraint_proto->set_upper_bound(constraint->ub());
1103 constraint_proto->set_is_lazy(constraint->is_lazy());
1104 // Vector linear_term will contain pairs (variable index, coeff), that will
1105 // be sorted by variable index.
1106 std::vector<std::pair<int, double>> linear_term;
1107 for (const auto& entry : constraint->coefficients_) {
1108 const MPVariable* const var = entry.first;
1109 const int var_index = gtl::FindWithDefault(var_to_index, var, -1);
1110 DCHECK_NE(-1, var_index);
1111 const double coeff = entry.second;
1112 linear_term.push_back(std::pair<int, double>(var_index, coeff));
1113 }
1114 // The cost of sort is expected to be low as constraints usually have very
1115 // few terms.
1116 std::sort(linear_term.begin(), linear_term.end());
1117 // Now use linear term.
1118 for (const std::pair<int, double>& var_and_coeff : linear_term) {
1119 constraint_proto->add_var_index(var_and_coeff.first);
1120 constraint_proto->add_coefficient(var_and_coeff.second);
1121 }
1122 }
1123
1124 output_model->set_maximize(Objective().maximization());
1125 output_model->set_objective_offset(Objective().offset());
1126
1127 if (!solution_hint_.empty()) {
1128 PartialVariableAssignment* const hint =
1129 output_model->mutable_solution_hint();
1130 for (const auto& var_value_pair : solution_hint_) {
1131 hint->add_var_index(var_value_pair.first->index());
1132 hint->add_var_value(var_value_pair.second);
1133 }
1134 }
1135}
1136
1137absl::Status MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response,
1138 double tolerance) {
1139 interface_->result_status_ = static_cast<ResultStatus>(response.status());
1140 if (response.status() != MPSOLVER_OPTIMAL &&
1141 response.status() != MPSOLVER_FEASIBLE) {
1142 return absl::InvalidArgumentError(absl::StrCat(
1143 "Cannot load a solution unless its status is OPTIMAL or FEASIBLE"
1144 " (status was: ",
1145 ProtoEnumToString<MPSolverResponseStatus>(response.status()), ")"));
1146 }
1147 // Before touching the variables, verify that the solution looks legit:
1148 // each variable of the MPSolver must have its value listed exactly once, and
1149 // each listed solution should correspond to a known variable.
1150 if (response.variable_value_size() != variables_.size()) {
1151 return absl::InvalidArgumentError(absl::StrCat(
1152 "Trying to load a solution whose number of variables (",
1153 response.variable_value_size(),
1154 ") does not correspond to the Solver's (", variables_.size(), ")"));
1155 }
1156 interface_->ExtractModel();
1157
1158 if (tolerance != infinity()) {
1159 // Look further: verify that the variable values are within the bounds.
1160 double largest_error = 0;
1161 int num_vars_out_of_bounds = 0;
1162 int last_offending_var = -1;
1163 for (int i = 0; i < response.variable_value_size(); ++i) {
1164 const double var_value = response.variable_value(i);
1165 MPVariable* var = variables_[i];
1166 // TODO(user): Use parameter when they become available in this class.
1167 const double lb_error = var->lb() - var_value;
1168 const double ub_error = var_value - var->ub();
1169 if (lb_error > tolerance || ub_error > tolerance) {
1170 ++num_vars_out_of_bounds;
1171 largest_error = std::max(largest_error, std::max(lb_error, ub_error));
1172 last_offending_var = i;
1173 }
1174 }
1175 if (num_vars_out_of_bounds > 0) {
1176 return absl::InvalidArgumentError(absl::StrCat(
1177 "Loaded a solution whose variables matched the solver's, but ",
1178 num_vars_out_of_bounds, " of ", variables_.size(),
1179 " variables were out of their bounds, by more than the primal"
1180 " tolerance which is: ",
1181 tolerance, ". Max error: ", largest_error, ", last offender var is #",
1182 last_offending_var, ": '", variables_[last_offending_var]->name(),
1183 "'"));
1184 }
1185 }
1186 for (int i = 0; i < response.variable_value_size(); ++i) {
1187 variables_[i]->set_solution_value(response.variable_value(i));
1188 }
1189 if (response.dual_value_size() > 0) {
1190 if (response.dual_value_size() != constraints_.size()) {
1191 return absl::InvalidArgumentError(absl::StrCat(
1192 "Trying to load a dual solution whose number of entries (",
1193 response.dual_value_size(), ") does not correspond to the Solver's (",
1194 constraints_.size(), ")"));
1195 }
1196 for (int i = 0; i < response.dual_value_size(); ++i) {
1197 constraints_[i]->set_dual_value(response.dual_value(i));
1198 }
1199 }
1200 if (response.reduced_cost_size() > 0) {
1201 if (response.reduced_cost_size() != variables_.size()) {
1202 return absl::InvalidArgumentError(absl::StrCat(
1203 "Trying to load a reduced cost solution whose number of entries (",
1204 response.reduced_cost_size(),
1205 ") does not correspond to the Solver's (", variables_.size(), ")"));
1206 }
1207 for (int i = 0; i < response.reduced_cost_size(); ++i) {
1208 variables_[i]->set_reduced_cost(response.reduced_cost(i));
1209 }
1210 }
1211 // Set the objective value, if is known.
1212 // NOTE(user): We do not verify the objective, even though we could!
1213 if (response.has_objective_value()) {
1214 interface_->objective_value_ = response.objective_value();
1215 }
1216 if (response.has_best_objective_bound()) {
1217 interface_->best_objective_bound_ = response.best_objective_bound();
1218 }
1219 // Mark the status as SOLUTION_SYNCHRONIZED, so that users may inspect the
1220 // solution normally.
1221 interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
1222 return absl::OkStatus();
1223}
1224
1226 {
1227 absl::MutexLock lock(&global_count_mutex_);
1228 global_num_variables_ += variables_.size();
1229 global_num_constraints_ += constraints_.size();
1230 }
1232 gtl::STLDeleteElements(&variables_);
1233 gtl::STLDeleteElements(&constraints_);
1234 if (variable_name_to_index_) {
1235 variable_name_to_index_->clear();
1236 }
1237 variable_is_extracted_.clear();
1238 if (constraint_name_to_index_) {
1239 constraint_name_to_index_->clear();
1240 }
1241 constraint_is_extracted_.clear();
1242 interface_->Reset();
1243 solution_hint_.clear();
1244}
1245
1246void MPSolver::Reset() { interface_->Reset(); }
1247
1248bool MPSolver::InterruptSolve() { return interface_->InterruptSolve(); }
1249
1251 const std::vector<BasisStatus>& variable_statuses,
1252 const std::vector<BasisStatus>& constraint_statuses) {
1253 interface_->SetStartingLpBasis(variable_statuses, constraint_statuses);
1254}
1255
1256MPVariable* MPSolver::MakeVar(double lb, double ub, bool integer,
1257 const std::string& name) {
1258 const int var_index = NumVariables();
1259 MPVariable* v =
1260 new MPVariable(var_index, lb, ub, integer, name, interface_.get());
1261 if (variable_name_to_index_) {
1262 gtl::InsertOrDie(&*variable_name_to_index_, v->name(), var_index);
1263 }
1264 variables_.push_back(v);
1265 variable_is_extracted_.push_back(false);
1266 interface_->AddVariable(v);
1267 return v;
1268}
1269
1270MPVariable* MPSolver::MakeNumVar(double lb, double ub,
1271 const std::string& name) {
1272 return MakeVar(lb, ub, false, name);
1273}
1274
1275MPVariable* MPSolver::MakeIntVar(double lb, double ub,
1276 const std::string& name) {
1277 return MakeVar(lb, ub, true, name);
1278}
1279
1281 return MakeVar(0.0, 1.0, true, name);
1282}
1283
1284void MPSolver::MakeVarArray(int nb, double lb, double ub, bool integer,
1285 const std::string& name,
1286 std::vector<MPVariable*>* vars) {
1287 DCHECK_GE(nb, 0);
1288 if (nb <= 0) return;
1289 const int num_digits = NumDigits(nb);
1290 for (int i = 0; i < nb; ++i) {
1291 if (name.empty()) {
1292 vars->push_back(MakeVar(lb, ub, integer, name));
1293 } else {
1294 std::string vname =
1295 absl::StrFormat("%s%0*d", name.c_str(), num_digits, i);
1296 vars->push_back(MakeVar(lb, ub, integer, vname));
1297 }
1298 }
1299}
1300
1301void MPSolver::MakeNumVarArray(int nb, double lb, double ub,
1302 const std::string& name,
1303 std::vector<MPVariable*>* vars) {
1304 MakeVarArray(nb, lb, ub, false, name, vars);
1305}
1306
1307void MPSolver::MakeIntVarArray(int nb, double lb, double ub,
1308 const std::string& name,
1309 std::vector<MPVariable*>* vars) {
1310 MakeVarArray(nb, lb, ub, true, name, vars);
1311}
1312
1313void MPSolver::MakeBoolVarArray(int nb, const std::string& name,
1314 std::vector<MPVariable*>* vars) {
1315 MakeVarArray(nb, 0.0, 1.0, true, name, vars);
1316}
1317
1319 return MakeRowConstraint(lb, ub, "");
1320}
1321
1323 return MakeRowConstraint(-infinity(), infinity(), "");
1324}
1325
1327 const std::string& name) {
1328 const int constraint_index = NumConstraints();
1329 MPConstraint* const constraint =
1330 new MPConstraint(constraint_index, lb, ub, name, interface_.get());
1331 if (constraint_name_to_index_) {
1332 gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(),
1333 constraint_index);
1334 }
1335 constraints_.push_back(constraint);
1336 constraint_is_extracted_.push_back(false);
1337 interface_->AddRowConstraint(constraint);
1338 return constraint;
1339}
1340
1342 return MakeRowConstraint(-infinity(), infinity(), name);
1343}
1344
1346 return MakeRowConstraint(range, "");
1347}
1348
1350 const std::string& name) {
1351 CheckLinearExpr(*this, range.linear_expr());
1353 MakeRowConstraint(range.lower_bound(), range.upper_bound(), name);
1354 for (const auto& kv : range.linear_expr().terms()) {
1355 constraint->SetCoefficient(kv.first, kv.second);
1356 }
1357 return constraint;
1358}
1359
1360int MPSolver::ComputeMaxConstraintSize(int min_constraint_index,
1361 int max_constraint_index) const {
1362 int max_constraint_size = 0;
1363 DCHECK_GE(min_constraint_index, 0);
1364 DCHECK_LE(max_constraint_index, constraints_.size());
1365 for (int i = min_constraint_index; i < max_constraint_index; ++i) {
1366 MPConstraint* const ct = constraints_[i];
1367 if (ct->coefficients_.size() > max_constraint_size) {
1368 max_constraint_size = ct->coefficients_.size();
1369 }
1370 }
1371 return max_constraint_size;
1372}
1373
1374bool MPSolver::HasInfeasibleConstraints() const {
1375 bool hasInfeasibleConstraints = false;
1376 for (int i = 0; i < constraints_.size(); ++i) {
1377 if (constraints_[i]->lb() > constraints_[i]->ub()) {
1378 LOG(WARNING) << "Constraint " << constraints_[i]->name() << " (" << i
1379 << ") has contradictory bounds:"
1380 << " lower bound = " << constraints_[i]->lb()
1381 << " upper bound = " << constraints_[i]->ub();
1382 hasInfeasibleConstraints = true;
1383 }
1384 }
1385 return hasInfeasibleConstraints;
1386}
1387
1388bool MPSolver::HasIntegerVariables() const {
1389 for (const MPVariable* const variable : variables_) {
1390 if (variable->integer()) return true;
1391 }
1392 return false;
1393}
1394
1396 MPSolverParameters default_param;
1397 return Solve(default_param);
1398}
1399
1401 // Special case for infeasible constraints so that all solvers have
1402 // the same behavior.
1403 // TODO(user): replace this by model extraction to proto + proto validation
1404 // (the proto has very low overhead compared to the wrapper, both in
1405 // performance and memory, so it's ok).
1406 if (HasInfeasibleConstraints()) {
1407 interface_->result_status_ = MPSolver::INFEASIBLE;
1408 return interface_->result_status_;
1409 }
1410
1411 MPSolver::ResultStatus status = interface_->Solve(param);
1412 if (absl::GetFlag(FLAGS_verify_solution)) {
1414 VLOG(1) << "--verify_solution enabled, but the solver did not find a"
1415 << " solution: skipping the verification.";
1416 } else if (!VerifySolution(
1418 absl::GetFlag(FLAGS_log_verification_errors))) {
1420 interface_->result_status_ = status;
1421 }
1422 }
1423 DCHECK_EQ(interface_->result_status_, status);
1424 return status;
1425}
1426
1427void MPSolver::Write(const std::string& file_name) {
1428 interface_->Write(file_name);
1429}
1430
1431namespace {
1432std::string PrettyPrintVar(const MPVariable& var) {
1433 const std::string prefix = "Variable '" + var.name() + "': domain = ";
1434 if (var.lb() >= MPSolver::infinity() || var.ub() <= -MPSolver::infinity() ||
1435 var.lb() > var.ub()) {
1436 return prefix + "∅"; // Empty set.
1437 }
1438 // Special case: integer variable with at most two possible values
1439 // (and potentially none).
1440 if (var.integer() && var.ub() - var.lb() <= 1) {
1441 const int64_t lb = static_cast<int64_t>(ceil(var.lb()));
1442 const int64_t ub = static_cast<int64_t>(floor(var.ub()));
1443 if (lb > ub) {
1444 return prefix + "∅";
1445 } else if (lb == ub) {
1446 return absl::StrFormat("%s{ %d }", prefix.c_str(), lb);
1447 } else {
1448 return absl::StrFormat("%s{ %d, %d }", prefix.c_str(), lb, ub);
1449 }
1450 }
1451 // Special case: single (non-infinite) real value.
1452 if (var.lb() == var.ub()) {
1453 return absl::StrFormat("%s{ %f }", prefix.c_str(), var.lb());
1454 }
1455 return prefix + (var.integer() ? "Integer" : "Real") + " in " +
1456 (var.lb() <= -MPSolver::infinity()
1457 ? std::string("]-∞")
1458 : absl::StrFormat("[%f", var.lb())) +
1459 ", " +
1460 (var.ub() >= MPSolver::infinity() ? std::string("+∞[")
1461 : absl::StrFormat("%f]", var.ub()));
1462}
1463
1464std::string PrettyPrintConstraint(const MPConstraint& constraint) {
1465 std::string prefix = "Constraint '" + constraint.name() + "': ";
1466 if (constraint.lb() >= MPSolver::infinity() ||
1467 constraint.ub() <= -MPSolver::infinity() ||
1468 constraint.lb() > constraint.ub()) {
1469 return prefix + "ALWAYS FALSE";
1470 }
1471 if (constraint.lb() <= -MPSolver::infinity() &&
1472 constraint.ub() >= MPSolver::infinity()) {
1473 return prefix + "ALWAYS TRUE";
1474 }
1475 prefix += "<linear expr>";
1476 // Equality.
1477 if (constraint.lb() == constraint.ub()) {
1478 return absl::StrFormat("%s = %f", prefix.c_str(), constraint.lb());
1479 }
1480 // Inequalities.
1481 if (constraint.lb() <= -MPSolver::infinity()) {
1482 return absl::StrFormat("%s ≤ %f", prefix.c_str(), constraint.ub());
1483 }
1484 if (constraint.ub() >= MPSolver::infinity()) {
1485 return absl::StrFormat("%s ≥ %f", prefix.c_str(), constraint.lb());
1486 }
1487 return absl::StrFormat("%s ∈ [%f, %f]", prefix.c_str(), constraint.lb(),
1488 constraint.ub());
1489}
1490} // namespace
1491
1493 interface_->ExtractModel();
1494 for (MPVariable* const variable : variables_) {
1495 const double value = variable->solution_value();
1496 if (std::isnan(value)) {
1497 return absl::InvalidArgumentError(
1498 absl::StrCat("NaN value for ", PrettyPrintVar(*variable)));
1499 }
1500 if (value < variable->lb()) {
1502 } else if (value > variable->ub()) {
1504 }
1505 }
1506 interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
1507 return absl::OkStatus();
1508}
1509
1510std::vector<double> MPSolver::ComputeConstraintActivities() const {
1511 // TODO(user): test this failure case.
1512 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return {};
1513 std::vector<double> activities(constraints_.size(), 0.0);
1514 for (int i = 0; i < constraints_.size(); ++i) {
1515 const MPConstraint& constraint = *constraints_[i];
1517 for (const auto& entry : constraint.coefficients_) {
1518 sum.Add(entry.first->solution_value() * entry.second);
1519 }
1520 activities[i] = sum.Value();
1521 }
1522 return activities;
1523}
1524
1525// TODO(user): split.
1526bool MPSolver::VerifySolution(double tolerance, bool log_errors) const {
1527 double max_observed_error = 0;
1528 if (tolerance < 0) tolerance = infinity();
1529 int num_errors = 0;
1530
1531 // Verify variables.
1532 for (int i = 0; i < variables_.size(); ++i) {
1533 const MPVariable& var = *variables_[i];
1534 const double value = var.solution_value();
1535 // Check for NaN.
1536 if (std::isnan(value)) {
1537 ++num_errors;
1538 max_observed_error = infinity();
1539 LOG_IF(ERROR, log_errors) << "NaN value for " << PrettyPrintVar(var);
1540 continue;
1541 }
1542 // Check lower bound.
1543 if (var.lb() != -infinity()) {
1544 if (value < var.lb() - tolerance) {
1545 ++num_errors;
1546 max_observed_error = std::max(max_observed_error, var.lb() - value);
1547 LOG_IF(ERROR, log_errors)
1548 << "Value " << value << " too low for " << PrettyPrintVar(var);
1549 }
1550 }
1551 // Check upper bound.
1552 if (var.ub() != infinity()) {
1553 if (value > var.ub() + tolerance) {
1554 ++num_errors;
1555 max_observed_error = std::max(max_observed_error, value - var.ub());
1556 LOG_IF(ERROR, log_errors)
1557 << "Value " << value << " too high for " << PrettyPrintVar(var);
1558 }
1559 }
1560 // Check integrality.
1561 if (IsMIP() && var.integer()) {
1562 if (fabs(value - round(value)) > tolerance) {
1563 ++num_errors;
1564 max_observed_error =
1565 std::max(max_observed_error, fabs(value - round(value)));
1566 LOG_IF(ERROR, log_errors)
1567 << "Non-integer value " << value << " for " << PrettyPrintVar(var);
1568 }
1569 }
1570 }
1571 if (!IsMIP() && HasIntegerVariables()) {
1572 LOG_IF(INFO, log_errors) << "Skipped variable integrality check, because "
1573 << "a continuous relaxation of the model was "
1574 << "solved (i.e., the selected solver does not "
1575 << "support integer variables).";
1576 }
1577
1578 // Verify constraints.
1579 const std::vector<double> activities = ComputeConstraintActivities();
1580 for (int i = 0; i < constraints_.size(); ++i) {
1581 const MPConstraint& constraint = *constraints_[i];
1582 const double activity = activities[i];
1583 // Re-compute the activity with a inaccurate summing algorithm.
1584 double inaccurate_activity = 0.0;
1585 for (const auto& entry : constraint.coefficients_) {
1586 inaccurate_activity += entry.first->solution_value() * entry.second;
1587 }
1588 // Catch NaNs.
1589 if (std::isnan(activity) || std::isnan(inaccurate_activity)) {
1590 ++num_errors;
1591 max_observed_error = infinity();
1592 LOG_IF(ERROR, log_errors)
1593 << "NaN value for " << PrettyPrintConstraint(constraint);
1594 continue;
1595 }
1596 // Check bounds.
1597 if (constraint.indicator_variable() == nullptr ||
1598 std::round(constraint.indicator_variable()->solution_value()) ==
1600 if (constraint.lb() != -infinity()) {
1601 if (activity < constraint.lb() - tolerance) {
1602 ++num_errors;
1603 max_observed_error =
1604 std::max(max_observed_error, constraint.lb() - activity);
1605 LOG_IF(ERROR, log_errors)
1606 << "Activity " << activity << " too low for "
1607 << PrettyPrintConstraint(constraint);
1608 } else if (inaccurate_activity < constraint.lb() - tolerance) {
1609 LOG_IF(WARNING, log_errors)
1610 << "Activity " << activity << ", computed with the (inaccurate)"
1611 << " standard sum of its terms, is too low for "
1612 << PrettyPrintConstraint(constraint);
1613 }
1614 }
1615 if (constraint.ub() != infinity()) {
1616 if (activity > constraint.ub() + tolerance) {
1617 ++num_errors;
1618 max_observed_error =
1619 std::max(max_observed_error, activity - constraint.ub());
1620 LOG_IF(ERROR, log_errors)
1621 << "Activity " << activity << " too high for "
1622 << PrettyPrintConstraint(constraint);
1623 } else if (inaccurate_activity > constraint.ub() + tolerance) {
1624 LOG_IF(WARNING, log_errors)
1625 << "Activity " << activity << ", computed with the (inaccurate)"
1626 << " standard sum of its terms, is too high for "
1627 << PrettyPrintConstraint(constraint);
1628 }
1629 }
1630 }
1631 }
1632
1633 // Verify that the objective value wasn't reported incorrectly.
1634 const MPObjective& objective = Objective();
1635 AccurateSum<double> objective_sum;
1636 objective_sum.Add(objective.offset());
1637 double inaccurate_objective_value = objective.offset();
1638 for (const auto& entry : objective.coefficients_) {
1639 const double term = entry.first->solution_value() * entry.second;
1640 objective_sum.Add(term);
1641 inaccurate_objective_value += term;
1642 }
1643 const double actual_objective_value = objective_sum.Value();
1645 objective.Value(), actual_objective_value, tolerance, tolerance)) {
1646 ++num_errors;
1647 max_observed_error = std::max(
1648 max_observed_error, fabs(actual_objective_value - objective.Value()));
1649 LOG_IF(ERROR, log_errors)
1650 << "Objective value " << objective.Value() << " isn't accurate"
1651 << ", it should be " << actual_objective_value
1652 << " (delta=" << actual_objective_value - objective.Value() << ").";
1653 } else if (!AreWithinAbsoluteOrRelativeTolerances(objective.Value(),
1654 inaccurate_objective_value,
1655 tolerance, tolerance)) {
1656 LOG_IF(WARNING, log_errors)
1657 << "Objective value " << objective.Value() << " doesn't correspond"
1658 << " to the value computed with the standard (and therefore inaccurate)"
1659 << " sum of its terms.";
1660 }
1661 if (num_errors > 0) {
1662 LOG_IF(ERROR, log_errors)
1663 << "There were " << num_errors << " errors above the tolerance ("
1664 << tolerance << "), the largest was " << max_observed_error;
1665 return false;
1666 }
1667 return true;
1668}
1669
1670bool MPSolver::OutputIsEnabled() const { return !interface_->quiet(); }
1671
1672void MPSolver::EnableOutput() { interface_->set_quiet(false); }
1673
1674void MPSolver::SuppressOutput() { interface_->set_quiet(true); }
1675
1676int64_t MPSolver::iterations() const { return interface_->iterations(); }
1677
1678int64_t MPSolver::nodes() const { return interface_->nodes(); }
1679
1681 return interface_->ComputeExactConditionNumber();
1682}
1683
1685 if (var == nullptr) return false;
1686 if (var->index() >= 0 && var->index() < variables_.size()) {
1687 // Then, verify that the variable with this index has the same address.
1688 return variables_[var->index()] == var;
1689 }
1690 return false;
1691}
1692
1694 std::string* model_str) const {
1695 MPModelProto proto;
1697 MPModelExportOptions options;
1698 options.obfuscate = obfuscate;
1699 const auto status_or =
1701 *model_str = status_or.value_or("");
1702 return status_or.ok();
1703}
1704
1705bool MPSolver::ExportModelAsMpsFormat(bool fixed_format, bool obfuscate,
1706 std::string* model_str) const {
1707 MPModelProto proto;
1709 MPModelExportOptions options;
1710 options.obfuscate = obfuscate;
1711 const auto status_or =
1713 *model_str = status_or.value_or("");
1714 return status_or.ok();
1715}
1716
1717void MPSolver::SetHint(std::vector<std::pair<const MPVariable*, double>> hint) {
1718 for (const auto& var_value_pair : hint) {
1719 CHECK(OwnsVariable(var_value_pair.first))
1720 << "hint variable does not belong to this solver";
1721 }
1722 solution_hint_ = std::move(hint);
1723}
1724
1725void MPSolver::GenerateVariableNameIndex() const {
1726 if (variable_name_to_index_) return;
1727 variable_name_to_index_ = absl::flat_hash_map<std::string, int>();
1728 for (const MPVariable* const var : variables_) {
1729 gtl::InsertOrDie(&*variable_name_to_index_, var->name(), var->index());
1730 }
1731}
1732
1733void MPSolver::GenerateConstraintNameIndex() const {
1734 if (constraint_name_to_index_) return;
1735 constraint_name_to_index_ = absl::flat_hash_map<std::string, int>();
1736 for (const MPConstraint* const cst : constraints_) {
1737 gtl::InsertOrDie(&*constraint_name_to_index_, cst->name(), cst->index());
1738 }
1739}
1740
1741bool MPSolver::NextSolution() { return interface_->NextSolution(); }
1742
1744 interface_->SetCallback(mp_callback);
1745}
1746
1748 return interface_->SupportsCallbacks();
1749}
1750
1751// Global counters.
1752absl::Mutex MPSolver::global_count_mutex_(absl::kConstInit);
1753int64_t MPSolver::global_num_variables_ = 0;
1754int64_t MPSolver::global_num_constraints_ = 0;
1755
1756// static
1758 // Why not ReaderMutexLock? See go/totw/197#when-are-shared-locks-useful.
1759 absl::MutexLock lock(&global_count_mutex_);
1760 return global_num_variables_;
1761}
1762
1763// static
1765 // Why not ReaderMutexLock? See go/totw/197#when-are-shared-locks-useful.
1766 absl::MutexLock lock(&global_count_mutex_);
1767 return global_num_constraints_;
1768}
1769
1770bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status) {
1771 switch (status) {
1772 // Cases that don't yield an RPC error when they happen on the server.
1773 case MPSOLVER_OPTIMAL:
1774 case MPSOLVER_FEASIBLE:
1775 case MPSOLVER_INFEASIBLE:
1776 case MPSOLVER_NOT_SOLVED:
1777 case MPSOLVER_UNBOUNDED:
1778 case MPSOLVER_ABNORMAL:
1779 case MPSOLVER_UNKNOWN_STATUS:
1780 return false;
1781 // Cases that should never happen with the linear solver server. We prefer
1782 // to consider those as "not RPC errors".
1783 case MPSOLVER_MODEL_IS_VALID:
1784 case MPSOLVER_CANCELLED_BY_USER:
1785 return false;
1786 // Cases that yield an RPC error when they happen on the server.
1787 case MPSOLVER_MODEL_INVALID:
1788 case MPSOLVER_MODEL_INVALID_SOLUTION_HINT:
1789 case MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS:
1790 case MPSOLVER_SOLVER_TYPE_UNAVAILABLE:
1791 case MPSOLVER_INCOMPATIBLE_OPTIONS:
1792 return true;
1793 }
1794 LOG(DFATAL)
1795 << "MPSolverResponseStatusIsRpcError() called with invalid status "
1796 << "(value: " << status << ")";
1797 return false;
1798}
1799
1800// ---------- MPSolverInterface ----------
1801
1803
1804// TODO(user): Initialize objective value and bound to +/- inf (depending on
1805// optimization direction).
1807 : solver_(solver),
1808 sync_status_(MODEL_SYNCHRONIZED),
1809 result_status_(MPSolver::NOT_SOLVED),
1810 maximize_(false),
1811 last_constraint_index_(0),
1812 last_variable_index_(0),
1813 objective_value_(0.0),
1814 best_objective_bound_(0.0),
1815 quiet_(true) {}
1816
1818
1819void MPSolverInterface::Write(const std::string& filename) {
1820 LOG(WARNING) << "Writing model not implemented in this solver interface.";
1821}
1822
1824 switch (sync_status_) {
1825 case MUST_RELOAD: {
1829
1830 last_constraint_index_ = solver_->constraints_.size();
1831 last_variable_index_ = solver_->variables_.size();
1833 break;
1834 }
1835 case MODEL_SYNCHRONIZED: {
1836 // Everything has already been extracted.
1837 DCHECK_EQ(last_constraint_index_, solver_->constraints_.size());
1838 DCHECK_EQ(last_variable_index_, solver_->variables_.size());
1839 break;
1840 }
1841 case SOLUTION_SYNCHRONIZED: {
1842 // Nothing has changed since last solve.
1843 DCHECK_EQ(last_constraint_index_, solver_->constraints_.size());
1844 DCHECK_EQ(last_variable_index_, solver_->variables_.size());
1845 break;
1846 }
1847 }
1848}
1849
1850// TODO(user): remove this method.
1855 solver_->variable_is_extracted_.assign(solver_->variables_.size(), false);
1856 solver_->constraint_is_extracted_.assign(solver_->constraints_.size(), false);
1857}
1858
1861 LOG(DFATAL)
1862 << "The model has been changed since the solution was last computed."
1863 << " MPSolverInterface::sync_status_ = " << sync_status_;
1864 return false;
1865 }
1866 return true;
1867}
1868
1869// Default version that can be overwritten by a solver-specific
1870// version to accommodate for the quirks of each solver.
1874 LOG(DFATAL) << "No solution exists. MPSolverInterface::result_status_ = "
1875 << result_status_;
1876 return false;
1877 }
1878 return true;
1879}
1880
1882 if (!CheckSolutionIsSynchronizedAndExists()) return 0;
1883 return objective_value_;
1884}
1885
1887 const double trivial_worst_bound =
1888 maximize_ ? -std::numeric_limits<double>::infinity()
1889 : std::numeric_limits<double>::infinity();
1890 if (!IsMIP()) {
1891 VLOG(1) << "Best objective bound only available for discrete problems.";
1892 return trivial_worst_bound;
1893 }
1895 return trivial_worst_bound;
1896 }
1897 // Special case for empty model.
1898 if (solver_->variables_.empty() && solver_->constraints_.empty()) {
1899 return solver_->Objective().offset();
1900 }
1901 return best_objective_bound_;
1902}
1903
1907 }
1908}
1909
1911 // Override this method in interfaces that actually support it.
1912 LOG(DFATAL) << "ComputeExactConditionNumber not implemented for "
1913 << ProtoEnumToString<MPModelRequest::SolverType>(
1914 static_cast<MPModelRequest::SolverType>(
1915 solver_->ProblemType()));
1916 return 0.0;
1917}
1918
1920 // TODO(user): Overhaul the code that sets parameters to enable changing
1921 // GLOP parameters without issuing warnings.
1922 // By default, we let GLOP keep its own default tolerance, much more accurate
1923 // than for the rest of the solvers.
1924 //
1929 }
1931 // TODO(user): In the future, we could distinguish between the
1932 // algorithm to solve the root LP and the algorithm to solve node
1933 // LPs. Not sure if underlying solvers support it.
1937 }
1938}
1939
1944 }
1945}
1946
1949 LOG(WARNING) << "Trying to set an unsupported parameter: " << param << ".";
1950}
1953 LOG(WARNING) << "Trying to set an unsupported parameter: " << param << ".";
1954}
1956 MPSolverParameters::DoubleParam param, double value) {
1957 LOG(WARNING) << "Trying to set a supported parameter: " << param
1958 << " to an unsupported value: " << value;
1959}
1962 LOG(WARNING) << "Trying to set a supported parameter: " << param
1963 << " to an unsupported value: " << value;
1964}
1965
1966absl::Status MPSolverInterface::SetNumThreads(int num_threads) {
1967 return absl::UnimplementedError(
1968 absl::StrFormat("SetNumThreads() not supported by %s.", SolverVersion()));
1969}
1970
1972 const std::string& parameters) {
1973 if (parameters.empty()) {
1974 return true;
1975 }
1976
1977 LOG(WARNING) << "SetSolverSpecificParametersAsString() not supported by "
1978 << SolverVersion();
1979 return false;
1980}
1981
1982// ---------- MPSolverParameters ----------
1983
1985// For the primal and dual tolerances, choose the same default as CLP and GLPK.
1994
1999
2000// The constructor sets all parameters to their default value.
2002 : relative_mip_gap_value_(kDefaultRelativeMipGap),
2003 primal_tolerance_value_(kDefaultPrimalTolerance),
2004 dual_tolerance_value_(kDefaultDualTolerance),
2005 presolve_value_(kDefaultPresolve),
2006 scaling_value_(kDefaultIntegerParamValue),
2007 lp_algorithm_value_(kDefaultIntegerParamValue),
2008 incrementality_value_(kDefaultIncrementality),
2009 lp_algorithm_is_default_(true) {}
2010
2012 double value) {
2013 switch (param) {
2014 case RELATIVE_MIP_GAP: {
2015 relative_mip_gap_value_ = value;
2016 break;
2017 }
2018 case PRIMAL_TOLERANCE: {
2019 primal_tolerance_value_ = value;
2020 break;
2021 }
2022 case DUAL_TOLERANCE: {
2023 dual_tolerance_value_ = value;
2024 break;
2025 }
2026 default: {
2027 LOG(ERROR) << "Trying to set an unknown parameter: " << param << ".";
2028 }
2029 }
2030}
2031
2033 int value) {
2034 switch (param) {
2035 case PRESOLVE: {
2036 if (value != PRESOLVE_OFF && value != PRESOLVE_ON) {
2037 LOG(ERROR) << "Trying to set a supported parameter: " << param
2038 << " to an unknown value: " << value;
2039 }
2040 presolve_value_ = value;
2041 break;
2042 }
2043 case SCALING: {
2044 if (value != SCALING_OFF && value != SCALING_ON) {
2045 LOG(ERROR) << "Trying to set a supported parameter: " << param
2046 << " to an unknown value: " << value;
2047 }
2048 scaling_value_ = value;
2049 break;
2050 }
2051 case LP_ALGORITHM: {
2052 if (value != DUAL && value != PRIMAL && value != BARRIER) {
2053 LOG(ERROR) << "Trying to set a supported parameter: " << param
2054 << " to an unknown value: " << value;
2055 }
2056 lp_algorithm_value_ = value;
2057 lp_algorithm_is_default_ = false;
2058 break;
2059 }
2060 case INCREMENTALITY: {
2062 LOG(ERROR) << "Trying to set a supported parameter: " << param
2063 << " to an unknown value: " << value;
2064 }
2065 incrementality_value_ = value;
2066 break;
2067 }
2068 default: {
2069 LOG(ERROR) << "Trying to set an unknown parameter: " << param << ".";
2070 }
2071 }
2072}
2073
2076 switch (param) {
2077 case RELATIVE_MIP_GAP: {
2078 relative_mip_gap_value_ = kDefaultRelativeMipGap;
2079 break;
2080 }
2081 case PRIMAL_TOLERANCE: {
2082 primal_tolerance_value_ = kDefaultPrimalTolerance;
2083 break;
2084 }
2085 case DUAL_TOLERANCE: {
2086 dual_tolerance_value_ = kDefaultDualTolerance;
2087 break;
2088 }
2089 default: {
2090 LOG(ERROR) << "Trying to reset an unknown parameter: " << param << ".";
2091 }
2092 }
2093}
2094
2097 switch (param) {
2098 case PRESOLVE: {
2099 presolve_value_ = kDefaultPresolve;
2100 break;
2101 }
2102 case SCALING: {
2103 scaling_value_ = kDefaultIntegerParamValue;
2104 break;
2105 }
2106 case LP_ALGORITHM: {
2107 lp_algorithm_is_default_ = true;
2108 break;
2109 }
2110 case INCREMENTALITY: {
2111 incrementality_value_ = kDefaultIncrementality;
2112 break;
2113 }
2114 default: {
2115 LOG(ERROR) << "Trying to reset an unknown parameter: " << param << ".";
2116 }
2117 }
2118}
2119
2128}
2129
2131 MPSolverParameters::DoubleParam param) const {
2132 switch (param) {
2133 case RELATIVE_MIP_GAP: {
2134 return relative_mip_gap_value_;
2135 }
2136 case PRIMAL_TOLERANCE: {
2137 return primal_tolerance_value_;
2138 }
2139 case DUAL_TOLERANCE: {
2140 return dual_tolerance_value_;
2141 }
2142 default: {
2143 LOG(ERROR) << "Trying to get an unknown parameter: " << param << ".";
2145 }
2146 }
2147}
2148
2151 switch (param) {
2152 case PRESOLVE: {
2153 return presolve_value_;
2154 }
2155 case LP_ALGORITHM: {
2156 if (lp_algorithm_is_default_) return kDefaultIntegerParamValue;
2157 return lp_algorithm_value_;
2158 }
2159 case INCREMENTALITY: {
2160 return incrementality_value_;
2161 }
2162 case SCALING: {
2163 return scaling_value_;
2164 }
2165 default: {
2166 LOG(ERROR) << "Trying to get an unknown parameter: " << param << ".";
2168 }
2169 }
2170}
2171
2172} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define DLOG_IF(severity, condition)
Definition: base/logging.h:883
#define LOG_IF(severity, condition)
Definition: base/logging.h:479
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:893
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:892
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:895
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
#define VLOG(verboselevel)
Definition: base/logging.h:984
void Add(const FpNumber &value)
Definition: accurate_sum.h:29
LinearExpr models a quantity that is linear in the decision variables (MPVariable) of an optimization...
Definition: linear_expr.h:114
const absl::flat_hash_map< const MPVariable *, double > & terms() const
Definition: linear_expr.h:143
An expression of the form:
Definition: linear_expr.h:192
const LinearExpr & linear_expr() const
Definition: linear_expr.h:206
The class for constraints of a Mathematical Programming (MP) model.
void SetBounds(double lb, double ub)
Sets both the lower and upper bounds.
const std::string & name() const
Returns the name of the constraint.
void SetCoefficient(const MPVariable *const var, double coeff)
Sets the coefficient of the variable on the constraint.
double GetCoefficient(const MPVariable *const var) const
Gets the coefficient of a given variable on the constraint (which is 0 if the variable does not appea...
double ub() const
Returns the upper bound.
const MPVariable * indicator_variable() const
void Clear()
Clears all variables and coefficients. Does not clear the bounds.
bool is_lazy() const
Advanced usage: returns true if the constraint is "lazy" (see below).
void set_is_lazy(bool laziness)
Advanced usage: sets the constraint "laziness".
double lb() const
Returns the lower bound.
MPSolver::BasisStatus basis_status() const
Advanced usage: returns the basis status of the constraint.
double dual_value() const
Advanced usage: returns the dual value of the constraint in the current solution (only available for ...
A class to express a linear objective.
void SetCoefficient(const MPVariable *const var, double coeff)
Sets the coefficient of the variable in the objective.
double GetCoefficient(const MPVariable *const var) const
Gets the coefficient of a given variable in the objective.
void SetOffset(double value)
Sets the constant term in the objective.
bool maximization() const
Is the optimization direction set to maximize?
void OptimizeLinearExpr(const LinearExpr &linear_expr, bool is_maximization)
Resets the current objective to take the value of linear_expr, and sets the objective direction to ma...
void AddLinearExpr(const LinearExpr &linear_expr)
Adds linear_expr to the current objective, does not change the direction.
double Value() const
Returns the objective value of the best solution found so far.
double offset() const
Gets the constant term in the objective.
double BestBound() const
Returns the best objective bound.
bool minimization() const
Is the optimization direction set to minimize?
void Clear()
Clears the offset, all variables and coefficients, and the optimization direction.
void SetMinimization()
Sets the optimization direction to minimize.
void SetOptimizationDirection(bool maximize)
Sets the optimization direction (maximize: true or minimize: false).
This mathematical programming (MP) solver class is the main class though which users build and solve ...
void FillSolutionResponseProto(MPSolutionResponse *response) const
Encodes the current solution in a solution response protocol buffer.
int NumConstraints() const
Returns the number of constraints.
static int64_t global_num_constraints()
static OptimizationProblemType ParseSolverTypeOrDie(const std::string &solver_id)
Parses the name of the solver and returns the correct optimization type or dies.
void MakeBoolVarArray(int nb, const std::string &name, std::vector< MPVariable * > *vars)
Creates an array of boolean variables.
bool VerifySolution(double tolerance, bool log_errors) const
Advanced usage: Verifies the correctness of the solution.
void Reset()
Advanced usage: resets extracted model to solve from scratch.
MPConstraint * constraint(int index) const
Returns the constraint at the given index.
MPVariable * LookupVariableOrNull(const std::string &var_name) const
Looks up a variable by name, and returns nullptr if it does not exist.
int64_t iterations() const
Returns the number of simplex iterations.
void SetStartingLpBasis(const std::vector< MPSolver::BasisStatus > &variable_statuses, const std::vector< MPSolver::BasisStatus > &constraint_statuses)
Advanced usage: Incrementality.
static bool SupportsProblemType(OptimizationProblemType problem_type)
Whether the given problem type is supported (this will depend on the targets that you linked).
static MPSolver * CreateSolver(const std::string &solver_id)
Recommended factory method to create a MPSolver instance, especially in non C++ languages.
MPVariable * MakeBoolVar(const std::string &name)
Creates a boolean variable.
void SetHint(std::vector< std::pair< const MPVariable *, double > > hint)
Sets a hint for solution.
double ComputeExactConditionNumber() const
Advanced usage: computes the exact condition number of the current scaled basis: L1norm(B) * L1norm(i...
ResultStatus
The status of solving the problem.
@ FEASIBLE
feasible, or stopped by limit.
@ NOT_SOLVED
not been solved yet.
@ INFEASIBLE
proven infeasible.
@ UNBOUNDED
proven unbounded.
@ ABNORMAL
abnormal, i.e., error of some kind.
@ MODEL_INVALID
the model is trivially invalid (NaN coefficients, etc).
static int64_t global_num_variables()
void MakeNumVarArray(int nb, double lb, double ub, const std::string &name, std::vector< MPVariable * > *vars)
Creates an array of continuous variables.
void MakeVarArray(int nb, double lb, double ub, bool integer, const std::string &name_prefix, std::vector< MPVariable * > *vars)
Creates an array of variables.
void * underlying_solver()
Advanced usage: returns the underlying solver.
OptimizationProblemType
The type of problems (LP or MIP) that will be solved and the underlying solver (GLOP,...
bool SetSolverSpecificParametersAsString(const std::string &parameters)
Advanced usage: pass solver specific parameters in text format.
absl::Status LoadSolutionFromProto(const MPSolutionResponse &response, double tolerance=std::numeric_limits< double >::infinity())
Load a solution encoded in a protocol buffer onto this solver for easy access via the MPSolver interf...
absl::Status SetNumThreads(int num_threads)
Sets the number of threads to use by the underlying solver.
const std::string & Name() const
Returns the name of the model set at construction.
std::string SolverVersion() const
Returns a string describing the underlying solver and its version.
void ExportModelToProto(MPModelProto *output_model) const
Exports model to protocol buffer.
void MakeIntVarArray(int nb, double lb, double ub, const std::string &name, std::vector< MPVariable * > *vars)
Creates an array of integer variables.
std::vector< double > ComputeConstraintActivities() const
Advanced usage: compute the "activities" of all constraints, which are the sums of their linear terms...
static double infinity()
Infinity.
static bool ParseSolverType(absl::string_view solver_id, OptimizationProblemType *type)
Parses the name of the solver.
int NumVariables() const
Returns the number of variables.
absl::Status ClampSolutionWithinBounds()
Resets values of out of bound variables to the corresponding bound and returns an error if any of the...
bool OwnsVariable(const MPVariable *var) const
int64_t nodes() const
Returns the number of branch-and-bound nodes evaluated during the solve.
void Clear()
Clears the objective (including the optimization direction), all variables and constraints.
MPVariable * variable(int index) const
Returns the variable at position index.
bool ExportModelAsLpFormat(bool obfuscate, std::string *model_str) const
Shortcuts to the homonymous MPModelProtoExporter methods, via exporting to a MPModelProto with Export...
void Write(const std::string &file_name)
Writes the model using the solver internal write function.
static void SolveWithProto(const MPModelRequest &model_request, MPSolutionResponse *response, std::atomic< bool > *interrupt=nullptr)
Solves the model encoded by a MPModelRequest protocol buffer and fills the solution encoded as a MPSo...
MPConstraint * MakeRowConstraint()
Creates a constraint with -infinity and +infinity bounds.
void SetCallback(MPCallback *mp_callback)
MPSolverResponseStatus LoadModelFromProto(const MPModelProto &input_model, std::string *error_message)
Loads model from protocol buffer.
bool OutputIsEnabled() const
Controls (or queries) the amount of output produced by the underlying solver.
bool ExportModelAsMpsFormat(bool fixed_format, bool obfuscate, std::string *model_str) const
ABSL_MUST_USE_RESULT bool NextSolution()
Some solvers (MIP only, not LP) can produce multiple solutions to the problem.
MPVariable * MakeVar(double lb, double ub, bool integer, const std::string &name)
Creates a variable with the given bounds, integrality requirement and name.
MPConstraint * LookupConstraintOrNull(const std::string &constraint_name) const
Looks up a constraint by name, and returns nullptr if it does not exist.
MPVariable * MakeNumVar(double lb, double ub, const std::string &name)
Creates a continuous variable.
bool InterruptSolve()
Interrupts the Solve() execution to terminate processing if possible.
MPVariable * MakeIntVar(double lb, double ub, const std::string &name)
Creates an integer variable.
const MPObjective & Objective() const
Returns the objective object.
MPSolver(const std::string &name, OptimizationProblemType problem_type)
Create a solver with the given name and underlying solver backend.
ResultStatus Solve()
Solves the problem using the default parameter values.
void EnableOutput()
Enables solver logging.
void SuppressOutput()
Suppresses solver logging.
static bool SolverTypeSupportsInterruption(const MPModelRequest::SolverType solver)
MPSolverResponseStatus LoadModelFromProtoWithUniqueNamesOrDie(const MPModelProto &input_model, std::string *error_message)
Loads model from protocol buffer.
MPObjective * MutableObjective()
Returns the mutable objective object.
virtual OptimizationProblemType ProblemType() const
Returns the optimization problem type set at construction.
BasisStatus
Advanced usage: possible basis status values for a variable and the slack variable of a linear constr...
void SetTimeLimit(absl::Duration time_limit)
virtual void SetLpAlgorithm(int value)=0
virtual void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param, int value)
void SetUnsupportedDoubleParam(MPSolverParameters::DoubleParam param)
void SetMIPParameters(const MPSolverParameters &param)
virtual bool IsContinuous() const =0
virtual double ComputeExactConditionNumber() const
virtual void Write(const std::string &filename)
MPSolverInterface(MPSolver *const solver)
bool constraint_is_extracted(int ct_index) const
virtual void SetVariableBounds(int index, double lb, double ub)=0
virtual void SetPrimalTolerance(double value)=0
virtual void BranchingPriorityChangedForVariable(int var_index)
virtual void SetRelativeMipGap(double value)=0
virtual void SetOptimizationDirection(bool maximize)=0
virtual bool SetSolverSpecificParametersAsString(const std::string &parameters)
virtual MPSolver::BasisStatus column_status(int variable_index) const =0
virtual MPSolver::BasisStatus row_status(int constraint_index) const =0
virtual std::string SolverVersion() const =0
virtual absl::Status SetNumThreads(int num_threads)
virtual void ClearConstraint(MPConstraint *const constraint)=0
virtual void SetObjectiveOffset(double value)=0
virtual void SetVariableInteger(int index, bool integer)=0
bool variable_is_extracted(int var_index) const
virtual void SetDualTolerance(double value)=0
virtual void SetPresolveMode(int value)=0
virtual void SetUnsupportedIntegerParam(MPSolverParameters::IntegerParam param)
virtual void SetCoefficient(MPConstraint *const constraint, const MPVariable *const variable, double new_value, double old_value)=0
virtual void SetObjectiveCoefficient(const MPVariable *const variable, double coefficient)=0
void SetDoubleParamToUnsupportedValue(MPSolverParameters::DoubleParam param, double value)
virtual void SetConstraintBounds(int index, double lb, double ub)=0
void SetCommonParameters(const MPSolverParameters &param)
This class stores parameter settings for LP and MIP solvers.
void ResetIntegerParam(MPSolverParameters::IntegerParam param)
Sets an integer parameter to its default value (default value defined in MPSolverParameters if it exi...
void SetDoubleParam(MPSolverParameters::DoubleParam param, double value)
Sets a double parameter to a specific value.
IncrementalityValues
Advanced usage: Incrementality options.
@ INCREMENTALITY_OFF
Start solve from scratch.
@ INCREMENTALITY_ON
Reuse results from previous solve as much as the underlying solver allows.
static const IncrementalityValues kDefaultIncrementality
void Reset()
Sets all parameters to their default value.
DoubleParam
Enumeration of parameters that take continuous values.
@ DUAL_TOLERANCE
Advanced usage: tolerance for dual feasibility of basic solutions.
@ PRIMAL_TOLERANCE
Advanced usage: tolerance for primal feasibility of basic solutions.
@ RELATIVE_MIP_GAP
Limit for relative MIP gap.
static const PresolveValues kDefaultPresolve
double GetDoubleParam(MPSolverParameters::DoubleParam param) const
Returns the value of a double parameter.
IntegerParam
Enumeration of parameters that take integer or categorical values.
@ LP_ALGORITHM
Algorithm to solve linear programs.
@ SCALING
Advanced usage: enable or disable matrix scaling.
@ PRESOLVE
Advanced usage: presolve mode.
@ INCREMENTALITY
Advanced usage: incrementality from one solve to the next.
PresolveValues
For each categorical parameter, enumeration of possible values.
void SetIntegerParam(MPSolverParameters::IntegerParam param, int value)
Sets a integer parameter to a specific value.
int GetIntegerParam(MPSolverParameters::IntegerParam param) const
Returns the value of an integer parameter.
MPSolverParameters()
The constructor sets all parameters to their default value.
void ResetDoubleParam(MPSolverParameters::DoubleParam param)
Sets a double parameter to its default value (default value defined in MPSolverParameters if it exist...
The class for variables of a Mathematical Programming (MP) model.
void SetBounds(double lb, double ub)
Sets both the lower and upper bounds.
double unrounded_solution_value() const
Advanced usage: unrounded solution value.
const std::string & name() const
Returns the name of the variable.
void set_solution_value(double value)
void SetBranchingPriority(int priority)
double ub() const
Returns the upper bound.
double reduced_cost() const
Advanced usage: returns the reduced cost of the variable in the current solution (only available for ...
void SetInteger(bool integer)
Sets the integrality requirement of the variable.
bool integer() const
Returns the integrality requirement of the variable.
int index() const
Returns the index of the variable in the MPSolver::variables_.
double lb() const
Returns the lower bound.
double solution_value() const
Returns the value of the variable in the current solution.
MPSolver::BasisStatus basis_status() const
Advanced usage: returns the basis status of the variable in the current solution (only available for ...
virtual std::string name() const
Object naming.
void Schedule(std::function< void()> closure)
Definition: threadpool.cc:77
SatParameters parameters
CpModelProto proto
SharedResponseManager * response
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Status status
Definition: g_gurobi.cc:35
absl::string_view name
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
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
const int WARNING
Definition: log_severity.h:31
const int INFO
Definition: log_severity.h:31
const int ERROR
Definition: log_severity.h:32
const int FATAL
Definition: log_severity.h:32
Definition: cleanup.h:22
void STLDeleteElements(T *container)
Definition: stl_util.h:372
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:154
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition: integer.h:1683
Collection of objects used to extend the Constraint Solver library.
MPSolverInterface * BuildGurobiInterface(bool mip, MPSolver *const solver)
MPSolverInterface * BuildSCIPInterface(MPSolver *const solver)
MPSolverInterface * BuildBopInterface(MPSolver *const solver)
constexpr double kDefaultPrimalTolerance
const absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
bool SolverTypeIsMip(MPModelRequest::SolverType solver_type)
MPSolverInterface * BuildCBCInterface(MPSolver *const solver)
bool AreWithinAbsoluteOrRelativeTolerances(FloatType x, FloatType y, FloatType relative_tolerance, FloatType absolute_tolerance)
Definition: fp_utils.h:124
absl::StatusOr< std::string > ExportModelAsMpsFormat(const MPModelProto &model, const MPModelExportOptions &options)
Outputs the current model (variables, constraints, objective) as a string encoded in MPS file format,...
bool AbslParseFlag(const absl::string_view text, MPSolver::OptimizationProblemType *solver_type, std::string *error)
absl::optional< LazyMutableCopy< MPModelProto > > ExtractValidMPModelOrPopulateResponseStatus(const MPModelRequest &request, MPSolutionResponse *response)
If the model is valid and non-empty, returns it (possibly after extracting the model_delta).
std::string FindErrorInMPModelProto(const MPModelProto &model, double abs_value_threshold, const bool accept_trivially_infeasible_bounds)
Returns an empty string iff the model is valid and not trivially infeasible.
MPSolverInterface * BuildSatInterface(MPSolver *const solver)
MPSolverInterface * BuildCLPInterface(MPSolver *const solver)
MPSolverInterface * BuildPdlpInterface(MPSolver *const solver)
constexpr NamedOptimizationProblemType kOptimizationProblemTypeNames[]
MPSolverInterface * BuildGLOPInterface(MPSolver *const solver)
bool GurobiIsCorrectlyInstalled()
Definition: environment.cc:31
absl::StatusOr< std::string > ExportModelAsLpFormat(const MPModelProto &model, const MPModelExportOptions &options)
Outputs the current model (variables, constraints, objective) as a string encoded in the so-called "C...
bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status)
STL namespace.
const bool maximize_
Definition: search.cc:2559
bool obfuscate
Obfuscates variable and constraint names.
const double coeff